You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/03/01 21:44:51 UTC

[camel-karavan] branch main updated (13902116 -> ec37f5fa)

This is an automated email from the ASF dual-hosted git repository.

marat pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


    from 13902116 Copy files from designer
     new eaaff792 Git progect pull for #647
     new ec37f5fa Fix #645

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 karavan-app/pom.xml                                |  17 +-
 .../{GitResource.java => ProjectGitResource.java}  |  23 ++-
 .../org/apache/camel/karavan/model/CommitInfo.java |  39 ++++
 .../apache/camel/karavan/service/GitService.java   | 218 ++++++++++++++++-----
 .../camel/karavan/service/InfinispanService.java   |  22 ++-
 .../camel/karavan/service/KaravanService.java      |   5 +-
 .../camel/karavan/service/KubernetesService.java   |   4 +-
 .../{ImportService.java => ProjectService.java}    | 146 ++++++++------
 .../apache/camel/karavan/service/ServiceUtil.java  |  66 +++++++
 .../src/main/resources/application.properties      |   6 +-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  12 +-
 .../main/webui/src/projects/ProjectPageToolbar.tsx |   7 +-
 12 files changed, 434 insertions(+), 131 deletions(-)
 rename karavan-app/src/main/java/org/apache/camel/karavan/api/{GitResource.java => ProjectGitResource.java} (70%)
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java
 rename karavan-app/src/main/java/org/apache/camel/karavan/service/{ImportService.java => ProjectService.java} (54%)
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java


[camel-karavan] 02/02: Fix #645

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit ec37f5fa1337f3fc7407a4e1afc3cda04c97b4ff
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Wed Mar 1 16:44:40 2023 -0500

    Fix #645
---
 karavan-app/pom.xml                                |  17 +-
 .../{GitResource.java => ProjectGitResource.java}  |  21 +-
 .../org/apache/camel/karavan/model/CommitInfo.java |  39 ++++
 .../apache/camel/karavan/service/GitService.java   | 214 ++++++++++++++++-----
 .../camel/karavan/service/InfinispanService.java   |  22 ++-
 .../camel/karavan/service/KaravanService.java      |   5 +-
 .../camel/karavan/service/KubernetesService.java   |   4 +-
 .../{ImportService.java => ProjectService.java}    | 139 +++++++------
 .../apache/camel/karavan/service/ServiceUtil.java  |  66 +++++++
 .../src/main/resources/application.properties      |   6 +-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  12 +-
 .../main/webui/src/projects/ProjectPageToolbar.tsx |   7 +-
 12 files changed, 403 insertions(+), 149 deletions(-)

diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 402b8cde..151cfb34 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -30,8 +30,9 @@
         <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
         <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
         <quarkus.platform.version>2.16.0.Final</quarkus.platform.version>
-        <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
+        <camel-quarkus.version>2.16.0</camel-quarkus.version>
         <camel.version>3.20.2</camel.version>
+        <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
         <infinispan.version>14.0.5.Final</infinispan.version>
         <tekton.version>6.2.0</tekton.version>
         <jgit.version>2.3.1</jgit.version>
@@ -114,6 +115,10 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-qute</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-scheduler</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.fabric8</groupId>
             <artifactId>tekton-client</artifactId>
@@ -152,11 +157,11 @@
             <artifactId>rest-assured</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>io.quarkiverse.quinoa</groupId>
-            <artifactId>quarkus-quinoa</artifactId>
-            <version>${quinoa.version}</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>io.quarkiverse.quinoa</groupId>-->
+<!--            <artifactId>quarkus-quinoa</artifactId>-->
+<!--            <version>${quinoa.version}</version>-->
+<!--        </dependency>-->
     </dependencies>
     <build>
         <resources>
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
similarity index 72%
rename from karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
rename to karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
index 3e9de9c2..f2d0196f 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
@@ -16,12 +16,8 @@
  */
 package org.apache.camel.karavan.api;
 
-import org.apache.camel.karavan.model.GitRepo;
 import org.apache.camel.karavan.model.Project;
-import org.apache.camel.karavan.model.ProjectFile;
-import org.apache.camel.karavan.service.GitService;
-import org.apache.camel.karavan.service.ImportService;
-import org.jboss.logging.Logger;
+import org.apache.camel.karavan.service.ProjectService;
 
 import javax.inject.Inject;
 import javax.ws.rs.Consumes;
@@ -33,24 +29,17 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import java.util.HashMap;
 
-
 @Path("/api/git")
-public class GitResource {
-
-    @Inject
-    GitService gitService;
+public class ProjectGitResource {
 
     @Inject
-    ImportService importService;
-
-    private static final Logger LOGGER = Logger.getLogger(GitResource.class.getName());
-
+    ProjectService projectService;
 
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public Project push(HashMap<String, String> params) throws Exception {
-        return gitService.commitAndPushProject(params.get("projectId"), params.get("message"));
+        return projectService.commitAndPushProject(params.get("projectId"), params.get("message"));
     }
 
     @GET
@@ -58,6 +47,6 @@ public class GitResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/{projectId}")
     public Project pull(@PathParam("projectId") String projectId) throws Exception {
-        return importService.importProject(projectId);
+        return projectService.importProject(projectId);
     }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java
new file mode 100644
index 00000000..2affc488
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/CommitInfo.java
@@ -0,0 +1,39 @@
+package org.apache.camel.karavan.model;
+
+import java.util.List;
+
+public class CommitInfo {
+    private String commitId;
+    private Integer time;
+    private List<GitRepo> repos;
+
+    public CommitInfo(String commitId, Integer time, List<GitRepo> repos) {
+        this.commitId = commitId;
+        this.time = time;
+        this.repos = repos;
+    }
+
+    public String getCommitId() {
+        return commitId;
+    }
+
+    public void setCommitId(String commitId) {
+        this.commitId = commitId;
+    }
+
+    public Integer getTime() {
+        return time;
+    }
+
+    public void setTime(Integer time) {
+        this.time = time;
+    }
+
+    public List<GitRepo> getRepos() {
+        return repos;
+    }
+
+    public void setRepos(List<GitRepo> repos) {
+        this.repos = repos;
+    }
+}
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
index 57765e4d..2b459861 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
@@ -17,27 +17,35 @@
 package org.apache.camel.karavan.service;
 
 import io.fabric8.kubernetes.api.model.Secret;
-import io.quarkus.runtime.StartupEvent;
 import io.smallrye.mutiny.tuples.Tuple2;
 import io.vertx.core.Vertx;
+import org.apache.camel.karavan.model.CommitInfo;
 import org.apache.camel.karavan.model.GitConfig;
 import org.apache.camel.karavan.model.GitRepo;
 import org.apache.camel.karavan.model.GitRepoFile;
 import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.model.ProjectFile;
-import org.eclipse.jgit.api.*;
+import org.eclipse.jgit.api.CheckoutCommand;
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.FetchCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RemoteAddCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.eclipse.microprofile.config.ConfigProvider;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import java.io.File;
 import java.io.IOException;
@@ -46,16 +54,19 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.time.Instant;
-import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Base64;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 @ApplicationScoped
 public class GitService {
@@ -66,25 +77,69 @@ public class GitService {
     @Inject
     KubernetesService kubernetesService;
 
-    @Inject
-    InfinispanService infinispanService;
+    private Git git;
 
     private static final Logger LOGGER = Logger.getLogger(GitService.class.getName());
 
-    void onStart(@Observes StartupEvent ev) {
-        LOGGER.info("Git service for repo: " + getGitConfig().getUri());
+    public Git getPollGit(){
+        if (git == null) {
+            try {
+                git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking("poll"));
+            } catch (Exception e) {
+                LOGGER.error("Error", e);
+            }
+        }
+        return git;
+    }
+
+    public Map<String, Integer> getAllCommits() {
+        Map<String, Integer> result = new HashMap();
+        try {
+            Git pollGit = getPollGit();
+            if (pollGit != null) {
+                StreamSupport.stream(pollGit.log().all().call().spliterator(), false)
+                        .sorted(Comparator.comparingInt(RevCommit::getCommitTime))
+                        .forEach(commit -> result.put(commit.getName(), commit.getCommitTime()));
+            }
+        } catch (Exception e) {
+            LOGGER.error(e.getMessage());
+        }
+        return result;
+    }
+
+    public List<CommitInfo> getCommitsAfterCommit(int commitTime) {
+        List<CommitInfo> result = new ArrayList<>();
+        try {
+            Git pollGit = getPollGit();
+            if (pollGit != null) {
+                GitConfig gitConfig = getGitConfig();
+                CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword());
+                fetch(pollGit, cred);
+                List<RevCommit> commits = StreamSupport.stream(pollGit.log().all().call().spliterator(), false)
+                        .filter(commit -> commit.getCommitTime() > commitTime)
+                        .sorted(Comparator.comparingInt(RevCommit::getCommitTime)).collect(Collectors.toList());
+                for (RevCommit commit: commits) {
+                    List<String> projects = new ArrayList<>(getChangedProjects(commit));
+                    List<GitRepo> repo = readProjectsFromRepository(pollGit, projects.toArray(new String[projects.size()]));
+                    result.add(new CommitInfo(commit.getName(), commit.getCommitTime(), repo));
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.error(e.getMessage());
+        }
+        return result;
     }
 
-    private GitConfig getGitConfig() {
+    public GitConfig getGitConfig() {
         String propertiesPrefix = "karavan.";
         String branch = ConfigProvider.getConfig().getValue(propertiesPrefix + "git-branch", String.class);
-        if (kubernetesService.inKubernetes()){
+        if (kubernetesService.inKubernetes()) {
             LOGGER.info("inKubernetes " + kubernetesService.getNamespace());
-            Secret secret =  kubernetesService.getKaravanSecret();
+            Secret secret = kubernetesService.getKaravanSecret();
             String uri = new String(Base64.getDecoder().decode(secret.getData().get("git-repository").getBytes(StandardCharsets.UTF_8)));
             String username = new String(Base64.getDecoder().decode(secret.getData().get("git-username").getBytes(StandardCharsets.UTF_8)));
             String password = new String(Base64.getDecoder().decode(secret.getData().get("git-password").getBytes(StandardCharsets.UTF_8)));
-            if (secret.getData().containsKey("git-branch")){
+            if (secret.getData().containsKey("git-branch")) {
                 branch = new String(Base64.getDecoder().decode(secret.getData().get("git-branch").getBytes(StandardCharsets.UTF_8)));
             }
             return new GitConfig(uri, username, password, branch);
@@ -96,18 +151,6 @@ public class GitService {
         }
     }
 
-    public Project commitAndPushProject(String projectId, String message) throws Exception {
-        Project p = infinispanService.getProject(projectId);
-        List<ProjectFile> files = infinispanService.getProjectFiles(projectId);
-        RevCommit commit = commitAndPushProject(p, files, message);
-        String commitId = commit.getId().getName();
-        Long lastUpdate = commit.getCommitTime() * 1000L;
-        p.setLastCommit(commitId);
-        p.setLastCommitTimestamp(lastUpdate);
-        infinispanService.saveProject(p, false);
-        return p;
-    }
-
     public RevCommit commitAndPushProject(Project project, List<ProjectFile> files, String message) throws GitAPIException, IOException, URISyntaxException {
         LOGGER.info("Commit and push project " + project.getProjectId());
         GitConfig gitConfig = getGitConfig();
@@ -131,29 +174,35 @@ public class GitService {
     }
 
     public List<GitRepo> readProjectsFromRepository() {
-        return readProjectsFromRepository(null);
+        Git git = null;
+        try {
+            git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking(UUID.randomUUID().toString()));
+        } catch (Exception e) {
+            LOGGER.error("Error", e);
+        }
+        return readProjectsFromRepository(git, null);
     }
 
     public GitRepo readProjectFromRepository(String projectId) {
-        return readProjectsFromRepository(projectId).get(0);
+        Git git = null;
+        try {
+            git = getGit(true, vertx.fileSystem().createTempDirectoryBlocking(UUID.randomUUID().toString()));
+        } catch (Exception e) {
+            LOGGER.error("Error", e);
+        }
+        return readProjectsFromRepository(git, projectId).get(0);
     }
 
-    private List<GitRepo> readProjectsFromRepository(String filter) {
+    private List<GitRepo> readProjectsFromRepository(Git git, String... filter) {
         LOGGER.info("Read projects...");
-        GitConfig gitConfig = getGitConfig();
-        LOGGER.info("Read projects from repository " + gitConfig.getUri());
-        CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword());
-        String uuid = UUID.randomUUID().toString();
-        String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid);
-        LOGGER.infof("Temp folder created: %s", folder);
         List<GitRepo> result = new ArrayList<>();
-        Git git = null;
         try {
-            git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred);
-            checkout(git, false, null, null, gitConfig.getBranch());
+            String folder = git.getRepository().getDirectory().getAbsolutePath().replace("/.git", "");
             List<String> projects = readProjectsFromFolder(folder);
             if (filter != null) {
-                projects = projects.stream().filter(s -> s.equals(filter)).collect(Collectors.toList());
+                projects = projects.stream().filter(s -> Arrays.stream(filter).filter(f -> f.equals(s)).findFirst().isPresent()).collect(Collectors.toList());
+            } else {
+                projects = projects.stream().filter(s -> !s.startsWith(".")).collect(Collectors.toList()); // do not import hidden folders
             }
             for (String project : projects) {
                 Map<String, String> filesRead = readProjectFilesFromFolder(folder, project);
@@ -178,6 +227,26 @@ public class GitService {
         }
     }
 
+    public Git getGit(boolean checkout, String folder) throws GitAPIException, IOException, URISyntaxException {
+        LOGGER.info("Git checkout");
+        GitConfig gitConfig = getGitConfig();
+        CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword());
+        LOGGER.info("Temp folder created " + folder);
+        Git git = null;
+        try {
+            git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred);
+            if (checkout) {
+                checkout(git, false, null, null, gitConfig.getBranch());
+            }
+        } catch (RefNotFoundException e) {
+            LOGGER.error("New repository");
+            git = init(folder, gitConfig.getUri(), gitConfig.getBranch());
+        } catch (Exception e) {
+            LOGGER.error("Error", e);
+        }
+        return git;
+    }
+
     private List<Tuple2<String, String>> readKameletsFromFolder(String folder) {
         LOGGER.info("Read kamelets from " + folder);
         List<Tuple2<String, String>> kamelets = new ArrayList<>();
@@ -198,7 +267,7 @@ public class GitService {
         List<String> files = new ArrayList<>();
         vertx.fileSystem().readDirBlocking(folder).forEach(f -> {
             String[] filenames = f.split(File.separator);
-            String folderName = filenames[filenames.length -1];
+            String folderName = filenames[filenames.length - 1];
             if (!folderName.startsWith(".") && Files.isDirectory(Paths.get(f))) {
                 LOGGER.info("Importing project from folder " + folderName);
                 files.add(folderName);
@@ -208,11 +277,11 @@ public class GitService {
     }
 
     private Map<String, String> readProjectFilesFromFolder(String repoFolder, String projectFolder) {
-        LOGGER.infof("Read files from %s/%s", repoFolder, projectFolder );
+        LOGGER.infof("Read files from %s/%s", repoFolder, projectFolder);
         Map<String, String> files = new HashMap<>();
         vertx.fileSystem().readDirBlocking(repoFolder + File.separator + projectFolder).forEach(f -> {
             String[] filenames = f.split(File.separator);
-            String filename = filenames[filenames.length -1];
+            String filename = filenames[filenames.length - 1];
             Path path = Paths.get(f);
             if (!filename.startsWith(".") && !Files.isDirectory(path)) {
                 LOGGER.info("Importing file " + filename);
@@ -244,9 +313,9 @@ public class GitService {
         LOGGER.info("Add deleted files to git index for project " + project.getProjectId());
         vertx.fileSystem().readDirBlocking(path.toString()).forEach(f -> {
             String[] filenames = f.split(File.separator);
-            String filename = filenames[filenames.length -1];
+            String filename = filenames[filenames.length - 1];
             LOGGER.info("Checking file " + filename);
-            if (files.stream().filter(pf -> Objects.equals(pf.getName(), filename)).count() == 0){
+            if (files.stream().filter(pf -> Objects.equals(pf.getName(), filename)).count() == 0) {
                 try {
                     LOGGER.info("Add deleted file " + filename);
                     git.rm().addFilepattern(project.getProjectId() + File.separator + filename).call();
@@ -257,7 +326,7 @@ public class GitService {
         });
     }
 
-    public RevCommit commitAddedAndPush(Git git, String branch, CredentialsProvider cred, String message) throws GitAPIException, IOException, URISyntaxException {
+    public RevCommit commitAddedAndPush(Git git, String branch, CredentialsProvider cred, String message) throws GitAPIException {
         LOGGER.info("Commit and push changes");
         LOGGER.info("Git add: " + git.add().addFilepattern(".").call());
         RevCommit commit = git.commit().setMessage(message).call();
@@ -273,16 +342,16 @@ public class GitService {
         return git;
     }
 
-    private void addDeletedFolderToIndex(Git git, String folder, String projectId, List<ProjectFile> files) throws IOException {
+    private void addDeletedFolderToIndex(Git git, String folder, String projectId, List<ProjectFile> files) {
         LOGGER.infof("Add folder %s to git index.", projectId);
         try {
-          git.rm().addFilepattern(projectId + File.separator).call();
+            git.rm().addFilepattern(projectId + File.separator).call();
         } catch (GitAPIException e) {
-          throw new RuntimeException(e);
+            throw new RuntimeException(e);
         }
     }
-	
-	public void deleteProject(String projectId, List<ProjectFile> files) throws GitAPIException, IOException, URISyntaxException {
+
+    public void deleteProject(String projectId, List<ProjectFile> files) {
         LOGGER.info("Delete and push project " + projectId);
         GitConfig gitConfig = getGitConfig();
         CredentialsProvider cred = new UsernamePasswordCredentialsProvider(gitConfig.getUsername(), gitConfig.getPassword());
@@ -298,7 +367,7 @@ public class GitService {
             commitAddedAndPush(git, gitConfig.getBranch(), cred, commitMessage);
             LOGGER.info("Delete Temp folder " + folder);
             vertx.fileSystem().deleteRecursiveBlocking(folder, true);
-            LOGGER.infof("Project %s deleted from Git" , projectId);
+            LOGGER.infof("Project %s deleted from Git", projectId);
         } catch (RefNotFoundException e) {
             LOGGER.error("Repository not found");
         } catch (Exception e) {
@@ -307,14 +376,16 @@ public class GitService {
         }
     }
 
-    private Git clone(String dir, String uri, String branch, CredentialsProvider cred) throws GitAPIException {
+    private Git clone(String dir, String uri, String branch, CredentialsProvider cred) throws GitAPIException, URISyntaxException {
         CloneCommand cloneCommand = Git.cloneRepository();
         cloneCommand.setCloneAllBranches(false);
         cloneCommand.setDirectory(Paths.get(dir).toFile());
         cloneCommand.setURI(uri);
         cloneCommand.setBranch(branch);
         cloneCommand.setCredentialsProvider(cred);
-        return cloneCommand.call();
+        Git git = cloneCommand.call();
+        addRemote(git, uri);
+        return git;
     }
 
     private void addRemote(Git git, String uri) throws URISyntaxException, GitAPIException {
@@ -325,12 +396,19 @@ public class GitService {
         remoteAddCommand.call();
     }
 
+    private void fetch(Git git, CredentialsProvider cred) throws GitAPIException {
+        // fetch:
+        FetchCommand fetchCommand = git.fetch();
+        fetchCommand.setCredentialsProvider(cred);
+        FetchResult result = fetchCommand.call();
+    }
+
     private void checkout(Git git, boolean create, String path, String startPoint, String branch) throws GitAPIException {
         // create branch:
         CheckoutCommand checkoutCommand = git.checkout();
         checkoutCommand.setName(branch);
         checkoutCommand.setCreateBranch(create);
-        if (startPoint != null){
+        if (startPoint != null) {
             checkoutCommand.setStartPoint(startPoint);
         }
         if (path != null) {
@@ -346,4 +424,34 @@ public class GitService {
         }
         return null;
     }
+
+    public Set<String> getChangedProjects(RevCommit commit) {
+        Set<String> files = new HashSet<>();
+        Git git = getPollGit();
+        if (git != null) {
+            TreeWalk walk = new TreeWalk(git.getRepository());
+            walk.setRecursive(true);
+            walk.setFilter(TreeFilter.ANY_DIFF);
+
+            ObjectId a = commit.getTree().getId();
+            RevCommit parent = commit.getParent(0);
+            ObjectId b = parent.getTree().getId();
+            try {
+                walk.reset(b, a);
+                List<DiffEntry> changes = DiffEntry.scan(walk);
+                changes.stream().forEach(de -> {
+                    String path = de.getNewPath();
+                    if (path != null) {
+                        String[] parts = path.split(File.separator);
+                        if (parts.length > 0) {
+                            files.add(parts[0]);
+                        }
+                    }
+                });
+            } catch (IOException e) {
+                LOGGER.error("Error", e);
+            }
+        }
+        return files;
+    }
 }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
index e7d49982..c85bde94 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.karavan.service;
 
+import io.smallrye.mutiny.tuples.Tuple2;
 import org.apache.camel.karavan.model.CamelStatus;
 import org.apache.camel.karavan.model.DeploymentStatus;
 import org.apache.camel.karavan.model.Environment;
@@ -59,6 +60,7 @@ public class InfinispanService {
     BasicCache<GroupedKey, CamelStatus> camelStatuses;
     BasicCache<GroupedKey, ServiceStatus> serviceStatuses;
     BasicCache<String, Environment> environments;
+    BasicCache<String, String> commits;
 
     @Inject
     RemoteCacheManager cacheManager;
@@ -95,7 +97,7 @@ public class InfinispanService {
             podStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PodStatus.CACHE, builder.build());
             serviceStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ServiceStatus.CACHE, builder.build());
             camelStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(CamelStatus.CACHE, builder.build());
-
+            commits = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache("commits", builder.build());
             cleanData();
         } else {
             LOGGER.info("InfinispanService is starting in remote mode");
@@ -107,6 +109,7 @@ public class InfinispanService {
             podStatuses = cacheManager.administration().getOrCreateCache(PodStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE)));
             serviceStatuses = cacheManager.administration().getOrCreateCache(ServiceStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, ServiceStatus.CACHE)));
             camelStatuses = cacheManager.administration().getOrCreateCache(CamelStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, CamelStatus.CACHE)));
+            commits = cacheManager.administration().getOrCreateCache("commits", new XMLStringConfiguration(String.format(CACHE_CONFIG, "commits")));
         }
     }
 
@@ -291,6 +294,23 @@ public class InfinispanService {
         environments.put(environment.getName(), environment);
     }
 
+    public void saveCommit(String commitId, int time) {
+        commits.put(commitId, String.valueOf(time));
+    }
+
+    public void saveLastCommit(String commitId) {
+        commits.put("lastCommitId", commitId);
+    }
+
+    public Tuple2<String, Integer> getLastCommit() {
+        String lastCommitId = commits.get("lastCommitId");
+        String time = commits.get(lastCommitId);
+        return Tuple2.of(lastCommitId, Integer.parseInt(time));
+    }
+
+    public boolean hasCommit(String commitId) {
+        return commits.get(commitId) != null;
+    }
 
     protected void clearAllStatuses() {
         CompletableFuture.allOf(
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
index d235d178..6f5d7c33 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
@@ -67,10 +67,11 @@ public class KaravanService {
     void initialImport() {
         if (infinispanService.getProjects().isEmpty()) {
             LOGGER.info("No projects found in the Data Grid");
-            bus.publish(ImportService.IMPORT_PROJECTS, "");
+            bus.publish(ProjectService.IMPORT_PROJECTS, "");
         } else {
-            bus.publish(ImportService.IMPORT_TEMPLATES, "");
+            bus.publish(ProjectService.IMPORT_TEMPLATES, "");
         }
+        bus.publish(ProjectService.IMPORT_COMMITS, "");
     }
 
     void startInformers() {
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
index 62175b2d..796dbfc3 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
@@ -137,8 +137,8 @@ public class KubernetesService {
         return "karavan-pipeline-" + environment + "-" + project.getRuntime();
     }
 
-    public String createPipelineRun(Project project) throws Exception {
-        String pipeline  = getPipelineName(project);
+    public String createPipelineRun(Project project) {
+        String pipeline = getPipelineName(project);
         LOGGER.info("Pipeline " + pipeline + " is creating for " + project.getProjectId());
 
         Map<String, String> labels = Map.of(
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
similarity index 60%
rename from karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
rename to karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 3ebc09c4..9d8f174b 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -16,31 +16,36 @@
  */
 package org.apache.camel.karavan.service;
 
+import io.quarkus.scheduler.Scheduled;
 import io.quarkus.vertx.ConsumeEvent;
+import io.smallrye.mutiny.tuples.Tuple2;
 import org.apache.camel.karavan.model.GitRepo;
-import org.apache.camel.karavan.model.GitRepoFile;
 import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.model.ProjectFile;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @ApplicationScoped
-public class ImportService {
+public class ProjectService {
 
-    private static final Logger LOGGER = Logger.getLogger(ImportService.class.getName());
+    private static final Logger LOGGER = Logger.getLogger(ProjectService.class.getName());
     public static final String IMPORT_TEMPLATES = "import-templates";
     public static final String IMPORT_PROJECTS = "import-projects";
-
+    public static final String IMPORT_COMMITS = "import-commits";
 
     @Inject
     InfinispanService infinispanService;
 
+    @Inject
+    KubernetesService kubernetesService;
+
     @Inject
     GitService gitService;
 
@@ -50,8 +55,37 @@ public class ImportService {
     @ConfigProperty(name = "karavan.default-runtime")
     String runtime;
 
+    private AtomicBoolean readyToPull = new AtomicBoolean(false);
+
+    @Scheduled(every = "{karavan.git-pull-interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
+    void pullCommits() {
+        if (readyToPull.get()) {
+            Tuple2<String, Integer> lastCommit = infinispanService.getLastCommit();
+            gitService.getCommitsAfterCommit(lastCommit.getItem2()).forEach(commitInfo -> {
+                if (!infinispanService.hasCommit(commitInfo.getCommitId())) {
+                    commitInfo.getRepos().forEach(repo -> {
+                        Project project = importProjectFromRepo(repo);
+                        kubernetesService.createPipelineRun(project);
+                    });
+                    infinispanService.saveCommit(commitInfo.getCommitId(), commitInfo.getTime());
+                }
+                infinispanService.saveLastCommit(commitInfo.getCommitId());
+            });
+        }
+    }
+
+    @ConsumeEvent(value = IMPORT_COMMITS, blocking = true)
+    void importCommits(String data) {
+        LOGGER.info("Import commits");
+        gitService.getAllCommits().forEach((commitId, time) -> {
+            infinispanService.saveCommit(commitId, time);
+            infinispanService.saveLastCommit(commitId);
+        });
+        readyToPull.set(true);
+    }
+
     @ConsumeEvent(value = IMPORT_PROJECTS, blocking = true)
-    void importProjects(String data) {
+    void importAllProjects(String data) {
         LOGGER.info("Import projects from Git");
         try {
             List<GitRepo> repos = gitService.readProjectsFromRepository();
@@ -63,11 +97,7 @@ public class ImportService {
                 } else if (folderName.equals(Project.NAME_KAMELETS)){
                     project = new Project(Project.NAME_KAMELETS, "Custom Kamelets", "Custom Kamelets", "quarkus", repo.getCommitId(), repo.getLastCommitTimestamp());
                 } else {
-                    String propertiesFile = getPropertiesFile(repo);
-                    String projectName = getProjectName(propertiesFile);
-                    String projectDescription = getProjectDescription(propertiesFile);
-                    String runtime = getProjectRuntime(propertiesFile);
-                    project = new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp());
+                    project = getProjectFromRepo(repo);
                 }
                 infinispanService.saveProject(project, true);
 
@@ -87,16 +117,20 @@ public class ImportService {
         LOGGER.info("Import project from Git " + projectId);
         try {
             GitRepo repo = gitService.readProjectFromRepository(projectId);
-            Project project;
-            String folderName = repo.getName();
-            String propertiesFile = getPropertiesFile(repo);
-            String projectName = getProjectName(propertiesFile);
-            String projectDescription = getProjectDescription(propertiesFile);
-            String runtime = getProjectRuntime(propertiesFile);
-            project = new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp());
+            return importProjectFromRepo(repo);
+        } catch (Exception e) {
+            LOGGER.error("Error during project import", e);
+            return null;
+        }
+    }
+
+    private Project importProjectFromRepo(GitRepo repo) {
+        LOGGER.info("Import project from GitRepo " + repo.getName());
+        try {
+            Project project = getProjectFromRepo(repo);
             infinispanService.saveProject(project, true);
             repo.getFiles().forEach(repoFile -> {
-                ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), folderName, repoFile.getLastCommitTimestamp());
+                ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), repo.getName(), repoFile.getLastCommitTimestamp());
                 infinispanService.saveProjectFile(file);
             });
             return project;
@@ -106,6 +140,28 @@ public class ImportService {
         }
     }
 
+    public Project getProjectFromRepo(GitRepo repo) {
+        String folderName = repo.getName();
+        String propertiesFile = ServiceUtil.getPropertiesFile(repo);
+        String projectName = ServiceUtil.getProjectName(propertiesFile);
+        String projectDescription = ServiceUtil.getProjectDescription(propertiesFile);
+        String runtime = ServiceUtil.getProjectRuntime(propertiesFile);
+        return new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp());
+    }
+
+    public Project commitAndPushProject(String projectId, String message) throws Exception {
+        Project p = infinispanService.getProject(projectId);
+        List<ProjectFile> files = infinispanService.getProjectFiles(projectId);
+        RevCommit commit = gitService.commitAndPushProject(p, files, message);
+        String commitId = commit.getId().getName();
+        Long lastUpdate = commit.getCommitTime() * 1000L;
+        p.setLastCommit(commitId);
+        p.setLastCommitTimestamp(lastUpdate);
+        infinispanService.saveProject(p, false);
+        infinispanService.saveCommit(commitId, commit.getCommitTime());
+        return p;
+    }
+
     void addKameletsProject(String data) {
         LOGGER.info("Add custom kamelets project if not exists");
         try {
@@ -113,7 +169,7 @@ public class ImportService {
             if (kamelets == null) {
                 kamelets = new Project(Project.NAME_KAMELETS, "Custom Kamelets", "Custom Kamelets", "quarkus", "", Instant.now().toEpochMilli());
                 infinispanService.saveProject(kamelets, true);
-                gitService.commitAndPushProject("kamelets", "Add custom kamelets");
+                commitAndPushProject("kamelets", "Add custom kamelets");
             }
         } catch (Exception e) {
             LOGGER.error("Error during custom kamelets project creation", e);
@@ -133,51 +189,10 @@ public class ImportService {
                     ProjectFile file = new ProjectFile(name, value, Project.NAME_TEMPLATES, Instant.now().toEpochMilli());
                     infinispanService.saveProjectFile(file);
                 });
-                gitService.commitAndPushProject("templates", "Add default templates");
+                commitAndPushProject("templates", "Add default templates");
             }
         } catch (Exception e) {
             LOGGER.error("Error during templates project creation", e);
         }
     }
-
-    private String getPropertiesFile(GitRepo repo) {
-        try {
-            for (GitRepoFile e : repo.getFiles()){
-                if (e.getName().equalsIgnoreCase("application.properties")) {
-                    return e.getBody();
-                }
-            }
-        } catch (Exception e) {
-            LOGGER.error(e.getMessage());
-        }
-        return null;
-    }
-
-    private static String capitalize(String str) {
-        if(str == null || str.isEmpty()) {
-            return str;
-        }
-        return str.substring(0, 1).toUpperCase() + str.substring(1);
-    }
-
-    private static String getProperty(String file, String property) {
-        String prefix = property + "=";
-        return  Arrays.stream(file.split(System.lineSeparator())).filter(s -> s.startsWith(prefix))
-                .findFirst().orElseGet(() -> "")
-                .replace(prefix, "");
-    }
-
-    private static String getProjectDescription(String file) {
-        String description = getProperty(file, "camel.jbang.project-description");
-        return description != null && !description.isBlank() ? description : getProperty(file, "camel.karavan.project-description");
-    }
-
-    private static String getProjectName(String file) {
-        String name = getProperty(file, "camel.jbang.project-name");
-        return name != null && !name.isBlank() ? name : getProperty(file, "camel.karavan.project-name");
-    }
-
-    private static String getProjectRuntime(String file) {
-        return getProperty(file, "camel.jbang.runtime");
-    }
 }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java
new file mode 100644
index 00000000..d6d95c40
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ServiceUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.karavan.service;
+
+import org.apache.camel.karavan.model.GitRepo;
+import org.apache.camel.karavan.model.GitRepoFile;
+
+import java.util.Arrays;
+
+public class ServiceUtil {
+
+    public static String getPropertiesFile(GitRepo repo) {
+        try {
+            for (GitRepoFile e : repo.getFiles()){
+                if (e.getName().equalsIgnoreCase("application.properties")) {
+                    return e.getBody();
+                }
+            }
+        } catch (Exception e) {
+
+        }
+        return null;
+    }
+
+    public static String capitalize(String str) {
+        if(str == null || str.isEmpty()) {
+            return str;
+        }
+        return str.substring(0, 1).toUpperCase() + str.substring(1);
+    }
+
+    public static String getProperty(String file, String property) {
+        String prefix = property + "=";
+        return  Arrays.stream(file.split(System.lineSeparator())).filter(s -> s.startsWith(prefix))
+                .findFirst().orElseGet(() -> "")
+                .replace(prefix, "");
+    }
+
+    public static String getProjectDescription(String file) {
+        String description = getProperty(file, "camel.jbang.project-description");
+        return description != null && !description.isBlank() ? description : getProperty(file, "camel.karavan.project-description");
+    }
+
+    public static String getProjectName(String file) {
+        String name = getProperty(file, "camel.jbang.project-name");
+        return name != null && !name.isBlank() ? name : getProperty(file, "camel.karavan.project-name");
+    }
+
+    public static String getProjectRuntime(String file) {
+        return getProperty(file, "camel.jbang.runtime");
+    }
+}
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index 8a27af6a..3c5236aa 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -9,6 +9,8 @@ karavan.git-repository=${GIT_REPOSITORY}
 karavan.git-username=${GIT_USERNAME}
 karavan.git-password=${GIT_TOKEN}
 karavan.git-branch=main
+karavan.git-pull-interval=5s
+quarkus.scheduler.enabled=true
 
 # Infinispan Server address
 #quarkus.infinispan-client.server-list=localhost:12345
@@ -16,8 +18,8 @@ quarkus.infinispan-client.devservices.enabled=false
 quarkus.infinispan-client.devservices.port=12345
 quarkus.infinispan-client.devservices.service-name=karavan
 # Authentication
-quarkus.infinispan-client.auth-username=admin
-quarkus.infinispan-client.auth-password=password
+quarkus.infinispan-client.username=admin
+quarkus.infinispan-client.password=password
 
 # Infinispan client intelligence
 # Use BASIC as a Docker for Mac workaround
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 53af69f5..d16e1083 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -274,7 +274,6 @@ export class KaravanApi {
     }
 
     static async push(params: {}, after: (res: AxiosResponse<any>) => void) {
-        console.log(params)
         instance.post('/api/git', params)
             .then(res => {
                 after(res);
@@ -283,6 +282,17 @@ export class KaravanApi {
         });
     }
 
+    static async pull(projectId: string, after: (res: AxiosResponse<any>) => void) {
+        instance.get('/api/git/' + projectId)
+            .then(res => {
+                if (res.status === 200) {
+                    after(res.data);
+                }
+            }).catch(err => {
+            console.log(err);
+        });
+    }
+
     static async getTemplatesFiles( after: (files: []) => void) {
         instance.get('/api/file/templates')
             .then(res => {
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
index 35facb46..dc38966b 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
@@ -7,10 +7,10 @@ import {
     FlexItem,
     ToggleGroup,
     ToggleGroupItem,
-    Checkbox, Tooltip, ToolbarItem, Modal, ModalVariant, Form, FormGroup, TextInput, FormHelperText, HelperText, HelperTextItem
+    Checkbox, Tooltip, ToolbarItem, Modal, ModalVariant, Form, FormGroup, TextInput, FormHelperText
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
+import {Project, ProjectFile} from "./ProjectModels";
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
 import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
@@ -18,7 +18,6 @@ import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
 import {KaravanApi} from "../api/KaravanApi";
-import {CamelUi} from "../designer/utils/CamelUi";
 
 interface Props {
     project: Project,
@@ -164,7 +163,7 @@ export class ProjectPageToolbar extends React.Component<Props> {
                                         commitMessageIsOpen: true,
                                         commitMessage : commitMessage === '' ? new Date().toLocaleString() : commitMessage
                                     })}>
-                                {isPushing ? "..." : "Commit"}
+                                {isPushing ? "..." : "Push"}
                             </Button>
                         </Tooltip>
                     </FlexItem>}


[camel-karavan] 01/02: Git progect pull for #647

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit eaaff7921dcd6ecb552af4b428821ed02052c933
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue Feb 28 15:11:46 2023 -0500

    Git progect pull for #647
---
 .../org/apache/camel/karavan/api/GitResource.java  | 18 +++++++++++++-
 .../apache/camel/karavan/service/GitService.java   | 12 +++++++++
 .../camel/karavan/service/ImportService.java       | 29 +++++++++++++++++++---
 3 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
index 9bd7d21a..3e9de9c2 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
@@ -16,24 +16,33 @@
  */
 package org.apache.camel.karavan.api;
 
+import org.apache.camel.karavan.model.GitRepo;
 import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.model.ProjectFile;
 import org.apache.camel.karavan.service.GitService;
+import org.apache.camel.karavan.service.ImportService;
 import org.jboss.logging.Logger;
 
 import javax.inject.Inject;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import java.util.HashMap;
 
+
 @Path("/api/git")
 public class GitResource {
 
     @Inject
     GitService gitService;
 
+    @Inject
+    ImportService importService;
+
     private static final Logger LOGGER = Logger.getLogger(GitResource.class.getName());
 
 
@@ -41,7 +50,14 @@ public class GitResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public Project push(HashMap<String, String> params) throws Exception {
-        System.out.println(params);
         return gitService.commitAndPushProject(params.get("projectId"), params.get("message"));
     }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/{projectId}")
+    public Project pull(@PathParam("projectId") String projectId) throws Exception {
+        return importService.importProject(projectId);
+    }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
index 26f2c821..57765e4d 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
@@ -55,6 +55,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 @ApplicationScoped
 public class GitService {
@@ -130,6 +131,14 @@ public class GitService {
     }
 
     public List<GitRepo> readProjectsFromRepository() {
+        return readProjectsFromRepository(null);
+    }
+
+    public GitRepo readProjectFromRepository(String projectId) {
+        return readProjectsFromRepository(projectId).get(0);
+    }
+
+    private List<GitRepo> readProjectsFromRepository(String filter) {
         LOGGER.info("Read projects...");
         GitConfig gitConfig = getGitConfig();
         LOGGER.info("Read projects from repository " + gitConfig.getUri());
@@ -143,6 +152,9 @@ public class GitService {
             git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred);
             checkout(git, false, null, null, gitConfig.getBranch());
             List<String> projects = readProjectsFromFolder(folder);
+            if (filter != null) {
+                projects = projects.stream().filter(s -> s.equals(filter)).collect(Collectors.toList());
+            }
             for (String project : projects) {
                 Map<String, String> filesRead = readProjectFilesFromFolder(folder, project);
                 List<GitRepoFile> files = new ArrayList<>(filesRead.size());
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
index a56d40ae..3ebc09c4 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
@@ -17,7 +17,6 @@
 package org.apache.camel.karavan.service;
 
 import io.quarkus.vertx.ConsumeEvent;
-import io.smallrye.mutiny.tuples.Tuple2;
 import org.apache.camel.karavan.model.GitRepo;
 import org.apache.camel.karavan.model.GitRepoFile;
 import org.apache.camel.karavan.model.Project;
@@ -30,7 +29,6 @@ import javax.inject.Inject;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 
 @ApplicationScoped
 public class ImportService {
@@ -85,6 +83,29 @@ public class ImportService {
         addTemplatesProject("");
     }
 
+    public Project importProject(String projectId) {
+        LOGGER.info("Import project from Git " + projectId);
+        try {
+            GitRepo repo = gitService.readProjectFromRepository(projectId);
+            Project project;
+            String folderName = repo.getName();
+            String propertiesFile = getPropertiesFile(repo);
+            String projectName = getProjectName(propertiesFile);
+            String projectDescription = getProjectDescription(propertiesFile);
+            String runtime = getProjectRuntime(propertiesFile);
+            project = new Project(folderName, projectName, projectDescription, runtime, repo.getCommitId(), repo.getLastCommitTimestamp());
+            infinispanService.saveProject(project, true);
+            repo.getFiles().forEach(repoFile -> {
+                ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), folderName, repoFile.getLastCommitTimestamp());
+                infinispanService.saveProjectFile(file);
+            });
+            return project;
+        } catch (Exception e) {
+            LOGGER.error("Error during project import", e);
+            return null;
+        }
+    }
+
     void addKameletsProject(String data) {
         LOGGER.info("Add custom kamelets project if not exists");
         try {
@@ -92,7 +113,7 @@ public class ImportService {
             if (kamelets == null) {
                 kamelets = new Project(Project.NAME_KAMELETS, "Custom Kamelets", "Custom Kamelets", "quarkus", "", Instant.now().toEpochMilli());
                 infinispanService.saveProject(kamelets, true);
-                gitService.commitAndPushProject(kamelets);
+                gitService.commitAndPushProject("kamelets", "Add custom kamelets");
             }
         } catch (Exception e) {
             LOGGER.error("Error during custom kamelets project creation", e);
@@ -112,7 +133,7 @@ public class ImportService {
                     ProjectFile file = new ProjectFile(name, value, Project.NAME_TEMPLATES, Instant.now().toEpochMilli());
                     infinispanService.saveProjectFile(file);
                 });
-                gitService.commitAndPushProject(templates);
+                gitService.commitAndPushProject("templates", "Add default templates");
             }
         } catch (Exception e) {
             LOGGER.error("Error during templates project creation", e);