From 9cda48cdd16ed35d946828505fd0c5016e64fbe3 Mon Sep 17 00:00:00 2001 From: Ben Gibson Date: Tue, 26 Jan 2016 18:10:25 +0000 Subject: [PATCH] dev: inital version 1 commit --- .gitignore | 6 + CHANGELOG.md | 20 +++ CONTRIBUTING.md | 22 +++ META-INF/plugin.xml | 53 +++++++ README.md | 26 ++++ remote-repository-mapper.iml | 14 ++ resources/META-INF/plugin.xml | 42 +++++ .../repositorymapper/Context/Context.java | 90 +++++++++++ .../Context/ContextProvider.java | 108 +++++++++++++ .../Context/ContextProviderException.java | 47 ++++++ .../repositorymapper/OpenContextAction.java | 100 ++++++++++++ .../RepositoryProvider.java | 32 ++++ .../Settings/Configuration.java | 145 ++++++++++++++++++ .../repositorymapper/Settings/Settings.java | 91 +++++++++++ .../UrlFactory/GitHubUrlFactory.java | 40 +++++ .../UrlFactory/StashUrlFactory.java | 60 ++++++++ .../UrlFactory/UrlFactory.java | 23 +++ .../UrlFactory/UrlFactoryException.java | 33 ++++ .../UrlFactory/UrlFactoryProvider.java | 30 ++++ 19 files changed, 982 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 META-INF/plugin.xml create mode 100644 README.md create mode 100644 remote-repository-mapper.iml create mode 100644 resources/META-INF/plugin.xml create mode 100644 src/uk/co/ben_gibson/repositorymapper/Context/Context.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/Context/ContextProvider.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/Context/ContextProviderException.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/OpenContextAction.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/RepositoryProvider/RepositoryProvider.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/Settings/Configuration.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/Settings/Settings.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/UrlFactory/GitHubUrlFactory.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/UrlFactory/StashUrlFactory.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactory.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryException.java create mode 100644 src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryProvider.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48bb353 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +*.jar +*.class +out +.DS_Store +build/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..131bfe7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All Notable changes to command-bus will be documented in this file + +## NEXT - YYYY-MM-DD + +### Added +- Nothing + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- Nothing + +### Security +- Nothing \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3e1c7e6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/ben-gibson/remote-repository-mapper). + +## Pull Requests + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Create feature branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + + +## Running Tests + +**Happy coding**! \ No newline at end of file diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml new file mode 100644 index 0000000..ae08d3a --- /dev/null +++ b/META-INF/plugin.xml @@ -0,0 +1,53 @@ + + uk.co.ben-gibson.remote.repository.mapper + Remote Repository Mapper + 1.0 + https://github.com/ben-gibson/remote-repository-mapper + + Other Settings -> Remote Repository Mapper (Defaults to GitHub). + The current checked out branch is used unless it does not track a remote branch, in which case it defaults to using master. + + To use, open a file that is under git version control in the editor and select File->Open in remote repository. + + The resulting link can be copied to the clipboard depending on your preference in the settings. + ]]> + + +
  • Fixed clipboard preference not persisting
  • +
  • Updated default shortcut
  • +
  • Moved action to file menu
  • +
  • Action appears as disabled when unusable in the current context instead of being hidden
  • + + ]]> +
    + + + + + + + com.intellij.modules.vcs + com.intellij.modules.lang + com.intellij.modules.platform + Git4Idea + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc0a6fb --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Remote Repository Mapper + +A Jetbrains plugin that opens a local file in a remote repository. + +## Install + +## Usage + +After installing select your remote repository provider in Settings -> Other Settings -> Remote Repository Mapper (Defaults to GitHub). +Open a file that is under git version control in the editor and select File->Open in remote repository. +The current checked out branch is used unless it does not track a remote branch, in which case it defaults to using master. +The resulting link can be copied to the clipboard depending on your preference in the settings. + +## Change log + +Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Credits + +## License + +Please see [CONTRIBUTING](LICENSE) for details. \ No newline at end of file diff --git a/remote-repository-mapper.iml b/remote-repository-mapper.iml new file mode 100644 index 0000000..2d5c330 --- /dev/null +++ b/remote-repository-mapper.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml new file mode 100644 index 0000000..bdfef94 --- /dev/null +++ b/resources/META-INF/plugin.xml @@ -0,0 +1,42 @@ + + uk.co.ben-gibson.remote.repository.mapper + Remote Repository Mapper + 1.0 + https://github.com/ben-gibson/remote-repository-mapper + + + + + + + + + + + + com.intellij.modules.vcs + com.intellij.modules.lang + com.intellij.modules.platform + Git4Idea + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/uk/co/ben_gibson/repositorymapper/Context/Context.java b/src/uk/co/ben_gibson/repositorymapper/Context/Context.java new file mode 100644 index 0000000..56dddb2 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/Context/Context.java @@ -0,0 +1,90 @@ +package uk.co.ben_gibson.repositorymapper.Context; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.net.URL; + +/** + * Represents some context that can be opened in a remote repository. + */ +public class Context +{ + private static final String DEFAULT_BRANCH = "master"; + + @NotNull + private URL remoteHost; + @NotNull + private String path; + @NotNull + private String branch; + @Nullable + private Integer caretLinePosition; + + + /** + * Constructor. + * + * @param remoteHost The remote host. + * @param path The path of the file we want to view. + * @param branch The branch if we have one. + * @param caretLinePosition The line position of the caret. + */ + public Context( + @NotNull URL remoteHost, + @NotNull String path, + @Nullable String branch, + @Nullable Integer caretLinePosition + ) + { + this.remoteHost = remoteHost; + this.path = path; + this.branch = (branch != null) ? branch : DEFAULT_BRANCH; + this.caretLinePosition = caretLinePosition; + } + + + /** + * Get the path. + * + * @return String + */ + @NotNull + public String getPath() + { + return this.path; + } + + + /** + * Get the caret line position. + * + * @return Integer + */ + @Nullable + public Integer getCaretLinePosition() + { + return this.caretLinePosition; + } + + + /** + * Get the remote host. + * + * @return URL + */ + @NotNull + public URL getRemoteHost() { + return remoteHost; + } + + + /** + * Get the branch. + * + * @return String + */ + @NotNull + public String getBranch() { + return this.branch; + } +} \ No newline at end of file diff --git a/src/uk/co/ben_gibson/repositorymapper/Context/ContextProvider.java b/src/uk/co/ben_gibson/repositorymapper/Context/ContextProvider.java new file mode 100644 index 0000000..05479eb --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/Context/ContextProvider.java @@ -0,0 +1,108 @@ +package uk.co.ben_gibson.repositorymapper.Context; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import git4idea.GitLocalBranch; +import git4idea.GitUtil; +import git4idea.repo.GitRemote; +import git4idea.repo.GitRepository; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Context Provider. + */ +public class ContextProvider +{ + + /** + * Provides a context based on the current environment. + * + * @param project The active project. + * + * @return Context + */ + @Nullable + public Context getContext(@NotNull Project project) throws MalformedURLException, ContextProviderException { + + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + + if (editor == null) { + return null; + } + + VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument()); + + if (file == null) { + return null; + } + + GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForFile(file); + + if (repository == null) { + return null; + } + + URL remoteHost = this.getRemoteHostFromRepository(repository); + + GitLocalBranch branch = null; + + if (repository.getCurrentBranch() != null && repository.getCurrentBranch().findTrackedBranch(repository) != null) { + branch = repository.getCurrentBranch(); + } + + String path = file.getPath().substring(repository.getRoot().getPath().length()); + String branchName = branch != null ? branch.getName() : null; + + Integer caretPosition = editor.getCaretModel().getLogicalPosition().line + 1; + + return new Context(remoteHost, path, branchName, caretPosition); + } + + + /** + * Get a clean url from the repositories remote origin. + * + * @return URL + */ + @NotNull + private URL getRemoteHostFromRepository(@NotNull GitRepository repository) throws MalformedURLException, ContextProviderException + { + GitRemote origin = null; + + for (GitRemote remote : repository.getRemotes()) { + if (remote.getName().equals("origin")) { + origin = remote; + } + } + + if (origin == null) { + throw ContextProviderException.originRemoteNotFound(repository); + } + + if (origin.getFirstUrl() == null) { + throw ContextProviderException.urlNotFoundForRemote(origin); + } + + String url = StringUtil.trimEnd(origin.getFirstUrl(), ".git"); + + url = url.replaceAll(":\\d{1,4}", ""); // remove port + + if (url.startsWith("http")) { + return new URL(url); + } + + url = StringUtil.replace(url, "git@", ""); + url = StringUtil.replace(url, "ssh://", ""); + + url = "https://" + StringUtil.replace(url, ":", "/"); + + return new URL(url); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/Context/ContextProviderException.java b/src/uk/co/ben_gibson/repositorymapper/Context/ContextProviderException.java new file mode 100644 index 0000000..70c7d82 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/Context/ContextProviderException.java @@ -0,0 +1,47 @@ +package uk.co.ben_gibson.repositorymapper.Context; + +import git4idea.repo.GitRemote; +import git4idea.repo.GitRepository; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when when a context cannot be provided. + */ +public class ContextProviderException extends Exception +{ + + /** + * Constructor. + * + * @param message The exception message. + */ + public ContextProviderException(String message) { + super(message); + } + + + /** + * Origin remote not found for repository. + * + * @param repository The repository that has no origin remote. + * + * @return ContextProviderException + */ + public static ContextProviderException originRemoteNotFound(@NotNull GitRepository repository) + { + return new ContextProviderException("The origin remote was not found for repository at path " + repository.getRoot().getPath()); + } + + + /** + * No url found on remote. + * + * @param remote The remote with no URL. + * + * @return ContextProviderException + */ + public static ContextProviderException urlNotFoundForRemote(@NotNull GitRemote remote) + { + return new ContextProviderException("URL not found on remote " + remote.getName()); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/OpenContextAction.java b/src/uk/co/ben_gibson/repositorymapper/OpenContextAction.java new file mode 100644 index 0000000..aca8aaf --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/OpenContextAction.java @@ -0,0 +1,100 @@ +package uk.co.ben_gibson.repositorymapper; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.Context.Context; +import uk.co.ben_gibson.repositorymapper.Context.ContextProvider; +import uk.co.ben_gibson.repositorymapper.Context.ContextProviderException; +import uk.co.ben_gibson.repositorymapper.Settings.Settings; +import com.intellij.ide.browsers.BrowserLauncher; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import uk.co.ben_gibson.repositorymapper.UrlFactory.UrlFactoryException; +import uk.co.ben_gibson.repositorymapper.UrlFactory.UrlFactoryProvider; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * Opens the current context in a remote repository. + */ +public class OpenContextAction extends AnAction +{ + + /** + * Open the current context. + * + * @param event The event. + */ + public void actionPerformed(AnActionEvent event) + { + Project project = event.getProject(); + + if (project == null) { + return; + } + + Settings settings = ServiceManager.getService(project, Settings.class); + + try { + + Context context = this.getContextProvider().getContext(project); + + if (context == null) { + return; + } + + UrlFactoryProvider urlFactoryProvider = ServiceManager.getService(UrlFactoryProvider.class); + + URL url = urlFactoryProvider.getUrlFactoryForProvider(settings.getRepositoryProvider()).getUrlFromContext(context); + + if (settings.getCopyToClipboard()) { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(url.toString()), null); + } + + BrowserLauncher.getInstance().browse(url.toURI()); + + } catch (MalformedURLException | URISyntaxException | ContextProviderException | UrlFactoryException e) { + Messages.showErrorDialog(event.getProject(), e.getMessage(), "Error"); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void update(AnActionEvent event) + { + Context context = null; + + if (event.getProject() != null) { + try { + context = this.getContextProvider().getContext(event.getProject()); + } catch (MalformedURLException | ContextProviderException e) { + Logger.getInstance(OpenContextAction.class).info(e); + } + } + + event.getPresentation().setEnabled((context != null)); + } + + + /** + * Get the context factory. + * + * @return ContextProvider + */ + @NotNull + private ContextProvider getContextProvider() + { + return ServiceManager.getService(ContextProvider.class); + } +} \ No newline at end of file diff --git a/src/uk/co/ben_gibson/repositorymapper/RepositoryProvider/RepositoryProvider.java b/src/uk/co/ben_gibson/repositorymapper/RepositoryProvider/RepositoryProvider.java new file mode 100644 index 0000000..99b1f3b --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/RepositoryProvider/RepositoryProvider.java @@ -0,0 +1,32 @@ +package uk.co.ben_gibson.repositorymapper.RepositoryProvider; + +/** + * Represents different remote repository providers that we support. + */ +public enum RepositoryProvider +{ + STASH("Stash"), + GIT_HUB("GitHub"); + + private final String name; + + + /** + * Constructor. + * + * @param name The name. + */ + RepositoryProvider(String name) + { + this.name = name; + } + + + /** + * {@inheritDoc} + */ + public String toString() + { + return this.name; + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/Settings/Configuration.java b/src/uk/co/ben_gibson/repositorymapper/Settings/Configuration.java new file mode 100644 index 0000000..0ba9f23 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/Settings/Configuration.java @@ -0,0 +1,145 @@ +package uk.co.ben_gibson.repositorymapper.Settings; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.util.Comparing; +import com.intellij.ui.EnumComboBoxModel; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBLabel; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import uk.co.ben_gibson.repositorymapper.RepositoryProvider.RepositoryProvider; +import javax.swing.*; + +/** + * Configuration used in the settings panel. + */ +public class Configuration implements Configurable +{ + + private static final String LABEL_COPY_TO_CLIPBOARD = "Copy to clipboard"; + private static final String LABEL_PROVIDERS = "Providers"; + + private JBCheckBox copyToClipboardCheckBox; + private ComboBox providerComboBox; + + private Settings settings; + + + /** + * Constructor. + * + * @param project The project. + */ + public Configuration(Project project) + { + this.settings = ServiceManager.getService(project, Settings.class); + + this.copyToClipboardCheckBox = new JBCheckBox(LABEL_COPY_TO_CLIPBOARD); + this.providerComboBox = new ComboBox(new EnumComboBoxModel<>(RepositoryProvider.class), 200); + } + + + /** + * Creates the panel component that is rendered in the setting dialog. + * + * @return JPanel + */ + public JComponent createComponent() + { + this.reset(); + + JPanel panel = new JPanel(); + + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + JBLabel label = new JBLabel(LABEL_PROVIDERS); + label.setMaximumSize(label.getPreferredSize()); + + this.providerComboBox.setMaximumSize(this.providerComboBox.getPreferredSize()); + this.providerComboBox.setAlignmentX(0.0f); + + this.copyToClipboardCheckBox.setMaximumSize(this.copyToClipboardCheckBox.getPreferredSize()); + + panel.add(label); + panel.add(this.providerComboBox); + + JPanel spacing = new JPanel(); + spacing.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + spacing.setMaximumSize(spacing.getPreferredSize()); + + panel.add(spacing); + + panel.add(this.copyToClipboardCheckBox); + + return panel; + } + + + /** + * {@inheritDoc} + * + * This determines if the 'apply' button should be disabled. + */ + public boolean isModified() + { + return !Comparing.equal(this.copyToClipboardCheckBox.isSelected(), this.settings.getCopyToClipboard()) || + this.providerComboBox.getSelectedItem() != this.settings.getRepositoryProvider(); + } + + + /** + * {@inheritDoc} + * + * Saves the changes. + */ + public void apply() throws ConfigurationException + { + this.settings.setCopyToClipboard(this.copyToClipboardCheckBox.isSelected()); + this.settings.setRepositoryProvider((RepositoryProvider) this.providerComboBox.getSelectedItem()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void reset() + { + this.copyToClipboardCheckBox.setSelected(this.settings.getCopyToClipboard()); + this.providerComboBox.setSelectedItem(this.settings.getRepositoryProvider()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void disposeUIResources() + { + this.copyToClipboardCheckBox = null; + this.providerComboBox = null; + } + + + /** + * {@inheritDoc} + */ + @Override + public String getHelpTopic() + { + return null; + } + + + /** + * {@inheritDoc} + */ + @Override + public String getDisplayName() + { + return "Remote Repository Mapper"; + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/Settings/Settings.java b/src/uk/co/ben_gibson/repositorymapper/Settings/Settings.java new file mode 100644 index 0000000..7ddc5bc --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/Settings/Settings.java @@ -0,0 +1,91 @@ +package uk.co.ben_gibson.repositorymapper.Settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.components.StoragePathMacros; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.RepositoryProvider.RepositoryProvider; + +@State(name = "SaveActionSettings", + storages = {@Storage(id = "default", file = StoragePathMacros.PROJECT_CONFIG_DIR + "/settings.xml")} +) + +/** + * Persistent settings. + */ +public class Settings implements PersistentStateComponent +{ + @NotNull + private Boolean copyToClipboard = false; + + @NotNull + private RepositoryProvider repositoryProvider = RepositoryProvider.GIT_HUB; + + + /** + * {@inheritDoc} + * + * Self maintaining state. + */ + public Settings getState() + { + return this; + } + + + /** + * Should the results be copied to the clipboard. + * + * @return Boolean + */ + @NotNull + public Boolean getCopyToClipboard() + { + return this.copyToClipboard; + } + + + /** + * Set copy to clip board preference. + * + * getCopyToClipboard Should the results be copied to the clipboard? + */ + public void setCopyToClipboard(@NotNull Boolean copyToClipboard) + { + this.copyToClipboard = copyToClipboard; + } + + + /** + * Get the repository provider. + * + * @return RepositoryProvider + */ + @NotNull + public RepositoryProvider getRepositoryProvider() + { + return repositoryProvider; + } + + + /** + * Set the repository provider. + * + * @param repositoryProvider The repository provider. + */ + public void setRepositoryProvider(@NotNull RepositoryProvider repositoryProvider) + { + this.repositoryProvider = repositoryProvider; + } + + + /** + * {@inheritDoc} + */ + public void loadState(Settings state) + { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/UrlFactory/GitHubUrlFactory.java b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/GitHubUrlFactory.java new file mode 100644 index 0000000..d0f8916 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/GitHubUrlFactory.java @@ -0,0 +1,40 @@ +package uk.co.ben_gibson.repositorymapper.UrlFactory; + +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.Context.Context; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +/** + * Creates a URL in the format expected by the remote repository provider GitHub. + */ +public class GitHubUrlFactory implements UrlFactory +{ + + /** + * {@inheritDoc} + */ + @Override + @NotNull + public URL getUrlFromContext(@NotNull Context context) throws MalformedURLException, UrlFactoryException + { + + String branch; + + try { + branch = URLEncoder.encode(context.getBranch(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new UrlFactoryException("Failed to encode path, unsupported encoding"); + } + + String path = context.getRemoteHost().toString() + "/blob/" + branch + context.getPath(); + + if (context.getCaretLinePosition() != null) { + path += "#L" + context.getCaretLinePosition().toString(); + } + + return new URL(path); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/UrlFactory/StashUrlFactory.java b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/StashUrlFactory.java new file mode 100644 index 0000000..14c8814 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/StashUrlFactory.java @@ -0,0 +1,60 @@ +package uk.co.ben_gibson.repositorymapper.UrlFactory; + +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.Context.Context; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +/** + * Creates a URL in the format expected by the remote repository provider Stash. + */ +public class StashUrlFactory implements UrlFactory +{ + + /** + * {@inheritDoc} + */ + @Override + @NotNull + public URL getUrlFromContext(@NotNull Context context) throws MalformedURLException, UrlFactoryException { + + URL remoteHost = context.getRemoteHost(); + + String[] parts = remoteHost.getPath().split("/", 3); + + if (parts.length < 3) { + throw new MalformedURLException("Could not find project and repo from path " + context.getRemoteHost().getPath()); + } + + /** + * If we find more providers need this level of flexibility we could split host, project and repo + * within Context but for now this will do. + */ + String project = parts[1]; + String repository = parts[2]; + + String fullPath = String.format( + "/projects/%s/repos/%s/browse%s", + project, + repository, + context.getPath() + ); + + try { + fullPath += "?at=" + URLEncoder.encode("refs/heads/" + context.getBranch(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new UrlFactoryException("Failed to encode path, unsupported encoding"); + } + + if (context.getCaretLinePosition() != null) { + fullPath += "#" + context.getCaretLinePosition().toString(); + } + + fullPath = remoteHost.getProtocol() + "://" + remoteHost.getHost() + fullPath; + + return new URL(fullPath); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactory.java b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactory.java new file mode 100644 index 0000000..7042fd8 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactory.java @@ -0,0 +1,23 @@ +package uk.co.ben_gibson.repositorymapper.UrlFactory; + +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.Context.Context; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * An interface for remote repository Url factories. + */ +public interface UrlFactory +{ + + /** + * Get a remote repository Url from a context. + * + * @param context The context to create a URL from. + * + * @return Url + */ + @NotNull + URL getUrlFromContext(@NotNull Context context) throws MalformedURLException, UrlFactoryException; +} diff --git a/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryException.java b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryException.java new file mode 100644 index 0000000..dbf9369 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryException.java @@ -0,0 +1,33 @@ +package uk.co.ben_gibson.repositorymapper.UrlFactory; + +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.RepositoryProvider.RepositoryProvider; + +/** + * URL factory exception. + */ +public class UrlFactoryException extends Exception +{ + + /** + * Constructor. + * + * @param message The exception message. + */ + public UrlFactoryException(String message) { + super(message); + } + + + /** + * Unsupported remote repository provider. + * + * @param provider The unsupported provider. + * + * @return UrlFactoryException + */ + public static UrlFactoryException unsupportedProvider(@NotNull RepositoryProvider provider) + { + return new UrlFactoryException("Unsupported remote repository provider " + provider.toString()); + } +} diff --git a/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryProvider.java b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryProvider.java new file mode 100644 index 0000000..4db2cd6 --- /dev/null +++ b/src/uk/co/ben_gibson/repositorymapper/UrlFactory/UrlFactoryProvider.java @@ -0,0 +1,30 @@ +package uk.co.ben_gibson.repositorymapper.UrlFactory; + +import org.jetbrains.annotations.NotNull; +import uk.co.ben_gibson.repositorymapper.RepositoryProvider.RepositoryProvider; + +/** + * Provides URL factories for remote repository providers. + */ +public class UrlFactoryProvider +{ + + /** + * Get a url factory for a remote repository provider. + * + * @param provider The provider we want a Url factory for. + * + * @return UrlFactory + */ + @NotNull + public UrlFactory getUrlFactoryForProvider(RepositoryProvider provider) throws UrlFactoryException + { + if (provider == RepositoryProvider.GIT_HUB) { + return new GitHubUrlFactory(); + } else if (provider == RepositoryProvider.STASH) { + return new StashUrlFactory(); + } + + throw UrlFactoryException.unsupportedProvider(provider); + } +}