Push first working version

This commit is contained in:
François TERROT 2021-03-07 11:56:15 +01:00
parent deb8cb91a8
commit 516c45d07b
86 changed files with 3046 additions and 37 deletions

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "io.gitea.mylyn.updatesite"]
path = io.gitea.mylyn.updatesite
url = https://forge.chapril.org/gitea/mylyn-gitea-updatesite.git
[submodule "java.gitea.api"]
path = java.gitea.api
url = https://forge.chapril.org/gitea/java.gitea.api.git

11
.project Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>mylyn-gitea</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

View File

@ -1,34 +1,40 @@
# mylyn-gitea
Gitea Mylyn Connector : Gitea Issues management for Eclipse/Mylyn.
Mylyn/Gitea Tasks Connector : Gitea's issues management for Eclipse/Mylyn.
This Mylyn connector will allow you to connect Mylyn to a Gitea instance (self hosted or not) in order to manage your issues on Gitea with your local Eclipse instance.
This Mylyn connector will allow you to connect Mylyn to a Gitea's instance (self hosted or not) in order to manage your issues from your local Eclipse instance.
The update site for the Mylyn/Gitea Tasks Connector plug-in is https://forge.chapril.org/gitea/mylyn-gitea-updatesite.
## Thanks and Credits
I want to thanks and credit:
* pweingardt for the "Mylyn Gilab Connector" https://github.com/pweingardt/mylyn-gitlab, the "Mylyn Gitea Connector" which helps me in my initial plugin setup.
* zeripath for the "Java Gitea API" https://github.com/zeripath/java-gitea-api , I used first to communicate with Gitea instance before facing ssl and JDK 11 compliance issues.
* pweingardt for the "Mylyn Gilab Connector" https://github.com/pweingardt/mylyn-gitlab, the "Mylyn Gitlab Connector" which helps me in my initial plug-ins setup.
* zeripath for the "Java Gitea API" https://github.com/zeripath/java-gitea-api , I used first to communicate with Gitea's instance before facing ssl and JDK 11 compliance issues.
## Changes
* 08.02.2021 - I'm currently working to make the connector working, resolving package and installation issues (plugins crash on eclipse start due to missing class Exception), then I will debug basic features (repository connectorion ...) before I will upload first code and binaries.
* 28.02.2021 - First connector version is working in debug in front og https://forge.chapril.org. I still have maven/jdk 11 generation issue. So I switch to a newly generated "Java Gitea API", OpenAPI based.
* 08.02.2021 - I'm currently working to make the connector working, resolving package and installation issues (plug-ins crash on eclipse start due to missing class Exception), then I will debug basic features (repository connectorion ...) before I will upload first code and binaries.
* 28.02.2021 - First connector version is working in debug in front of https://forge.chapril.org. I still have maven/jdk 11 generation issue. So I switch to a newly generated "Java Gitea API", OpenAPI based.
## Planned Features
### Version 0.1.x - prove of concept
* use basic (user,password) or token authentication (preferred)
* list and query for issues
* handles issues on a repository basis
- [ ] use basic (user,password) or token authentication (preferred)
- [ ] list and query for issues
- [ ] handles issues on a repository basis
- [ ] installation though update site
### Planned features
* edit issue - add comment on issues
* edit issue - close/reopen issue
* edit issue - change assignee list, labels, project, milestone
* create issue
* enhance query hmi - labels selection per list
- [ ] edit issue - add comment on issues
- [ ] edit issue - close/reopen issue
- [ ] edit issue - change assignee list, labels, project, milestone
- [ ] create issue
- [ ] enhance query hmi - labels selection per list
### Missing features
@ -36,34 +42,26 @@ I want to thanks and credit:
### Won't be implemented
The goal of this connector is not to replaced the Gitea web interfac, so everything which is not Issue related or not link with what may help a developper in his day-to-day issue management will not be implemented, typically Team/Repository management and milestone management.
The goal of this connector is not to replaced the Gitea's web interface, so everything which is not Issue related or not link with what may help a developer in his day-to-day issues management
like Team/Repository management and milestone management will not be implemented.
## Usage
1. Install the plugin obviously (you can use the https://github.com/teilginn/mylyn-gitea update site)
2. Add a new Connector, using the new Gitea Connector
1. enter the project URL (something like `http(s)://my-gitea-instance.org/myname_or_organisation/myrepository`)
2. enter your usename and your password
3. **Do not forget to check the "Save Password" checkbox**. I don't know how to create a password prompt...
3. You can now create queries and issues
If you use https instead of http (and you absolutely should use https), be sure you have a valid certificate. That means it is signed by a trusted CA. If you don't have a valid certificate (like a self signed certificate), the plugin will refuse to connect. If you want to add your CA certificate to the java keystore, you have to:
1. find the keystore which is used by your JVM (on my machine it is /etc/ssl/certs/java/cacerts)
2. find out the password for the keystore (the default is "changeit")
3. add the CA certificate to this keystore
1. On Linux, Mac OS X, or Unix systems, use `keytool -import -alias A-UNIQUE-ALIAS -file YOUR-CA.crt -keystore $PATH_TO_YOUR_KEYSTORE` (root permissions may be necessary)
2. On Windows, in an Administrator Command Prompt use `"%PROGRAMFILES%\java\jre7\bin\keytool" -import -alias A-UNIQUE-ALIAS -file YOUR-CA.cer -keystore "%PROGRAMFILES%\Java\jre7\lib\security\cacerts"`
I don't kown if I will introduce the option to ignore certificate errors. It is not a good way to do those kind of things, especially as it's now not so difficult to get a valid certificate for self hosted services thanks to Let's Encrypt.
see (Mylyn/Gitea Tasks Connector plug-ins update site)[https://forge.chapril.org/gitea/mylyn-gitea-updatesite]
## Known issues/Limitations
* List of Eclipse versions plugins is compliant with, is to be defined. It will be test with the version of Eclipse I'm using (Linux/Windows) in front of an always up to date gitea instance.
* Version 1.x will only support read acess and queries, we have may to wait the Version 2.x to create and modify issues.
* If you created a new milestone or added a new project member via the web interface, you have to update the repository configuration, so that the connector reloads the project members and milestones. Right click on the Gitea repository in the Task repositories view and click on "Update Repository Configuration".
* Offline mode does not work.
* Out of the box support for valid certificates only.
* Ssl connection issue with some servers
* [Known issues](https://github.com/teilginn/mylyn-gitea/issues?q=is%3Aopen+is%3Aissue)
* [known limitations](https://github.com/teilginn/mylyn-gitea/labels/known_limitation)
## Report an issue
To avoid any account management activity, issues tracker is handled at [Github](https://github.com/teilginn/mylyn-gitea/issues).
Feel free to open issues to report bugs or request new features.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry exported="true" kind="lib" path="lib/annotations-13.0.jar"/>
<classpathentry exported="true" kind="lib" path="lib/commons-lang3-3.11.jar"/>
<classpathentry exported="true" kind="lib" path="lib/gson-2.8.6.jar"/>
<classpathentry exported="true" kind="lib" path="lib/gson-fire-1.8.5.jar"/>
<classpathentry exported="true" kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry exported="true" kind="lib" path="lib/java.gitea.api-1.13.0.jar"/>
<classpathentry exported="true" kind="lib" path="lib/javax.annotation-api-1.3.2.jar"/>
<classpathentry exported="true" kind="lib" path="lib/jsr305-3.0.2.jar"/>
<classpathentry exported="true" kind="lib" path="lib/junit-4.13.1.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlin-stdlib-1.4.10.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlin-stdlib-common-1.4.0.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlin-stdlib-jdk7-1.4.10.jar"/>
<classpathentry exported="true" kind="lib" path="lib/kotlin-stdlib-jdk8-1.4.10.jar"/>
<classpathentry exported="true" kind="lib" path="lib/logging-interceptor-4.9.1.jar"/>
<classpathentry exported="true" kind="lib" path="lib/okhttp-4.9.1.jar"/>
<classpathentry exported="true" kind="lib" path="lib/okio-2.8.0.jar"/>
<classpathentry exported="true" kind="lib" path="lib/swagger-annotations-1.6.2.jar"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="org.eclipse.pde.api.tools.apiAnalysisBuilder"/>
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
</launchConfiguration>

24
io.gitea.mylyn.core/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/bin/

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>io.gitea.mylyn.core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/org.eclipse.pde.api.tools.apiAnalysisBuilder.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,15 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false

View File

@ -0,0 +1,19 @@
MIT License Copyright (c) 2021 F.Terrot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,37 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Mylyn Gitea Connector Core
Bundle-SymbolicName: io.gitea.mylyn.core;singleton:=true
Bundle-Version: 0.1.0
Bundle-Activator: io.gitea.mylyn.core.GiteaPluginCore
Bundle-Vendor: io.gitea
Require-Bundle: org.eclipse.core.runtime,
org.eclipse.mylyn.tasks.core;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.tasks.ui,
org.eclipse.mylyn.commons.net;bundle-version="[3.8.0,4.0.0)"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Automatic-Module-Name: io.gitea.mylyn.core
Bundle-ActivationPolicy: lazy
Export-Package: io.gitea,
io.gitea.api,
io.gitea.model,
io.gitea.mylyn.core,
io.gitea.mylyn.core.exceptions
Bundle-ClassPath: lib/annotations-13.0.jar,
lib/commons-lang3-3.11.jar,
lib/gson-2.8.6.jar,
lib/gson-fire-1.8.5.jar,
lib/hamcrest-core-1.3.jar,
lib/java.gitea.api-1.13.0.jar,
lib/javax.annotation-api-1.3.2.jar,
lib/jsr305-3.0.2.jar,
lib/junit-4.13.1.jar,
lib/kotlin-stdlib-1.4.10.jar,
lib/kotlin-stdlib-common-1.4.0.jar,
lib/kotlin-stdlib-jdk7-1.4.10.jar,
lib/kotlin-stdlib-jdk8-1.4.10.jar,
lib/logging-interceptor-4.9.1.jar,
lib/okhttp-4.9.1.jar,
lib/okio-2.8.0.jar,
lib/swagger-annotations-1.6.2.jar,
.

View File

@ -0,0 +1,22 @@
source.. = src/
bin.includes = META-INF/,\
.,\
plugin.xml,\
lib/annotations-13.0.jar,\
lib/commons-lang3-3.11.jar,\
lib/gson-2.8.6.jar,\
lib/gson-fire-1.8.5.jar,\
lib/hamcrest-core-1.3.jar,\
lib/java.gitea.api-1.13.0.jar,\
lib/javax.annotation-api-1.3.2.jar,\
lib/jsr305-3.0.2.jar,\
lib/junit-4.13.1.jar,\
lib/kotlin-stdlib-1.4.10.jar,\
lib/kotlin-stdlib-common-1.4.0.jar,\
lib/kotlin-stdlib-jdk7-1.4.10.jar,\
lib/kotlin-stdlib-jdk8-1.4.10.jar,\
lib/logging-interceptor-4.9.1.jar,\
lib/okhttp-4.9.1.jar,\
lib/okio-2.8.0.jar,\
lib/swagger-annotations-1.6.2.jar
jre.compilation.profile = JavaSE-1.8

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
id="io.gitea.mylyn.core"
name="%repository.name"
point="org.eclipse.mylyn.tasks.ui.repositories">
<connectorCore
class="io.gitea.mylyn.core.GiteaConnector"
id="io.gitea.mylyn.core"
name="Gitea Connector"/>
</extension>
</plugin>

View File

@ -0,0 +1,52 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.api;
import io.gitea.ApiClient;
/** Version of the Gitea API to communicate with. */
public enum ApiVersion {
V1;
/** API namespace to be used in URL. */
public String getApiNamespace() {
return "/api/" + name().toLowerCase();
}
/**
* Build the client Api base path (URL) adapted to the selected Api version.
*
* @param hostUrl Uri of the server hosting the Gitea service (with protocol,
* without trailing /).
*
* @return Url to be use to communication with Gitea
*
* @implNote No check is done on the validity of hostUrl
*/
public String getBasePath(String hostUrl) {
return hostUrl + getApiNamespace();
}
/**
* Set the client Api base path adapted to the selected Api version.
*
* @param apiClient Gitea Api client object to configure.
* @param hostUrl Uri of the server hosting the Gitea service (with protocol,
* without trailing /).
*
* @implNote No check is done on the validity of hostUrl
*
* <pre>
* {@code
* ApiClient defaultConfiguration = Configuration.getDefaultApiClient();
* String host = "https://forge.chapril.org" ;
* ApiVersion.V1.setBasePath(defaultConfiguration, host);
* }
* </pre>
*/
public void setBasePath(ApiClient apiClient, String hostUrl) {
apiClient.setBasePath(hostUrl + getApiNamespace());
}
}

View File

@ -0,0 +1,6 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
/** <i>local</i> extension of the generated Java Gitea API "io.gitea.api" package. */
package io.gitea.api;

View File

@ -0,0 +1,51 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.util.Date;
/** Handles Gitea to Mylyn date conversions */
public class GiteaDateTimeUtils {
private static final String dateFormat_from_LocalDateTime = "yyyy-MM-dd'T'HH:mm:ss"; //$NON-NLS-1$
private static final String dateFormat_from_Offset = "yyyy-MM-dd'T'HH:mm:ssX"; //$NON-NLS-1$
private static final String dateFormat_1 = "yyyy-MM-dd HH:mm:ss"; //$NON-NLS-1$
private static final String dateFormat_2 = "yyyy-MM-dd HH:mm"; //$NON-NLS-1$
private static final String dateFormat_3 = "yyyy-MM-dd"; //$NON-NLS-1$
private static final String dateFormat_1_TimeZone = "yyyy-MM-dd HH:mm:ss Z"; //$NON-NLS-1$
private static final String dateFormat_2_TimeZone = "yyyy-MM-dd HH:mm z"; //$NON-NLS-1$
private static final String dateFormat_3_TimeZone = "yyyy-MM-dd z"; //$NON-NLS-1$
// Order is significant
private static final String[] dateFormats = { dateFormat_from_Offset, dateFormat_from_LocalDateTime,
dateFormat_1_TimeZone, dateFormat_1, dateFormat_2_TimeZone, dateFormat_2, dateFormat_3_TimeZone,
dateFormat_3 };
/**
* Note: Date formatter constructed within method for thread safety
*/
public static final Date parseDate(String dateString) {
for (String format : dateFormats) {
try {
SimpleDateFormat simpleFormatter = new SimpleDateFormat(format);
return simpleFormatter.parse(dateString);
} catch (ParseException e) {
} catch (NumberFormatException e) {
}
}
return null;
}
/** Convert OffsetDateTime to Date */
public static final Date toDate(OffsetDateTime offset) {
return parseDate(offset.toString()); // FIXME: use better Offset to date convertion
}
}

View File

@ -0,0 +1,108 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel;
/**
* Extract Mylyn PriorityLevel from Issue label
*
*/
public enum GiteaPriorityLevel {
HIGHEST(PriorityLevel.P1), HIGH(PriorityLevel.P2), MIDDLE(PriorityLevel.P3), LOW(PriorityLevel.P4),
LOWEST(PriorityLevel.P5);
private PriorityLevel level;
/**
* Constructor.
*
* @private
*/
private GiteaPriorityLevel(PriorityLevel p) {
this.level = p;
}
/**
* Get GiteaPriority level.
*
* @return PriorityLevel
*/
public PriorityLevel getLevel() {
return level;
}
/**
* GiteaPriority level as String
*
* {@code giteaPriority.ToString() <=> giteaPriority.GetLevel().toString()}
*
* To Get GiteaPriority as String use standard Enum name() API.
*
* @{code HIGHEST.toString() -> "P1" HIGHEST.name() -> "HIGHEST"}
*/
public String toString() {
return level.toString();
}
/**
* Get GiteaPriority from PriorityLevel
*
* @param level
* @return
* @throws IllegalArgumentException
*/
public static GiteaPriorityLevel getPriority(PriorityLevel level) throws IllegalArgumentException {
return Arrays.stream(values()).filter(v -> v.level.equals(level)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("unknown priority level:" + level.toString()));
}
// P1 .. P5
private static Pattern valuePattern = Pattern.compile("(?::?priority:)?(p[1-5])", Pattern.CASE_INSENSITIVE);
// Xtra very_normal is supported, surprising but not a big issue, kept it regexp
// 'simple'
private static Pattern alternativePattern = Pattern.compile("(?::?priority:)?(?:very_?)?(high|low|normal)",
Pattern.CASE_INSENSITIVE);
private static Pattern priorityPattern = Pattern.compile("(?::?priority:)?(highest|high|middle|low|lowest)",
Pattern.CASE_INSENSITIVE);
/**
* Return the Priority from labels value.
*
* @param labels as string
*
* @return found priority or MIDDLE if no priority found.
*/
public static GiteaPriorityLevel getPriority(String labels) {
Matcher m = priorityPattern.matcher(labels);
if (m.find()) {
return GiteaPriorityLevel.valueOf(m.group(1).toUpperCase());
}
m = valuePattern.matcher(labels);
if (m.find()) {
return GiteaPriorityLevel.getPriority(PriorityLevel.valueOf(m.group(1).toUpperCase()));
}
m = alternativePattern.matcher(labels);
if (m.find()) {
switch (m.group(1).toLowerCase()) {
case "high":
return GiteaPriorityLevel.HIGHEST;
case "normal":
return GiteaPriorityLevel.MIDDLE;
case "low":
return GiteaPriorityLevel.LOWEST;
default:
}
}
return GiteaPriorityLevel.MIDDLE;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
public class GiteaUser {
/** Name is user FullName or Login when FullName is empty. */
static public String getName(User user) {
String name = "";
if (user != null) {
name = user.getFullName();
if (name.isBlank() || name.isEmpty())
name = user.getLogin();
}
return name;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
public enum IssueAction {
CLOSE("close"),
LEAVE("leave"),
REOPEN("open");
IssueAction(String label) {
this.label = label;
}
public final String label;
}

View File

@ -0,0 +1,66 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
import io.gitea.model.Issue;
/** Gitea Issue State
*
* Note: Constructor is private to limit IssueState to predefined ones.
*/
public enum IssueState {
STATE_ANY("all"),STATE_OPENED("open"),STATE_CLOSED("closed");
private String value;
private IssueState(String value) {
this.value = value;
}
public String toString() {
return this.value;
}
public boolean equals(IssueState arg0) {
return this.value.equals(arg0.toString());
}
public boolean equals(String arg0) {
return this.value.equals(arg0);
}
/**Check if an issue is "open"
*
* @param issue Gitea Issue
* @return True if the state is "open".
*
* @apiNote isOpen() is not the opposite of isClose()
* "isOpen() == false" doesn't means that "isClose() == true"
*
*/
public static boolean isOpen(Issue issue) {
return IssueState.isOpen(issue.getState());
}
/**Check if a status value is "open".
*
* @param status Status value as String
*/
public static boolean isOpen(String value) {
return STATE_OPENED.equals(value);
}
/**Check if the issue is "closed".
*
* @param issue GItea Issue
* @return True if the issue state is "closed"
*/
public static boolean isClosed(Issue issue) {
return IssueState.isClosed(issue.getState());
}
/**Check if a status value is "closed".
*
* @param status Status value as String
*/
public static boolean isClosed(String value) {
return STATE_CLOSED.equals(value);
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.model;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
public enum IssueType {
EPIC("epic"), STORY("story"), FEATURE("feature"), BUG("bug"), TASK("task"), ENHANCEMENT("enhancement"),
QUESTION("question"), IMPEDIMENT("impediment");
static public final Pattern PATTERN = Pattern.compile("(" + StringUtils.join(IssueType.values(), "|") + ")");
private String value;
private IssueType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String toString() {
return value;
}
public boolean equals(IssueType arg0) {
return this.value.contentEquals(arg0.getValue());
}
public boolean equals(String arg0) {
return this.value.equals(arg0);
}
public static boolean contains(String value) {
for (IssueType t : IssueType.values()) {
if (t.equals(value))
return true;
}
return false;
}
}

View File

@ -0,0 +1,6 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
/** <i>local</i> extension of the generated Java Gitea API "io.gitea.model" package. */
package io.gitea.model;

View File

@ -0,0 +1,200 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import io.gitea.*;
import io.gitea.auth.*;
import io.gitea.model.*;
import io.gitea.api.UserApi;
import io.gitea.mylyn.core.exceptions.GiteaException;
import io.gitea.mylyn.core.exceptions.GiteaExceptionHandler;
import io.gitea.mylyn.core.exceptions.UnknownRepositoryException;
/**
* The ConnectionManager is a singleton that handles all GiteaConnection
* instances in a HashMap. The key in this HashMap is the URL to the Gitea
* instance constructed using a TaskRepository class.
*/
public class ConnectionManager {
/**
* The HashMap used to store all GiteaConnections
*/
private static HashMap<String, GiteaConnection> connections = new HashMap<String, GiteaConnection>();
/**
* The pattern is used to verify a ULR to a valid Gitea repository URL. group 1
* -> server including protocol (without trailing '/') group 2 ->
* repository/project path excluding potential ".git"
*
* trick: for server we are locking for any character which is not / which is
* same as looking not greedy to all character until /
*
* example: https://forge.chapril.org/gitea/mylyn-gitea.git group 1 =>
* https//forge.chapril.org group 2 => gitea/mylyn-gitea
*/
private static Pattern URLPattern = Pattern.compile("(https?://(?:[^\\/]*))/((?:.*?)/(?:[^\\/]*?))(?:\\.git)?$");
/**
* Returns the GiteaConnection for the given task repository
*
* @param repository
* @return
* @throws GiteaException
*/
static public GiteaConnection get(TaskRepository repository) throws GiteaException {
return get(repository, false);
}
/**
* Returns the GiteaConnection for the given task repository. If it fails for
* whatever reason, it returns null.
*
* @param repository
* @return
*/
static public GiteaConnection getSafe(TaskRepository repository) {
try {
return get(repository);
} catch (GiteaException e) {
return null;
}
}
/**
* Gitea connection configuration.
*/
static public ApiClient getConfiguration() {
return Configuration.getDefaultApiClient();
}
/**
* Constructs a "pseudo" URL string for the given task repository.
*
* @param repository
* @return
*/
private static String constructHash(TaskRepository repository) {
String username = repository.getCredentials(AuthenticationType.REPOSITORY).getUserName();
String password = repository.getCredentials(AuthenticationType.REPOSITORY).getPassword();
return repository.getUrl() + "?username=" + username + "&password=" + password.hashCode();
}
/**
* Validates the given task repository and returns a GiteaConnection if the task
* repository is a valid repository.
*
* @param repository
* @return
* @throws GiteaException
*/
// FIXME: redesign to limit this function to validation but not configuration
static GiteaConnection validate(TaskRepository repository) throws GiteaException {
try {
String projectPath = null;
String host = null;
// Extract host&project path from valid url
if (repository.getProperty("giteaBaseUrl").trim().length() > 0) {
host = repository.getProperty("giteaBaseUrl").trim();
if (!repository.getUrl().startsWith(host)) {
throw new GiteaException("Invalid repository URL!");
}
projectPath = repository.getUrl().replaceFirst(Matcher.quoteReplacement(host), "");
if (projectPath.startsWith("/")) {
projectPath = projectPath.substring(1);
}
} else {
Matcher matcher = URLPattern.matcher(repository.getUrl());
if (!matcher.find()) {
throw new GiteaException("Invalid Project-URL!");
}
projectPath = matcher.group(2);
host = matcher.group(1);
}
// Configure credentials
String username = repository.getCredentials(AuthenticationType.REPOSITORY).getUserName();
String password = repository.getCredentials(AuthenticationType.REPOSITORY).getPassword();
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath(GiteaConnection.apiVersion.getBasePath(host));
if (repository.getProperty("usePrivateToken") != null
&& repository.getProperty("usePrivateToken").equals("true")) {
ApiKeyAuth accessToken = (ApiKeyAuth) defaultClient.getAuthentication("AccessToken");
accessToken.setApiKey(password); // token is stored in the password field.
} else {
HttpBasicAuth basicAuth = (HttpBasicAuth) defaultClient.getAuthentication("BasicAuth");
basicAuth.setUsername(username);
basicAuth.setPassword(password);
}
// Get user related repository list
UserApi userApi = new UserApi();
List<Repository> repositories = userApi.userCurrentListRepos(0, 0);
for (Repository p : repositories) {
if (p.getFullName().equals(projectPath)) {
GiteaConnection connection = new GiteaConnection(host, p, new GiteaAttributeMapper(repository));
return connection;
}
}
// At this point the authentication was successful, but the corresponding
// repository
// could not be found! which not not occurs with Gitea.
throw new UnknownRepositoryException(projectPath);
} catch (GiteaException e) {
throw e;
} catch (Exception e) {
throw GiteaExceptionHandler.handle(e);
} catch (Error e) {
throw GiteaExceptionHandler.handle(e);
}
}
/**
* Returns a *valid* GiteaConnection, otherwise this method throws an exception.
*
* @param repository
* @param forceUpdate if true, a new GiteaConnection instance will be created,
* even if a Gitea Connection already exists for the given
* task repository
* @return connection
* @throws GiteaException
*/
static GiteaConnection get(TaskRepository repository, boolean forceUpdate) throws GiteaException {
try {
String hash = constructHash(repository);
if (connections.containsKey(hash) && !forceUpdate) {
return connections.get(hash);
} else {
GiteaConnection connection = validate(repository);
connections.put(hash, connection);
connection.update();
return connection;
}
} catch (GiteaException e) {
throw e;
} catch (Exception e) {
throw GiteaExceptionHandler.handle(e);
} catch (Error e) {
throw GiteaExceptionHandler.handle(e);
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import io.gitea.model.IssueState;
import io.gitea.model.Issue;
/**
* Represents an action that tell Gitea what to do with the issue, e.g. "close"
* will close the issue in Gitea. This wrapper is necessary.
*
*/
public enum GiteaAction {
LEAVE("leave"), CLOSE("close"), REOPEN("reopen");
/**
* The valid actions for an open issue
*/
private final static GiteaAction[] opened = { LEAVE, CLOSE };
/**
* The valid actions for a closed issue
*/
private final static GiteaAction[] closed = { LEAVE, REOPEN };
private GiteaAction(String label) {
this.label = label;
}
public final String label;
/**
* Returns all valid actions for the given issue.
*
* @param issue
* @return
*/
public static GiteaAction[] getActions(Issue issue) {
if (IssueState.isClosed(issue)) {
return closed;
} else {
return opened;
}
}
/**
* Returns the GiteaAction enum for the given action string.
*
* @param action
* @return
*/
public static GiteaAction find(String action) {
for (GiteaAction a : values()) {
if (a.label.equals(action)) {
return a;
}
}
return LEAVE;
}
}

View File

@ -0,0 +1,114 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
public enum GiteaAttribute {
BODY("Description", TaskAttribute.DESCRIPTION, TaskAttribute.TYPE_LONG_RICH_TEXT),
TITLE("Summary", TaskAttribute.SUMMARY, TaskAttribute.TYPE_SHORT_RICH_TEXT),
STATUS("Status", TaskAttribute.STATUS, TaskAttribute.TYPE_SHORT_TEXT, GiteaFlag.ATTRIBUTE, GiteaFlag.READ_ONLY),
LABELS("Labels", "io.gitea.mylyn.issue.labels", TaskAttribute.TYPE_LONG_TEXT, GiteaFlag.ATTRIBUTE),
UPDATED("Updated", TaskAttribute.DATE_MODIFICATION, TaskAttribute.TYPE_DATETIME, GiteaFlag.READ_ONLY,
GiteaFlag.ATTRIBUTE),
CREATED("Created", TaskAttribute.DATE_CREATION, TaskAttribute.TYPE_DATETIME, GiteaFlag.READ_ONLY,
GiteaFlag.ATTRIBUTE),
COMPLETED("Completed", TaskAttribute.DATE_COMPLETION, TaskAttribute.TYPE_DATETIME, GiteaFlag.READ_ONLY),
AUTHOR("Author", TaskAttribute.USER_REPORTER, TaskAttribute.TYPE_PERSON, GiteaFlag.READ_ONLY, GiteaFlag.ATTRIBUTE),
PROJECT("Project", TaskAttribute.PRODUCT, TaskAttribute.TYPE_SHORT_TEXT, GiteaFlag.READ_ONLY, GiteaFlag.ATTRIBUTE),
ASSIGNEE("Assignee", TaskAttribute.USER_ASSIGNED, TaskAttribute.TYPE_PERSON, GiteaFlag.ATTRIBUTE),
MILESTONE("Milestone", "io.gitea.mylyn.issue.milestone", TaskAttribute.TYPE_SINGLE_SELECT, GiteaFlag.ATTRIBUTE),
IID("IID", TaskAttribute.TASK_KEY, TaskAttribute.TYPE_INTEGER, GiteaFlag.READ_ONLY),
PRIORITY("Priority", TaskAttribute.PRIORITY, TaskAttribute.TYPE_SHORT_TEXT, GiteaFlag.READ_ONLY),
TYPE("Type", TaskAttribute.TASK_KIND, TaskAttribute.TYPE_SHORT_TEXT, GiteaFlag.READ_ONLY);
private Set<GiteaFlag> flags;
private final String prettyName;
private final String taskKey;
private final String type;
public String getKind() {
if (type.equals(TaskAttribute.TYPE_PERSON)) {
return TaskAttribute.KIND_PEOPLE;
} else if (flags.contains(GiteaFlag.ATTRIBUTE)) {
return TaskAttribute.KIND_DEFAULT;
}
return null;
}
public boolean isReadOnly() {
return flags.contains(GiteaFlag.READ_ONLY);
}
GiteaAttribute(String prettyName, String taskKey, String type, GiteaFlag... flags) {
this.taskKey = taskKey;
this.prettyName = prettyName;
this.type = type;
if (flags == null || flags.length == 0) {
this.setFlags(EnumSet.noneOf(GiteaFlag.class));
} else {
this.setFlags(EnumSet.copyOf(Arrays.asList(flags)));
}
}
GiteaAttribute(String prettyName, String taskKey, String type) {
this(prettyName, taskKey, type, new GiteaFlag[] {});
}
public Set<GiteaFlag> getFlags() {
return flags;
}
public void setFlags(Set<GiteaFlag> flags) {
this.flags = flags;
}
public String getPrettyName() {
return prettyName;
}
public String getTaskKey() {
return taskKey;
}
public String getType() {
return type;
}
public String toString() {
return this.prettyName;
}
public static GiteaAttribute get(String key) {
for (GiteaAttribute attr : GiteaAttribute.values()) {
if (attr.getTaskKey().equals(key)) {
return attr;
}
}
return null;
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import io.gitea.model.GiteaDateTimeUtils;
import io.gitea.model.Milestone;
import io.gitea.model.User;
public class GiteaAttributeMapper extends TaskAttributeMapper {
public GiteaAttributeMapper(TaskRepository taskRepository) throws CoreException, IOException {
super(taskRepository);
}
@Override
public Map<String, String> getOptions(TaskAttribute attribute) {
if (attribute.getId().equals(GiteaAttribute.MILESTONE.getTaskKey())) {
return getAsMap(getMilestones());
} else {
return super.getOptions(attribute);
}
}
private GiteaConnection getConnection() throws CoreException {
return ConnectionManager.get(getTaskRepository());
}
@Override
public Date getDateValue(TaskAttribute attribute) {
if (attribute == null) {
return null;
}
Date parsedDate = GiteaDateTimeUtils.parseDate(attribute.getValue());
if (parsedDate != null) {
return parsedDate;
}
return super.getDateValue(attribute);
}
public User findProjectMemberByName(String name) {
try {
List<User> members = getConnection().getRepositoryCollaborators();
for (User member : members) {
if (member.getFullName().equals(name) || member.getLogin().equals(name)) {
return member;
}
}
} catch (CoreException e) {
}
return null;
}
public Milestone findMilestoneByName(String name) {
try {
List<Milestone> milestones = getConnection().getMilestones();
for (Milestone m : milestones) {
if (m.getTitle().equals(name)) {
return m;
}
}
} catch (CoreException e) {
}
return null;
}
private List<String> getMilestones() {
List<String> target = new ArrayList<String>();
try {
List<Milestone> milestones = getConnection().getMilestones();
for (Milestone m : milestones) {
target.add(m.getTitle());
}
} catch (CoreException e) {
}
return target;
}
private HashMap<String, String> getAsMap(List<String> list) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("", "");
for (String s : list) {
map.put(s, s);
}
return map;
}
}

View File

@ -0,0 +1,166 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.gitea.ApiException;
import io.gitea.api.ApiVersion;
import io.gitea.api.IssueApi;
import io.gitea.api.RepositoryApi;
import io.gitea.api.UserApi;
import io.gitea.model.Comment;
import io.gitea.model.Issue;
import io.gitea.model.IssueState;
import io.gitea.model.Milestone;
import io.gitea.model.Repository;
import io.gitea.model.User;
/**
* Handle Gitea connection and provides helpers to API.
*
*/
public class GiteaConnection {
public final String owner;
public final String repo;
public final String host;
public final Repository repository;
public final GiteaAttributeMapper mapper;
private List<Milestone> milestones;
private List<User> members;
/** Targetted Gitea Api Version */
public static final ApiVersion apiVersion = ApiVersion.V1;
public GiteaConnection(String host, Repository project, GiteaAttributeMapper mapper) {
this.host = host;
this.repository = project;
this.mapper = mapper;
this.owner = project.getOwner().getLogin();
this.repo = project.getName();
}
public UserApi userApi() {
return new UserApi();
}
public IssueApi issueApi() {
return new IssueApi();
}
public RepositoryApi repositoryApi() {
return new RepositoryApi();
}
public void update() throws IOException {
ArrayList<User> memberList = new ArrayList<User>();
try {
milestones = issueGetAllMilestones();
} catch (ApiException e1) {
e1.printStackTrace();
}
try {
memberList.addAll(repoListAllCollaborators());
} catch (ApiException e1) {
e1.printStackTrace();
}
members = Collections.unmodifiableList(memberList);
}
public List<Repository> userCurrentListRepos() throws ApiException {
return userApi().userCurrentListRepos(null, null);
}
public List<Milestone> getMilestones() {
return Collections.unmodifiableList(milestones);
}
public List<User> getRepositoryCollaborators() {
return Collections.unmodifiableList(members);
}
public List<User> repoListAllCollaborators() throws ApiException {
return repositoryApi().repoListCollaborators(owner, repo, null, null);
}
public List<Milestone> issueGetMilestones(IssueState state, String filterByName) throws ApiException {
return issueApi().issueGetMilestonesList(owner, repo, state.toString(), filterByName, null, null);
}
public List<Milestone> issueGetAllMilestones() throws ApiException {
return issueGetMilestones(IssueState.STATE_ANY, null);
}
public List<Milestone> issueGetOpenMilestones() throws ApiException {
return issueGetMilestones(IssueState.STATE_OPENED, null);
}
/**
* Get issues list by state.
*
* @param state
* @return
* @throws ApiException
*/
public List<Issue> repoGetIssues(IssueState state, String labels, Integer page, String query, String milestones) throws ApiException {
return issueApi().issueListIssues(owner, repo, state.toString(), labels, "", query, milestones, page, 0);
}
public List<Issue> repoGetIssues(String state, String labels, Integer page, String query, String milestones) throws ApiException {
return issueApi().issueListIssues(owner, repo, state, labels, query, "", milestones, page, 0);
}
public List<Issue> repoGetAllIssues() throws ApiException {
return repoGetIssues(IssueState.STATE_ANY, "", 0, "", "");
}
public List<Issue> repoGetOpenIssues() throws ApiException {
return repoGetIssues(IssueState.STATE_OPENED, "", 0, "", "");
}
/**
* Get a specific Issue.
*
* @param issueId : issue id
* @return Issue
* @throws ApiException
*/
public Issue issueGetIssue(Long issueId) throws ApiException {
return issueApi().issueGetIssue(owner, repo, issueId);
}
/**
* Get all comments attached to an issue.
*
* @param issue Issue to retrieve comments from.
* @param since
* @return List<Comment>
* @throws ApiException
*/
public List<Comment> issueGetComments(Issue issue, String since, String before) throws ApiException {
return issueApi().issueGetComments(owner, repo, issue.getNumber(), null, null); // FIXME: support since and before
}
/**
* Get all comments attached to an issue identified by its id.
*
* @param issueId
* @param since
* @return
* @throws ApiException
*/
public List<Comment> issueGetComments(Long issueId, String since) throws ApiException {
return issueGetComments(issueGetIssue(issueId), since, null); // FIXME: support since and before
}
public List<Comment> issueGetComments(Issue issue) throws ApiException {
return issueGetComments(issue, null, null);
}
}

View File

@ -0,0 +1,160 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.util.Date;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;
import io.gitea.*;
import io.gitea.model.Issue;
import io.gitea.mylyn.core.exceptions.GiteaException;
/**
* Establishes a connection to the Gitea Instance and handles all requests like
* search requests etc.
*/
public class GiteaConnector extends AbstractRepositoryConnector {
private GiteaTaskDataHandler handler = new GiteaTaskDataHandler();
@Override
public boolean canCreateNewTask(TaskRepository repository) {
return true;
}
@Override
public boolean canCreateTaskFromKey(TaskRepository repository) {
return false;
}
@Override
public String getConnectorKind() {
return GiteaPluginCore.CONNECTOR_KIND;
}
@Override
public String getLabel() {
return "Gitea issues";
}
@Override
public String getRepositoryUrlFromTaskUrl(String arg0) {
return null;
}
@Override
public TaskData getTaskData(TaskRepository repository, String id, IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask("Task Download", IProgressMonitor.UNKNOWN);
return handler.downloadTaskData(repository, GiteaConnector.getTicketId(id));
} finally {
monitor.done();
}
}
@Override
public String getTaskIdFromTaskUrl(String url) {
return null;
}
@Override
public String getTaskUrl(String arg0, String arg1) {
return null;
}
@Override
public boolean hasTaskChanged(TaskRepository repository, ITask task, TaskData data) {
TaskMapper mapper = new GiteaTaskMapper(data);
if (data.isPartial()) {
return mapper.hasChanges(task);
} else {
Date repositoryDate = mapper.getModificationDate();
Date localDate = task.getModificationDate();
if (repositoryDate != null && repositoryDate.equals(localDate)) {
return false;
}
return true;
}
}
@Override
public IStatus performQuery(TaskRepository repository, IRepositoryQuery query, TaskDataCollector collector,
ISynchronizationSession session, IProgressMonitor monitor) {
try {
monitor.beginTask("Tasks querying", IProgressMonitor.UNKNOWN);
GiteaConnection connection = ConnectionManager.get(repository);
GiteaIssueSearch search = new GiteaIssueSearch(query);
List<Issue> issues = connection.repoGetAllIssues();
for (Issue i : issues) {
if (search.doesMatch(i)) {
collector.accept(
handler.createTaskDataFromGiteaIssue(i, repository, connection.issueGetComments(i)));
}
}
return Status.OK_STATUS;
} catch (ApiException e) {
return new Status(Status.ERROR, GiteaPluginCore.ID_PLUGIN, "Unable to execute Query: " + e.getMessage());
} catch (CoreException e) {
return new Status(Status.ERROR, GiteaPluginCore.ID_PLUGIN, "Unable to execute Query: " + e.getMessage());
} finally {
monitor.done();
}
}
@Override
public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor)
throws CoreException {
try {
monitor.beginTask("Updating repository configuration", IProgressMonitor.UNKNOWN);
ConnectionManager.get(repository, true);
} finally {
monitor.done();
}
}
@Override
public void updateTaskFromTaskData(TaskRepository repository, ITask task, TaskData data) {
GiteaTaskMapper mapper = new GiteaTaskMapper(data);
mapper.applyTo(task);
}
public static void validate(TaskRepository taskRepo) throws CoreException {
try {
ConnectionManager.validate(taskRepo);
} catch (GiteaException e) {
throw e;
} catch (Exception e) {
throw new GiteaException("Connection not successful or repository not found: " + e.getMessage());
} catch (Error e) {
throw new GiteaException("Connection not successful or repository not found: " + e.getMessage());
}
}
@Override
public AbstractTaskDataHandler getTaskDataHandler() {
return handler;
}
public static Long getTicketId(String id) {
return (long) Integer.parseInt(id);
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
/**
* A flag for Gitea attributes.
*
*/
public enum GiteaFlag {
/**
* Sets the attribute to ReadOnly.
*/
READ_ONLY,
/**
* Default Attribute
*/
ATTRIBUTE,
/**
* A flag to indicate, that an attribute describes a person.
*/
PEOPLE;
}

View File

@ -0,0 +1,76 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import io.gitea.model.IssueState;
import io.gitea.model.Issue;
import io.gitea.model.Label;
public class GiteaIssueSearch {
private String assignee;
private String milestone;
private Boolean opened;
private Boolean closed;
private List<Pattern> labels = new ArrayList<Pattern>();
public GiteaIssueSearch(IRepositoryQuery query) {
assignee = query.getAttribute("assignee");
milestone = query.getAttribute("milestone");
opened = Boolean.parseBoolean(query.getAttribute("opened"));
closed = Boolean.parseBoolean(query.getAttribute("closed"));
for (String label : query.getAttribute("labels").split(",")) {
if (label.trim().length() > 0) {
labels.add(Pattern.compile(label.trim()));
}
}
}
public boolean doesMatch(Issue issue) {
if (!assignee.equals("") && (issue.getAssignee() == null || !(assignee.equals(issue.getAssignee().getLogin())
|| assignee.equals(issue.getAssignee().getFullName())))) {
return false;
}
if (!milestone.equals("")
&& (issue.getMilestone() == null || !milestone.equals(issue.getMilestone().getTitle()))) {
return false;
}
List<Pattern> matchedLabels = new ArrayList<Pattern>();
for (Pattern p : labels) {
for (Label label : issue.getLabels()) {
if (p.matcher(label.getName()).find()) {
matchedLabels.add(p);
}
}
}
if (matchedLabels.size() < labels.size()) {
return false;
}
if (!closed && IssueState.isClosed(issue)) {
return false;
}
if (!opened && IssueState.isOpen(issue)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import org.eclipse.core.runtime.Plugin;
import org.osgi.framework.BundleContext;
public class GiteaPluginCore extends Plugin {
private static GiteaPluginCore plugin;
public static final String ID_PLUGIN = "io.gitea.mylyn.core";
public static final String CONNECTOR_KIND = "gitea";
public static final String ENCODING_UTF_8 = "UTF-8";
public GiteaPluginCore() {
}
public static GiteaPluginCore get() {
return plugin;
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
}
@Override
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
}
}

View File

@ -0,0 +1,275 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.mylyn.tasks.core.ITaskMapping;
import org.eclipse.mylyn.tasks.core.RepositoryResponse;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMetaData;
import org.eclipse.mylyn.tasks.core.data.TaskCommentMapper;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskOperation;
import io.gitea.ApiException;
import io.gitea.model.Comment;
import io.gitea.model.GiteaDateTimeUtils;
import io.gitea.model.GiteaPriorityLevel;
import io.gitea.model.GiteaUser;
import io.gitea.model.Issue;
import io.gitea.model.IssueState;
import io.gitea.model.IssueType;
import io.gitea.model.Label;
import io.gitea.model.Milestone;
import io.gitea.model.User;
import io.gitea.mylyn.core.exceptions.GiteaException;
/**
* Handles the issues. It maps the attributes from the Gitea API to real Mylyn
* issue attributes, downloads the issues for a specific task repository, and
* creates new issues.
*/
public class GiteaTaskDataHandler extends AbstractTaskDataHandler {
public GiteaTaskDataHandler() {
}
@Override
public TaskAttributeMapper getAttributeMapper(TaskRepository repository) {
try {
return ConnectionManager.get(repository).mapper;
} catch (CoreException e) {
throw new Error(e);
}
}
@Override
public boolean initializeTaskData(TaskRepository repository, TaskData data, ITaskMapping mapping,
IProgressMonitor monitor) throws CoreException {
createDefaultAttributes(data, false);
GiteaConnection connection = ConnectionManager.get(repository);
TaskAttribute root = data.getRoot();
root.getAttribute(GiteaAttribute.PROJECT.getTaskKey()).setValue(connection.repository.getName());// FIXME: not
// the project
root.getAttribute(GiteaAttribute.LABELS.getTaskKey()).setValue("");
root.getAttribute(GiteaAttribute.STATUS.getTaskKey()).setValue("open");
root.getAttribute(GiteaAttribute.MILESTONE.getTaskKey()).setValue("");
return true;
}
@Override
public RepositoryResponse postTaskData(TaskRepository repository, TaskData data, Set<TaskAttribute> attributes,
IProgressMonitor monitor) throws CoreException {
GiteaAttributeMapper attributeMapper = (GiteaAttributeMapper) data.getAttributeMapper();
TaskAttribute root = data.getRoot();
String labels = root.getAttribute(GiteaAttribute.LABELS.getTaskKey()).getValue();
String title = root.getAttribute(GiteaAttribute.TITLE.getTaskKey()).getValue();
String body = root.getAttribute(GiteaAttribute.BODY.getTaskKey()).getValue();
Long assigneeId = 0L;
// TODO : confirm gitea behaviour
// We have to check, if the assignee has changed. The gitea api leaves three
// posiblities for the assignee ID:
// 0: leave as it is
// -1: unassign
// real id: assign
// If we didnt do this, Gitea would create a comment everytime we edit the
// issue and there is still no
// assignee
for (TaskAttribute a : attributes) {
if (a.getId().equals(GiteaAttribute.ASSIGNEE.getTaskKey())) {
User assignee = attributeMapper
.findProjectMemberByName(root.getAttribute(GiteaAttribute.ASSIGNEE.getTaskKey()).getValue());
assigneeId = (assignee == null ? -1 : assignee.getId());
}
}
Milestone milestone = attributeMapper
.findMilestoneByName(root.getAttribute(GiteaAttribute.MILESTONE.getTaskKey()).getValue());
Long milestoneId = (milestone == null ? 0 : milestone.getId());
/*
* FIXME: Support issue creation GiteaConnection connection =
* ConnectionManager.get(repository); Gitlea API api = connection.api(); try {
* monitor.beginTask("Uploading task", IProgressMonitor.UNKNOWN); Issue issue =
* null; if(data.isNew()) { issue =
* api.createIssue(connection.repository.getId(), assigneeId, milestoneId,
* labels, body, title); return new
* RepositoryResponse(ResponseKind.TASK_CREATED, "" + issue.getIid()); } else {
* if(root.getAttribute(TaskAttribute.COMMENT_NEW) != null &&
* !root.getAttribute(TaskAttribute.COMMENT_NEW).getValue().equals("")) {
* api.createNote(connection.repository.getId(),
* GiteaConnector.getTicketId(data.getTaskId()),
* root.getAttribute(TaskAttribute.COMMENT_NEW).getValue()); } String action =
* root.getAttribute(TaskAttribute.OPERATION).getValue(); issue =
* api.editIssue(connection.repository.getId(),
* GiteaConnector.getTicketId(data.getTaskId()), assigneeId, milestoneId,
* labels, body, title, GiteaAction.find(action).getGiteaIssueAction()); return
* new RepositoryResponse(ResponseKind.TASK_UPDATED, "" + issue.getIid()); } }
* catch (IOException e) { throw new
* GiteaException("Unknown connection error!"); } finally { monitor.done(); }
*/
return null;// FIXME:
}
public TaskData downloadTaskData(TaskRepository repository, Long ticketId) throws CoreException {
try {
GiteaConnection connection = ConnectionManager.get(repository);
Issue issue = connection.issueGetIssue(ticketId);
List<Comment> notes = connection.issueGetComments(issue);
return createTaskDataFromGiteaIssue(issue, repository, notes);
} catch (ApiException e) {
throw new GiteaException("Unknown connection error!");
}
}
public TaskData createTaskDataFromGiteaIssue(Issue issue, TaskRepository repository, List<Comment> notes)
throws CoreException {
GiteaConnection connection = ConnectionManager.get(repository);
TaskData data = new TaskData(connection.mapper, GiteaPluginCore.CONNECTOR_KIND, repository.getUrl(),
issue.getNumber().toString());
// Labels
List<String> labelsNames = new ArrayList<String>();
List<Label> issueLabels = issue.getLabels();
issueLabels.forEach((label) -> labelsNames.add(label.getName()));
String labels = StringUtils.join(labelsNames, ", ");
createDefaultAttributes(data, true);
TaskAttribute root = data.getRoot();
root.getAttribute(GiteaAttribute.AUTHOR.getTaskKey()).setValue(GiteaUser.getName(issue.getUser()));
root.getAttribute(GiteaAttribute.CREATED.getTaskKey()).setValue(issue.getCreatedAt().toString());
root.getAttribute(GiteaAttribute.BODY.getTaskKey()).setValue(issue.getBody() == null ? "" : issue.getBody());
root.getAttribute(GiteaAttribute.LABELS.getTaskKey()).setValue(labels);
root.getAttribute(GiteaAttribute.PROJECT.getTaskKey()).setValue(connection.repository.getName()); // FIXME:
root.getAttribute(GiteaAttribute.STATUS.getTaskKey()).setValue(issue.getState());
root.getAttribute(GiteaAttribute.TITLE.getTaskKey()).setValue(issue.getTitle());
root.getAttribute(GiteaAttribute.IID.getTaskKey()).setValue("" + issue.getNumber().toString());
root.getAttribute(GiteaAttribute.PRIORITY.getTaskKey())
.setValue(GiteaPriorityLevel.getPriority(labels).toString());
root.getAttribute(GiteaAttribute.TYPE.getTaskKey()).setValue(getType(labels));
if (issue.getMilestone() != null) {
root.getAttribute(GiteaAttribute.MILESTONE.getTaskKey()).setValue(issue.getMilestone().getTitle());
}
if (issue.getUpdatedAt() != null) {
root.getAttribute(GiteaAttribute.UPDATED.getTaskKey()).setValue(issue.getUpdatedAt().toString());
}
if (IssueState.isClosed(issue.getState())) {
root.getAttribute(GiteaAttribute.COMPLETED.getTaskKey()).setValue(issue.getUpdatedAt().toString());
}
// Assignee name is either FulleName either Login either empty
List<String> assigneesNames = new ArrayList<String>();
List<User> assignees = issue.getAssignees();
if (assignees != null) {
assignees.forEach((assignee) -> assigneesNames.add(GiteaUser.getName(assignee)));
root.getAttribute(GiteaAttribute.ASSIGNEE.getTaskKey()).setValue(StringUtils.join(assigneesNames, ", "));
}
Collections.sort(notes, new Comparator<Comment>() {
@Override
public int compare(Comment o1, Comment o2) {
return o1.getCreatedAt().compareTo(o2.getCreatedAt());
}
});
int i = 0;
for (Comment note : notes) {
TaskCommentMapper cmapper = new TaskCommentMapper();
cmapper.setAuthor(repository.createPerson(GiteaUser.getName(note.getUser())));
cmapper.setCreationDate(GiteaDateTimeUtils.toDate(note.getCreatedAt()));//FIXME:
cmapper.setText(note.getBody());
cmapper.setNumber(++i);
TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_COMMENT + (i + 1));
cmapper.applyTo(attribute);
}
GiteaAction[] actions = GiteaAction.getActions(issue);
for (GiteaAction action : actions) {
TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_OPERATION + action.label);
TaskOperation.applyTo(attribute, action.label, action.label);
}
return data;
}
private void createDefaultAttributes(TaskData data, boolean existingTask) {
createAttribute(data, GiteaAttribute.BODY);
createAttribute(data, GiteaAttribute.TITLE);
createAttribute(data, GiteaAttribute.LABELS);
createAttribute(data, GiteaAttribute.STATUS);
createAttribute(data, GiteaAttribute.PROJECT);
createAttribute(data, GiteaAttribute.CREATED);
createAttribute(data, GiteaAttribute.COMPLETED);
createAttribute(data, GiteaAttribute.UPDATED);
createAttribute(data, GiteaAttribute.ASSIGNEE);
createAttribute(data, GiteaAttribute.MILESTONE);
createAttribute(data, GiteaAttribute.IID);
createAttribute(data, GiteaAttribute.PRIORITY);
createAttribute(data, GiteaAttribute.TYPE);
data.getRoot().getAttribute(GiteaAttribute.CREATED.getTaskKey()).setValue("" + (new Date().getTime()));
if (existingTask) {
data.getRoot().createAttribute(TaskAttribute.COMMENT_NEW).getMetaData()
.setType(TaskAttribute.TYPE_LONG_RICH_TEXT).setReadOnly(false);
createAttribute(data, GiteaAttribute.AUTHOR);
}
TaskAttribute operation = data.getRoot().createAttribute(TaskAttribute.OPERATION);
operation.getMetaData().setType(TaskAttribute.TYPE_OPERATION);
}
private void createAttribute(TaskData data, GiteaAttribute attribute) {
TaskAttribute attr = data.getRoot().createAttribute(attribute.getTaskKey());
TaskAttributeMetaData metaData = attr.getMetaData();
metaData.setType(attribute.getType());
metaData.setKind(attribute.getKind());
metaData.setLabel(attribute.toString());
metaData.setReadOnly(attribute.isReadOnly());
}
/**
* Returns the type string for Mylyn. Uses a regular expression to check for
* types in the given label.
*
* @param labels
* @return
*/
private String getType(String labels) {
Matcher m = IssueType.PATTERN.matcher(labels);
if (m.find()) {
return m.group(1);
}
return "";
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
public class GiteaTaskMapper extends TaskMapper {
public GiteaTaskMapper(TaskData taskData) {
super(taskData);
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core.exceptions;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Status;
import io.gitea.mylyn.core.GiteaPluginCore;
/**
* Gitea Exception class
*/
public class GiteaException extends CoreException {
private static final long serialVersionUID = 7631319898671055531L;
/**
* Constructor
*
* @param message : Exception message
*/
public GiteaException(String message) {
super(new Status(Status.ERROR, GiteaPluginCore.ID_PLUGIN, message));
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core.exceptions;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import javax.net.ssl.SSLHandshakeException;
public class GiteaExceptionHandler {
public static GiteaException handle(Throwable e) {
if (e instanceof SSLHandshakeException) {
return new GiteaException("Invalid TLS Certificate: " + e.getMessage());
} else if (e instanceof ConnectException) {
return new GiteaException("Connection refused");
} else if (e instanceof NoRouteToHostException) {
return new GiteaException("No route to host");
} else if (e instanceof FileNotFoundException) {
return new GiteaException("Invalid path in host");
} else if (e instanceof IOException) {
return new GiteaException("Invalid username/password/private token combination");
}
return new GiteaException("Unknown Exception: " + e.getMessage());
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.core.exceptions;
/**
* Exception raised when trying to access unkown repository.
*
*/
public class UnknownRepositoryException extends GiteaException {
private static final long serialVersionUID = 4976633904836890061L;
public UnknownRepositoryException(String project) {
super("Unknown repository " + project + " or insufficient access rights");
}
}

View File

@ -0,0 +1,6 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
/** <i>local</i> extension of the generated Java Gitea API "io.gitea" package. */
package io.gitea;

1
io.gitea.mylyn.core/target/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/classes/

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry combineaccessrules="false" kind="src" path="/io.gitea.mylyn.core"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="org.eclipse.pde.api.tools.apiAnalysisBuilder"/>
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
</launchConfiguration>

1
io.gitea.mylyn.ui/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>io.gitea.mylyn.ui</name>
<comment></comment>
<projects>
<project>io.gitea.mylyn.core</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/org.eclipse.pde.api.tools.apiAnalysisBuilder (1).launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,108 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=ignore
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.8

19
io.gitea.mylyn.ui/LICENSE Normal file
View File

@ -0,0 +1,19 @@
MIT License Copyright (c) 2021 F.Terrot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,20 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Mylyn Gitea Connector UI
Bundle-SymbolicName: io.gitea.mylyn.ui;singleton:=true
Bundle-Version: 0.1.0
Automatic-Module-Name: io.gitea.mylyn.ui
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Activator: io.gitea.mylyn.ui.GiteaUIPlugin
Require-Bundle: org.eclipse.core.runtime,
org.eclipse.ui,
org.eclipse.ui.forms,
org.eclipse.mylyn.commons.net;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.commons.core;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.tasks.ui;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.tasks.core;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.commons.workbench;bundle-version="[3.8.0,4.0.0)",
org.eclipse.mylyn.commons.ui;bundle-version="[3.8.0,4.0.0)",
io.gitea.mylyn.core
Bundle-ActivationPolicy: lazy
Export-Package: io.gitea.mylyn.ui

View File

@ -0,0 +1,9 @@
source.. = src/
bin.includes = META-INF/,.,plugin.xml,icons/
bin.includes = META-INF/,\
.,\
plugin.xml,\
LICENSE,\
icons,\
icons/
jre.compilation.profile = JavaSE-1.8

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
id="io.gitea.mylyn.ui"
name="%repository.name"
point="org.eclipse.mylyn.tasks.ui.repositories">
<connectorUi
brandingIcon="icons/gitea-icon.png"
class="io.gitea.mylyn.ui.GiteaConnectorUI"
id="io.gitea.mylyn.ui"
name="GiteaConnector Ui"
overlayIcon="icons/gitea-overlay.png"/>
</extension>
<extension point="org.eclipse.mylyn.tasks.ui.editors">
<pageFactory
class="io.gitea.mylyn.ui.GiteaEditorPageFactory"
id="io.gitea.mylyn.ui.pageFactory">
</pageFactory>
</extension>
</plugin>

View File

@ -0,0 +1,78 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.ITaskComment;
import org.eclipse.mylyn.tasks.core.ITaskMapping;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi;
import org.eclipse.mylyn.tasks.ui.wizards.ITaskRepositoryPage;
import org.eclipse.mylyn.tasks.ui.wizards.NewTaskWizard;
import org.eclipse.mylyn.tasks.ui.wizards.RepositoryQueryWizard;
import io.gitea.model.IssueType;
import io.gitea.mylyn.core.GiteaPluginCore;
public class GiteaConnectorUI extends AbstractRepositoryConnectorUi {
@Override
public String getConnectorKind() {
return GiteaPluginCore.CONNECTOR_KIND;
}
@Override
public IWizard getNewTaskWizard(TaskRepository repository, ITaskMapping mapping) {
return new NewTaskWizard(repository, mapping);
}
@Override
public IWizard getQueryWizard(TaskRepository repository, IRepositoryQuery query) {
RepositoryQueryWizard wizard = new RepositoryQueryWizard(repository);
wizard.addPage(new GiteaQueryPage("New Page", repository, query));
return wizard;
}
@Override
public ITaskRepositoryPage getSettingsPage(TaskRepository repository) {
return new GiteaRepositorySettingsPage(Labels.NEW_REPOSITORY, Labels.SETTINGS_PAGE, repository);
}
@Override
public boolean hasSearchPage() {
return false;
}
@Override
public ImageDescriptor getTaskKindOverlay(ITask task) {
//FIXME: complete with other kind epic,...enhancement ...
String kind = task.getTaskKind();
try {
switch(IssueType.valueOf(kind)) {
case BUG:
return GiteaImages.ISSUE;
case FEATURE:
return GiteaImages.OVERLAY_FEATURE;
case STORY:
return GiteaImages.OVERLAY_STORY;
default:
}
}
catch (IllegalArgumentException e) {
// unhandled task kind.
}
return super.getTaskKindOverlay(task);
}
@Override
public String getReplyText(TaskRepository taskRepository, ITask task, ITaskComment taskComment,
boolean includeTask) {
return "Reply to " + taskComment.getAuthor();
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditorPartDescriptor;
public class GiteaEditorPage extends AbstractTaskEditorPage {
public GiteaEditorPage(TaskEditor editor, String connectorKind) {
super(editor, connectorKind);
setNeedsPrivateSection(false);
setNeedsSubmitButton(true);
setNeedsAddToCategory(false);
}
@Override
protected Set<TaskEditorPartDescriptor> createPartDescriptors() {
Set<TaskEditorPartDescriptor> descriptors = super.createPartDescriptors();
// FIXME: remove unnecessary default editor parts
for (Iterator<TaskEditorPartDescriptor> it = descriptors.iterator(); it.hasNext();) {
TaskEditorPartDescriptor descriptor = it.next();
if (descriptor.getId().equals(ID_PART_PLANNING)) {
it.remove();
}
}
return descriptors;
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import org.eclipse.mylyn.commons.ui.CommonImages;
import org.eclipse.mylyn.tasks.ui.ITasksUiConstants;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.mylyn.tasks.ui.TasksUiUtil;
import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPageFactory;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.forms.editor.IFormPage;
import io.gitea.mylyn.core.GiteaPluginCore;
public class GiteaEditorPageFactory extends AbstractTaskEditorPageFactory {
@Override
public boolean canCreatePageFor(TaskEditorInput input) {
if (input.getTask().getConnectorKind().equals(GiteaPluginCore.CONNECTOR_KIND)) {
return true;
} else if (TasksUiUtil.isOutgoingNewTask(input.getTask(), GiteaPluginCore.CONNECTOR_KIND)) {
return true;
}
return false;
}
@Override
public IFormPage createPage(TaskEditor editor) {
return new GiteaEditorPage(editor, GiteaPluginCore.CONNECTOR_KIND);
}
@Override
public int getPriority() {
return 0;
}
@Override
public Image getPageImage() {
return CommonImages.getImage(TasksUiImages.TASK);
}
@Override
public String getPageText() {
return Labels.GITEA_ISSUE;
}
@Override
public String[] getConflictingIds(TaskEditorInput input) {
return new String[] { ITasksUiConstants.ID_PAGE_PLANNING };
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
//FIXME: complete with other kind of issue
public class GiteaImages {
private static final URL baseURL = GiteaUIPlugin.getDefault().getBundle().getEntry("/icons/"); //$NON-NLS-1$
public static final ImageDescriptor ISSUE = create("gitea-issue.png"); //$NON-NLS-1$
public static final ImageDescriptor ISSUE_OPEN = create("gitea-issue-open.png"); //$NON-NLS-1$
public static final ImageDescriptor ISSUE_CLOSED = create("gitea-issue-closed.png"); //$NON-NLS-1$
public static final ImageDescriptor OVERLAY_FEATURE = create("overlay-feature.png"); //$NON-NLS-1$
public static final ImageDescriptor OVERLAY_STORY = create("overlay-story.png"); //$NON-NLS-1$
public static final Image LABEL = create("gitea-label.png").createImage();
public static final Image MILESTONE = create("gitea-milestone.png").createImage();
public static final Image REPOSITORY = create("gitea-repository.png").createImage();
private static ImageDescriptor create(String name) {
try {
return ImageDescriptor.createFromURL(makeIconFileURL(name));
} catch (MalformedURLException e) {
return ImageDescriptor.getMissingImageDescriptor();
}
}
private static URL makeIconFileURL(String name) throws MalformedURLException {
if (baseURL == null) {
throw new MalformedURLException();
}
StringBuilder buffer = new StringBuilder();
buffer.append(name);
return new URL(baseURL, buffer.toString());
}
}

View File

@ -0,0 +1,289 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import java.util.ArrayList;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositoryQueryPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import io.gitea.model.IssueState;
import io.gitea.model.Milestone;
import io.gitea.mylyn.core.ConnectionManager;
import io.gitea.mylyn.core.GiteaConnection;
public class GiteaQueryPage extends AbstractRepositoryQueryPage implements IWizardPage {
private Button openButton;
private Button closedButton;
private Text titleText;
private Text assigneeText;
private Text newLabel;
private Combo milestoneCombo;
private TableViewer labelsViewer;
private SelectionListener completeListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
setPageComplete(isPageComplete());
}
};
/**
* @param pageName
* @param taskRepository
* @param query
*/
public GiteaQueryPage(String pageName, TaskRepository taskRepository, IRepositoryQuery query) {
super(pageName, taskRepository, query);
setDescription("Specify your query");
setPageComplete(false);
}
private void createLabelsArea(Composite parent) {
Group labelsArea = new Group(parent, SWT.NONE);
labelsArea.setText(Labels.QUERY_GROUP_LABELS);
GridDataFactory.fillDefaults().grab(true, true).applyTo(labelsArea);
GridLayoutFactory.swtDefaults().applyTo(labelsArea);
labelsViewer = new TableViewer(labelsArea, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
GridDataFactory.fillDefaults().grab(true, true).hint(100, 80).applyTo(labelsViewer.getControl());
labelsViewer.setContentProvider(ArrayContentProvider.getInstance());
labelsViewer.setLabelProvider(new LabelProvider() {
public Image getImage(Object element) {
return GiteaImages.LABEL;
}
});
newLabel = new Text(labelsArea, SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).applyTo(newLabel);
Composite btnArea = new Composite(labelsArea, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(btnArea);
GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(btnArea);
final Button btnAdd = new Button(btnArea, SWT.BORDER);
btnAdd.setText(Labels.QUERY_NEW_LABEL_REGEX);
GridDataFactory.fillDefaults().grab(true, false).applyTo(btnAdd);
Button btnRemove = new Button(btnArea, SWT.BORDER);
btnRemove.setText(Labels.QUERY_REMOVE_LABEL_REGEX);
GridDataFactory.fillDefaults().grab(true, false).applyTo(btnRemove);
btnAdd.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
String[] split = newLabel.getText().split(",");
for (String s : split) {
if (s.trim().length() > 0) {
labelsViewer.add(s.trim());
}
}
newLabel.setText("");
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
btnRemove.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
removeSelection();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
newLabel.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
getShell().setDefaultButton(null);
setPageComplete(isPageComplete());
}
@Override
public void focusGained(FocusEvent e) {
getShell().setDefaultButton(btnAdd);
}
});
labelsViewer.getTable().addKeyListener(new KeyListener() {
@Override
public void keyReleased(KeyEvent e) {
if (e.keyCode == SWT.DEL) {
removeSelection();
}
}
@Override
public void keyPressed(KeyEvent e) {
}
});
}
private void removeSelection() {
StructuredSelection selection = (StructuredSelection) labelsViewer.getSelection();
if (!selection.isEmpty()) {
labelsViewer.remove(selection.toArray());
}
}
private void createOptionsArea(Composite parent) {
Composite optionsArea = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(optionsArea);
GridDataFactory.fillDefaults().grab(true, true).applyTo(optionsArea);
Composite statusArea = new Composite(optionsArea, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(4).equalWidth(false).applyTo(statusArea);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(statusArea);
new Label(statusArea, SWT.NONE).setText(Labels.QUERY_STATE);
openButton = new Button(statusArea, SWT.CHECK);
openButton.setSelection(true);
openButton.setText(IssueState.STATE_OPENED.toString());
openButton.addSelectionListener(completeListener);
closedButton = new Button(statusArea, SWT.CHECK);
closedButton.setSelection(true);
closedButton.setText(IssueState.STATE_CLOSED.toString());
closedButton.addSelectionListener(completeListener);
Label milestonesLabel = new Label(optionsArea, SWT.NONE);
milestonesLabel.setText(Labels.QUERY_MILESTONE);
milestoneCombo = new Combo(optionsArea, SWT.DROP_DOWN | SWT.READ_ONLY);
GridDataFactory.fillDefaults().grab(true, false).applyTo(milestoneCombo);
GiteaConnection connection = ConnectionManager.getSafe(getTaskRepository());
if (connection != null) {
milestoneCombo.add("");
for (Milestone s : connection.getMilestones()) {
milestoneCombo.add(s.getTitle());
}
}
Label assigneeLabel = new Label(optionsArea, SWT.NONE);
assigneeLabel.setText(Labels.QUERY_ASSIGNEE);
assigneeText = new Text(optionsArea, SWT.BORDER | SWT.SINGLE);
GridDataFactory.fillDefaults().grab(true, false).applyTo(assigneeText);
}
public void createControl(Composite parent) {
Composite displayArea = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true).applyTo(displayArea);
GridDataFactory.fillDefaults().grab(true, true).applyTo(displayArea);
if (!inSearchContainer()) {
Composite titleArea = new Composite(displayArea, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(titleArea);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(titleArea);
new Label(titleArea, SWT.NONE).setText(Labels.QUERY_TITLE);
titleText = new Text(titleArea, SWT.SINGLE | SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).applyTo(titleText);
titleText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
setPageComplete(isPageComplete());
}
});
}
createOptionsArea(displayArea);
createLabelsArea(displayArea);
initialize();
setControl(displayArea);
}
private void initialize() {
IRepositoryQuery query = getQuery();
if (query == null) {
return;
}
titleText.setText(query.getSummary());
assigneeText.setText(query.getAttribute("assignee"));
milestoneCombo.setText(query.getAttribute("milestone"));
openButton.setSelection(Boolean.parseBoolean(query.getAttribute("opened")));
closedButton.setSelection(Boolean.parseBoolean(query.getAttribute("closed")));
for (String label : query.getAttribute("labels").split(",")) {
if (label.trim().length() > 0) {
labelsViewer.add(label.trim());
}
}
}
public boolean isPageComplete() {
boolean complete = inSearchContainer() ? true : super.isPageComplete();
if (complete) {
String message = null;
if (!openButton.getSelection() && !closedButton.getSelection()) {
message = "Select either closed, opened or both issue states";
}
setErrorMessage(message);
complete = message == null;
}
return complete;
}
public String getQueryTitle() {
return titleText != null ? titleText.getText() : null;
}
public void applyTo(IRepositoryQuery query) {
query.setSummary(titleText.getText());
query.setAttribute("assignee", assigneeText.getText());
query.setAttribute("milestone", milestoneCombo.getText());
query.setAttribute("opened", "" + openButton.getSelection());
query.setAttribute("closed", "" + closedButton.getSelection());
ArrayList<String> labels = new ArrayList<String>();
for (TableItem i : labelsViewer.getTable().getItems()) {
labels.add(i.getText());
}
query.setAttribute("labels", StringUtils.join(labels, ","));
}
}

View File

@ -0,0 +1,150 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositorySettingsPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import io.gitea.mylyn.core.GiteaConnector;
import io.gitea.mylyn.core.GiteaPluginCore;
/** Mylyn Gitea repository configuration page. */
public class GiteaRepositorySettingsPage extends AbstractRepositorySettingsPage {
private Button useToken;
private Text giteaBaseUrl;
public GiteaRepositorySettingsPage(String title, String description, TaskRepository taskRepository) {
super(title, description, taskRepository);
setNeedsValidateOnFinish(true);
}
@Override
protected void createAdditionalControls(final Composite composite) {
savePasswordButton.setSelection(true);
useToken = new Button(composite, SWT.CHECK);
useToken.setText("Use private token instead of username/password");
GridDataFactory.fillDefaults().span(2, 1).applyTo(useToken);
useToken.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
setUsernameFieldEnabled(!useToken.getSelection());
getWizard().getContainer().updateButtons();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
Label l = new Label(composite, SWT.NONE);
l.setText("Gitea base URL");
giteaBaseUrl = new Text(composite, SWT.SINGLE | SWT.BORDER);
GridDataFactory.fillDefaults().span(1, 1).applyTo(giteaBaseUrl);
/**
* Set widget texts and check boxes if necessary.
*/
if (serverUrlCombo.getText().length() == 0) {
// This means, that there the user is *not* editing an existing repository
// configuration
serverUrlCombo.setText("https://your-host.org/namespace/repository.git");
serverUrlCombo.setText(Labels.SAMPLE_URL);
}
if (getRepository() != null) {
if ("true".equals(getRepository().getProperty("usePrivateToken"))) {
useToken.setSelection(true);
setUsernameFieldEnabled(false);
}
if (getRepository().getProperty("giteaBaseUrl") != null) {
giteaBaseUrl.setText(getRepository().getProperty("giteaBaseUrl"));
}
} else {
// Default is to use token
useToken.setSelection(true); // Let select use token by default
setUsernameFieldEnabled(false);
}
}
private void setUsernameFieldEnabled(boolean enabled) {
if (enabled) {
repositoryUserNameEditor.getTextControl(compositeContainer).setEnabled(true);
repositoryUserNameEditor.setEmptyStringAllowed(false);
repositoryPasswordEditor.setLabelText(LABEL_PASSWORD);
compositeContainer.layout();
} else {
repositoryUserNameEditor.setStringValue("");
repositoryUserNameEditor.getTextControl(compositeContainer).setEnabled(false);
repositoryUserNameEditor.setEmptyStringAllowed(true);
repositoryPasswordEditor.setLabelText("Private token:");
compositeContainer.layout();
}
}
@Override
public String getConnectorKind() {
return GiteaPluginCore.CONNECTOR_KIND;
}
@Override
public TaskRepository createTaskRepository() {
TaskRepository repo = super.createTaskRepository();
return repo;
}
@Override
public void applyTo(TaskRepository repository) {
repository.setCategory(TaskRepository.CATEGORY_BUGS);
super.applyTo(repository);
if (useToken.getSelection()) {
repository.setProperty("usePrivateToken", "true");
} else {
repository.setProperty("usePrivateToken", "false");
}
repository.setProperty("giteaBaseUrl", giteaBaseUrl.getText());
}
@Override
protected boolean isMissingCredentials() {
if (useToken != null && useToken.getSelection()) {
return repositoryPasswordEditor.getStringValue().trim().equals("");
} else {
return super.isMissingCredentials();
}
}
@Override
protected Validator getValidator(final TaskRepository repository) {
return new Validator() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
GiteaConnector.validate(repository);
}
};
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
public class GiteaUIPlugin extends AbstractUIPlugin {
private static GiteaUIPlugin plugin;
public GiteaUIPlugin() {
plugin = this;
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
}
@Override
public void stop(BundleContext context) throws Exception {
plugin = null;
super.stop(context);
}
public static GiteaUIPlugin getDefault() {
return plugin;
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) 2021, Fr.Terrot. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package io.gitea.mylyn.ui;
public class Labels {
public final static String SAMPLE_URL = "https://forge.chapril.org/gitea/mylyn-gitea.git";
public final static String NEW_REPOSITORY = "New repository";
public final static String SETTINGS_PAGE = "Enter the HTTPS-URL to your Gitea repository";
public final static String QUERY_TITLE = "Query title: ";
public final static String QUERY_GROUP_LABELS = "Labels";
public final static String QUERY_NEW_LABEL_REGEX = "Add label regex";
public final static String QUERY_REMOVE_LABEL_REGEX = "Remove selected label";
public final static String QUERY_ASSIGNEE = "Assignee: ";
public final static String QUERY_MILESTONE = "Milestone: ";
public final static String QUERY_STATE = "State: ";
public final static String GITEA_ISSUE = "Gitea issue";
}

@ -0,0 +1 @@
Subproject commit f379a24a325596b54048a634152fda657e53a6c9

17
io.gitea.mylyn/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>io.gitea.mylyn</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.pde.FeatureBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.FeatureNature</nature>
</natures>
</projectDescription>

19
io.gitea.mylyn/LICENSE Normal file
View File

@ -0,0 +1,19 @@
MIT License Copyright (c) 2021 F.Terrot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,3 @@
bin.includes = feature.xml,\
feature.properties,\
LICENSE

View File

@ -0,0 +1,46 @@
#/* **************************************************************************
# *
# * Copyright (c) 2021, Fr.Terrot
# *
# * All rights reserved.
# *
# * This program and the accompanying materials are made available under the terms
# * of the Eclipse Public License v2.0 which accompanies this distribution, and is
# * available at http://www.eclipse.org/legal/epl-v20.html
# *
# * ************************************************************************** */
featureName=Mylyn Gitea Connector
providerName=Gitea
description=Mylyn Tasks connector for the open source management software Gitea.
# "licenseURL" property - URL of the "Feature License"
# do not translate value - just change to point to a locale-specific HTML page
licenseURL=https://forge.chapril.org/gitea/mylyn-gitea/raw/branch/development/LICENSE
# "license" property - text of the "Feature Update License"
# should be plain text version of license agreement pointed to be "licenseURL"
license=\
\n
MIT License Copyright (c) 2021 F.Terrot\n
\n
Permission is hereby granted, free of charge, to any person obtaining a copy\n
of this software and associated documentation files (the "Software"), to deal\n
in the Software without restriction, including without limitation the rights\n
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n
copies of the Software, and to permit persons to whom the Software is furnished\n
to do so, subject to the following conditions:\n
\n
The above copyright notice and this permission notice (including the next\n
paragraph) shall be included in all copies or substantial portions of the\n
Software.\n
\n
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\n
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF\n
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n
\n
########### end of license property ##########################################

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<feature
id="io.gitea.mylyn"
label="Mylyn Gitea Connector"
version="0.1.0"
provider-name="io.gitea">
<description>
%description
</description>
<copyright>
Copyright (c) 2021 François Terrot
All rights reserved. This program and the accompanying materials
are made available under the terms of the MIT License which
accompanies this distribution.
</copyright>
<license url="">
%license
</license>
<url>
<update label="Mylyn Gitea Connector Update Site" url="https://forge.chapril.org/gitea/mylyn-gitea/io.gitea.mylyn.updatesite"/>
</url>
<requires>
<import plugin="org.eclipse.core.runtime"/>
<import plugin="org.eclipse.mylyn.tasks.core" version="3.8.0" match="compatible"/>
<import plugin="org.eclipse.mylyn.tasks.ui"/>
<import plugin="org.eclipse.mylyn.commons.net" version="3.8.0" match="compatible"/>
<import plugin="org.eclipse.ui"/>
<import plugin="org.eclipse.ui.forms"/>
<import plugin="org.eclipse.mylyn.commons.core" version="3.8.0" match="compatible"/>
<import plugin="org.eclipse.mylyn.tasks.ui" version="3.8.0" match="compatible"/>
<import plugin="org.eclipse.mylyn.commons.workbench" version="3.8.0" match="compatible"/>
<import plugin="org.eclipse.mylyn.commons.ui" version="3.8.0" match="compatible"/>
</requires>
<plugin
id="io.gitea.mylyn.core"
download-size="0"
install-size="0"
version="0.1.2"
unpack="false"/>
<plugin
id="io.gitea.mylyn.ui"
download-size="0"
install-size="0"
version="0.1.1"
unpack="false"/>
</feature>

1
java.gitea.api Submodule

@ -0,0 +1 @@
Subproject commit 0f63be058234fcfa1bce7d09f3b4caff06f84ffc