Manages {@code AppContext#getRootProject}
+ * current root project state, in the case of adding and removing 'contribution' mixin.
+ * Contribution mixin itself is 'synthetic' one and needed only for managing plugin specific project attributes.
+ *
+ * @author Stephane Tournie
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+@Extension(title = "Contributor", version = "1.0.0")
+public class ContributionExtension {
+
+ @Inject
+ @SuppressWarnings("unused")
+ public ContributionExtension(ContributeResources resources,
+ ContributionMixinProvider contributionMixinProvider) {
+ resources.contributeCss().ensureInjected();
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/ContributionMixinProvider.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/ContributionMixinProvider.java
new file mode 100644
index 0000000000..6215315ca5
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/ContributionMixinProvider.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client;
+
+import org.eclipse.che.plugin.pullrequest.client.parts.contribute.ContributePartPresenter;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsService;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.common.base.Optional;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.FunctionException;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.api.promises.client.js.Promises;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.event.SelectionChangedEvent;
+import org.eclipse.che.ide.api.event.SelectionChangedHandler;
+import org.eclipse.che.ide.api.factory.FactoryAcceptedEvent;
+import org.eclipse.che.ide.api.factory.FactoryAcceptedHandler;
+import org.eclipse.che.ide.api.parts.PartStack;
+import org.eclipse.che.ide.api.parts.WorkspaceAgent;
+import org.eclipse.che.ide.api.project.MutableProjectConfig;
+import org.eclipse.che.ide.api.resources.Project;
+import org.eclipse.che.ide.api.workspace.WorkspaceReadyEvent;
+
+import static org.eclipse.che.plugin.pullrequest.shared.ContributionProjectTypeConstants.CONTRIBUTE_TO_BRANCH_VARIABLE_NAME;
+import static org.eclipse.che.plugin.pullrequest.shared.ContributionProjectTypeConstants.CONTRIBUTION_PROJECT_TYPE_ID;
+import static java.util.Collections.singletonList;
+import static org.eclipse.che.ide.api.constraints.Constraints.FIRST;
+import static org.eclipse.che.ide.api.parts.PartStackType.TOOLING;
+
+/**
+ * Responsible for setting up contribution mixin for the currently selected project in application context.
+ *
+ * @author Vlad Zhukovskyi
+ * @since 5.0.0
+ */
+@Singleton
+public class ContributionMixinProvider {
+
+ private final EventBus eventBus;
+ private final AppContext appContext;
+ private final WorkspaceAgent workspaceAgent;
+ private final ContributePartPresenter contributePart;
+ private final WorkflowExecutor workflowExecutor;
+ private final VcsServiceProvider vcsServiceProvider;
+ private final VcsHostingServiceProvider vcsHostingServiceProvider;
+
+ private HandlerRegistration handlerRegistration;
+
+ private Project lastSelected;
+
+ @Inject
+ public ContributionMixinProvider(EventBus eventBus,
+ AppContext appContext,
+ WorkspaceAgent workspaceAgent,
+ ContributePartPresenter contributePart,
+ WorkflowExecutor workflowExecutor,
+ VcsServiceProvider vcsServiceProvider,
+ VcsHostingServiceProvider vcsHostingServiceProvider) {
+ this.eventBus = eventBus;
+ this.appContext = appContext;
+ this.workspaceAgent = workspaceAgent;
+ this.contributePart = contributePart;
+ this.workflowExecutor = workflowExecutor;
+ this.vcsServiceProvider = vcsServiceProvider;
+ this.vcsHostingServiceProvider = vcsHostingServiceProvider;
+
+ if (appContext.getFactory() != null) {
+ handlerRegistration = eventBus.addHandler(FactoryAcceptedEvent.TYPE, new FactoryAcceptedHandler() {
+ @Override
+ public void onFactoryAccepted(FactoryAcceptedEvent event) {
+ handlerRegistration.removeHandler();
+
+ subscribeToSelectionChangedEvent();
+ }
+ });
+ } else {
+ handlerRegistration = eventBus.addHandler(WorkspaceReadyEvent.getType(), new WorkspaceReadyEvent.WorkspaceReadyHandler() {
+ @Override
+ public void onWorkspaceReady(WorkspaceReadyEvent event) {
+ handlerRegistration.removeHandler();
+
+ subscribeToSelectionChangedEvent();
+ }
+ });
+ }
+ }
+
+ private void subscribeToSelectionChangedEvent() {
+ eventBus.addHandler(SelectionChangedEvent.TYPE, new SelectionChangedHandler() {
+ @Override
+ public void onSelectionChanged(SelectionChangedEvent event) {
+ processCurrentProject();
+ }
+ });
+ }
+
+ void processCurrentProject() {
+ final Project rootProject = appContext.getRootProject();
+
+ if (lastSelected != null && lastSelected.equals(rootProject)) {
+ return;
+ }
+
+ final PartStack toolingPartStack = workspaceAgent.getPartStack(TOOLING);
+
+ if (rootProject == null) {
+
+ if (toolingPartStack.containsPart(contributePart)) {
+ invalidateContext(lastSelected);
+ hidePart();
+ }
+ } else if (hasVcsService(rootProject)) {
+
+ if (hasContributionMixin(rootProject)) {
+
+ vcsHostingServiceProvider.getVcsHostingService(rootProject).then(new Operation() {
+ @Override
+ public void apply(VcsHostingService vcsHostingService) throws OperationException {
+ workflowExecutor.init(vcsHostingService, rootProject);
+ addPart(toolingPartStack);
+ }
+ });
+ } else {
+ vcsHostingServiceProvider.getVcsHostingService(rootProject)
+ .then(new Operation() {
+ @Override
+ public void apply(final VcsHostingService vcsHostingService)
+ throws OperationException {
+ addMixin(rootProject)
+ .then(new Operation() {
+ @Override
+ public void apply(Project project) throws OperationException {
+ workflowExecutor.init(vcsHostingService, project);
+ addPart(toolingPartStack);
+
+ lastSelected = project;
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(final PromiseError error) throws OperationException {
+ invalidateContext(rootProject);
+ hidePart();
+ }
+ });
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(final PromiseError error) throws OperationException {
+ invalidateContext(rootProject);
+ hidePart();
+ }
+ });
+ }
+
+ } else {
+ invalidateContext(rootProject);
+ hidePart();
+ }
+
+ lastSelected = rootProject;
+ }
+
+ private void invalidateContext(Project project) {
+ final Optional context = workflowExecutor.getContext(project.getName());
+ if (context.isPresent()) {
+ workflowExecutor.invalidateContext(context.get().getProject());
+ }
+ }
+
+ private void hidePart() {
+ workspaceAgent.hidePart(contributePart);
+ workspaceAgent.removePart(contributePart);
+ }
+
+ private void addPart(PartStack partStack) {
+ if (!partStack.containsPart(contributePart)) {
+ partStack.addPart(contributePart, FIRST);
+ }
+ }
+
+ private boolean hasVcsService(Project project) {
+ return vcsServiceProvider.getVcsService(project) != null;
+ }
+
+ private boolean hasContributionMixin(Project project) {
+ return project.getMixins().contains(CONTRIBUTION_PROJECT_TYPE_ID);
+ }
+
+ private Promise addMixin(final Project project) {
+ final VcsService vcsService = vcsServiceProvider.getVcsService(project);
+
+ if (vcsService == null || project.getMixins().contains(CONTRIBUTION_PROJECT_TYPE_ID)) {
+ return Promises.resolve(project);
+ }
+
+ return vcsService.getBranchName(project)
+ .thenPromise(new Function>() {
+ @Override
+ public Promise apply(String branchName) throws FunctionException {
+ MutableProjectConfig mutableConfig = new MutableProjectConfig(project);
+ mutableConfig.getMixins().add(CONTRIBUTION_PROJECT_TYPE_ID);
+ mutableConfig.getAttributes().put(CONTRIBUTE_TO_BRANCH_VARIABLE_NAME,
+ singletonList(branchName));
+
+ return project.update().withBody(mutableConfig).send();
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitPresenter.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitPresenter.java
new file mode 100644
index 0000000000..051addf14c
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitPresenter.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.commit;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.api.resources.Project;
+
+import javax.validation.constraints.NotNull;
+
+import static org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitPresenter.CommitActionHandler.CommitAction.CANCEL;
+import static org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitPresenter.CommitActionHandler.CommitAction.CONTINUE;
+import static org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitPresenter.CommitActionHandler.CommitAction.OK;
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+import static org.eclipse.che.ide.ext.git.client.GitUtil.isUnderGit;
+
+/**
+ * This presenter provides base functionality to commit project changes or not before cloning or generating a factory url.
+ *
+ * @author Kevin Pollet
+ */
+public class CommitPresenter implements CommitView.ActionDelegate {
+
+ private final CommitView view;
+ private final AppContext appContext;
+ private final VcsServiceProvider vcsServiceProvider;
+ private final NotificationManager notificationManager;
+ private CommitActionHandler handler;
+
+ @Inject
+ public CommitPresenter(@NotNull final CommitView view,
+ @NotNull final AppContext appContext,
+ @NotNull final VcsServiceProvider vcsServiceProvider,
+ @NotNull final NotificationManager notificationManager) {
+ this.view = view;
+ this.appContext = appContext;
+ this.vcsServiceProvider = vcsServiceProvider;
+ this.notificationManager = notificationManager;
+
+ this.view.setDelegate(this);
+ }
+
+ /**
+ * Opens the {@link CommitView}.
+ *
+ * @param commitDescription
+ * the default commit description.
+ */
+ public void showView(@NotNull String commitDescription) {
+ view.show(commitDescription);
+ }
+
+ /**
+ * Sets the {@link CommitPresenter.CommitActionHandler} called after the ok or
+ * continue action is
+ * executed.
+ *
+ * @param handler
+ * the handler to set.
+ */
+ public void setCommitActionHandler(final CommitActionHandler handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * Returns if the current project has uncommitted changes.
+ */
+ public void hasUncommittedChanges(final AsyncCallback callback) {
+ final Project project = appContext.getRootProject();
+ if (project == null) {
+ callback.onFailure(new IllegalStateException("No project opened"));
+
+ } else if (!isUnderGit(project)) {
+ callback.onFailure(new IllegalStateException("Opened project is not has no Git repository"));
+
+ } else {
+ vcsServiceProvider.getVcsService(project).hasUncommittedChanges(project, callback);
+ }
+ }
+
+ @Override
+ public void onOk() {
+ final Project project = appContext.getRootProject();
+ if (project != null) {
+ vcsServiceProvider.getVcsService(project).commit(project, view.isIncludeUntracked(),
+ view.getCommitDescription(), new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ notificationManager.notify(exception.getMessage(), FAIL, FLOAT_MODE);
+ }
+
+ @Override
+ public void onSuccess(final Void result) {
+ view.close();
+
+ if (handler != null) {
+ handler.onCommitAction(OK);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onContinue() {
+ view.close();
+
+ if (handler != null) {
+ handler.onCommitAction(CONTINUE);
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ view.close();
+
+ if (handler != null) {
+ handler.onCommitAction(CANCEL);
+ }
+ }
+
+ @Override
+ public void onCommitDescriptionChanged() {
+ view.setOkButtonEnabled(!view.getCommitDescription().isEmpty());
+ }
+
+ public interface CommitActionHandler {
+ /**
+ * Called when a commit actions is done on the commit view.
+ *
+ * @param action
+ * the action.
+ */
+ void onCommitAction(CommitAction action);
+
+ enum CommitAction {
+ OK,
+ CONTINUE,
+ CANCEL
+ }
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitView.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitView.java
new file mode 100644
index 0000000000..61893bce0a
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitView.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.commit;
+
+import org.eclipse.che.ide.api.mvp.View;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * View for committing uncommitted project changes.
+ *
+ * @author Kevin Pollet
+ */
+public interface CommitView extends View {
+ /**
+ * Opens the commit view with the given commit description.
+ */
+ void show(String commitDescription);
+
+ /**
+ * Close the commit view.
+ */
+ void close();
+
+ /**
+ * Returns the current commit description.
+ *
+ * @return the current commit description.
+ */
+ @NotNull
+ String getCommitDescription();
+
+ /**
+ * Enables or disables the button OK.
+ *
+ * @param enabled
+ * {@code true} to enable the OK button, {@code false} otherwise.
+ */
+ void setOkButtonEnabled(final boolean enabled);
+
+ /**
+ * Returns if the untracked files must be added.
+ *
+ * @return {@code true} if untracked files must be added, {@code false} otherwise.
+ */
+ boolean isIncludeUntracked();
+
+ /**
+ * The action delegate.
+ */
+ interface ActionDelegate {
+ /**
+ * Called when project changes must be committed.
+ */
+ void onOk();
+
+ /**
+ * Called when project changes must not be committed.
+ */
+ void onContinue();
+
+ /**
+ * Called when the operation must be aborted.
+ */
+ void onCancel();
+
+ /**
+ * Called when the commit description is changed.
+ */
+ void onCommitDescriptionChanged();
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.java
new file mode 100644
index 0000000000..e7832a70d5
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.commit;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.inject.Inject;
+
+import org.eclipse.che.ide.ui.window.Window;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * UI for {@link CommitView}.
+ *
+ * @author Kevin Pollet
+ */
+public class CommitViewImpl extends Window implements CommitView {
+
+ /** The UI binder for this component. */
+ private static final CommitViewUiBinder UI_BINDER = GWT.create(CommitViewUiBinder.class);
+
+ private final Button ok;
+
+ @UiField(provided = true)
+ ContributeMessages messages;
+
+ @UiField
+ TextArea commitDescription;
+
+ @UiField
+ CheckBox includeUntracked;
+
+ private ActionDelegate delegate;
+
+ @Inject
+ public CommitViewImpl(final ContributeMessages messages) {
+ this.messages = messages;
+
+ setWidget(UI_BINDER.createAndBindUi(this));
+ setTitle(messages.commitDialogTitle());
+
+ ok = createButton(messages.commitDialogButtonOkText(), "commit-dialog-ok", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ delegate.onOk();
+ }
+ });
+ ok.addStyleName(resources.windowCss().button());
+
+ final Button continueWithoutCommitting =
+ createButton(messages.commitDialogButtonContinueText(), "commit-dialog-continue-without-committing",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ delegate.onContinue();
+ }
+ });
+
+ final Button cancel = createButton(messages.commitDialogButtonCancelText(), "commit-dialog-cancel",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ delegate.onCancel();
+ }
+ });
+
+ addButtonToFooter(ok);
+ addButtonToFooter(continueWithoutCommitting);
+ addButtonToFooter(cancel);
+ }
+
+ @Override
+ public void show(@NotNull String commitDescription) {
+ this.commitDescription.setText(commitDescription);
+ new Timer() {
+ @Override
+ public void run() {
+ CommitViewImpl.this.commitDescription.setFocus(true);
+ }
+ }.schedule(300);
+ super.show();
+ }
+
+ @Override
+ public void close() {
+ hide();
+ }
+
+ @NotNull
+ @Override
+ public String getCommitDescription() {
+ return commitDescription.getText();
+ }
+
+ @Override
+ public void setOkButtonEnabled(final boolean enabled) {
+ ok.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isIncludeUntracked() {
+ return includeUntracked.getValue();
+ }
+
+ @Override
+ public void setDelegate(final ActionDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected void onClose() {
+ delegate.onCancel();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("commitDescription")
+ void onCommitDescriptionChanged(final KeyUpEvent event) {
+ delegate.onCommitDescriptionChanged();
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.ui.xml b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.ui.xml
new file mode 100644
index 0000000000..540ffaa5f3
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewImpl.ui.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+ .border {
+ margin: 15px;
+ }
+
+ .margin {
+ margin-bottom: 5px;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewUiBinder.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewUiBinder.java
new file mode 100644
index 0000000000..ecf8e96d70
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/commit/CommitViewUiBinder.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.commit;
+
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * {@link com.google.gwt.uibinder.client.UiBinder} interface for the commit dialog.
+ *
+ * @author Kevin Pollet
+ */
+@UiTemplate("CommitViewImpl.ui.xml")
+interface CommitViewUiBinder extends UiBinder {
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteAwareTextBox.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteAwareTextBox.java
new file mode 100644
index 0000000000..9d713e4eb7
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteAwareTextBox.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.paste;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.TextBox;
+
+/**
+ * {@link TextBox} that handles onpaste events.
+ */
+public class PasteAwareTextBox extends TextBox {
+
+ public PasteAwareTextBox() {
+ sinkEvents(Event.ONPASTE);
+ }
+
+ public PasteAwareTextBox(final Element element) {
+ super(element);
+ sinkEvents(Event.ONPASTE);
+ }
+
+ @Override
+ public void onBrowserEvent(final Event event) {
+ super.onBrowserEvent(event);
+ switch (event.getTypeInt()) {
+ case Event.ONPASTE:
+ event.stopPropagation();
+ delayedFireEvent();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Fires an event, after waiting the state of the textbox the be updated.
+ */
+ private void delayedFireEvent() {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ fireEvent(new PasteEvent());
+ }
+ });
+ }
+
+ /**
+ * Adds a {@link PasteHandler} to the component.
+ *
+ * @param handler
+ * the handler to add
+ * @return a registration object for removal
+ */
+ public HandlerRegistration addPasteHandler(final PasteHandler handler) {
+ return addHandler(handler, PasteEvent.TYPE);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteEvent.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteEvent.java
new file mode 100644
index 0000000000..d7bffb5dab
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteEvent.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.paste;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * {@link GwtEvent} class for paste events.
+ */
+public class PasteEvent extends GwtEvent {
+ /**
+ * The type of the event.
+ */
+ public static Type TYPE = new Type();
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(final PasteHandler handler) {
+ handler.onPaste(this);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteHandler.java
new file mode 100644
index 0000000000..981045e5fe
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/dialogs/paste/PasteHandler.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.dialogs.paste;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * {@link EventHandler} for {@link PasteEvent}s.
+ */
+public interface PasteHandler extends EventHandler {
+ void onPaste(PasteEvent event);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedEvent.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedEvent.java
new file mode 100644
index 0000000000..3a75203182
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedEvent.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.gwt.event.shared.GwtEvent;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+
+/**
+ * This event is fired when context is invalidated.
+ *
+ * @author Yevhenii Voevodin
+ * @see WorkflowExecutor#invalidateContext(ProjectConfig)
+ */
+public class ContextInvalidatedEvent extends GwtEvent {
+
+ public static final Type TYPE = new Type<>();
+
+ private final Context context;
+
+ public ContextInvalidatedEvent(final Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(ContextInvalidatedHandler handler) {
+ handler.onContextInvalidated(context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedHandler.java
new file mode 100644
index 0000000000..4621ddcd39
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextInvalidatedHandler.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler for the {@link ContextInvalidatedEvent}.
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface ContextInvalidatedHandler extends EventHandler {
+
+ /**
+ * Called when {@code context} is invalidated.
+ *
+ * @param context
+ * invalidated context
+ */
+ void onContextInvalidated(final Context context);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeEvent.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeEvent.java
new file mode 100644
index 0000000000..0fd96bb6bb
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeEvent.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import com.google.gwt.event.shared.GwtEvent;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author Kevin Pollet
+ */
+public class ContextPropertyChangeEvent extends GwtEvent {
+ public static Type TYPE = new Type<>();
+
+ private final Context context;
+ private final ContextProperty contextProperty;
+
+ public ContextPropertyChangeEvent(@NotNull final Context context, @NotNull final ContextProperty contextProperty) {
+ this.context = context;
+ this.contextProperty = contextProperty;
+ }
+
+ /**
+ * Returns the context object.
+ *
+ * @return the context object.
+ */
+ public Context getContext() {
+ return context;
+ }
+
+ /**
+ * Returns the property changed.
+ *
+ * @return the property changed.
+ */
+ public ContextProperty getContextProperty() {
+ return contextProperty;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(final ContextPropertyChangeHandler handler) {
+ handler.onContextPropertyChange(this);
+ }
+
+ public enum ContextProperty {
+ PROJECT,
+ ORIGIN_REPOSITORY_OWNER,
+ ORIGIN_REPOSITORY_NAME,
+ CONTRIBUTE_TO_BRANCH_NAME,
+ WORK_BRANCH_NAME
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeHandler.java
new file mode 100644
index 0000000000..fcbae967d2
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/ContextPropertyChangeHandler.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler to be advised when a property of the context object is changed.
+ *
+ * @author Kevin Pollet
+ */
+public interface ContextPropertyChangeHandler extends EventHandler {
+ /**
+ * Called when a property of the context object changed.
+ *
+ * @param event
+ * the {@link ContextPropertyChangeEvent} event.
+ */
+ void onContextPropertyChange(ContextPropertyChangeEvent event);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedEvent.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedEvent.java
new file mode 100644
index 0000000000..b9e9f0b421
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedEvent.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Sent when current plugin context is changed to an existing one.
+ *
+ *
Note that if context is just created then this event won't be fired.
+ *
+ * @author Yevhenii Voevodin
+ */
+public class CurrentContextChangedEvent extends GwtEvent {
+
+ public static final Type TYPE = new Type<>();
+
+ private final Context context;
+
+ public CurrentContextChangedEvent(final Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(CurrentContextChangedHandler handler) {
+ handler.onContextChanged(context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedHandler.java
new file mode 100644
index 0000000000..fba45a8a33
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/CurrentContextChangedHandler.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Handler for {@link CurrentContextChangedEvent}.
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface CurrentContextChangedHandler extends EventHandler {
+
+ /**
+ * Called when the current context changed.
+ *
+ * @param context
+ * new context
+ */
+ void onContextChanged(final Context context);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepEvent.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepEvent.java
new file mode 100644
index 0000000000..42b8a8672d
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepEvent.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import com.google.gwt.event.shared.GwtEvent;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Event sent when a step is done or in error.
+ *
+ * @author Kevin Pollet
+ */
+public class StepEvent extends GwtEvent {
+ public static final Type TYPE = new Type<>();
+
+ private final Step step;
+ private final boolean success;
+ private final String message;
+ private final Context context;
+
+ public StepEvent(final Context context, final Step step, final boolean success) {
+ this(context, step, success, null);
+ }
+
+ public StepEvent(final Context context, final Step step, final boolean success, final String message) {
+ this.step = step;
+ this.success = success;
+ this.message = message;
+ this.context = context;
+ }
+
+ @Override
+ public Type getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(@NotNull final StepHandler handler) {
+ if (success) {
+ handler.onStepDone(this);
+
+ } else {
+ handler.onStepError(this);
+ }
+ }
+
+ public Step getStep() {
+ return step;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepHandler.java
new file mode 100644
index 0000000000..e4dfe39088
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/events/StepHandler.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.events;
+
+import com.google.gwt.event.shared.EventHandler;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Handler for step event.
+ *
+ * @author Kevin Pollet
+ */
+public interface StepHandler extends EventHandler {
+ /**
+ * Called when a step is successfully done.
+ *
+ * @param event
+ * the step event.
+ */
+ void onStepDone(@NotNull StepEvent event);
+
+ /**
+ * Called when a step is in error.
+ *
+ * @param event
+ * the step event.
+ */
+ void onStepError(@NotNull StepEvent event);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/inject/PullRequestGinModule.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/inject/PullRequestGinModule.java
new file mode 100644
index 0000000000..7688d943ed
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/inject/PullRequestGinModule.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.inject;
+
+import org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitView;
+import org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitViewImpl;
+import org.eclipse.che.plugin.pullrequest.client.parts.contribute.ContributePartView;
+import org.eclipse.che.plugin.pullrequest.client.parts.contribute.ContributePartViewImpl;
+import org.eclipse.che.plugin.pullrequest.client.steps.AddForkRemoteStepFactory;
+import org.eclipse.che.plugin.pullrequest.client.steps.PushBranchOnForkStep;
+import org.eclipse.che.plugin.pullrequest.client.steps.PushBranchStepFactory;
+import org.eclipse.che.plugin.pullrequest.client.steps.WaitForkOnRemoteStepFactory;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.gwt.inject.client.AbstractGinModule;
+import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder;
+
+import org.eclipse.che.ide.api.extension.ExtensionGinModule;
+
+import javax.inject.Singleton;
+
+/**
+ * Gin module definition for the contributor extension.
+ */
+@ExtensionGinModule
+public class PullRequestGinModule extends AbstractGinModule {
+
+ @Override
+ protected void configure() {
+
+ // bind the commit dialog view
+ bind(CommitView.class).to(CommitViewImpl.class);
+
+ // bind the part view
+ bind(ContributePartView.class).to(ContributePartViewImpl.class);
+
+ // the steps
+ bind(WorkflowExecutor.class).in(Singleton.class);
+ bind(PushBranchOnForkStep.class);
+ install(new GinFactoryModuleBuilder().build(WaitForkOnRemoteStepFactory.class));
+ install(new GinFactoryModuleBuilder().build(PushBranchStepFactory.class));
+ install(new GinFactoryModuleBuilder().build(AddForkRemoteStepFactory.class));
+
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartPresenter.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartPresenter.java
new file mode 100644
index 0000000000..b94ec95c92
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartPresenter.java
@@ -0,0 +1,751 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.events.ContextInvalidatedEvent;
+import org.eclipse.che.plugin.pullrequest.client.events.ContextInvalidatedHandler;
+import org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent;
+import org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeHandler;
+import org.eclipse.che.plugin.pullrequest.client.events.CurrentContextChangedEvent;
+import org.eclipse.che.plugin.pullrequest.client.events.CurrentContextChangedHandler;
+import org.eclipse.che.plugin.pullrequest.client.events.StepEvent;
+import org.eclipse.che.plugin.pullrequest.client.events.StepHandler;
+import org.eclipse.che.plugin.pullrequest.client.steps.CommitWorkingTreeStep;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowStatus;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.AcceptsOneWidget;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.inject.Singleton;
+import com.google.web.bindery.event.shared.EventBus;
+
+import org.eclipse.che.api.git.shared.Branch;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.dialogs.CancelCallback;
+import org.eclipse.che.ide.api.dialogs.DialogFactory;
+import org.eclipse.che.ide.api.dialogs.InputCallback;
+import org.eclipse.che.ide.api.dialogs.InputValidator;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.api.parts.WorkspaceAgent;
+import org.eclipse.che.ide.api.parts.base.BasePresenter;
+import org.eclipse.che.ide.api.resources.Project;
+import org.eclipse.che.ide.util.loging.Log;
+
+import javax.inject.Inject;
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static java.util.Arrays.asList;
+import static org.eclipse.che.ide.api.constraints.Constraints.LAST;
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+import static org.eclipse.che.ide.api.parts.PartStackType.TOOLING;
+
+/**
+ * Part for the contribution configuration.
+ *
+ * @author Kevin Pollet
+ */
+@Singleton
+public class ContributePartPresenter extends BasePresenter implements ContributePartView.ActionDelegate,
+ StepHandler,
+ ContextPropertyChangeHandler,
+ CurrentContextChangedHandler,
+ ContextInvalidatedHandler {
+ private final ContributePartView view;
+ private final WorkspaceAgent workspaceAgent;
+ private final ContributeMessages messages;
+ private final WorkflowExecutor workflowExecutor;
+ private final AppContext appContext;
+ private final NotificationManager notificationManager;
+ private final DialogFactory dialogFactory;
+ private final Map stagesProviders;
+
+ @Inject
+ public ContributePartPresenter(final ContributePartView view,
+ final ContributeMessages messages,
+ final WorkspaceAgent workspaceAgent,
+ final EventBus eventBus,
+ final WorkflowExecutor workflow,
+ final AppContext appContext,
+ final NotificationManager notificationManager,
+ final DialogFactory dialogFactory,
+ final Map stagesProviders) {
+ this.view = view;
+ this.workspaceAgent = workspaceAgent;
+ this.workflowExecutor = workflow;
+ this.messages = messages;
+ this.appContext = appContext;
+ this.notificationManager = notificationManager;
+ this.dialogFactory = dialogFactory;
+ this.stagesProviders = stagesProviders;
+
+ this.view.setDelegate(this);
+
+ view.addContributionTitleChangedHandler(new TextChangedHandler() {
+ @Override
+ public void onTextChanged(String newText) {
+ final Context curContext = workflowExecutor.getCurrentContext();
+ curContext.getViewState().setContributionTitle(newText);
+ }
+ });
+
+ view.addContributionCommentChangedHandler(new TextChangedHandler() {
+ @Override
+ public void onTextChanged(String newText) {
+ final Context curContext = workflowExecutor.getCurrentContext();
+ curContext.getViewState().setContributionComment(newText);
+ }
+ });
+
+ view.addBranchChangedHandler(new TextChangedHandler() {
+ @Override
+ public void onTextChanged(String branchName) {
+ final Context curContext = workflowExecutor.getCurrentContext();
+ if (!branchName.equals(messages.contributePartConfigureContributionSectionContributionBranchNameCreateNewItemText()) &&
+ !branchName.equals(curContext.getWorkBranchName())) {
+ checkoutBranch(curContext, branchName, false);
+ }
+ }
+ });
+
+ eventBus.addHandler(StepEvent.TYPE, this);
+ eventBus.addHandler(ContextPropertyChangeEvent.TYPE, this);
+ eventBus.addHandler(CurrentContextChangedEvent.TYPE, this);
+ eventBus.addHandler(ContextInvalidatedEvent.TYPE, this);
+ }
+
+ public void open() {
+ resetView();
+ workspaceAgent.openPart(ContributePartPresenter.this, TOOLING, LAST);
+ }
+
+ public void remove() {
+ workspaceAgent.removePart(ContributePartPresenter.this);
+ }
+
+ @Override
+ public void onContribute() {
+ final Context context = workflowExecutor.getCurrentContext();
+ context.getViewState().setStatusMessage(null);
+ context.getViewState().resetStages();
+
+ updateView(context,
+ new NewContributionPanelUpdate(),
+ new StatusMessageUpdate(),
+ new StatusSectionUpdate());
+
+ // Extract configuration values and perform contribution
+ if (isCurrentContext(context)) {
+ context.getConfiguration()
+ .withContributionBranchName(view.getContributionBranchName())
+ .withContributionComment(view.getContributionComment())
+ .withContributionTitle(view.getContributionTitle());
+ }
+
+ if (context.isUpdateMode()) {
+ workflowExecutor.updatePullRequest(context);
+ } else {
+ workflowExecutor.createPullRequest(context);
+ }
+
+ updateView(context, new ContributionButtonUpdate(messages));
+ }
+
+ private void restore(final Context context) {
+ final List updates = new ArrayList<>();
+ // Repository panel updates
+ updates.add(new RepositoryUrlUpdate());
+ updates.add(new ClonedBranchUpdate());
+ updates.add(new ProjectNameUpdate());
+ // All the other panels are available only if the mode is different from INITIALIZING
+ // All the other panels are hidden by the #open method, which is called before initialization
+ if (context.getStatus() != WorkflowStatus.INITIALIZING) {
+ // Configuration panel updates
+ updates.add(new WorkBranchUpdate());
+ updates.add(new ContributionTitleUpdate());
+ updates.add(new ContributionCommentUpdate());
+ // Contribution button update
+ updates.add(new ContributionButtonUpdate(messages));
+ // Status panel updates
+ updates.add(new StatusSectionUpdate());
+ updates.add(new StatusMessageUpdate());
+ // New contribution panel updates
+ updates.add(new NewContributionPanelUpdate());
+ }
+ updateView(context, updates);
+ }
+
+ /** Continuously resets the view state as long as current project is not changed. */
+ private void resetView() {
+ final Project project = appContext.getRootProject();
+ final String projectName = project != null ? project.getName() : null;
+ if (!isCurrentProject(projectName)) return;
+ view.setRepositoryUrl("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributeToBranch("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionBranchName("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionBranchNameEnabled(true);
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionBranchNameList(Collections.emptyList());
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionTitle("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setProjectName("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionTitleEnabled(true);
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionComment("");
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributionCommentEnabled(true);
+
+ if (!isCurrentProject(projectName)) return;
+ view.setContributeButtonText(messages.contributePartConfigureContributionSectionButtonContributeText());
+
+ if (!isCurrentProject(projectName)) return;
+ view.hideStatusSection();
+
+ if (!isCurrentProject(projectName)) return;
+ view.hideNewContributionSection();
+
+ if (!isCurrentProject(projectName)) return;
+ updateControls();
+ }
+
+ @Override
+ public void onOpenPullRequestOnVcsHost() {
+ final Context context = workflowExecutor.getCurrentContext();
+
+ Window.open(context.getVcsHostingService().makePullRequestUrl(context.getUpstreamRepositoryOwner(),
+ context.getUpstreamRepositoryName(),
+ context.getPullRequestIssueNumber()), "", "");
+ }
+
+ @Override
+ public void onNewContribution() {
+ final Context context = workflowExecutor.getCurrentContext();
+ context.getVcsService().checkoutBranch(context.getProject(), context.getContributeToBranchName(),
+ false, new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ notificationManager.notify(exception.getMessage(), FAIL, FLOAT_MODE);
+ }
+
+ @Override
+ public void onSuccess(final String branchName) {
+ resetView();
+ workflowExecutor.invalidateContext(context.getProject());
+ workflowExecutor.init(context.getVcsHostingService(), context.getProject());
+ }
+ });
+ }
+
+ @Override
+ public void onRefreshContributionBranchNameList() {
+ updateView(workflowExecutor.getCurrentContext(), new WorkBranchUpdate());
+ }
+
+ @Override
+ public void onCreateNewBranch() {
+ final Context context = workflowExecutor.getCurrentContext();
+ dialogFactory.createInputDialog(messages.contributePartConfigureContributionDialogNewBranchTitle(),
+ messages.contributePartConfigureContributionDialogNewBranchLabel(),
+ new CreateNewBranchCallback(context),
+ new CancelNewBranchCallback(context))
+ .withValidator(new BranchNameValidator())
+ .show();
+ }
+
+ @Override
+ public void updateControls() {
+ final String contributionTitle = view.getContributionTitle();
+
+ boolean isValid = true;
+ view.showContributionTitleError(false);
+
+ if (contributionTitle == null || contributionTitle.trim().isEmpty()) {
+ view.showContributionTitleError(true);
+ isValid = false;
+ }
+
+ view.setContributeButtonEnabled(isValid);
+ }
+
+ @Override
+ public void go(final AcceptsOneWidget container) {
+ container.setWidget(view.asWidget());
+ }
+
+ @NotNull
+ @Override
+ public String getTitle() {
+ return messages.contributePartTitle();
+ }
+
+ @Override
+ public IsWidget getView() {
+ return view;
+ }
+
+ @Nullable
+ @Override
+ public String getTitleToolTip() {
+ return null;
+ }
+
+ @Override
+ public int getSize() {
+ return 350;
+ }
+
+ @Override
+ public void onStepDone(final StepEvent event) {
+ final Class extends Step> stepClass = event.getStep().getClass();
+ final Context context = event.getContext();
+
+ // if it is necessarily to display stages on this step
+ if (getProvider(context).getDisplayStagesType(context) == stepClass) {
+ context.getViewState().setStages(getProvidedStages(context));
+ updateView(context, new StatusSectionUpdate());
+ }
+
+ // if current step is in list of provided stages types
+ // then this step is done and view should be affected
+ if (!context.getViewState().getStages().isEmpty() && getProvidedStepDoneTypes(context).contains(stepClass)) {
+ context.getViewState().setStageDone(true);
+ updateView(context, new DisplayCurrentStepResultUpdate(true));
+ } else if (stepClass == WorkflowExecutor.ChangeContextStatusStep.class) {
+ if (context.getStatus() == WorkflowStatus.READY_TO_UPDATE_PR) {
+ final List updates = new ArrayList<>();
+ // Display status message
+ final String message;
+ if (context.getPreviousStatus() == WorkflowStatus.CREATING_PR) {
+ message = messages.contributePartStatusSectionContributionCreatedMessage();
+ } else {
+ message = messages.contributePartStatusSectionContributionUpdatedMessage();
+ }
+ context.getViewState().setStatusMessage(message, false);
+ updates.add(new StatusMessageUpdate());
+
+ // Contribution button
+ updates.add(new ContributionButtonUpdate(messages));
+
+ // Config panel
+ updates.add(new ContributionTitleUpdate());
+ updates.add(new ContributionCommentUpdate());
+
+ // New contribution panel
+ updates.add(new NewContributionPanelUpdate());
+
+ updateView(context, updates);
+ }
+ }
+ }
+
+ @Override
+ public void onStepError(final StepEvent event) {
+ final Step step = event.getStep();
+ final Class extends Step> stepClass = step.getClass();
+ final Context context = event.getContext();
+ if (stepClass == CommitWorkingTreeStep.class) {
+ if (!context.isUpdateMode()) {
+ context.getViewState().resetStages();
+ context.getViewState().setStatusMessage(null);
+ updateView(context,
+ new StatusSectionUpdate(),
+ new StatusMessageUpdate(),
+ new NewContributionPanelUpdate(),
+ new ContributionButtonUpdate(messages));
+ } else {
+ context.getViewState().resetStages();
+ context.getViewState().setStatusMessage(event.getMessage(), true);
+ updateView(context,
+ new StatusSectionUpdate(),
+ new StatusMessageUpdate(),
+ new ContributionButtonUpdate(messages));
+ }
+ } else if (getProvidedStepErrorTypes(context).contains(stepClass)) {
+ context.getViewState().setStageDone(false);
+ context.getViewState().setStatusMessage(event.getMessage(), true);
+ updateView(context,
+ new DisplayCurrentStepResultUpdate(false),
+ new StatusMessageUpdate(),
+ new ContributionButtonUpdate(messages));
+ } else {
+ context.getViewState().resetStages();
+ restore(context);
+ Log.error(ContributePartPresenter.class, "Step error: ", event.getMessage());
+ }
+ }
+
+ @Override
+ public void onContextPropertyChange(final ContextPropertyChangeEvent event) {
+ final Context context = event.getContext();
+
+ switch (event.getContextProperty()) {
+ case CONTRIBUTE_TO_BRANCH_NAME:
+ updateView(context, new ClonedBranchUpdate());
+ break;
+
+ case WORK_BRANCH_NAME:
+ updateView(context, new WorkBranchUpdate());
+ break;
+
+ case ORIGIN_REPOSITORY_NAME:
+ case ORIGIN_REPOSITORY_OWNER:
+ updateView(context, new RepositoryUrlUpdate());
+ break;
+
+ case PROJECT:
+ updateView(context, new ProjectNameUpdate());
+ break;
+
+ default:
+ // nothing to do
+ break;
+ }
+ }
+
+ @Override
+ public void onContextChanged(final Context context) {
+ restore(context);
+ }
+
+ private void updateView(final Context context, final ViewUpdate... updates) {
+ updateView(context, asList(updates));
+ }
+
+ private void updateView(final Context context, final List updates) {
+ for (Iterator it = updates.iterator(); it.hasNext() && isCurrentContext(context); ) {
+ it.next().update(view, context);
+ }
+ }
+
+ private void updateView(final Context context, final ViewUpdate update) {
+ if (isCurrentContext(context)) {
+ update.update(view, context);
+ }
+ }
+
+ @Override
+ public void onContextInvalidated(Context context) {
+ resetView();
+ }
+
+ /**
+ * Defines a single update operation.
+ * Single update operations are required as view is shared between multiple projects.
+ * If context is switched view should not be updated with the updates related to the previous context.
+ */
+ private interface ViewUpdate {
+ void update(final ContributePartView view, final Context context);
+ }
+
+ private static class DisplayCurrentStepResultUpdate implements ViewUpdate {
+ private final boolean result;
+
+ public DisplayCurrentStepResultUpdate(boolean result) {
+ this.result = result;
+ }
+
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.setCurrentStatusStepStatus(result);
+ }
+ }
+
+ private static class NewContributionPanelUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.hideNewContributionSection();
+ if (context.isUpdateMode()) {
+ view.showNewContributionSection(context.getVcsHostingService().getName());
+ }
+ }
+ }
+
+ private static class StatusMessageUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.hideStatusSectionMessage();
+ final Context.ViewState.StatusMessage statusMessage = context.getViewState().getStatusMessage();
+ if (statusMessage != null) {
+ view.showStatusSectionMessage(statusMessage.getMessage(), statusMessage.isError());
+ }
+ }
+ }
+
+ private static class StatusSectionUpdate implements ViewUpdate {
+ @Override
+ public void update(ContributePartView view, Context context) {
+ view.hideStatusSection();
+ final List stepStatuses = context.getViewState().getStages();
+ if (stepStatuses.size() > 0) {
+ final String[] names = context.getViewState().getStageNames().toArray(new String[stepStatuses.size()]);
+ view.showStatusSection(names);
+ for (Boolean stepStatus : context.getViewState().getStageValues()) {
+ if (stepStatus == null) {
+ break;
+ }
+ view.setCurrentStatusStepStatus(stepStatus);
+ }
+ } else if (context.getViewState().getStatusMessage() != null) {
+ view.showStatusSection();
+ }
+ }
+ }
+
+ private static class ClonedBranchUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.setContributeToBranch(nullToEmpty(context.getContributeToBranchName()));
+ }
+ }
+
+ private static class ContributionButtonUpdate implements ViewUpdate {
+ private final ContributeMessages messages;
+
+ private ContributionButtonUpdate(final ContributeMessages messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ final boolean isEnabled = !nullToEmpty(context.getViewState().getContributionTitle()).isEmpty();
+ final boolean isInProgress;
+ final String buttonText;
+ switch (context.getStatus()) {
+ case UPDATING_PR:
+ buttonText = messages.contributePartConfigureContributionSectionButtonContributeUpdateText();
+ isInProgress = true;
+ break;
+ case READY_TO_UPDATE_PR:
+ buttonText = messages.contributePartConfigureContributionSectionButtonContributeUpdateText();
+ isInProgress = false;
+ break;
+ case CREATING_PR:
+ buttonText = messages.contributePartConfigureContributionSectionButtonContributeText();
+ isInProgress = true;
+ break;
+ case READY_TO_CREATE_PR:
+ buttonText = messages.contributePartConfigureContributionSectionButtonContributeText();
+ isInProgress = false;
+ break;
+ default:
+ throw new IllegalStateException("Illegal workflow status " + context.getStatus());
+ }
+ view.setContributeButtonText(buttonText);
+ view.setContributionProgressState(isInProgress);
+ view.setContributeButtonEnabled(isEnabled);
+ }
+ }
+
+ private class ContributionCommentUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.setContributionComment(nullToEmpty(context.getViewState().getContributionComment()));
+ view.setContributionCommentEnabled(context.getStatus() == WorkflowStatus.READY_TO_CREATE_PR);
+ }
+ }
+
+ private static class ContributionTitleUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.setContributionTitle(nullToEmpty(context.getViewState().getContributionTitle()));
+ view.setContributionTitleEnabled(context.getStatus() == WorkflowStatus.READY_TO_CREATE_PR);
+ }
+ }
+
+ private static class RepositoryUrlUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ final String originRepositoryName = context.getOriginRepositoryName();
+ final String originRepositoryOwner = context.getOriginRepositoryOwner();
+ if (originRepositoryName != null && originRepositoryOwner != null) {
+ view.setRepositoryUrl(context.getVcsHostingService()
+ .makeHttpRemoteUrl(originRepositoryOwner, originRepositoryName));
+ }
+ }
+ }
+
+ private static class WorkBranchUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ context.getVcsService()
+ .listLocalBranches(context.getProject(), new AsyncCallback>() {
+ @Override
+ public void onFailure(final Throwable notUsed) {
+ }
+
+ @Override
+ public void onSuccess(final List branches) {
+ final List branchNames = new ArrayList<>();
+ for (final Branch oneBranch : branches) {
+ branchNames.add(oneBranch.getDisplayName());
+ }
+ view.setContributionBranchNameList(branchNames);
+ view.setContributionBranchName(context.getWorkBranchName());
+ }
+ });
+ }
+ }
+
+ private static class ProjectNameUpdate implements ViewUpdate {
+ @Override
+ public void update(final ContributePartView view, final Context context) {
+ view.setProjectName(context.getProject().getName());
+ }
+ }
+
+ private boolean isCurrentContext(final Context context) {
+ final Project project = appContext.getRootProject();
+
+ return project != null && Objects.equals(context.getProject().getName(), project.getName());
+ }
+
+ private boolean isCurrentProject(final String projectName) {
+ final Project project = appContext.getRootProject();
+
+ return project != null && Objects.equals(projectName, project.getName());
+ }
+
+ private StagesProvider getProvider(final Context context) {
+ for (Map.Entry entry : stagesProviders.entrySet()) {
+ if (entry.getKey().equals(context.getVcsHostingService().getName())) {
+ return entry.getValue();
+ }
+ }
+ throw new IllegalStateException("StagesProvider for VCS hosting service "
+ + context.getVcsHostingService().getName()
+ + " isn't registered");
+ }
+
+ private List getProvidedStages(final Context context) {
+ return getProvider(context).getStages(context);
+ }
+
+ private Set> getProvidedStepDoneTypes(final Context context) {
+ return getProvider(context).getStepDoneTypes(context);
+ }
+
+ private Set> getProvidedStepErrorTypes(final Context context) {
+ return getProvider(context).getStepErrorTypes(context);
+ }
+
+ private static class BranchNameValidator implements InputValidator {
+ private static final Violation ERROR_WITH_NO_MESSAGE = new InputValidator.Violation() {
+ @Nullable
+ @Override
+ public String getMessage() {
+ return "";
+ }
+
+ @Nullable
+ @Override
+ public String getCorrectedValue() {
+ return null;
+ }
+ };
+
+ @Nullable
+ @Override
+ public Violation validate(final String branchName) {
+ return branchName.matches("[0-9A-Za-z-]+") ? null : ERROR_WITH_NO_MESSAGE;
+ }
+ }
+
+ private class CreateNewBranchCallback implements InputCallback {
+ private final Context context;
+
+ public CreateNewBranchCallback(final Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public void accepted(final String branchName) {
+ context.getVcsService()
+ .isLocalBranchWithName(context.getProject(), branchName, new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ notificationManager.notify(exception.getMessage(), FAIL, FLOAT_MODE);
+ }
+
+ @Override
+ public void onSuccess(final Boolean branchExists) {
+ if (branchExists) {
+ notificationManager
+ .notify(messages.contributePartConfigureContributionDialogNewBranchErrorBranchExists(branchName),
+ FAIL,
+ FLOAT_MODE);
+
+ } else {
+ checkoutBranch(context, branchName, true);
+ }
+ }
+ });
+ }
+ }
+
+ private void checkoutBranch(final Context context, final String branchName, final boolean createNew) {
+ context.getVcsService()
+ .checkoutBranch(context.getProject(),
+ branchName,
+ createNew,
+ new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ notificationManager.notify(exception.getLocalizedMessage(), FAIL, FLOAT_MODE);
+ }
+
+ @Override
+ public void onSuccess(final String notUsed) {
+ workflowExecutor.invalidateContext(context.getProject());
+ workflowExecutor.init(context.getVcsHostingService(), context.getProject());
+ }
+ });
+ }
+
+ private class CancelNewBranchCallback implements CancelCallback {
+ private final Context context;
+
+ private CancelNewBranchCallback(final Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public void cancelled() {
+ updateView(context, new WorkBranchUpdate());
+ }
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartView.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartView.java
new file mode 100644
index 0000000000..2911b3b570
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartView.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+import org.eclipse.che.ide.api.mvp.View;
+import org.eclipse.che.ide.api.parts.base.BaseActionDelegate;
+
+import java.util.List;
+
+/**
+ * Interface for the contribution configuration shown when the user decides to send their contribution.
+ */
+public interface ContributePartView extends View {
+ /**
+ * Set factory's repository URL.
+ */
+ void setRepositoryUrl(String url);
+
+ /**
+ * Set factory's contribute to branch name.
+ */
+ void setContributeToBranch(String branch);
+
+ /**
+ * Set project name.
+ */
+ void setProjectName(String projectName);
+
+ /**
+ * Returns the contribution branch name.
+ *
+ * @return the contribution branch name
+ */
+ String getContributionBranchName();
+
+ /**
+ * Sets the contribution branch name.
+ *
+ * @param branchName
+ * the contribution branch name.
+ */
+ void setContributionBranchName(String branchName);
+
+ /**
+ * Set the contribution branch name list.
+ *
+ * @param branchNames
+ * the branch name list.
+ */
+ void setContributionBranchNameList(List branchNames);
+
+ /**
+ * Sets the enabled/disabled state of the contribution branch name field.
+ */
+ void setContributionBranchNameEnabled(boolean enabled);
+
+ /**
+ * Returns the current content of the contribution comment.
+ *
+ * @return the comment.
+ */
+ String getContributionComment();
+
+ /**
+ * Sets the contribution comment.
+ *
+ * @param comment
+ * the contribution comment.
+ */
+ void setContributionComment(String comment);
+
+ void addContributionCommentChangedHandler(TextChangedHandler handler);
+
+ /**
+ * Sets the enabled/disabled state of the contribution comment field.
+ */
+ void setContributionCommentEnabled(boolean enabled);
+
+ /**
+ * Returns the contribution title.
+ *
+ * @return the title.
+ */
+ String getContributionTitle();
+
+ /**
+ * Sets the contribution title.
+ *
+ * @param title
+ * the contribution title.
+ */
+ void setContributionTitle(String title);
+
+ void addContributionTitleChangedHandler(TextChangedHandler handler);
+
+ void addBranchChangedHandler(TextChangedHandler changeHandler);
+
+ /**
+ * Sets the enabled/disabled state of the contribution title field.
+ */
+ void setContributionTitleEnabled(boolean enabled);
+
+ /**
+ * Sets the contribution title input error state.
+ *
+ * @param showError
+ * {@code true} if the contribution title is in error, {@code false} otherwise.
+ */
+ void showContributionTitleError(boolean showError);
+
+ /**
+ * Sets the enabled/disabled state of the "Contribute" button.
+ *
+ * @param enabled
+ * true to enable, false to disable
+ */
+ void setContributeButtonEnabled(boolean enabled);
+
+ /**
+ * Sets the text displayed into the "Contribute" button.
+ *
+ * @param text
+ * the text to display
+ */
+ void setContributeButtonText(String text);
+
+ /**
+ * Shows the status section.
+ */
+ void showStatusSection(String... statusSteps);
+
+ /**
+ * Sets the current status step state.
+ *
+ * @param success
+ * {@code true} if success, {@code false} otherwise.
+ */
+ void setCurrentStatusStepStatus(boolean success);
+
+ /**
+ * Shows the status section message.
+ *
+ * @param error
+ * {@code true} if the message displayed is an error, {@code false} otherwise.
+ */
+ void showStatusSectionMessage(String message, boolean error);
+
+ /**
+ * Hides the status section message.
+ */
+ void hideStatusSectionMessage();
+
+ /**
+ * Hides the status section.
+ */
+ void hideStatusSection();
+
+ /**
+ * Show the new contribution section.
+ *
+ * @param vcsHostName
+ * the VCS host name.
+ */
+ void showNewContributionSection(String vcsHostName);
+
+ /**
+ * Hide the new contribution section.
+ */
+ void hideNewContributionSection();
+
+ /**
+ * Defines if the contribution is in progress.
+ *
+ * @param progress
+ * {@code true} if the contribution is in progress, {@code false} otherwise.
+ */
+ void setContributionProgressState(boolean progress);
+
+ String getCurrentStatusStepName();
+
+ /**
+ * Action delegate interface for the contribution configuration dialog.
+ */
+ interface ActionDelegate extends BaseActionDelegate {
+ /** Performs any actions appropriate in response to the user having pressed the Contribute button. */
+ void onContribute();
+
+ /** Performs any action appropriate in response to the user having pressed the open pull request on vcs host button. */
+ void onOpenPullRequestOnVcsHost();
+
+ /** Performs any action appropriate in response to the user having pressed the start new contribution button. */
+ void onNewContribution();
+
+ /** Performs any action appropriate in response to the user having pressed the refresh contribution branch names list button. */
+ void onRefreshContributionBranchNameList();
+
+ /** Performs any action appropriate in response to the user having selected the create new branch item. */
+ void onCreateNewBranch();
+
+ /** Performs any action when view state is modified. */
+ void updateControls();
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.java
new file mode 100644
index 0000000000..94f75282f3
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.java
@@ -0,0 +1,498 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.ContributeResources;
+import org.eclipse.che.plugin.pullrequest.client.dialogs.paste.PasteEvent;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.ValueBoxBase;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.eclipse.che.ide.FontAwesome;
+import org.eclipse.che.ide.api.parts.PartStackUIResources;
+import org.eclipse.che.ide.api.parts.base.BaseView;
+import org.eclipse.che.ide.ui.buttonLoader.ButtonLoaderResources;
+import org.eclipse.che.ide.ui.listbox.CustomListBox;
+import org.vectomatic.dom.svg.ui.SVGPushButton;
+
+import javax.inject.Inject;
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.gwt.dom.client.Style.Cursor.POINTER;
+import static com.google.gwt.dom.client.Style.Unit.PX;
+
+/**
+ * Implementation of {@link ContributePartView}.
+ */
+public class ContributePartViewImpl extends BaseView implements ContributePartView {
+
+ /** The status component. */
+ private final StatusSteps statusSteps;
+
+ /** The contribute button. */
+ @UiField
+ Button contributeButton;
+
+ /** The resources for the view. */
+ @UiField(provided = true)
+ ContributeResources resources;
+
+ /** The component for the URL of factory repository. */
+ @UiField
+ Anchor repositoryUrl;
+
+ /** The component for the name of contribute to branch. */
+ @UiField
+ Label contributeToBranch;
+
+ /** The component for the name of the project */
+ @UiField
+ Label projectName;
+
+ /** The input component for the contribution branch name. */
+ @UiField
+ CustomListBox contributionBranchName;
+
+ /** Button used to refresh the contribution branch name list. */
+ @UiField
+ SVGPushButton refreshContributionBranchNameListButton;
+
+ /** The input component for the contribution title. */
+ @UiField
+ TextBox contributionTitle;
+
+ /** The input zone for the contribution comment. */
+ @UiField
+ TextArea contributionComment;
+
+ /** The i18n messages. */
+ @UiField(provided = true)
+ ContributeMessages messages;
+
+ /** The contribution status section. */
+ @UiField
+ FlowPanel statusSection;
+
+ /** The status section message. */
+ @UiField
+ Label statusSectionMessage;
+
+ /** Open on repository host button. */
+ @UiField
+ Button openPullRequestOnVcsHostButton;
+
+ /** The start new contribution section. */
+ @UiField
+ HTMLPanel newContributionSection;
+
+ /** The new contribution button. */
+ @UiField
+ Button newContributionButton;
+
+ /** The contribute button text. */
+ private String contributeButtonText;
+
+ @Inject
+ public ContributePartViewImpl(@NotNull final PartStackUIResources partStackUIResources,
+ @NotNull final ContributeMessages messages,
+ @NotNull final ContributeResources resources,
+ @NotNull final ButtonLoaderResources buttonLoaderResources,
+ @NotNull final ContributePartViewUiBinder uiBinder) {
+ super(partStackUIResources);
+
+ this.messages = messages;
+ this.resources = resources;
+ this.statusSteps = new StatusSteps();
+
+ setContentWidget(uiBinder.createAndBindUi(this));
+
+ setTitle(messages.contributePartTitle());
+
+ this.contributeButtonText = contributeButton.getText();
+ this.contributeButton.addStyleName(buttonLoaderResources.Css().buttonLoader());
+
+ this.refreshContributionBranchNameListButton.getElement().getStyle().setWidth(23, PX);
+ this.refreshContributionBranchNameListButton.getElement().getStyle().setHeight(20, PX);
+ this.refreshContributionBranchNameListButton.getElement().getStyle().setCursor(POINTER);
+ this.refreshContributionBranchNameListButton.getElement().getStyle().setProperty("fill", "#dbdbdb");
+
+ this.statusSection.setVisible(false);
+ this.newContributionSection.setVisible(false);
+ this.contributionTitle.getElement().setPropertyString("placeholder",
+ messages.contributePartConfigureContributionSectionContributionTitlePlaceholder());
+ this.contributionComment.getElement().setPropertyString("placeholder",
+ messages.contributePartConfigureContributionSectionContributionCommentPlaceholder());
+
+ this.statusSection.insert(statusSteps, 1);
+ }
+
+ @Override
+ public void setRepositoryUrl(final String url) {
+ repositoryUrl.setHref(url);
+ repositoryUrl.setText(url);
+ }
+
+ @Override
+ public void setContributeToBranch(final String branch) {
+ contributeToBranch.setText(branch);
+ }
+
+ @Override
+ public void setProjectName(String projectName) {
+ this.projectName.setText(projectName);
+ }
+
+ @Override
+ public void setContributeButtonText(final String text) {
+ contributeButton.setText(text);
+ contributeButtonText = contributeButton.getText();
+ }
+
+ @Override
+ public String getContributionBranchName() {
+ final int selectedIndex = contributionBranchName.getSelectedIndex();
+ return selectedIndex == -1 ? null : contributionBranchName.getValue(selectedIndex);
+ }
+
+ @Override
+ public void setContributionBranchName(final String branchName) {
+ for (int i = 0; i < contributionBranchName.getItemCount(); i++) {
+ if (contributionBranchName.getValue(i).equals(branchName)) {
+ contributionBranchName.setSelectedIndex(i);
+ return;
+ }
+ }
+
+ if (contributionBranchName.getItemCount() > 1) {
+ contributionBranchName.setSelectedIndex(1);
+ }
+ }
+
+ @Override
+ public void setContributionBranchNameList(final List branchNames) {
+ final String selectedBranchName = getContributionBranchName();
+
+ contributionBranchName.clear();
+ contributionBranchName.addItem(messages.contributePartConfigureContributionSectionContributionBranchNameCreateNewItemText());
+ for (final String oneBranchName : branchNames) {
+ contributionBranchName.addItem(oneBranchName);
+ }
+
+ setContributionBranchName(selectedBranchName);
+ }
+
+ @Override
+ public String getContributionComment() {
+ return contributionComment.getValue();
+ }
+
+ @Override
+ public void setContributionComment(final String comment) {
+ contributionComment.setText(comment);
+ }
+
+ @Override
+ public void addContributionCommentChangedHandler(TextChangedHandler handler) {
+ contributionComment.addKeyUpHandler(new TextChangedHandlerAdapter(handler));
+ }
+
+ @Override
+ public String getContributionTitle() {
+ return contributionTitle.getValue();
+ }
+
+ @Override
+ public void setContributionTitle(final String title) {
+ contributionTitle.setText(title);
+ }
+
+ @Override
+ public void addContributionTitleChangedHandler(TextChangedHandler handler) {
+ contributionTitle.addKeyUpHandler(new TextChangedHandlerAdapter(handler));
+ }
+
+ @Override
+ public void addBranchChangedHandler(final TextChangedHandler changeHandler) {
+ contributionBranchName.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event) {
+ changeHandler.onTextChanged(contributionBranchName.getSelectedItemText());
+ }
+ });
+ }
+
+ @Override
+ public void setContributionBranchNameEnabled(final boolean enabled) {
+ contributionBranchName.setEnabled(enabled);
+ }
+
+ @Override
+ public void setContributionCommentEnabled(final boolean enabled) {
+ contributionComment.setEnabled(enabled);
+ if (!enabled) {
+ contributionComment.getElement().getStyle().setBackgroundColor("#5a5c5c");
+ } else {
+ contributionComment.getElement().getStyle().clearBackgroundColor();
+ }
+ }
+
+ @Override
+ public void setContributionTitleEnabled(final boolean enabled) {
+ contributionTitle.setEnabled(enabled);
+ }
+
+ @Override
+ public void setContributeButtonEnabled(final boolean enabled) {
+ contributeButton.setEnabled(enabled);
+ }
+
+ @Override
+ public void showContributionTitleError(final boolean showError) {
+ if (showError) {
+ contributionTitle.addStyleName(resources.contributeCss().inputError());
+ } else {
+ contributionTitle.removeStyleName(resources.contributeCss().inputError());
+ }
+ }
+
+ @Override
+ public void showStatusSection(final String... statusSteps) {
+ this.statusSteps.removeAll();
+ for (final String oneStatusStep : statusSteps) {
+ this.statusSteps.addStep(oneStatusStep);
+ }
+ statusSection.setVisible(true);
+ }
+
+ @Override
+ public void setCurrentStatusStepStatus(boolean success) {
+ statusSteps.setCurrentStepStatus(success);
+ }
+
+ @Override
+ public String getCurrentStatusStepName() {
+ return statusSteps.getCurrentStepName();
+ }
+
+ @Override
+ public void showStatusSectionMessage(final String message, final boolean error) {
+ if (error) {
+ statusSectionMessage.addStyleName(resources.contributeCss().errorMessage());
+ } else {
+ statusSectionMessage.removeStyleName(resources.contributeCss().errorMessage());
+ }
+
+ statusSectionMessage.setText(message);
+ statusSectionMessage.setVisible(true);
+ }
+
+ @Override
+ public void hideStatusSectionMessage() {
+ statusSectionMessage.setVisible(false);
+ }
+
+ @Override
+ public void hideStatusSection() {
+ statusSection.setVisible(false);
+ }
+
+ @Override
+ public void setContributionProgressState(final boolean progress) {
+ if (progress) {
+ contributeButton.setHTML("");
+ } else {
+ contributeButton.setText(contributeButtonText);
+ }
+ }
+
+ @Override
+ public void showNewContributionSection(final String vcsHostName) {
+ openPullRequestOnVcsHostButton
+ .setText(messages.contributePartNewContributionSectionButtonOpenPullRequestOnVcsHostText(vcsHostName));
+ newContributionSection.setVisible(true);
+ }
+
+ @Override
+ public void hideNewContributionSection() {
+ newContributionSection.setVisible(false);
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributionBranchName")
+ protected void contributionBranchNameChange(final ChangeEvent event) {
+ final int selectedIndex = contributionBranchName.getSelectedIndex();
+ if (selectedIndex == 0) {
+ delegate.onCreateNewBranch();
+ }
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("refreshContributionBranchNameListButton")
+ protected void refreshContributionBranchNameList(final ClickEvent event) {
+ delegate.onRefreshContributionBranchNameList();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributionComment")
+ protected void contributionCommentChanged(final ValueChangeEvent event) {
+ delegate.updateControls();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributionTitle")
+ protected void contributionTitleChanged(final ValueChangeEvent event) {
+ delegate.updateControls();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("openPullRequestOnVcsHostButton")
+ protected void openPullRequestOnVcsHostClick(final ClickEvent event) {
+ delegate.onOpenPullRequestOnVcsHost();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("newContributionButton")
+ protected void newContributionClick(final ClickEvent event) {
+ delegate.onNewContribution();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributeButton")
+ protected void contributeClick(final ClickEvent event) {
+ delegate.onContribute();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributionTitle")
+ protected void contributionTitleKeyUp(final KeyUpEvent event) {
+ delegate.updateControls();
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ @UiHandler("contributionTitle")
+ protected void contributionTitlePaste(final PasteEvent event) {
+ delegate.updateControls();
+ }
+
+ private class StatusSteps extends FlowPanel {
+ private final List steps;
+ private int currentStep;
+
+ private StatusSteps() {
+ this.currentStep = 0;
+ this.steps = new ArrayList<>();
+
+ addStyleName(resources.contributeCss().statusSteps());
+ }
+
+ public void addStep(final String label) {
+ final StatusStep statusStep = new StatusStep(steps.size() + 1, label);
+
+ steps.add(statusStep);
+ add(statusStep);
+ }
+
+ public void removeAll() {
+ clear();
+ currentStep = 0;
+ steps.clear();
+ }
+
+ public void setCurrentStepStatus(final boolean status) {
+ steps.get(currentStep).setStatus(status);
+ currentStep++;
+ }
+
+ public String getCurrentStepName() {
+ return steps.get(currentStep).getLabel();
+ }
+ }
+
+ private class StatusStep extends FlowPanel {
+ private final SimplePanel status;
+ private final String label;
+
+ private StatusStep(final int index, final String label) {
+ final Label indexLabel = new Label();
+ final Label titleLabel = new Label(this.label = label);
+ this.status = new SimplePanel();
+
+ add(indexLabel);
+ add(titleLabel);
+ add(this.status);
+
+ // initialize panel style
+ addStyleName(resources.contributeCss().stepLabelRow());
+
+ // initialize index style
+ indexLabel.addStyleName(resources.contributeCss().statusIndexStepLabel());
+
+ // initialize label style
+ titleLabel.addStyleName(resources.contributeCss().statusTitleStepLabel());
+
+ // initialize status style
+ this.status.addStyleName(resources.contributeCss().stepLabel());
+ }
+
+ public void setStatus(final boolean success) {
+ status.clear();
+ status.add(getStatusIcon(success));
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ private Widget getStatusIcon(final boolean success) {
+ final Widget icon = new HTML(success ? FontAwesome.CHECK : FontAwesome.EXCLAMATION_TRIANGLE);
+
+ icon.addStyleName(success ? resources.contributeCss().checkIcon() : resources.contributeCss().errorIcon());
+
+ return icon;
+ }
+ }
+
+ /** Adapts {@link TextChangedHandler} to the {@link KeyUpEvent}. */
+ private static class TextChangedHandlerAdapter implements KeyUpHandler {
+ private final TextChangedHandler handler;
+
+ public TextChangedHandlerAdapter(TextChangedHandler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ if (event.getSource() instanceof ValueBoxBase) {
+ handler.onTextChanged(((ValueBoxBase)event.getSource()).getText());
+ }
+ }
+ }
+
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.ui.xml b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.ui.xml
new file mode 100644
index 0000000000..2a1e088ea1
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewImpl.ui.xml
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+ .panel {
+ font-size: 11px;
+ position: relative;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .border {
+ border-top: 1px solid textFieldBorderColor;
+ margin-bottom: 5px;
+ }
+
+ .section {
+ display: flex;
+ flex-direction: column;
+ padding: 5px 5px 5px 0;
+ }
+
+ .section > * {
+ margin-left: 20px;
+ }
+
+ .section .title {
+ position: relative;
+ top: 0;
+ font-size: 12px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: mainFontColor;
+ margin-left: 10px;
+ }
+
+ .section button {
+ height: 25px;
+ padding: 0 20px;
+ }
+
+ .fieldItem {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ margin-bottom: 7px;
+ overflow: visible;
+ }
+
+ .fieldItem .field {
+ flex-grow: 1;
+ }
+
+ .fieldItem .field span {
+ display: inherit;
+ }
+
+ .horizontal {
+ color: mainFontColor;
+ display: inline-block;
+ text-align: left;
+ height: 18px;
+ width: literal("calc(100% - 20px)");
+ }
+
+ .link {
+ color: outputLinkColor !important;
+ text-decoration: underline;
+ }
+
+ .horizontal .field {
+ color: inherit;
+ width: inherit;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .icon {
+ display: inline-block;
+ text-decoration: none;
+ text-align: center;
+ line-height: 18px;
+ margin-right: 5px;
+ width: 15px;
+ float: left;
+ color: inherit;
+ }
+
+ .fieldItem .label {
+ float: left;
+ display: flex;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ }
+
+ .fixed-textarea {
+ overflow: scroll;
+ resize: none;
+ }
+
+ .contributeButton {
+ align-self: flex-end;
+ }
+
+ .statusMessage {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ align-self: center;
+ width: auto;
+ line-height: 15px;
+ display: inline-box;
+ white-space: pre-wrap;
+ word-break: normal;
+ }
+
+ .section.repository {
+ background: inherit;
+ }
+
+ .branchField {
+ display: flex;
+ }
+
+ .branchField svg {
+ flex-shrink: 0;
+ }
+
+ .repository .fieldItem .field {
+ font-weight: bold;
+ display: inline-block;
+ }
+
+ .section.newContribution button {
+ align-self: center;
+ margin-top: 1em;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewUiBinder.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewUiBinder.java
new file mode 100644
index 0000000000..160329ce3a
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/ContributePartViewUiBinder.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.ScrollPanel;
+
+/**
+ * {@link com.google.gwt.uibinder.client.UiBinder} interface for the configure contribution dialog.
+ */
+@UiTemplate("ContributePartViewImpl.ui.xml")
+public interface ContributePartViewUiBinder extends UiBinder {
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/StagesProvider.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/StagesProvider.java
new file mode 100644
index 0000000000..f30306b493
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/StagesProvider.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provider should be implemented one per {@link VcsHostingService}.
+ *
+ *
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface StagesProvider {
+
+ /**
+ * Returns the list of stages which should be displayed
+ * when pull request update or creation starts.
+ *
+ * @param context
+ * current execution context
+ * @return the list of stages
+ */
+ List getStages(final Context context);
+
+ /**
+ * When step is done and its class is result of this method
+ * then current stage is considered as successfully done.
+ *
+ * @param context
+ * current execution context
+ * @return react classes
+ */
+ Set> getStepDoneTypes(final Context context);
+
+
+ /**
+ * When step is done with an error and its class is result of this method
+ * then current stage is considered as successfully done.
+ *
+ * @param context
+ * current execution context
+ * @return error react classes
+ */
+ Set> getStepErrorTypes(final Context context);
+
+ /**
+ * Stages are shown only once and the time to show stages is defined
+ * by return type of this method. If that step(which type is returned) is
+ * successfully executed then {@link #getStages(Context)} method will be used
+ * to show the stages. It is needed for dynamic stages list detection
+ * (e.g. when workflow configures context in create/update chains).
+ *
+ * @param context
+ * current execution context
+ * @return returns step class after which successful execution stages should be shown
+ */
+ Class extends Step> getDisplayStagesType(final Context context);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/TextChangedHandler.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/TextChangedHandler.java
new file mode 100644
index 0000000000..0cbea6b504
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/parts/contribute/TextChangedHandler.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.parts.contribute;
+
+/**
+ * Used to detect if pull request title/comment/branch is changed.
+ *
+ * @author Yevhenii Voevodin
+ * @see ContributePartView#addBranchChangedHandler(TextChangedHandler)
+ * @see ContributePartView#addContributionCommentChangedHandler(TextChangedHandler)
+ * @see ContributePartView#addContributionTitleChangedHandler(TextChangedHandler)
+ */
+public interface TextChangedHandler {
+
+ /**
+ * Called when title/comment/branch is changed
+ *
+ * @param newText
+ * new text content
+ */
+ void onTextChanged(String newText);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStep.java
new file mode 100644
index 0000000000..33e204fe10
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStep.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.che.api.git.shared.Remote;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Adds the forked remote repository to the remotes of the project.
+ */
+public class AddForkRemoteStep implements Step {
+ private final static String ORIGIN_REMOTE_NAME = "origin";
+ private final static String FORK_REMOTE_NAME = "fork";
+
+ private final Step delegate;
+ private final String remoteUrl;
+ private final VcsServiceProvider vcsServiceProvider;
+ private final ContributeMessages messages;
+
+ @Inject
+ public AddForkRemoteStep(@Assisted("delegate") Step delegate,
+ @Assisted("remoteUrl") String remoteUrl,
+ final VcsServiceProvider vcsServiceProvider,
+ final ContributeMessages messages) {
+ this.delegate = delegate;
+ this.remoteUrl = remoteUrl;
+ this.vcsServiceProvider = vcsServiceProvider;
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final String originRepositoryOwner = context.getOriginRepositoryOwner();
+ final String originRepositoryName = context.getOriginRepositoryName();
+ final String upstreamRepositoryOwner = context.getUpstreamRepositoryOwner();
+ final String upstreamRepositoryName = context.getUpstreamRepositoryName();
+
+ // the fork remote has to be added only if we cloned the upstream else it's origin
+ if (originRepositoryOwner.equalsIgnoreCase(upstreamRepositoryOwner) &&
+ originRepositoryName.equalsIgnoreCase(upstreamRepositoryName)) {
+ checkRemotePresent(executor, context, remoteUrl);
+ } else {
+ context.setForkedRemoteName(ORIGIN_REMOTE_NAME);
+ proceed(executor, context);
+ }
+ }
+
+ private void checkRemotePresent(final WorkflowExecutor executor, final Context context, final String remoteUrl) {
+ vcsServiceProvider.getVcsService(context.getProject()).listRemotes(context.getProject())
+ .then(new Operation>() {
+ @Override
+ public void apply(List result) throws OperationException {
+ for (final Remote remote : result) {
+ if (FORK_REMOTE_NAME.equals(remote.getName())) {
+ context.setForkedRemoteName(FORK_REMOTE_NAME);
+ if (remoteUrl.equals(remote.getUrl())) {
+ // all is correct, continue
+ proceed(executor, context);
+
+ } else {
+ replaceRemote(executor, context, remoteUrl);
+ }
+ // leave the method, do not go to addRemote(...)
+ return;
+ }
+ }
+ addRemote(executor, context, remoteUrl);
+ }
+ }).catchError(new Operation() {
+ @Override
+ public void apply(PromiseError arg) throws OperationException {
+ executor.fail(delegate, context, messages.stepAddForkRemoteErrorCheckRemote());
+ }
+ });
+ }
+
+ /**
+ * Add the remote to the project.
+ *
+ * @param executor
+ * the {@link WorkflowExecutor}.
+ * @param remoteUrl
+ * the url of the remote
+ */
+ private void addRemote(final WorkflowExecutor executor, final Context context, final String remoteUrl) {
+ vcsServiceProvider.getVcsService(context.getProject())
+ .addRemote(context.getProject(), FORK_REMOTE_NAME, remoteUrl, new AsyncCallback() {
+ @Override
+ public void onSuccess(final Void notUsed) {
+ context.setForkedRemoteName(FORK_REMOTE_NAME);
+
+ proceed(executor, context);
+ }
+
+ @Override
+ public void onFailure(final Throwable exception) {
+ executor.fail(delegate, context, messages.stepAddForkRemoteErrorAddFork());
+ }
+ });
+ }
+
+ /**
+ * Removes the fork remote from the project before adding it with the correct URL.
+ *
+ * @param executor
+ * the {@link WorkflowExecutor}.
+ * @param remoteUrl
+ * the url of the remote
+ */
+ private void replaceRemote(final WorkflowExecutor executor, final Context context, final String remoteUrl) {
+ vcsServiceProvider.getVcsService(context.getProject())
+ .deleteRemote(context.getProject(), FORK_REMOTE_NAME, new AsyncCallback() {
+ @Override
+ public void onSuccess(final Void result) {
+ addRemote(executor, context, remoteUrl);
+ }
+
+ @Override
+ public void onFailure(final Throwable caught) {
+ executor.fail(delegate,
+ context,
+ messages.stepAddForkRemoteErrorSetForkedRepositoryRemote());
+ }
+ });
+ }
+
+ private void proceed(final WorkflowExecutor executor, final Context context) {
+ executor.done(delegate, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStepFactory.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStepFactory.java
new file mode 100644
index 0000000000..7acb33353a
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddForkRemoteStepFactory.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import com.google.inject.assistedinject.Assisted;
+
+/**
+ * @author Mihail Kuznyetsov
+ */
+public interface AddForkRemoteStepFactory {
+ AddForkRemoteStep create(@Assisted("delegate") Step delegate,
+ @Assisted("remoteUrl") String remoteUrl);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddHttpForkRemoteStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddHttpForkRemoteStep.java
new file mode 100644
index 0000000000..d803978389
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddHttpForkRemoteStep.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+/**
+ * Add HTTP fork remote URL to repository.
+ *
+ * @author Mihail Kuznyetsov
+ */
+@Singleton
+public class AddHttpForkRemoteStep implements Step {
+ private final AddForkRemoteStepFactory addForkRemoteStepFactory;
+
+ @Inject
+ public AddHttpForkRemoteStep(AddForkRemoteStepFactory addForkRemoteStepFactory) {
+ this.addForkRemoteStepFactory = addForkRemoteStepFactory;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ String remoteUrl = context.getVcsHostingService().makeHttpRemoteUrl(context.getHostUserLogin(), context.getForkedRepositoryName());
+ addForkRemoteStepFactory.create(this, remoteUrl)
+ .execute(executor, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddReviewFactoryLinkStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddReviewFactoryLinkStep.java
new file mode 100644
index 0000000000..09df1d7d5b
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddReviewFactoryLinkStep.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Configuration;
+import com.google.inject.Singleton;
+
+/**
+ * Adds a factory link to the contribution comment.
+ *
+ * @author Kevin Pollet
+ */
+@Singleton
+public class AddReviewFactoryLinkStep implements Step {
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final String reviewFactoryUrl = context.getReviewFactoryUrl();
+ final Configuration contributionConfiguration = context.getConfiguration();
+ final String formattedReviewFactoryUrl = context.getVcsHostingService().formatReviewFactoryUrl(reviewFactoryUrl);
+ final String contributionCommentWithReviewFactoryUrl = formattedReviewFactoryUrl
+ + "\n\n"
+ + contributionConfiguration.getContributionComment();
+ contributionConfiguration.withContributionComment(contributionCommentWithReviewFactoryUrl);
+
+ executor.done(this, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddSshForkRemoteStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddSshForkRemoteStep.java
new file mode 100644
index 0000000000..b895027fb1
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AddSshForkRemoteStep.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+/**
+ * Add SSH fork remote URL to repository.
+ *
+ * @author Mihail Kuznyetsov
+ */
+@Singleton
+public class AddSshForkRemoteStep implements Step {
+ private final AddForkRemoteStepFactory addForkRemoteStepFactory;
+
+ @Inject
+ public AddSshForkRemoteStep(AddForkRemoteStepFactory addForkRemoteStepFactory) {
+ this.addForkRemoteStepFactory = addForkRemoteStepFactory;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ String remoteUrl = context.getVcsHostingService().makeSSHRemoteUrl(context.getHostUserLogin(), context.getForkedRepositoryName());
+ addForkRemoteStepFactory.create(this, remoteUrl)
+ .execute(executor, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AuthorizeCodenvyOnVCSHostStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AuthorizeCodenvyOnVCSHostStep.java
new file mode 100644
index 0000000000..af03755dde
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/AuthorizeCodenvyOnVCSHostStep.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.HostUser;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.commons.exception.UnauthorizedException;
+
+import javax.inject.Inject;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * This step authorizes Codenvy on the VCS Host.
+ *
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class AuthorizeCodenvyOnVCSHostStep implements Step {
+ private final NotificationManager notificationManager;
+ private final AppContext appContext;
+ private final ContributeMessages messages;
+
+ @Inject
+ public AuthorizeCodenvyOnVCSHostStep(final NotificationManager notificationManager,
+ final AppContext appContext,
+ final ContributeMessages messages) {
+ this.notificationManager = notificationManager;
+ this.appContext = appContext;
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, Context context) {
+ context.getVcsHostingService()
+ .getUserInfo()
+ .then(authSuccessOp(executor, context))
+ .catchError(getUserErrorOp(executor, context));
+ }
+
+ private Operation authSuccessOp(final WorkflowExecutor executor, final Context context) {
+ return new Operation() {
+ @Override
+ public void apply(HostUser user) throws OperationException {
+ context.setHostUserLogin(user.getLogin());
+ executor.done(AuthorizeCodenvyOnVCSHostStep.this, context);
+ }
+ };
+ }
+
+ private Operation getUserErrorOp(final WorkflowExecutor executor, final Context context) {
+ return new Operation() {
+ @Override
+ public void apply(PromiseError error) throws OperationException {
+ try {
+ throw error.getCause();
+ } catch (UnauthorizedException unEx) {
+ authenticate(executor, context);
+ } catch (Throwable thr) {
+ handleThrowable(thr, executor, context);
+ }
+ }
+ };
+ }
+
+ private void authenticate(final WorkflowExecutor executor, final Context context) {
+ context.getVcsHostingService()
+ .authenticate(appContext.getCurrentUser())
+ .then(authSuccessOp(executor, context))
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ try {
+ throw err.getCause();
+ } catch (UnauthorizedException unEx) {
+ notificationManager.notify(messages.stepAuthorizeCodenvyOnVCSHostErrorCannotAccessVCSHostTitle(),
+ messages.stepAuthorizeCodenvyOnVCSHostErrorCannotAccessVCSHostContent(),
+ FAIL,
+ FLOAT_MODE);
+ executor.fail(AuthorizeCodenvyOnVCSHostStep.this, context, unEx.getLocalizedMessage());
+ } catch (Throwable thr) {
+ handleThrowable(thr, executor, context);
+ }
+ }
+ });
+ }
+
+ private void handleThrowable(final Throwable thr, final WorkflowExecutor workflow, final Context context) {
+ notificationManager.notify(thr.getLocalizedMessage(), FAIL, FLOAT_MODE);
+ workflow.fail(this, context, thr.getLocalizedMessage());
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CheckBranchToPush.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CheckBranchToPush.java
new file mode 100644
index 0000000000..ca1580663d
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CheckBranchToPush.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Checks that working branch is different from the cloned branch.
+ *
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class CheckBranchToPush implements Step {
+
+ private final ContributeMessages messages;
+
+ @Inject
+ public CheckBranchToPush(final ContributeMessages messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ if (context.getWorkBranchName().equals(context.getContributeToBranchName())) {
+ executor.fail(this,
+ context,
+ messages.stepCheckBranchClonedBranchIsEqualToWorkBranch());
+ } else {
+ executor.done(this, context);
+ }
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CommitWorkingTreeStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CommitWorkingTreeStep.java
new file mode 100644
index 0000000000..b843c8687f
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CommitWorkingTreeStep.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitPresenter;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Configuration;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.ide.api.notification.NotificationManager;
+
+import javax.inject.Inject;
+
+import static org.eclipse.che.plugin.pullrequest.client.dialogs.commit.CommitPresenter.CommitActionHandler.CommitAction.CANCEL;
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * This step allow the user to commit the current working tree if the git repository status is not clean.
+ *
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class CommitWorkingTreeStep implements Step {
+ private final CommitPresenter commitPresenter;
+ private final ContributeMessages messages;
+ private final NotificationManager notificationManager;
+
+ @Inject
+ public CommitWorkingTreeStep(final CommitPresenter commitPresenter,
+ final ContributeMessages messages,
+ final NotificationManager notificationManager) {
+ this.commitPresenter = commitPresenter;
+ this.messages = messages;
+ this.notificationManager = notificationManager;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final Configuration configuration = context.getConfiguration();
+
+ commitPresenter.setCommitActionHandler(new CommitPresenter.CommitActionHandler() {
+ @Override
+ public void onCommitAction(final CommitAction action) {
+ if (action == CANCEL) {
+ executor.fail(CommitWorkingTreeStep.this, context, messages.stepCommitCanceled());
+ } else {
+ executor.done(CommitWorkingTreeStep.this, context);
+ }
+ }
+ });
+ commitPresenter.hasUncommittedChanges(new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ notificationManager.notify(exception.getLocalizedMessage(), FAIL, FLOAT_MODE);
+ executor.fail(CommitWorkingTreeStep.this, context, exception.getLocalizedMessage());
+ }
+
+ @Override
+ public void onSuccess(final Boolean hasUncommittedChanges) {
+ if (hasUncommittedChanges) {
+ commitPresenter
+ .showView(messages.contributorExtensionDefaultCommitDescription(configuration.getContributionBranchName(),
+ configuration.getContributionTitle()));
+ } else {
+ executor.done(CommitWorkingTreeStep.this, context);
+ }
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CreateForkStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CreateForkStep.java
new file mode 100644
index 0000000000..3d632fe56c
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/CreateForkStep.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.NoUserForkException;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Repository;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+
+/**
+ * Create a fork of the contributed project (upstream) to push the user's contribution.
+ */
+@Singleton
+public class CreateForkStep implements Step {
+ private final ContributeMessages messages;
+
+ @Inject
+ public CreateForkStep(final ContributeMessages messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final String originRepositoryOwner = context.getOriginRepositoryOwner();
+ final String originRepositoryName = context.getOriginRepositoryName();
+ final String upstreamRepositoryOwner = context.getUpstreamRepositoryOwner();
+ final String upstreamRepositoryName = context.getUpstreamRepositoryName();
+
+ // the upstream repository has been cloned a fork must be created
+ if (originRepositoryOwner.equalsIgnoreCase(upstreamRepositoryOwner) &&
+ originRepositoryName.equalsIgnoreCase(upstreamRepositoryName)) {
+
+ context.getVcsHostingService()
+ .getUserFork(context.getHostUserLogin(), upstreamRepositoryOwner, upstreamRepositoryName)
+ .then(new Operation() {
+ @Override
+ public void apply(Repository fork) throws OperationException {
+ proceed(fork.getName(), executor, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError error) throws OperationException {
+ if (error.getCause() instanceof NoUserForkException) {
+ createFork(executor, context, upstreamRepositoryOwner, upstreamRepositoryName);
+ return;
+ }
+
+ executor.fail(CreateForkStep.this, context, error.getCause().getMessage());
+ }
+ });
+ } else {
+ // user fork has been cloned
+ proceed(originRepositoryName, executor, context);
+ }
+ }
+
+ private void createFork(final WorkflowExecutor executor,
+ final Context context,
+ final String upstreamRepositoryOwner,
+ final String upstreamRepositoryName) {
+ context.getVcsHostingService()
+ .fork(upstreamRepositoryOwner, upstreamRepositoryName)
+ .then(new Operation() {
+ @Override
+ public void apply(Repository result) throws OperationException {
+ proceed(result.getName(), executor, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ final String errorMessage = messages.stepCreateForkErrorCreatingFork(upstreamRepositoryOwner,
+ upstreamRepositoryName,
+ err.getCause().getMessage());
+ executor.fail(CreateForkStep.this, context, errorMessage);
+ }
+ });
+ }
+
+ private void proceed(final String forkName, final WorkflowExecutor executor, final Context context) {
+ context.setForkedRepositoryName(forkName);
+ executor.done(this, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineExecutionConfiguration.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineExecutionConfiguration.java
new file mode 100644
index 0000000000..08a079b120
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineExecutionConfiguration.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+
+/**
+ * This step defines ability to create forks.
+ *
+ * @author Mihail Kuznyetsov
+ */
+public class DefineExecutionConfiguration implements Step {
+
+ @Override
+ public void execute(WorkflowExecutor executor, Context context) {
+ context.setForkAvailable(!context.getOriginRepositoryOwner().equals(context.getHostUserLogin()));
+ executor.done(this, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineForkRemoteUrlProtocolStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineForkRemoteUrlProtocolStep.java
new file mode 100644
index 0000000000..c0563dcf53
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineForkRemoteUrlProtocolStep.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+
+/**
+ * @author Mihail Kuznyetsov
+ */
+public class DefineForkRemoteUrlProtocolStep implements Step {
+ @Override
+ public void execute(WorkflowExecutor executor, Context context) {
+ context.setSshAvailable(false);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineWorkBranchStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineWorkBranchStep.java
new file mode 100644
index 0000000000..ac44a2c3e6
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DefineWorkBranchStep.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsService;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+
+import javax.inject.Inject;
+import javax.validation.constraints.NotNull;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * This step defines the working branch for the user contribution.
+ *
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class DefineWorkBranchStep implements Step {
+
+ private final NotificationManager notificationManager;
+ private final VcsServiceProvider vcsServiceProvider;
+
+ @Inject
+ public DefineWorkBranchStep(final NotificationManager notificationManager, final VcsServiceProvider vcsServiceProvider) {
+ this.notificationManager = notificationManager;
+ this.vcsServiceProvider = vcsServiceProvider;
+ }
+
+ @Override
+ public void execute(@NotNull final WorkflowExecutor executor, final Context context) {
+ final VcsService vcsService = vcsServiceProvider.getVcsService(context.getProject());
+
+ vcsService.getBranchName(context.getProject())
+ .then(new Operation() {
+ @Override
+ public void apply(String branchName) throws OperationException {
+ if (context.getContributeToBranchName() == null) {
+ context.setContributeToBranchName(branchName);
+ }
+ context.setWorkBranchName(branchName);
+ executor.done(DefineWorkBranchStep.this, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ notificationManager.notify(err.getCause().getLocalizedMessage(), FAIL, FLOAT_MODE);
+ executor.fail(DefineWorkBranchStep.this, context, err.getCause().getLocalizedMessage());
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetectPullRequestStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetectPullRequestStep.java
new file mode 100644
index 0000000000..3d4a1e7262
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetectPullRequestStep.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+import org.eclipse.che.ide.api.notification.StatusNotification;
+
+import static org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowStatus.READY_TO_UPDATE_PR;
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+
+/**
+ * Detects if pull request exists for current working branch,
+ * stops creation workflow if so and toggles update pull request mode.
+ *
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class DetectPullRequestStep implements Step {
+
+ private final ContributeMessages messages;
+ private final NotificationManager notificationManager;
+
+ @Inject
+ public DetectPullRequestStep(ContributeMessages messages,
+ NotificationManager manager) {
+ this.messages = messages;
+ this.notificationManager = manager;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ context.getVcsHostingService()
+ .getPullRequest(context.getOriginRepositoryOwner(),
+ context.getOriginRepositoryName(),
+ context.getHostUserLogin(),
+ context.getWorkBranchName())
+ .then(new Operation() {
+ @Override
+ public void apply(final PullRequest pr) throws OperationException {
+ notificationManager.notify(messages.stepDetectPrExistsTitle(),
+ messages.stepDetectPrExistsTitle(context.getWorkBranchName()),
+ StatusNotification.Status.FAIL,
+ FLOAT_MODE);
+ context.setPullRequest(pr);
+ context.setPullRequestIssueNumber(pr.getNumber());
+ context.setForkedRepositoryName(context.getOriginRepositoryName());
+ context.setStatus(READY_TO_UPDATE_PR);
+ executor.fail(DetectPullRequestStep.this, context, messages.stepDetectPrExistsTitle());
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(final PromiseError error) throws OperationException {
+ // keep going if pr already exists
+ executor.done(DetectPullRequestStep.this, context);
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetermineUpstreamRepositoryStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetermineUpstreamRepositoryStep.java
new file mode 100644
index 0000000000..fb4bd06142
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/DetermineUpstreamRepositoryStep.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Repository;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * Determines what is the upstream repository and updates the context with
+ * {@link Context#getUpstreamRepositoryOwner()} () repository owner} and
+ * {@link Context#getUpstreamRepositoryName()} repository name}.
+ *
+ *
The algorithm is simple: if origin repository is user's fork
+ * then get its parent and set as upstream repo, otherwise use
+ * {@link Context#getOriginRepositoryOwner() origin owner} and
+ * {@link Context#getOriginRepositoryName() origin name}.
+ *
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class DetermineUpstreamRepositoryStep implements Step {
+
+ private final NotificationManager notificationManager;
+
+ @Inject
+ public DetermineUpstreamRepositoryStep(NotificationManager notificationManager) {
+ this.notificationManager = notificationManager;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final VcsHostingService hostingService = context.getVcsHostingService();
+ hostingService.getRepository(context.getOriginRepositoryOwner(), context.getOriginRepositoryName())
+ .then(new Operation() {
+ @Override
+ public void apply(Repository repo) throws OperationException {
+ if (repo.isFork() && context.getOriginRepositoryOwner().equalsIgnoreCase(context.getHostUserLogin())) {
+ final String upstreamUrl = repo.getParent().getCloneUrl();
+ context.setUpstreamRepositoryName(hostingService.getRepositoryNameFromUrl(upstreamUrl));
+ context.setUpstreamRepositoryOwner(hostingService.getRepositoryOwnerFromUrl(upstreamUrl));
+ } else {
+ context.setUpstreamRepositoryName(context.getOriginRepositoryName());
+ context.setUpstreamRepositoryOwner(context.getOriginRepositoryOwner());
+ }
+ executor.done(DetermineUpstreamRepositoryStep.this, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError error) throws OperationException {
+ notificationManager.notify(error.getMessage(), FAIL, FLOAT_MODE);
+ executor.fail(DetermineUpstreamRepositoryStep.this, context, error.getMessage());
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/GenerateReviewFactoryStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/GenerateReviewFactoryStep.java
new file mode 100644
index 0000000000..b95e5234fe
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/GenerateReviewFactoryStep.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.utils.FactoryHelper;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.factory.shared.dto.FactoryDto;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.FunctionException;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.factory.FactoryServiceClient;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+
+import javax.inject.Inject;
+
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * Generates a factory for the contribution reviewer.
+ */
+@Singleton
+public class GenerateReviewFactoryStep implements Step {
+ private final ContributeMessages messages;
+ private final AppContext appContext;
+ private final NotificationManager notificationManager;
+ private final FactoryServiceClient factoryService;
+
+ @Inject
+ public GenerateReviewFactoryStep(final ContributeMessages messages,
+ final AppContext appContext,
+ final NotificationManager notificationManager,
+ final FactoryServiceClient factoryService) {
+ this.messages = messages;
+ this.appContext = appContext;
+ this.notificationManager = notificationManager;
+ this.factoryService = factoryService;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ factoryService.getFactoryJson(appContext.getWorkspaceId(), null)
+ .then(updateProjectAttributes(context))
+ .then(new Operation() {
+ @Override
+ public void apply(FactoryDto factory) throws OperationException {
+ factoryService.saveFactory(factory)
+ .then(new Operation() {
+ @Override
+ public void apply(FactoryDto factory) throws OperationException {
+ context.setReviewFactoryUrl(FactoryHelper.getAcceptFactoryUrl(factory));
+ executor.done(GenerateReviewFactoryStep.this, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError arg) throws OperationException {
+ notificationManager.notify(messages.stepGenerateReviewFactoryErrorCreateFactory(),
+ FAIL,
+ NOT_EMERGE_MODE);
+ executor.done(GenerateReviewFactoryStep.this, context);
+ }
+ });
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError arg) throws OperationException {
+ notificationManager.notify(messages.stepGenerateReviewFactoryErrorCreateFactory(),
+ FAIL,
+ NOT_EMERGE_MODE);
+ executor.done(GenerateReviewFactoryStep.this, context);
+ }
+ });
+ }
+
+ private Function updateProjectAttributes(final Context context) {
+ return new Function() {
+ @Override
+ public FactoryDto apply(FactoryDto factory) throws FunctionException {
+ final Optional projectOpt = FluentIterable.from(factory.getWorkspace().getProjects())
+ .filter(new Predicate() {
+ @Override
+ public boolean apply(ProjectConfigDto project) {
+ return project.getName()
+ .equals(context.getProject().getName());
+ }
+ }).first();
+ if (projectOpt.isPresent()) {
+ final ProjectConfigDto project = projectOpt.get();
+ project.getSource().getParameters().put("branch", context.getWorkBranchName());
+
+ if (context.isForkAvailable()) {
+ project.getSource().setLocation(context.getVcsHostingService()
+ .makeHttpRemoteUrl(context.getHostUserLogin(), context.getOriginRepositoryName()));
+ }
+ }
+ return factory;
+ }
+ };
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/InitializeWorkflowContextStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/InitializeWorkflowContextStep.java
new file mode 100644
index 0000000000..9fb0cb86c9
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/InitializeWorkflowContextStep.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+import org.eclipse.che.api.git.shared.Remote;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.notification.NotificationManager;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Map;
+
+import static org.eclipse.che.plugin.pullrequest.shared.ContributionProjectTypeConstants.CONTRIBUTE_TO_BRANCH_VARIABLE_NAME;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
+import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
+
+/**
+ * This step initialize the contribution workflow context.
+ *
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class InitializeWorkflowContextStep implements Step {
+
+ private static final Predicate ORIGIN_REMOTE_FILTER = new Predicate() {
+ @Override
+ public boolean apply(Remote remote) {
+ return remote.getName().equals("origin");
+ }
+ };
+
+ private final VcsServiceProvider vcsServiceProvider;
+ private final NotificationManager notificationManager;
+ private final ContributeMessages messages;
+
+ @Inject
+ public InitializeWorkflowContextStep(final VcsServiceProvider vcsServiceProvider,
+ final NotificationManager notificationManager,
+ final ContributeMessages messages) {
+ this.vcsServiceProvider = vcsServiceProvider;
+ this.notificationManager = notificationManager;
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ vcsServiceProvider.getVcsService(context.getProject())
+ .listRemotes(context.getProject())
+ .then(setUpOriginRepoOp(executor, context))
+ .catchError(errorSettingUpOriginRepoOp(executor, context));
+ }
+
+ private Operation> setUpOriginRepoOp(final WorkflowExecutor executor, final Context context) {
+ return new Operation>() {
+ @Override
+ public void apply(final List remotes) throws OperationException {
+ final Optional remoteOpt = FluentIterable.from(remotes)
+ .filter(ORIGIN_REMOTE_FILTER)
+ .first();
+ if (remoteOpt.isPresent()) {
+ final Remote remote = remoteOpt.get();
+ final String originUrl = remote.getUrl();
+ final VcsHostingService vcsHostingService = context.getVcsHostingService();
+
+ context.setOriginRepositoryOwner(vcsHostingService.getRepositoryOwnerFromUrl(originUrl));
+ context.setOriginRepositoryName(vcsHostingService.getRepositoryNameFromUrl(originUrl));
+
+ setContributeToBranchName(context);
+
+ executor.done(InitializeWorkflowContextStep.this, context);
+ } else {
+ notificationManager.notify(messages.stepInitWorkflowOriginRemoteNotFound(), FAIL, FLOAT_MODE);
+ executor.fail(InitializeWorkflowContextStep.this, context, messages.stepInitWorkflowOriginRemoteNotFound());
+ }
+ }
+ };
+ }
+
+ protected void setContributeToBranchName(Context context) {
+ String contributeToBranchName = getBranchFromProjectMetadata(context.getProject());
+
+ if (contributeToBranchName != null) {
+ context.setContributeToBranchName(contributeToBranchName);
+ return;
+ }
+
+ vcsServiceProvider.getVcsService(context.getProject())
+ .getBranchName(context.getProject())
+ .then(
+ (String branchName) -> {
+ context.setContributeToBranchName(branchName);
+ context.getProject().getSource().getParameters().put("branch", branchName);
+ });
+ }
+
+ private String getBranchFromProjectMetadata(final ProjectConfig project) {
+ final Map> attrs = project.getAttributes();
+ if (attrs.containsKey(CONTRIBUTE_TO_BRANCH_VARIABLE_NAME) && !attrs.get(CONTRIBUTE_TO_BRANCH_VARIABLE_NAME).isEmpty()) {
+ return attrs.get(CONTRIBUTE_TO_BRANCH_VARIABLE_NAME).get(0);
+ }
+ if (project.getSource() != null) {
+ final String branchName = project.getSource().getParameters().get("branch");
+ if (!isNullOrEmpty(branchName)) {
+ return branchName;
+ }
+ }
+ return null;
+ }
+
+ private Operation errorSettingUpOriginRepoOp(final WorkflowExecutor executor, final Context context) {
+ return new Operation() {
+ @Override
+ public void apply(final PromiseError error) throws OperationException {
+ notificationManager.notify(messages.contributorExtensionErrorSetupOriginRepository(error.getMessage()),
+ FAIL,
+ FLOAT_MODE);
+ executor.fail(InitializeWorkflowContextStep.this, context, error.getMessage());
+ }
+ };
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/IssuePullRequestStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/IssuePullRequestStep.java
new file mode 100644
index 0000000000..6706fb9dfd
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/IssuePullRequestStep.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.NoCommitsInPullRequestException;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.NoHistoryInCommonException;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.PullRequestAlreadyExistsException;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Configuration;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+
+import javax.inject.Inject;
+
+/**
+ * Create the pull request on the remote VCS repository.
+ *
+ * @author Kevin Pollet
+ */
+@Singleton
+public class IssuePullRequestStep implements Step {
+ private final ContributeMessages messages;
+
+ @Inject
+ public IssuePullRequestStep(final ContributeMessages messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final Configuration configuration = context.getConfiguration();
+ context.getVcsHostingService()
+ .createPullRequest(context.getUpstreamRepositoryOwner(),
+ context.getUpstreamRepositoryName(),
+ context.getHostUserLogin(),
+ context.getWorkBranchName(),
+ context.getContributeToBranchName(),
+ configuration.getContributionTitle(),
+ configuration.getContributionComment())
+ .then(new Operation() {
+ @Override
+ public void apply(PullRequest pullRequest) throws OperationException {
+ context.setPullRequestIssueNumber(pullRequest.getNumber());
+ context.setPullRequest(pullRequest);
+ executor.done(IssuePullRequestStep.this, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(final PromiseError exception) throws OperationException {
+ try {
+ throw exception.getCause();
+ } catch (PullRequestAlreadyExistsException | NoHistoryInCommonException ex) {
+ executor.fail(IssuePullRequestStep.this,
+ context,
+ ex.getLocalizedMessage());
+ } catch (NoCommitsInPullRequestException noCommitsEx) {
+ executor.fail(IssuePullRequestStep.this,
+ context,
+ messages.stepIssuePullRequestErrorCreatePullRequestWithoutCommits());
+ } catch (Throwable thr) {
+ executor.fail(IssuePullRequestStep.this,
+ context,
+ messages.stepIssuePullRequestErrorCreatePullRequest());
+ }
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnForkStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnForkStep.java
new file mode 100644
index 0000000000..d8dff65421
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnForkStep.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+/**
+ * Push the local contribution branch on the user fork.
+ *
+ * @author Kevin Pollet
+ */
+@Singleton
+public class PushBranchOnForkStep implements Step {
+
+ private final PushBranchStepFactory pushBranchStepFactory;
+
+ @Inject
+ public PushBranchOnForkStep(PushBranchStepFactory pushBranchStepFactory) {
+ this.pushBranchStepFactory = pushBranchStepFactory;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ pushBranchStepFactory.create(this,
+ context.getHostUserLogin(),
+ context.getForkedRepositoryName())
+ .execute(executor, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnOriginStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnOriginStep.java
new file mode 100644
index 0000000000..f63b314c58
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchOnOriginStep.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import com.google.inject.Singleton;
+
+import javax.inject.Inject;
+
+/**
+ * Push the local contribution branch to origin repository
+ *
+ * @author Mihail Kuznyetsov
+ */
+@Singleton
+public class PushBranchOnOriginStep implements Step {
+
+ private final static String ORIGIN_REMOTE_NAME = "origin";
+
+ private final PushBranchStepFactory pushBranchStepFactory;
+
+ @Inject
+ public PushBranchOnOriginStep(PushBranchStepFactory pushBranchStepFactory) {
+ this.pushBranchStepFactory = pushBranchStepFactory;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ context.setForkedRemoteName(ORIGIN_REMOTE_NAME);
+ pushBranchStepFactory.create(this,
+ context.getOriginRepositoryOwner(),
+ context.getOriginRepositoryName())
+ .execute(executor, context);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStep.java
new file mode 100644
index 0000000000..42e80eda97
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStep.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.ide.util.loging.Log;
+import org.eclipse.che.plugin.pullrequest.client.ContributeMessages;
+import org.eclipse.che.plugin.pullrequest.client.vcs.BranchUpToDateException;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.NoPullRequestException;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.SyntheticStep;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.che.api.git.shared.PushResponse;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.api.ssh.shared.dto.SshPairDto;
+import org.eclipse.che.ide.api.dialogs.CancelCallback;
+import org.eclipse.che.ide.api.dialogs.ConfirmCallback;
+import org.eclipse.che.ide.api.dialogs.DialogFactory;
+import org.eclipse.che.ide.api.ssh.SshServiceClient;
+
+/**
+ * Pushes branch to the repository.
+ *
+ * @author Yevhenii Voevodin
+ */
+public class PushBranchStep implements SyntheticStep {
+
+ private final Step delegate;
+ private final String repositoryOwner;
+ private final String repositoryName;
+ private final ContributeMessages messages;
+ private final DialogFactory dialogFactory;
+ private final SshServiceClient sshService;
+
+ @AssistedInject
+ public PushBranchStep(@Assisted("delegate") Step delegate,
+ @Assisted("repositoryOwner") String repositoryOwner,
+ @Assisted("repositoryName") String repositoryName,
+ ContributeMessages messages,
+ DialogFactory dialogFactory,
+ SshServiceClient sshService) {
+ this.delegate = delegate;
+ this.repositoryOwner = repositoryOwner;
+ this.repositoryName = repositoryName;
+ this.messages = messages;
+ this.dialogFactory = dialogFactory;
+ this.sshService = sshService;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+
+ /*
+ * Check if a Pull Request with given base and head branches already exists.
+ * If there is none, push the contribution branch.
+ * If there is one, propose to updatePullRequest the pull request.
+ */
+
+ context.getVcsHostingService()
+ .getPullRequest(repositoryOwner,
+ repositoryName,
+ context.getHostUserLogin(),
+ context.getWorkBranchName())
+ .then(new Operation() {
+ @Override
+ public void apply(PullRequest pullRequest) throws OperationException {
+ context.setPullRequest(pullRequest);
+ context.getConfiguration().withContributionComment(pullRequest.getDescription());
+ final ConfirmCallback okCallback = new ConfirmCallback() {
+ @Override
+ public void accepted() {
+ pushBranch(executor, context);
+ }
+ };
+ final CancelCallback cancelCallback = new CancelCallback() {
+ @Override
+ public void cancelled() {
+ executor.fail(delegate, context, messages.stepPushBranchCanceling());
+ }
+ };
+
+ dialogFactory.createConfirmDialog(
+ messages.contributePartConfigureContributionDialogUpdateTitle(),
+ messages.contributePartConfigureContributionDialogUpdateText(
+ pullRequest.getHeadRef()),
+ okCallback,
+ cancelCallback).show();
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ try {
+ throw err.getCause();
+ } catch (NoPullRequestException ex) {
+ pushBranch(executor, context);
+ } catch (Throwable thr) {
+ executor.fail(delegate, context, thr.getMessage());
+ }
+ }
+ });
+ }
+
+ private void pushBranch(final WorkflowExecutor workflow, final Context context) {
+ context.getVcsService()
+ .pushBranch(context.getProject(),
+ context.getForkedRemoteName(),
+ context.getWorkBranchName())
+ .then(new Operation() {
+ @Override
+ public void apply(PushResponse result) throws OperationException {
+ workflow.done(delegate, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ try {
+ throw err.getCause();
+ } catch (BranchUpToDateException branchUpEx) {
+ workflow.fail(delegate,
+ context,
+ messages.stepPushBranchErrorBranchUpToDate());
+ } catch (Throwable throwable) {
+ if (throwable.getMessage().contains("Unable get private ssh key")) {
+ askGenerateSSH(workflow, context);
+ } else {
+ workflow.fail(delegate,
+ context,
+ messages.stepPushBranchErrorPushingBranch(throwable.getLocalizedMessage()));
+ }
+ }
+ }
+ });
+ }
+
+ private void askGenerateSSH(final WorkflowExecutor executor, final Context context) {
+ final ConfirmCallback okCallback = new ConfirmCallback() {
+ @Override
+ public void accepted() {
+ generateSSHAndPushBranch(executor, context, context.getVcsHostingService().getHost());
+ }
+ };
+
+ final CancelCallback cancelCallback = new CancelCallback() {
+ @Override
+ public void cancelled() {
+ executor.fail(delegate, context, messages.stepPushBranchCanceling());
+ }
+ };
+
+ dialogFactory.createConfirmDialog(messages.contributePartConfigureContributionDialogSshNotFoundTitle(),
+ messages.contributePartConfigureContributionDialogSshNotFoundText(),
+ okCallback,
+ cancelCallback).show();
+ }
+
+ private void generateSSHAndPushBranch(final WorkflowExecutor executor, final Context context, String host) {
+ sshService.generatePair("vcs", host)
+ .then(new Operation() {
+ @Override
+ public void apply(SshPairDto arg) throws OperationException {
+ pushBranch(executor, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ executor.fail(delegate, context, err.getMessage());
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStepFactory.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStepFactory.java
new file mode 100644
index 0000000000..446ac8a1bc
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/PushBranchStepFactory.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import com.google.inject.assistedinject.Assisted;
+
+public interface PushBranchStepFactory {
+
+ PushBranchStep create(@Assisted("delegate") Step delegate,
+ @Assisted("repositoryOwner") String repositoryOwner,
+ @Assisted("repositoryName") String repositoryName);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/UpdatePullRequestStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/UpdatePullRequestStep.java
new file mode 100644
index 0000000000..1e51ec28c9
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/UpdatePullRequestStep.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.utils.FactoryHelper;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Configuration;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import com.google.common.base.Strings;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.factory.shared.dto.FactoryDto;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.factory.FactoryServiceClient;
+
+/**
+ * Used for toggling last step mark.
+ *
+ * @author Yevhenii Voevodin
+ * @author Anton Korneta
+ */
+@Singleton
+public class UpdatePullRequestStep implements Step {
+ private static final RegExp SCHEMA = RegExp.compile("[^\\.]*(f\\?id=)([a-zA-Z0-9]*)[^\\.]*");
+
+ private final FactoryServiceClient factoryService;
+ private final AppContext appContext;
+
+ @Inject
+ public UpdatePullRequestStep(FactoryServiceClient factoryService,
+ AppContext appContext) {
+ this.factoryService = factoryService;
+ this.appContext = appContext;
+ }
+
+ @Override
+ public void execute(final WorkflowExecutor executor, final Context context) {
+ final PullRequest pullRequest = context.getPullRequest();
+ factoryService.getFactoryJson(appContext.getWorkspaceId(), null)
+ .then(new Operation() {
+ @Override
+ public void apply(FactoryDto currentFactory) throws OperationException {
+ final String factoryId = extractFactoryId(pullRequest.getDescription());
+ if (!Strings.isNullOrEmpty(factoryId)) {
+ updateFactory(executor, context, factoryId, currentFactory);
+ } else {
+ addReviewUrl(executor, context, currentFactory);
+ }
+ }
+ })
+ .catchError(handleError(executor, context));
+ }
+
+ private Promise updateFactory(final WorkflowExecutor executor,
+ final Context context,
+ final String factoryId,
+ final FactoryDto currentFactory) {
+ return factoryService.updateFactory(factoryId, currentFactory)
+ .then(new Operation() {
+ @Override
+ public void apply(FactoryDto updatedFactory) throws OperationException {
+ context.setReviewFactoryUrl(FactoryHelper.getAcceptFactoryUrl(updatedFactory));
+ executor.done(UpdatePullRequestStep.this, context);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError error) throws OperationException {
+ createNewFactory(executor,
+ context,
+ currentFactory,
+ new Operation() {
+ @Override
+ public void apply(FactoryDto factory) throws OperationException {
+ final PullRequest pull = context.getPullRequest();
+ doUpdate(executor,
+ context,
+ pull,
+ pull.getDescription().replaceAll(factoryId, factory.getId()));
+ }
+ });
+ }
+ });
+ }
+
+ private void addReviewUrl(final WorkflowExecutor executor,
+ final Context context,
+ final FactoryDto currentFactory) {
+ createNewFactory(executor,
+ context,
+ currentFactory,
+ new Operation() {
+ @Override
+ public void apply(FactoryDto factory) throws OperationException {
+ final Configuration configuration = context.getConfiguration();
+ final String reviewUrl = context.getVcsHostingService()
+ .formatReviewFactoryUrl(FactoryHelper.getAcceptFactoryUrl(factory));
+ context.setReviewFactoryUrl(reviewUrl);
+ final String comment = reviewUrl + "\n" + configuration.getContributionComment();
+ configuration.withContributionComment(comment);
+ doUpdate(executor, context, context.getPullRequest(), comment);
+ }
+ });
+ }
+
+
+ private void createNewFactory(final WorkflowExecutor executor,
+ final Context context,
+ final FactoryDto factory,
+ Operation operation) {
+ factoryService.saveFactory(factory).then(operation).catchError(handleError(executor, context));
+ }
+
+ private void doUpdate(final WorkflowExecutor executor,
+ final Context context,
+ final PullRequest pullRequest,
+ final String comment) {
+ context.getVcsHostingService()
+ .updatePullRequest(context.getOriginRepositoryOwner(),
+ context.getUpstreamRepositoryName(),
+ pullRequest.withDescription(comment))
+ .then(new Operation() {
+ @Override
+ public void apply(PullRequest pr) throws OperationException {
+ executor.done(UpdatePullRequestStep.this, context);
+ }
+ })
+ .catchError(handleError(executor, context));
+ }
+
+ private String extractFactoryId(String description) {
+ if (SCHEMA.test(description)) {
+ return SCHEMA.exec(description).getGroup(2);
+ }
+ return null;
+ }
+
+ private Operation handleError(final WorkflowExecutor executor, final Context context) {
+ return new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ context.getViewState().setStatusMessage(err.getMessage(), true);
+ executor.fail(UpdatePullRequestStep.this, context, err.getMessage());
+ }
+ };
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStep.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStep.java
new file mode 100644
index 0000000000..dd24685c35
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStep.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingServiceProvider;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Context;
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+import org.eclipse.che.plugin.pullrequest.client.workflow.WorkflowExecutor;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Repository;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.PromiseError;
+
+import javax.validation.constraints.NotNull;
+
+public class WaitForkOnRemoteStep implements Step {
+ private static final int POLL_FREQUENCY_MS = 1000;
+
+ private final VcsHostingServiceProvider vcsHostingServiceProvider;
+ private final Step nextStep;
+ private Timer timer;
+
+ @AssistedInject
+ public WaitForkOnRemoteStep(@NotNull final VcsHostingServiceProvider vcsHostingServiceProvider,
+ @NotNull final @Assisted Step nextStep) {
+ this.vcsHostingServiceProvider = vcsHostingServiceProvider;
+ this.nextStep = nextStep;
+ }
+
+ @Override
+ public void execute(@NotNull final WorkflowExecutor executor, final Context context) {
+ if (timer == null) {
+ timer = new Timer() {
+ @Override
+ public void run() {
+ checkRepository(context, new AsyncCallback() {
+ @Override
+ public void onFailure(final Throwable caught) {
+ timer.schedule(POLL_FREQUENCY_MS);
+ }
+
+ @Override
+ public void onSuccess(final Void result) {
+ executor.done(WaitForkOnRemoteStep.this, context);
+ }
+ });
+ }
+ };
+ }
+
+ timer.schedule(POLL_FREQUENCY_MS);
+ }
+
+ private void checkRepository(final Context context, final AsyncCallback callback) {
+ context.getVcsHostingService().getRepository(context.getHostUserLogin(), context.getForkedRepositoryName())
+ .then(new Operation() {
+ @Override
+ public void apply(Repository arg) throws OperationException {
+ callback.onSuccess(null);
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError arg) throws OperationException {
+ callback.onFailure(arg.getCause());
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStepFactory.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStepFactory.java
new file mode 100644
index 0000000000..4c79183124
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/steps/WaitForkOnRemoteStepFactory.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.steps;
+
+import org.eclipse.che.plugin.pullrequest.client.workflow.Step;
+
+/**
+ * Factory for {@link WaitForkOnRemoteStep}.
+ */
+public interface WaitForkOnRemoteStepFactory {
+ WaitForkOnRemoteStep create(Step nextStep);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/utils/FactoryHelper.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/utils/FactoryHelper.java
new file mode 100644
index 0000000000..d99ee447f9
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/utils/FactoryHelper.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.utils;
+
+import org.eclipse.che.api.core.rest.shared.dto.Link;
+import org.eclipse.che.api.factory.shared.dto.FactoryDto;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Helper providing methods to work with factory.
+ *
+ * @author Kevin Pollet
+ */
+public final class FactoryHelper {
+ private static final String ACCEPT_FACTORY_LINK_REF = "accept";
+
+ /**
+ * Disable instantiation.
+ */
+ private FactoryHelper() {
+ }
+
+ /**
+ * Returns the create project relation link for the given factory.
+ *
+ * @param factory
+ * the factory.
+ * @return the create project url or {@code null} if none.
+ */
+ public static String getAcceptFactoryUrl(@NotNull FactoryDto factory) {
+ for (final Link oneLink : factory.getLinks()) {
+ if (ACCEPT_FACTORY_LINK_REF.equals(oneLink.getRel())) {
+ return oneLink.getHref();
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/BranchUpToDateException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/BranchUpToDateException.java
new file mode 100644
index 0000000000..fa0d6a0dfd
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/BranchUpToDateException.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Exception raised when the branch pushed is up to date.
+ *
+ * @author Kevin Pollet
+ */
+public class BranchUpToDateException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link BranchUpToDateException}.
+ *
+ * @param branchName
+ * the branch name.
+ */
+ public BranchUpToDateException(@NotNull final String branchName) {
+ super("Branch '" + branchName + "' is up-to-date");
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/GitVcsService.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/GitVcsService.java
new file mode 100644
index 0000000000..011bd2d205
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/GitVcsService.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+import org.eclipse.che.api.git.shared.Branch;
+import org.eclipse.che.api.git.shared.BranchListMode;
+import org.eclipse.che.api.git.shared.CheckoutRequest;
+import org.eclipse.che.api.git.shared.PushResponse;
+import org.eclipse.che.api.git.shared.Remote;
+import org.eclipse.che.api.git.shared.Revision;
+import org.eclipse.che.api.git.shared.Status;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.FunctionException;
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.api.promises.client.js.JsPromiseError;
+import org.eclipse.che.api.promises.client.js.Promises;
+import org.eclipse.che.ide.api.app.AppContext;
+import org.eclipse.che.ide.api.git.GitServiceClient;
+import org.eclipse.che.ide.dto.DtoFactory;
+import org.eclipse.che.ide.resource.Path;
+import org.eclipse.che.ide.rest.AsyncRequestCallback;
+import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
+import org.eclipse.che.ide.rest.Unmarshallable;
+import org.eclipse.che.ide.websocket.WebSocketException;
+import org.eclipse.che.ide.websocket.rest.RequestCallback;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Git backed implementation for {@link VcsService}.
+ */
+@Singleton
+public class GitVcsService implements VcsService {
+ private static final String BRANCH_UP_TO_DATE_ERROR_MESSAGE = "Everything up-to-date";
+
+ private final GitServiceClient service;
+ private final DtoFactory dtoFactory;
+ private final DtoUnmarshallerFactory dtoUnmarshallerFactory;
+ private final AppContext appContext;
+
+ @Inject
+ public GitVcsService(final DtoFactory dtoFactory,
+ final DtoUnmarshallerFactory dtoUnmarshallerFactory,
+ final GitServiceClient service,
+ final AppContext appContext) {
+ this.dtoFactory = dtoFactory;
+ this.dtoUnmarshallerFactory = dtoUnmarshallerFactory;
+ this.service = service;
+ this.appContext = appContext;
+ }
+
+ @Override
+ public void addRemote(@NotNull final ProjectConfig project, @NotNull final String remote, @NotNull final String remoteUrl,
+ @NotNull final AsyncCallback callback) {
+
+ service.remoteAdd(appContext.getDevMachine(), project, remote, remoteUrl, new AsyncRequestCallback() {
+ @Override
+ protected void onSuccess(final String notUsed) {
+ callback.onSuccess(null);
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+ }
+
+ @Override
+ public void checkoutBranch(@NotNull final ProjectConfig project, @NotNull final String name,
+ final boolean createNew, @NotNull final AsyncCallback callback) {
+
+ service.checkout(appContext.getDevMachine(),
+ project,
+ dtoFactory.createDto(CheckoutRequest.class)
+ .withName(name)
+ .withCreateNew(createNew),
+ new AsyncRequestCallback() {
+ @Override
+ protected void onSuccess(final String branchName) {
+ callback.onSuccess(branchName);
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+ }
+
+ @Override
+ public void commit(@NotNull final ProjectConfig project, final boolean includeUntracked, @NotNull final String commitMessage,
+ @NotNull final AsyncCallback callback) {
+ try {
+
+ service.add(appContext.getDevMachine(), project, !includeUntracked, null, new RequestCallback() {
+ @Override
+ protected void onSuccess(Void aVoid) {
+
+ service.commit(appContext.getDevMachine(), project, commitMessage, true, false, new AsyncRequestCallback() {
+ @Override
+ protected void onSuccess(final Revision revision) {
+ callback.onSuccess(null);
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+
+ } catch (final WebSocketException exception) {
+ callback.onFailure(exception);
+ }
+ }
+
+ @Override
+ public void deleteRemote(@NotNull final ProjectConfig project, @NotNull final String remote,
+ @NotNull final AsyncCallback callback) {
+ service.remoteDelete(appContext.getDevMachine(), project, remote, new AsyncRequestCallback() {
+ @Override
+ protected void onSuccess(final String notUsed) {
+ callback.onSuccess(null);
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+ }
+
+ @Override
+ public Promise getBranchName(ProjectConfig project) {
+ return service.getStatus(appContext.getDevMachine(), Path.valueOf(project.getPath()))
+ .then(new Function() {
+ @Override
+ public String apply(Status status) throws FunctionException {
+ return status.getBranchName();
+ }
+ });
+ }
+
+ @Override
+ public void hasUncommittedChanges(@NotNull final ProjectConfig project, @NotNull final AsyncCallback callback) {
+ service.getStatus(appContext.getDevMachine(), Path.valueOf(project.getPath()))
+ .then(new Operation() {
+ @Override
+ public void apply(Status status) throws OperationException {
+ callback.onSuccess(!status.isClean());
+ }
+ })
+ .catchError(new Operation() {
+ @Override
+ public void apply(PromiseError err) throws OperationException {
+ callback.onFailure(err.getCause());
+ }
+ });
+ }
+
+ @Override
+ public void isLocalBranchWithName(@NotNull final ProjectConfig project, @NotNull final String branchName,
+ @NotNull final AsyncCallback callback) {
+
+ listLocalBranches(project, new AsyncCallback>() {
+ @Override
+ public void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+
+ @Override
+ public void onSuccess(final List branches) {
+ for (final Branch oneBranch : branches) {
+ if (oneBranch.getDisplayName().equals(branchName)) {
+ callback.onSuccess(true);
+ return;
+ }
+ }
+ callback.onSuccess(false);
+ }
+ });
+ }
+
+ @Override
+ public void listLocalBranches(@NotNull final ProjectConfig project, @NotNull final AsyncCallback> callback) {
+ listBranches(project, null, callback);
+ }
+
+ @Override
+ public Promise> listRemotes(ProjectConfig project) {
+ return service.remoteList(appContext.getDevMachine(), project, null, false);
+ }
+
+ @Override
+ public Promise pushBranch(final ProjectConfig project, final String remote, final String localBranchName) {
+ return service.push(appContext.getDevMachine(), project, Collections.singletonList(localBranchName), remote, true)
+ .catchErrorPromise(new Function>() {
+ @Override
+ public Promise apply(PromiseError error) throws FunctionException {
+ if (BRANCH_UP_TO_DATE_ERROR_MESSAGE.equalsIgnoreCase(error.getMessage())) {
+ return Promises.reject(JsPromiseError.create(new BranchUpToDateException(localBranchName)));
+ } else {
+ return Promises.reject(error);
+ }
+ }
+ });
+ }
+
+ /**
+ * List branches of a given type.
+ *
+ * @param project
+ * the project descriptor.
+ * @param listMode
+ * null -> list local branches; "r" -> list remote branches; "a" -> list all branches.
+ * @param callback
+ * callback when the operation is done.
+ */
+ private void listBranches(final ProjectConfig project, final BranchListMode listMode, final AsyncCallback> callback) {
+ final Unmarshallable> unMarshaller =
+ dtoUnmarshallerFactory.newListUnmarshaller(Branch.class);
+ service.branchList(appContext.getDevMachine(), project, listMode,
+ new AsyncRequestCallback>(unMarshaller) {
+ @Override
+ protected void onSuccess(final List branches) {
+ final List result = new ArrayList<>();
+ for (final Branch branch : branches) {
+ result.add(fromGitBranch(branch));
+ }
+ callback.onSuccess(result);
+ }
+
+ @Override
+ protected void onFailure(final Throwable exception) {
+ callback.onFailure(exception);
+ }
+ });
+ }
+
+ /**
+ * Converts a git branch DTO to an abstracted {@link org.eclipse.che.api.git.shared.Branch} object.
+ *
+ * @param gitBranch
+ * the object to convert.
+ * @return the converted object.
+ */
+ private Branch fromGitBranch(final Branch gitBranch) {
+ final Branch branch = GitVcsService.this.dtoFactory.createDto(Branch.class);
+ branch.withActive(gitBranch.isActive()).withRemote(gitBranch.isRemote())
+ .withName(gitBranch.getName()).withDisplayName(gitBranch.getDisplayName());
+ return branch;
+ }
+
+ /**
+ * Converts a git remote DTO to an abstracted {@link org.eclipse.che.api.git.shared.Remote} object.
+ *
+ * @param gitRemote
+ * the object to convert.
+ * @return the converted object.
+ */
+ private Remote fromGitRemote(final Remote gitRemote) {
+ final Remote remote = GitVcsService.this.dtoFactory.createDto(Remote.class);
+ remote.withName(gitRemote.getName()).withUrl(gitRemote.getUrl());
+ return remote;
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsService.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsService.java
new file mode 100644
index 0000000000..92f14bec79
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsService.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+import org.eclipse.che.api.git.shared.Branch;
+import org.eclipse.che.api.git.shared.PushResponse;
+import org.eclipse.che.api.git.shared.Remote;
+import org.eclipse.che.api.promises.client.Promise;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * Service for VCS operations.
+ */
+public interface VcsService {
+
+ /**
+ * Add a remote to the project VCS metadata.
+ *
+ * @param project
+ * the project descriptor.
+ * @param remote
+ * the remote name.
+ * @param remoteUrl
+ * the remote URL.
+ * @param callback
+ * callback when the operation is done.
+ */
+ void addRemote(@NotNull ProjectConfig project, @NotNull String remote, @NotNull String remoteUrl,
+ @NotNull AsyncCallback callback);
+
+ /**
+ * Checkout a branch of the given project.
+ *
+ * @param project
+ * the project descriptor.
+ * @param branchName
+ * the name of the branch to checkout.
+ * @param createNew
+ * create a new branch if {@code true}.
+ * @param callback
+ * callback when the operation is done.
+ */
+ void checkoutBranch(@NotNull ProjectConfig project, @NotNull String branchName, boolean createNew,
+ @NotNull AsyncCallback callback);
+
+ /**
+ * Commits the current changes of the given project.
+ *
+ * @param project
+ * the project descriptor.
+ * @param includeUntracked
+ * {@code true} to include untracked files, {@code false} otherwise.
+ * @param commitMessage
+ * the commit message.
+ * @param callback
+ * callback when the operation is done.
+ */
+ void commit(@NotNull ProjectConfig project, boolean includeUntracked, @NotNull String commitMessage,
+ @NotNull AsyncCallback callback);
+
+ /**
+ * Removes a remote to the project VCS metadata.
+ *
+ * @param project
+ * the project descriptor.
+ * @param remote
+ * the remote name.
+ * @param callback
+ * callback when the operation is done.
+ */
+ void deleteRemote(@NotNull ProjectConfig project, @NotNull String remote, @NotNull AsyncCallback callback);
+
+ /**
+ * Returns the name of the current branch for the given {@code project}.
+ *
+ * @param project
+ * the project.
+ * @return the promise that resolves branch name or rejects with an error
+ */
+ Promise getBranchName(ProjectConfig project);
+
+ /**
+ * Returns if the given project has uncommitted changes.
+ *
+ * @param project
+ * the project descriptor.
+ * @param callback
+ * what to do if the project has uncommitted changes.
+ */
+ void hasUncommittedChanges(@NotNull ProjectConfig project, @NotNull AsyncCallback callback);
+
+ /**
+ * Returns if a local branch with the given name exists in the given project.
+ *
+ * @param project
+ * the project descriptor.
+ * @param branchName
+ * the branch name.
+ * @param callback
+ * callback called when operation is done.
+ */
+ void isLocalBranchWithName(@NotNull ProjectConfig project, @NotNull String branchName, @NotNull AsyncCallback callback);
+
+ /**
+ * List the local branches.
+ *
+ * @param project
+ * the project descriptor.
+ * @param callback
+ * what to do with the branches list.
+ */
+ void listLocalBranches(@NotNull ProjectConfig project, @NotNull AsyncCallback> callback);
+
+ /**
+ * Returns the list of the remotes for given {@code project}.
+ *
+ * @param project
+ * the project
+ * @return the promise which resolves {@literal List} or rejects with an error
+ */
+ Promise> listRemotes(ProjectConfig project);
+
+ /**
+ * Push a local branch to remote.
+ *
+ * @param project
+ * the project descriptor.
+ * @param remote
+ * the remote name
+ * @param localBranchName
+ * the local branch name
+ */
+ Promise pushBranch(ProjectConfig project,
+ String remote,
+ String localBranchName);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsServiceProvider.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsServiceProvider.java
new file mode 100644
index 0000000000..8f133871a1
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/VcsServiceProvider.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+
+import javax.inject.Inject;
+import javax.validation.constraints.NotNull;
+
+import static org.eclipse.che.ide.ext.git.client.GitUtil.isUnderGit;
+
+/**
+ * Provider for the {@link VcsService}.
+ *
+ * @author Kevin Pollet
+ */
+public class VcsServiceProvider {
+ private final GitVcsService gitVcsService;
+
+ @Inject
+ public VcsServiceProvider(@NotNull final GitVcsService gitVcsService) {
+ this.gitVcsService = gitVcsService;
+ }
+
+ /**
+ * Returns the {@link VcsService} implementation corresponding to the current project VCS.
+ *
+ * @return the {@link VcsService} implementation or {@code null} if not supported or not
+ * initialized.
+ */
+ public VcsService getVcsService(final ProjectConfig project) {
+ if (project != null) {
+ if (isUnderGit(project)) {
+ return gitVcsService;
+ }
+ }
+ return null;
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/HostingServiceTemplates.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/HostingServiceTemplates.java
new file mode 100644
index 0000000000..8dcc5c2e15
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/HostingServiceTemplates.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import com.google.gwt.i18n.client.Messages;
+
+/**
+ * Hosting service templates.
+ *
+ * @author Kevin Pollet
+ */
+public interface HostingServiceTemplates extends Messages {
+ /**
+ * The SSH URL to a repository.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @return the URL
+ */
+ String sshUrlTemplate(String username, String repository);
+
+ /**
+ * The HTTP URL to a repository.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @return the URL
+ */
+ String httpUrlTemplate(String username, String repository);
+
+ /**
+ * The URL to a pull request.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @param pullRequestNumber
+ * the pull request number.
+ * @return the URL
+ */
+ String pullRequestUrlTemplate(String username, String repository, String pullRequestNumber);
+
+ /**
+ * The formatted version of the review factory url using the Hosting service markup language.
+ *
+ * @param protocol
+ * the protocol used http or https
+ * @param host
+ * the host.
+ * @param reviewFactoryUrl
+ * the review factory url.
+ * @return the formatted version of the review factory url
+ */
+ String formattedReviewFactoryUrlTemplate(String protocol, String host, String reviewFactoryUrl);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoCommitsInPullRequestException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoCommitsInPullRequestException.java
new file mode 100644
index 0000000000..20edbe12d5
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoCommitsInPullRequestException.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Exception raised when a pull request is created with no commits.
+ *
+ * @author Kevin Pollet
+ */
+public class NoCommitsInPullRequestException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link NoCommitsInPullRequestException}.
+ *
+ * @param headBranch
+ * the head branch name.
+ * @param baseBranch
+ * the base branch name.
+ */
+ public NoCommitsInPullRequestException(@NotNull final String headBranch, @NotNull final String baseBranch) {
+ super("No commits between " + baseBranch + " and " + headBranch);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoHistoryInCommonException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoHistoryInCommonException.java
new file mode 100644
index 0000000000..a1d0667127
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoHistoryInCommonException.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+/**
+ * This exception should be thrown when separate branches have no commits in common.
+ *
+ * @author Anton Korneta
+ */
+public class NoHistoryInCommonException extends Exception {
+
+ public NoHistoryInCommonException(String msg) {
+ super(msg);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoPullRequestException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoPullRequestException.java
new file mode 100644
index 0000000000..f8697a5280
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoPullRequestException.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import javax.validation.constraints.NotNull;
+
+public class NoPullRequestException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link NoPullRequestException}.
+ *
+ * @param branchName
+ * the branch name.
+ */
+ public NoPullRequestException(@NotNull final String branchName) {
+ super("No Pull Request for branch " + branchName);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoUserForkException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoUserForkException.java
new file mode 100644
index 0000000000..3cd476e028
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoUserForkException.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Exception raised when trying to get a fork of a repository for a user and no fork being found.
+ */
+public class NoUserForkException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link NoUserForkException}.
+ *
+ * @param user
+ * the user.
+ */
+ public NoUserForkException(@NotNull final String user) {
+ super("No fork for user: " + user);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoVcsHostingServiceImplementationException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoVcsHostingServiceImplementationException.java
new file mode 100644
index 0000000000..cad7855f64
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/NoVcsHostingServiceImplementationException.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+/**
+ * Exception raised when there is no {@link VcsHostingService} implementation for the
+ * current project.
+ *
+ * @author Kevin Pollet
+ */
+public class NoVcsHostingServiceImplementationException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link NoVcsHostingServiceImplementationException}.
+ */
+ public NoVcsHostingServiceImplementationException() {
+ super("No implementation of the VcsHostingService for the current project");
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/PullRequestAlreadyExistsException.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/PullRequestAlreadyExistsException.java
new file mode 100644
index 0000000000..4616c6cb91
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/PullRequestAlreadyExistsException.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Exception raised when a pull request already exists for a branch.
+ *
+ * @author Kevin Pollet
+ */
+public class PullRequestAlreadyExistsException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance of {@link PullRequestAlreadyExistsException}.
+ *
+ * @param headBranch
+ * the head branch name.
+ */
+ public PullRequestAlreadyExistsException(@NotNull final String headBranch) {
+ super("A pull request for " + headBranch + " already exists");
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java
new file mode 100644
index 0000000000..c68b1c6bf5
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import org.eclipse.che.plugin.pullrequest.shared.dto.HostUser;
+
+import org.eclipse.che.api.promises.client.Operation;
+import org.eclipse.che.api.promises.client.OperationException;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.PromiseError;
+import org.eclipse.che.api.promises.client.js.Executor;
+import org.eclipse.che.api.promises.client.js.Promises;
+import org.eclipse.che.api.promises.client.js.RejectFunction;
+import org.eclipse.che.api.promises.client.js.ResolveFunction;
+import org.eclipse.che.security.oauth.JsOAuthWindow;
+import org.eclipse.che.security.oauth.OAuthCallback;
+import org.eclipse.che.security.oauth.OAuthStatus;
+
+/**
+ * Utils for {@link VcsHostingService} implementations.
+ *
+ * @author Yevhenii Voevodin
+ */
+public final class ServiceUtil {
+
+ /**
+ * Performs {@link JsOAuthWindow} authentication and tries to get current user.
+ *
+ * @param service
+ * hosting service, used to authorized user
+ * @param authUrl
+ * url to perform authentication
+ * @return the promise which resolves authorized user or rejects with an error
+ */
+ public static Promise performWindowAuth(final VcsHostingService service, final String authUrl) {
+ final Executor.ExecutorBody exBody = new Executor.ExecutorBody() {
+ @Override
+ public void apply(final ResolveFunction resolve, final RejectFunction reject) {
+ new JsOAuthWindow(authUrl, "error.url", 500, 980, new OAuthCallback() {
+ @Override
+ public void onAuthenticated(final OAuthStatus authStatus) {
+ // maybe it's possible to avoid this request if authStatus contains the vcs host user.
+ service.getUserInfo().then(new Operation() {
+ @Override
+ public void apply(HostUser user) throws OperationException {
+ resolve.apply(user);
+ }
+ }).catchError(new Operation() {
+ @Override
+ public void apply(PromiseError error) throws OperationException {
+ reject.apply(error);
+ }
+ });
+ }
+ }).loginWithOAuth();
+ }
+ };
+ return Promises.create(Executor.create(exBody));
+ }
+
+ private ServiceUtil() {}
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingService.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingService.java
new file mode 100644
index 0000000000..7abd401cad
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingService.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import org.eclipse.che.plugin.pullrequest.shared.dto.HostUser;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Repository;
+
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.ide.api.app.CurrentUser;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * Represents a repository host
+ *
+ * @author Kevin Pollet
+ */
+public interface VcsHostingService {
+
+ /**
+ * Initializes new implementation if additional data from remote url is required
+ *
+ * @param remoteUrl
+ * @return
+ */
+ VcsHostingService init(String remoteUrl);
+
+ /**
+ * Returns the VCS Host name.
+ *
+ * @return the VCS Host name never {@code null}.
+ */
+ @NotNull
+ String getName();
+
+ /**
+ * Returns the VCS Host.
+ *
+ * @return the VCS Host never {@code null}.
+ */
+ @NotNull
+ String getHost();
+
+ /**
+ * Checks if the given remote URL is hosted by this service.
+ *
+ * @param remoteUrl
+ * the remote url to check.
+ * @return {@code true} if the given remote url is hosted by this service, {@code false} otherwise.
+ */
+ boolean isHostRemoteUrl(@NotNull String remoteUrl);
+
+ /**
+ * Get a pull request by qualified name.
+ *
+ * @param owner
+ * the repository owner.
+ * @param repository
+ * the repository name.
+ * @param username
+ * the user name.
+ * @param branchName
+ * pull request branch name.
+ */
+ Promise getPullRequest(@NotNull String owner,
+ @NotNull String repository,
+ @NotNull String username,
+ @NotNull String branchName);
+
+ /**
+ * Creates a pull request.
+ *
+ * @param owner
+ * the repository owner.
+ * @param repository
+ * the repository name.
+ * @param username
+ * the user name.
+ * @param headBranchName
+ * the head branch name.
+ * @param baseBranchName
+ * the base branch name.
+ * @param title
+ * the pull request title.
+ * @param body
+ * the pull request body.
+ */
+ Promise createPullRequest(String owner,
+ String repository,
+ String username,
+ String headBranchName,
+ String baseBranchName,
+ String title,
+ String body);
+
+ /**
+ * Forks the given repository for the current user.
+ *
+ * @param owner
+ * the repository owner.
+ * @param repository
+ * the repository name.
+ */
+ Promise fork(String owner, String repository);
+
+ /**
+ * Returns the promise which either resolves repository or rejects with an error.
+ *
+ * @param owner
+ * the owner of the repositoryName
+ * @param repositoryName
+ * the name of the repository
+ */
+ Promise getRepository(String owner, String repositoryName);
+
+ /**
+ * Returns the repository name from the given url.
+ *
+ * @param url
+ * the url.
+ * @return the repository name, never {@code null}.
+ */
+ @NotNull
+ String getRepositoryNameFromUrl(@NotNull String url);
+
+ /**
+ * Returns the repository owner from the given url.
+ *
+ * @param url
+ * the url.
+ * @return the repository owner, never {@code null}.
+ */
+ @NotNull
+ String getRepositoryOwnerFromUrl(@NotNull String url);
+
+ /**
+ * Returns the repository fork of the given user.
+ *
+ * @param user
+ * the user.
+ * @param owner
+ * the repository owner.
+ * @param repository
+ * the repository name.
+ */
+ Promise getUserFork(String user, String owner, String repository);
+
+ /**
+ * Returns the user information on the repository host.
+ */
+ Promise getUserInfo();
+
+ /**
+ * Makes the remote SSH url for the given username and repository.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @return the remote url.
+ */
+ String makeSSHRemoteUrl(@NotNull String username, @NotNull String repository);
+
+ /**
+ * Makes the remote HTTP url for the given username and repository.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @return the remote url.
+ */
+ String makeHttpRemoteUrl(@NotNull String username, @NotNull String repository);
+
+ /**
+ * Makes the pull request url for the given username, repository and pull request number.
+ *
+ * @param username
+ * the user name.
+ * @param repository
+ * the repository name.
+ * @param pullRequestNumber
+ * the pull request number.
+ * @return the remote url.
+ */
+ String makePullRequestUrl(@NotNull String username, @NotNull String repository, @NotNull String pullRequestNumber);
+
+ /**
+ * Use the VCS hosting comment markup language to format the review factory URL.
+ *
+ * @param reviewFactoryUrl
+ * the review factory URL to format.
+ * @return the formatted review factory URL.
+ */
+ @NotNull
+ String formatReviewFactoryUrl(@NotNull String reviewFactoryUrl);
+
+ /**
+ * Authenticates the current user on the hosting service.
+ *
+ * @param user
+ * the user to authenticate
+ * @return the promise which resolves host user or rejects with an error
+ */
+ Promise authenticate(CurrentUser user);
+
+ /**
+ * Update pull request information e.g. title, description
+ *
+ * @param owner
+ * repository owner
+ * @param repository
+ * name of repository
+ * @param pullRequest
+ * pull request for update
+ * @return updated pull request
+ */
+ Promise updatePullRequest(String owner, String repository, PullRequest pullRequest);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingServiceProvider.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingServiceProvider.java
new file mode 100644
index 0000000000..36a3a18cb5
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/VcsHostingServiceProvider.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.vcs.hosting;
+
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsService;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsServiceProvider;
+import com.google.inject.Singleton;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+import org.eclipse.che.api.git.shared.Remote;
+import org.eclipse.che.api.promises.client.Function;
+import org.eclipse.che.api.promises.client.FunctionException;
+import org.eclipse.che.api.promises.client.Promise;
+import org.eclipse.che.api.promises.client.js.JsPromiseError;
+import org.eclipse.che.api.promises.client.js.Promises;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Provider for the {@link VcsHostingService}.
+ *
+ * @author Kevin Pollet
+ * @author Yevhenii Voevodin
+ */
+@Singleton
+public class VcsHostingServiceProvider {
+ private static final String ORIGIN_REMOTE_NAME = "origin";
+
+ private final VcsServiceProvider vcsServiceProvider;
+ private final Set vcsHostingServices;
+
+ @Inject
+ public VcsHostingServiceProvider(final VcsServiceProvider vcsServiceProvider,
+ final Set vcsHostingServices) {
+ this.vcsServiceProvider = vcsServiceProvider;
+ this.vcsHostingServices = vcsHostingServices;
+ }
+
+ /**
+ * Returns the dedicated {@link VcsHostingService} implementation for the {@link #ORIGIN_REMOTE_NAME origin} remote.
+ *
+ * @param project
+ * project used to find origin remote and extract VCS hosting service
+ */
+ public Promise getVcsHostingService(final ProjectConfig project) {
+ if (project == null) {
+ return Promises.reject(JsPromiseError.create(new NoVcsHostingServiceImplementationException()));
+ }
+ final VcsService vcsService = vcsServiceProvider.getVcsService(project);
+ if (vcsService == null) {
+ return Promises.reject(JsPromiseError.create(new NoVcsHostingServiceImplementationException()));
+ }
+ return vcsService.listRemotes(project)
+ .then(new Function, VcsHostingService>() {
+ @Override
+ public VcsHostingService apply(List remotes) throws FunctionException {
+ for (Remote remote : remotes) {
+ if (ORIGIN_REMOTE_NAME.equals(remote.getName())) {
+ for (final VcsHostingService hostingService : vcsHostingServices) {
+ if (hostingService.isHostRemoteUrl(remote.getUrl())) {
+ return hostingService.init(remote.getUrl());
+ }
+ }
+ }
+ }
+ throw new FunctionException(new NoVcsHostingServiceImplementationException());
+ }
+ });
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ChainExecutor.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ChainExecutor.java
new file mode 100644
index 0000000000..5b9ce52370
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ChainExecutor.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.workflow;
+
+import com.google.common.base.Optional;
+
+import org.eclipse.che.ide.util.loging.Log;
+
+import java.util.Iterator;
+
+import static com.google.common.base.Optional.fromNullable;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Executor for the {@link StepsChain}.
+ * If the chain is modified after executor is created (e.g. new step added to the chain)
+ * executor state won't be affected and newly added steps will be ignored by executor.
+ *
+ * @author Yevhenii Voevodin
+ */
+public final class ChainExecutor {
+
+ private final Iterator chainIt;
+
+ private Step currentStep;
+
+ public ChainExecutor(final StepsChain chain) {
+ chainIt = requireNonNull(chain, "Expected non-null steps chain").getSteps().iterator();
+ }
+
+ /**
+ * Executes the next chain step, does nothing - if there are no steps left .
+ *
+ * @param workflow
+ * the contribution workflow
+ * @param context
+ * the context for current chain execution
+ */
+ public void execute(final WorkflowExecutor workflow, final Context context) {
+ if (chainIt.hasNext()) {
+ currentStep = chainIt.next();
+ Log.info(getClass(), "Executing :: " + context.getProject().getName() + " :: => " + currentStep.getClass());
+ currentStep.execute(workflow, context);
+ }
+ }
+
+ /**
+ * Returns an empty optional when current step is null, otherwise
+ * returns the optional which contains current step value.
+ */
+ public Optional getCurrentStep() {
+ return fromNullable(currentStep);
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Context.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Context.java
new file mode 100644
index 0000000000..c3909ad2a6
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Context.java
@@ -0,0 +1,443 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.workflow;
+
+import org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent;
+import org.eclipse.che.plugin.pullrequest.client.vcs.VcsService;
+import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService;
+import org.eclipse.che.plugin.pullrequest.shared.dto.Configuration;
+import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest;
+import com.google.web.bindery.event.shared.EventBus;
+
+import org.eclipse.che.api.core.model.project.ProjectConfig;
+import org.eclipse.che.commons.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent.ContextProperty.CONTRIBUTE_TO_BRANCH_NAME;
+import static org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent.ContextProperty.PROJECT;
+import static org.eclipse.che.plugin.pullrequest.client.events.ContextPropertyChangeEvent.ContextProperty.WORK_BRANCH_NAME;
+
+/**
+ * Context used to share information between the steps in the contribution workflow.
+ *
+ * @author Kevin Pollet
+ */
+public class Context {
+ /** The event bus. */
+ private final EventBus eventBus;
+
+ /** The project. */
+ private ProjectConfig project;
+
+ /** The name of the branch to contribute to. */
+ private String contributeToBranchName;
+
+ /** The name of the working branch. */
+ private String workBranchName;
+
+ /** The name of the user on host VCS. */
+ private String hostUserLogin;
+
+ /** The name of the owner of the repository forked on VCS. */
+ private String upstreamRepositoryOwner;
+
+ /** The name of the repository forked on VCS. */
+ private String upstreamRepositoryName;
+
+ /** The name of the owner of the repository cloned on VCS. */
+ private String originRepositoryOwner;
+
+ /** The name of the repository cloned on VCS. */
+ private String originRepositoryName;
+
+ /** The identifier of the pull request on the hosting service. */
+ private PullRequest pullRequest;
+
+ /** The issue number of the pull request issued for the contribution. */
+ private String pullRequestIssueNumber;
+
+ /** The generated review factory URL. */
+ private String reviewFactoryUrl;
+
+ /** The name of the forked remote. */
+ private String forkedRemoteName;
+
+ /** The name of the forked repository. */
+ private String forkedRepositoryName;
+
+ /** Defines availability of fork creation. */
+ private boolean forkAvailable;
+
+ /** Defines ability to use ssh URLs. */
+ private boolean sshAvailable;
+
+ private VcsHostingService vcsHostingService;
+
+ /** The name of the origin remote. */
+ private String originRemoteName;
+ private WorkflowStatus status;
+ private WorkflowStatus previousStatus;
+ private Configuration configuration;
+ public ViewState viewState;
+ private VcsService vcsService;
+
+ public Context(final EventBus eventBus) {
+ this.eventBus = eventBus;
+ viewState = new ViewState();
+ }
+
+ public ProjectConfig getProject() {
+ return project;
+ }
+
+ public void setProject(final ProjectConfig project) {
+ final ProjectConfig oldValue = this.project;
+ this.project = project;
+
+ fireContextPropertyChange(PROJECT, oldValue, project);
+ }
+
+ public String getContributeToBranchName() {
+ return contributeToBranchName;
+ }
+
+ public void setContributeToBranchName(final String contributeToBranchName) {
+ final String oldValue = this.contributeToBranchName;
+ this.contributeToBranchName = contributeToBranchName;
+
+ fireContextPropertyChange(CONTRIBUTE_TO_BRANCH_NAME, oldValue, contributeToBranchName);
+ }
+
+ public String getWorkBranchName() {
+ return workBranchName;
+ }
+
+ public void setWorkBranchName(final String workBranchName) {
+ final String oldValue = this.workBranchName;
+ this.workBranchName = workBranchName;
+
+ fireContextPropertyChange(WORK_BRANCH_NAME, oldValue, workBranchName);
+ }
+
+ public String getHostUserLogin() {
+ return hostUserLogin;
+ }
+
+ public void setHostUserLogin(final String hostUserLogin) {
+ this.hostUserLogin = hostUserLogin;
+ }
+
+ public String getUpstreamRepositoryOwner() {
+ return upstreamRepositoryOwner;
+ }
+
+ public void setUpstreamRepositoryOwner(String upstreamRepositoryOwner) {
+ this.upstreamRepositoryOwner = upstreamRepositoryOwner;
+ }
+
+ public String getUpstreamRepositoryName() {
+ return upstreamRepositoryName;
+ }
+
+ public void setUpstreamRepositoryName(String upstreamRepositoryName) {
+ this.upstreamRepositoryName = upstreamRepositoryName;
+ }
+
+ public String getOriginRepositoryOwner() {
+ return originRepositoryOwner;
+ }
+
+ public void setOriginRepositoryOwner(final String originRepositoryOwner) {
+ final String oldValue = this.originRepositoryOwner;
+ this.originRepositoryOwner = originRepositoryOwner;
+
+ fireContextPropertyChange(ContextPropertyChangeEvent.ContextProperty.ORIGIN_REPOSITORY_OWNER, oldValue, originRepositoryOwner);
+ }
+
+ public String getOriginRepositoryName() {
+ return originRepositoryName;
+ }
+
+ public void setOriginRepositoryName(final String originRepositoryName) {
+ final String oldValue = this.originRepositoryName;
+ this.originRepositoryName = originRepositoryName;
+
+ fireContextPropertyChange(ContextPropertyChangeEvent.ContextProperty.ORIGIN_REPOSITORY_NAME, oldValue, originRepositoryName);
+ }
+
+ public PullRequest getPullRequest() {
+ return pullRequest;
+ }
+
+ public void setPullRequest(PullRequest pullRequest) {
+ this.pullRequest = pullRequest;
+ }
+
+ /**
+ * Return the issue number of the pull request issued for this contribution.
+ *
+ * @return the pull request issue id
+ */
+ public String getPullRequestIssueNumber() {
+ return pullRequestIssueNumber;
+ }
+
+ /**
+ * Sets the issue number of the pull request issued for this contribution.
+ *
+ * @param pullRequestIssueNumber
+ * the new value
+ */
+ public void setPullRequestIssueNumber(final String pullRequestIssueNumber) {
+ this.pullRequestIssueNumber = pullRequestIssueNumber;
+ }
+
+ /**
+ * Returns the generated review factory URL (if available).
+ *
+ * @return factory URL
+ */
+ public String getReviewFactoryUrl() {
+ return this.reviewFactoryUrl;
+ }
+
+ /**
+ * Sets the generated review factory URL (if available).
+ *
+ * @param reviewFactoryUrl
+ * new value
+ */
+ public void setReviewFactoryUrl(final String reviewFactoryUrl) {
+ this.reviewFactoryUrl = reviewFactoryUrl;
+ }
+
+ public String getOriginRemoteName() {
+ return originRemoteName;
+ }
+
+ public void setOriginRemoteName(String originRemoteName) {
+ this.originRemoteName = originRemoteName;
+ }
+
+ public String getForkedRemoteName() {
+ return forkedRemoteName;
+ }
+
+ public void setForkedRemoteName(String forkedRemoteName) {
+ this.forkedRemoteName = forkedRemoteName;
+ }
+
+ public String getForkedRepositoryName() {
+ return forkedRepositoryName;
+ }
+
+ public void setForkedRepositoryName(String forkedRepositoryName) {
+ this.forkedRepositoryName = forkedRepositoryName;
+ }
+
+ private void fireContextPropertyChange(final ContextPropertyChangeEvent.ContextProperty contextProperty,
+ final Object oldValue,
+ final Object newValue) {
+ if (!Objects.equals(oldValue, newValue)) {
+ eventBus.fireEvent(new ContextPropertyChangeEvent(this, contextProperty));
+ }
+ }
+
+ public boolean isUpdateMode() {
+ return status == WorkflowStatus.UPDATING_PR || status == WorkflowStatus.READY_TO_UPDATE_PR;
+ }
+
+ public VcsHostingService getVcsHostingService() {
+ return vcsHostingService;
+ }
+
+ public void setVcsHostingService(VcsHostingService service) {
+ this.vcsHostingService = service;
+ }
+
+ @Nullable
+ public WorkflowStatus getStatus() {
+ return status;
+ }
+
+ @Nullable
+ public WorkflowStatus getPreviousStatus() {
+ return previousStatus;
+ }
+
+ public void setStatus(@Nullable WorkflowStatus status) {
+ this.previousStatus = this.status;
+ this.status = status;
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ public ViewState getViewState() {
+ return viewState;
+ }
+
+ public void setVcsService(VcsService vcsService) {
+ this.vcsService = vcsService;
+ }
+
+ public VcsService getVcsService() {
+ return vcsService;
+ }
+
+ public boolean isForkAvailable() {
+ return forkAvailable;
+ }
+ public void setForkAvailable(boolean forkAvailable) {
+ this.forkAvailable = forkAvailable;
+ }
+
+ public boolean isSshAvailable() {
+ return sshAvailable;
+ }
+
+ public void setSshAvailable(boolean sshAvailable) {
+ this.sshAvailable = sshAvailable;
+ }
+
+ public static final class ViewState {
+
+ private String contributionTitle;
+ private String contributionComment;
+ private StatusMessage statusMessage;
+ private List stages;
+ private int currentStage;
+
+ private ViewState() {
+ currentStage = 0;
+ }
+
+ public void setStatusMessage(String message, boolean error) {
+ this.statusMessage = new StatusMessage(message, error);
+ }
+
+ public void setStatusMessage(StatusMessage message) {
+ this.statusMessage = message;
+ }
+
+ public void setContributionTitle(String title) {
+ this.contributionTitle = title;
+ }
+
+ public String getContributionTitle() {
+ return contributionTitle;
+ }
+
+ public void setContributionComment(String contributionComment) {
+ this.contributionComment = contributionComment;
+ }
+
+ public String getContributionComment() {
+ return contributionComment;
+ }
+
+ public List getStages() {
+ if (stages == null) {
+ stages = new ArrayList<>(3);
+ }
+ return stages;
+ }
+
+ public List getStageNames() {
+ final List statusNames = new ArrayList<>(getStages().size());
+ for (Stage stepStatus : getStages()) {
+ statusNames.add(stepStatus.getName());
+ }
+ return statusNames;
+ }
+
+ public List getStageValues() {
+ final List statusNames = new ArrayList<>(getStages().size());
+ for (Stage stepStatus : getStages()) {
+ statusNames.add(stepStatus.getStatus());
+ }
+ return statusNames;
+ }
+
+ public void resetStages() {
+ getStages().clear();
+ currentStage = 0;
+ }
+
+ public void setStep(String name, Boolean value) {
+ getStages().add(new Stage(name, value));
+ }
+
+ public void setStages(List stages) {
+ resetStages();
+ for (String newStage : stages) {
+ setStep(newStage, null);
+ }
+ }
+
+ public StatusMessage getStatusMessage() {
+ return statusMessage;
+ }
+
+ public void setStageDone(boolean stepDone) {
+ getStages().get(currentStage++).setStatus(stepDone);
+ }
+
+ public static class StatusMessage {
+ private final boolean error;
+ private final String message;
+
+ private StatusMessage(String message, boolean error) {
+ this.message = message;
+ this.error = error;
+ }
+
+ public boolean isError() {
+ return error;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
+
+ public static class Stage {
+ private final String name;
+
+ private Boolean status;
+
+ public Stage(String name, Boolean status) {
+ this.status = status;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Boolean getStatus() {
+ return status;
+ }
+
+ public void setStatus(Boolean status) {
+ this.status = status;
+ }
+ }
+ }
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ContributionWorkflow.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ContributionWorkflow.java
new file mode 100644
index 0000000000..aa0280de7b
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/ContributionWorkflow.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.workflow;
+
+/**
+ * Defines contribution workflow.
+ *
+ *
The contribution workflow consists of 3 main steps:
+ * initialization, pull request creation, pull request update.
+ * According to these 3 steps implementation should provide steps chains
+ * based on specific VCS Hosting service.
+ *
+ *
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface ContributionWorkflow {
+
+ /** Returns the steps chain which should be executed when plugin initializes. */
+ StepsChain initChain(Context context);
+
+ /** Returns the steps chain which should be executed when pull request should be created. */
+ StepsChain creationChain(Context context);
+
+ /** Returns the steps chain which should be executed for the pull request updatePullRequest. */
+ StepsChain updateChain(Context context);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Step.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Step.java
new file mode 100644
index 0000000000..ffc6f5f9bb
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/Step.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.workflow;
+
+import org.eclipse.che.plugin.pullrequest.client.events.StepEvent;
+import org.eclipse.che.plugin.pullrequest.client.steps.PushBranchStep;
+
+/**
+ * Contract for a step in the contribution workflow.
+ *
+ *
Step should not depend on another steps as it is
+ * a workflow part but workflow is defined by {@link ContributionWorkflow} implementations.
+ * Each step should end its execution with either {@link WorkflowExecutor#done(Step, Context)}
+ * or {@link WorkflowExecutor#fail(Step, Context, String)} method.
+ *
+ *
{@link WorkflowExecutor} fires {@link StepEvent} for each
+ * done/fail step if this step is not {@link SyntheticStep}.
+ *
+ *
If step contains common logic for several steps
+ * then this logic should be either extracted to the other
+ * step or used along with factory (e.g. {@link PushBranchStep}).
+ *
+ * @author Yevhenii Voevodin
+ */
+public interface Step {
+
+ /**
+ * Executes this step.
+ *
+ * @param executor
+ * contribution workflow executor
+ */
+ void execute(final WorkflowExecutor executor, final Context context);
+}
diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/StepsChain.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/StepsChain.java
new file mode 100644
index 0000000000..5d50e7c4aa
--- /dev/null
+++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/workflow/StepsChain.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2017 Codenvy, S.A.
+ * 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:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.plugin.pullrequest.client.workflow;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Defines a chain of steps.
+ *
+ * @author Yevhenii Voevodin
+ * @see ChainExecutor
+ * @see ContributionWorkflow
+ */
+public final class StepsChain {
+
+ /**
+ * Creates a new {@code StepsChain} with the initial step.
+ *
+ * @param firstStep
+ * the first step in the chain
+ * @return new chain instance
+ * @throws NullPointerException
+ * when {@code firstStep} is null
+ */
+ public static StepsChain first(Step firstStep) {
+ return new StepsChain().then(firstStep);
+ }
+
+ private final List steps;
+
+ private StepsChain() {
+ steps = new ArrayList<>();
+ }
+
+ /**
+ * Returns the list of the steps currently added to this chain.
+ * The list is unmodifiable copy of added steps, so next chain modification
+ * won't affect returned list.
+ */
+ public List getSteps() {
+ return ImmutableList.copyOf(steps);
+ }
+
+ /**
+ * Adds the step to the chain.
+ *
+ * @param step
+ * the next chain step
+ * @throws NullPointerException
+ * when {@code step} is null
+ */
+ public StepsChain then(Step step) {
+ steps.add(step);
+ return this;
+ }
+
+ /**
+ * Adds {@code stepIfTrue} to the chain, and executes added step only
+ * if supplier supplies true.
+ *
+ * @param supplier
+ * supplier of a boolean value
+ * @param stepIfTrue
+ * step which should be added to the chain if expression is true
+ * @return this chain instance
+ * @throws NullPointerException
+ * when {@code stepIfTrue} is null
+ */
+ public StepsChain thenIf(Supplier supplier, Step stepIfTrue) {
+ return then(new ExpressionStep(supplier, stepIfTrue));
+ }
+
+ /**
+ * Adds all the steps from the {@code chain} to this chain.
+ *
+ * @param chain
+ * chain which steps should be added to this chain
+ * @return this chain instance
+ * @throws NullPointerException
+ * when {@code chain} is null
+ */
+ public StepsChain thenChain(StepsChain chain) {
+ steps.addAll(requireNonNull(chain, "Expected non-null chain").getSteps());
+ return this;
+ }
+
+ /**
+ * Adds all the steps from the {@code chainIfTrue} chain to
+ * this chain with a boolean supplier. Each added step will be executed only if
+ * supplier provides true value.
+ *
+ * @param supplier
+ * any boolean value or expression
+ * @param chainIfTrue
+ * chain which steps should be added to this chain if expression is true
+ * @return this chain instance
+ * @throws NullPointerException
+ * when {@code chainIfTrue} is null
+ */
+ public StepsChain thenChainIf(Supplier supplier, StepsChain chainIfTrue) {
+ final CachingSupplier cachingSupplier = new CachingSupplier(supplier);
+ for (Step stepIfTrue : chainIfTrue.getSteps()) {
+ thenIf(cachingSupplier, stepIfTrue);
+ }
+ return this;
+ }
+
+ /**
+ * Adds all the steps from the {@code chainIfTrue} and {@code chainIfFalse} chains to this chain
+ * with a opposite suppliers. If supplier supplies true then all the steps from the {@code chainIfTrue} chain
+ * will be executed, otherwise all the steps from the chainIfFalse step will be executed.
+ *
+ * @param supplier
+ * any boolean value or expression
+ * @param chainIfTrue
+ * chain which steps should be added to this chain if expression is true
+ * @param chainIfFalse
+ * chain which steps should be added to this chain if expression is false
+ * @return this chain instance
+ * @throws NullPointerException
+ * when either {@code chainIfTrue} or {@code chainIfFalse} is null
+ */
+ public StepsChain thenChainIf(Supplier supplier, StepsChain chainIfTrue, StepsChain chainIfFalse) {
+ thenChainIf(supplier, chainIfTrue);
+ thenChainIf(new NegateSupplier(supplier), chainIfFalse);
+ return this;
+ }
+
+ public static StepsChain firstIf(Supplier condition, Step stepIfTrue) {
+ return new StepsChain().thenIf(condition, stepIfTrue);
+ }
+
+ /**
+ * Executes given step only if {@code supplier} provides true value,
+ * otherwise continues workflow execution with {@link WorkflowExecutor#executeNextStep(Context)} method.
+ * This is helpful class for defining steps which execution is based on the future condition.
+ */
+ private static class ExpressionStep implements SyntheticStep {
+
+ final Supplier supplier;
+ final Step stepIfTrue;
+
+ ExpressionStep(Supplier supplier, Step stepIfTrue) {
+ this.supplier = supplier;
+ this.stepIfTrue = stepIfTrue;
+ }
+
+ @Override
+ public void execute(WorkflowExecutor executor, Context context) {
+ if (supplier.get()) {
+ stepIfTrue.execute(executor, context);
+ } else {
+ executor.done(this, context);
+ }
+ }
+ }
+
+ /**
+ * Supplier which caches {@code delegate.get()} value and reuses it.
+ * It allows chains to depend on the other chain and future check.
+ *
+ * For example:
+ *
+ */
+ INITIALIZING,
+
+ /**
+ * The workflow is ready to createPr when either initialization is successfully performed
+ * or {@link #CREATING_PR} failed with an error.
+ *
+ *
+ */
+ READY_TO_CREATE_PR,
+
+ /**
+ * The workflow is in creating pr status status only when chain executor is
+ * performing {@link ContributionWorkflow#creationChain(Context)}.
+ * If any error occurs during pr creating then workflow status should be
+ * changed either to the {@link #READY_TO_CREATE_PR} or to the {@link #READY_TO_UPDATE_PR}
+ *
+ *
+ */
+ CREATING_PR,
+
+ /**
+ * The workflow is ready to updatePullRequest pr either when plugin
+ * was initialized successfully and pull request already
+ * exists, or if pull request was successfully created.
+ *
+ *
+ */
+ READY_TO_UPDATE_PR,
+
+ /**
+ * The workflow is updating pull request only and only if chain executor
+ * executes {@link ContributionWorkflow#updateChain(Context)}.
+ * If an error occurs during the updatePullRequest, status should be changed to {@link #READY_TO_UPDATE_PR}.
+ *
+ *