From f6830be31bc4d8fee9321322972bbe84782d8b09 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 11:36:55 +0200 Subject: [PATCH 01/76] Add classes to simplify the API for creating a Mastodon menu entry. --- .../utils/ActionDescriptions.java | 94 ++++++++++++++ .../utils/BasicDescriptionProvider.java | 57 +++++++++ .../collaboration/utils/BasicMamutPlugin.java | 117 ++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java new file mode 100644 index 00000000..a43ccab8 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java @@ -0,0 +1,94 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * This class contains a list of details about actions that a plugin can + * perform. It should be used together with {@link BasicMamutPlugin} and + * {@link BasicDescriptionProvider}. + */ +public class ActionDescriptions< T > +{ + + private final Class< T > pluginClass; + + private final List< Entry< ? > > entries = new ArrayList<>(); + + public ActionDescriptions( Class< T > pluginClass ) + { + this.pluginClass = pluginClass; + } + + public final ActionDescriptions< T > addActionDescription( String key, String menuText, String description, Consumer< T > action ) + { + return addActionDescription( key, menuText, description, action, "not mapped" ); + } + + public final ActionDescriptions< T > addActionDescription( String key, String menuText, String description, Consumer< T > action, String... keyStrokes ) + { + entries.add( new Entry<>( key, menuText, keyStrokes, description, action ) ); + return this; + } + + public Class< T > getPluginClass() + { + return pluginClass; + } + + public List< Entry< ? > > getEntries() + { + return entries; + } + + public static class Entry< T > + { + public final String key; + + public final String menuEntry; + + public final String[] shortCuts; + + public final String description; + + public final Consumer< T > action; + + public Entry( String key, String menuEntry, String[] shortCuts, String description, Consumer< T > action ) + { + this.key = key; + this.menuEntry = menuEntry; + this.shortCuts = shortCuts; + this.description = description; + this.action = action; + } + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java new file mode 100644 index 00000000..04ca24a6 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java @@ -0,0 +1,57 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration.utils; + +import org.mastodon.ui.keymap.CommandDescriptionProvider; +import org.mastodon.ui.keymap.CommandDescriptions; + +/** + * This class simplifies the task of implementing a {@link CommandDescriptionProvider}. + *

+ * It provides the description of the actions listed in the {@link ActionDescriptions} object. + */ +public abstract class BasicDescriptionProvider extends CommandDescriptionProvider +{ + + private final ActionDescriptions< ? > actionDescriptions; + + public BasicDescriptionProvider( ActionDescriptions< ? > actionDescriptions, String... contexts ) + { + super( contexts ); + this.actionDescriptions = actionDescriptions; + } + + @Override + public void getCommandDescriptions( CommandDescriptions descriptions ) + { + for ( ActionDescriptions.Entry entry : actionDescriptions.getEntries() ) + descriptions.add( entry.key, entry.shortCuts, entry.description ); + } + +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java new file mode 100644 index 00000000..55187a1e --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -0,0 +1,117 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration.utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.imglib2.util.Cast; + +import org.mastodon.app.ui.ViewMenuBuilder; +import org.mastodon.mamut.MamutAppModel; +import org.mastodon.mamut.plugin.MamutPlugin; +import org.mastodon.mamut.plugin.MamutPluginAppModel; +import org.scijava.ui.behaviour.util.AbstractNamedAction; +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.RunnableAction; + +/** + * A class that simplifies the creation of a {@link MamutPlugin}. + * See {@link org.mastodon.mamut.tomancak.collaboration.MastodonGitPlugins} for + * usage example. + */ +public class BasicMamutPlugin implements MamutPlugin +{ + + private final List< ActionDescriptions.Entry< ? > > actionDescriptions; + + private final Map< String, String > menuTexts = new HashMap<>(); + + private final Map< String, AbstractNamedAction > actions = new HashMap<>(); + + private final List< ViewMenuBuilder.MenuItem > menuItems = new ArrayList<>(); + + public < T > BasicMamutPlugin( ActionDescriptions< T > description ) + { + if ( !this.getClass().equals( description.getPluginClass() ) ) + throw new IllegalArgumentException( "Plugin class mismatch." ); + actionDescriptions = description.getEntries(); + for ( ActionDescriptions.Entry< ? > entry : actionDescriptions ) + { + menuTexts.put( entry.key, extractMenuText( entry.menuEntry ) ); + menuItems.add( initMenuItem( entry.key, entry.menuEntry ) ); + actions.put( entry.key, new RunnableAction( entry.key, () -> entry.action.accept( Cast.unchecked( this ) ) ) ); + } + } + + private String extractMenuText( String menuEntry ) + { + // From menuEntry, extract the last part, which is the menu item name. + final String[] parts = menuEntry.split( ">" ); + return parts[ parts.length - 1 ].trim(); + } + + private ViewMenuBuilder.MenuItem initMenuItem( String key, String menuEntry ) + { + final String[] parts = menuEntry.split( ">" ); + ViewMenuBuilder.MenuItem item = ViewMenuBuilder.item( key ); + for ( int i = parts.length - 2; i >= 0; i-- ) + item = ViewMenuBuilder.menu( parts[ i ].trim(), item ); + return item; + } + + @Override + public void setAppPluginModel( MamutPluginAppModel appPluginModel ) + { + MamutAppModel appModel = appPluginModel.getAppModel(); + actions.forEach( ( key, action ) -> action.setEnabled( appModel != null ) ); + } + + @Override + public Map< String, String > getMenuTexts() + { + return menuTexts; + } + + @Override + public List< ViewMenuBuilder.MenuItem > getMenuItems() + { + return menuItems; + } + + @Override + public void installGlobalActions( Actions pluginActions ) + { + for ( ActionDescriptions.Entry< ? > entry : actionDescriptions ) + pluginActions.namedAction( actions.get( entry.key ), entry.shortCuts ); + } + +} From 1bbf6cc941696f1c542cca7aa30af5c988197a75 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 11:38:02 +0200 Subject: [PATCH 02/76] Add plugin for uploading a Mastodon project to github / gitlab --- pom.xml | 11 +++ .../MastodonGitCreateRepository.java | 56 +++++++++++++ .../collaboration/MastodonGitPlugins.java | 80 ++++++++++++++++++ .../collaboration/MastodonGitUtils.java | 82 +++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java diff --git a/pom.xml b/pom.xml index 4a135e36..a650d006 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,17 @@ mpicbg + + org.eclipse.jgit + org.eclipse.jgit + 6.7.0.202309050840-r + + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache + 6.7.0.202309050840-r + junit diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java new file mode 100644 index 00000000..cec6786f --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java @@ -0,0 +1,56 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration; + +import java.io.File; + +import org.mastodon.mamut.WindowManager; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; + +public class MastodonGitCreateRepository implements Command +{ + @Parameter + WindowManager windowManager; + + @Parameter( label = "Directory to contain the repository", style = "directory" ) + File parentDirectory; + + @Parameter( label = "Repository name" ) + String repositoryName; + + @Parameter( label = "URL on github or gitlab" ) + String repositoryURL; + + @Override + public void run() + { + MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java new file mode 100644 index 00000000..88526f84 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -0,0 +1,80 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration; + +import org.mastodon.mamut.plugin.MamutPlugin; +import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; +import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; +import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; +import org.mastodon.ui.keymap.CommandDescriptionProvider; +import org.mastodon.ui.keymap.KeyConfigContexts; +import org.scijava.command.CommandService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +@Plugin( type = MamutPlugin.class ) +public class MastodonGitPlugins extends BasicMamutPlugin +{ + @Parameter + private CommandService commandService; + + public static final ActionDescriptions< MastodonGitPlugins > actionDescriptions = new ActionDescriptions<>( MastodonGitPlugins.class ) + .addActionDescription( "[mastodon git] create repository", + "Plugins > Git > New git repository", + "Upload Mastodon project to a newly created git repository.", + MastodonGitPlugins::createRepository ) + .addActionDescription( "[mastodon git] clone repository", + "Plugins > Git > Clone git repository", + "Clone a git repository to a new Mastodon project.", + MastodonGitPlugins::cloneGitRepository ); + + public MastodonGitPlugins() + { + super( actionDescriptions ); + } + + private void createRepository() + { + commandService.run( MastodonGitCreateRepository.class, true ); + } + + private void cloneGitRepository() + { + System.out.println( "git clone" ); + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class DescriptionProvider extends BasicDescriptionProvider + { + public DescriptionProvider() + { + super( actionDescriptions, KeyConfigContexts.MASTODON, KeyConfigContexts.TRACKSCHEME ); + } + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java new file mode 100644 index 00000000..f2372007 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -0,0 +1,82 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.URIish; +import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.project.MamutProjectIO; +import org.scijava.Context; + +import mpicbg.spim.data.SpimDataException; + +public class MastodonGitUtils +{ + + public static void main( String... args ) throws IOException, SpimDataException + { + String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; + Context context = new Context(); + WindowManager windowManager = new WindowManager( context ); + windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) ); + File parentDirectory = new File( "/home/arzt/tmp/" ); + String repositoryName = "mgit-test"; + String repositoryURL = "git@github.com:maarzt/mgit-test.git"; + MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + } + + public static void createRepositoryAndUpload( WindowManager windowManager, File parentDirectory, String repositoryName, String repositoryURL ) + { + try + { + Path gitRepositoryPath = parentDirectory.toPath().resolve( repositoryName ); + Files.createDirectories( gitRepositoryPath ); + Git git = Git.init().setDirectory( gitRepositoryPath.toFile() ).call(); + Path mastodonProjectPath = gitRepositoryPath.resolve( "mastodon.project" ); + Files.createDirectory( mastodonProjectPath ); + windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); + git.add().addFilepattern( "mastodon.project" ).call(); + git.commit().setMessage( "Initial commit" ).call(); + git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); + git.push().setRemote( "origin" ).call(); + git.close(); + } + catch ( GitAPIException | IOException | URISyntaxException e ) + { + throw new RuntimeException( e ); + } + } +} From 08dc647f3cd6580274d45658e28b9a2810640f19 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 12:27:20 +0200 Subject: [PATCH 03/76] Add basic command to clone repository and open dataset --- .../MastodonGitCloneRepository.java | 41 +++++++++++++++++ .../collaboration/MastodonGitPlugins.java | 2 +- .../collaboration/MastodonGitUtils.java | 44 ++++++++++++++++--- 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java new file mode 100644 index 00000000..0c44256d --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java @@ -0,0 +1,41 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import java.io.File; + +import org.scijava.Context; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; + +// TODOs: +// - warn if parentDirectory already exists +// - warn if repositoryName already exists and the corresponding directory is not empty +// - fill repositoryName with a default value based on the repositoryURL +public class MastodonGitCloneRepository implements Command +{ + @Parameter + Context context; + + @Parameter( label = "URL on github or gitlab" ) + String repositoryURL; + + @Parameter( label = "Directory that will contain the cloned repository", style = "directory" ) + File parentDirectory; + + @Parameter( label = "Repository name" ) + String repositoryName; + + @Override + public void run() + { + try + { + File directory = new File( this.parentDirectory, repositoryName ); + MastodonGitUtils.cloneRepository( repositoryURL, directory ); + MastodonGitUtils.openProjectInRepository( context, directory ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 88526f84..0abcb7a8 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -66,7 +66,7 @@ private void createRepository() private void cloneGitRepository() { - System.out.println( "git clone" ); + commandService.run( MastodonGitCloneRepository.class, true ); } @Plugin( type = CommandDescriptionProvider.class ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index f2372007..3e2d4803 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.IOException; +import java.net.ConnectException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; @@ -37,6 +38,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.transport.URIish; +import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.project.MamutProjectIO; import org.scijava.Context; @@ -49,13 +51,18 @@ public class MastodonGitUtils public static void main( String... args ) throws IOException, SpimDataException { String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; - Context context = new Context(); - WindowManager windowManager = new WindowManager( context ); - windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) ); - File parentDirectory = new File( "/home/arzt/tmp/" ); String repositoryName = "mgit-test"; String repositoryURL = "git@github.com:maarzt/mgit-test.git"; - MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + File parentDirectory = new File( "/home/arzt/tmp/" ); + +// Context context = new Context(); +// WindowManager windowManager = new WindowManager( context ); +// windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) ); +// MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + +// MastodonGitUtils.cloneRepository( repositoryURL, new File( parentDirectory, "2/" ) ); + + MastodonGitUtils.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); } public static void createRepositoryAndUpload( WindowManager windowManager, File parentDirectory, String repositoryName, String repositoryURL ) @@ -79,4 +86,31 @@ public static void createRepositoryAndUpload( WindowManager windowManager, File throw new RuntimeException( e ); } } + + public static void cloneRepository( String repositoryURL, File parentDirectory ) + { + try + { + Git.cloneRepository().setURI( repositoryURL ).setDirectory( parentDirectory ).call(); + } + catch ( GitAPIException e ) + { + throw new RuntimeException( e ); + } + } + + public static void openProjectInRepository( Context context, File directory ) + { + try + { + WindowManager windowManager = new WindowManager( context ); + Path path = directory.toPath().resolve( "mastodon.project" ); + windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( path.toAbsolutePath().toString() ) ); + new MainWindow( windowManager ).setVisible( true ); + } + catch ( SpimDataException | IOException e ) + { + throw new RuntimeException( e ); + } + } } From b580eb6436d5992cd00463d43630324e0817515b Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 12:55:28 +0200 Subject: [PATCH 04/76] BasicMamutPlugin: make WindowManager and MamutAppModel accessible --- .../collaboration/utils/BasicMamutPlugin.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java index 55187a1e..5913ee27 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -37,6 +37,7 @@ import org.mastodon.app.ui.ViewMenuBuilder; import org.mastodon.mamut.MamutAppModel; +import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.plugin.MamutPluginAppModel; import org.scijava.ui.behaviour.util.AbstractNamedAction; @@ -59,6 +60,10 @@ public class BasicMamutPlugin implements MamutPlugin private final List< ViewMenuBuilder.MenuItem > menuItems = new ArrayList<>(); + private MamutAppModel appModel; + + private WindowManager windowManager; + public < T > BasicMamutPlugin( ActionDescriptions< T > description ) { if ( !this.getClass().equals( description.getPluginClass() ) ) @@ -91,7 +96,8 @@ private ViewMenuBuilder.MenuItem initMenuItem( String key, String menuEntry ) @Override public void setAppPluginModel( MamutPluginAppModel appPluginModel ) { - MamutAppModel appModel = appPluginModel.getAppModel(); + appModel = appPluginModel.getAppModel(); + windowManager = appPluginModel.getWindowManager(); actions.forEach( ( key, action ) -> action.setEnabled( appModel != null ) ); } @@ -114,4 +120,13 @@ public void installGlobalActions( Actions pluginActions ) pluginActions.namedAction( actions.get( entry.key ), entry.shortCuts ); } + protected MamutAppModel getAppModel() + { + return appModel; + } + + protected WindowManager getWindowManager() + { + return windowManager; + } } From 6d1a3772306eb85e389517722a5a8c9efdd3216a Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 12:56:03 +0200 Subject: [PATCH 05/76] Add git commit plugin --- .../collaboration/MastodonGitCommit.java | 20 ++++++++++++++++++ .../collaboration/MastodonGitPlugins.java | 17 +++++++++++---- .../collaboration/MastodonGitUtils.java | 21 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java new file mode 100644 index 00000000..887c61d2 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java @@ -0,0 +1,20 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import org.mastodon.mamut.WindowManager; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; + +// TODOs: +// - allow to ignore project.xml and gui.xml +// - allow to provide a commit message +public class MastodonGitCommit implements Command +{ + @Parameter + WindowManager windowManager; + + @Override + public void run() + { + MastodonGitUtils.commit( windowManager ); + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 0abcb7a8..89990755 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -46,13 +46,17 @@ public class MastodonGitPlugins extends BasicMamutPlugin public static final ActionDescriptions< MastodonGitPlugins > actionDescriptions = new ActionDescriptions<>( MastodonGitPlugins.class ) .addActionDescription( "[mastodon git] create repository", - "Plugins > Git > New git repository", + "Plugins > Git > Create Repository", "Upload Mastodon project to a newly created git repository.", MastodonGitPlugins::createRepository ) .addActionDescription( "[mastodon git] clone repository", - "Plugins > Git > Clone git repository", + "Plugins > Git > Clone Repository", "Clone a git repository to a new Mastodon project.", - MastodonGitPlugins::cloneGitRepository ); + MastodonGitPlugins::cloneGitRepository ) + .addActionDescription( "[mastodon git] commit", + "Plugins > Git > Commit", + "Commit changes to the git repository.", + MastodonGitPlugins::commit ); public MastodonGitPlugins() { @@ -61,7 +65,7 @@ public MastodonGitPlugins() private void createRepository() { - commandService.run( MastodonGitCreateRepository.class, true ); + commandService.run( MastodonGitCreateRepository.class, true, "windowManager", getWindowManager() ); } private void cloneGitRepository() @@ -69,6 +73,11 @@ private void cloneGitRepository() commandService.run( MastodonGitCloneRepository.class, true ); } + private void commit() + { + commandService.run( MastodonGitCommit.class, true, "windowManager", getWindowManager() ); + } + @Plugin( type = CommandDescriptionProvider.class ) public static class DescriptionProvider extends BasicDescriptionProvider { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 3e2d4803..b3322043 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -113,4 +113,25 @@ public static void openProjectInRepository( Context context, File directory ) throw new RuntimeException( e ); } } + + public static void commit( WindowManager windowManager ) + { + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); + if ( !correctFolder ) + throw new RuntimeException( "The current project does not appear to be in a git repo." ); + File gitRoot = projectRoot.getParentFile(); + if ( !new File( gitRoot, ".git" ).exists() ) + throw new RuntimeException( "The current project does not appear to be in a git repo." ); + windowManager.getProjectManager().saveProject(); + try (Git git = Git.open( gitRoot )) + { + git.add().addFilepattern( "mastodon.project" ).call(); + git.commit().setMessage( "Commit from Mastodon" ).call(); + } + catch ( IOException | GitAPIException e ) + { + throw new RuntimeException( e ); + } + } } From 4948ad90aab4aa8bccf31905121f6176e7439c59 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 14:05:42 +0200 Subject: [PATCH 06/76] Mastodon Git: move SciJava commands to a separate package --- .../mamut/tomancak/collaboration/MastodonGitPlugins.java | 3 +++ .../{ => commands}/MastodonGitCloneRepository.java | 3 ++- .../collaboration/{ => commands}/MastodonGitCommit.java | 3 ++- .../{ => commands}/MastodonGitCreateRepository.java | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{ => commands}/MastodonGitCloneRepository.java (88%) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{ => commands}/MastodonGitCommit.java (75%) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{ => commands}/MastodonGitCreateRepository.java (94%) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 89990755..768193cf 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -29,6 +29,9 @@ package org.mastodon.mamut.tomancak.collaboration; import org.mastodon.mamut.plugin.MamutPlugin; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCommit; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java similarity index 88% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 0c44256d..2799e8a4 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -1,7 +1,8 @@ -package org.mastodon.mamut.tomancak.collaboration; +package org.mastodon.mamut.tomancak.collaboration.commands; import java.io.File; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; import org.scijava.Context; import org.scijava.command.Command; import org.scijava.plugin.Parameter; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java similarity index 75% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java index 887c61d2..2fcd02c6 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCommit.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java @@ -1,6 +1,7 @@ -package org.mastodon.mamut.tomancak.collaboration; +package org.mastodon.mamut.tomancak.collaboration.commands; import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; import org.scijava.command.Command; import org.scijava.plugin.Parameter; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java similarity index 94% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index cec6786f..615609ba 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -26,11 +26,12 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.mastodon.mamut.tomancak.collaboration; +package org.mastodon.mamut.tomancak.collaboration.commands; import java.io.File; import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; import org.scijava.command.Command; import org.scijava.plugin.Parameter; From 39cf7891fe51d1c7a3f9af1fa4530b50bb2eabb8 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 14:25:39 +0200 Subject: [PATCH 07/76] MastodonGit: remove unnecessary MastodonGitCommit class --- .../collaboration/MastodonGitPlugins.java | 9 ++++++-- .../collaboration/MastodonGitUtils.java | 23 +++++++++++-------- .../commands/MastodonGitCommit.java | 21 ----------------- 3 files changed, 21 insertions(+), 32 deletions(-) delete mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 768193cf..cd7f4d40 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -28,9 +28,9 @@ */ package org.mastodon.mamut.tomancak.collaboration; +import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; -import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCommit; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; @@ -78,7 +78,12 @@ private void cloneGitRepository() private void commit() { - commandService.run( MastodonGitCommit.class, true, "windowManager", getWindowManager() ); + run( () -> MastodonGitUtils.commit( getWindowManager() ) ); + } + + private void run( Runnable action ) + { + new Thread( action ).start(); } @Plugin( type = CommandDescriptionProvider.class ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index b3322043..d37a8232 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -116,15 +116,7 @@ public static void openProjectInRepository( Context context, File directory ) public static void commit( WindowManager windowManager ) { - File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); - boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); - if ( !correctFolder ) - throw new RuntimeException( "The current project does not appear to be in a git repo." ); - File gitRoot = projectRoot.getParentFile(); - if ( !new File( gitRoot, ".git" ).exists() ) - throw new RuntimeException( "The current project does not appear to be in a git repo." ); - windowManager.getProjectManager().saveProject(); - try (Git git = Git.open( gitRoot )) + try (Git git = initGit( windowManager )) { git.add().addFilepattern( "mastodon.project" ).call(); git.commit().setMessage( "Commit from Mastodon" ).call(); @@ -134,4 +126,17 @@ public static void commit( WindowManager windowManager ) throw new RuntimeException( e ); } } + + private static Git initGit( WindowManager windowManager ) throws IOException + { + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); + if ( !correctFolder ) + throw new RuntimeException( "The current project does not appear to be in a git repo." ); + File gitRoot = projectRoot.getParentFile(); + if ( !new File( gitRoot, ".git" ).exists() ) + throw new RuntimeException( "The current project does not appear to be in a git repo." ); + windowManager.getProjectManager().saveProject(); + return Git.open( gitRoot ); + } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java deleted file mode 100644 index 2fcd02c6..00000000 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommit.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.mastodon.mamut.tomancak.collaboration.commands; - -import org.mastodon.mamut.WindowManager; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; -import org.scijava.command.Command; -import org.scijava.plugin.Parameter; - -// TODOs: -// - allow to ignore project.xml and gui.xml -// - allow to provide a commit message -public class MastodonGitCommit implements Command -{ - @Parameter - WindowManager windowManager; - - @Override - public void run() - { - MastodonGitUtils.commit( windowManager ); - } -} From 646d614753a21a15f84394b0c1f13b45270cf7b6 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 14:26:24 +0200 Subject: [PATCH 08/76] Mastodon Git: implement push on create branch commands --- .../collaboration/MastodonGitPlugins.java | 21 +++++++++++++++- .../collaboration/MastodonGitUtils.java | 25 ++++++++++++++++++- .../commands/MastodonGitNewBranch.java | 22 ++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index cd7f4d40..e0a443d6 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -32,6 +32,7 @@ import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitNewBranch; import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; @@ -59,7 +60,15 @@ public class MastodonGitPlugins extends BasicMamutPlugin .addActionDescription( "[mastodon git] commit", "Plugins > Git > Commit", "Commit changes to the git repository.", - MastodonGitPlugins::commit ); + MastodonGitPlugins::commit ) + .addActionDescription( "[mastodon git] push", + "Plugins > Git > Push", + "Push changes to the git repository.", + MastodonGitPlugins::push ) + .addActionDescription( "[mastodon git] new branch", + "Plugins > Git > Create New Branch", + "Create a new branch in the git repository.", + MastodonGitPlugins::newBranch ); public MastodonGitPlugins() { @@ -81,6 +90,16 @@ private void commit() run( () -> MastodonGitUtils.commit( getWindowManager() ) ); } + private void push() + { + run( () -> MastodonGitUtils.push( getWindowManager() ) ); + } + + private void newBranch() + { + commandService.run( MastodonGitNewBranch.class, true, "windowManager", getWindowManager() ); + } + private void run( Runnable action ) { new Thread( action ).start(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index d37a8232..d2f0cff0 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.net.ConnectException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; @@ -127,6 +126,30 @@ public static void commit( WindowManager windowManager ) } } + public static void push( WindowManager windowManager ) + { + try (Git git = initGit( windowManager )) + { + git.push().setRemote( "origin" ).call(); + } + catch ( IOException | GitAPIException e ) + { + throw new RuntimeException( e ); + } + } + + public static void createNewBranch( WindowManager windowManager, String branchName ) + { + try (Git git = initGit( windowManager )) + { + git.checkout().setCreateBranch( true ).setName( branchName ).call(); + } + catch ( IOException | GitAPIException e ) + { + throw new RuntimeException( e ); + } + } + private static Git initGit( WindowManager windowManager ) throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java new file mode 100644 index 00000000..45c3f56e --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -0,0 +1,22 @@ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; + +public class MastodonGitNewBranch implements Command +{ + + @Parameter + private WindowManager windowManager; + + @Parameter( label = "Branch name", persist = false ) + private String branchName; + + @Override + public void run() + { + MastodonGitUtils.createNewBranch( windowManager, branchName ); + } +} From a289403c70bc3e8f50d8ee394ca223b00abc5e7a Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 15:11:21 +0200 Subject: [PATCH 09/76] MastodonGitUtils: don't unnecessary save the project --- .../mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index d2f0cff0..c042b89b 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -117,6 +117,7 @@ public static void commit( WindowManager windowManager ) { try (Git git = initGit( windowManager )) { + windowManager.getProjectManager().saveProject(); git.add().addFilepattern( "mastodon.project" ).call(); git.commit().setMessage( "Commit from Mastodon" ).call(); } @@ -159,7 +160,6 @@ private static Git initGit( WindowManager windowManager ) throws IOException File gitRoot = projectRoot.getParentFile(); if ( !new File( gitRoot, ".git" ).exists() ) throw new RuntimeException( "The current project does not appear to be in a git repo." ); - windowManager.getProjectManager().saveProject(); return Git.open( gitRoot ); } } From 3f9709af1094b01522cca8807f6685029c43c3b1 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 21 Sep 2023 15:13:25 +0200 Subject: [PATCH 10/76] Mastodon Git: add switch branch plugin --- .../collaboration/MastodonGitPlugins.java | 24 ++++++++- .../collaboration/MastodonGitUtils.java | 49 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index e0a443d6..450a01f2 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -28,7 +28,10 @@ */ package org.mastodon.mamut.tomancak.collaboration; -import org.mastodon.mamut.WindowManager; +import java.util.List; + +import javax.swing.JOptionPane; + import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; @@ -68,7 +71,11 @@ public class MastodonGitPlugins extends BasicMamutPlugin .addActionDescription( "[mastodon git] new branch", "Plugins > Git > Create New Branch", "Create a new branch in the git repository.", - MastodonGitPlugins::newBranch ); + MastodonGitPlugins::newBranch ) + .addActionDescription( "[mastodon git] switch branch", + "Plugins > Git > Switch Branch", + "Switch to a different branch in the git repository.", + MastodonGitPlugins::switchBranch ); public MastodonGitPlugins() { @@ -100,6 +107,19 @@ private void newBranch() commandService.run( MastodonGitNewBranch.class, true, "windowManager", getWindowManager() ); } + private void switchBranch() + { + // TODO: the branches are not formatted nicely + List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); + String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + // show JOptionPane that allows to select a branch + String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); + if ( selectedBranch == null ) + return; + // switch to selected branch + MastodonGitUtils.switchBranch( getWindowManager(), selectedBranch ); + } + private void run( Runnable action ) { new Thread( action ).start(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index c042b89b..65d8fd49 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -33,9 +33,12 @@ import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.URIish; import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; @@ -151,9 +154,55 @@ public static void createNewBranch( WindowManager windowManager, String branchNa } } + public static void switchBranch( WindowManager windowManager, String branchName ) + { + try + { + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + try (Git git = initGit( projectRoot )) + { + git.checkout().setName( branchName ).call(); + } + windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); + } + catch ( IOException | SpimDataException | GitAPIException e ) + { + throw new RuntimeException( e ); + } + } + + public static List< String > getBranches( WindowManager windowManager ) + { + try (Git git = initGit( windowManager )) + { + return git.branchList().call().stream().map( Ref::getName ).collect( Collectors.toList() ); + } + catch ( IOException | GitAPIException e ) + { + throw new RuntimeException( e ); + } + } + + public static String getCurrentBranch( WindowManager windowManager ) + { + try (Git git = initGit( windowManager )) + { + return git.getRepository().getBranch(); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } + private static Git initGit( WindowManager windowManager ) throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + return initGit( projectRoot ); + } + + private static Git initGit( File projectRoot ) throws IOException + { boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); if ( !correctFolder ) throw new RuntimeException( "The current project does not appear to be in a git repo." ); From 4f3f398ec9cf06824f2272d6350b31bfb2f79bd1 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 22 Sep 2023 12:52:31 +0200 Subject: [PATCH 11/76] Simplify the computation of max non empty time point in the merge Dataset The computation previously needed to read the SpimData and went through all the SpatialTemporalIndex. This can be simplified to one iteration over the list of spots which is actually a lot less complex than creating the SpatialTemporalIndex. --- .../mamut/tomancak/merging/Dataset.java | 17 ++--- .../mamut/tomancak/merging/MergingUtil.java | 65 ------------------- 2 files changed, 9 insertions(+), 73 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/merging/Dataset.java b/src/main/java/org/mastodon/mamut/tomancak/merging/Dataset.java index 0155e8b0..cc08792e 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/merging/Dataset.java +++ b/src/main/java/org/mastodon/mamut/tomancak/merging/Dataset.java @@ -28,9 +28,6 @@ */ package org.mastodon.mamut.tomancak.merging; -import static org.mastodon.mamut.tomancak.merging.MergingUtil.getMaxNonEmptyTimepoint; -import static org.mastodon.mamut.tomancak.merging.MergingUtil.getNumTimepoints; - import java.io.IOException; import org.mastodon.mamut.model.Model; @@ -47,8 +44,6 @@ public class Dataset { private final MamutProject project; - private final int numTimepoints; - private final Model model; private final int maxNonEmptyTimepoint; @@ -56,17 +51,23 @@ public class Dataset public Dataset( final String path ) throws IOException { project = new MamutProjectIO().load( path ); - numTimepoints = getNumTimepoints( project ); model = new Model(); try (final MamutProject.ProjectReader reader = project.openForReading()) { model.loadRaw( reader ); } - maxNonEmptyTimepoint = getMaxNonEmptyTimepoint( model, numTimepoints ); - + maxNonEmptyTimepoint = maxTimepoint( model ); verify(); } + private int maxTimepoint( Model model ) + { + int max = 0; + for ( Spot spot : model.getGraph().vertices() ) + max = Math.max( max, spot.getTimepoint() ); + return max; + } + public Model model() { return model; diff --git a/src/main/java/org/mastodon/mamut/tomancak/merging/MergingUtil.java b/src/main/java/org/mastodon/mamut/tomancak/merging/MergingUtil.java index 9025434a..0653896b 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/merging/MergingUtil.java +++ b/src/main/java/org/mastodon/mamut/tomancak/merging/MergingUtil.java @@ -28,18 +28,9 @@ */ package org.mastodon.mamut.tomancak.merging; -import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.Spot; import org.mastodon.mamut.model.SpotPool; -import org.mastodon.mamut.project.MamutProject; import org.mastodon.properties.ObjPropertyMap; -import org.mastodon.spatial.SpatialIndex; -import org.mastodon.spatial.SpatioTemporalIndex; -import org.mastodon.util.DummySpimData; - -import bdv.spimdata.SpimDataMinimal; -import bdv.spimdata.XmlIoSpimDataMinimal; -import mpicbg.spim.data.SpimDataException; public class MergingUtil { @@ -58,60 +49,4 @@ public static boolean hasLabel( final Spot spot ) final ObjPropertyMap< Spot, String > labels = ( ObjPropertyMap< Spot, String > ) pool.labelProperty(); return labels.isSet( spot ); } - - /** - * Returns number of time-points in {@code project}. To to that, loads - * {@code spimdata} for {@code project}. - * - * @param project - * the project. - * @return the number of time-points in the project. - */ - public static int getNumTimepoints( final MamutProject project ) - { - try - { - final String spimDataXmlFilename = project.getDatasetXmlFile().getAbsolutePath(); - SpimDataMinimal spimData = DummySpimData.tryCreate( project.getDatasetXmlFile().getName() ); - if ( spimData == null ) - spimData = new XmlIoSpimDataMinimal().load( spimDataXmlFilename ); - return spimData.getSequenceDescription().getTimePoints().size(); - } - catch ( final SpimDataException e ) - { - throw new RuntimeException( e ); - } - } - - // Helper: max timepoint that has at least one spot - - /** - * Returns the largest timepoint (index) where model has a least one spot. - * - * @param model - * the model. - * @param numTimepoints - * the number of time-points in the model. - * @return the largest timepoint (index) where model has a least one spot. - */ - public static int getMaxNonEmptyTimepoint( final Model model, final int numTimepoints ) - { - int maxNonEmptyTimepoint = 0; - final SpatioTemporalIndex< Spot > spatioTemporalIndex = model.getSpatioTemporalIndex(); - spatioTemporalIndex.readLock().lock(); - try - { - for ( int t = 0; t < numTimepoints; ++t ) - { - final SpatialIndex< Spot > index = spatioTemporalIndex.getSpatialIndex( t ); - if ( index.size() > 0 ) - maxNonEmptyTimepoint = t; - } - } - finally - { - spatioTemporalIndex.readLock().unlock(); - } - return maxNonEmptyTimepoint; - } } From 866815d3d73fce7db583216ebfd24b6baace13dd Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 22 Sep 2023 12:57:49 +0200 Subject: [PATCH 12/76] Mastodon Git: implement merge command based on the exiting merge tool --- .../collaboration/MastodonGitPlugins.java | 18 ++++- .../collaboration/MastodonGitUtils.java | 68 ++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 450a01f2..4961997d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -75,7 +75,11 @@ public class MastodonGitPlugins extends BasicMamutPlugin .addActionDescription( "[mastodon git] switch branch", "Plugins > Git > Switch Branch", "Switch to a different branch in the git repository.", - MastodonGitPlugins::switchBranch ); + MastodonGitPlugins::switchBranch ) + .addActionDescription( "[mastodon git] merge branch", + "Plugins > Git > Merge Branch", + "Merge a branch into the current branch.", + MastodonGitPlugins::mergeBranch ); public MastodonGitPlugins() { @@ -120,6 +124,18 @@ private void switchBranch() MastodonGitUtils.switchBranch( getWindowManager(), selectedBranch ); } + private void mergeBranch() + { + List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); + String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + branches.remove( currentBranch ); + // show JOptionPane that allows to select a branch + String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), null ); + if ( selectedBranch == null ) + return; + MastodonGitUtils.mergeBranch( getWindowManager(), selectedBranch ); + } + private void run( Runnable action ) { new Thread( action ).start(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 65d8fd49..96ea597a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -38,11 +38,19 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.OrTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.project.MamutProject; import org.mastodon.mamut.project.MamutProjectIO; +import org.mastodon.mamut.tomancak.merging.Dataset; +import org.mastodon.mamut.tomancak.merging.MergeDatasets; import org.scijava.Context; import mpicbg.spim.data.SpimDataException; @@ -121,8 +129,16 @@ public static void commit( WindowManager windowManager ) try (Git git = initGit( windowManager )) { windowManager.getProjectManager().saveProject(); - git.add().addFilepattern( "mastodon.project" ).call(); - git.commit().setMessage( "Commit from Mastodon" ).call(); + List< DiffEntry > changedFiles = git.diff().setPathFilter( AndTreeFilter.create( PathFilter.create( "mastodon.project" ), ignorePattern() ) ).call(); + if ( !changedFiles.isEmpty() ) + { + for ( DiffEntry diffEntry : changedFiles ) + { + git.add().addFilepattern( diffEntry.getOldPath() ).call(); + git.add().addFilepattern( diffEntry.getNewPath() ).call(); + } + git.commit().setMessage( "Commit from Mastodon" ).call(); + } } catch ( IOException | GitAPIException e ) { @@ -195,6 +211,35 @@ public static String getCurrentBranch( WindowManager windowManager ) } } + public static void mergeBranch( WindowManager windowManager, String selectedBranch ) + { + try (Git git = initGit( windowManager )) + { + boolean clean = isClean( windowManager ); + if ( !clean ) + throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before merging." ); + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + String currentBranch = getCurrentBranch( windowManager ); + Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setName( selectedBranch ).call(); + Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setName( currentBranch ).call(); + git.merge().setCommit( false ).include( git.getRepository().exactRef( selectedBranch ) ).call(); // TODO selected branch, should not be a string but a ref instead + windowManager.getProjectManager().open( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); + final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet( windowManager.getAppModel().getModel() ); + double distCutoff = 1000; + double mahalanobisDistCutoff = 1; + double ratioThreshold = 2; + MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); + windowManager.getProjectManager().saveProject( projectRoot ); + commit( windowManager ); + } + catch ( IOException | GitAPIException | SpimDataException e ) + { + throw new RuntimeException( e ); + } + } + private static Git initGit( WindowManager windowManager ) throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); @@ -211,4 +256,23 @@ private static Git initGit( File projectRoot ) throws IOException throw new RuntimeException( "The current project does not appear to be in a git repo." ); return Git.open( gitRoot ); } + + private static boolean isClean( WindowManager windowManager ) throws GitAPIException, IOException + { + try (Git git = initGit( windowManager )) + { + windowManager.getProjectManager().saveProject(); + return git.diff().setPathFilter( ignorePattern() ).call().isEmpty(); + } + } + + private static TreeFilter ignorePattern() + { + TreeFilter[] filters = { + PathFilter.create( "mastodon.project/gui.xml" ), + PathFilter.create( "mastodon.project/project.xml" ), + PathFilter.create( "mastodon.project/dataset.xml.backup" ) + }; + return OrTreeFilter.create( filters ).negate(); + } } From a0a11135dba97a9841a42156b9dbd0d0fc262718 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 22 Sep 2023 15:57:59 +0200 Subject: [PATCH 13/76] Mastodon Git: add pull and reset commands --- .../collaboration/MastodonGitPlugins.java | 18 +++++ .../collaboration/MastodonGitUtils.java | 73 ++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 4961997d..3157e6dd 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -68,6 +68,14 @@ public class MastodonGitPlugins extends BasicMamutPlugin "Plugins > Git > Push", "Push changes to the git repository.", MastodonGitPlugins::push ) + .addActionDescription( "[mastodon git] pull", + "Plugins > Git > Pull", + "Pull changes from the git repository.", + MastodonGitPlugins::pull ) + .addActionDescription( "[mastodon git] reset", + "Plugins > Git > Reset", + "Reset changes in the git repository.", + MastodonGitPlugins::reset ) .addActionDescription( "[mastodon git] new branch", "Plugins > Git > Create New Branch", "Create a new branch in the git repository.", @@ -136,6 +144,16 @@ private void mergeBranch() MastodonGitUtils.mergeBranch( getWindowManager(), selectedBranch ); } + private void pull() + { + run( () -> MastodonGitUtils.pull( getWindowManager() ) ); + } + + private void reset() + { + run( () -> MastodonGitUtils.reset( getWindowManager() ) ); + } + private void run( Runnable action ) { new Thread( action ).start(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 96ea597a..8fcbad13 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -37,8 +37,10 @@ import java.util.stream.Collectors; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.lib.BranchTrackingStatus; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; @@ -129,7 +131,7 @@ public static void commit( WindowManager windowManager ) try (Git git = initGit( windowManager )) { windowManager.getProjectManager().saveProject(); - List< DiffEntry > changedFiles = git.diff().setPathFilter( AndTreeFilter.create( PathFilter.create( "mastodon.project" ), ignorePattern() ) ).call(); + List< DiffEntry > changedFiles = relevantChanges( git ); if ( !changedFiles.isEmpty() ) { for ( DiffEntry diffEntry : changedFiles ) @@ -146,6 +148,11 @@ public static void commit( WindowManager windowManager ) } } + private static List< DiffEntry > relevantChanges( Git git ) throws GitAPIException + { + return git.diff().setPathFilter( AndTreeFilter.create( PathFilter.create( "mastodon.project" ), ignorePattern() ) ).call(); + } + public static void push( WindowManager windowManager ) { try (Git git = initGit( windowManager )) @@ -240,6 +247,70 @@ public static void mergeBranch( WindowManager windowManager, String selectedBran } } + public static void pull( WindowManager windowManager ) + { + try (Git git = initGit( windowManager )) + { + windowManager.getProjectManager().saveProject(); + boolean isClean = isClean( windowManager ); + if ( !isClean ) + throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); + git.fetch().call(); + int aheadCount = BranchTrackingStatus.of( git.getRepository(), git.getRepository().getBranch() ).getAheadCount(); + if ( aheadCount > 0 ) + throw new RuntimeException( "There are local changes. UNSUPPORTED operation. Cannot be done without merge." ); + git.pull().call(); + reloadFromDisc( windowManager ); + } + catch ( IOException | GitAPIException | SpimDataException e ) + { + throw new RuntimeException( e ); + } + } + + private static void reloadFromDisc( WindowManager windowManager ) throws IOException, SpimDataException + { + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); + } + + public static void reset( WindowManager windowManager ) + { + try (Git git = initGit( windowManager )) + { + resetRelevantChanges( git ); + reloadFromDisc( windowManager ); + } + catch ( IOException | GitAPIException | SpimDataException e ) + { + throw new RuntimeException( e ); + } + } + + private static void resetRelevantChanges( Git git ) throws GitAPIException + { + // NB: More complicated than a simple reset --hard, because gui.xml, project.xml and dataset.xml.backup should remain untouched. + List< DiffEntry > diffEntries = relevantChanges( git ); + ResetCommand resetCommand = git.reset().setMode( ResetCommand.ResetType.HARD ); + for ( DiffEntry entry : diffEntries ) + { + switch ( entry.getChangeType() ) + { + case ADD: + resetCommand.addPath( entry.getNewPath() ); + break; + case DELETE: + resetCommand.addPath( entry.getOldPath() ); + break; + case MODIFY: + resetCommand.addPath( entry.getNewPath() ); + resetCommand.addPath( entry.getOldPath() ); + break; + } + resetCommand.call(); + } + } + private static Git initGit( WindowManager windowManager ) throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); From 013b3df0b8df2bd6cb35a3a7df5566a2bea65810 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 22 Sep 2023 15:59:35 +0200 Subject: [PATCH 14/76] Add TODO --- .../mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 8fcbad13..6ae582b7 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -179,6 +179,7 @@ public static void createNewBranch( WindowManager windowManager, String branchNa public static void switchBranch( WindowManager windowManager, String branchName ) { + // TODO allow to switch to remote branches try { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); From 340b07e7a7ce9048913161379049d64e6869a80f Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 27 Sep 2023 13:54:03 +0200 Subject: [PATCH 15/76] Mastodon Git: add commit graph example --- pom.xml | 10 +++ .../collaboration/CommitGraphExample.java | 68 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitGraphExample.java diff --git a/pom.xml b/pom.xml index a650d006..708dae03 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,16 @@ org.eclipse.jgit.ssh.apache 6.7.0.202309050840-r + + org.eclipse.jgit + org.eclipse.jgit.ui + 6.7.0.202309050840-r + + + org.eclipse.jgit + org.eclipse.jgit.ui + 6.7.0.202309050840-r + junit diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitGraphExample.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitGraphExample.java new file mode 100644 index 00000000..12e09935 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitGraphExample.java @@ -0,0 +1,68 @@ +/*- + * #%L + * mastodon-tomancak + * %% + * Copyright (C) 2018 - 2022 Tobias Pietzsch + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration; + +import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.awtui.CommitGraphPane; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revplot.PlotWalk; + +// Inspired by https://git.eclipse.org/r/plugins/gitiles/jgit/jgit/+/84022ac9de54ded6f04ca196264a8f2370e9da9a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java +public class CommitGraphExample +{ + public static void main( String... args ) throws IOException, GitAPIException + { + Git git = Git.open( new File( "/home/arzt/tmp/2/mgit-test" ) ); + Repository repository = git.getRepository(); + PlotWalk plotWalk = new PlotWalk( repository ); + plotWalk.markStart( plotWalk.parseCommit( repository.resolve( "HEAD" ) ) ); + JFrame frame = new JFrame( "Commit Graph!" ); + CommitGraphPane comp = new CommitGraphPane(); + comp.getCommitList().source( plotWalk ); + comp.getCommitList().fillTo( Integer.MAX_VALUE ); + JLabel label = new JLabel(); + comp.getSelectionModel().addListSelectionListener( e -> { + int rowIndex = comp.getSelectedRow(); + label.setText( comp.getCommitList().get( rowIndex ).getId().toString() ); + } ); + frame.add( new JScrollPane( comp ) ); + frame.add( label, BorderLayout.PAGE_END ); + frame.setSize( 600, 600 ); + frame.setVisible( true ); + } +} From d28a1574b48a3dbc070d0da0c8686a1fa9c22ecb Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 27 Sep 2023 14:30:02 +0200 Subject: [PATCH 16/76] MastodonGitUtils: refactoring Change exception handling. Exception are handled by the caller. Change method ignorePattern() to relevantFilesFilter(). Simplify isClean --- .../collaboration/MastodonGitPlugins.java | 66 +++++-- .../collaboration/MastodonGitUtils.java | 162 ++++++------------ .../commands/MastodonGitCreateRepository.java | 9 +- .../commands/MastodonGitNewBranch.java | 9 +- 4 files changed, 116 insertions(+), 130 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 3157e6dd..83de5787 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -121,27 +121,41 @@ private void newBranch() private void switchBranch() { - // TODO: the branches are not formatted nicely - List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); - String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); - // show JOptionPane that allows to select a branch - String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); - if ( selectedBranch == null ) - return; - // switch to selected branch - MastodonGitUtils.switchBranch( getWindowManager(), selectedBranch ); + try + { + // TODO: the branches are not formatted nicely + List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); + String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + // show JOptionPane that allows to select a branch + String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); + if ( selectedBranch == null ) + return; + // switch to selected branch + run( () -> MastodonGitUtils.switchBranch( getWindowManager(), selectedBranch ) ); + } + catch ( Exception e ) + { + e.printStackTrace(); + } } private void mergeBranch() { - List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); - String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); - branches.remove( currentBranch ); - // show JOptionPane that allows to select a branch - String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), null ); - if ( selectedBranch == null ) - return; - MastodonGitUtils.mergeBranch( getWindowManager(), selectedBranch ); + try + { + List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); + String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + branches.remove( currentBranch ); + // show JOptionPane that allows to select a branch + String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), null ); + if ( selectedBranch == null ) + return; + MastodonGitUtils.mergeBranch( getWindowManager(), selectedBranch ); + } + catch ( Exception e ) + { + e.printStackTrace(); + } } private void pull() @@ -154,9 +168,23 @@ private void reset() run( () -> MastodonGitUtils.reset( getWindowManager() ) ); } - private void run( Runnable action ) + private void run( RunnableWithException action ) + { + new Thread( () -> { + try + { + action.run(); + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } ).start(); + } + + interface RunnableWithException { - new Thread( action ).start(); + void run() throws Exception; } @Plugin( type = CommandDescriptionProvider.class ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 6ae582b7..7a37c9b2 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -44,7 +43,6 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; -import org.eclipse.jgit.treewalk.filter.OrTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.mastodon.mamut.MainWindow; @@ -60,7 +58,7 @@ public class MastodonGitUtils { - public static void main( String... args ) throws IOException, SpimDataException + public static void main( String... args ) throws Exception { String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; String repositoryName = "mgit-test"; @@ -77,56 +75,41 @@ public static void main( String... args ) throws IOException, SpimDataException MastodonGitUtils.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); } - public static void createRepositoryAndUpload( WindowManager windowManager, File parentDirectory, String repositoryName, String repositoryURL ) + public static void createRepositoryAndUpload( + WindowManager windowManager, + File parentDirectory, + String repositoryName, + String repositoryURL ) + throws Exception { - try - { - Path gitRepositoryPath = parentDirectory.toPath().resolve( repositoryName ); - Files.createDirectories( gitRepositoryPath ); - Git git = Git.init().setDirectory( gitRepositoryPath.toFile() ).call(); - Path mastodonProjectPath = gitRepositoryPath.resolve( "mastodon.project" ); - Files.createDirectory( mastodonProjectPath ); - windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); - git.add().addFilepattern( "mastodon.project" ).call(); - git.commit().setMessage( "Initial commit" ).call(); - git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); - git.push().setRemote( "origin" ).call(); - git.close(); - } - catch ( GitAPIException | IOException | URISyntaxException e ) - { - throw new RuntimeException( e ); - } + Path gitRepositoryPath = parentDirectory.toPath().resolve( repositoryName ); + Files.createDirectories( gitRepositoryPath ); + Git git = Git.init().setDirectory( gitRepositoryPath.toFile() ).call(); + Path mastodonProjectPath = gitRepositoryPath.resolve( "mastodon.project" ); + Files.createDirectory( mastodonProjectPath ); + windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); + git.add().addFilepattern( "mastodon.project" ).call(); + git.commit().setMessage( "Initial commit" ).call(); + git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); + git.push().setRemote( "origin" ).call(); + git.close(); } - public static void cloneRepository( String repositoryURL, File parentDirectory ) + public static void cloneRepository( String repositoryURL, File parentDirectory ) throws Exception { - try - { - Git.cloneRepository().setURI( repositoryURL ).setDirectory( parentDirectory ).call(); - } - catch ( GitAPIException e ) - { - throw new RuntimeException( e ); - } + Git git = Git.cloneRepository().setURI( repositoryURL ).setDirectory( parentDirectory ).call(); + git.close(); } - public static void openProjectInRepository( Context context, File directory ) + public static void openProjectInRepository( Context context, File directory ) throws Exception { - try - { - WindowManager windowManager = new WindowManager( context ); - Path path = directory.toPath().resolve( "mastodon.project" ); - windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( path.toAbsolutePath().toString() ) ); - new MainWindow( windowManager ).setVisible( true ); - } - catch ( SpimDataException | IOException e ) - { - throw new RuntimeException( e ); - } + WindowManager windowManager = new WindowManager( context ); + Path path = directory.toPath().resolve( "mastodon.project" ); + windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( path.toAbsolutePath().toString() ) ); + new MainWindow( windowManager ).setVisible( true ); } - public static void commit( WindowManager windowManager ) + public static void commit( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { @@ -142,88 +125,62 @@ public static void commit( WindowManager windowManager ) git.commit().setMessage( "Commit from Mastodon" ).call(); } } - catch ( IOException | GitAPIException e ) - { - throw new RuntimeException( e ); - } } private static List< DiffEntry > relevantChanges( Git git ) throws GitAPIException { - return git.diff().setPathFilter( AndTreeFilter.create( PathFilter.create( "mastodon.project" ), ignorePattern() ) ).call(); + return git.diff().setPathFilter( relevantFilesFilter() ).call(); } - public static void push( WindowManager windowManager ) + public static void push( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { git.push().setRemote( "origin" ).call(); } - catch ( IOException | GitAPIException e ) - { - throw new RuntimeException( e ); - } } - public static void createNewBranch( WindowManager windowManager, String branchName ) + public static void createNewBranch( WindowManager windowManager, String branchName ) throws Exception { try (Git git = initGit( windowManager )) { git.checkout().setCreateBranch( true ).setName( branchName ).call(); } - catch ( IOException | GitAPIException e ) - { - throw new RuntimeException( e ); - } } - public static void switchBranch( WindowManager windowManager, String branchName ) + public static void switchBranch( WindowManager windowManager, String branchName ) throws Exception { // TODO allow to switch to remote branches - try - { - File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); - try (Git git = initGit( projectRoot )) - { - git.checkout().setName( branchName ).call(); - } - windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); - } - catch ( IOException | SpimDataException | GitAPIException e ) + File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); + try (Git git = initGit( projectRoot )) { - throw new RuntimeException( e ); + git.checkout().setName( branchName ).call(); } + windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); } - public static List< String > getBranches( WindowManager windowManager ) + public static List< String > getBranches( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { return git.branchList().call().stream().map( Ref::getName ).collect( Collectors.toList() ); } - catch ( IOException | GitAPIException e ) - { - throw new RuntimeException( e ); - } } - public static String getCurrentBranch( WindowManager windowManager ) + public static String getCurrentBranch( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { return git.getRepository().getBranch(); } - catch ( IOException e ) - { - throw new RuntimeException( e ); - } } - public static void mergeBranch( WindowManager windowManager, String selectedBranch ) + public static void mergeBranch( WindowManager windowManager, String selectedBranch ) throws Exception { try (Git git = initGit( windowManager )) { - boolean clean = isClean( windowManager ); + windowManager.getProjectManager().saveProject(); + boolean clean = isClean( git ); if ( !clean ) throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before merging." ); File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); @@ -242,18 +199,14 @@ public static void mergeBranch( WindowManager windowManager, String selectedBran windowManager.getProjectManager().saveProject( projectRoot ); commit( windowManager ); } - catch ( IOException | GitAPIException | SpimDataException e ) - { - throw new RuntimeException( e ); - } } - public static void pull( WindowManager windowManager ) + public static void pull( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { windowManager.getProjectManager().saveProject(); - boolean isClean = isClean( windowManager ); + boolean isClean = isClean( git ); if ( !isClean ) throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); git.fetch().call(); @@ -263,10 +216,6 @@ public static void pull( WindowManager windowManager ) git.pull().call(); reloadFromDisc( windowManager ); } - catch ( IOException | GitAPIException | SpimDataException e ) - { - throw new RuntimeException( e ); - } } private static void reloadFromDisc( WindowManager windowManager ) throws IOException, SpimDataException @@ -275,17 +224,13 @@ private static void reloadFromDisc( WindowManager windowManager ) throws IOExcep windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); } - public static void reset( WindowManager windowManager ) + public static void reset( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { resetRelevantChanges( git ); reloadFromDisc( windowManager ); } - catch ( IOException | GitAPIException | SpimDataException e ) - { - throw new RuntimeException( e ); - } } private static void resetRelevantChanges( Git git ) throws GitAPIException @@ -329,22 +274,21 @@ private static Git initGit( File projectRoot ) throws IOException return Git.open( gitRoot ); } - private static boolean isClean( WindowManager windowManager ) throws GitAPIException, IOException + private static boolean isClean( Git git ) throws GitAPIException { - try (Git git = initGit( windowManager )) - { - windowManager.getProjectManager().saveProject(); - return git.diff().setPathFilter( ignorePattern() ).call().isEmpty(); - } + return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); } - private static TreeFilter ignorePattern() + private static TreeFilter relevantFilesFilter() { TreeFilter[] filters = { - PathFilter.create( "mastodon.project/gui.xml" ), - PathFilter.create( "mastodon.project/project.xml" ), - PathFilter.create( "mastodon.project/dataset.xml.backup" ) + // only files in subdirectory "mastodon.project" + PathFilter.create( "mastodon.project" ), + // but exclude gui.xml project.xml and dataset.xml.backup + PathFilter.create( "mastodon.project/gui.xml" ).negate(), + PathFilter.create( "mastodon.project/project.xml" ).negate(), + PathFilter.create( "mastodon.project/dataset.xml.backup" ).negate() }; - return OrTreeFilter.create( filters ).negate(); + return AndTreeFilter.create( filters ); } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index 615609ba..421490dd 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -52,6 +52,13 @@ public class MastodonGitCreateRepository implements Command @Override public void run() { - MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + try + { + MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + } + catch ( Exception e ) + { + e.printStackTrace(); + } } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index 45c3f56e..dfcf1ce8 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -17,6 +17,13 @@ public class MastodonGitNewBranch implements Command @Override public void run() { - MastodonGitUtils.createNewBranch( windowManager, branchName ); + try + { + MastodonGitUtils.createNewBranch( windowManager, branchName ); + } + catch ( Exception e ) + { + e.printStackTrace(); + } } } From dc39105899fdf2cd41d44541249b31996e9ce8fb Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 27 Sep 2023 14:45:26 +0200 Subject: [PATCH 17/76] Mastodon Git: shuffle menu entries --- .../tomancak/collaboration/MastodonGitPlugins.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index 83de5787..cac7b251 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -45,6 +45,8 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +// TODO: allow to switch to remote only branch +// TODO: disable commands if not in a git repo @Plugin( type = MamutPlugin.class ) public class MastodonGitPlugins extends BasicMamutPlugin { @@ -53,11 +55,11 @@ public class MastodonGitPlugins extends BasicMamutPlugin public static final ActionDescriptions< MastodonGitPlugins > actionDescriptions = new ActionDescriptions<>( MastodonGitPlugins.class ) .addActionDescription( "[mastodon git] create repository", - "Plugins > Git > Create Repository", + "Plugins > Git > Initialize > Create New Repository", "Upload Mastodon project to a newly created git repository.", MastodonGitPlugins::createRepository ) .addActionDescription( "[mastodon git] clone repository", - "Plugins > Git > Clone Repository", + "Plugins > Git > Initialize > Clone Existing Repository", "Clone a git repository to a new Mastodon project.", MastodonGitPlugins::cloneGitRepository ) .addActionDescription( "[mastodon git] commit", @@ -77,15 +79,15 @@ public class MastodonGitPlugins extends BasicMamutPlugin "Reset changes in the git repository.", MastodonGitPlugins::reset ) .addActionDescription( "[mastodon git] new branch", - "Plugins > Git > Create New Branch", + "Plugins > Git > Branches > Create New Branch", "Create a new branch in the git repository.", MastodonGitPlugins::newBranch ) .addActionDescription( "[mastodon git] switch branch", - "Plugins > Git > Switch Branch", + "Plugins > Git > Branches > Switch Branch", "Switch to a different branch in the git repository.", MastodonGitPlugins::switchBranch ) .addActionDescription( "[mastodon git] merge branch", - "Plugins > Git > Merge Branch", + "Plugins > Git > Branches > Merge Branch", "Merge a branch into the current branch.", MastodonGitPlugins::mergeBranch ); From 409a5582f1f4f65e006322b316eef1415be02e06 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 27 Sep 2023 15:24:07 +0200 Subject: [PATCH 18/76] Mastodon Git: allow to switch to remote only branch --- .../collaboration/MastodonGitPlugins.java | 1 - .../collaboration/MastodonGitUtils.java | 32 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java index cac7b251..064304ad 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java @@ -45,7 +45,6 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -// TODO: allow to switch to remote only branch // TODO: disable commands if not in a git repo @Plugin( type = MamutPlugin.class ) public class MastodonGitPlugins extends BasicMamutPlugin diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index 7a37c9b2..c41ff4d2 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -35,7 +35,9 @@ import java.util.List; import java.util.stream.Collectors; +import org.eclipse.jgit.api.CreateBranchCommand; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; @@ -150,28 +152,48 @@ public static void createNewBranch( WindowManager windowManager, String branchNa public static void switchBranch( WindowManager windowManager, String branchName ) throws Exception { - // TODO allow to switch to remote branches File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); try (Git git = initGit( projectRoot )) { - git.checkout().setName( branchName ).call(); + boolean isRemoteBranch = branchName.startsWith( "refs/remotes/" ); + if ( isRemoteBranch ) + { + String simpleName = getSimpleName( branchName ); + boolean conflict = git.branchList().call().stream().map( Ref::getName ).anyMatch( localName -> simpleName.equals( getSimpleName( localName ) ) ); + if ( conflict ) + throw new RuntimeException( "There's already a local branch with the same name." ); + git.checkout() + .setCreateBranch( true ) + .setName( simpleName ) + .setUpstreamMode( CreateBranchCommand.SetupUpstreamMode.TRACK ) + .setStartPoint( branchName ) + .call(); + } + else + git.checkout().setName( branchName ).call(); } windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); } + private static String getSimpleName( String branchName ) + { + String[] parts = branchName.split( "/" ); + return parts[ parts.length - 1 ]; + } + public static List< String > getBranches( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { - return git.branchList().call().stream().map( Ref::getName ).collect( Collectors.toList() ); + return git.branchList().setListMode( ListBranchCommand.ListMode.ALL ).call().stream().map( Ref::getName ).collect( Collectors.toList() ); } } - public static String getCurrentBranch( WindowManager windowManager ) throws Exception + public static String getCurrentBranch( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { - return git.getRepository().getBranch(); + return git.getRepository().getFullBranch(); } } From f24f1ddcfa0b03a7116373ae09f3b9ce4ce7253b Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 28 Sep 2023 15:55:46 +0200 Subject: [PATCH 19/76] Add class that asks for username and password once --- .../collaboration/MastodonGitUtils.java | 23 ++-- .../credentials/PersistentCredentials.java | 119 ++++++++++++++++++ 2 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java index c41ff4d2..d159b949 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java @@ -51,15 +51,20 @@ import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.project.MamutProject; import org.mastodon.mamut.project.MamutProjectIO; +import org.mastodon.mamut.tomancak.collaboration.credentials.PersistentCredentials; import org.mastodon.mamut.tomancak.merging.Dataset; import org.mastodon.mamut.tomancak.merging.MergeDatasets; import org.scijava.Context; import mpicbg.spim.data.SpimDataException; +// make it one synchronized class per repository +// don't allow to open a repository twice (maybe read only) public class MastodonGitUtils { + private static final PersistentCredentials credentials = new PersistentCredentials(); + public static void main( String... args ) throws Exception { String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; @@ -93,13 +98,17 @@ public static void createRepositoryAndUpload( git.add().addFilepattern( "mastodon.project" ).call(); git.commit().setMessage( "Initial commit" ).call(); git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); - git.push().setRemote( "origin" ).call(); + git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); git.close(); } public static void cloneRepository( String repositoryURL, File parentDirectory ) throws Exception { - Git git = Git.cloneRepository().setURI( repositoryURL ).setDirectory( parentDirectory ).call(); + Git git = Git.cloneRepository() + .setURI( repositoryURL ) + .setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) + .setDirectory( parentDirectory ) + .call(); git.close(); } @@ -138,7 +147,7 @@ public static void push( WindowManager windowManager ) throws Exception { try (Git git = initGit( windowManager )) { - git.push().setRemote( "origin" ).call(); + git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); } } @@ -230,12 +239,12 @@ public static void pull( WindowManager windowManager ) throws Exception windowManager.getProjectManager().saveProject(); boolean isClean = isClean( git ); if ( !isClean ) - throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); - git.fetch().call(); + throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before.setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) pulling." ); + git.fetch().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); int aheadCount = BranchTrackingStatus.of( git.getRepository(), git.getRepository().getBranch() ).getAheadCount(); if ( aheadCount > 0 ) throw new RuntimeException( "There are local changes. UNSUPPORTED operation. Cannot be done without merge." ); - git.pull().call(); + git.pull().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); reloadFromDisc( windowManager ); } } @@ -298,7 +307,7 @@ private static Git initGit( File projectRoot ) throws IOException private static boolean isClean( Git git ) throws GitAPIException { - return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); + return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); } private static TreeFilter relevantFilesFilter() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java new file mode 100644 index 00000000..86e105e1 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java @@ -0,0 +1,119 @@ +package org.mastodon.mamut.tomancak.collaboration.credentials; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import net.miginfocom.swing.MigLayout; + +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +public class PersistentCredentials +{ + + private String username = null; + + private String password = null; + + private boolean missingCredentials() + { + return password == null || username == null; + } + + private boolean queryPassword( String url ) + { + JTextField usernameField = new JTextField( 20 ); + JPasswordField passwordField = new JPasswordField( 20 ); + + final JPanel panel = new JPanel(); + panel.setLayout( new MigLayout( "insets dialog" ) ); + panel.add( new JLabel( "Please enter your credentials for the Git repository:" ), "span, wrap" ); + panel.add( new JLabel( url ), "span, wrap, gapbottom unrelated" ); + panel.add( new JLabel( "username" ) ); + panel.add( usernameField, "wrap" ); + panel.add( new JLabel( " password" ) ); + panel.add( passwordField, "wrap" ); + boolean ok = JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog( null, + panel, "Authentication for Git Repository", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ); + if ( !ok ) + return false; + + username = usernameField.getText(); + password = new String( passwordField.getPassword() ); + return true; + } + + public CredentialsProvider getSingleUseCredentialsProvider() + { + return new SingleUseCredentialsProvider(); + } + + /** + * The JGIT api does not tell a CredentialsProvider if the credentials + * where correct. It simply asks for them again if they were wrong. + *

+ * We can exploit this behavior by counting the number of times the + * CredentialsProvider was asked for credentials. If it was asked more than + * once, we assume that the credentials were wrong. + *

+ * This only works if the CredentialsProvider is only used once. + */ + private class SingleUseCredentialsProvider extends CredentialsProvider + { + private int counter = 0; + + @Override + public boolean isInteractive() + { + return true; + } + + @Override + public boolean supports( CredentialItem... items ) + { + for ( CredentialItem item : items ) + if ( !isUsernameOrPassword( item ) ) + return false; + return true; + } + + private boolean isUsernameOrPassword( CredentialItem item ) + { + return ( item instanceof CredentialItem.Username ) || ( item instanceof CredentialItem.Password ); + } + + @Override + public boolean get( URIish uri, CredentialItem... items ) throws UnsupportedCredentialItem + { + if ( !supports( items ) ) + return false; + counter++; + boolean previousAuthenticationFailed = counter > 1; + if ( previousAuthenticationFailed || missingCredentials() ) + if ( !queryPassword( uri.toString() ) ) + return false; + fillUsernameAndPassword( items ); + return true; + } + + private void fillUsernameAndPassword( CredentialItem[] items ) + { + for ( CredentialItem item : items ) + fillItem( item ); + } + + private void fillItem( CredentialItem item ) + { + if ( item instanceof CredentialItem.Username ) + ( ( CredentialItem.Username ) item ).setValue( username ); + else if ( item instanceof CredentialItem.Password ) + ( ( CredentialItem.Password ) item ).setValue( password.toCharArray() ); + } + } +} From 1f3388c59b3a7da3ecd1e6adfd450e711079c132 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 12:16:01 +0200 Subject: [PATCH 20/76] Rename MastodonGitUtils and MastodonGitPlugins MastodonGitUtils -> MastodonGitRepository MastodonGitPlugins -> MastodonGitController --- ...lugins.java => MastodonGitController.java} | 44 +++++++++---------- ...tUtils.java => MastodonGitRepository.java} | 4 +- .../commands/MastodonGitCloneRepository.java | 6 +-- .../commands/MastodonGitCreateRepository.java | 4 +- .../commands/MastodonGitNewBranch.java | 4 +- .../collaboration/utils/BasicMamutPlugin.java | 3 +- 6 files changed, 33 insertions(+), 32 deletions(-) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{MastodonGitPlugins.java => MastodonGitController.java} (80%) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{MastodonGitUtils.java => MastodonGitRepository.java} (98%) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java similarity index 80% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 064304ad..7f4655ee 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -47,50 +47,50 @@ // TODO: disable commands if not in a git repo @Plugin( type = MamutPlugin.class ) -public class MastodonGitPlugins extends BasicMamutPlugin +public class MastodonGitController extends BasicMamutPlugin { @Parameter private CommandService commandService; - public static final ActionDescriptions< MastodonGitPlugins > actionDescriptions = new ActionDescriptions<>( MastodonGitPlugins.class ) + public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ) .addActionDescription( "[mastodon git] create repository", "Plugins > Git > Initialize > Create New Repository", "Upload Mastodon project to a newly created git repository.", - MastodonGitPlugins::createRepository ) + MastodonGitController::createRepository ) .addActionDescription( "[mastodon git] clone repository", "Plugins > Git > Initialize > Clone Existing Repository", "Clone a git repository to a new Mastodon project.", - MastodonGitPlugins::cloneGitRepository ) + MastodonGitController::cloneGitRepository ) .addActionDescription( "[mastodon git] commit", "Plugins > Git > Commit", "Commit changes to the git repository.", - MastodonGitPlugins::commit ) + MastodonGitController::commit ) .addActionDescription( "[mastodon git] push", "Plugins > Git > Push", "Push changes to the git repository.", - MastodonGitPlugins::push ) + MastodonGitController::push ) .addActionDescription( "[mastodon git] pull", "Plugins > Git > Pull", "Pull changes from the git repository.", - MastodonGitPlugins::pull ) + MastodonGitController::pull ) .addActionDescription( "[mastodon git] reset", "Plugins > Git > Reset", "Reset changes in the git repository.", - MastodonGitPlugins::reset ) + MastodonGitController::reset ) .addActionDescription( "[mastodon git] new branch", "Plugins > Git > Branches > Create New Branch", "Create a new branch in the git repository.", - MastodonGitPlugins::newBranch ) + MastodonGitController::newBranch ) .addActionDescription( "[mastodon git] switch branch", "Plugins > Git > Branches > Switch Branch", "Switch to a different branch in the git repository.", - MastodonGitPlugins::switchBranch ) + MastodonGitController::switchBranch ) .addActionDescription( "[mastodon git] merge branch", "Plugins > Git > Branches > Merge Branch", "Merge a branch into the current branch.", - MastodonGitPlugins::mergeBranch ); + MastodonGitController::mergeBranch ); - public MastodonGitPlugins() + public MastodonGitController() { super( actionDescriptions ); } @@ -107,12 +107,12 @@ private void cloneGitRepository() private void commit() { - run( () -> MastodonGitUtils.commit( getWindowManager() ) ); + run( () -> MastodonGitRepository.commit( getWindowManager() ) ); } private void push() { - run( () -> MastodonGitUtils.push( getWindowManager() ) ); + run( () -> MastodonGitRepository.push( getWindowManager() ) ); } private void newBranch() @@ -125,14 +125,14 @@ private void switchBranch() try { // TODO: the branches are not formatted nicely - List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); - String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + List< String > branches = MastodonGitRepository.getBranches( getWindowManager() ); + String currentBranch = MastodonGitRepository.getCurrentBranch( getWindowManager() ); // show JOptionPane that allows to select a branch String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); if ( selectedBranch == null ) return; // switch to selected branch - run( () -> MastodonGitUtils.switchBranch( getWindowManager(), selectedBranch ) ); + run( () -> MastodonGitRepository.switchBranch( getWindowManager(), selectedBranch ) ); } catch ( Exception e ) { @@ -144,14 +144,14 @@ private void mergeBranch() { try { - List< String > branches = MastodonGitUtils.getBranches( getWindowManager() ); - String currentBranch = MastodonGitUtils.getCurrentBranch( getWindowManager() ); + List< String > branches = MastodonGitRepository.getBranches( getWindowManager() ); + String currentBranch = MastodonGitRepository.getCurrentBranch( getWindowManager() ); branches.remove( currentBranch ); // show JOptionPane that allows to select a branch String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), null ); if ( selectedBranch == null ) return; - MastodonGitUtils.mergeBranch( getWindowManager(), selectedBranch ); + MastodonGitRepository.mergeBranch( getWindowManager(), selectedBranch ); } catch ( Exception e ) { @@ -161,12 +161,12 @@ private void mergeBranch() private void pull() { - run( () -> MastodonGitUtils.pull( getWindowManager() ) ); + run( () -> MastodonGitRepository.pull( getWindowManager() ) ); } private void reset() { - run( () -> MastodonGitUtils.reset( getWindowManager() ) ); + run( () -> MastodonGitRepository.reset( getWindowManager() ) ); } private void run( RunnableWithException action ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java similarity index 98% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index d159b949..f6e870a8 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -60,7 +60,7 @@ // make it one synchronized class per repository // don't allow to open a repository twice (maybe read only) -public class MastodonGitUtils +public class MastodonGitRepository { private static final PersistentCredentials credentials = new PersistentCredentials(); @@ -79,7 +79,7 @@ public static void main( String... args ) throws Exception // MastodonGitUtils.cloneRepository( repositoryURL, new File( parentDirectory, "2/" ) ); - MastodonGitUtils.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); + MastodonGitRepository.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); } public static void createRepositoryAndUpload( diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 2799e8a4..dfcbd701 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -2,7 +2,7 @@ import java.io.File; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.Context; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -31,8 +31,8 @@ public void run() try { File directory = new File( this.parentDirectory, repositoryName ); - MastodonGitUtils.cloneRepository( repositoryURL, directory ); - MastodonGitUtils.openProjectInRepository( context, directory ); + MastodonGitRepository.cloneRepository( repositoryURL, directory ); + MastodonGitRepository.openProjectInRepository( context, directory ); } catch ( final Exception e ) { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index 421490dd..95c805e6 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -31,7 +31,7 @@ import java.io.File; import org.mastodon.mamut.WindowManager; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -54,7 +54,7 @@ public void run() { try { - MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + MastodonGitRepository.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); } catch ( Exception e ) { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index dfcf1ce8..fdb3f7de 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -1,7 +1,7 @@ package org.mastodon.mamut.tomancak.collaboration.commands; import org.mastodon.mamut.WindowManager; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitUtils; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -19,7 +19,7 @@ public void run() { try { - MastodonGitUtils.createNewBranch( windowManager, branchName ); + MastodonGitRepository.createNewBranch( windowManager, branchName ); } catch ( Exception e ) { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java index 5913ee27..aa8835a1 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -40,13 +40,14 @@ import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.plugin.MamutPluginAppModel; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitController; import org.scijava.ui.behaviour.util.AbstractNamedAction; import org.scijava.ui.behaviour.util.Actions; import org.scijava.ui.behaviour.util.RunnableAction; /** * A class that simplifies the creation of a {@link MamutPlugin}. - * See {@link org.mastodon.mamut.tomancak.collaboration.MastodonGitPlugins} for + * See {@link MastodonGitController} for * usage example. */ public class BasicMamutPlugin implements MamutPlugin From c63b699910e043cd0563eadc79795b14892a249d Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 12:16:38 +0200 Subject: [PATCH 21/76] Introduce a MastodonGitRepository object per Mastodon instance --- .../collaboration/MastodonGitController.java | 42 +++++--- .../collaboration/MastodonGitRepository.java | 98 +++++++++---------- .../commands/MastodonGitCreateRepository.java | 12 +-- .../commands/MastodonGitNewBranch.java | 4 +- .../collaboration/utils/BasicMamutPlugin.java | 3 + .../MastodonGitRepositoryDemo.java | 25 +++++ 6 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepositoryDemo.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 7f4655ee..3ae4786d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -28,7 +28,9 @@ */ package org.mastodon.mamut.tomancak.collaboration; +import java.io.File; import java.util.List; +import java.util.function.BiConsumer; import javax.swing.JOptionPane; @@ -90,14 +92,26 @@ public class MastodonGitController extends BasicMamutPlugin "Merge a branch into the current branch.", MastodonGitController::mergeBranch ); + private MastodonGitRepository repository; + public MastodonGitController() { super( actionDescriptions ); } + @Override + protected void initialize() + { + super.initialize(); + repository = new MastodonGitRepository( getWindowManager() ); + } + private void createRepository() { - commandService.run( MastodonGitCreateRepository.class, true, "windowManager", getWindowManager() ); + BiConsumer< File, String > directoryAndUrlCallback = ( directory, url ) -> { + run( () -> this.repository = MastodonGitRepository.createRepositoryAndUpload( getWindowManager(), directory, url ) ); + }; + commandService.run( MastodonGitCreateRepository.class, true, "directoryAndUrlCallback", directoryAndUrlCallback ); } private void cloneGitRepository() @@ -107,17 +121,17 @@ private void cloneGitRepository() private void commit() { - run( () -> MastodonGitRepository.commit( getWindowManager() ) ); + run( () -> repository.commit() ); } private void push() { - run( () -> MastodonGitRepository.push( getWindowManager() ) ); + run( () -> repository.push() ); } private void newBranch() { - commandService.run( MastodonGitNewBranch.class, true, "windowManager", getWindowManager() ); + commandService.run( MastodonGitNewBranch.class, true, "repository", repository ); } private void switchBranch() @@ -125,14 +139,14 @@ private void switchBranch() try { // TODO: the branches are not formatted nicely - List< String > branches = MastodonGitRepository.getBranches( getWindowManager() ); - String currentBranch = MastodonGitRepository.getCurrentBranch( getWindowManager() ); + List< String > branches = repository.getBranches(); + String currentBranch = repository.getCurrentBranch(); // show JOptionPane that allows to select a branch String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); if ( selectedBranch == null ) return; // switch to selected branch - run( () -> MastodonGitRepository.switchBranch( getWindowManager(), selectedBranch ) ); + run( () -> repository.switchBranch( selectedBranch ) ); } catch ( Exception e ) { @@ -144,14 +158,14 @@ private void mergeBranch() { try { - List< String > branches = MastodonGitRepository.getBranches( getWindowManager() ); - String currentBranch = MastodonGitRepository.getCurrentBranch( getWindowManager() ); + List< String > branches = repository.getBranches(); + String currentBranch = repository.getCurrentBranch(); branches.remove( currentBranch ); // show JOptionPane that allows to select a branch String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), null ); if ( selectedBranch == null ) return; - MastodonGitRepository.mergeBranch( getWindowManager(), selectedBranch ); + repository.mergeBranch( selectedBranch ); } catch ( Exception e ) { @@ -161,12 +175,12 @@ private void mergeBranch() private void pull() { - run( () -> MastodonGitRepository.pull( getWindowManager() ) ); + run( () -> repository.pull() ); } private void reset() { - run( () -> MastodonGitRepository.reset( getWindowManager() ) ); + run( () -> repository.reset() ); } private void run( RunnableWithException action ) @@ -183,6 +197,10 @@ private void run( RunnableWithException action ) } ).start(); } + public void createRepositoryAndUpload( File directory, String repositoryURL ) + { + } + interface RunnableWithException { void run() throws Exception; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index f6e870a8..a0717b1d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -65,34 +65,25 @@ public class MastodonGitRepository private static final PersistentCredentials credentials = new PersistentCredentials(); - public static void main( String... args ) throws Exception - { - String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; - String repositoryName = "mgit-test"; - String repositoryURL = "git@github.com:maarzt/mgit-test.git"; - File parentDirectory = new File( "/home/arzt/tmp/" ); - -// Context context = new Context(); -// WindowManager windowManager = new WindowManager( context ); -// windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) ); -// MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + private final WindowManager windowManager; -// MastodonGitUtils.cloneRepository( repositoryURL, new File( parentDirectory, "2/" ) ); - - MastodonGitRepository.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); + public MastodonGitRepository( WindowManager windowManager ) + { + this.windowManager = windowManager; } - public static void createRepositoryAndUpload( + public static MastodonGitRepository createRepositoryAndUpload( WindowManager windowManager, - File parentDirectory, - String repositoryName, + File directory, String repositoryURL ) throws Exception { - Path gitRepositoryPath = parentDirectory.toPath().resolve( repositoryName ); - Files.createDirectories( gitRepositoryPath ); - Git git = Git.init().setDirectory( gitRepositoryPath.toFile() ).call(); - Path mastodonProjectPath = gitRepositoryPath.resolve( "mastodon.project" ); + if ( !directory.isDirectory() ) + throw new IllegalArgumentException( "Not a directory: " + directory ); + if ( !isEmpty( directory ) ) + throw new IllegalArgumentException( "Directory not empty: " + directory ); + Git git = Git.init().setDirectory( directory ).call(); + Path mastodonProjectPath = directory.toPath().resolve( "mastodon.project" ); Files.createDirectory( mastodonProjectPath ); windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); git.add().addFilepattern( "mastodon.project" ).call(); @@ -100,6 +91,13 @@ public static void createRepositoryAndUpload( git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); git.close(); + return new MastodonGitRepository( windowManager ); + } + + private static boolean isEmpty( File directory ) + { + String[] containedFiles = directory.list(); + return containedFiles == null || containedFiles.length == 0; } public static void cloneRepository( String repositoryURL, File parentDirectory ) throws Exception @@ -120,9 +118,9 @@ public static void openProjectInRepository( Context context, File directory ) th new MainWindow( windowManager ).setVisible( true ); } - public static void commit( WindowManager windowManager ) throws Exception + public synchronized void commit() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); List< DiffEntry > changedFiles = relevantChanges( git ); @@ -138,28 +136,28 @@ public static void commit( WindowManager windowManager ) throws Exception } } - private static List< DiffEntry > relevantChanges( Git git ) throws GitAPIException + private synchronized List< DiffEntry > relevantChanges( Git git ) throws GitAPIException { return git.diff().setPathFilter( relevantFilesFilter() ).call(); } - public static void push( WindowManager windowManager ) throws Exception + public synchronized void push() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); } } - public static void createNewBranch( WindowManager windowManager, String branchName ) throws Exception + public synchronized void createNewBranch( String branchName ) throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { git.checkout().setCreateBranch( true ).setName( branchName ).call(); } } - public static void switchBranch( WindowManager windowManager, String branchName ) throws Exception + public synchronized void switchBranch( String branchName ) throws Exception { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); try (Git git = initGit( projectRoot )) @@ -184,38 +182,38 @@ public static void switchBranch( WindowManager windowManager, String branchName windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); } - private static String getSimpleName( String branchName ) + private synchronized String getSimpleName( String branchName ) { String[] parts = branchName.split( "/" ); return parts[ parts.length - 1 ]; } - public static List< String > getBranches( WindowManager windowManager ) throws Exception + public synchronized List< String > getBranches() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { return git.branchList().setListMode( ListBranchCommand.ListMode.ALL ).call().stream().map( Ref::getName ).collect( Collectors.toList() ); } } - public static String getCurrentBranch( WindowManager windowManager ) throws Exception + public synchronized String getCurrentBranch() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { return git.getRepository().getFullBranch(); } } - public static void mergeBranch( WindowManager windowManager, String selectedBranch ) throws Exception + public synchronized void mergeBranch( String selectedBranch ) throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); boolean clean = isClean( git ); if ( !clean ) throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before merging." ); File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); - String currentBranch = getCurrentBranch( windowManager ); + String currentBranch = getCurrentBranch(); Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( selectedBranch ).call(); Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); @@ -228,13 +226,13 @@ public static void mergeBranch( WindowManager windowManager, String selectedBran double ratioThreshold = 2; MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); windowManager.getProjectManager().saveProject( projectRoot ); - commit( windowManager ); + commit(); } } - public static void pull( WindowManager windowManager ) throws Exception + public synchronized void pull() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); boolean isClean = isClean( git ); @@ -245,26 +243,26 @@ public static void pull( WindowManager windowManager ) throws Exception if ( aheadCount > 0 ) throw new RuntimeException( "There are local changes. UNSUPPORTED operation. Cannot be done without merge." ); git.pull().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); - reloadFromDisc( windowManager ); + reloadFromDisc(); } } - private static void reloadFromDisc( WindowManager windowManager ) throws IOException, SpimDataException + private synchronized void reloadFromDisc() throws IOException, SpimDataException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); } - public static void reset( WindowManager windowManager ) throws Exception + public synchronized void reset() throws Exception { - try (Git git = initGit( windowManager )) + try (Git git = initGit()) { resetRelevantChanges( git ); - reloadFromDisc( windowManager ); + reloadFromDisc(); } } - private static void resetRelevantChanges( Git git ) throws GitAPIException + private synchronized void resetRelevantChanges( Git git ) throws GitAPIException { // NB: More complicated than a simple reset --hard, because gui.xml, project.xml and dataset.xml.backup should remain untouched. List< DiffEntry > diffEntries = relevantChanges( git ); @@ -288,13 +286,13 @@ private static void resetRelevantChanges( Git git ) throws GitAPIException } } - private static Git initGit( WindowManager windowManager ) throws IOException + private synchronized Git initGit() throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); return initGit( projectRoot ); } - private static Git initGit( File projectRoot ) throws IOException + private synchronized Git initGit( File projectRoot ) throws IOException { boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); if ( !correctFolder ) @@ -305,12 +303,12 @@ private static Git initGit( File projectRoot ) throws IOException return Git.open( gitRoot ); } - private static boolean isClean( Git git ) throws GitAPIException + private synchronized boolean isClean( Git git ) throws GitAPIException { return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); } - private static TreeFilter relevantFilesFilter() + private synchronized TreeFilter relevantFilesFilter() { TreeFilter[] filters = { // only files in subdirectory "mastodon.project" diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index 95c805e6..d776edcc 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -29,8 +29,9 @@ package org.mastodon.mamut.tomancak.collaboration.commands; import java.io.File; +import java.util.function.BiConsumer; -import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.tomancak.collaboration.MastodonGitController; import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -38,13 +39,10 @@ public class MastodonGitCreateRepository implements Command { @Parameter - WindowManager windowManager; + BiConsumer< File, String > directoryAndUrlCallback; @Parameter( label = "Directory to contain the repository", style = "directory" ) - File parentDirectory; - - @Parameter( label = "Repository name" ) - String repositoryName; + File directory; @Parameter( label = "URL on github or gitlab" ) String repositoryURL; @@ -54,7 +52,7 @@ public void run() { try { - MastodonGitRepository.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + directoryAndUrlCallback.accept( directory, repositoryURL ); } catch ( Exception e ) { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index fdb3f7de..cf2ed867 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -9,7 +9,7 @@ public class MastodonGitNewBranch implements Command { @Parameter - private WindowManager windowManager; + private MastodonGitRepository repository; @Parameter( label = "Branch name", persist = false ) private String branchName; @@ -19,7 +19,7 @@ public void run() { try { - MastodonGitRepository.createNewBranch( windowManager, branchName ); + repository.createNewBranch( branchName ); } catch ( Exception e ) { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java index aa8835a1..b817f713 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -100,8 +100,11 @@ public void setAppPluginModel( MamutPluginAppModel appPluginModel ) appModel = appPluginModel.getAppModel(); windowManager = appPluginModel.getWindowManager(); actions.forEach( ( key, action ) -> action.setEnabled( appModel != null ) ); + initialize(); } + protected void initialize() {} + @Override public Map< String, String > getMenuTexts() { diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepositoryDemo.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepositoryDemo.java new file mode 100644 index 00000000..ddfd773c --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepositoryDemo.java @@ -0,0 +1,25 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import java.io.File; + +import org.scijava.Context; + +public class MastodonGitRepositoryDemo +{ + public static void main( String... args ) throws Exception + { + String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; + String repositoryName = "mgit-test"; + String repositoryURL = "git@github.com:maarzt/mgit-test.git"; + File parentDirectory = new File( "/home/arzt/tmp/" ); + +// Context context = new Context(); +// WindowManager windowManager = new WindowManager( context ); +// windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) ); +// MastodonGitUtils.createRepositoryAndUpload( windowManager, parentDirectory, repositoryName, repositoryURL ); + +// MastodonGitUtils.cloneRepository( repositoryURL, new File( parentDirectory, "2/" ) ); + + MastodonGitRepository.openProjectInRepository( new Context(), new File( parentDirectory, "2/" ) ); + } +} From abaa7a0b51db6b734c37c303c70f99d815798b7e Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 12:24:33 +0200 Subject: [PATCH 22/76] Mastodon Git: rename menu entries --- .../tomancak/collaboration/MastodonGitController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 3ae4786d..295c64fa 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -56,7 +56,7 @@ public class MastodonGitController extends BasicMamutPlugin public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ) .addActionDescription( "[mastodon git] create repository", - "Plugins > Git > Initialize > Create New Repository", + "Plugins > Git > Initialize > Share Project", "Upload Mastodon project to a newly created git repository.", MastodonGitController::createRepository ) .addActionDescription( "[mastodon git] clone repository", @@ -64,18 +64,18 @@ public class MastodonGitController extends BasicMamutPlugin "Clone a git repository to a new Mastodon project.", MastodonGitController::cloneGitRepository ) .addActionDescription( "[mastodon git] commit", - "Plugins > Git > Commit", + "Plugins > Git > Add Save Point (commit)", "Commit changes to the git repository.", MastodonGitController::commit ) .addActionDescription( "[mastodon git] push", - "Plugins > Git > Push", + "Plugins > Git > Upload Changes (push)", "Push changes to the git repository.", MastodonGitController::push ) .addActionDescription( "[mastodon git] pull", - "Plugins > Git > Pull", + "Plugins > Git > Download Changes (pull)", "Pull changes from the git repository.", MastodonGitController::pull ) - .addActionDescription( "[mastodon git] reset", + .addActionDescription( "[mastodon git] Undo Changes (reset)", "Plugins > Git > Reset", "Reset changes in the git repository.", MastodonGitController::reset ) From c4a0b474155d9965fd7556a29f0b3ce71831c6b1 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 14:58:05 +0200 Subject: [PATCH 23/76] Mastodon Git share project / clone project: add checkbox create new subdir --- .../commands/MastodonGitCloneRepository.java | 13 +++- .../commands/MastodonGitCreateRepository.java | 23 +++++- .../commands/NewDirectoryUtils.java | 77 +++++++++++++++++++ .../commands/NewDirectoryUtilsTest.java | 52 +++++++++++++ 4 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtilsTest.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index dfcbd701..a09d59f9 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -6,11 +6,15 @@ import org.scijava.Context; import org.scijava.command.Command; import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; // TODOs: // - warn if parentDirectory already exists // - warn if repositoryName already exists and the corresponding directory is not empty // - fill repositoryName with a default value based on the repositoryURL +@Plugin( type = Command.class, + label = "Mastodon Collaborative - Download Shared Project (clone)", + menuPath = "Plugins > Mastodon Collaborative > Download Shared Project" ) public class MastodonGitCloneRepository implements Command { @Parameter @@ -20,17 +24,17 @@ public class MastodonGitCloneRepository implements Command String repositoryURL; @Parameter( label = "Directory that will contain the cloned repository", style = "directory" ) - File parentDirectory; + File directory; - @Parameter( label = "Repository name" ) - String repositoryName; + @Parameter( label = "Create new subdirectory", required = false ) + boolean createSubdirectory = false; @Override public void run() { try { - File directory = new File( this.parentDirectory, repositoryName ); + directory = NewDirectoryUtils.createRepositoryDirectory( createSubdirectory, directory, repositoryURL ); MastodonGitRepository.cloneRepository( repositoryURL, directory ); MastodonGitRepository.openProjectInRepository( context, directory ); } @@ -39,4 +43,5 @@ public void run() e.printStackTrace(); } } + } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index d776edcc..c5adeb70 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -31,27 +31,42 @@ import java.io.File; import java.util.function.BiConsumer; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitController; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; +import org.scijava.ItemVisibility; import org.scijava.command.Command; import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +@Plugin( type = Command.class, + label = "Share Current Project via GitHub or GitLab", + visible = false ) public class MastodonGitCreateRepository implements Command { + @Parameter( visibility = ItemVisibility.MESSAGE ) + private String text = "" + + "

Share Project

" + + "

Share the current project on github or gitlab.

" + + "

Go to github.com or to ure institute's gitlab and create a new repository.

" + + "

Then copy the URL of the repository and paste it below.

" + + "

A copy of will be created in the directory you specify, and then uploaded to the specified URL.

"; + @Parameter BiConsumer< File, String > directoryAndUrlCallback; + @Parameter( label = "URL on github or gitlab" ) + String repositoryURL; + @Parameter( label = "Directory to contain the repository", style = "directory" ) File directory; - @Parameter( label = "URL on github or gitlab" ) - String repositoryURL; + @Parameter( label = "Create new subdirectory", required = false ) + boolean createSubdirectory = false; @Override public void run() { try { + directory = NewDirectoryUtils.createRepositoryDirectory( createSubdirectory, directory, repositoryURL ); directoryAndUrlCallback.accept( directory, repositoryURL ); } catch ( Exception e ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java new file mode 100644 index 00000000..63c26433 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java @@ -0,0 +1,77 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NewDirectoryUtils +{ + + /** + * If {@code newSubdirectory} is true, create a new subdirectory in the + * given {@code directory}. The name of the subdirectory is extracted from + * the {@code repositoryURL}. + *

+ * If {@code newSubdirectory} is false, return the {@code directory} as it is. + */ + static File createRepositoryDirectory( boolean newSubdirectory, File directory, String repositoryURL ) throws IOException + { + if ( !newSubdirectory ) + return directory; + + return createSubdirectory( directory, extractRepositoryName( repositoryURL ) ); + } + + static File createSubdirectory( File parentDirectory, String repositoryName ) throws IOException + { + File directory = new File( parentDirectory, repositoryName ); + if ( directory.isDirectory() ) + { + if ( directory.list().length > 0 ) + throw new IOException( "Directory already exists and is not empty: " + directory ); + else + return directory; + } + Files.createDirectory( directory.toPath() ); + return directory; + } + + static String extractRepositoryName( String repositoryURL ) + { + Pattern pattern = Pattern.compile( "/([\\w-]+)(\\.git|/)?$" ); + Matcher matcher = pattern.matcher( repositoryURL ); + if ( matcher.find() ) + return matcher.group( 1 ); + throw new IllegalArgumentException( "Could not extract repository name from URL:" + repositoryURL ); + } +} diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtilsTest.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtilsTest.java new file mode 100644 index 00000000..8aa12321 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtilsTest.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class NewDirectoryUtilsTest +{ + @Test + public void testExtractRepositoryName() + { + final String expected = "mastodon-tomancak"; + List< String > urls = Arrays.asList( + "https://github.com/mastodon-sc/mastodon-tomancak.git", + "https://github.com/mastodon-sc/mastodon-tomancak/", + "git@github.com:mastodon-sc/mastodon-tomancak.git" + ); + for ( String url : urls ) + assertEquals( expected, NewDirectoryUtils.extractRepositoryName( url ) ); + } +} From d14608b63b3d5005f070c23980304c97473d26fe Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 15:01:23 +0200 Subject: [PATCH 24/76] MastodonGitNewBranch: better title --- .../tomancak/collaboration/commands/MastodonGitNewBranch.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index cf2ed867..7e574f52 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -1,10 +1,11 @@ package org.mastodon.mamut.tomancak.collaboration.commands; -import org.mastodon.mamut.WindowManager; import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.command.Command; import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +@Plugin( type = Command.class, label = "Create New Branch", visible = false ) public class MastodonGitNewBranch implements Command { From c66506794f310f893db087c372bf45895eb4402c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 15:09:45 +0200 Subject: [PATCH 25/76] MastodonGit: fix title for the reset command --- .../mamut/tomancak/collaboration/MastodonGitController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 295c64fa..074ac2aa 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -75,8 +75,8 @@ public class MastodonGitController extends BasicMamutPlugin "Plugins > Git > Download Changes (pull)", "Pull changes from the git repository.", MastodonGitController::pull ) - .addActionDescription( "[mastodon git] Undo Changes (reset)", - "Plugins > Git > Reset", + .addActionDescription( "[mastodon git] git reset", + "Plugins > Git > Undo Changes (reset)", "Reset changes in the git repository.", MastodonGitController::reset ) .addActionDescription( "[mastodon git] new branch", From 323203e7324ab1be721c38b8f42c5776cff335d9 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 15:29:29 +0200 Subject: [PATCH 26/76] POM: remove duplicated dependency --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index 708dae03..2176683c 100644 --- a/pom.xml +++ b/pom.xml @@ -62,11 +62,6 @@ org.eclipse.jgit.ui 6.7.0.202309050840-r - - org.eclipse.jgit - org.eclipse.jgit.ui - 6.7.0.202309050840-r - junit From 13055ee56ac0b508d6b14814a159c8cbd0632590 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 2 Oct 2023 15:30:01 +0200 Subject: [PATCH 27/76] Mastodon Git: let the user know if credentials where wrong --- .../collaboration/credentials/PersistentCredentials.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java index 86e105e1..7ec39aad 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java @@ -25,7 +25,7 @@ private boolean missingCredentials() return password == null || username == null; } - private boolean queryPassword( String url ) + private boolean queryPassword( String url, boolean previousAuthenticationFailed ) { JTextField usernameField = new JTextField( 20 ); JPasswordField passwordField = new JPasswordField( 20 ); @@ -38,6 +38,8 @@ private boolean queryPassword( String url ) panel.add( usernameField, "wrap" ); panel.add( new JLabel( " password" ) ); panel.add( passwordField, "wrap" ); + if ( previousAuthenticationFailed ) + panel.add( new JLabel( "(Authentication failed. Please try again!)" ), "span, wrap" ); boolean ok = JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog( null, panel, "Authentication for Git Repository", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ); @@ -96,7 +98,7 @@ public boolean get( URIish uri, CredentialItem... items ) throws UnsupportedCred counter++; boolean previousAuthenticationFailed = counter > 1; if ( previousAuthenticationFailed || missingCredentials() ) - if ( !queryPassword( uri.toString() ) ) + if ( !queryPassword( uri.toString(), previousAuthenticationFailed ) ) return false; fillUsernameAndPassword( items ); return true; From d96f4ad57def2ac73b805e3860a8da2587d20305 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 11:52:44 +0200 Subject: [PATCH 28/76] Example on using SSH certificate in non standard location. --- .../CustomCredentialsProvider.java | 63 +++++++++++++++++++ .../GenerateKeysExample.java | 26 ++++++++ .../JGitSshAuthenticationExample.java | 41 ++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/CustomCredentialsProvider.java create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/GenerateKeysExample.java create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/JGitSshAuthenticationExample.java diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/CustomCredentialsProvider.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/CustomCredentialsProvider.java new file mode 100644 index 00000000..5889a6b6 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/CustomCredentialsProvider.java @@ -0,0 +1,63 @@ +package org.mastodon.mamut.tomancak.collaboration.sshauthentication; + +import java.util.Scanner; + +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** + * A {@link CredentialsProvider} that allows to answer yes/no questions + * interactively on the command line. + */ +public class CustomCredentialsProvider extends CredentialsProvider +{ + + @Override + public boolean isInteractive() + { + return true; + } + + @Override + public boolean supports( CredentialItem... items ) + { + return true; + } + + @Override + public boolean get( URIish uri, CredentialItem... items ) throws UnsupportedCredentialItem + { + boolean ok = true; + for ( CredentialItem item : items ) + ok &= processItem( item ); + if ( !ok ) + throw new UnsupportedOperationException(); + return ok; + } + + private boolean processItem( CredentialItem item ) + { + if ( item instanceof CredentialItem.InformationalMessage ) + return processInformalMessage( ( CredentialItem.InformationalMessage ) item ); + if ( item instanceof CredentialItem.YesNoType ) + return processYesNo( ( CredentialItem.YesNoType ) item ); + return false; + } + + private boolean processInformalMessage( CredentialItem.InformationalMessage item ) + { + System.out.println( item.getPromptText() ); + return true; + } + + private boolean processYesNo( CredentialItem.YesNoType item ) + { + System.out.println( item.getPromptText() + " (yes/no)" ); + String line = new Scanner( System.in ).nextLine(); + item.setValue( "yes".equals( line ) ); + return true; + } +} + diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/GenerateKeysExample.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/GenerateKeysExample.java new file mode 100644 index 00000000..a6e31cea --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/GenerateKeysExample.java @@ -0,0 +1,26 @@ +package org.mastodon.mamut.tomancak.collaboration.sshauthentication; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyEncryptionContext; +import org.apache.sshd.common.config.keys.writer.openssh.OpenSSHKeyPairResourceWriter; + +/** + * Example of how to generate a OpenSSH key pair and write it to stdout + * with the apache sshd library. + */ +public class GenerateKeysExample +{ + + public static void main( String[] args ) throws GeneralSecurityException, IOException + { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance( "RSA" ); + keyGen.initialize( 4 * 1024 ); + KeyPair keyPair = keyGen.genKeyPair(); + OpenSSHKeyPairResourceWriter.INSTANCE.writePrivateKey( keyPair, "user@example", new OpenSSHKeyEncryptionContext(), System.out ); + OpenSSHKeyPairResourceWriter.INSTANCE.writePublicKey( keyPair, "user@example", System.out ); + } +} diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/JGitSshAuthenticationExample.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/JGitSshAuthenticationExample.java new file mode 100644 index 00000000..ef4e652f --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/sshauthentication/JGitSshAuthenticationExample.java @@ -0,0 +1,41 @@ +package org.mastodon.mamut.tomancak.collaboration.sshauthentication; + +import java.io.File; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.SshTransport; +import org.eclipse.jgit.transport.sshd.JGitKeyCache; +import org.eclipse.jgit.transport.sshd.SshdSessionFactoryBuilder; + +/** + * Example of how to use JGit with a custom SSH key. + */ +public class JGitSshAuthenticationExample +{ + public static void main( String... args ) throws Exception + { + String projectPath = "/home/arzt/devel/mastodon/mastodon/src/test/resources/org/mastodon/mamut/examples/tiny/tiny-project.mastodon"; + String repositoryName = "mgit-test"; + String repositoryURL = "git@github.com:masgitoff/mastodon-test-dataset.git"; + File parentDirectory = new File( "/home/arzt/tmp/" ); + + SshSessionFactory sshSessionFactory = new SshdSessionFactoryBuilder() + .setPreferredAuthentications( "publickey" ) + .setHomeDirectory( new File( "/home/arzt/" ) ) + .setSshDirectory( new File( "/home/arzt/ssh-experiment" ) ) + .build( new JGitKeyCache() ); + try (Git git = Git.cloneRepository() + .setURI( repositoryURL ) + .setDirectory( new File( parentDirectory, "xyz" ) ) + .setCredentialsProvider( new CustomCredentialsProvider() ) + .setTransportConfigCallback( transport -> ( ( SshTransport ) transport ).setSshSessionFactory( sshSessionFactory ) ) + .call()) + { + git.push() + .setTransportConfigCallback( transport -> ( ( SshTransport ) transport ).setSshSessionFactory( sshSessionFactory ) ) + .setCredentialsProvider( new CustomCredentialsProvider() ) + .call(); + } + } +} From 9367de4fe72502fc8247bc3f8b912a2e307746f7 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 12:19:49 +0200 Subject: [PATCH 29/76] MastodonGitCloneRepository: Add descriptions --- .../commands/MastodonGitCloneRepository.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index a09d59f9..52feff6a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -20,15 +20,34 @@ public class MastodonGitCloneRepository implements Command @Parameter Context context; - @Parameter( label = "URL on github or gitlab" ) + @Parameter( label = "URL on github or gitlab", description = URL_DESCRIPTION ) String repositoryURL; - @Parameter( label = "Directory that will contain the cloned repository", style = "directory" ) + private static final String URL_DESCRIPTION = "" + + "Here are two examples of valid URLs:
" + + "

" + + ""; + + @Parameter( label = "Directory, to store the project:", style = "directory", description = DIRECTORY_DESCRIPTION ) File directory; - @Parameter( label = "Create new subdirectory", required = false ) + private static final String DIRECTORY_DESCRIPTION = "" + + "A copy of the shared project will be downloaded to your computer.
" + + "Please select a directory where to store it.
" + + "The directory should be empty, or select \"Create new subdirectory\"." + + ""; + + @Parameter( label = "Create new subdirectory", required = false, description = CREATE_SUBDIRECTORY_DESCRIPTION ) boolean createSubdirectory = false; + private static final String CREATE_SUBDIRECTORY_DESCRIPTION = "" + + "If selected, a new subdirectory will be created in the selected directory.
" + + "The name of the subdirectory will be the name of the repository." + + ""; + @Override public void run() { From afd272ffee6dca399ea47792250843d8a227375f Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 12:32:50 +0200 Subject: [PATCH 30/76] Mastodon Git: fix merge for projects where the image data is not available --- .../mamut/tomancak/collaboration/MastodonGitRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index a0717b1d..c146d3fe 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -219,7 +219,7 @@ public synchronized void mergeBranch( String selectedBranch ) throws Exception Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( currentBranch ).call(); git.merge().setCommit( false ).include( git.getRepository().exactRef( selectedBranch ) ).call(); // TODO selected branch, should not be a string but a ref instead - windowManager.getProjectManager().open( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); + windowManager.getProjectManager().openWithDialog( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet( windowManager.getAppModel().getModel() ); double distCutoff = 1000; double mahalanobisDistCutoff = 1; From 14f713fdbfd67e77a00b6479ba13f9dd2d21110b Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 12:35:30 +0200 Subject: [PATCH 31/76] MastodonGitRepository: small refactoring --- .../collaboration/MastodonGitRepository.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index c146d3fe..55669114 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -219,17 +219,22 @@ public synchronized void mergeBranch( String selectedBranch ) throws Exception Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( currentBranch ).call(); git.merge().setCommit( false ).include( git.getRepository().exactRef( selectedBranch ) ).call(); // TODO selected branch, should not be a string but a ref instead - windowManager.getProjectManager().openWithDialog( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); - final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet( windowManager.getAppModel().getModel() ); - double distCutoff = 1000; - double mahalanobisDistCutoff = 1; - double ratioThreshold = 2; - MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); + mergeTwoProjectsAndOpenTheResult( dsA, dsB ); windowManager.getProjectManager().saveProject( projectRoot ); commit(); } } + private void mergeTwoProjectsAndOpenTheResult( Dataset dsA, Dataset dsB ) throws IOException, SpimDataException + { + windowManager.getProjectManager().openWithDialog( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); + final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet( windowManager.getAppModel().getModel() ); + double distCutoff = 1000; + double mahalanobisDistCutoff = 1; + double ratioThreshold = 2; + MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); + } + public synchronized void pull() throws Exception { try (Git git = initGit()) From ac351e2bb93a1459f16155164a61398bd2162fce Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 17:11:15 +0200 Subject: [PATCH 32/76] MastodonGitController: make switch branch also perform a git fetch This is necessary to have a complete up to date list of remote branches. --- .../tomancak/collaboration/MastodonGitController.java | 11 ++++++++++- .../tomancak/collaboration/MastodonGitRepository.java | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 074ac2aa..06fccc96 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -139,10 +139,19 @@ private void switchBranch() try { // TODO: the branches are not formatted nicely + String message = "Select a branch"; + try + { + repository.fetchAll(); + } + catch ( Exception e ) + { + message += " \n(The was a failure downloading the latest branch changes.)"; + } List< String > branches = repository.getBranches(); String currentBranch = repository.getCurrentBranch(); // show JOptionPane that allows to select a branch - String selectedBranch = ( String ) JOptionPane.showInputDialog( null, "Select a branch", "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); + String selectedBranch = ( String ) JOptionPane.showInputDialog( null, message, "Switch Git Branch", JOptionPane.PLAIN_MESSAGE, null, branches.toArray(), currentBranch ); if ( selectedBranch == null ) return; // switch to selected branch diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 55669114..a5fe4c56 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -196,6 +196,14 @@ public synchronized List< String > getBranches() throws Exception } } + public synchronized void fetchAll() throws Exception + { + try (Git git = initGit()) + { + git.fetch().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); + } + } + public synchronized String getCurrentBranch() throws Exception { try (Git git = initGit()) From ab3d7c3e3346cd5c549d7fe78f6aede6fcf86394 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 6 Oct 2023 17:12:30 +0200 Subject: [PATCH 33/76] MastodonGitRepository: fix exception message --- .../mamut/tomancak/collaboration/MastodonGitRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index a5fe4c56..918157ac 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -250,7 +250,7 @@ public synchronized void pull() throws Exception windowManager.getProjectManager().saveProject(); boolean isClean = isClean( git ); if ( !isClean ) - throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before.setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) pulling." ); + throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); git.fetch().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); int aheadCount = BranchTrackingStatus.of( git.getRepository(), git.getRepository().getBranch() ).getAheadCount(); if ( aheadCount > 0 ) From c9d1a920ff2f634a6f08b9829d4a76af1301779c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 10 Oct 2023 16:42:31 +0200 Subject: [PATCH 34/76] Mastodon Git: allow to specify author, email and message used in commits --- .../collaboration/MastodonGitController.java | 13 ++++- .../collaboration/MastodonGitRepository.java | 26 ++++++--- .../MastodonGitSettingsService.java | 55 +++++++++++++++++++ .../commands/MastodonGitCommitCommand.java | 30 ++++++++++ .../commands/MastodonGitSetAuthorCommand.java | 43 +++++++++++++++ 5 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 06fccc96..5fa0a245 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -36,8 +36,10 @@ import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCommitCommand; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitNewBranch; +import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitSetAuthorCommand; import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; @@ -63,6 +65,10 @@ public class MastodonGitController extends BasicMamutPlugin "Plugins > Git > Initialize > Clone Existing Repository", "Clone a git repository to a new Mastodon project.", MastodonGitController::cloneGitRepository ) + .addActionDescription( "[mastodon git] set author", + "Plugins > Git > Initialize > Set Author Name", + "Set the author name that is used for your commits.", + MastodonGitController::setAuthor ) .addActionDescription( "[mastodon git] commit", "Plugins > Git > Add Save Point (commit)", "Commit changes to the git repository.", @@ -106,6 +112,11 @@ protected void initialize() repository = new MastodonGitRepository( getWindowManager() ); } + private void setAuthor() + { + commandService.run( MastodonGitSetAuthorCommand.class, true ); + } + private void createRepository() { BiConsumer< File, String > directoryAndUrlCallback = ( directory, url ) -> { @@ -121,7 +132,7 @@ private void cloneGitRepository() private void commit() { - run( () -> repository.commit() ); + commandService.run( MastodonGitCommitCommand.class, true, "repository", repository ); } private void push() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 918157ac..a778b7dd 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.CreateBranchCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand; @@ -67,9 +68,12 @@ public class MastodonGitRepository private final WindowManager windowManager; + private final MastodonGitSettingsService settingsService; + public MastodonGitRepository( WindowManager windowManager ) { this.windowManager = windowManager; + settingsService = windowManager.getContext().service( MastodonGitSettingsService.class ); } public static MastodonGitRepository createRepositoryAndUpload( @@ -118,21 +122,25 @@ public static void openProjectInRepository( Context context, File directory ) th new MainWindow( windowManager ).setVisible( true ); } - public synchronized void commit() throws Exception + public synchronized void commit( String message ) throws Exception { try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); List< DiffEntry > changedFiles = relevantChanges( git ); - if ( !changedFiles.isEmpty() ) + if ( changedFiles.isEmpty() ) + return; + + for ( DiffEntry diffEntry : changedFiles ) { - for ( DiffEntry diffEntry : changedFiles ) - { - git.add().addFilepattern( diffEntry.getOldPath() ).call(); - git.add().addFilepattern( diffEntry.getNewPath() ).call(); - } - git.commit().setMessage( "Commit from Mastodon" ).call(); + git.add().addFilepattern( diffEntry.getOldPath() ).call(); + git.add().addFilepattern( diffEntry.getNewPath() ).call(); } + + CommitCommand commit = git.commit(); + commit.setMessage( message ); + commit.setAuthor( settingsService.getPersonIdent() ); + commit.call(); } } @@ -229,7 +237,7 @@ public synchronized void mergeBranch( String selectedBranch ) throws Exception git.merge().setCommit( false ).include( git.getRepository().exactRef( selectedBranch ) ).call(); // TODO selected branch, should not be a string but a ref instead mergeTwoProjectsAndOpenTheResult( dsA, dsB ); windowManager.getProjectManager().saveProject( projectRoot ); - commit(); + commit( "Merge commit generated with Mastodon" ); } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java new file mode 100644 index 00000000..99846b69 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java @@ -0,0 +1,55 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import org.eclipse.jgit.lib.PersonIdent; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.prefs.PrefService; +import org.scijava.service.AbstractService; +import org.scijava.service.SciJavaService; + +@Plugin( type = SciJavaService.class ) +public class MastodonGitSettingsService extends AbstractService +{ + + @Parameter + private PrefService prefService; + + private String authorName; + + private String authorEmail; + + @Override + public void initialize() + { + super.initialize(); + authorName = prefService.get( MastodonGitSettingsService.class, "author.name", null ); + authorEmail = prefService.get( MastodonGitSettingsService.class, "author.email", null ); + } + + public void setAuthorName( String name ) + { + this.authorName = name; + prefService.put( MastodonGitSettingsService.class, "author.name", name ); + } + + public void setAuthorEmail( String email ) + { + this.authorEmail = email; + prefService.put( MastodonGitSettingsService.class, "author.email", email ); + } + + public String getAuthorName() + { + return authorName; + } + + public String getAuthorEmail() + { + return authorEmail; + } + + public PersonIdent getPersonIdent() + { + return new PersonIdent( authorName, authorEmail ); + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java new file mode 100644 index 00000000..1fed5850 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java @@ -0,0 +1,30 @@ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; + +@Plugin( type = Command.class, label = "Add Save Point (commit)", visible = false ) +public class MastodonGitCommitCommand implements Command +{ + @Parameter + private MastodonGitRepository repository; + + @Parameter( label = "Commit message", style = "textarea", persist = false, description = "A short description of the changes." ) + private String commitMessage; + + @Override + public void run() + { + try + { + repository.commit( commitMessage ); + } + catch ( Exception e ) + { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java new file mode 100644 index 00000000..4c63cf63 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java @@ -0,0 +1,43 @@ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import org.mastodon.mamut.tomancak.collaboration.MastodonGitSettingsService; +import org.scijava.Initializable; +import org.scijava.ItemVisibility; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +@Plugin( type = Command.class, label = "Set Author Name", visible = false ) +public class MastodonGitSetAuthorCommand implements Command, Initializable +{ + @Parameter + private MastodonGitSettingsService settings; + + @Parameter( visibility = ItemVisibility.MESSAGE ) + private String description = "" + + "The name and email that you specify below
" + + "are used to identify you as the author of the
" + + "changes you make to the shared project.

" + + "Name and email are likely to become publicly visible on the internet.
" + + "You may use a nickname and dummy email address if you wish."; + + @Parameter( label = "Author name", persist = false ) + private String authorName; + + @Parameter( label = "Author email", persist = false ) + private String authorEmail = "noreply@example.com"; + + @Override + public void initialize() + { + authorName = settings.getAuthorName(); + authorEmail = settings.getAuthorEmail(); + } + + @Override + public void run() + { + settings.setAuthorName( authorName ); + settings.setAuthorEmail( authorEmail ); + } +} From 884f43c46c9351878e7905b91e6b3e272d5615ec Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 10 Oct 2023 16:52:46 +0200 Subject: [PATCH 35/76] Mastodon Git: add a cancel button to the commands --- .../commands/AbstractCancellable.java | 30 +++++++++++++++++++ .../commands/MastodonGitCloneRepository.java | 2 +- .../commands/MastodonGitCommitCommand.java | 2 +- .../commands/MastodonGitCreateRepository.java | 2 +- .../commands/MastodonGitNewBranch.java | 2 +- .../commands/MastodonGitSetAuthorCommand.java | 2 +- 6 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/AbstractCancellable.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/AbstractCancellable.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/AbstractCancellable.java new file mode 100644 index 00000000..92af522a --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/AbstractCancellable.java @@ -0,0 +1,30 @@ +package org.mastodon.mamut.tomancak.collaboration.commands; + +import org.scijava.Cancelable; + +public class AbstractCancellable implements Cancelable +{ + + private boolean canceled = false; + + private String reason = null; + + @Override + public boolean isCanceled() + { + return canceled; + } + + @Override + public void cancel( String reason ) + { + this.canceled = true; + this.reason = reason; + } + + @Override + public String getCancelReason() + { + return reason; + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 52feff6a..6e47d92d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -15,7 +15,7 @@ @Plugin( type = Command.class, label = "Mastodon Collaborative - Download Shared Project (clone)", menuPath = "Plugins > Mastodon Collaborative > Download Shared Project" ) -public class MastodonGitCloneRepository implements Command +public class MastodonGitCloneRepository extends AbstractCancellable implements Command { @Parameter Context context; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java index 1fed5850..2e05e877 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java @@ -7,7 +7,7 @@ import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; @Plugin( type = Command.class, label = "Add Save Point (commit)", visible = false ) -public class MastodonGitCommitCommand implements Command +public class MastodonGitCommitCommand extends AbstractCancellable implements Command { @Parameter private MastodonGitRepository repository; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index c5adeb70..d9ae5a37 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -39,7 +39,7 @@ @Plugin( type = Command.class, label = "Share Current Project via GitHub or GitLab", visible = false ) -public class MastodonGitCreateRepository implements Command +public class MastodonGitCreateRepository extends AbstractCancellable implements Command { @Parameter( visibility = ItemVisibility.MESSAGE ) private String text = "" diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index 7e574f52..9b29940f 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -6,7 +6,7 @@ import org.scijava.plugin.Plugin; @Plugin( type = Command.class, label = "Create New Branch", visible = false ) -public class MastodonGitNewBranch implements Command +public class MastodonGitNewBranch extends AbstractCancellable implements Command { @Parameter diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java index 4c63cf63..c9efd687 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java @@ -8,7 +8,7 @@ import org.scijava.plugin.Plugin; @Plugin( type = Command.class, label = "Set Author Name", visible = false ) -public class MastodonGitSetAuthorCommand implements Command, Initializable +public class MastodonGitSetAuthorCommand extends AbstractCancellable implements Command, Initializable { @Parameter private MastodonGitSettingsService settings; From bc270806205dee613694dca7e115cd7b1a9cbe7a Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 10 Oct 2023 16:53:19 +0200 Subject: [PATCH 36/76] Mastodon Git: remove dead code --- .../mamut/tomancak/collaboration/MastodonGitController.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 5fa0a245..cfb991c5 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -217,10 +217,6 @@ private void run( RunnableWithException action ) } ).start(); } - public void createRepositoryAndUpload( File directory, String repositoryURL ) - { - } - interface RunnableWithException { void run() throws Exception; From 24daad79fcf2fe460eef20693123956f44bd6904 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 17 Oct 2023 15:47:48 +0200 Subject: [PATCH 37/76] Mastodon Git: ask for author name --- .../collaboration/MastodonGitController.java | 28 +++++++++++++++++++ .../MastodonGitSettingsService.java | 5 ++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index cfb991c5..59f7afdd 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -56,6 +56,9 @@ public class MastodonGitController extends BasicMamutPlugin @Parameter private CommandService commandService; + @Parameter + private MastodonGitSettingsService settingsService; + public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ) .addActionDescription( "[mastodon git] create repository", "Plugins > Git > Initialize > Share Project", @@ -119,6 +122,11 @@ private void setAuthor() private void createRepository() { + if ( !settingsService.isAuthorSpecified() ) + { + askForAuthorName( "Please set your author name before sharing a project." ); + return; + } BiConsumer< File, String > directoryAndUrlCallback = ( directory, url ) -> { run( () -> this.repository = MastodonGitRepository.createRepositoryAndUpload( getWindowManager(), directory, url ) ); }; @@ -132,6 +140,11 @@ private void cloneGitRepository() private void commit() { + if ( !settingsService.isAuthorSpecified() ) + { + askForAuthorName( "Please set your author name before your first commit." ); + return; + } commandService.run( MastodonGitCommitCommand.class, true, "repository", repository ); } @@ -176,6 +189,11 @@ private void switchBranch() private void mergeBranch() { + if ( !settingsService.isAuthorSpecified() ) + { + askForAuthorName( "You need to set your author name before you can merge branches." ); + return; + } try { List< String > branches = repository.getBranches(); @@ -203,6 +221,16 @@ private void reset() run( () -> repository.reset() ); } + private void askForAuthorName( String message ) + { + String title = "Set Author Name"; + String[] options = { "Set Author Name", "Cancel" }; + int result = JOptionPane.showOptionDialog( null, message, title, JOptionPane.YES_NO_OPTION, + JOptionPane.PLAIN_MESSAGE, null, options, options[ 0 ] ); + if ( result == JOptionPane.YES_OPTION ) + setAuthor(); + } + private void run( RunnableWithException action ) { new Thread( () -> { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java index 99846b69..83bc3cd1 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java @@ -26,6 +26,11 @@ public void initialize() authorEmail = prefService.get( MastodonGitSettingsService.class, "author.email", null ); } + public boolean isAuthorSpecified() + { + return authorName != null && authorEmail != null; + } + public void setAuthorName( String name ) { this.authorName = name; From ea80b2803b009307219f72de5e830ab708291c44 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 17 Oct 2023 16:10:03 +0200 Subject: [PATCH 38/76] Mastodon Git: disable git related commands if not in a git repository --- .../collaboration/MastodonGitController.java | 118 ++++++++++++------ .../collaboration/MastodonGitRepository.java | 12 ++ .../commands/MastodonGitCreateRepository.java | 10 +- .../utils/ActionDescriptions.java | 6 +- .../collaboration/utils/BasicMamutPlugin.java | 5 + 5 files changed, 106 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 59f7afdd..4b85adee 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -29,8 +29,8 @@ package org.mastodon.mamut.tomancak.collaboration; import java.io.File; +import java.util.Arrays; import java.util.List; -import java.util.function.BiConsumer; import javax.swing.JOptionPane; @@ -59,47 +59,79 @@ public class MastodonGitController extends BasicMamutPlugin @Parameter private MastodonGitSettingsService settingsService; - public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ) - .addActionDescription( "[mastodon git] create repository", - "Plugins > Git > Initialize > Share Project", - "Upload Mastodon project to a newly created git repository.", - MastodonGitController::createRepository ) - .addActionDescription( "[mastodon git] clone repository", - "Plugins > Git > Initialize > Clone Existing Repository", - "Clone a git repository to a new Mastodon project.", - MastodonGitController::cloneGitRepository ) - .addActionDescription( "[mastodon git] set author", - "Plugins > Git > Initialize > Set Author Name", - "Set the author name that is used for your commits.", - MastodonGitController::setAuthor ) - .addActionDescription( "[mastodon git] commit", - "Plugins > Git > Add Save Point (commit)", - "Commit changes to the git repository.", - MastodonGitController::commit ) - .addActionDescription( "[mastodon git] push", - "Plugins > Git > Upload Changes (push)", - "Push changes to the git repository.", - MastodonGitController::push ) - .addActionDescription( "[mastodon git] pull", + public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ); + + private static final String SHARE_PROJECT_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] create repository", + "Plugins > Git > Initialize > Share Project", + "Upload Mastodon project to a newly created git repository.", + MastodonGitController::createRepository ); + + private static final String CLONE_REPOSITORY_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] clone repository", + "Plugins > Git > Initialize > Clone Existing Repository", + "Clone a git repository to a new Mastodon project.", + MastodonGitController::cloneGitRepository ); + + private static final String SET_AUTHOR_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] set author", + "Plugins > Git > Initialize > Set Author Name", + "Set the author name that is used for your commits.", + MastodonGitController::setAuthor ); + + private static final String COMMIT_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] commit", + "Plugins > Git > Add Save Point (commit)", + "Commit changes to the git repository.", + MastodonGitController::commit ); + + private static final String PUSH_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] push", + "Plugins > Git > Upload Changes (push)", + "Push changes to the git repository.", + MastodonGitController::push ); + + private static final String PULL_ACTION_KEY = actionDescriptions. + addActionDescription( + "[mastodon git] pull", "Plugins > Git > Download Changes (pull)", "Pull changes from the git repository.", - MastodonGitController::pull ) - .addActionDescription( "[mastodon git] git reset", + MastodonGitController::pull ); + + private static final String RESET_ACTION_KEY = actionDescriptions. + addActionDescription( + "[mastodon git] git reset", "Plugins > Git > Undo Changes (reset)", "Reset changes in the git repository.", - MastodonGitController::reset ) - .addActionDescription( "[mastodon git] new branch", + MastodonGitController::reset ); + + private static final String NEW_BRANCH_ACTION_KEY = actionDescriptions. + addActionDescription( + "[mastodon git] new branch", "Plugins > Git > Branches > Create New Branch", "Create a new branch in the git repository.", - MastodonGitController::newBranch ) - .addActionDescription( "[mastodon git] switch branch", - "Plugins > Git > Branches > Switch Branch", - "Switch to a different branch in the git repository.", - MastodonGitController::switchBranch ) - .addActionDescription( "[mastodon git] merge branch", - "Plugins > Git > Branches > Merge Branch", - "Merge a branch into the current branch.", - MastodonGitController::mergeBranch ); + MastodonGitController::newBranch ); + + private static final String SWITCH_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] switch branch", + "Plugins > Git > Branches > Switch Branch", + "Switch to a different branch in the git repository.", + MastodonGitController::switchBranch ); + + private static final String MERGE_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] merge branch", + "Plugins > Git > Branches > Merge Branch", + "Merge a branch into the current branch.", + MastodonGitController::mergeBranch ); + + private static final List< String > IN_REPOSITORY_ACTIONS = Arrays.asList( + COMMIT_ACTION_KEY, + PUSH_ACTION_KEY, + PULL_ACTION_KEY, + RESET_ACTION_KEY, + NEW_BRANCH_ACTION_KEY, + SWITCH_ACTION_KEY, + MERGE_ACTION_KEY ); private MastodonGitRepository repository; @@ -113,6 +145,7 @@ protected void initialize() { super.initialize(); repository = new MastodonGitRepository( getWindowManager() ); + updateEnableCommands(); } private void setAuthor() @@ -127,10 +160,17 @@ private void createRepository() askForAuthorName( "Please set your author name before sharing a project." ); return; } - BiConsumer< File, String > directoryAndUrlCallback = ( directory, url ) -> { - run( () -> this.repository = MastodonGitRepository.createRepositoryAndUpload( getWindowManager(), directory, url ) ); + MastodonGitCreateRepository.Callback callback = ( File directory, String url ) -> { + this.repository = MastodonGitRepository.createRepositoryAndUpload( getWindowManager(), directory, url ); + updateEnableCommands(); }; - commandService.run( MastodonGitCreateRepository.class, true, "directoryAndUrlCallback", directoryAndUrlCallback ); + commandService.run( MastodonGitCreateRepository.class, true, "callback", callback ); + } + + private void updateEnableCommands() + { + boolean isRepository = repository.isRepository(); + IN_REPOSITORY_ACTIONS.forEach( action -> setActionEnabled( action, isRepository ) ); } private void cloneGitRepository() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index a778b7dd..59d0b2a8 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -341,4 +341,16 @@ private synchronized TreeFilter relevantFilesFilter() }; return AndTreeFilter.create( filters ); } + + public boolean isRepository() + { + try (Git ignored = initGit()) + { + return true; + } + catch ( Exception e ) + { + return false; + } + } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index d9ae5a37..381ac365 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -29,7 +29,6 @@ package org.mastodon.mamut.tomancak.collaboration.commands; import java.io.File; -import java.util.function.BiConsumer; import org.scijava.ItemVisibility; import org.scijava.command.Command; @@ -50,7 +49,7 @@ public class MastodonGitCreateRepository extends AbstractCancellable implements + "

A copy of will be created in the directory you specify, and then uploaded to the specified URL.

"; @Parameter - BiConsumer< File, String > directoryAndUrlCallback; + Callback callback; @Parameter( label = "URL on github or gitlab" ) String repositoryURL; @@ -67,11 +66,16 @@ public void run() try { directory = NewDirectoryUtils.createRepositoryDirectory( createSubdirectory, directory, repositoryURL ); - directoryAndUrlCallback.accept( directory, repositoryURL ); + callback.run( directory, repositoryURL ); } catch ( Exception e ) { e.printStackTrace(); } } + + public interface Callback + { + void run( File directory, String repositoryURL ) throws Exception; + } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java index a43ccab8..9f46652c 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ActionDescriptions.java @@ -49,15 +49,15 @@ public ActionDescriptions( Class< T > pluginClass ) this.pluginClass = pluginClass; } - public final ActionDescriptions< T > addActionDescription( String key, String menuText, String description, Consumer< T > action ) + public final String addActionDescription( String key, String menuText, String description, Consumer< T > action ) { return addActionDescription( key, menuText, description, action, "not mapped" ); } - public final ActionDescriptions< T > addActionDescription( String key, String menuText, String description, Consumer< T > action, String... keyStrokes ) + public final String addActionDescription( String key, String menuText, String description, Consumer< T > action, String... keyStrokes ) { entries.add( new Entry<>( key, menuText, keyStrokes, description, action ) ); - return this; + return key; } public Class< T > getPluginClass() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java index b817f713..cfd88780 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -78,6 +78,11 @@ public < T > BasicMamutPlugin( ActionDescriptions< T > description ) } } + public void setActionEnabled( String key, boolean enabled ) + { + actions.get( key ).setEnabled( enabled ); + } + private String extractMenuText( String menuEntry ) { // From menuEntry, extract the last part, which is the menu item name. From a21b71342798e8fd709e0b2d7e141c0ec514921f Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 17 Oct 2023 16:34:27 +0200 Subject: [PATCH 39/76] Mastodon Git: fix typo --- .../mamut/tomancak/collaboration/MastodonGitController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 4b85adee..1e09d2b2 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -210,7 +210,7 @@ private void switchBranch() } catch ( Exception e ) { - message += " \n(The was a failure downloading the latest branch changes.)"; + message += " \n(There was a failure downloading the latest branch changes.)"; } List< String > branches = repository.getBranches(); String currentBranch = repository.getCurrentBranch(); From e9ec343b8da3b99ac21e8ebbb694bb2d836b0cb5 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 17 Oct 2023 17:00:22 +0200 Subject: [PATCH 40/76] Mastodon Git: don't initialize repository when sharing a project It is very easy to initialize a repository when using github or gitlab. And is impossible to go back to the uninitialized state. So it is better for the "share project" command to assume that the github / gitlab repo was already created. That way we also don't need to decide whether to call the default branch "main" or "master". --- .../collaboration/MastodonGitController.java | 8 ++++---- .../collaboration/MastodonGitRepository.java | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 1e09d2b2..68ec4f27 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -62,10 +62,10 @@ public class MastodonGitController extends BasicMamutPlugin public static final ActionDescriptions< MastodonGitController > actionDescriptions = new ActionDescriptions<>( MastodonGitController.class ); private static final String SHARE_PROJECT_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] create repository", + "[mastodon git] share project", "Plugins > Git > Initialize > Share Project", "Upload Mastodon project to a newly created git repository.", - MastodonGitController::createRepository ); + MastodonGitController::shareProject ); private static final String CLONE_REPOSITORY_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] clone repository", @@ -153,7 +153,7 @@ private void setAuthor() commandService.run( MastodonGitSetAuthorCommand.class, true ); } - private void createRepository() + private void shareProject() { if ( !settingsService.isAuthorSpecified() ) { @@ -161,7 +161,7 @@ private void createRepository() return; } MastodonGitCreateRepository.Callback callback = ( File directory, String url ) -> { - this.repository = MastodonGitRepository.createRepositoryAndUpload( getWindowManager(), directory, url ); + this.repository = MastodonGitRepository.shareProject( getWindowManager(), directory, url ); updateEnableCommands(); }; commandService.run( MastodonGitCreateRepository.class, true, "callback", callback ); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 59d0b2a8..d315c058 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -44,7 +44,6 @@ import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.BranchTrackingStatus; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -76,7 +75,7 @@ public MastodonGitRepository( WindowManager windowManager ) settingsService = windowManager.getContext().service( MastodonGitSettingsService.class ); } - public static MastodonGitRepository createRepositoryAndUpload( + public static MastodonGitRepository shareProject( WindowManager windowManager, File directory, String repositoryURL ) @@ -86,13 +85,18 @@ public static MastodonGitRepository createRepositoryAndUpload( throw new IllegalArgumentException( "Not a directory: " + directory ); if ( !isEmpty( directory ) ) throw new IllegalArgumentException( "Directory not empty: " + directory ); - Git git = Git.init().setDirectory( directory ).call(); + Git git = Git.cloneRepository() + .setURI( repositoryURL ) + .setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) + .setDirectory( directory ) + .call(); Path mastodonProjectPath = directory.toPath().resolve( "mastodon.project" ); + if ( Files.exists( mastodonProjectPath ) ) + throw new RuntimeException( "The repository already contains a shared mastodon project: " + repositoryURL ); Files.createDirectory( mastodonProjectPath ); windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); git.add().addFilepattern( "mastodon.project" ).call(); - git.commit().setMessage( "Initial commit" ).call(); - git.remoteAdd().setName( "origin" ).setUri( new URIish( repositoryURL ) ).call(); + git.commit().setMessage( "Share mastodon project" ).call(); git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); git.close(); return new MastodonGitRepository( windowManager ); @@ -104,12 +108,12 @@ private static boolean isEmpty( File directory ) return containedFiles == null || containedFiles.length == 0; } - public static void cloneRepository( String repositoryURL, File parentDirectory ) throws Exception + public static void cloneRepository( String repositoryURL, File directory ) throws Exception { Git git = Git.cloneRepository() .setURI( repositoryURL ) .setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) - .setDirectory( parentDirectory ) + .setDirectory( directory ) .call(); git.close(); } From ed8aaa1603a70a9724f4d22b6411911f898d099c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 17 Oct 2023 17:19:51 +0200 Subject: [PATCH 41/76] Mastodon Git: use text area in the commit command --- .../collaboration/commands/MastodonGitCommitCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java index 2e05e877..cb508a97 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java @@ -12,7 +12,7 @@ public class MastodonGitCommitCommand extends AbstractCancellable implements Com @Parameter private MastodonGitRepository repository; - @Parameter( label = "Commit message", style = "textarea", persist = false, description = "A short description of the changes." ) + @Parameter( label = "Commit message", style = "text area", persist = false, description = "A short description of the changes." ) private String commitMessage; @Override From 66376ab53e3a7712bcff7e29e8ee894ba3be192c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 11:23:04 +0200 Subject: [PATCH 42/76] Mastodon Git: fix reset command, to undo changes on disc Previously the reset command would fail. If for example the user made some changes, and then saved them to disc. --- .../collaboration/MastodonGitRepository.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index d315c058..d854d354 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.eclipse.jgit.api.CheckoutCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.CreateBranchCommand; import org.eclipse.jgit.api.Git; @@ -287,28 +288,31 @@ public synchronized void reset() throws Exception } } - private synchronized void resetRelevantChanges( Git git ) throws GitAPIException + private synchronized void resetRelevantChanges( Git git ) throws Exception { // NB: More complicated than a simple reset --hard, because gui.xml, project.xml and dataset.xml.backup should remain untouched. List< DiffEntry > diffEntries = relevantChanges( git ); - ResetCommand resetCommand = git.reset().setMode( ResetCommand.ResetType.HARD ); + if ( diffEntries.isEmpty() ) + return; + + CheckoutCommand command = git.checkout(); for ( DiffEntry entry : diffEntries ) { switch ( entry.getChangeType() ) { case ADD: - resetCommand.addPath( entry.getNewPath() ); + command.addPath( entry.getNewPath() ); break; case DELETE: - resetCommand.addPath( entry.getOldPath() ); + command.addPath( entry.getOldPath() ); break; case MODIFY: - resetCommand.addPath( entry.getNewPath() ); - resetCommand.addPath( entry.getOldPath() ); + command.addPath( entry.getNewPath() ); + command.addPath( entry.getOldPath() ); break; } - resetCommand.call(); } + command.call(); } private synchronized Git initGit() throws IOException From 1e1dfd9d2b40602be3a0a567a555afcbfe506814 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 13:36:39 +0200 Subject: [PATCH 43/76] Mastodon Git: let the pull command perform an automatic merge if possible --- .../collaboration/MastodonGitController.java | 36 ++++++- .../collaboration/MastodonGitRepository.java | 94 +++++++++++++++++-- .../MergeConflictDuringPullException.java | 5 + .../collaboration/utils/ConflictUtils.java | 61 ++++++++++++ 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 68ec4f27..c4e4a3c9 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -33,6 +33,7 @@ import java.util.List; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; @@ -253,7 +254,40 @@ private void mergeBranch() private void pull() { - run( () -> repository.pull() ); + run( () -> { + try + { + repository.pull(); + } + catch ( MergeConflictDuringPullException e ) + { + SwingUtilities.invokeLater( () -> suggestPullAlternative() ); + } + } ); + } + + private void suggestPullAlternative() + { + // TODO: add pull alternative, save to new branch + String title = "Conflict During Pull"; + String message = "There was a merge conflict during the pull.\n" + + "You made changes on your computer that conflict with changes on the server.\n" + + "The conflicts could not be resolved automatically.\n" + + "You can either:\n" + + " 1. Throw away you local changes.\n" + + " 2. Cancel (And maybe save your local changes to a new branch,\n" + + " which you can then merge into the remote branch.)\n"; + + String[] options = { "Discard Local Changes", "Cancel" }; + int result = JOptionPane.showOptionDialog( null, message, title, JOptionPane.YES_NO_OPTION, + JOptionPane.PLAIN_MESSAGE, null, options, options[ 0 ] ); + if ( result == JOptionPane.YES_OPTION ) + resetToRemoteBranch(); + } + + private void resetToRemoteBranch() + { + run( () -> repository.resetToRemoteBranch() ); } private void reset() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index d854d354..85b5192c 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -43,16 +43,23 @@ import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.lib.BranchTrackingStatus; +import org.eclipse.jgit.lib.BranchConfig; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.mastodon.graph.io.RawGraphIO; import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; +import org.mastodon.mamut.feature.MamutRawFeatureModelIO; +import org.mastodon.mamut.model.Link; +import org.mastodon.mamut.model.Model; +import org.mastodon.mamut.model.Spot; import org.mastodon.mamut.project.MamutProject; import org.mastodon.mamut.project.MamutProjectIO; import org.mastodon.mamut.tomancak.collaboration.credentials.PersistentCredentials; +import org.mastodon.mamut.tomancak.collaboration.utils.ConflictUtils; import org.mastodon.mamut.tomancak.merging.Dataset; import org.mastodon.mamut.tomancak.merging.MergeDatasets; import org.scijava.Context; @@ -128,10 +135,15 @@ public static void openProjectInRepository( Context context, File directory ) th } public synchronized void commit( String message ) throws Exception + { + windowManager.getProjectManager().saveProject(); + commitWithoutSave( message ); + } + + private void commitWithoutSave( String message ) throws Exception { try (Git git = initGit()) { - windowManager.getProjectManager().saveProject(); List< DiffEntry > changedFiles = relevantChanges( git ); if ( changedFiles.isEmpty() ) return; @@ -258,21 +270,69 @@ private void mergeTwoProjectsAndOpenTheResult( Dataset dsA, Dataset dsB ) throws public synchronized void pull() throws Exception { + Context context = windowManager.getContext(); + MamutProject project = windowManager.getProjectManager().getProject(); + File projectRoot = project.getProjectRoot(); try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); boolean isClean = isClean( git ); if ( !isClean ) throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); - git.fetch().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); - int aheadCount = BranchTrackingStatus.of( git.getRepository(), git.getRepository().getBranch() ).getAheadCount(); - if ( aheadCount > 0 ) - throw new RuntimeException( "There are local changes. UNSUPPORTED operation. Cannot be done without merge." ); - git.pull().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).call(); + try + { + boolean conflict = !git.pull() + .setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) + .setRemote( "origin" ) + .setRebase( false ) + .call().isSuccessful(); + if ( conflict ) + automaticMerge( context, project, projectRoot, git ); + } + finally + { + abortMerge( git ); + } reloadFromDisc(); } } + private void automaticMerge( Context context, MamutProject project, File projectRoot, Git git ) throws Exception + { + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); + Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.THEIRS ).call(); + Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); + Model mergedModel = merge( dsA, dsB ); + if ( ConflictUtils.hasConflict( mergedModel ) ) + throw new MergeConflictDuringPullException(); + ConflictUtils.removeMergeConflictTagSets( mergedModel ); + saveModel( context, mergedModel, project ); + commitWithoutSave( "Automatic merge by Mastodon during pull" ); + } + + private static void saveModel( Context context, Model model, MamutProject project ) throws IOException + { + project.setProjectRoot( project.getProjectRoot() ); + try (final MamutProject.ProjectWriter writer = project.openForWriting()) + { + new MamutProjectIO().save( project, writer ); + final RawGraphIO.GraphToFileIdMap< Spot, Link > idmap = model.saveRaw( writer ); + MamutRawFeatureModelIO.serialize( context, model, idmap, writer ); + } + } + + private static Model merge( Dataset dsA, Dataset dsB ) throws IOException, SpimDataException + { + final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet(); + double distCutoff = 1000; + double mahalanobisDistCutoff = 1; + double ratioThreshold = 2; + MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); + return output.getModel(); + } + private synchronized void reloadFromDisc() throws IOException, SpimDataException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); @@ -361,4 +421,24 @@ public boolean isRepository() return false; } } + + private static void abortMerge( Git git ) throws IOException, GitAPIException + { + Repository repository = git.getRepository(); + repository.writeMergeCommitMsg( null ); + repository.writeMergeHeads( null ); + git.reset().setMode( ResetCommand.ResetType.HARD ).call(); + } + + public void resetToRemoteBranch() throws Exception + { + try (Git git = initGit()) + { + Repository repository = git.getRepository(); + String remoteTrackingBranch = new BranchConfig( repository.getConfig(), repository.getBranch() ).getRemoteTrackingBranch(); + git.reset().setMode( ResetCommand.ResetType.MIXED ).setRef( remoteTrackingBranch ).call(); + resetRelevantChanges( git ); + reloadFromDisc(); + } + } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java new file mode 100644 index 00000000..89b4b9f1 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java @@ -0,0 +1,5 @@ +package org.mastodon.mamut.tomancak.collaboration; + +public class MergeConflictDuringPullException extends RuntimeException +{ +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java new file mode 100644 index 00000000..383e3587 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java @@ -0,0 +1,61 @@ +package org.mastodon.mamut.tomancak.collaboration.utils; + +import java.util.List; + +import org.mastodon.mamut.model.Link; +import org.mastodon.mamut.model.Model; +import org.mastodon.mamut.model.Spot; +import org.mastodon.model.tag.TagSetModel; +import org.mastodon.model.tag.TagSetStructure; + +public class ConflictUtils +{ + private ConflictUtils() + { + // prevent from instantiation + } + + public static boolean hasConflict( Model model ) + { + TagSetModel< Spot, Link > tagSetModel = model.getTagSetModel(); + return !isTagSetEmpty( tagSetModel, "Merge Conflict", "Conflict" ) || + !isTagSetEmpty( tagSetModel, "Merge Conflict (Tags)", "Tag Conflict" ) || + !isTagSetEmpty( tagSetModel, "Merge Conflict (Labels)", "Label Conflict" ); + } + + public static void removeMergeConflictTagSets( Model model ) + { + TagSetModel< Spot, Link > tagSetModel = model.getTagSetModel(); + TagSetStructure original = tagSetModel.getTagSetStructure(); + TagSetStructure replacement = new TagSetStructure(); + replacement.set( original ); + for ( TagSetStructure.TagSet tagSet : original.getTagSets() ) + if ( isConflictTagSetName( tagSet.getName() ) ) + replacement.remove( tagSet ); + tagSetModel.setTagSetStructure( replacement ); + } + + private static boolean isConflictTagSetName( String name ) + { + return name.equals( "Merge Conflict" ) || + name.equals( "Merge Conflict (Tags)" ) || + name.equals( "Merge Conflict (Labels)" ) || + name.equals( "Merge Source A" ) || + name.equals( "Merge Source B" ) || + name.startsWith( "((A)) " ) || + name.startsWith( "((B)) " ); + } + + private static boolean isTagSetEmpty( TagSetModel< Spot, Link > tagSetModel, String tagSetName, String tagLabel ) + { + TagSetStructure tagSetStructure = tagSetModel.getTagSetStructure(); + List< TagSetStructure.TagSet > tagSets = tagSetStructure.getTagSets(); + TagSetStructure.TagSet tagSet = tagSets.stream().filter( ts -> tagSetName.equals( ts.getName() ) ).findFirst().orElse( null ); + if ( tagSet == null ) + return true; + TagSetStructure.Tag tag = tagSet.getTags().stream().filter( t -> tagLabel.equals( t.label() ) ).findFirst().orElse( null ); + if ( tag == null ) + return true; + return tagSetModel.getVertexTags().getTaggedWith( tag ).isEmpty() && tagSetModel.getEdgeTags().getTaggedWith( tag ).isEmpty(); + } +} From 8c425482d1045327ec7ae40026def5005f8e958c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 13:44:18 +0200 Subject: [PATCH 44/76] Mastodon Git: reuse code for normal merge and pull merge --- .../collaboration/MastodonGitRepository.java | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 85b5192c..af1b284f 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -239,35 +239,28 @@ public synchronized String getCurrentBranch() throws Exception public synchronized void mergeBranch( String selectedBranch ) throws Exception { + Context context = windowManager.getContext(); + MamutProject project = windowManager.getProjectManager().getProject(); + File projectRoot = project.getProjectRoot(); try (Git git = initGit()) { windowManager.getProjectManager().saveProject(); boolean clean = isClean( git ); if ( !clean ) throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before merging." ); - File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); String currentBranch = getCurrentBranch(); Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( selectedBranch ).call(); Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( currentBranch ).call(); git.merge().setCommit( false ).include( git.getRepository().exactRef( selectedBranch ) ).call(); // TODO selected branch, should not be a string but a ref instead - mergeTwoProjectsAndOpenTheResult( dsA, dsB ); - windowManager.getProjectManager().saveProject( projectRoot ); - commit( "Merge commit generated with Mastodon" ); + Model mergedModel = merge( dsA, dsB ); + saveModel( context, mergedModel, project ); + commitWithoutSave( "Merge commit generated with Mastodon" ); + reloadFromDisc(); } } - private void mergeTwoProjectsAndOpenTheResult( Dataset dsA, Dataset dsB ) throws IOException, SpimDataException - { - windowManager.getProjectManager().openWithDialog( new MamutProject( null, dsA.project().getDatasetXmlFile() ) ); - final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet( windowManager.getAppModel().getModel() ); - double distCutoff = 1000; - double mahalanobisDistCutoff = 1; - double ratioThreshold = 2; - MergeDatasets.merge( dsA, dsB, output, distCutoff, mahalanobisDistCutoff, ratioThreshold ); - } - public synchronized void pull() throws Exception { Context context = windowManager.getContext(); @@ -335,8 +328,8 @@ private static Model merge( Dataset dsA, Dataset dsB ) throws IOException, SpimD private synchronized void reloadFromDisc() throws IOException, SpimDataException { - File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); - windowManager.getProjectManager().openWithDialog( new MamutProjectIO().load( projectRoot.getAbsolutePath() ) ); + MamutProject project = windowManager.getProjectManager().getProject(); + windowManager.getProjectManager().openWithDialog( project ); } public synchronized void reset() throws Exception From 26dc17313a5e4d0a4b35536a194e9f051ced8859 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 22:26:27 +0200 Subject: [PATCH 45/76] Add plugin to fix graph inconsistencies --- .../FixGraphInconsistenciesPlugin.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/FixGraphInconsistenciesPlugin.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/FixGraphInconsistenciesPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/FixGraphInconsistenciesPlugin.java new file mode 100644 index 00000000..e96023d5 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/FixGraphInconsistenciesPlugin.java @@ -0,0 +1,144 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import org.mastodon.collection.RefCollection; +import org.mastodon.collection.RefCollections; +import org.mastodon.collection.RefList; +import org.mastodon.collection.RefSet; +import org.mastodon.collection.ref.RefArrayList; +import org.mastodon.collection.ref.RefSetImp; +import org.mastodon.mamut.model.Link; +import org.mastodon.mamut.model.Model; +import org.mastodon.mamut.model.ModelGraph; +import org.mastodon.mamut.model.Spot; +import org.mastodon.mamut.plugin.MamutPlugin; +import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; +import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; +import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; +import org.mastodon.ui.keymap.CommandDescriptionProvider; +import org.mastodon.ui.keymap.KeyConfigContexts; +import org.scijava.plugin.Plugin; + +@Plugin( type = MamutPlugin.class ) +public class FixGraphInconsistenciesPlugin extends BasicMamutPlugin +{ + + private static final ActionDescriptions< FixGraphInconsistenciesPlugin > actionDescriptions = new ActionDescriptions<>( FixGraphInconsistenciesPlugin.class ); + + static + { + actionDescriptions.addActionDescription( + "[mastodon-tomancak] fix graph inconsistencies", + "Plugins > Trees Management > Fix Graph Inconsistencies", + "Flip reversed edges", + FixGraphInconsistenciesPlugin::fixGraphInconsistencies ); + } + + public < T > FixGraphInconsistenciesPlugin() + { + super( actionDescriptions ); + } + + private void fixGraphInconsistencies() + { + Model model = getAppModel().getModel(); + ModelGraph graph = model.getGraph(); + flipBackwardsEdges( graph ); + removeDoubleEdges( graph ); + removeSameTimepointEdge( graph ); + printWarnings( graph ); + } + + private static void printWarnings( ModelGraph graph ) + { + for ( Spot spot : graph.vertices() ) + { + if ( spot.incomingEdges().size() > 1 ) + System.out.println( "More than one parent: " + spot.getLabel() ); + + if ( spot.outgoingEdges().size() > 2 ) + System.out.println( "More than two children: " + spot.getLabel() ); + } + } + + private static void removeSameTimepointEdge( ModelGraph graph ) + { + RefCollection< Link > backwardEdge = RefCollections.createRefList( graph.edges() ); + Spot sourceRef = graph.vertexRef(); + Spot targetRef = graph.vertexRef(); + for ( Link edge : graph.edges() ) + { + Spot source = edge.getSource( sourceRef ); + Spot target = edge.getTarget( targetRef ); + if ( source.getTimepoint() == target.getTimepoint() ) + backwardEdge.add( edge ); + } + for ( Link edge : backwardEdge ) + { + Spot source = edge.getSource( sourceRef ); + Spot target = edge.getTarget( targetRef ); + System.out.println( "Remove same timepoint edge " + source.getLabel() + " -> " + target.getLabel() ); + graph.remove( edge ); + } + } + + private static void removeDoubleEdges( ModelGraph graph ) + { + Spot ref1 = graph.vertexRef(); + Spot ref2 = graph.vertexRef(); + RefSet< Spot > sources = new RefSetImp<>( graph.vertices().getRefPool() ); + RefList< Link > doubleEdge = new RefArrayList<>( graph.edges().getRefPool() ); + for ( Spot spot : graph.vertices() ) + { + if ( spot.incomingEdges().size() > 1 ) + { + for ( Link link : spot.incomingEdges() ) + { + Spot source = link.getSource( ref1 ); + if ( sources.contains( source ) ) + doubleEdge.add( link ); + else + sources.add( source ); + } + sources.clear(); + } + } + for ( Link link : doubleEdge ) + { + Spot source = link.getSource( ref1 ); + Spot target = link.getTarget( ref2 ); + System.out.println( "Remove duplicated edge: " + source.getLabel() + " -> " + target.getLabel() ); + graph.remove( link ); + } + } + + private static void flipBackwardsEdges( ModelGraph graph ) + { + RefCollection< Link > backwardEdge = RefCollections.createRefList( graph.edges() ); + Spot sourceRef = graph.vertexRef(); + Spot targetRef = graph.vertexRef(); + for ( Link edge : graph.edges() ) + { + Spot source = edge.getSource( sourceRef ); + Spot target = edge.getTarget( targetRef ); + if ( source.getTimepoint() > target.getTimepoint() ) + backwardEdge.add( edge ); + } + for ( Link edge : backwardEdge ) + { + Spot source = edge.getSource( sourceRef ); + Spot target = edge.getTarget( targetRef ); + System.out.println( "Flip backwards edge " + source.getLabel() + " -> " + target.getLabel() ); + graph.remove( edge ); + graph.addEdge( source, target ).init(); + } + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class DescriptionProvider extends BasicDescriptionProvider + { + public DescriptionProvider() + { + super( actionDescriptions, KeyConfigContexts.MASTODON, KeyConfigContexts.TRACKSCHEME ); + } + } +} From 676f4a649cabac33342ab4da83d381b9aefc8780 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 22:27:17 +0200 Subject: [PATCH 46/76] Mastodon Git: don't reset project.xml, gui.xml during merge abort --- .../collaboration/MastodonGitRepository.java | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index af1b284f..9931ecb0 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -161,7 +161,7 @@ private void commitWithoutSave( String message ) throws Exception } } - private synchronized List< DiffEntry > relevantChanges( Git git ) throws GitAPIException + private static synchronized List< DiffEntry > relevantChanges( Git git ) throws GitAPIException { return git.diff().setPathFilter( relevantFilesFilter() ).call(); } @@ -341,7 +341,7 @@ public synchronized void reset() throws Exception } } - private synchronized void resetRelevantChanges( Git git ) throws Exception + private static synchronized void resetRelevantChanges( Git git ) throws Exception { // NB: More complicated than a simple reset --hard, because gui.xml, project.xml and dataset.xml.backup should remain untouched. List< DiffEntry > diffEntries = relevantChanges( git ); @@ -390,7 +390,7 @@ private synchronized boolean isClean( Git git ) throws GitAPIException return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); } - private synchronized TreeFilter relevantFilesFilter() + private static synchronized TreeFilter relevantFilesFilter() { TreeFilter[] filters = { // only files in subdirectory "mastodon.project" @@ -415,12 +415,33 @@ public boolean isRepository() } } - private static void abortMerge( Git git ) throws IOException, GitAPIException + public static void main( String... args ) + { + + File dir = new File( "/home/arzt/tmp/2/mgit-test" ); + File projectRoot = new File( dir, "mastodon.project" ); + try (Git git = Git.open( dir ); Context context = new Context()) + { + git.checkout() + .addPath( "mastodon.project/gui.xml" ) + .addPath( "mastodon.project/model.raw" ) + .addPath( "mastodon.project/tags.raw" ) + .addPath( "mastodon.project/ksdfghksdgh.raw" ) + .call(); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + private static void abortMerge( Git git ) throws Exception { Repository repository = git.getRepository(); repository.writeMergeCommitMsg( null ); repository.writeMergeHeads( null ); - git.reset().setMode( ResetCommand.ResetType.HARD ).call(); + git.reset().setMode( ResetCommand.ResetType.MIXED ).call(); + resetRelevantChanges( git ); } public void resetToRemoteBranch() throws Exception From 331e0d0bcec380864f0fe4495c7a9aaaaa7aa337 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 18 Oct 2023 23:33:01 +0200 Subject: [PATCH 47/76] Mastodon Git: tweak UI texts --- .../collaboration/MastodonGitController.java | 14 +++++++------- .../commands/MastodonGitCloneRepository.java | 4 ++-- .../commands/MastodonGitCommitCommand.java | 7 ++++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index c4e4a3c9..3678d672 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -69,9 +69,9 @@ public class MastodonGitController extends BasicMamutPlugin MastodonGitController::shareProject ); private static final String CLONE_REPOSITORY_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] clone repository", - "Plugins > Git > Initialize > Clone Existing Repository", - "Clone a git repository to a new Mastodon project.", + "[mastodon git] clone", + "Plugins > Git > Initialize > Download Shared Project (clone)", + "Download a shared project, save a copy on the local disc and open it with Mastodon.", MastodonGitController::cloneGitRepository ); private static final String SET_AUTHOR_ACTION_KEY = actionDescriptions.addActionDescription( @@ -89,21 +89,21 @@ public class MastodonGitController extends BasicMamutPlugin private static final String PUSH_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] push", "Plugins > Git > Upload Changes (push)", - "Push changes to the git repository.", + "Push local changed to the remote server.", MastodonGitController::push ); private static final String PULL_ACTION_KEY = actionDescriptions. addActionDescription( "[mastodon git] pull", "Plugins > Git > Download Changes (pull)", - "Pull changes from the git repository.", + "Pull changes from the remote server and merge them with my changes.", MastodonGitController::pull ); private static final String RESET_ACTION_KEY = actionDescriptions. addActionDescription( "[mastodon git] git reset", - "Plugins > Git > Undo Changes (reset)", - "Reset changes in the git repository.", + "Plugins > Git > Go Back To Latest Save Point (reset)", + "Discard all changes made since the last save point.", MastodonGitController::reset ); private static final String NEW_BRANCH_ACTION_KEY = actionDescriptions. diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 6e47d92d..fc183422 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -13,8 +13,8 @@ // - warn if repositoryName already exists and the corresponding directory is not empty // - fill repositoryName with a default value based on the repositoryURL @Plugin( type = Command.class, - label = "Mastodon Collaborative - Download Shared Project (clone)", - menuPath = "Plugins > Mastodon Collaborative > Download Shared Project" ) + label = "Mastodon Git - Download Shared Project (clone)", + menuPath = "Plugins > Mastodon Git > Download Shared Project" ) public class MastodonGitCloneRepository extends AbstractCancellable implements Command { @Parameter diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java index cb508a97..afd59652 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java @@ -1,5 +1,6 @@ package org.mastodon.mamut.tomancak.collaboration.commands; +import org.scijava.ItemVisibility; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -12,9 +13,13 @@ public class MastodonGitCommitCommand extends AbstractCancellable implements Com @Parameter private MastodonGitRepository repository; - @Parameter( label = "Commit message", style = "text area", persist = false, description = "A short description of the changes." ) + @Parameter( label = "Save point message", style = "text area", persist = false, + description = "A good message, is very helpful when inspecting the history of changes." ) private String commitMessage; + @Parameter( visibility = ItemVisibility.MESSAGE ) + private String comment = "Please describe briefly the changes since the last save point!"; + @Override public void run() { From 1effc68ac36aecaa1a904162e610984b10a2e98c Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 00:14:29 +0200 Subject: [PATCH 48/76] Mastodon Git: use gitignore for gui.xml, project.xml etc. --- .../collaboration/MastodonGitRepository.java | 87 +++++-------------- 1 file changed, 23 insertions(+), 64 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 9931ecb0..942dfbef 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.List; import java.util.stream.Collectors; @@ -103,6 +104,15 @@ public static MastodonGitRepository shareProject( throw new RuntimeException( "The repository already contains a shared mastodon project: " + repositoryURL ); Files.createDirectory( mastodonProjectPath ); windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); + Files.copy( mastodonProjectPath.resolve( "gui.xml" ), mastodonProjectPath.resolve( "gui.xml_remote" ) ); + Files.copy( mastodonProjectPath.resolve( "project.xml" ), mastodonProjectPath.resolve( "project.xml_remote" ) ); + Files.copy( mastodonProjectPath.resolve( "dataset.xml.backup" ), mastodonProjectPath.resolve( "dataset.xml.backup_remote" ) ); + Path gitignore = directory.toPath().resolve( ".gitignore" ); + Files.write( gitignore, "/mastodon.project/gui.xml\n".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND ); + Files.write( gitignore, "/mastodon.project/project.xml\n".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND ); + Files.write( gitignore, "/mastodon.project/dataset.xml.backup\n".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND ); + git.add().addFilepattern( ".gitignore" ).call(); + git.commit().setMessage( "Add .gitignore file" ).call(); git.add().addFilepattern( "mastodon.project" ).call(); git.commit().setMessage( "Share mastodon project" ).call(); git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); @@ -118,12 +128,17 @@ private static boolean isEmpty( File directory ) public static void cloneRepository( String repositoryURL, File directory ) throws Exception { - Git git = Git.cloneRepository() + try (Git git = Git.cloneRepository() .setURI( repositoryURL ) .setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ) .setDirectory( directory ) - .call(); - git.close(); + .call()) + { + Path mastodonProjectPath = directory.toPath().resolve( "mastodon.project" ); + Files.copy( mastodonProjectPath.resolve( "gui.xml_remote" ), mastodonProjectPath.resolve( "gui.xml" ) ); + Files.copy( mastodonProjectPath.resolve( "project.xml_remote" ), mastodonProjectPath.resolve( "project.xml" ) ); + Files.copy( mastodonProjectPath.resolve( "dataset.xml.backup_remote" ), mastodonProjectPath.resolve( "dataset.xml.backup" ) ); + } } public static void openProjectInRepository( Context context, File directory ) throws Exception @@ -144,16 +159,7 @@ private void commitWithoutSave( String message ) throws Exception { try (Git git = initGit()) { - List< DiffEntry > changedFiles = relevantChanges( git ); - if ( changedFiles.isEmpty() ) - return; - - for ( DiffEntry diffEntry : changedFiles ) - { - git.add().addFilepattern( diffEntry.getOldPath() ).call(); - git.add().addFilepattern( diffEntry.getNewPath() ).call(); - } - + git.add().addFilepattern( "mastodon.project" ).call(); CommitCommand commit = git.commit(); commit.setMessage( message ); commit.setAuthor( settingsService.getPersonIdent() ); @@ -161,11 +167,6 @@ private void commitWithoutSave( String message ) throws Exception } } - private static synchronized List< DiffEntry > relevantChanges( Git git ) throws GitAPIException - { - return git.diff().setPathFilter( relevantFilesFilter() ).call(); - } - public synchronized void push() throws Exception { try (Git git = initGit()) @@ -336,38 +337,11 @@ public synchronized void reset() throws Exception { try (Git git = initGit()) { - resetRelevantChanges( git ); + git.reset().setMode( ResetCommand.ResetType.HARD ).call(); reloadFromDisc(); } } - private static synchronized void resetRelevantChanges( Git git ) throws Exception - { - // NB: More complicated than a simple reset --hard, because gui.xml, project.xml and dataset.xml.backup should remain untouched. - List< DiffEntry > diffEntries = relevantChanges( git ); - if ( diffEntries.isEmpty() ) - return; - - CheckoutCommand command = git.checkout(); - for ( DiffEntry entry : diffEntries ) - { - switch ( entry.getChangeType() ) - { - case ADD: - command.addPath( entry.getNewPath() ); - break; - case DELETE: - command.addPath( entry.getOldPath() ); - break; - case MODIFY: - command.addPath( entry.getNewPath() ); - command.addPath( entry.getOldPath() ); - break; - } - } - command.call(); - } - private synchronized Git initGit() throws IOException { File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); @@ -387,20 +361,7 @@ private synchronized Git initGit( File projectRoot ) throws IOException private synchronized boolean isClean( Git git ) throws GitAPIException { - return git.diff().setPathFilter( relevantFilesFilter() ).call().isEmpty(); - } - - private static synchronized TreeFilter relevantFilesFilter() - { - TreeFilter[] filters = { - // only files in subdirectory "mastodon.project" - PathFilter.create( "mastodon.project" ), - // but exclude gui.xml project.xml and dataset.xml.backup - PathFilter.create( "mastodon.project/gui.xml" ).negate(), - PathFilter.create( "mastodon.project/project.xml" ).negate(), - PathFilter.create( "mastodon.project/dataset.xml.backup" ).negate() - }; - return AndTreeFilter.create( filters ); + return git.status().call().isClean(); } public boolean isRepository() @@ -440,8 +401,7 @@ private static void abortMerge( Git git ) throws Exception Repository repository = git.getRepository(); repository.writeMergeCommitMsg( null ); repository.writeMergeHeads( null ); - git.reset().setMode( ResetCommand.ResetType.MIXED ).call(); - resetRelevantChanges( git ); + git.reset().setMode( ResetCommand.ResetType.HARD ).call(); } public void resetToRemoteBranch() throws Exception @@ -450,8 +410,7 @@ public void resetToRemoteBranch() throws Exception { Repository repository = git.getRepository(); String remoteTrackingBranch = new BranchConfig( repository.getConfig(), repository.getBranch() ).getRemoteTrackingBranch(); - git.reset().setMode( ResetCommand.ResetType.MIXED ).setRef( remoteTrackingBranch ).call(); - resetRelevantChanges( git ); + git.reset().setMode( ResetCommand.ResetType.HARD ).setRef( remoteTrackingBranch ).call(); reloadFromDisc(); } } From 5f6b9bebfd8ba8c270081578fb09c5a5ecc06718 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 12:02:47 +0200 Subject: [PATCH 49/76] Mastodon Git: show a dialog if there is an exception --- .../tomancak/collaboration/ErrorDialog.java | 20 +++++++++ .../collaboration/MastodonGitController.java | 41 +++++++++++-------- .../collaboration/MastodonGitRepository.java | 38 ++++++++++------- .../MergeConflictDuringPullException.java | 5 --- .../commands/MastodonGitCloneRepository.java | 3 +- .../commands/MastodonGitCommitCommand.java | 3 +- .../commands/MastodonGitCreateRepository.java | 3 +- .../commands/MastodonGitNewBranch.java | 3 +- .../GraphMergeConflictException.java | 9 ++++ .../exceptions/GraphMergeException.java | 15 +++++++ 10 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java delete mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeConflictException.java create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeException.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java new file mode 100644 index 00000000..52cc7ee2 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java @@ -0,0 +1,20 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +public class ErrorDialog +{ + public static void showErrorMessage( String title, Exception e ) + { + e.printStackTrace(); + // show the error message in a nice dialog + String message = "\nThere was a problem:\n\n" + e.getMessage() + "\n"; + SwingUtilities.invokeLater( () -> JOptionPane.showMessageDialog( null, message, title + " (Error)", JOptionPane.ERROR_MESSAGE ) ); + } + + public static void main( String... args ) + { + showErrorMessage( "Test", new Exception( "Test exception" ) ); + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 3678d672..10c57b71 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -41,6 +41,8 @@ import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitNewBranch; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitSetAuthorCommand; +import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeConflictException; +import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeException; import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; @@ -191,7 +193,7 @@ private void commit() private void push() { - run( () -> repository.push() ); + run( "Upload Changes (Push)", () -> repository.push() ); } private void newBranch() @@ -220,11 +222,11 @@ private void switchBranch() if ( selectedBranch == null ) return; // switch to selected branch - run( () -> repository.switchBranch( selectedBranch ) ); + run( "Switch To Branch", () -> repository.switchBranch( selectedBranch ) ); } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Select Branch", e ); } } @@ -248,35 +250,38 @@ private void mergeBranch() } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Merge Branch", e ); } } private void pull() { - run( () -> { + run( "Download Changes (Pull)", () -> { try { repository.pull(); } - catch ( MergeConflictDuringPullException e ) + catch ( GraphMergeException e ) { - SwingUtilities.invokeLater( () -> suggestPullAlternative() ); + if ( !( e instanceof GraphMergeConflictException ) ) + e.printStackTrace(); + SwingUtilities.invokeLater( () -> suggestPullAlternative( e.getMessage() ) ); } } ); } - private void suggestPullAlternative() + private void suggestPullAlternative( String errorMessage ) { // TODO: add pull alternative, save to new branch String title = "Conflict During Pull"; - String message = "There was a merge conflict during the pull.\n" - + "You made changes on your computer that conflict with changes on the server.\n" - + "The conflicts could not be resolved automatically.\n" + String message = "There was a merge conflict during the pull. Details:\n" + + " " + errorMessage + "\n\n" + + "You made changes on your computer that could not be automatically\n" + + "merged with the changes on the server.\n\n" + "You can either:\n" - + " 1. Throw away you local changes.\n" - + " 2. Cancel (And maybe save your local changes to a new branch,\n" - + " which you can then merge into the remote branch.)\n"; + + " 1. Throw away your local changes & local save points.\n" + + " 2. Or cancel (And maybe save your local changes to a new branch,\n" + + " which you can then be merged into the remote branch.)\n"; String[] options = { "Discard Local Changes", "Cancel" }; int result = JOptionPane.showOptionDialog( null, message, title, JOptionPane.YES_NO_OPTION, @@ -287,12 +292,12 @@ private void suggestPullAlternative() private void resetToRemoteBranch() { - run( () -> repository.resetToRemoteBranch() ); + run( "Throw Away All Local Changes (Reset To Remote)", () -> repository.resetToRemoteBranch() ); } private void reset() { - run( () -> repository.reset() ); + run( "Go Back To Last Save Point (Reset)", () -> repository.reset() ); } private void askForAuthorName( String message ) @@ -305,7 +310,7 @@ private void askForAuthorName( String message ) setAuthor(); } - private void run( RunnableWithException action ) + private void run( String title, RunnableWithException action ) { new Thread( () -> { try @@ -314,7 +319,7 @@ private void run( RunnableWithException action ) } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( title, e ); } } ).start(); } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 942dfbef..4f31a92a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -43,13 +43,9 @@ import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.BranchConfig; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.treewalk.filter.AndTreeFilter; -import org.eclipse.jgit.treewalk.filter.PathFilter; -import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.mastodon.graph.io.RawGraphIO; import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; @@ -60,6 +56,8 @@ import org.mastodon.mamut.project.MamutProject; import org.mastodon.mamut.project.MamutProjectIO; import org.mastodon.mamut.tomancak.collaboration.credentials.PersistentCredentials; +import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeConflictException; +import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeException; import org.mastodon.mamut.tomancak.collaboration.utils.ConflictUtils; import org.mastodon.mamut.tomancak.merging.Dataset; import org.mastodon.mamut.tomancak.merging.MergeDatasets; @@ -172,6 +170,7 @@ public synchronized void push() throws Exception try (Git git = initGit()) { git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); + // TODO: Tell user about failed push } } @@ -293,17 +292,26 @@ public synchronized void pull() throws Exception private void automaticMerge( Context context, MamutProject project, File projectRoot, Git git ) throws Exception { - git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); - Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); - git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.THEIRS ).call(); - Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); - git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); - Model mergedModel = merge( dsA, dsB ); - if ( ConflictUtils.hasConflict( mergedModel ) ) - throw new MergeConflictDuringPullException(); - ConflictUtils.removeMergeConflictTagSets( mergedModel ); - saveModel( context, mergedModel, project ); - commitWithoutSave( "Automatic merge by Mastodon during pull" ); + try + { + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); + Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.THEIRS ).call(); + Dataset dsB = new Dataset( projectRoot.getAbsolutePath() ); + git.checkout().setAllPaths( true ).setStage( CheckoutCommand.Stage.OURS ).call(); + Model mergedModel = merge( dsA, dsB ); + if ( ConflictUtils.hasConflict( mergedModel ) ) + throw new GraphMergeConflictException(); + ConflictUtils.removeMergeConflictTagSets( mergedModel ); + saveModel( context, mergedModel, project ); + commitWithoutSave( "Automatic merge by Mastodon during pull" ); + } + catch ( Throwable t ) + { + if ( t instanceof GraphMergeException ) + throw t; + throw new GraphMergeException( "There was a failure, when merging changes to the Model.", t ); + } } private static void saveModel( Context context, Model model, MamutProject project ) throws IOException diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java deleted file mode 100644 index 89b4b9f1..00000000 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MergeConflictDuringPullException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.mastodon.mamut.tomancak.collaboration; - -public class MergeConflictDuringPullException extends RuntimeException -{ -} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index fc183422..14c81db8 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -2,6 +2,7 @@ import java.io.File; +import org.mastodon.mamut.tomancak.collaboration.ErrorDialog; import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.Context; import org.scijava.command.Command; @@ -59,7 +60,7 @@ public void run() } catch ( final Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Download Shares Project (Clone)", e ); } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java index afd59652..d30f3013 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java @@ -1,5 +1,6 @@ package org.mastodon.mamut.tomancak.collaboration.commands; +import org.mastodon.mamut.tomancak.collaboration.ErrorDialog; import org.scijava.ItemVisibility; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -29,7 +30,7 @@ public void run() } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Add Save Point (Commit)", e ); } } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index 381ac365..cea3b658 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -30,6 +30,7 @@ import java.io.File; +import org.mastodon.mamut.tomancak.collaboration.ErrorDialog; import org.scijava.ItemVisibility; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -70,7 +71,7 @@ public void run() } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Share Project", e ); } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java index 9b29940f..c1a9d0aa 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitNewBranch.java @@ -1,5 +1,6 @@ package org.mastodon.mamut.tomancak.collaboration.commands; +import org.mastodon.mamut.tomancak.collaboration.ErrorDialog; import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; import org.scijava.command.Command; import org.scijava.plugin.Parameter; @@ -24,7 +25,7 @@ public void run() } catch ( Exception e ) { - e.printStackTrace(); + ErrorDialog.showErrorMessage( "Create New Branch", e ); } } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeConflictException.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeConflictException.java new file mode 100644 index 00000000..efd81f50 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeConflictException.java @@ -0,0 +1,9 @@ +package org.mastodon.mamut.tomancak.collaboration.exceptions; + +public class GraphMergeConflictException extends GraphMergeException +{ + public GraphMergeConflictException() + { + super( "There are conflicting changes in the two versions of the ModelGraph." ); + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeException.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeException.java new file mode 100644 index 00000000..524c1319 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/GraphMergeException.java @@ -0,0 +1,15 @@ +package org.mastodon.mamut.tomancak.collaboration.exceptions; + +public class GraphMergeException extends RuntimeException +{ + + public GraphMergeException( final String message ) + { + super( message ); + } + + public GraphMergeException( String message, final Throwable cause ) + { + super( message, cause ); + } +} From 5965628862459649d2d5a31db31fd02809e3b825 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 12:19:06 +0200 Subject: [PATCH 50/76] Mastodon Git: inform user about an failed push --- .../collaboration/MastodonGitRepository.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 4f31a92a..521c1431 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -46,6 +46,8 @@ import org.eclipse.jgit.lib.BranchConfig; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; import org.mastodon.graph.io.RawGraphIO; import org.mastodon.mamut.MainWindow; import org.mastodon.mamut.WindowManager; @@ -169,8 +171,25 @@ public synchronized void push() throws Exception { try (Git git = initGit()) { - git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); - // TODO: Tell user about failed push + Iterable< PushResult > results = git.push().setCredentialsProvider( credentials.getSingleUseCredentialsProvider() ).setRemote( "origin" ).call(); + raiseExceptionOnUnsuccessfulPush( results ); + } + } + + private static void raiseExceptionOnUnsuccessfulPush( Iterable< PushResult > results ) + { + for ( PushResult result : results ) + { + for ( RemoteRefUpdate update : result.getRemoteUpdates() ) + { + if ( update.getStatus() == RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ) + throw new RuntimeException( "The remote server has changes, that you didn't download yet.\n" + + "Please download changes (pull) first.\n" + + "You can upload your changes afterwards.\n" ); + if ( update.getStatus() != RemoteRefUpdate.Status.OK && + update.getStatus() != RemoteRefUpdate.Status.UP_TO_DATE ) + throw new RuntimeException( "Push failed: " + update.getMessage() + " " + update.getStatus() ); + } } } From 60fcffcd238a890e427971ed1ce255160339a218 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 13:52:30 +0200 Subject: [PATCH 51/76] Mastodon Git: make sure the repo is clean before, switch, pull & merge --- .../collaboration/MastodonGitRepository.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 521c1431..a0c3667a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -206,6 +206,7 @@ public synchronized void switchBranch( String branchName ) throws Exception File projectRoot = windowManager.getProjectManager().getProject().getProjectRoot(); try (Git git = initGit( projectRoot )) { + ensureClean( git, "switching the branch" ); boolean isRemoteBranch = branchName.startsWith( "refs/remotes/" ); if ( isRemoteBranch ) { @@ -263,10 +264,7 @@ public synchronized void mergeBranch( String selectedBranch ) throws Exception File projectRoot = project.getProjectRoot(); try (Git git = initGit()) { - windowManager.getProjectManager().saveProject(); - boolean clean = isClean( git ); - if ( !clean ) - throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before merging." ); + ensureClean( git, "merging" ); String currentBranch = getCurrentBranch(); Dataset dsA = new Dataset( projectRoot.getAbsolutePath() ); git.checkout().setName( selectedBranch ).call(); @@ -287,10 +285,7 @@ public synchronized void pull() throws Exception File projectRoot = project.getProjectRoot(); try (Git git = initGit()) { - windowManager.getProjectManager().saveProject(); - boolean isClean = isClean( git ); - if ( !isClean ) - throw new RuntimeException( "There are uncommitted changes. Please commit or stash them before pulling." ); + ensureClean( git, "pulling" ); try { boolean conflict = !git.pull() @@ -441,4 +436,12 @@ public void resetToRemoteBranch() throws Exception reloadFromDisc(); } } + + private void ensureClean( Git git, String title ) throws GitAPIException + { + windowManager.getProjectManager().saveProject(); + boolean clean = isClean( git ); + if ( !clean ) + throw new RuntimeException( "There are uncommitted changes. Please add a save point before " + title + "." ); + } } From b736536a484d0afe49ba208e0832767daab389b5 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 14:59:40 +0200 Subject: [PATCH 52/76] Mastodon Git: let the SettingsService implement ImageJService Why is this necessary? I don't now. But "extends Service" would lead to a missing service error when starting Mastodon from within Fiji. --- .../collaboration/MastodonGitController.java | 1 + .../collaboration/MastodonGitRepository.java | 1 + .../commands/MastodonGitSetAuthorCommand.java | 2 +- .../DefaultMastodonGitSettingsService.java} | 22 ++++++++++++------- .../settings/MastodonGitSettingsService.java | 21 ++++++++++++++++++ 5 files changed, 38 insertions(+), 9 deletions(-) rename src/main/java/org/mastodon/mamut/tomancak/collaboration/{MastodonGitSettingsService.java => settings/DefaultMastodonGitSettingsService.java} (55%) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/MastodonGitSettingsService.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 10c57b71..2d75ebf3 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -46,6 +46,7 @@ import org.mastodon.mamut.tomancak.collaboration.utils.ActionDescriptions; import org.mastodon.mamut.tomancak.collaboration.utils.BasicDescriptionProvider; import org.mastodon.mamut.tomancak.collaboration.utils.BasicMamutPlugin; +import org.mastodon.mamut.tomancak.collaboration.settings.MastodonGitSettingsService; import org.mastodon.ui.keymap.CommandDescriptionProvider; import org.mastodon.ui.keymap.KeyConfigContexts; import org.scijava.command.CommandService; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index a0c3667a..5aee9c16 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -61,6 +61,7 @@ import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeConflictException; import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeException; import org.mastodon.mamut.tomancak.collaboration.utils.ConflictUtils; +import org.mastodon.mamut.tomancak.collaboration.settings.MastodonGitSettingsService; import org.mastodon.mamut.tomancak.merging.Dataset; import org.mastodon.mamut.tomancak.merging.MergeDatasets; import org.scijava.Context; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java index c9efd687..3fd215ba 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java @@ -1,6 +1,6 @@ package org.mastodon.mamut.tomancak.collaboration.commands; -import org.mastodon.mamut.tomancak.collaboration.MastodonGitSettingsService; +import org.mastodon.mamut.tomancak.collaboration.settings.MastodonGitSettingsService; import org.scijava.Initializable; import org.scijava.ItemVisibility; import org.scijava.command.Command; diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/DefaultMastodonGitSettingsService.java similarity index 55% rename from src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java rename to src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/DefaultMastodonGitSettingsService.java index 83bc3cd1..777f46ae 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitSettingsService.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/DefaultMastodonGitSettingsService.java @@ -1,14 +1,14 @@ -package org.mastodon.mamut.tomancak.collaboration; +package org.mastodon.mamut.tomancak.collaboration.settings; import org.eclipse.jgit.lib.PersonIdent; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.prefs.PrefService; import org.scijava.service.AbstractService; -import org.scijava.service.SciJavaService; +import org.scijava.service.Service; -@Plugin( type = SciJavaService.class ) -public class MastodonGitSettingsService extends AbstractService +@Plugin( type = Service.class ) +public class DefaultMastodonGitSettingsService extends AbstractService implements MastodonGitSettingsService { @Parameter @@ -22,37 +22,43 @@ public class MastodonGitSettingsService extends AbstractService public void initialize() { super.initialize(); - authorName = prefService.get( MastodonGitSettingsService.class, "author.name", null ); - authorEmail = prefService.get( MastodonGitSettingsService.class, "author.email", null ); + authorName = prefService.get( DefaultMastodonGitSettingsService.class, "author.name", null ); + authorEmail = prefService.get( DefaultMastodonGitSettingsService.class, "author.email", null ); } + @Override public boolean isAuthorSpecified() { return authorName != null && authorEmail != null; } + @Override public void setAuthorName( String name ) { this.authorName = name; - prefService.put( MastodonGitSettingsService.class, "author.name", name ); + prefService.put( DefaultMastodonGitSettingsService.class, "author.name", name ); } + @Override public void setAuthorEmail( String email ) { this.authorEmail = email; - prefService.put( MastodonGitSettingsService.class, "author.email", email ); + prefService.put( DefaultMastodonGitSettingsService.class, "author.email", email ); } + @Override public String getAuthorName() { return authorName; } + @Override public String getAuthorEmail() { return authorEmail; } + @Override public PersonIdent getPersonIdent() { return new PersonIdent( authorName, authorEmail ); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/MastodonGitSettingsService.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/MastodonGitSettingsService.java new file mode 100644 index 00000000..f77a7b99 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/settings/MastodonGitSettingsService.java @@ -0,0 +1,21 @@ +package org.mastodon.mamut.tomancak.collaboration.settings; + +import net.imagej.ImageJService; + +import org.eclipse.jgit.lib.PersonIdent; +import org.scijava.service.Service; + +public interface MastodonGitSettingsService extends ImageJService +{ + boolean isAuthorSpecified(); + + void setAuthorName( String name ); + + void setAuthorEmail( String email ); + + String getAuthorName(); + + String getAuthorEmail(); + + PersonIdent getPersonIdent(); +} From 86366e7e6e6b8b7b43caf5fce030019ecb83a0d2 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 19 Oct 2023 15:06:24 +0200 Subject: [PATCH 53/76] Mastodon Git: use JGit version that still support Java8 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 2176683c..af726559 100644 --- a/pom.xml +++ b/pom.xml @@ -49,18 +49,18 @@ org.eclipse.jgit org.eclipse.jgit - 6.7.0.202309050840-r + 5.13.2.202306221912-r org.eclipse.jgit org.eclipse.jgit.ssh.apache - 6.7.0.202309050840-r + 5.13.2.202306221912-r org.eclipse.jgit org.eclipse.jgit.ui - 6.7.0.202309050840-r + 5.13.2.202306221912-r From ee362b0a3371671a9a1c5dd0f670395e83b862f5 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 20 Oct 2023 18:36:49 +0200 Subject: [PATCH 54/76] Mastodon Git: show success notification on push --- .../collaboration/MastodonGitController.java | 8 +++++-- .../collaboration/MastodonGitRepository.java | 2 +- .../collaboration/NotificationDialog.java | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 2d75ebf3..f5eb740d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -194,7 +194,11 @@ private void commit() private void push() { - run( "Upload Changes (Push)", () -> repository.push() ); + run( "Upload Changes (Push)", () -> { + repository.push(); + NotificationDialog.show( "Upload Changes (Push)", + " Completed successfully." ); + } ); } private void newBranch() @@ -274,7 +278,7 @@ private void pull() private void suggestPullAlternative( String errorMessage ) { // TODO: add pull alternative, save to new branch - String title = "Conflict During Pull"; + String title = "Conflict During Download Of Changes (Pull)"; String message = "There was a merge conflict during the pull. Details:\n" + " " + errorMessage + "\n\n" + "You made changes on your computer that could not be automatically\n" diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 5aee9c16..79447193 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -185,7 +185,7 @@ private static void raiseExceptionOnUnsuccessfulPush( Iterable< PushResult > res { if ( update.getStatus() == RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ) throw new RuntimeException( "The remote server has changes, that you didn't download yet.\n" - + "Please download changes (pull) first.\n" + + "Please download changes first. (pull)\n" + "You can upload your changes afterwards.\n" ); if ( update.getStatus() != RemoteRefUpdate.Status.OK && update.getStatus() != RemoteRefUpdate.Status.UP_TO_DATE ) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java new file mode 100644 index 00000000..7a2a015f --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java @@ -0,0 +1,22 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.Timer; + +public class NotificationDialog +{ + public static void main( String... args ) + { + show( "Upload Changes", " Changes were uploaded successfully." ); + } + + public static void show( String title, String message ) + { + JOptionPane pane = new JOptionPane( message, JOptionPane.PLAIN_MESSAGE ); + JDialog dialog = pane.createDialog( null, title ); + dialog.setModal( false ); + dialog.setVisible( true ); + new Timer( 1500, ignore -> dialog.setVisible( false ) ).start(); + } +} From 915ee0f49cafb00cd6ac960ee7d128710e045e4d Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Sun, 22 Oct 2023 18:04:10 +0200 Subject: [PATCH 55/76] Mastodon Git: add show branch name command --- .../collaboration/MastodonGitController.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index f5eb740d..54a3a7e4 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -116,6 +116,12 @@ public class MastodonGitController extends BasicMamutPlugin "Create a new branch in the git repository.", MastodonGitController::newBranch ); + private static final String SHOW_BRANCH_NAME_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] show branch name", + "Plugins > Git > Branches > Show Branch Name", + "Show the name of the current git branch", + MastodonGitController::showBranchName ); + private static final String SWITCH_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] switch branch", "Plugins > Git > Branches > Switch Branch", @@ -315,6 +321,18 @@ private void askForAuthorName( String message ) setAuthor(); } + private void showBranchName() + { + run( "Show Branch Name", () -> { + String longBranchName = repository.getCurrentBranch(); + String shortBranchName = longBranchName.replaceAll( "^refs/heads/", "" ); + String title = "Current Branch Name"; + String message = "The current branch is:
" + shortBranchName; + SwingUtilities.invokeLater( () -> + JOptionPane.showMessageDialog( null, message, title, JOptionPane.PLAIN_MESSAGE ) ); + } ); + } + private void run( String title, RunnableWithException action ) { new Thread( () -> { From ef14faf4713f8872f8e862c5c3269adda1d0ed2a Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Sun, 22 Oct 2023 18:06:13 +0200 Subject: [PATCH 56/76] Mastodon Git: add synchronize menu item --- .../collaboration/CommitMessageDialog.java | 53 +++++++++++++++++++ .../collaboration/MastodonGitController.java | 47 ++++++++++++++++ .../collaboration/MastodonGitRepository.java | 11 +++- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java new file mode 100644 index 00000000..d83f43a8 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java @@ -0,0 +1,53 @@ +package org.mastodon.mamut.tomancak.collaboration; + +import java.awt.KeyboardFocusManager; + +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.WindowConstants; + +import net.miginfocom.swing.MigLayout; + +public class CommitMessageDialog +{ + public static void main( String... args ) + { + System.out.println( showDialog( "Add Save Point (Commit)" ) ); + } + + public static String showDialog( String title ) + { + JTextArea textArea = new JTextArea( 5, 40 ); + textArea.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null ); + textArea.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null ); + + JPanel panel = new JPanel(); + panel.setLayout( new MigLayout( "insets dialog" ) ); + panel.add( new JLabel( "Save point message:" ), "wrap" ); + panel.add( new JScrollPane( textArea ), "wrap" ); + panel.add( new JLabel( "Please describe briefly the changes since the last save point!" ) ); + + // Show a JOptionPane, where the TextArea has focus when the dialog is shown. + JOptionPane optionPane = new JOptionPane( panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) + { + @Override + public void selectInitialValue() + { + super.selectInitialValue(); + textArea.requestFocusInWindow(); + } + }; + JDialog dialog = optionPane.createDialog( "Add Save Point (commit)" ); + dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + dialog.setVisible( true ); + Object result = optionPane.getValue(); + if ( result instanceof Integer && ( int ) result == JOptionPane.OK_OPTION ) + return textArea.getText(); + else + return null; + } +} diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 54a3a7e4..3c4d7bc0 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -83,6 +83,12 @@ public class MastodonGitController extends BasicMamutPlugin "Set the author name that is used for your commits.", MastodonGitController::setAuthor ); + private static final String SYNCHRONIZE_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] synchronize", + "Plugins > Git > Synchronize (commit, pull, push)", + "Download remote changes and upload local changes.", + MastodonGitController::synchronize ); + private static final String COMMIT_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] commit", "Plugins > Git > Add Save Point (commit)", @@ -333,6 +339,47 @@ private void showBranchName() } ); } + private void synchronize() + { + // check if clean + // - yes, continue + // - no -> ask for commit message, and commit + + // pull + // try to merge + // - no conflict -> continue + // - conflict, let the user change an option: + // - discard local changes + // - cancel + + // push + // notify about success or failure + run( "Synchronize Changes", () -> { + boolean clean = repository.isClean(); + if ( !clean ) + { + String commitMessage = CommitMessageDialog.showDialog( "Add Save Point (commit)" ); + if ( commitMessage == null ) + return; + repository.commitWithoutSave( commitMessage ); + } + try + { + repository.pull(); + } + catch ( GraphMergeException e ) + { + if ( !( e instanceof GraphMergeConflictException ) ) + e.printStackTrace(); + SwingUtilities.invokeLater( () -> suggestPullAlternative( e.getMessage() ) ); + return; + } + repository.push(); + NotificationDialog.show( "Synchronize Changes (Commit, Pull, Push)", + " Completed successfully." ); + } ); + } + private void run( String title, RunnableWithException action ) { new Thread( () -> { diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 79447193..aa1f72a9 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -156,7 +156,7 @@ public synchronized void commit( String message ) throws Exception commitWithoutSave( message ); } - private void commitWithoutSave( String message ) throws Exception + public void commitWithoutSave( String message ) throws Exception { try (Git git = initGit()) { @@ -445,4 +445,13 @@ private void ensureClean( Git git, String title ) throws GitAPIException if ( !clean ) throw new RuntimeException( "There are uncommitted changes. Please add a save point before " + title + "." ); } + + public boolean isClean() throws Exception + { + windowManager.getProjectManager().saveProject(); + try (Git git = initGit()) + { + return isClean( git ); + } + } } From 9efb1c911d9c2915ee5676cba143bb150cddf6ae Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 23 Oct 2023 23:52:14 +0200 Subject: [PATCH 57/76] MastodonGitController: fix code formatting --- .../collaboration/MastodonGitController.java | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 3c4d7bc0..ad549ab1 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -101,26 +101,23 @@ public class MastodonGitController extends BasicMamutPlugin "Push local changed to the remote server.", MastodonGitController::push ); - private static final String PULL_ACTION_KEY = actionDescriptions. - addActionDescription( - "[mastodon git] pull", - "Plugins > Git > Download Changes (pull)", - "Pull changes from the remote server and merge them with my changes.", - MastodonGitController::pull ); - - private static final String RESET_ACTION_KEY = actionDescriptions. - addActionDescription( - "[mastodon git] git reset", - "Plugins > Git > Go Back To Latest Save Point (reset)", - "Discard all changes made since the last save point.", - MastodonGitController::reset ); - - private static final String NEW_BRANCH_ACTION_KEY = actionDescriptions. - addActionDescription( - "[mastodon git] new branch", - "Plugins > Git > Branches > Create New Branch", - "Create a new branch in the git repository.", - MastodonGitController::newBranch ); + private static final String PULL_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] pull", + "Plugins > Git > Download Changes (pull)", + "Pull changes from the remote server and merge them with my changes.", + MastodonGitController::pull ); + + private static final String RESET_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] git reset", + "Plugins > Git > Go Back To Latest Save Point (reset)", + "Discard all changes made since the last save point.", + MastodonGitController::reset ); + + private static final String NEW_BRANCH_ACTION_KEY = actionDescriptions.addActionDescription( + "[mastodon git] new branch", + "Plugins > Git > Branches > Create New Branch", + "Create a new branch in the git repository.", + MastodonGitController::newBranch ); private static final String SHOW_BRANCH_NAME_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] show branch name", From d6779be090c751ad341e5e78fd5a8284defa5ac3 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 9 Nov 2023 13:55:40 +0100 Subject: [PATCH 58/76] PersistentCredentials: fix typo Co-authored-by: Stefan Hahmann --- .../collaboration/credentials/PersistentCredentials.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java index 7ec39aad..f3624baf 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java @@ -36,7 +36,7 @@ private boolean queryPassword( String url, boolean previousAuthenticationFailed panel.add( new JLabel( url ), "span, wrap, gapbottom unrelated" ); panel.add( new JLabel( "username" ) ); panel.add( usernameField, "wrap" ); - panel.add( new JLabel( " password" ) ); + panel.add( new JLabel( "password" ) ); panel.add( passwordField, "wrap" ); if ( previousAuthenticationFailed ) panel.add( new JLabel( "(Authentication failed. Please try again!)" ), "span, wrap" ); From 1bec3102ac2ff49e279bb779e1a96919f66420aa Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Thu, 9 Nov 2023 14:52:56 +0100 Subject: [PATCH 59/76] Refactor PersistentCredentials Try to improve code readability by introducing a callback method for the interaction between the SingleUseCredentialsProvider and the PersistentCredentials class. Improve thread safety by making the getUsernameAndPassword method syncronized. This avoids to show multiple dialogues in parallel to the user in parallel. --- .../credentials/PersistentCredentials.java | 88 ++++------------- .../SingleUseCredentialsProvider.java | 97 +++++++++++++++++++ 2 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/SingleUseCredentialsProvider.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java index f3624baf..6a68461e 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java @@ -8,11 +8,17 @@ import net.miginfocom.swing.MigLayout; -import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.transport.CredentialItem; +import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.URIish; +/** + * This class is meant to be used with JGIT for a comfortable way to ask the + * user for credentials. The user is only asked once for username and password. + * The credentials are stored in memory and reused for all subsequent requests. + * It also tries to detect if the user entered wrong credentials, + * and asks for new credentials. + */ public class PersistentCredentials { @@ -20,9 +26,18 @@ public class PersistentCredentials private String password = null; - private boolean missingCredentials() + /** + * This method simply returns the username and password if they are already + * known. It asks the user for credentials if they are not known yet, or if + * the previous attempt to use the credentials failed. + */ + private synchronized Pair< String, String > getUsernameAndPassword( URIish uri, boolean authenticationFailure ) { - return password == null || username == null; + boolean missingCredentials = password == null || username == null; + if ( missingCredentials || authenticationFailure ) + if ( !queryPassword( uri.toString(), authenticationFailure ) ) + return null; + return Pair.of( username, password ); } private boolean queryPassword( String url, boolean previousAuthenticationFailed ) @@ -53,69 +68,6 @@ private boolean queryPassword( String url, boolean previousAuthenticationFailed public CredentialsProvider getSingleUseCredentialsProvider() { - return new SingleUseCredentialsProvider(); - } - - /** - * The JGIT api does not tell a CredentialsProvider if the credentials - * where correct. It simply asks for them again if they were wrong. - *

- * We can exploit this behavior by counting the number of times the - * CredentialsProvider was asked for credentials. If it was asked more than - * once, we assume that the credentials were wrong. - *

- * This only works if the CredentialsProvider is only used once. - */ - private class SingleUseCredentialsProvider extends CredentialsProvider - { - private int counter = 0; - - @Override - public boolean isInteractive() - { - return true; - } - - @Override - public boolean supports( CredentialItem... items ) - { - for ( CredentialItem item : items ) - if ( !isUsernameOrPassword( item ) ) - return false; - return true; - } - - private boolean isUsernameOrPassword( CredentialItem item ) - { - return ( item instanceof CredentialItem.Username ) || ( item instanceof CredentialItem.Password ); - } - - @Override - public boolean get( URIish uri, CredentialItem... items ) throws UnsupportedCredentialItem - { - if ( !supports( items ) ) - return false; - counter++; - boolean previousAuthenticationFailed = counter > 1; - if ( previousAuthenticationFailed || missingCredentials() ) - if ( !queryPassword( uri.toString(), previousAuthenticationFailed ) ) - return false; - fillUsernameAndPassword( items ); - return true; - } - - private void fillUsernameAndPassword( CredentialItem[] items ) - { - for ( CredentialItem item : items ) - fillItem( item ); - } - - private void fillItem( CredentialItem item ) - { - if ( item instanceof CredentialItem.Username ) - ( ( CredentialItem.Username ) item ).setValue( username ); - else if ( item instanceof CredentialItem.Password ) - ( ( CredentialItem.Password ) item ).setValue( password.toCharArray() ); - } + return new SingleUseCredentialsProvider( this::getUsernameAndPassword ); } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/SingleUseCredentialsProvider.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/SingleUseCredentialsProvider.java new file mode 100644 index 00000000..754a9ae5 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/SingleUseCredentialsProvider.java @@ -0,0 +1,97 @@ +package org.mastodon.mamut.tomancak.collaboration.credentials; + +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** + * The JGIT api does not tell a CredentialsProvider if the credentials + * where correct. It simply asks for them again if they were wrong. + *

+ * We can exploit this behavior by counting the number of times the + * CredentialsProvider was asked for credentials. If it was asked more than + * once, we assume that the credentials were wrong. + *

+ * This only works if the CredentialsProvider is only used once. + */ +class SingleUseCredentialsProvider extends CredentialsProvider +{ + private final Callback callback; + + private int counter = 0; + + public SingleUseCredentialsProvider( Callback callback ) + { + this.callback = callback; + } + + @Override + public boolean isInteractive() + { + return true; + } + + @Override + public boolean supports( CredentialItem... items ) + { + for ( CredentialItem item : items ) + if ( !isUsernameOrPassword( item ) ) + return false; + return true; + } + + private boolean isUsernameOrPassword( CredentialItem item ) + { + return ( item instanceof CredentialItem.Username ) || ( item instanceof CredentialItem.Password ); + } + + @Override + public boolean get( URIish uri, CredentialItem... items ) throws UnsupportedCredentialItem + { + if ( !supports( items ) ) + throw new UnsupportedCredentialItem( uri, "" ); + counter++; + boolean previousAuthenticationFailed = counter > 1; + Pair< String, String > usernameAndPassword = callback.getUsernameAndPassword( uri, previousAuthenticationFailed ); + if ( usernameAndPassword == null ) + return false; + fillUsernameAndPassword( items, usernameAndPassword.getLeft(), usernameAndPassword.getRight() ); + return true; + } + + private void fillUsernameAndPassword( CredentialItem[] items, String username, String password ) + { + for ( CredentialItem item : items ) + fillItem( item, username, password ); + } + + private void fillItem( CredentialItem item, String username, String password ) + { + if ( item instanceof CredentialItem.Username ) + ( ( CredentialItem.Username ) item ).setValue( username ); + else if ( item instanceof CredentialItem.Password ) + ( ( CredentialItem.Password ) item ).setValue( password.toCharArray() ); + } + + /** + * Callback interface for {@link SingleUseCredentialsProvider}. + */ + interface Callback + { + + /** + * The {@link SingleUseCredentialsProvider} calls this method to get + * username and password for the given {@link URIish}. + * + * @param uri the URI for which credentials are requested. + * @param authenticationFailure true if the SingleUseCredentialsProvider + * thinks that the credentials were wrong. + * @return username and password or null if the user canceled the + * request. + * @see SingleUseCredentialsProvider + */ + Pair< String, String > getUsernameAndPassword( URIish uri, boolean authenticationFailure ); + } +} From 6d773fdcd0d309172c73048b1b42d3a36abed015 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 10 Nov 2023 12:14:53 +0100 Subject: [PATCH 60/76] Mastodon Git: clicking cancel on username, password dialog is handled better Previously: The user clicking cancel on the username & password dialog inderectly triggered a TransportException. The exception message would be shown in an error dialog, and the stack trace was printed on the console. Now: The user clicking cancel throws a CancellationExcpetion. The exception is catched at an appropriate location and no confusing error dialog or stack trace is shown. --- .../collaboration/MastodonGitController.java | 5 +++++ .../credentials/PersistentCredentials.java | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index ad549ab1..1fa082f7 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -31,6 +31,7 @@ import java.io.File; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CancellationException; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -384,6 +385,10 @@ private void run( String title, RunnableWithException action ) { action.run(); } + catch ( CancellationException e ) + { + // ignore + } catch ( Exception e ) { ErrorDialog.showErrorMessage( title, e ); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java index 6a68461e..33e14096 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/credentials/PersistentCredentials.java @@ -1,5 +1,7 @@ package org.mastodon.mamut.tomancak.collaboration.credentials; +import java.util.concurrent.CancellationException; + import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -9,6 +11,7 @@ import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.URIish; @@ -18,6 +21,13 @@ * The credentials are stored in memory and reused for all subsequent requests. * It also tries to detect if the user entered wrong credentials, * and asks for new credentials. + *

+ * WARNING: JGIT expects a {@link CredentialsProvider} to return false if the + * user cancels for example a username/password dialog. (see {@link CredentialsProvider#get}) + * This class behaves differently: It throws a {@link CancellationException} + * instead. This difference is on purpose. The CancellationException + * better describes the situation and is easier to handle, than the + * {@link org.eclipse.jgit.errors.TransportException} that JGIT throws. */ public class PersistentCredentials { @@ -35,12 +45,11 @@ private synchronized Pair< String, String > getUsernameAndPassword( URIish uri, { boolean missingCredentials = password == null || username == null; if ( missingCredentials || authenticationFailure ) - if ( !queryPassword( uri.toString(), authenticationFailure ) ) - return null; + queryPassword( uri.toString(), authenticationFailure ); return Pair.of( username, password ); } - private boolean queryPassword( String url, boolean previousAuthenticationFailed ) + private void queryPassword( String url, boolean previousAuthenticationFailed ) { JTextField usernameField = new JTextField( 20 ); JPasswordField passwordField = new JPasswordField( 20 ); @@ -59,11 +68,10 @@ private boolean queryPassword( String url, boolean previousAuthenticationFailed panel, "Authentication for Git Repository", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ); if ( !ok ) - return false; + throw new CancellationException( "User cancelled username & password dialog." ); username = usernameField.getText(); password = new String( passwordField.getPassword() ); - return true; } public CredentialsProvider getSingleUseCredentialsProvider() From ae511d1db359f7242d98dab502f81cbb82228817 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 10 Nov 2023 15:16:25 +0100 Subject: [PATCH 61/76] Mastodon Git: improve error message in NewDirectoryUtils --- .../collaboration/commands/NewDirectoryUtils.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java index 63c26433..4489c4af 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/NewDirectoryUtils.java @@ -57,15 +57,24 @@ static File createSubdirectory( File parentDirectory, String repositoryName ) th File directory = new File( parentDirectory, repositoryName ); if ( directory.isDirectory() ) { - if ( directory.list().length > 0 ) - throw new IOException( "Directory already exists and is not empty: " + directory ); - else + if ( isEmptyDirectory( directory ) ) return directory; + else + throw new IOException( "Directory already exists but is not empty: " + directory + + "\nPlease move or delete the directory and try again." ); } Files.createDirectory( directory.toPath() ); return directory; } + private static boolean isEmptyDirectory( File directory ) + { + String[] list = directory.list(); + if ( list == null ) // not a directory + return false; + return list.length == 0; + } + static String extractRepositoryName( String repositoryURL ) { Pattern pattern = Pattern.compile( "/([\\w-]+)(\\.git|/)?$" ); From cef6a52e13e49cfcc5a2085647e6a2a5afae8845 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 10 Nov 2023 15:16:39 +0100 Subject: [PATCH 62/76] Mastodon Git: fix typo --- .../collaboration/commands/MastodonGitCloneRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 14c81db8..5848763d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -60,7 +60,7 @@ public void run() } catch ( final Exception e ) { - ErrorDialog.showErrorMessage( "Download Shares Project (Clone)", e ); + ErrorDialog.showErrorMessage( "Download Shared Project (Clone)", e ); } } From 050375b43acbf4d5ed5cdc5a34674b59eba970e0 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Fri, 10 Nov 2023 15:17:38 +0100 Subject: [PATCH 63/76] Mastodon Git: include stack traces in the error message dialog Avoid to print stack traces on the console. --- .../tomancak/collaboration/ErrorDialog.java | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java index 52cc7ee2..a18f6915 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java @@ -1,20 +1,77 @@ package org.mastodon.mamut.tomancak.collaboration; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.event.ItemEvent; + +import javax.swing.JCheckBox; +import javax.swing.JDialog; import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.SwingUtilities; +import org.apache.commons.lang3.exception.ExceptionUtils; + public class ErrorDialog { - public static void showErrorMessage( String title, Exception e ) + + public static void showErrorMessage( String title, Exception exception ) + { + SwingUtilities.invokeLater( () -> showDialog( null, title + " (Error)", exception ) ); + } + + private static void showDialog( Frame parent, String text, Exception exception ) + { + String message = "\nThere was a problem:\n\n " + exception.getMessage() + "\n\n"; + final JScrollPane scrollPane = initScrollPane( exception ); + final JCheckBox checkBox = new JCheckBox( "show details" ); + checkBox.setForeground( Color.BLUE ); + Object[] objects = { message, checkBox, scrollPane }; + JOptionPane pane = new JOptionPane( objects, JOptionPane.ERROR_MESSAGE ); + JDialog dialog = pane.createDialog( parent, text ); + dialog.setResizable( true ); + checkBox.addItemListener( event -> { + boolean visible = event.getStateChange() == ItemEvent.SELECTED; + scrollPane.setVisible( visible ); + scrollPane.setPreferredSize( visible ? null : new Dimension( 0, 0 ) ); + dialog.pack(); + } ); + dialog.setModal( true ); + dialog.pack(); + dialog.setVisible( true ); + } + + private static JScrollPane initScrollPane( Exception exception ) + { + String stackTrace = ExceptionUtils.getStackTrace( exception ); + int lines = Math.min( 20, countLines( stackTrace ) ); + JTextArea textArea = new JTextArea( stackTrace, lines, 70 ); + textArea.setForeground( new Color( 0x880000 ) ); + textArea.setEditable( false ); + textArea.setFont( new Font( Font.MONOSPACED, Font.PLAIN, textArea.getFont().getSize() ) ); + final JScrollPane scrollPane = new JScrollPane( textArea ); + scrollPane.setVisible( false ); + return scrollPane; + } + + private static int countLines( String str ) { - e.printStackTrace(); - // show the error message in a nice dialog - String message = "\nThere was a problem:\n\n" + e.getMessage() + "\n"; - SwingUtilities.invokeLater( () -> JOptionPane.showMessageDialog( null, message, title + " (Error)", JOptionPane.ERROR_MESSAGE ) ); + String[] lines = str.split( "\r\n|\r|\n" ); + return lines.length; } public static void main( String... args ) { - showErrorMessage( "Test", new Exception( "Test exception" ) ); + try + { + throw new IllegalArgumentException( "Test exception" ); + } + catch ( Exception e ) + { + showErrorMessage( "Test", e ); + } } } From b2c868a4e3f1f401d83574d45f0ff509677191ad Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 14:03:09 +0100 Subject: [PATCH 64/76] MastodonGitRepository: improve exceptions Don't throw the very generic RuntimeException --- .../collaboration/MastodonGitRepository.java | 23 +++++++++++-------- .../exceptions/MastodonGitException.java | 20 ++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/MastodonGitException.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index aa1f72a9..5bc7df2e 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -60,6 +60,7 @@ import org.mastodon.mamut.tomancak.collaboration.credentials.PersistentCredentials; import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeConflictException; import org.mastodon.mamut.tomancak.collaboration.exceptions.GraphMergeException; +import org.mastodon.mamut.tomancak.collaboration.exceptions.MastodonGitException; import org.mastodon.mamut.tomancak.collaboration.utils.ConflictUtils; import org.mastodon.mamut.tomancak.collaboration.settings.MastodonGitSettingsService; import org.mastodon.mamut.tomancak.merging.Dataset; @@ -102,7 +103,7 @@ public static MastodonGitRepository shareProject( .call(); Path mastodonProjectPath = directory.toPath().resolve( "mastodon.project" ); if ( Files.exists( mastodonProjectPath ) ) - throw new RuntimeException( "The repository already contains a shared mastodon project: " + repositoryURL ); + throw new MastodonGitException( "The repository already contains a shared mastodon project: " + repositoryURL ); Files.createDirectory( mastodonProjectPath ); windowManager.getProjectManager().saveProject( mastodonProjectPath.toFile() ); Files.copy( mastodonProjectPath.resolve( "gui.xml" ), mastodonProjectPath.resolve( "gui.xml_remote" ) ); @@ -184,12 +185,12 @@ private static void raiseExceptionOnUnsuccessfulPush( Iterable< PushResult > res for ( RemoteRefUpdate update : result.getRemoteUpdates() ) { if ( update.getStatus() == RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ) - throw new RuntimeException( "The remote server has changes, that you didn't download yet.\n" + throw new MastodonGitException( "The remote server has changes, that you didn't download yet.\n" + "Please download changes first. (pull)\n" + "You can upload your changes afterwards.\n" ); if ( update.getStatus() != RemoteRefUpdate.Status.OK && update.getStatus() != RemoteRefUpdate.Status.UP_TO_DATE ) - throw new RuntimeException( "Push failed: " + update.getMessage() + " " + update.getStatus() ); + throw new MastodonGitException( "Push failed: " + update.getMessage() + " " + update.getStatus() ); } } } @@ -214,7 +215,7 @@ public synchronized void switchBranch( String branchName ) throws Exception String simpleName = getSimpleName( branchName ); boolean conflict = git.branchList().call().stream().map( Ref::getName ).anyMatch( localName -> simpleName.equals( getSimpleName( localName ) ) ); if ( conflict ) - throw new RuntimeException( "There's already a local branch with the same name." ); + throw new MastodonGitException( "There's already a local branch with the same name." ); git.checkout() .setCreateBranch( true ) .setName( simpleName ) @@ -321,10 +322,12 @@ private void automaticMerge( Context context, MamutProject project, File project saveModel( context, mergedModel, project ); commitWithoutSave( "Automatic merge by Mastodon during pull" ); } + catch ( GraphMergeException e ) + { + throw e; + } catch ( Throwable t ) { - if ( t instanceof GraphMergeException ) - throw t; throw new GraphMergeException( "There was a failure, when merging changes to the Model.", t ); } } @@ -340,7 +343,7 @@ private static void saveModel( Context context, Model model, MamutProject projec } } - private static Model merge( Dataset dsA, Dataset dsB ) throws IOException, SpimDataException + private static Model merge( Dataset dsA, Dataset dsB ) { final MergeDatasets.OutputDataSet output = new MergeDatasets.OutputDataSet(); double distCutoff = 1000; @@ -375,10 +378,10 @@ private synchronized Git initGit( File projectRoot ) throws IOException { boolean correctFolder = projectRoot.getName().equals( "mastodon.project" ); if ( !correctFolder ) - throw new RuntimeException( "The current project does not appear to be in a git repo." ); + throw new MastodonGitException( "The current project does not appear to be in a git repo." ); File gitRoot = projectRoot.getParentFile(); if ( !new File( gitRoot, ".git" ).exists() ) - throw new RuntimeException( "The current project does not appear to be in a git repo." ); + throw new MastodonGitException( "The current project does not appear to be in a git repo." ); return Git.open( gitRoot ); } @@ -443,7 +446,7 @@ private void ensureClean( Git git, String title ) throws GitAPIException windowManager.getProjectManager().saveProject(); boolean clean = isClean( git ); if ( !clean ) - throw new RuntimeException( "There are uncommitted changes. Please add a save point before " + title + "." ); + throw new MastodonGitException( "There are uncommitted changes. Please add a save point before " + title + "." ); } public boolean isClean() throws Exception diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/MastodonGitException.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/MastodonGitException.java new file mode 100644 index 00000000..6cc64617 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/exceptions/MastodonGitException.java @@ -0,0 +1,20 @@ +package org.mastodon.mamut.tomancak.collaboration.exceptions; + +/** + * Exception class that is used in + * {@link org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository}. + * The exception messages should be user-friendly. Such that they can be + * printed to the user. + */ +public class MastodonGitException extends RuntimeException +{ + public MastodonGitException( final String message ) + { + super( message ); + } + + public MastodonGitException( final String message, final Throwable cause ) + { + super( message, cause ); + } +} From 23462c7429177cc4de2c8c1773f93666d89d14a5 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 14:03:38 +0100 Subject: [PATCH 65/76] MastodonGitRepository: remove unused code --- .../collaboration/MastodonGitRepository.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java index 5bc7df2e..4ecabba5 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitRepository.java @@ -402,26 +402,6 @@ public boolean isRepository() } } - public static void main( String... args ) - { - - File dir = new File( "/home/arzt/tmp/2/mgit-test" ); - File projectRoot = new File( dir, "mastodon.project" ); - try (Git git = Git.open( dir ); Context context = new Context()) - { - git.checkout() - .addPath( "mastodon.project/gui.xml" ) - .addPath( "mastodon.project/model.raw" ) - .addPath( "mastodon.project/tags.raw" ) - .addPath( "mastodon.project/ksdfghksdgh.raw" ) - .call(); - } - catch ( Exception e ) - { - throw new RuntimeException( e ); - } - } - private static void abortMerge( Git git ) throws Exception { Repository repository = git.getRepository(); From 3736a42a367026445c34874461676c2be3674d4d Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 14:31:52 +0100 Subject: [PATCH 66/76] Mastodon Git: refactor the dialogues Move the demo code into the src/test/java folder. Make sure the dialogues are properly disposed such that they can be garbage collected and that the JVM properly shuts down. --- .../collaboration/CommitMessageDialog.java | 20 ++++++++++++----- .../tomancak/collaboration/ErrorDialog.java | 22 +++++++------------ .../collaboration/NotificationDialog.java | 9 ++++---- .../CommitMessageDialogDemo.java | 9 ++++++++ .../collaboration/ErrorDialogDemo.java | 16 ++++++++++++++ .../collaboration/NotificationDialogDemo.java | 9 ++++++++ 6 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialogDemo.java create mode 100644 src/test/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialogDemo.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java index d83f43a8..e11cee9a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java @@ -8,20 +8,28 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; -import javax.swing.WindowConstants; import net.miginfocom.swing.MigLayout; +/** + * A dialog that asks the user to enter a commit message. + * The dialog can be closed by using the mouse to clock OK or Cancel, + * by pressing CTRL-ENTER or ESCAPE, or by pressing TAB and ENTER. + */ public class CommitMessageDialog { - public static void main( String... args ) - { - System.out.println( showDialog( "Add Save Point (Commit)" ) ); - } + /** + * Show a dialog that asks the user to enter a commit message. + * + * @param title The title of the dialog. + * @return The commit message as a String, or null if the dialog was cancelled. + */ public static String showDialog( String title ) { JTextArea textArea = new JTextArea( 5, 40 ); + + // Make the tab key move focus to the next component and shift-tab to the previous. textArea.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null ); textArea.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null ); @@ -42,8 +50,8 @@ public void selectInitialValue() } }; JDialog dialog = optionPane.createDialog( "Add Save Point (commit)" ); - dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); dialog.setVisible( true ); + dialog.dispose(); Object result = optionPane.getValue(); if ( result instanceof Integer && ( int ) result == JOptionPane.OK_OPTION ) return textArea.getText(); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java index a18f6915..8ded6e72 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialog.java @@ -15,6 +15,11 @@ import org.apache.commons.lang3.exception.ExceptionUtils; +/** + * A dialog that shows an exception message and stack trace. + * The stack trace is hidden by default and can be shown by + * clicking on "details". + */ public class ErrorDialog { @@ -23,7 +28,7 @@ public static void showErrorMessage( String title, Exception exception ) SwingUtilities.invokeLater( () -> showDialog( null, title + " (Error)", exception ) ); } - private static void showDialog( Frame parent, String text, Exception exception ) + private static void showDialog( Frame parent, String title, Exception exception ) { String message = "\nThere was a problem:\n\n " + exception.getMessage() + "\n\n"; final JScrollPane scrollPane = initScrollPane( exception ); @@ -31,7 +36,7 @@ private static void showDialog( Frame parent, String text, Exception exception ) checkBox.setForeground( Color.BLUE ); Object[] objects = { message, checkBox, scrollPane }; JOptionPane pane = new JOptionPane( objects, JOptionPane.ERROR_MESSAGE ); - JDialog dialog = pane.createDialog( parent, text ); + JDialog dialog = pane.createDialog( parent, title ); dialog.setResizable( true ); checkBox.addItemListener( event -> { boolean visible = event.getStateChange() == ItemEvent.SELECTED; @@ -42,6 +47,7 @@ private static void showDialog( Frame parent, String text, Exception exception ) dialog.setModal( true ); dialog.pack(); dialog.setVisible( true ); + dialog.dispose(); } private static JScrollPane initScrollPane( Exception exception ) @@ -62,16 +68,4 @@ private static int countLines( String str ) String[] lines = str.split( "\r\n|\r|\n" ); return lines.length; } - - public static void main( String... args ) - { - try - { - throw new IllegalArgumentException( "Test exception" ); - } - catch ( Exception e ) - { - showErrorMessage( "Test", e ); - } - } } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java index 7a2a015f..187a9a1d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialog.java @@ -4,12 +4,11 @@ import javax.swing.JOptionPane; import javax.swing.Timer; +/** + * A dialog that shows a message for a short time and then disappears. + */ public class NotificationDialog { - public static void main( String... args ) - { - show( "Upload Changes", " Changes were uploaded successfully." ); - } public static void show( String title, String message ) { @@ -17,6 +16,6 @@ public static void show( String title, String message ) JDialog dialog = pane.createDialog( null, title ); dialog.setModal( false ); dialog.setVisible( true ); - new Timer( 1500, ignore -> dialog.setVisible( false ) ).start(); + new Timer( 1500, ignore -> dialog.dispose() ).start(); } } diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java new file mode 100644 index 00000000..dbe94564 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java @@ -0,0 +1,9 @@ +package org.mastodon.mamut.tomancak.collaboration; + +public class CommitMessageDialogDemo +{ + public static void main( String... args ) + { + System.out.println( CommitMessageDialog.showDialog( "Add Save Point (Commit)" ) ); + } +} diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialogDemo.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialogDemo.java new file mode 100644 index 00000000..56f702fa --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/ErrorDialogDemo.java @@ -0,0 +1,16 @@ +package org.mastodon.mamut.tomancak.collaboration; + +public class ErrorDialogDemo +{ + public static void main( String... args ) + { + try + { + throw new IllegalArgumentException( "Test exception" ); + } + catch ( Exception e ) + { + ErrorDialog.showErrorMessage( "Title", e ); + } + } +} diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialogDemo.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialogDemo.java new file mode 100644 index 00000000..5356bb4e --- /dev/null +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/NotificationDialogDemo.java @@ -0,0 +1,9 @@ +package org.mastodon.mamut.tomancak.collaboration; + +public class NotificationDialogDemo +{ + public static void main( String... args ) + { + NotificationDialog.show( "Upload Changes", " Changes were uploaded successfully." ); + } +} From 3170f4d2900c77ea8733b07cea01354c5aa7853f Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 15:04:02 +0100 Subject: [PATCH 67/76] MastodonGitController: move the TODO notes into a github issue --- .../collaboration/MastodonGitController.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 1fa082f7..7c3a0006 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -54,7 +54,6 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -// TODO: disable commands if not in a git repo @Plugin( type = MamutPlugin.class ) public class MastodonGitController extends BasicMamutPlugin { @@ -220,7 +219,6 @@ private void switchBranch() { try { - // TODO: the branches are not formatted nicely String message = "Select a branch"; try { @@ -287,7 +285,6 @@ private void pull() private void suggestPullAlternative( String errorMessage ) { - // TODO: add pull alternative, save to new branch String title = "Conflict During Download Of Changes (Pull)"; String message = "There was a merge conflict during the pull. Details:\n" + " " + errorMessage + "\n\n" @@ -339,19 +336,6 @@ private void showBranchName() private void synchronize() { - // check if clean - // - yes, continue - // - no -> ask for commit message, and commit - - // pull - // try to merge - // - no conflict -> continue - // - conflict, let the user change an option: - // - discard local changes - // - cancel - - // push - // notify about success or failure run( "Synchronize Changes", () -> { boolean clean = repository.isClean(); if ( !clean ) From 24f299481bc6697a3510b34b348f0cc38ce6cc06 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 15:05:38 +0100 Subject: [PATCH 68/76] MastodonGitController: make the command identifiers more user-friendly Users will see the command identifiers in the short-cuts config page. These identifiers should therefor be understandable. --- .../collaboration/MastodonGitController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 7c3a0006..38ae399d 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -72,43 +72,43 @@ public class MastodonGitController extends BasicMamutPlugin MastodonGitController::shareProject ); private static final String CLONE_REPOSITORY_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] clone", + "[mastodon git] download shared project (clone)", "Plugins > Git > Initialize > Download Shared Project (clone)", "Download a shared project, save a copy on the local disc and open it with Mastodon.", MastodonGitController::cloneGitRepository ); private static final String SET_AUTHOR_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] set author", + "[mastodon git] set author name", "Plugins > Git > Initialize > Set Author Name", "Set the author name that is used for your commits.", MastodonGitController::setAuthor ); private static final String SYNCHRONIZE_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] synchronize", + "[mastodon git] synchronize (commit, pull, push)", "Plugins > Git > Synchronize (commit, pull, push)", "Download remote changes and upload local changes.", MastodonGitController::synchronize ); private static final String COMMIT_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] commit", + "[mastodon git] add save point (commit)", "Plugins > Git > Add Save Point (commit)", "Commit changes to the git repository.", MastodonGitController::commit ); private static final String PUSH_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] push", + "[mastodon git] upload changes (push)", "Plugins > Git > Upload Changes (push)", "Push local changed to the remote server.", MastodonGitController::push ); private static final String PULL_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] pull", + "[mastodon git] download changes (pull)", "Plugins > Git > Download Changes (pull)", "Pull changes from the remote server and merge them with my changes.", MastodonGitController::pull ); private static final String RESET_ACTION_KEY = actionDescriptions.addActionDescription( - "[mastodon git] git reset", + "[mastodon git] go back to latest save point (reset)", "Plugins > Git > Go Back To Latest Save Point (reset)", "Discard all changes made since the last save point.", MastodonGitController::reset ); From 09effdcc554688e042b049fdbf3b58383f38f0eb Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 15:53:12 +0100 Subject: [PATCH 69/76] CommitMessageDialog: remove unused parameter --- .../mamut/tomancak/collaboration/CommitMessageDialog.java | 3 +-- .../mamut/tomancak/collaboration/MastodonGitController.java | 2 +- .../mamut/tomancak/collaboration/CommitMessageDialogDemo.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java index e11cee9a..2dc24a14 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialog.java @@ -22,10 +22,9 @@ public class CommitMessageDialog /** * Show a dialog that asks the user to enter a commit message. * - * @param title The title of the dialog. * @return The commit message as a String, or null if the dialog was cancelled. */ - public static String showDialog( String title ) + public static String showDialog() { JTextArea textArea = new JTextArea( 5, 40 ); diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 38ae399d..2cb6b70c 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -340,7 +340,7 @@ private void synchronize() boolean clean = repository.isClean(); if ( !clean ) { - String commitMessage = CommitMessageDialog.showDialog( "Add Save Point (commit)" ); + String commitMessage = CommitMessageDialog.showDialog(); if ( commitMessage == null ) return; repository.commitWithoutSave( commitMessage ); diff --git a/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java index dbe94564..fe4303be 100644 --- a/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java +++ b/src/test/java/org/mastodon/mamut/tomancak/collaboration/CommitMessageDialogDemo.java @@ -4,6 +4,6 @@ public class CommitMessageDialogDemo { public static void main( String... args ) { - System.out.println( CommitMessageDialog.showDialog( "Add Save Point (Commit)" ) ); + System.out.println( CommitMessageDialog.showDialog() ); } } From efc98a7bda3ce75863c3e0ef37e555e805868592 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 15:54:20 +0100 Subject: [PATCH 70/76] MastodonGitController: improve descriptions --- .../mamut/tomancak/collaboration/MastodonGitController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 2cb6b70c..3dbf234e 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -104,7 +104,7 @@ public class MastodonGitController extends BasicMamutPlugin private static final String PULL_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] download changes (pull)", "Plugins > Git > Download Changes (pull)", - "Pull changes from the remote server and merge them with my changes.", + "Download changes from the remote server and merge them with my changes.", MastodonGitController::pull ); private static final String RESET_ACTION_KEY = actionDescriptions.addActionDescription( @@ -195,7 +195,7 @@ private void commit() { if ( !settingsService.isAuthorSpecified() ) { - askForAuthorName( "Please set your author name before your first commit." ); + askForAuthorName( "Please set your author name before adding a save point (commit)." ); return; } commandService.run( MastodonGitCommitCommand.class, true, "repository", repository ); From baf318d2f797daf8f104814a622e56ca87cdc178 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 16:05:07 +0100 Subject: [PATCH 71/76] Remove MastodonGitCommitCommand The class is replace with the CommitMessageDialog --- .../collaboration/MastodonGitController.java | 14 ++++++-- .../commands/MastodonGitCommitCommand.java | 36 ------------------- 2 files changed, 12 insertions(+), 38 deletions(-) delete mode 100644 src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 3dbf234e..095df4d3 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -38,7 +38,6 @@ import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCloneRepository; -import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCommitCommand; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitCreateRepository; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitNewBranch; import org.mastodon.mamut.tomancak.collaboration.commands.MastodonGitSetAuthorCommand; @@ -198,7 +197,18 @@ private void commit() askForAuthorName( "Please set your author name before adding a save point (commit)." ); return; } - commandService.run( MastodonGitCommitCommand.class, true, "repository", repository ); + run( "Add Save Point (Commit)", () -> { + if ( repository.isClean() ) + NotificationDialog.show( "Add Save Point (Commit)", + " No changes to commit." ); + else + { + String commitMessage = CommitMessageDialog.showDialog(); + if ( commitMessage == null ) + return; + repository.commitWithoutSave( commitMessage ); + } + } ); } private void push() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java deleted file mode 100644 index d30f3013..00000000 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCommitCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.mastodon.mamut.tomancak.collaboration.commands; - -import org.mastodon.mamut.tomancak.collaboration.ErrorDialog; -import org.scijava.ItemVisibility; -import org.scijava.command.Command; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -import org.mastodon.mamut.tomancak.collaboration.MastodonGitRepository; - -@Plugin( type = Command.class, label = "Add Save Point (commit)", visible = false ) -public class MastodonGitCommitCommand extends AbstractCancellable implements Command -{ - @Parameter - private MastodonGitRepository repository; - - @Parameter( label = "Save point message", style = "text area", persist = false, - description = "A good message, is very helpful when inspecting the history of changes." ) - private String commitMessage; - - @Parameter( visibility = ItemVisibility.MESSAGE ) - private String comment = "Please describe briefly the changes since the last save point!"; - - @Override - public void run() - { - try - { - repository.commit( commitMessage ); - } - catch ( Exception e ) - { - ErrorDialog.showErrorMessage( "Add Save Point (Commit)", e ); - } - } -} From 69f265ad6b369ceb31d00278e25b722302acd176 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 16:06:03 +0100 Subject: [PATCH 72/76] Mastodon Git: rename Fiji menu entry for downloading a shared project --- .../collaboration/commands/MastodonGitCloneRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 5848763d..16970c2b 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -15,7 +15,7 @@ // - fill repositoryName with a default value based on the repositoryURL @Plugin( type = Command.class, label = "Mastodon Git - Download Shared Project (clone)", - menuPath = "Plugins > Mastodon Git > Download Shared Project" ) + menuPath = "Plugins > Mastodon Collaborative (Git) > Download Shared Project" ) public class MastodonGitCloneRepository extends AbstractCancellable implements Command { @Parameter From 9c7cc4aa766a62b83bd93dc306328d9662e07309 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 16:18:52 +0100 Subject: [PATCH 73/76] Mastodon Git: make parameter private and final if possible --- .../commands/MastodonGitCloneRepository.java | 8 ++++---- .../commands/MastodonGitCreateRepository.java | 10 +++++----- .../commands/MastodonGitSetAuthorCommand.java | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java index 16970c2b..ad58da66 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCloneRepository.java @@ -19,10 +19,10 @@ public class MastodonGitCloneRepository extends AbstractCancellable implements Command { @Parameter - Context context; + private Context context; @Parameter( label = "URL on github or gitlab", description = URL_DESCRIPTION ) - String repositoryURL; + private String repositoryURL; private static final String URL_DESCRIPTION = "" + "Here are two examples of valid URLs:
" @@ -33,7 +33,7 @@ public class MastodonGitCloneRepository extends AbstractCancellable implements C + ""; @Parameter( label = "Directory, to store the project:", style = "directory", description = DIRECTORY_DESCRIPTION ) - File directory; + private File directory; private static final String DIRECTORY_DESCRIPTION = "" + "A copy of the shared project will be downloaded to your computer.
" @@ -42,7 +42,7 @@ public class MastodonGitCloneRepository extends AbstractCancellable implements C + ""; @Parameter( label = "Create new subdirectory", required = false, description = CREATE_SUBDIRECTORY_DESCRIPTION ) - boolean createSubdirectory = false; + private boolean createSubdirectory = false; private static final String CREATE_SUBDIRECTORY_DESCRIPTION = "" + "If selected, a new subdirectory will be created in the selected directory.
" diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java index cea3b658..c8014269 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitCreateRepository.java @@ -42,7 +42,7 @@ public class MastodonGitCreateRepository extends AbstractCancellable implements Command { @Parameter( visibility = ItemVisibility.MESSAGE ) - private String text = "" + private final String text = "" + "

Share Project

" + "

Share the current project on github or gitlab.

" + "

Go to github.com or to ure institute's gitlab and create a new repository.

" @@ -50,16 +50,16 @@ public class MastodonGitCreateRepository extends AbstractCancellable implements + "

A copy of will be created in the directory you specify, and then uploaded to the specified URL.

"; @Parameter - Callback callback; + private Callback callback; @Parameter( label = "URL on github or gitlab" ) - String repositoryURL; + private String repositoryURL; @Parameter( label = "Directory to contain the repository", style = "directory" ) - File directory; + private File directory; @Parameter( label = "Create new subdirectory", required = false ) - boolean createSubdirectory = false; + private boolean createSubdirectory = false; @Override public void run() diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java index 3fd215ba..de3e178c 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/commands/MastodonGitSetAuthorCommand.java @@ -14,7 +14,7 @@ public class MastodonGitSetAuthorCommand extends AbstractCancellable implements private MastodonGitSettingsService settings; @Parameter( visibility = ItemVisibility.MESSAGE ) - private String description = "" + private final String description = "" + "The name and email that you specify below
" + "are used to identify you as the author of the
" + "changes you make to the shared project.

" From 136eeb1f8137ee67e40fa38112f397db2d076107 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 16:32:20 +0100 Subject: [PATCH 74/76] Clean up BasicMamutPlugin and BasicDescriptionProvider --- .../collaboration/utils/BasicDescriptionProvider.java | 4 ++-- .../mamut/tomancak/collaboration/utils/BasicMamutPlugin.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java index 04ca24a6..325aea79 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicDescriptionProvider.java @@ -41,7 +41,7 @@ public abstract class BasicDescriptionProvider extends CommandDescriptionProvide private final ActionDescriptions< ? > actionDescriptions; - public BasicDescriptionProvider( ActionDescriptions< ? > actionDescriptions, String... contexts ) + protected BasicDescriptionProvider( ActionDescriptions< ? > actionDescriptions, String... contexts ) { super( contexts ); this.actionDescriptions = actionDescriptions; @@ -50,7 +50,7 @@ public BasicDescriptionProvider( ActionDescriptions< ? > actionDescriptions, Str @Override public void getCommandDescriptions( CommandDescriptions descriptions ) { - for ( ActionDescriptions.Entry entry : actionDescriptions.getEntries() ) + for ( ActionDescriptions.Entry< ? > entry : actionDescriptions.getEntries() ) descriptions.add( entry.key, entry.shortCuts, entry.description ); } diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java index cfd88780..749215cf 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/BasicMamutPlugin.java @@ -50,7 +50,7 @@ * See {@link MastodonGitController} for * usage example. */ -public class BasicMamutPlugin implements MamutPlugin +public abstract class BasicMamutPlugin implements MamutPlugin { private final List< ActionDescriptions.Entry< ? > > actionDescriptions; @@ -65,7 +65,7 @@ public class BasicMamutPlugin implements MamutPlugin private WindowManager windowManager; - public < T > BasicMamutPlugin( ActionDescriptions< T > description ) + protected < T > BasicMamutPlugin( ActionDescriptions< T > description ) { if ( !this.getClass().equals( description.getPluginClass() ) ) throw new IllegalArgumentException( "Plugin class mismatch." ); From 911d9fe12202796a11e5a9ad7af19a57bb3de2d0 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Mon, 13 Nov 2023 16:54:03 +0100 Subject: [PATCH 75/76] Mastodon Git: add javadoc --- .../mamut/tomancak/collaboration/utils/ConflictUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java index 383e3587..e2fe1492 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/utils/ConflictUtils.java @@ -46,6 +46,9 @@ private static boolean isConflictTagSetName( String name ) name.startsWith( "((B)) " ); } + /** + * Returns true if the given tag set is empty or if it does not exist. + */ private static boolean isTagSetEmpty( TagSetModel< Spot, Link > tagSetModel, String tagSetName, String tagLabel ) { TagSetStructure tagSetStructure = tagSetModel.getTagSetStructure(); From ff26e88e80be3de6fede9fe0a8d38b042af142b9 Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Tue, 14 Nov 2023 14:36:15 +0100 Subject: [PATCH 76/76] Mastodon Git: rename menu entry to "Collaborative (Git)" --- .../collaboration/MastodonGitController.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java index 095df4d3..f4845518 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java +++ b/src/main/java/org/mastodon/mamut/tomancak/collaboration/MastodonGitController.java @@ -66,73 +66,73 @@ public class MastodonGitController extends BasicMamutPlugin private static final String SHARE_PROJECT_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] share project", - "Plugins > Git > Initialize > Share Project", + "Plugins > Collaborative (Git) > Initialize > Share Project", "Upload Mastodon project to a newly created git repository.", MastodonGitController::shareProject ); private static final String CLONE_REPOSITORY_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] download shared project (clone)", - "Plugins > Git > Initialize > Download Shared Project (clone)", + "Plugins > Collaborative (Git) > Initialize > Download Shared Project (clone)", "Download a shared project, save a copy on the local disc and open it with Mastodon.", MastodonGitController::cloneGitRepository ); private static final String SET_AUTHOR_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] set author name", - "Plugins > Git > Initialize > Set Author Name", + "Plugins > Collaborative (Git) > Initialize > Set Author Name", "Set the author name that is used for your commits.", MastodonGitController::setAuthor ); private static final String SYNCHRONIZE_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] synchronize (commit, pull, push)", - "Plugins > Git > Synchronize (commit, pull, push)", + "Plugins > Collaborative (Git) > Synchronize (commit, pull, push)", "Download remote changes and upload local changes.", MastodonGitController::synchronize ); private static final String COMMIT_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] add save point (commit)", - "Plugins > Git > Add Save Point (commit)", + "Plugins > Collaborative (Git) > Add Save Point (commit)", "Commit changes to the git repository.", MastodonGitController::commit ); private static final String PUSH_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] upload changes (push)", - "Plugins > Git > Upload Changes (push)", + "Plugins > Collaborative (Git) > Upload Changes (push)", "Push local changed to the remote server.", MastodonGitController::push ); private static final String PULL_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] download changes (pull)", - "Plugins > Git > Download Changes (pull)", + "Plugins > Collaborative (Git) > Download Changes (pull)", "Download changes from the remote server and merge them with my changes.", MastodonGitController::pull ); private static final String RESET_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] go back to latest save point (reset)", - "Plugins > Git > Go Back To Latest Save Point (reset)", + "Plugins > Collaborative (Git) > Go Back To Latest Save Point (reset)", "Discard all changes made since the last save point.", MastodonGitController::reset ); private static final String NEW_BRANCH_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] new branch", - "Plugins > Git > Branches > Create New Branch", + "Plugins > Collaborative (Git) > Branches > Create New Branch", "Create a new branch in the git repository.", MastodonGitController::newBranch ); private static final String SHOW_BRANCH_NAME_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] show branch name", - "Plugins > Git > Branches > Show Branch Name", + "Plugins > Collaborative (Git) > Branches > Show Branch Name", "Show the name of the current git branch", MastodonGitController::showBranchName ); private static final String SWITCH_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] switch branch", - "Plugins > Git > Branches > Switch Branch", + "Plugins > Collaborative (Git) > Branches > Switch Branch", "Switch to a different branch in the git repository.", MastodonGitController::switchBranch ); private static final String MERGE_ACTION_KEY = actionDescriptions.addActionDescription( "[mastodon git] merge branch", - "Plugins > Git > Branches > Merge Branch", + "Plugins > Collaborative (Git) > Branches > Merge Branch", "Merge a branch into the current branch.", MastodonGitController::mergeBranch );