diff --git a/.idea/modules.xml b/.idea/modules.xml index 9f60bd4226c6..ae36a87dbf69 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -47,7 +47,8 @@ - + + diff --git a/platform/dvcs-api/dvcs-api.iml b/platform/dvcs-api/dvcs-api.iml new file mode 100644 index 000000000000..f425c986a066 --- /dev/null +++ b/platform/dvcs-api/dvcs-api.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingCommitsProvider.java b/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingCommitsProvider.java new file mode 100644 index 000000000000..4532146f9b4a --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingCommitsProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.dvcs.repo.Repository; +import org.jetbrains.annotations.NotNull; + +/** + * Provider for outgoing commits + */ +public abstract class OutgoingCommitsProvider { + + /** + * Collect outgoing commits or errors for selected repo for specified {@link PushSpec} and store to {@link OutgoingResult} + * + * @param initial true for first commits loading, which identify that all inside actions should be silent + * and do not ask user about smth, a.e authorization request + */ + @NotNull + public abstract OutgoingResult getOutgoingCommits(@NotNull Repository repository, + @NotNull PushSpec pushSpec, boolean initial); +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingResult.java b/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingResult.java new file mode 100644 index 000000000000..5cc5a26abec8 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/OutgoingResult.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.vcs.log.VcsFullCommitDetails; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class OutgoingResult { + @NotNull private final List myErrors; + @NotNull private final List myCommits; + + public OutgoingResult(@NotNull List commits, @NotNull List errors) { + myCommits = commits; + myErrors = errors; + } + + @NotNull + public List getErrors() { + return myErrors; + } + + @NotNull + public List getCommits() { + return myCommits; + } + + public boolean hasErrors() { + return !myErrors.isEmpty(); + } +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java new file mode 100644 index 000000000000..8e756d8fe76a --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +/** + * Source to push from. For example, local branch for git or branch/bookmark for mercurial. + */ +public interface PushSource { + String getPresentation(); +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushSpec.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSpec.java new file mode 100644 index 000000000000..81ff874979bd --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSpec.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Specified a push from-to settings for one repository + */ +public class PushSpec { + + @NotNull private PushSource mySource; + @Nullable private PushTarget myTarget; + + public PushSpec(@NotNull PushSource source, @Nullable PushTarget target) { + mySource = source; + myTarget = target; + } + + @NotNull + public PushSource getSource() { + return mySource; + } + + @Nullable + public PushTarget getTarget() { + return myTarget; + } +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushSupport.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSupport.java new file mode 100644 index 000000000000..eba516124212 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSupport.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.dvcs.repo.Repository; +import com.intellij.dvcs.repo.RepositoryManager; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.vcs.AbstractVcs; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * Base class to provide vcs-specific info + */ + +public abstract class PushSupport { + public static final ExtensionPointName PUSH_SUPPORT_EP = ExtensionPointName.create("com.intellij.pushSupport"); + + @NotNull + public abstract AbstractVcs getVcs(); + + @NotNull + public abstract Pusher getPusher(); + + @NotNull + public abstract OutgoingCommitsProvider getOutgoingCommitsProvider(); + + /** + * @return Default push destination + */ + @Nullable + public abstract PushTarget getDefaultTarget(@NotNull Repository repository); + + /** + * @return All remembered remote destinations used for completion + */ + @NotNull + public abstract Collection getTargetNames(@NotNull Repository repository); + + /** + * @return current source(branch) for repository + */ + @NotNull + public abstract PushSource getSource(@NotNull Repository repository); + + /** Create destination target from user input string + * @return + */ + @Nullable + public abstract PushTarget createTarget(String targetName); + + /** + * @return RepositoryManager for vcs + */ + @NotNull + public abstract RepositoryManager getRepositoryManager(); + + @Nullable + public VcsPushOptionsPanel getVcsPushOptionsPanel(){ + return null; + } +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java new file mode 100644 index 000000000000..87c7244bd77c --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import org.jetbrains.annotations.NotNull; + + +/** + * Destination for push action. (Remote for git or push-path for mercurial). + */ +public interface PushTarget { + + @NotNull + String getPresentation(); +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/Pusher.java b/platform/dvcs-api/src/com/intellij/dvcs/push/Pusher.java new file mode 100644 index 000000000000..6d5fc1611030 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/Pusher.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.dvcs.repo.Repository; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Base class to execute push command. + */ +public abstract class Pusher { + /** + * Perform push command for all repositories belonged to one vcs. + * + * @param pushSpecs specify push from and to params + * @param vcsPushOptionValue specify additional options to push, null if not supported + * @param force if true then execute force push + */ + public abstract void push(@NotNull Map pushSpecs, + @Nullable VcsPushOptionValue vcsPushOptionValue, + boolean force); +} + diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/TreeNodeLinkListener.java b/platform/dvcs-api/src/com/intellij/dvcs/push/TreeNodeLinkListener.java new file mode 100644 index 000000000000..8ce03d299b29 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/TreeNodeLinkListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; + +public interface TreeNodeLinkListener { + void onClick(@NotNull DefaultMutableTreeNode source); +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/VcsError.java b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsError.java new file mode 100644 index 000000000000..f3ea7adf5634 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import org.jetbrains.annotations.NotNull; + +public class VcsError extends VcsLinkedText { + public VcsError(@NotNull String text) { + super(text, ""); + } + + public VcsError(@NotNull String text, @NotNull String link) { + super(text, link); + } +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/VcsLinkedText.java b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsLinkedText.java new file mode 100644 index 000000000000..99a0d3e77b5d --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsLinkedText.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + +public class VcsLinkedText { + @NotNull String myText; + @NotNull String myHandledLink; + + private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + + public VcsLinkedText(@NotNull String text, @NotNull String link) { + myText = text; + myHandledLink = link; + } + + @NotNull + public String getText() { + return myText; + } + + @NotNull + public String getLinkText() { + return myHandledLink; + } + + public void addClickListener(@NotNull TreeNodeLinkListener listener) { + myListeners.add(listener); + } + + public void fireOnClick(@NotNull DefaultMutableTreeNode relatedNode) { + for (TreeNodeLinkListener listener : myListeners) { + listener.onClick(relatedNode); + } + } +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionValue.java b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionValue.java new file mode 100644 index 000000000000..61a50eb09d73 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionValue.java @@ -0,0 +1,19 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +public interface VcsPushOptionValue { +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionsPanel.java b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionsPanel.java new file mode 100644 index 000000000000..5ecd9af0d182 --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushOptionsPanel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import javax.swing.*; +import java.awt.event.ActionListener; + +public abstract class VcsPushOptionsPanel extends JPanel { + + public abstract VcsPushOptionValue getValue(); + + /** + * @param listener handle valueChange event + */ + public abstract void addValueChangeListener(ActionListener listener); +} diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushReferenceStrategy.java b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushReferenceStrategy.java new file mode 100644 index 000000000000..7e4cbecb796b --- /dev/null +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/VcsPushReferenceStrategy.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +/** + * Specify which references(tags) should be pushed + */ + +public enum VcsPushReferenceStrategy { + none, // do not push references/tags + follow, // push references only for selected sources + all // push all references for all repositories +} diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/Repository.java b/platform/dvcs-api/src/com/intellij/dvcs/repo/Repository.java similarity index 97% rename from platform/dvcs/src/com/intellij/dvcs/repo/Repository.java rename to platform/dvcs-api/src/com/intellij/dvcs/repo/Repository.java index c12cf4ff0407..47cc17866c08 100644 --- a/platform/dvcs/src/com/intellij/dvcs/repo/Repository.java +++ b/platform/dvcs-api/src/com/intellij/dvcs/repo/Repository.java @@ -17,6 +17,7 @@ package com.intellij.dvcs.repo; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -97,6 +98,9 @@ public interface Repository extends Disposable { @NotNull State getState(); + @Nullable + AbstractVcs getVcs(); + /** * Returns the hash of the revision, which HEAD currently points to. * Returns null only in the case of a fresh repository, when no commit have been made. diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/RepositoryManager.java b/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/repo/RepositoryManager.java rename to platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java diff --git a/platform/dvcs/dvcs.iml b/platform/dvcs-impl/dvcs-impl.iml similarity index 90% rename from platform/dvcs/dvcs.iml rename to platform/dvcs-impl/dvcs-impl.iml index 145ad6363140..4c0daa71326c 100644 --- a/platform/dvcs/dvcs.iml +++ b/platform/dvcs-impl/dvcs-impl.iml @@ -17,6 +17,8 @@ + + diff --git a/platform/dvcs-impl/src/META-INF/dvcs.xml b/platform/dvcs-impl/src/META-INF/dvcs.xml new file mode 100644 index 000000000000..aa81616f24f6 --- /dev/null +++ b/platform/dvcs-impl/src/META-INF/dvcs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/platform/dvcs/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java rename to platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java diff --git a/platform/dvcs/src/com/intellij/dvcs/DvcsPlatformFacade.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsPlatformFacade.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/DvcsPlatformFacade.java rename to platform/dvcs-impl/src/com/intellij/dvcs/DvcsPlatformFacade.java diff --git a/platform/dvcs/src/com/intellij/dvcs/DvcsPlatformFacadeImpl.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsPlatformFacadeImpl.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/DvcsPlatformFacadeImpl.java rename to platform/dvcs-impl/src/com/intellij/dvcs/DvcsPlatformFacadeImpl.java diff --git a/platform/dvcs/src/com/intellij/dvcs/DvcsRememberedInputs.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsRememberedInputs.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/DvcsRememberedInputs.java rename to platform/dvcs-impl/src/com/intellij/dvcs/DvcsRememberedInputs.java diff --git a/platform/dvcs/src/com/intellij/dvcs/DvcsUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java similarity index 91% rename from platform/dvcs/src/com/intellij/dvcs/DvcsUtil.java rename to platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java index c2aef0502db5..1fba1827084c 100644 --- a/platform/dvcs/src/com/intellij/dvcs/DvcsUtil.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java @@ -39,6 +39,8 @@ import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.status.StatusBarUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.DateFormatUtil; +import com.intellij.vcs.log.TimedVcsCommit; import com.intellij.vcs.log.VcsLog; import com.intellij.vcs.log.VcsLogProvider; import org.intellij.images.editor.ImageFileEditor; @@ -55,6 +57,8 @@ import java.util.List; */ public class DvcsUtil { + private static final int SHORT_HASH_LENGTH = 8; + public static void installStatusBarWidget(@NotNull Project project, @NotNull StatusBarWidget widget) { StatusBar statusBar = WindowManager.getInstance().getStatusBar(project); if (statusBar != null) { @@ -183,4 +187,19 @@ public class DvcsUtil { return result; } + @NotNull + public static String getShortHash(@NotNull String hash) { + if (hash.length() == 0) return ""; + if (hash.length() == 40) return hash.substring(0, SHORT_HASH_LENGTH); + if (hash.length() > 40) // revision string encoded with date too + { + return hash.substring(hash.indexOf("[") + 1, SHORT_HASH_LENGTH); + } + return hash; + } + + @NotNull + public static String getDateString(@NotNull TimedVcsCommit commit) { + return DateFormatUtil.formatPrettyDateTime(commit.getTimestamp()) + " "; + } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/MoreCommitsLink.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/MoreCommitsLink.java new file mode 100644 index 000000000000..e7fdedccc849 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/MoreCommitsLink.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +public class MoreCommitsLink extends VcsLinkedText { + MoreCommitsLink() { + super("", "..."); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java new file mode 100644 index 000000000000..9237a1455ec8 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java @@ -0,0 +1,378 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +import com.intellij.dvcs.DvcsUtil; +import com.intellij.dvcs.push.ui.*; +import com.intellij.dvcs.repo.Repository; +import com.intellij.dvcs.repo.RepositoryManager; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.impl.ProgressManagerImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.ui.CheckedTreeNode; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; +import com.intellij.vcs.log.VcsFullCommitDetails; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +public class PushController implements Disposable { + + @NotNull private final Project myProject; + @NotNull private final List myPushSupports; + @NotNull private final PushLog myPushLog; + private boolean mySingleRepoProject; + private static final int DEFAULT_CHILDREN_PRESENTATION_NUMBER = 20; + private final Map myAdditionalValuesMap; + + private final Map myView2Model = new HashMap(); + + + public PushController(@NotNull Project project, + @NotNull VcsPushDialog dialog, + @NotNull List preselectedRepositories) { + myProject = project; + //todo what would be in case of null + myPushSupports = Arrays.asList(Extensions.getExtensions(PushSupport.PUSH_SUPPORT_EP, myProject)); + CheckedTreeNode rootNode = new CheckedTreeNode(null); + mySingleRepoProject = createTreeModel(rootNode, preselectedRepositories); + myPushLog = new PushLog(myProject, rootNode); + myAdditionalValuesMap = new HashMap(); + startLoadingCommits(); + Disposer.register(dialog.getDisposable(), this); + } + + private void startLoadingCommits() { + Map priorityLoading = new HashMap(); + Map others = new HashMap(); + for (Map.Entry entry : myView2Model.entrySet()) { + MyRepoModel model = entry.getValue(); + if (model.isSelected()) { + priorityLoading.put(entry.getKey(), model); + } + else { + others.put(entry.getKey(), model); + } + } + loadCommitsFromMap(priorityLoading); + loadCommitsFromMap(others); + } + + private void loadCommitsFromMap(@NotNull Map items) { + for (Map.Entry entry : items.entrySet()) { + RepositoryNode node = entry.getKey(); + myPushLog.startLoading(node); + loadCommits(entry.getValue(), node, true); + } + } + + //return is single repository project or not + private boolean createTreeModel(@NotNull CheckedTreeNode rootNode, @NotNull List preselectedRepositories) { + if (myPushSupports.isEmpty()) return true; + + boolean isSingleRepositoryProject = myPushSupports.size() == 1; + for (final PushSupport support : myPushSupports) { + RepositoryManager repositoryManager = support.getRepositoryManager(); + List repositories = repositoryManager.getRepositories(); + isSingleRepositoryProject = isSingleRepositoryProject && repositories.size() == 1; + for (Repository repository : repositories) { + PushTarget target = support.getDefaultTarget(repository); + final MyRepoModel model = new MyRepoModel(repository, support, preselectedRepositories.contains(repository), + new PushSpec(support.getSource(repository), + target), + DEFAULT_CHILDREN_PRESENTATION_NUMBER); + RepositoryWithBranchPanel repoPanel = new RepositoryWithBranchPanel(myProject, DvcsUtil.getShortRepositoryName(repository), + support.getSource(repository).getPresentation(), + target == null ? "" : target.getPresentation(), + support.getTargetNames(repository)); + final RepositoryNode repoNode = + isSingleRepositoryProject ? new SingleRepositoryNode(repoPanel) : new RepositoryNode(repoPanel); + myView2Model.put(repoNode, model); + repoNode.setChecked(model.isSelected()); + repoNode.addRepoNodeListener(new RepositoryNodeListener() { + @Override + public void onTargetChanged(String newValue) { + myView2Model.get(repoNode).setSpec(new PushSpec(model.getSpec().getSource(), support.createTarget(newValue))); + myPushLog.startLoading(repoNode); + loadCommits(model, repoNode, false); + } + + @Override + public void onSelectionChanged(boolean isSelected) { + myView2Model.get(repoNode).setSelected(isSelected); + } + }); + rootNode.add(repoNode); + } + } + return isSingleRepositoryProject; + } + + private void loadCommits(@NotNull final MyRepoModel model, + @NotNull final RepositoryNode node, + final boolean initial) { + node.stopLoading(); + final ProgressIndicator indicator = node.startLoading(); + final PushSupport support = model.getSupport(); + final AtomicReference result = new AtomicReference(); + Task.Backgroundable task = new Task.Backgroundable(myProject, "Loading Commits", true) { + + @Override + public void onCancel() { + node.stopLoading(); + } + + @Override + public void onSuccess() { + OutgoingResult outgoing = result.get(); + if (outgoing.hasErrors()) { + myPushLog.setChildren(node, ContainerUtil.map(outgoing.getErrors(), new Function() { + @Override + public DefaultMutableTreeNode fun(VcsError error) { + return new TextWithLinkNode(error); + } + }), model.isSelected()); + } + else { + model.setLoadedCommits(outgoing.getCommits()); + myPushLog.setChildren(node, + getPresentationForCommits(PushController.this.myProject, model.getLoadedCommits(), + model.getNumberOfShownCommits()), model.isSelected()); + } + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + OutgoingResult outgoing = support.getOutgoingCommitsProvider() + .getOutgoingCommits(model.getRepository(), model.getSpec(), initial); + result.compareAndSet(null, outgoing); + } + }; + + ProgressManagerImpl.runProcessWithProgressAsynchronously(task, indicator, null, ModalityState.any()); + } + + + public PushLog getPushPanelInfo() { + return myPushLog; + } + + public boolean isValid() { + //todo implement + return false; + } + + public void push(final boolean force) { + Task.Backgroundable task = new Task.Backgroundable(myProject, "Pushing...", false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + for (PushSupport support : myPushSupports) { + MyPushOptionValueModel additionalOptionsModel = myAdditionalValuesMap.get(support); + support.getPusher() + .push(collectPushInfoForVcs(support), additionalOptionsModel == null ? null : additionalOptionsModel.getCurrentValue(), force); + } + } + }; + task.queue(); + } + + @NotNull + private Map collectPushInfoForVcs(@NotNull final PushSupport pushSupport) { + Map pushSpecs = new HashMap(); + Collection repositoriesInformation = getSelectedRepoNode(); + for (MyRepoModel repoModel : repositoriesInformation) { + if (repoModel.getSupport().equals(pushSupport)) { + pushSpecs.put(repoModel.getRepository(), repoModel.getSpec()); + } + } + return pushSpecs; + } + + public Collection getSelectedRepoNode() { + if (mySingleRepoProject) { + return myView2Model.values(); + } + return ContainerUtil.filter(myView2Model.values(), new Condition() { + @Override + public boolean value(MyRepoModel model) { + return model.isSelected(); + } + }); + } + + @Override + public void dispose() { + for (RepositoryNode node : myView2Model.keySet()) { + node.stopLoading(); + } + } + + private void addMoreCommits(RepositoryNode repositoryNode) { + MyRepoModel repoModel = myView2Model.get(repositoryNode); + repoModel.increaseShownCommits(); + myPushLog.setChildren(repositoryNode, + getPresentationForCommits( + myProject, + repoModel.getLoadedCommits(), + repoModel.getNumberOfShownCommits() + )); + } + + + @NotNull + public List getPresentationForCommits(@NotNull final Project project, + @NotNull List commits, + int commitsNum) { + Function commitToNode = new Function() { + @Override + public DefaultMutableTreeNode fun(VcsFullCommitDetails commit) { + return new VcsFullCommitDetailsNode(project, commit); + } + }; + List childrenToShown = new ArrayList(); + for (int i = 0; i < commits.size(); ++i) { + if (i >= commitsNum) { + final MoreCommitsLink moreCommitsLink = new MoreCommitsLink(); + moreCommitsLink.addClickListener(new TreeNodeLinkListener() { + @Override + public void onClick(@NotNull DefaultMutableTreeNode source) { + TreeNode parentNode = source.getParent(); + if (parentNode instanceof RepositoryNode) { + addMoreCommits((RepositoryNode)parentNode); + } + } + }); + childrenToShown.add(new TextWithLinkNode(moreCommitsLink)); + break; + } + childrenToShown.add(commitToNode.fun(commits.get(i))); + } + return childrenToShown; + } + + @NotNull + public List getAdditionalPanels() { + List additionalPanels = new ArrayList(); + for (final PushSupport support : myPushSupports) { + final VcsPushOptionsPanel panel = support.getVcsPushOptionsPanel(); + if (panel != null) { + additionalPanels.add(panel); + myAdditionalValuesMap.put(support, new MyPushOptionValueModel(panel.getValue())); + panel.addValueChangeListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myAdditionalValuesMap.get(support).setCurrentValue(panel.getValue()); + } + }); + } + } + return additionalPanels; + } + + private static class MyRepoModel { + final Repository myRepository; + private PushSupport mySupport; + + PushSpec mySpec; + int myNumberOfShownCommits; + + List myLoadedCommits; + boolean myIsSelected; + + public MyRepoModel(Repository repository, PushSupport supportForRepo, boolean isSelected, PushSpec spec, int num) { + myRepository = repository; + mySupport = supportForRepo; + myIsSelected = isSelected; + mySpec = spec; + myNumberOfShownCommits = num; + } + + public Repository getRepository() { + return myRepository; + } + + public PushSupport getSupport() { + return mySupport; + } + + public boolean isSelected() { + return myIsSelected; + } + + public AbstractVcs getVcs() { + return myRepository.getVcs(); + } + + public PushSpec getSpec() { + return mySpec; + } + + public void setSpec(PushSpec spec) { + mySpec = spec; + } + + public void setSelected(boolean isSelected) { + myIsSelected = isSelected; + } + + public int getNumberOfShownCommits() { + return myNumberOfShownCommits; + } + + public void increaseShownCommits() { + myNumberOfShownCommits *= 2; + } + + public List getLoadedCommits() { + return myLoadedCommits; + } + + public void setLoadedCommits(List loadedCommits) { + myLoadedCommits = loadedCommits; + } + } + + private static class MyPushOptionValueModel { + @NotNull private VcsPushOptionValue myCurrentValue; + + public MyPushOptionValueModel(@NotNull VcsPushOptionValue currentValue) { + myCurrentValue = currentValue; + } + + public void setCurrentValue(@NotNull VcsPushOptionValue currentValue) { + myCurrentValue = currentValue; + } + + @NotNull + public VcsPushOptionValue getCurrentValue() { + return myCurrentValue; + } + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java new file mode 100644 index 000000000000..7ca285fc95aa --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push; + +public interface RepositoryNodeListener { + + void onTargetChanged(String newValue); + + void onSelectionChanged(boolean isSelected); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/CustomRenderedTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/CustomRenderedTreeNode.java new file mode 100644 index 000000000000..18a69bae53be --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/CustomRenderedTreeNode.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.ui.ColoredTreeCellRenderer; +import org.jetbrains.annotations.NotNull; + +interface CustomRenderedTreeNode { + + void render(@NotNull ColoredTreeCellRenderer renderer); +} \ No newline at end of file diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/DvcsStrategyPanel.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/DvcsStrategyPanel.java new file mode 100644 index 000000000000..5b3aa4b153ed --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/DvcsStrategyPanel.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.dvcs.push.VcsPushReferenceStrategy; +import com.intellij.openapi.ui.ComboBox; + +import javax.swing.*; +import java.awt.*; + +public class DvcsStrategyPanel extends JPanel { + + private ComboBox myReferenceStrategyCombobox; + + public DvcsStrategyPanel() { + setLayout(new BorderLayout()); + myReferenceStrategyCombobox = new ComboBox(); + DefaultComboBoxModel comboModel = new DefaultComboBoxModel(VcsPushReferenceStrategy.values()); + myReferenceStrategyCombobox.setModel(comboModel); + JPanel bottomPanel = new JPanel(new FlowLayout()); + JLabel referenceStrategyLabel = new JLabel("Push Reference Strategy: "); + bottomPanel.add(referenceStrategyLabel, FlowLayout.LEFT); + bottomPanel.add(myReferenceStrategyCombobox); + add(bottomPanel, BorderLayout.WEST); + } + + public VcsPushReferenceStrategy getStrategy() { + return (VcsPushReferenceStrategy)myReferenceStrategyCombobox.getSelectedItem(); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java new file mode 100644 index 000000000000..6b3a93aa46fb --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.dvcs.push.RepositoryNodeListener; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +public interface EditableTreeNode extends CustomRenderedTreeNode { + void addRepoNodeListener(@NotNull RepositoryNodeListener listener); + + void fireOnChange(@NotNull String value); + + void fireOnSelectionChange(boolean isSelected); + + void stopLoading(); + + ProgressIndicator startLoading(); + + String getValue(); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java new file mode 100644 index 000000000000..e4b48dac8750 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.JBColor; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import java.net.URL; + +public class LoadingTreeNode extends DefaultMutableTreeNode implements CustomRenderedTreeNode { + @NotNull protected ImageIcon myLoadingIcon; + private static final String LOADING_ICON = "loadingSimpleIcon.gif"; + private static final String LOADING_DARK_ICON = "loadingSimpleDarkIcon.gif"; + + @NotNull + public ImageIcon getIcon() { + return myLoadingIcon; + } + + public LoadingTreeNode() { + super(null, false); + URL loadingIconPath = getClass().getResource(UIUtil.isUnderDarcula() ? LOADING_DARK_ICON : LOADING_ICON); + myLoadingIcon = new ImageIcon(loadingIconPath.getPath()); + } + + + @Override + public void render(@NotNull ColoredTreeCellRenderer renderer) { + renderer.setIcon(myLoadingIcon); + renderer.append("Loading Commits...", new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, JBColor.GRAY)); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/NodeImageObserver.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/NodeImageObserver.java new file mode 100644 index 000000000000..1e3d82015afe --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/NodeImageObserver.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.image.ImageObserver; + +class NodeImageObserver implements ImageObserver { + JTree tree; + DefaultTreeModel model; + TreeNode node; + + NodeImageObserver(JTree tree, TreeNode node) { + this.tree = tree; + this.model = (DefaultTreeModel)tree.getModel(); + this.node = node; + } + + public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) { + if ((flags & (FRAMEBITS | ALLBITS)) != 0) { + TreePath path = new TreePath(model.getPathToRoot(node)); + Rectangle rect = tree.getPathBounds(path); + if (rect != null) { + tree.repaint(rect); + } + } + return (flags & (ALLBITS | ABORT)) == 0; + } +} \ No newline at end of file diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java new file mode 100644 index 000000000000..3a2e47cfd048 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java @@ -0,0 +1,280 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.openapi.actionSystem.CommonShortcuts; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.actionSystem.DataSink; +import com.intellij.openapi.actionSystem.TypeSafeDataProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; +import com.intellij.ui.*; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.tree.TreeUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EventObject; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class PushLog extends JPanel implements TypeSafeDataProvider { + + private final ReentrantReadWriteLock TREE_CONSTRUCTION_LOCK = new ReentrantReadWriteLock(); + + private static final String START_EDITING = "startEditing"; + private final ChangesBrowser myChangesBrowser; + private final CheckboxTree myTree; + private final CheckedTreeNode myRootNode; + private final MyTreeCellRenderer myTreeCellRenderer; + + public PushLog(Project project, CheckedTreeNode root) { + myRootNode = root; + DefaultTreeModel treeModel = new DefaultTreeModel(myRootNode); + treeModel.nodeStructureChanged(myRootNode); + myTreeCellRenderer = new MyTreeCellRenderer(); + myTree = new CheckboxTree(myTreeCellRenderer, myRootNode) { + + public boolean isPathEditable(TreePath path) { + return isEditable() && path.getLastPathComponent() instanceof DefaultMutableTreeNode; + } + + @Override + protected void onNodeStateChanged(CheckedTreeNode node) { + if (node instanceof EditableTreeNode) { + ((EditableTreeNode)node).fireOnSelectionChange(node.isChecked()); + } + } + + @Override + public String getToolTipText(MouseEvent event) { + final TreePath path = myTree.getPathForLocation(event.getX(), event.getY()); + if (path == null) { + return ""; + } + Object node = path.getLastPathComponent(); + if (node == null || (!(node instanceof DefaultMutableTreeNode))) { + return ""; + } + if (node instanceof TooltipNode) { + return ((TooltipNode)node).getTooltip(); + } + return ""; + } + }; + myTree.setEditable(true); + MyTreeCellEditor treeCellEditor = new MyTreeCellEditor(new JBTextField()); + myTree.setCellEditor(treeCellEditor); + treeCellEditor.addCellEditorListener(new CellEditorListener() { + @Override + public void editingStopped(ChangeEvent e) { + } + + @Override + public void editingCanceled(ChangeEvent e) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent(); + if (node != null && node instanceof EditableTreeNode) { + //todo restore from appropriate editor + ((EditableTreeNode)node).fireOnChange(((EditableTreeNode)node).getValue()); + } + } + }); + myTree.setRootVisible(false); + TreeUtil.expandAll(myTree); + final VcsBranchEditorListener linkMouseListener = new VcsBranchEditorListener(myTreeCellRenderer); + linkMouseListener.installOn(myTree); + + myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + myTree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + TreePath[] nodes = myTree.getSelectionPaths(); + if (nodes != null) { + ArrayList changes = new ArrayList(); + for (TreePath node : nodes) { + Object nodeInfo = ((DefaultMutableTreeNode)node.getLastPathComponent()).getUserObject(); + if (nodeInfo instanceof VcsFullCommitDetails) { + changes.addAll(((VcsFullCommitDetails)nodeInfo).getChanges()); + } + } + myChangesBrowser.getViewer().setEmptyText("No differences"); + myChangesBrowser.setChangesToDisplay(CommittedChangesTreeBrowser.zipChanges(changes)); + return; + } + setDefaultEmptyText(); + myChangesBrowser.setChangesToDisplay(Collections.emptyList()); + } + }); + myTree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), START_EDITING); + myTree.setRowHeight(0); + ToolTipManager.sharedInstance().registerComponent(myTree); + + myChangesBrowser = + new ChangesBrowser(project, null, Collections.emptyList(), null, false, true, null, ChangesBrowser.MyUseCase.LOCAL_CHANGES, + null); + myChangesBrowser.getDiffAction().registerCustomShortcutSet(CommonShortcuts.getDiff(), myTree); + setDefaultEmptyText(); + + Splitter splitter = new Splitter(false, 0.7f); + splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree)); + splitter.setSecondComponent(myChangesBrowser); + + setLayout(new BorderLayout()); + add(splitter); + } + + private void setDefaultEmptyText() { + myChangesBrowser.getViewer().setEmptyText("No commits selected"); + } + + // Make changes available for diff action + @Override + public void calcData(DataKey key, DataSink sink) { + if (VcsDataKeys.CHANGES.equals(key)) { + DefaultMutableTreeNode[] selectedNodes = myTree.getSelectedNodes(DefaultMutableTreeNode.class, null); + if (selectedNodes.length == 0) { + return; + } + Object object = selectedNodes[0].getUserObject(); + if (object instanceof VcsFullCommitDetails) { + sink.put(key, ArrayUtil.toObjectArray(((VcsFullCommitDetails)object).getChanges(), Change.class)); + } + } + } + + @Override + protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { + if (e.getKeyCode() == KeyEvent.VK_ENTER && myTree.isEditing()) { + myTree.cancelEditing(); + return true; + } + return super.processKeyBinding(ks, e, condition, pressed); + } + + public void startLoading(DefaultMutableTreeNode parentNode) { + LoadingTreeNode loading = new LoadingTreeNode(); + loading.getIcon().setImageObserver(new NodeImageObserver(myTree, loading)); + setChildren(parentNode, Collections.singleton(loading)); + } + + private class MyTreeCellEditor extends DefaultCellEditor { + + public MyTreeCellEditor(JTextField field) { + super(field); + setClickCountToStart(1); + } + + @Override + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { + final Object node = ((DefaultMutableTreeNode)value).getUserObject(); + editorComponent = + (JComponent)((RepositoryWithBranchPanel)node).getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, true); + return editorComponent; + } + + @Override + public boolean isCellEditable(EventObject anEvent) { + if (anEvent instanceof MouseEvent) { + MouseEvent me = ((MouseEvent)anEvent); + final TreePath path = myTree.getClosestPathForLocation(me.getX(), me.getY()); + final int row = myTree.getRowForLocation(me.getX(), me.getY()); + myTree.getCellRenderer().getTreeCellRendererComponent(myTree, path.getLastPathComponent(), false, false, true, row, true); + Object tag = me.getClickCount() >= clickCountToStart + ? PushLogTreeUtil.getTagAtForRenderer(myTreeCellRenderer, me) + : null; + return tag instanceof EditorTextField; + } + //if keyboard event - then anEvent will be null =( See BasicTreeUi + Object treeNode = myTree.getAnchorSelectionPath().getLastPathComponent(); + return treeNode instanceof EditableTreeNode; + } + + //Implement the one CellEditor method that AbstractCellEditor doesn't. + public Object getCellEditorValue() { + return ((RepositoryWithBranchPanel)editorComponent).getRemoteTargetName(); + } + } + + private static class MyTreeCellRenderer extends CheckboxTree.CheckboxTreeCellRenderer { + + @Override + public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + if (!(value instanceof DefaultMutableTreeNode)) { + return; + } + if (value instanceof RepositoryNode) { + //todo simplify, remove instance of + myCheckbox.setVisible(((RepositoryNode)value).isCheckboxVisible()); + } + Object userObject = ((DefaultMutableTreeNode)value).getUserObject(); + ColoredTreeCellRenderer renderer = getTextRenderer(); + renderer.setBorder(null); + if (value instanceof CustomRenderedTreeNode) { + ((CustomRenderedTreeNode)value).render(renderer); + } + else { + renderer.append(userObject == null ? "" : userObject.toString()); + } + } + } + + public void setChildren(DefaultMutableTreeNode parentNode, @NotNull Collection childrenNodes) { + setChildren(parentNode, childrenNodes, true); + } + + public void setChildren(DefaultMutableTreeNode parentNode, + @NotNull Collection childrenNodes, + boolean shouldExpand) { + try { + TREE_CONSTRUCTION_LOCK.writeLock().lock(); + parentNode.removeAllChildren(); + for (DefaultMutableTreeNode child : childrenNodes) { + parentNode.add(child); + } + final DefaultTreeModel model = ((DefaultTreeModel)myTree.getModel()); + model.nodeStructureChanged(parentNode); + TreePath path = TreeUtil.getPathFromRoot(parentNode); + if (shouldExpand) { + myTree.expandPath(path); + } + else { + myTree.collapsePath(path); + } + } + finally { + TREE_CONSTRUCTION_LOCK.writeLock().unlock(); + } + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLogTreeUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLogTreeUtil.java new file mode 100644 index 000000000000..c7da9247ae74 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLogTreeUtil.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.ui.CheckboxTree; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.MouseEvent; + +public class PushLogTreeUtil { + + @Nullable + public static Object getTagAtForRenderer(CheckboxTree.CheckboxTreeCellRenderer renderer, MouseEvent e) { + JTree tree = (JTree)e.getSource(); + Object tag = null; + final TreePath path = tree.getPathForLocation(e.getX(), e.getY()); + if (path != null) { + final Rectangle rectangle = tree.getPathBounds(path); + assert rectangle != null; + int dx = e.getX() - rectangle.x; + final TreeNode treeNode = (TreeNode)path.getLastPathComponent(); + final int row = tree.getRowForLocation(e.getX(), e.getY()); + tree.getCellRenderer().getTreeCellRendererComponent(tree, treeNode, false, false, true, row, true); + if (treeNode instanceof RepositoryNode) { + RepositoryNode repositoryNode = (RepositoryNode)treeNode; + int checkBoxWidth = repositoryNode.isCheckboxVisible() ? renderer.getCheckbox().getWidth() : 0; + tag = renderer.getTextRenderer().getFragmentTagAt(dx - checkBoxWidth); + } + else { + tag = renderer.getTextRenderer().getFragmentTagAt(dx); + } + } + return tag; + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java new file mode 100644 index 000000000000..21c6608e2a67 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.dvcs.push.RepositoryNodeListener; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.CheckedTreeNode; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.EditorTextField; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.util.List; + +public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode { + protected final static String ENTER_REMOTE = "Enter Remote"; + + private RepositoryWithBranchPanel myRepositoryPanel; + private ProgressIndicator myCurrentIndicator; + private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + + public RepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { + super(repositoryPanel); + myRepositoryPanel = repositoryPanel; + } + + public boolean isCheckboxVisible() { + return true; + } + + public void render(@NotNull ColoredTreeCellRenderer renderer) { + String repositoryPath = myRepositoryPanel.getRepositoryName(); + renderer.append(repositoryPath, SimpleTextAttributes.GRAY_ATTRIBUTES); + renderer.appendFixedTextFragmentWidth(120); + renderer + .append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + renderer + .append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + EditorTextField textField = myRepositoryPanel.getRemoteTextFiled(); + String targetName = myRepositoryPanel.getRemoteTargetName(); + if (StringUtil.isEmptyOrSpaces(targetName)) { + renderer.append(ENTER_REMOTE, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES, textField); + } + else { + renderer.append(targetName, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, textField); + } + Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(textField); + renderer.setBorder(new EmptyBorder(insets)); + } + + @Override + @NotNull + public String getValue() { + return myRepositoryPanel.getRemoteTargetName(); + } + + @Override + public void addRepoNodeListener(@NotNull RepositoryNodeListener listener) { + myListeners.add(listener); + } + + @Override + public void fireOnChange(@NotNull String newValue) { + for (RepositoryNodeListener listener : myListeners) { + listener.onTargetChanged(newValue); + } + } + + @Override + public void fireOnSelectionChange(boolean isSelected) { + for (RepositoryNodeListener listener : myListeners) { + listener.onSelectionChanged(isSelected); + } + } + + @Override + public void stopLoading() { + if (myCurrentIndicator != null && myCurrentIndicator.isRunning()) { + myCurrentIndicator.cancel(); + } + } + + @Override + public ProgressIndicator startLoading() { + return myCurrentIndicator = new EmptyProgressIndicator(); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java new file mode 100644 index 000000000000..d8d8f57fe922 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java @@ -0,0 +1,156 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.project.Project; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.TextFieldWithAutoCompletion; +import com.intellij.ui.TextFieldWithAutoCompletionListProvider; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.tree.TreeCellRenderer; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.util.Collection; + +public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCellRenderer { + + private final JBCheckBox myRepositoryCheckbox; + private final TextFieldWithAutoCompletion myDestBranchTextField; + private final JBLabel myLocalBranch; + private final JLabel myArrowLabel; + private final JLabel myRepositoryLabel; + private final ColoredTreeCellRenderer myTextRenderer; + + public RepositoryWithBranchPanel(Project project, @NotNull String repoName, + @NotNull String sourceName, String targetName, @NotNull Collection targetVariants) { + super(); + setLayout(new BorderLayout()); + myRepositoryCheckbox = new JBCheckBox(); + myRepositoryCheckbox.setOpaque(false); + myRepositoryCheckbox.setEnabled(false); //may be return editor depends on focus + myRepositoryLabel = new JLabel(repoName); + myLocalBranch = new JBLabel(sourceName); + myArrowLabel = new JLabel(" -> "); + TextFieldWithAutoCompletionListProvider provider = + new TextFieldWithAutoCompletion.StringsCompletionProvider(targetVariants, null); + myDestBranchTextField = new TextFieldWithAutoCompletion(project, provider, true, targetName) { + + @Override + public boolean shouldHaveBorder() { + return false; + } + + @Override + protected void updateBorder(@NotNull final EditorEx editor) { + } + }; + myDestBranchTextField.setBorder(UIUtil.getTableFocusCellHighlightBorder());//getTextFieldBorder()); + myDestBranchTextField.setOneLineMode(true); + myDestBranchTextField.setOpaque(true); + myDestBranchTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + myDestBranchTextField.selectAll(); + } + }); + + myTextRenderer = new ColoredTreeCellRenderer() { + public void customizeCellRenderer(@NotNull JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + + } + }; + myTextRenderer.setOpaque(false); + layoutComponents(); + } + + private void layoutComponents() { + add(myRepositoryCheckbox, BorderLayout.WEST); + JPanel panel = new NonOpaquePanel(new BorderLayout()); + panel.add(myTextRenderer, BorderLayout.WEST); + panel.add(myDestBranchTextField, BorderLayout.CENTER); + add(panel, BorderLayout.CENTER); + } + + @NotNull + public String getRepositoryName() { + return myRepositoryLabel.getText(); + } + + public String getSourceName() { + return myLocalBranch.getText(); + } + + public String getArrow() { + return myArrowLabel.getText(); + } + + public TextFieldWithAutoCompletion getRemoteTextFiled() { + return myDestBranchTextField; + } + + @NotNull + public String getRemoteTargetName() { + return myDestBranchTextField.getText(); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + Rectangle bounds = tree.getPathBounds(tree.getPathForRow(row)); + invalidate(); + if (!(value instanceof SingleRepositoryNode)) { + RepositoryNode node = (RepositoryNode)value; + myRepositoryCheckbox.setSelected(node.isChecked()); + myRepositoryCheckbox.setVisible(true); + myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + myTextRenderer.append(getRepositoryName(), SimpleTextAttributes.GRAY_ATTRIBUTES); + myTextRenderer.appendFixedTextFragmentWidth(120); + } + else { + myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + myRepositoryCheckbox.setVisible(false); + } + myTextRenderer.append(getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + myTextRenderer.append(getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (bounds != null) { + setPreferredSize(new Dimension(tree.getWidth() - bounds.x, bounds.height)); + } + revalidate(); + return this; + } +} + + diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java new file mode 100644 index 000000000000..73f9ad615d5f --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.EditorTextField; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +public class SingleRepositoryNode extends RepositoryNode { + private RepositoryWithBranchPanel myRepositoryPanel; + + public SingleRepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { + super(repositoryPanel); + myRepositoryPanel = repositoryPanel; + } + + public boolean isCheckboxVisible() { + return false; + } + + @Override + public void render(@NotNull ColoredTreeCellRenderer renderer) { + renderer + .append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + renderer + .append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + EditorTextField textField = myRepositoryPanel.getRemoteTextFiled(); + String targetName = myRepositoryPanel.getRemoteTargetName(); + if (StringUtil.isEmptyOrSpaces(targetName)) { + renderer.append(ENTER_REMOTE, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES, textField); + } + else { + renderer.append(targetName, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, textField); + } + Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(textField); + renderer.setBorder(new EmptyBorder(insets)); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TextWithLinkNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TextWithLinkNode.java new file mode 100644 index 000000000000..9f1bfc229623 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TextWithLinkNode.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + + +import com.intellij.dvcs.push.VcsLinkedText; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class TextWithLinkNode extends DefaultMutableTreeNode implements CustomRenderedTreeNode { + + @NotNull protected VcsLinkedText myLinkedText; + + public TextWithLinkNode(@NotNull VcsLinkedText linkedText) { + myLinkedText = linkedText; + } + + + public void fireOnClick(@NotNull TextWithLinkNode relatedNode) { + myLinkedText.fireOnClick(relatedNode); + } + + @Override + public void render(@NotNull ColoredTreeCellRenderer renderer) { + renderer.append(myLinkedText.getText(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + String linkedText = myLinkedText.getLinkText(); + if (!StringUtil.isEmptyOrSpaces(linkedText)) { + renderer.append(" "); + renderer.append(myLinkedText.getLinkText(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, this); + } + } +} \ No newline at end of file diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TooltipNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TooltipNode.java new file mode 100644 index 000000000000..fef83cc12f63 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/TooltipNode.java @@ -0,0 +1,21 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +public interface TooltipNode { + + String getTooltip(); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java new file mode 100644 index 000000000000..d8e35fd25d53 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase; +import com.intellij.ui.CheckboxTree; +import com.intellij.ui.EditorTextField; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.awt.event.MouseEvent; + +public class VcsBranchEditorListener extends LinkMouseListenerBase { + private final CheckboxTree.CheckboxTreeCellRenderer myRenderer; + + public VcsBranchEditorListener(final CheckboxTree.CheckboxTreeCellRenderer renderer) { + myRenderer = renderer; + } + + @Override + public void mouseMoved(MouseEvent e) { + Component component = (Component)e.getSource(); + Object tag = getTagAt(e); + if (tag != null && tag instanceof EditorTextField) { + component.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + } + else if (tag != null && tag instanceof TextWithLinkNode) { + component.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + else { + component.setCursor(Cursor.getDefaultCursor()); + } + } + + @Nullable + @Override + protected Object getTagAt(@NotNull final MouseEvent e) { + return PushLogTreeUtil.getTagAtForRenderer(myRenderer, e); + } + + protected void handleTagClick(@Nullable Object tag, @NotNull MouseEvent event) { + if (tag instanceof TextWithLinkNode) { + TextWithLinkNode textWithLink = (TextWithLinkNode)tag; + textWithLink.fireOnClick(textWithLink); + } + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsFullCommitDetailsNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsFullCommitDetailsNode.java new file mode 100644 index 000000000000..0df7af05d496 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsFullCommitDetailsNode.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.dvcs.DvcsUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.vcs.log.VcsFullCommitDetails; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class VcsFullCommitDetailsNode extends DefaultMutableTreeNode implements CustomRenderedTreeNode, TooltipNode { + + @NotNull private final Project myProject; + private final VcsFullCommitDetails myCommit; + + public VcsFullCommitDetailsNode(@NotNull Project project, VcsFullCommitDetails commit) { + super(commit, false); + myProject = project; + myCommit = commit; + } + + @Override + public void render(@NotNull ColoredTreeCellRenderer renderer) { + renderer + .append(myCommit.getSubject(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, renderer.getForeground())); + } + + public String getTooltip() { + return DvcsUtil.getShortHash(myCommit.getId().toString()) + + " " + + DvcsUtil.getDateString(myCommit) + + " by " + + myCommit.getAuthor().getName() + + "\n\n" + + IssueLinkHtmlRenderer.formatTextWithLinks(myProject, myCommit.getFullMessage()); + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java new file mode 100644 index 000000000000..29ac0374cbef --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.dvcs.push.ui; + +import com.intellij.dvcs.push.PushController; +import com.intellij.dvcs.push.VcsPushOptionsPanel; +import com.intellij.dvcs.repo.Repository; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.OptionAction; +import net.miginfocom.swing.MigLayout; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; + +public class VcsPushDialog extends DialogWrapper { + + private final PushLog myListPanel; + private final PushController myController; + private final Action[] myExecutorActions = {new DvcsPushAction("&Force Push", true)}; + @NotNull private final JPanel myAdditionalOptionsFromVcsPanel; + + private DvcsPushAction myPushAction; + + public VcsPushDialog(@NotNull Project project, @NotNull List selectedRepositories) { + super(project); + myController = new PushController(project, this, selectedRepositories); + myListPanel = myController.getPushPanelInfo(); + myAdditionalOptionsFromVcsPanel = new JPanel(new MigLayout("ins 0 0, flowx")); + init(); + setOKButtonText("Push"); + setOKButtonMnemonic('P'); + setTitle("Push Dialog"); + } + + @Override + protected JComponent createCenterPanel() { + + JComponent rootPanel = new JPanel(new BorderLayout(0, 15)); + rootPanel.add(myListPanel, BorderLayout.CENTER); + for (VcsPushOptionsPanel panel : myController.getAdditionalPanels()) { + myAdditionalOptionsFromVcsPanel.add(panel); + } + rootPanel.add(myAdditionalOptionsFromVcsPanel, BorderLayout.SOUTH); + return rootPanel; + } + + @Override + protected String getDimensionServiceKey() { + return VcsPushDialog.class.getName(); + } + + protected void setOKActionEnabled(boolean isEnabled) { + if (myPushAction != null) { + myPushAction.setEnabled(isEnabled); + } + for (Action executorAction : myExecutorActions) { + executorAction.setEnabled(isEnabled); + } + } + + @Override + @NotNull + protected Action[] createActions() { + final List actions = new ArrayList(); + myPushAction = new DvcsPushAction("&Push", false); + myPushAction.putValue(DEFAULT_ACTION, Boolean.TRUE); + actions.add(myPushAction); + myPushAction.setOptions(myExecutorActions); + actions.add(getCancelAction()); + actions.add(getHelpAction()); + return actions.toArray(new Action[actions.size()]); + } + + @NotNull + @Override + protected Action getOKAction() { + return new DvcsPushAction("&Push", false); + } + + @Override + protected String getHelpId() { + return "reference.mercurial.push.dialog"; + } + + private class DvcsPushAction extends AbstractAction implements OptionAction { + private Action[] myOptions = new Action[0]; + private final boolean myForce; + + private DvcsPushAction(String title, boolean force) { + super(title); + myForce = force; + } + + @Override + public void actionPerformed(ActionEvent e) { + myController.push(myForce); + close(OK_EXIT_CODE); + } + + @NotNull + @Override + public Action[] getOptions() { + return myOptions; + } + + public void setOptions(Action[] actions) { + myOptions = actions; + } + } +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleDarkIcon.gif b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleDarkIcon.gif new file mode 100644 index 000000000000..e41778b2c6e7 Binary files /dev/null and b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleDarkIcon.gif differ diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleIcon.gif b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleIcon.gif new file mode 100644 index 000000000000..bfd4da223753 Binary files /dev/null and b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/loadingSimpleIcon.gif differ diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java rename to platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/RepoStateException.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepoStateException.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/repo/RepoStateException.java rename to platform/dvcs-impl/src/com/intellij/dvcs/repo/RepoStateException.java diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/RepositoryImpl.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryImpl.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/repo/RepositoryImpl.java rename to platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryImpl.java diff --git a/platform/dvcs/src/com/intellij/dvcs/repo/RepositoryUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/repo/RepositoryUtil.java rename to platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/CloneDvcsDialog.form b/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.form similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/CloneDvcsDialog.form rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.form diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/CloneDvcsDialog.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/CloneDvcsDialog.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/DvcsBundle.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/DvcsBundle.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/DvcsBundle.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/DvcsBundle.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/DvcsBundle.properties b/platform/dvcs-impl/src/com/intellij/dvcs/ui/DvcsBundle.properties similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/DvcsBundle.properties rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/DvcsBundle.properties diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/NewBranchAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/NewBranchAction.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/RootAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/RootAction.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/VcsLogAction.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogAction.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogOneCommitPerRepoAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogOneCommitPerRepoAction.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/VcsLogOneCommitPerRepoAction.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogOneCommitPerRepoAction.java diff --git a/platform/dvcs/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java similarity index 100% rename from platform/dvcs/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java rename to platform/dvcs-impl/src/com/intellij/dvcs/ui/VcsLogSingleCommitAction.java diff --git a/platform/dvcs/testFramework/com/intellij/dvcs/test/MockProject.java b/platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockProject.java similarity index 100% rename from platform/dvcs/testFramework/com/intellij/dvcs/test/MockProject.java rename to platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockProject.java index 6ca5f302607c..a8dc262d7be1 100644 --- a/platform/dvcs/testFramework/com/intellij/dvcs/test/MockProject.java +++ b/platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockProject.java @@ -15,15 +15,15 @@ */ package com.intellij.dvcs.test; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.components.BaseComponent; -import org.picocontainer.PicoContainer; -import com.intellij.util.messages.MessageBus; import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.messages.MessageBus; import org.jetbrains.annotations.NotNull; +import org.picocontainer.PicoContainer; /** * diff --git a/platform/dvcs/testFramework/com/intellij/dvcs/test/MockProjectRootManager.java b/platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockProjectRootManager.java similarity index 100% rename from platform/dvcs/testFramework/com/intellij/dvcs/test/MockProjectRootManager.java rename to platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockProjectRootManager.java diff --git a/platform/dvcs/testFramework/com/intellij/dvcs/test/MockVcsHelper.java b/platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockVcsHelper.java similarity index 100% rename from platform/dvcs/testFramework/com/intellij/dvcs/test/MockVcsHelper.java rename to platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockVcsHelper.java diff --git a/platform/dvcs/testFramework/com/intellij/dvcs/test/MockVirtualFile.java b/platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockVirtualFile.java similarity index 100% rename from platform/dvcs/testFramework/com/intellij/dvcs/test/MockVirtualFile.java rename to platform/dvcs-impl/testFramework/com/intellij/dvcs/test/MockVirtualFile.java diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java index 27bb8eb5f245..b24aa64d930f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java @@ -371,6 +371,14 @@ public class ProgressManagerImpl extends ProgressManager implements Disposable{ public static Future runProcessWithProgressAsynchronously(@NotNull final Task.Backgroundable task, @NotNull final ProgressIndicator progressIndicator, @Nullable final Runnable continuation) { + return runProcessWithProgressAsynchronously(task, progressIndicator, continuation, ModalityState.NON_MODAL); + } + + @NotNull + public static Future runProcessWithProgressAsynchronously(@NotNull final Task.Backgroundable task, + @NotNull final ProgressIndicator progressIndicator, + @Nullable final Runnable continuation, + @NotNull final ModalityState modalityState) { if (progressIndicator instanceof Disposable) { Disposer.register(ApplicationManager.getApplication(), (Disposable)progressIndicator); } @@ -397,7 +405,7 @@ public class ProgressManagerImpl extends ProgressManager implements Disposable{ public void run() { task.onCancel(); } - }, ModalityState.NON_MODAL); + }, modalityState); } else { final Task.NotificationInfo notificationInfo = task.notifyFinished(); @@ -412,7 +420,7 @@ public class ProgressManagerImpl extends ProgressManager implements Disposable{ public void run() { task.onSuccess(); } - }, ModalityState.NON_MODAL); + }, modalityState); } } }; diff --git a/platform/platform-resources/src/META-INF/PlatformLangPlugin.xml b/platform/platform-resources/src/META-INF/PlatformLangPlugin.xml index b7a11c39ecae..ea14882e5ae6 100644 --- a/platform/platform-resources/src/META-INF/PlatformLangPlugin.xml +++ b/platform/platform-resources/src/META-INF/PlatformLangPlugin.xml @@ -101,5 +101,8 @@ + + + diff --git a/platform/platform-resources/src/META-INF/PlatformPlugin.xml b/platform/platform-resources/src/META-INF/PlatformPlugin.xml index db1cae3e76aa..773b00c2b9f2 100644 --- a/platform/platform-resources/src/META-INF/PlatformPlugin.xml +++ b/platform/platform-resources/src/META-INF/PlatformPlugin.xml @@ -10,6 +10,9 @@ + + + diff --git a/plugins/git4idea/git4idea.iml b/plugins/git4idea/git4idea.iml index 1b6091a77419..a56af8a257c9 100644 --- a/plugins/git4idea/git4idea.iml +++ b/plugins/git4idea/git4idea.iml @@ -49,7 +49,7 @@ - + @@ -58,6 +58,7 @@ + diff --git a/plugins/git4idea/remote-servers-git/remote-servers-git.iml b/plugins/git4idea/remote-servers-git/remote-servers-git.iml index a2ae7940093c..754094b7fb98 100644 --- a/plugins/git4idea/remote-servers-git/remote-servers-git.iml +++ b/plugins/git4idea/remote-servers-git/remote-servers-git.iml @@ -18,6 +18,7 @@ + diff --git a/plugins/git4idea/src/git4idea/GitRevisionNumber.java b/plugins/git4idea/src/git4idea/GitRevisionNumber.java index cd6387a4e629..06fba24f015a 100644 --- a/plugins/git4idea/src/git4idea/GitRevisionNumber.java +++ b/plugins/git4idea/src/git4idea/GitRevisionNumber.java @@ -15,6 +15,7 @@ */ package git4idea; +import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; @@ -103,7 +104,7 @@ public class GitRevisionNumber implements ShortVcsRevisionNumber { */ @NotNull public String getShortRev() { - return GitUtil.getShortHash(myRevisionHash); + return DvcsUtil.getShortHash(myRevisionHash); } /** diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 4ff61fb4a68b..e96d52de8ea0 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -102,7 +102,6 @@ public class GitUtil { public static final String DOT_GIT = ".git"; private final static Logger LOG = Logger.getInstance(GitUtil.class); - private static final int SHORT_HASH_LENGTH = 8; public static final Predicate NOT_NULL_PREDICATE = new Predicate() { @Override @@ -828,17 +827,6 @@ public class GitUtil { }, "\n"); } - @NotNull - public static String getShortHash(@NotNull String hash) { - if (hash.length() == 0) return ""; - if (hash.length() == 40) return hash.substring(0, SHORT_HASH_LENGTH); - if (hash.length() > 40) // revision string encoded with date too - { - return hash.substring(hash.indexOf("[") + 1, SHORT_HASH_LENGTH); - } - return hash; - } - @NotNull public static String fileOrFolder(@NotNull VirtualFile file) { if (file.isDirectory()) { diff --git a/plugins/git4idea/src/git4idea/actions/GitCompareWithBranchAction.java b/plugins/git4idea/src/git4idea/actions/GitCompareWithBranchAction.java index f0ad3b7a5e31..8799c0e69651 100644 --- a/plugins/git4idea/src/git4idea/actions/GitCompareWithBranchAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitCompareWithBranchAction.java @@ -15,6 +15,7 @@ */ package git4idea.actions; +import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.Presentation; @@ -70,7 +71,7 @@ public class GitCompareWithBranchAction extends DumbAwareAction { String currentRevision = repository.getCurrentRevision(); LOG.assertTrue(currentRevision != null, "Current revision is null for " + repository + ". Compare with branch shouldn't be available for fresh repository"); - head = GitUtil.getShortHash(currentRevision); + head = DvcsUtil.getShortHash(currentRevision); } else { head = currentBranch.getName(); diff --git a/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java b/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java index a6488d4ce3d0..d0f051c50475 100644 --- a/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java +++ b/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java @@ -15,6 +15,7 @@ */ package git4idea.history; +import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.SimpleDataContext; import com.intellij.openapi.components.ServiceManager; @@ -116,7 +117,7 @@ public class GitDiffFromHistoryHandler extends BaseDiffFromHistoryHandler" + text + ""; } diff --git a/plugins/git4idea/src/git4idea/ui/GitCommitListPanel.java b/plugins/git4idea/src/git4idea/ui/GitCommitListPanel.java index dcd1c3182b47..94d01022c83e 100644 --- a/plugins/git4idea/src/git4idea/ui/GitCommitListPanel.java +++ b/plugins/git4idea/src/git4idea/ui/GitCommitListPanel.java @@ -15,6 +15,7 @@ */ package git4idea.ui; +import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.vcs.VcsDataKeys; import com.intellij.openapi.vcs.changes.Change; @@ -28,7 +29,6 @@ import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.ListTableModel; import com.intellij.util.ui.UIUtil; import git4idea.GitCommit; -import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -198,7 +198,7 @@ public class GitCommitListPanel extends JPanel implements TypeSafeDataProvider { } private static String getHash(GitCommit commit) { - return GitUtil.getShortHash(commit.getId().toString()); + return DvcsUtil.getShortHash(commit.getId().toString()); } private static String getAuthor(GitCommit commit) { diff --git a/plugins/git4idea/tests/git4idea/history/GitHistoryUtilsTest.java b/plugins/git4idea/tests/git4idea/history/GitHistoryUtilsTest.java index 29072ea53940..bdf87b265427 100644 --- a/plugins/git4idea/tests/git4idea/history/GitHistoryUtilsTest.java +++ b/plugins/git4idea/tests/git4idea/history/GitHistoryUtilsTest.java @@ -38,9 +38,9 @@ import java.io.File; import java.io.IOException; import java.util.*; +import static com.intellij.dvcs.DvcsUtil.getShortHash; import static com.intellij.openapi.vcs.Executor.overwrite; import static com.intellij.openapi.vcs.Executor.touch; -import static git4idea.GitUtil.getShortHash; import static git4idea.test.GitExecutor.*; import static git4idea.test.GitTestUtil.USER_EMAIL; import static git4idea.test.GitTestUtil.USER_NAME; diff --git a/plugins/git4idea/tests/git4idea/log/GitRefManagerTest.java b/plugins/git4idea/tests/git4idea/log/GitRefManagerTest.java index 14d88315c596..2ce560800066 100644 --- a/plugins/git4idea/tests/git4idea/log/GitRefManagerTest.java +++ b/plugins/git4idea/tests/git4idea/log/GitRefManagerTest.java @@ -3,6 +3,7 @@ package git4idea.log; import com.intellij.mock.MockVirtualFile; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.testFramework.UsefulTestCase; import com.intellij.util.Function; @@ -283,6 +284,12 @@ public class GitRefManagerTest extends UsefulTestCase { throw new UnsupportedOperationException(); } + @Nullable + @Override + public AbstractVcs getVcs() { + return null; + } + @Nullable @Override public String getCurrentRevision() { diff --git a/plugins/github/github.iml b/plugins/github/github.iml index 54d546aa0886..7866631c1521 100644 --- a/plugins/github/github.iml +++ b/plugins/github/github.iml @@ -18,7 +18,8 @@ - + + diff --git a/plugins/hg4idea/hg4idea.iml b/plugins/hg4idea/hg4idea.iml index 7f5074895e4f..93b5a5109bde 100644 --- a/plugins/hg4idea/hg4idea.iml +++ b/plugins/hg4idea/hg4idea.iml @@ -16,9 +16,10 @@ - + + diff --git a/plugins/hg4idea/src/META-INF/plugin.xml b/plugins/hg4idea/src/META-INF/plugin.xml index 9ccc175803f5..26b69326a703 100644 --- a/plugins/hg4idea/src/META-INF/plugin.xml +++ b/plugins/hg4idea/src/META-INF/plugin.xml @@ -21,6 +21,7 @@ + diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java index 2b6108dddbbc..c8e6075649ce 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java @@ -16,26 +16,18 @@ package org.zmlx.hg4idea; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsNotifier; -import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.action.HgCommandResultNotifier; import org.zmlx.hg4idea.command.HgPushCommand; import org.zmlx.hg4idea.execution.HgCommandResult; import org.zmlx.hg4idea.execution.HgCommandResultHandler; -import org.zmlx.hg4idea.repo.HgRepository; import org.zmlx.hg4idea.ui.HgPushDialog; -import java.util.Collection; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,17 +46,17 @@ public class HgPusher { myProject = project; } - public void showDialogAndPush (@NotNull Collection repositories,@Nullable final HgRepository selectedRepo) { + /* public void showDialogAndPush(@NotNull List selectedRepositories) { - if (repositories.isEmpty()) { + if (selectedRepositories.isEmpty()) { VcsBalloonProblemNotifier.showOverChangesView(myProject, "No Mercurial repositories in the project", MessageType.ERROR); return; } final AtomicReference pushCommand = new AtomicReference(); final HgPushDialog dialog = new HgPushDialog(myProject, repositories, selectedRepo); dialog.show(); - if (dialog.isOK()) { - pushCommand.set(preparePushCommand(myProject, dialog)); + if (d2.isOK()) { + pushCommand.set(preparePushCommand(myProject, dialog)); new Task.Backgroundable(myProject, "Pushing...", false) { @Override public void run(@NotNull ProgressIndicator indicator) { @@ -74,9 +66,9 @@ public class HgPusher { } }.queue(); } - } + }*/ - private static void push(final Project project, HgPushCommand command) { + public static void push(final Project project, HgPushCommand command) { final VirtualFile repo = command.getRepo(); command.execute(new HgCommandResultHandler() { @Override @@ -91,9 +83,11 @@ public class HgPusher { String successDescription = String.format("Pushed %d %s [%s]", commitsNum, StringUtil.pluralize("commit", commitsNum), repo.getPresentableName()); VcsNotifier.getInstance(project).notifySuccess(successTitle, successDescription); - } else if (result.getExitValue() == NOTHING_TO_PUSH_EXIT_VALUE) { + } + else if (result.getExitValue() == NOTHING_TO_PUSH_EXIT_VALUE) { VcsNotifier.getInstance(project).notifySuccess("Nothing to push"); - } else { + } + else { new HgCommandResultNotifier(project).notifyError(result, "Push failed", "Failed to push to [" + repo.getPresentableName() + "]"); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java index d38bc7bfc6a7..d6282ebe8786 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java @@ -29,7 +29,9 @@ import org.zmlx.hg4idea.util.HgUtil; import javax.swing.*; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; abstract class HgAbstractGlobalAction extends AnAction { @@ -48,12 +50,17 @@ abstract class HgAbstractGlobalAction extends AnAction { if (project == null) { return; } - VirtualFile file = event.getData(CommonDataKeys.VIRTUAL_FILE); - HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(project); - HgRepository repo = file != null ? repositoryManager.getRepositoryForFile(file): HgUtil.getCurrentRepository(project); + VirtualFile[] files = event.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); + final HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(project); List repositories = repositoryManager.getRepositories(); if (!repositories.isEmpty()) { - execute(project, repositories, repo); + List selectedRepositories = (files == null || files.length == 0) + ? + Collections.singletonList(HgUtil.getCurrentRepository(project)) + : HgActionUtil.collectRepositoriesFromFiles(repositoryManager, + Arrays.asList(files)); + + execute(project, repositories, selectedRepositories); } } @@ -66,7 +73,7 @@ abstract class HgAbstractGlobalAction extends AnAction { protected abstract void execute(@NotNull Project project, @NotNull Collection repositories, - @Nullable HgRepository selectedRepo); + @NotNull List selectedRepositories); public static void handleException(@Nullable Project project, @NotNull Exception e) { handleException(project, "Error", e); @@ -102,16 +109,4 @@ abstract class HgAbstractGlobalAction extends AnAction { return true; } - @Nullable - public static HgRepository getSelectedRepositoryFromEvent(AnActionEvent e) { - final DataContext dataContext = e.getDataContext(); - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - if (project == null) { - return null; - } - VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); - HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(project); - return file != null ? repositoryManager.getRepositoryForFile(file) : HgUtil.getCurrentRepository(project); - } - } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalSingleRepoAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalSingleRepoAction.java new file mode 100644 index 000000000000..bfb2d7715e68 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalSingleRepoAction.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.action; + +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.repo.HgRepository; + +import javax.swing.*; +import java.util.Collection; +import java.util.List; + +public abstract class HgAbstractGlobalSingleRepoAction extends HgAbstractGlobalAction { + + public HgAbstractGlobalSingleRepoAction(Icon icon) { + super(icon); + } + + public HgAbstractGlobalSingleRepoAction() { + super(); + } + + @Override + protected void execute(@NotNull Project project, + @NotNull Collection repositories, + @NotNull List selectedRepositories) { + execute(project, repositories, selectedRepositories.isEmpty() ? null : selectedRepositories.get(0)); + } + + protected abstract void execute(@NotNull Project project, + @NotNull Collection repositories, + @Nullable HgRepository selectedRepo); +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgActionUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgActionUtil.java new file mode 100644 index 000000000000..6abebd587787 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgActionUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.action; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.sun.istack.internal.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.repo.HgRepository; +import org.zmlx.hg4idea.repo.HgRepositoryManager; +import org.zmlx.hg4idea.util.HgUtil; + +import java.util.Collection; +import java.util.List; + +public class HgActionUtil { + + @NotNull + public static List collectRepositoriesFromFiles(@NotNull final HgRepositoryManager repositoryManager, + @NotNull Collection files) { + return ContainerUtil.mapNotNull(files, new Function() { + @Override + public HgRepository fun(VirtualFile file) { + return repositoryManager.getRepositoryForFile(file); + } + }); + } + + @Nullable + public static HgRepository getSelectedRepositoryFromEvent(AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + if (project == null) { + return null; + } + VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); + HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(project); + return file != null ? repositoryManager.getRepositoryForFile(file) : HgUtil.getCurrentRepository(project); + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java index b8a10ed92116..d799cde1705c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgBranchesAction.java @@ -22,7 +22,7 @@ import org.zmlx.hg4idea.repo.HgRepository; import java.util.Collection; -public class HgBranchesAction extends HgAbstractGlobalAction { +public class HgBranchesAction extends HgAbstractGlobalSingleRepoAction { @Override protected void execute(@NotNull Project project, @NotNull Collection repositories, @Nullable HgRepository selectedRepo) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java index 5c97e3ca9f39..c8734e8109f4 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java @@ -25,7 +25,7 @@ import org.zmlx.hg4idea.util.HgErrorUtil; import java.util.Collection; -public class HgCreateTagAction extends HgAbstractGlobalAction { +public class HgCreateTagAction extends HgAbstractGlobalSingleRepoAction { public void execute(@NotNull final Project project, @NotNull Collection repositories, diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMerge.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMerge.java index 1743e7e5b56e..45ff24e382ce 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMerge.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMerge.java @@ -33,7 +33,7 @@ import org.zmlx.hg4idea.ui.HgMergeDialog; import java.util.Collection; -public class HgMerge extends HgAbstractGlobalAction { +public class HgMerge extends HgAbstractGlobalSingleRepoAction { @Override public void execute(@NotNull final Project project, diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgProcessRebaseAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgProcessRebaseAction.java index 342369f0a9bb..84e9d5b1beaf 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgProcessRebaseAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgProcessRebaseAction.java @@ -18,10 +18,10 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.actionSystem.AnActionEvent; import org.zmlx.hg4idea.repo.HgRepository; -public abstract class HgProcessRebaseAction extends HgAbstractGlobalAction { +public abstract class HgProcessRebaseAction extends HgAbstractGlobalSingleRepoAction { protected static boolean isRebasing(AnActionEvent e) { - HgRepository repository = HgAbstractGlobalAction.getSelectedRepositoryFromEvent(e); + HgRepository repository = HgActionUtil.getSelectedRepositoryFromEvent(e); return repository != null && repository.getState() == HgRepository.State.REBASING; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java index d5a934e1d86d..74beff13eb81 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java @@ -24,7 +24,7 @@ import org.zmlx.hg4idea.ui.HgPullDialog; import java.util.Collection; -public class HgPullAction extends HgAbstractGlobalAction { +public class HgPullAction extends HgAbstractGlobalSingleRepoAction { public HgPullAction() { super(AllIcons.Actions.CheckOut); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java index 1b5704f1d0f4..232b2ef7fa73 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java @@ -12,14 +12,14 @@ // limitations under the License. package org.zmlx.hg4idea.action; +import com.intellij.dvcs.push.ui.VcsPushDialog; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.zmlx.hg4idea.HgPusher; import org.zmlx.hg4idea.repo.HgRepository; import java.util.Collection; +import java.util.List; public class HgPushAction extends HgAbstractGlobalAction { public HgPushAction() { @@ -29,7 +29,7 @@ public class HgPushAction extends HgAbstractGlobalAction { @Override public void execute(@NotNull final Project project, @NotNull Collection repositories, - @Nullable final HgRepository selectedRepo) { - new HgPusher(project).showDialogAndPush(repositories, selectedRepo); + @NotNull final List selectedRepositories) { + new VcsPushDialog(project, selectedRepositories).show(); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java index 938413e95356..279747c5278b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java @@ -25,7 +25,7 @@ import org.zmlx.hg4idea.ui.HgRunConflictResolverDialog; import java.util.Collection; -public class HgRunConflictResolverAction extends HgAbstractGlobalAction { +public class HgRunConflictResolverAction extends HgAbstractGlobalSingleRepoAction { @Override public void execute(@NotNull final Project project, @NotNull Collection repositories, @Nullable HgRepository selectedRepo) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgUpdateToAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgUpdateToAction.java index 7e1025921234..415cd223bd1f 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgUpdateToAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgUpdateToAction.java @@ -29,7 +29,7 @@ import org.zmlx.hg4idea.util.HgErrorUtil; import java.util.Collection; -public class HgUpdateToAction extends HgAbstractGlobalAction { +public class HgUpdateToAction extends HgAbstractGlobalSingleRepoAction { @Override protected void execute(@NotNull Project project, @NotNull Collection repositories, @Nullable HgRepository selectedRepo) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java index c719d9174d39..4acbc1f72370 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java @@ -31,7 +31,7 @@ public class HgIdentifyCommand { public HgCommandResult execute(@NotNull ModalityState state) { final List arguments = new LinkedList(); arguments.add(source); - final HgRemoteCommandExecutor executor = new HgRemoteCommandExecutor(project, source, state); + final HgRemoteCommandExecutor executor = new HgRemoteCommandExecutor(project, source, state, false); executor.setSilent(true); return executor.executeInCurrentThread(null, "identify", arguments); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgOutgoingCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgOutgoingCommand.java index 64f155064ead..89f5d7ad4b8e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgOutgoingCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgOutgoingCommand.java @@ -12,11 +12,19 @@ // limitations under the License. package org.zmlx.hg4idea.command; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgRemoteCommandExecutor; +import org.zmlx.hg4idea.log.HgHistoryUtil; import org.zmlx.hg4idea.util.HgUtil; +import java.util.LinkedList; +import java.util.List; + public class HgOutgoingCommand extends HgRemoteChangesetsCommand { public HgOutgoingCommand(Project project) { @@ -27,4 +35,22 @@ public class HgOutgoingCommand extends HgRemoteChangesetsCommand { protected String getRepositoryUrl(VirtualFile root) { return HgUtil.getRepositoryDefaultPushPath(project, root); } + + @Nullable + public HgCommandResult execute(@NotNull VirtualFile repo, + @NotNull String template, + @NotNull String source, + @NotNull String destination, + boolean doNotShowAuthorizationRequest) { + + List arguments = new LinkedList(); + arguments.add("--template"); + arguments.add(template); + arguments.add(HgHistoryUtil.prepareParameter("rev", source)); + arguments.add(destination); + HgRemoteCommandExecutor commandExecutor = + new HgRemoteCommandExecutor(project, destination, ModalityState.any(), doNotShowAuthorizationRequest); + commandExecutor.setOutputAlwaysSuppressed(true); + return commandExecutor.executeInCurrentThread(repo, "outgoing", arguments); + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java index 14f6c981777b..01f9083da808 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java @@ -39,10 +39,13 @@ class HgCommandAuthenticator { private GetPasswordRunnable myGetPassword; private final Project myProject; private boolean myForceAuthorization; + //todo replace silent mode and/or force authorization + private boolean mySilentMode; - public HgCommandAuthenticator(Project project, boolean forceAuthorization) { + public HgCommandAuthenticator(Project project, boolean forceAuthorization, boolean silent) { myProject = project; myForceAuthorization = forceAuthorization; + mySilentMode = silent; } public void saveCredentials() { @@ -71,7 +74,7 @@ class HgCommandAuthenticator { } public boolean promptForAuthentication(Project project, String proposedLogin, String uri, String path, @Nullable ModalityState state) { - GetPasswordRunnable runnable = new GetPasswordRunnable(project, proposedLogin, uri, path, myForceAuthorization); + GetPasswordRunnable runnable = new GetPasswordRunnable(project, proposedLogin, uri, path, myForceAuthorization, mySilentMode); ApplicationManager.getApplication().invokeAndWait(runnable, state == null ? ModalityState.defaultModalityState() : state); myGetPassword = runnable; return runnable.isOk(); @@ -96,19 +99,27 @@ class HgCommandAuthenticator { @Nullable private String myURL; private boolean myRememberPassword; private boolean myForceAuthorization; + private final boolean mySilent; - public GetPasswordRunnable(Project project, String proposedLogin, String uri, String path, boolean forceAuthorization) { + public GetPasswordRunnable(Project project, + String proposedLogin, + String uri, + String path, + boolean forceAuthorization, boolean silent) { this.myProject = project; this.myProposedLogin = proposedLogin; this.myURL = uri + path; this.myForceAuthorization = forceAuthorization; + mySilent = silent; } - + public void run() { // find if we've already been here final HgVcs vcs = HgVcs.getInstance(myProject); - if (vcs == null) { return; } + if (vcs == null) { + return; + } @NotNull final HgGlobalSettings hgGlobalSettings = vcs.getGlobalSettings(); @Nullable String rememberedLoginsForUrl = null; @@ -128,11 +139,14 @@ class HgCommandAuthenticator { final String key = keyForUrlAndLogin(myURL, login); try { final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - password = passwordSafe.getMemoryProvider().getPassword(myProject, HgCommandAuthenticator.class, key); - if (password == null) { + if (mySilent) { + password = passwordSafe.getMemoryProvider().getPassword(myProject, HgCommandAuthenticator.class, key); + } + else { password = passwordSafe.getPassword(myProject, HgCommandAuthenticator.class, key); } - } catch (PasswordSafeException e) { + } + catch (PasswordSafeException e) { LOG.info("Couldn't get password for key [" + key + "]", e); } } @@ -146,7 +160,13 @@ class HgCommandAuthenticator { return; } - final AuthDialog dialog = new AuthDialog(myProject, HgVcsMessages.message("hg4idea.dialog.login.password.required"), HgVcsMessages.message("hg4idea.dialog.login.description", myURL), + if (mySilent) { + ok = false; + return; + } + + final AuthDialog dialog = new AuthDialog(myProject, HgVcsMessages.message("hg4idea.dialog.login.password.required"), + HgVcsMessages.message("hg4idea.dialog.login.description", myURL), login, password, true); dialog.show(); if (dialog.isOK()) { @@ -182,5 +202,4 @@ class HgCommandAuthenticator { private static String keyForUrlAndLogin(String stringUrl, String login) { return login + ":" + stringUrl; } - } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgRemoteCommandExecutor.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgRemoteCommandExecutor.java index fdab67fbed6f..ce139cb16d9e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgRemoteCommandExecutor.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgRemoteCommandExecutor.java @@ -30,16 +30,22 @@ import java.util.List; public class HgRemoteCommandExecutor extends HgCommandExecutor { @Nullable private ModalityState myState; + final boolean myIgnoreAuthorizationRequest; public HgRemoteCommandExecutor(@NotNull Project project, @Nullable String destination) { - this(project, destination, null); + this(project, destination, null, false); + } + + public HgRemoteCommandExecutor(@NotNull Project project, @Nullable String destination, boolean ignoreAuthorizationRequest) { + this(project, destination, null, ignoreAuthorizationRequest); } public HgRemoteCommandExecutor(@NotNull Project project, @Nullable String destination, - @Nullable ModalityState state) { + @Nullable ModalityState state, boolean ignoreAuthorizationRequest) { super(project, destination); myState = state; + myIgnoreAuthorizationRequest = ignoreAuthorizationRequest; } @Nullable @@ -48,7 +54,7 @@ public class HgRemoteCommandExecutor extends HgCommandExecutor { HgCommandResult result = executeInCurrentThread(repo, operation, arguments, false); - if (HgErrorUtil.isAuthorizationError(result)) { + if (!myIgnoreAuthorizationRequest && HgErrorUtil.isAuthorizationError(result)) { if (HgErrorUtil.hasAuthorizationInDestinationPath(myDestination)) { new HgCommandResultNotifier(myProject) .notifyError(result, "Authorization failed", "Your hgrc file settings have wrong username or password in [paths].\n" + @@ -62,11 +68,11 @@ public class HgRemoteCommandExecutor extends HgCommandExecutor { @Nullable private HgCommandResult executeInCurrentThread(@Nullable final VirtualFile repo, - @NotNull final String operation, - @Nullable final List arguments, - boolean forceAuthorization) { + @NotNull final String operation, + @Nullable final List arguments, + boolean forceAuthorization) { - PassReceiver passReceiver = new PassReceiver(myProject, forceAuthorization, myState); + PassReceiver passReceiver = new PassReceiver(myProject, forceAuthorization, myIgnoreAuthorizationRequest, myState); SocketServer passServer = new SocketServer(passReceiver); try { int passPort = passServer.start(); @@ -109,11 +115,13 @@ public class HgRemoteCommandExecutor extends HgCommandExecutor { private final Project myProject; private HgCommandAuthenticator myAuthenticator; private boolean myForceAuthorization; + private boolean mySilentMode; @Nullable private ModalityState myState; - private PassReceiver(Project project, boolean forceAuthorization, @Nullable ModalityState state) { + private PassReceiver(Project project, boolean forceAuthorization, boolean silent, @Nullable ModalityState state) { myProject = project; myForceAuthorization = forceAuthorization; + mySilentMode = silent; myState = state; } @@ -129,7 +137,7 @@ public class HgRemoteCommandExecutor extends HgCommandExecutor { String path = new String(readDataBlock(dataInputStream)); String proposedLogin = new String(readDataBlock(dataInputStream)); - HgCommandAuthenticator authenticator = new HgCommandAuthenticator(myProject, myForceAuthorization); + HgCommandAuthenticator authenticator = new HgCommandAuthenticator(myProject, myForceAuthorization, mySilentMode); boolean ok = authenticator.promptForAuthentication(myProject, proposedLogin, uri, path, myState); if (ok) { myAuthenticator = authenticator; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java index dfb576f98ab3..73998ba4ff8e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java @@ -94,23 +94,33 @@ public class HgHistoryUtil { } /** - *

Get & parse hg log detailed output with commits, their parents and their changes.

+ *

Get & parse hg log detailed output with commits, their parents and their changes. + * For null destination return log command result

*

*

Warning: this is method is efficient by speed, but don't query too much, because the whole log output is retrieved at once, * and it can occupy too much memory. The estimate is ~600Kb for 1000 commits.

*/ @NotNull - public static List history(@NotNull final Project project, @NotNull final VirtualFile root, int limit, + public static List history(@NotNull final Project project, + @NotNull final VirtualFile root, + int limit, @NotNull List parameters) throws VcsException { - final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); - if (factory == null) { - return Collections.emptyList(); - } HgVcs hgvcs = HgVcs.getInstance(project); assert hgvcs != null; final HgVersion version = hgvcs.getVersion(); String[] templates = HgBaseLogParser.constructFullTemplateArgument(true, version); HgCommandResult result = getLogResult(project, root, version, limit, parameters, HgChangesetUtil.makeTemplate(templates)); + return createFullCommitsFromResult(project, root, result, version); + } + + public static List createFullCommitsFromResult(@NotNull Project project, + @NotNull VirtualFile root, + @Nullable HgCommandResult result, + @NotNull HgVersion version) { + final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); + if (factory == null) { + return Collections.emptyList(); + } List hgRevisions = getCommitRecords(project, result, new HgFileRevisionLogParser(project, getOriginalHgFile(project, root), version)); List vcsFullCommitDetailsList = new ArrayList(); @@ -331,4 +341,8 @@ public class HgHistoryUtil { } return branchHeads; } + + public static String prepareParameter(String paramName, String value) { + return "--" + paramName + "=" + value; // no value escaping needed, because the parameter itself will be quoted by GeneralCommandLine + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java index 4adec9dff515..3b989e448b61 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java @@ -180,11 +180,11 @@ public class HgLogProvider implements VcsLogProvider { boolean atLeastOneBranchExists = false; for (String branchName : filterCollection.getBranchFilter().getBranchNames()) { if (branchName.equals(TIP_REFERENCE) || branchExists(repository, branchName)) { - filterParameters.add(prepareParameter("branch", branchName)); + filterParameters.add(HgHistoryUtil.prepareParameter("branch", branchName)); atLeastOneBranchExists = true; } else if (branchName.equals(HEAD_REFERENCE)) { - filterParameters.add(prepareParameter("branch", ".")); + filterParameters.add(HgHistoryUtil.prepareParameter("branch", ".")); filterParameters.add("-r"); filterParameters.add("::."); //all ancestors for current revision; atLeastOneBranchExists = true; @@ -197,7 +197,7 @@ public class HgLogProvider implements VcsLogProvider { if (filterCollection.getUserFilter() != null) { for (String authorName : filterCollection.getUserFilter().getUserNames(root)) { - filterParameters.add(prepareParameter("user", authorName)); + filterParameters.add(HgHistoryUtil.prepareParameter("user", authorName)); } } @@ -223,7 +223,7 @@ public class HgLogProvider implements VcsLogProvider { if (filterCollection.getTextFilter() != null) { String textFilter = filterCollection.getTextFilter().getText(); - filterParameters.add(prepareParameter("keyword", textFilter)); + filterParameters.add(HgHistoryUtil.prepareParameter("keyword", textFilter)); } if (filterCollection.getStructureFilter() != null) { @@ -262,10 +262,6 @@ public class HgLogProvider implements VcsLogProvider { return HgHistoryUtil.getDescendingHeadsOfBranches(myProject, root, commitHash); } - private static String prepareParameter(String paramName, String value) { - return "--" + paramName + "=" + value; // no value escaping needed, because the parameter itself will be quoted by GeneralCommandLine - } - private static boolean branchExists(@NotNull HgRepository repository, @NotNull String branchName) { return repository.getBranches().keySet().contains(branchName) || HgUtil.getNamesWithoutHashes(repository.getBookmarks()).contains(branchName); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java index c60e46c63ced..06b74148156e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java @@ -13,6 +13,7 @@ package org.zmlx.hg4idea.provider.commit; import com.intellij.dvcs.DvcsCommitAdditionalComponent; +import com.intellij.dvcs.push.ui.VcsPushDialog; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.ProgressIndicator; @@ -37,6 +38,7 @@ import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.*; +import org.zmlx.hg4idea.action.HgActionUtil; import org.zmlx.hg4idea.command.*; import org.zmlx.hg4idea.execution.HgCommandException; import org.zmlx.hg4idea.execution.HgCommandExecutor; @@ -132,13 +134,12 @@ public class HgCheckinEnvironment implements CheckinEnvironment { // push if needed if (myNextCommitIsPushed && exceptions.isEmpty()) { - final VirtualFile preselectedRepo = repositoriesMap.size() == 1 ? repositoriesMap.keySet().iterator().next() : null; + final Set preselectedFiles = repositoriesMap.keySet(); HgRepositoryManager repositoryManager = HgUtil.getRepositoryManager(myProject); - final HgRepository repo = preselectedRepo != null ? repositoryManager.getRepositoryForFile(preselectedRepo) : null; - final Collection repositories = repositoryManager.getRepositories(); + final List preselectedRepositories = HgActionUtil.collectRepositoriesFromFiles(repositoryManager, preselectedFiles); UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { - new HgPusher(myProject).showDialogAndPush(repositories, repo); + new VcsPushDialog(myProject, preselectedRepositories).show(); } }); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java new file mode 100644 index 000000000000..8d24b20ff7c5 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java @@ -0,0 +1,105 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.*; +import com.intellij.dvcs.push.ui.RepositoryNode; +import com.intellij.dvcs.repo.Repository; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.command.HgOutgoingCommand; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.log.HgBaseLogParser; +import org.zmlx.hg4idea.log.HgHistoryUtil; +import org.zmlx.hg4idea.util.HgChangesetUtil; +import org.zmlx.hg4idea.util.HgErrorUtil; +import org.zmlx.hg4idea.util.HgVersion; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HgOutgoingCommitsProvider extends OutgoingCommitsProvider { + + + private static final Logger LOG = Logger.getInstance(HgOutgoingCommitsProvider.class); + private static final String LOGIN_AND_REFRESH_LINK = "Enter Password & Refresh"; + + @NotNull + @Override + public OutgoingResult getOutgoingCommits(@NotNull final Repository repository, + @NotNull final PushSpec pushSpec, + boolean initial) { + final Project project = repository.getProject(); + HgVcs hgvcs = HgVcs.getInstance(project); + assert hgvcs != null; + final HgVersion version = hgvcs.getVersion(); + String[] templates = HgBaseLogParser.constructFullTemplateArgument(true, version); + HgOutgoingCommand hgOutgoingCommand = new HgOutgoingCommand(project); + PushTarget target = pushSpec.getTarget(); + if (target != null) { + assert target instanceof HgTarget : String.format("Wrong Target for Repository %s", repository.getPresentableUrl()); + } + HgTarget hgTarget = (HgTarget)target; + List errors = new ArrayList(); + if (target == null || StringUtil.isEmptyOrSpaces(hgTarget.myTarget)) { + errors.add(new VcsError("Hg push path could not be empty.")); + return new OutgoingResult(Collections.emptyList(), errors); + } + HgCommandResult result = hgOutgoingCommand + .execute(repository.getRoot(), HgChangesetUtil.makeTemplate(templates), pushSpec.getSource().getPresentation(), + hgTarget.myTarget, initial); + if (result == null) { + errors.add(new VcsError("Couldn't execute hg outgoing command for " + repository)); + return new OutgoingResult(Collections.emptyList(), errors); + } + List resultErrors = result.getErrorLines(); + if (resultErrors != null && !resultErrors.isEmpty() && result.getExitValue() != 0) { + for (String error : resultErrors) { + if (HgErrorUtil.isAbortLine(error)) { + if (HgErrorUtil.isAuthorizationError(error)) { + VcsError authorizationError = new VcsError(error, LOGIN_AND_REFRESH_LINK); + authorizationError.addClickListener(new TreeNodeLinkListener() { + @Override + public void onClick(@NotNull DefaultMutableTreeNode source) { + TreeNode parent = source.getParent(); + if (parent instanceof RepositoryNode) { + //todo change value to something special, or create force refreshNode method + ((RepositoryNode)parent).fireOnChange(((RepositoryNode)parent).getValue()); + } + } + } + ); + errors.add(authorizationError); + } + else { + errors.add(new VcsError(error)); + } + } + } + LOG.warn(resultErrors.toString()); + } + return new OutgoingResult( + ContainerUtil.reverse(HgHistoryUtil.createFullCommitsFromResult(project, repository.getRoot(), result, version)), errors); + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushOptionsPanel.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushOptionsPanel.java new file mode 100644 index 000000000000..336f8ff1aa4c --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushOptionsPanel.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.VcsPushOptionsPanel; +import com.intellij.openapi.ui.ComboBox; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; + +public class HgPushOptionsPanel extends VcsPushOptionsPanel { + + private final ComboBox myReferenceStrategyCombobox; + + public HgPushOptionsPanel() { + setLayout(new BorderLayout()); + myReferenceStrategyCombobox = new ComboBox(); + HgVcsPushOptionValue[] values = HgVcsPushOptionValue.values(); + DefaultComboBoxModel comboModel = new DefaultComboBoxModel(values); + myReferenceStrategyCombobox.setModel(comboModel); + JLabel referenceStrategyLabel = new JLabel("Export Bookmarks: "); + add(referenceStrategyLabel, BorderLayout.WEST); + add(myReferenceStrategyCombobox, BorderLayout.CENTER); + } + + @Override + @NotNull + public HgVcsPushOptionValue getValue() { + return (HgVcsPushOptionValue)myReferenceStrategyCombobox.getSelectedItem(); + } + + @Override + public void addValueChangeListener(ActionListener listener) { + myReferenceStrategyCombobox.addActionListener(listener); + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java new file mode 100644 index 000000000000..c56d1b8ea5be --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.OutgoingCommitsProvider; +import com.intellij.dvcs.push.PushSupport; +import com.intellij.dvcs.push.Pusher; +import com.intellij.dvcs.push.VcsPushOptionsPanel; +import com.intellij.dvcs.repo.Repository; +import com.intellij.dvcs.repo.RepositoryManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.repo.HgRepository; +import org.zmlx.hg4idea.util.HgUtil; + +import java.util.Collection; + +public class HgPushSupport extends PushSupport { + private final Project myProject; + + public HgPushSupport(Project project) { + myProject = project; + } + + @NotNull + @Override + public AbstractVcs getVcs() { + return HgVcs.getInstance(myProject); + } + + @NotNull + @Override + public Pusher getPusher() { + return new HgPusher(); + } + + @NotNull + @Override + public OutgoingCommitsProvider getOutgoingCommitsProvider() { + return new HgOutgoingCommitsProvider(); + } + + @Nullable + @Override + public HgTarget getDefaultTarget(@NotNull Repository repository) { + if (!(repository instanceof HgRepository)) return null; + String defaultPushPath = ((HgRepository)repository).getRepositoryConfig().getDefaultPushPath(); + return defaultPushPath == null ? null : new HgTarget(defaultPushPath); + } + + @NotNull + @Override + public Collection getTargetNames(@NotNull Repository repository) { + if (repository instanceof HgRepository) { + return ContainerUtil.map(((HgRepository)repository).getRepositoryConfig().getPaths(), new Function() { + @Override + public String fun(String s) { + return HgUtil.removePasswordIfNeeded(s); + } + }); + } + return ContainerUtil.emptyList(); + } + + @NotNull + @Override + public HgSource getSource(@NotNull Repository repository) { + String localBranch = + repository instanceof HgRepository ? HgUtil.getActiveBranchName((HgRepository)repository) : HgRepository.DEFAULT_BRANCH; + return new HgSource(localBranch); + } + + @Override + public HgTarget createTarget(String targetName) { + return new HgTarget(targetName); + } + + @NotNull + @Override + public RepositoryManager getRepositoryManager() { + return HgUtil.getRepositoryManager(myProject); + } + + @Nullable + public VcsPushOptionsPanel getVcsPushOptionsPanel() { + return new HgPushOptionsPanel(); + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java new file mode 100644 index 000000000000..d9dfbcc5f666 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.PushSpec; +import com.intellij.dvcs.push.Pusher; +import com.intellij.dvcs.push.VcsPushOptionValue; +import com.intellij.dvcs.repo.Repository; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.command.HgPushCommand; +import org.zmlx.hg4idea.repo.HgRepository; + +import java.util.Map; + +public class HgPusher extends Pusher { + + @Override + public void push(@NotNull Map pushSpecs, @Nullable VcsPushOptionValue vcsPushOptionValue, boolean force) { + for (Map.Entry entry : pushSpecs.entrySet()) { + Repository repository = entry.getKey(); + if (repository instanceof HgRepository) { + HgRepository hgRepository = (HgRepository)repository; + PushSpec hgSpec = entry.getValue(); + HgTarget destination = (HgTarget)hgSpec.getTarget(); + if (destination == null) { + continue; + } + HgSource source = (HgSource)hgSpec.getSource(); + Project project = repository.getProject(); + final HgPushCommand pushCommand = new HgPushCommand(project, repository.getRoot(), destination.myTarget); + pushCommand.setForce(force); + if (source.mySource.equals(hgRepository.getCurrentBookmark())) { + if (vcsPushOptionValue == HgVcsPushOptionValue.Current) { + pushCommand.setBookmarkName(source.mySource); + } + else { + pushCommand.setRevision(source.mySource); + } + } + else { + pushCommand.setBranchName(source.mySource); + } + org.zmlx.hg4idea.HgPusher.push(project, pushCommand); + } + } + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java new file mode 100644 index 000000000000..292e2df7244c --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.PushSource; + +public class HgSource implements PushSource { + String mySource; + + public HgSource(String branch) { + mySource = branch; + } + + @Override + public String getPresentation() { + return mySource; + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgTarget.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgTarget.java new file mode 100644 index 000000000000..8f5aca3838ca --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgTarget.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.PushTarget; +import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.util.HgUtil; + +public class HgTarget implements PushTarget { + @NotNull String myTarget; + + public HgTarget(@NotNull String name) { + myTarget = name; + } + + @Override + @NotNull + public String getPresentation() { + return HgUtil.removePasswordIfNeeded(myTarget); + } +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgVcsPushOptionValue.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgVcsPushOptionValue.java new file mode 100644 index 000000000000..5b35e7f5e6e0 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgVcsPushOptionValue.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.push; + +import com.intellij.dvcs.push.VcsPushOptionValue; + +public enum HgVcsPushOptionValue implements VcsPushOptionValue { + None, Current +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java index 77c81ee80993..b670fb056c0c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/repo/HgRepositoryImpl.java @@ -21,6 +21,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcs.log.Hash; @@ -96,6 +97,12 @@ public class HgRepositoryImpl extends RepositoryImpl implements HgRepository { return myInfo.getState(); } + @Nullable + @Override + public AbstractVcs getVcs() { + return HgVcs.getInstance(getProject()); + } + @Override @NotNull public String getCurrentBranch() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgErrorUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgErrorUtil.java index 1810370588b7..7592f5830ee5 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgErrorUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgErrorUtil.java @@ -43,7 +43,7 @@ public final class HgErrorUtil { } final List errorLines = result.getErrorLines(); for (String line : errorLines) { - if (!StringUtil.isEmptyOrSpaces(line) && line.trim().startsWith("abort:")) { + if (isAbortLine(line)) { return true; } } @@ -55,8 +55,7 @@ public final class HgErrorUtil { return false; } String line = getLastErrorLine(result); - return !StringUtil.isEmptyOrSpaces(line) && (line.contains("authorization required") || line.contains("authorization failed") - ); + return isAuthorizationError(line); } @Nullable @@ -119,4 +118,12 @@ public final class HgErrorUtil { Matcher matcher = UNCOMMITTED_PATTERN.matcher(result.getRawError()); return matcher.matches(); } + + public static boolean isAuthorizationError(String line) { + return !StringUtil.isEmptyOrSpaces(line) && (line.contains("authorization required") || line.contains("authorization failed")); + } + + public static boolean isAbortLine(String line) { + return !StringUtil.isEmptyOrSpaces(line) && line.trim().startsWith("abort:"); + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgUtil.java index ef7f78f38836..3bb14721f0a8 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/util/HgUtil.java @@ -584,11 +584,16 @@ public abstract class HgUtil { if (state != HgRepository.State.NORMAL) { branchText += state.toString() + " "; } + return branchText + getActiveBranchName(repository); + } + + @NotNull + public static String getActiveBranchName(@NotNull HgRepository repository) { String branchOrBookMarkName = repository.getCurrentBookmark(); if (StringUtil.isEmptyOrSpaces(branchOrBookMarkName)) { branchOrBookMarkName = repository.getCurrentBranch(); } - return branchText + branchOrBookMarkName; + return branchOrBookMarkName; } @NotNull diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 93aa37c999c8..2b1794d236a3 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -393,6 +393,9 @@ + + +