Entities VC with Git - initial implementation
This commit is contained in:
		
							parent
							
								
									335b4ef465
								
							
						
					
					
						commit
						f82be0153b
					
				@ -337,6 +337,10 @@
 | 
			
		||||
            <artifactId>Java-WebSocket</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.eclipse.jgit</groupId>
 | 
			
		||||
            <artifactId>org.eclipse.jgit</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,118 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.controller;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.ExportableEntity;
 | 
			
		||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
 | 
			
		||||
import org.thingsboard.server.service.sync.exporting.data.EntityExportData;
 | 
			
		||||
import org.thingsboard.server.service.sync.importing.EntityImportResult;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.DefaultEntitiesVersionControlService;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntitiesVersionControlSettings;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntityVersion;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/api/entities/vc")
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
public class EntitiesVersionControlController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    private final DefaultEntitiesVersionControlService versionControlService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/version/{entityType}/{entityId}")
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    public EntityVersion saveEntityVersion(@PathVariable EntityType entityType,
 | 
			
		||||
                                           @PathVariable("entityId") UUID entityUuid,
 | 
			
		||||
                                           @RequestParam String branch,
 | 
			
		||||
                                           @RequestBody String versionName) throws Exception {
 | 
			
		||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
 | 
			
		||||
        return versionControlService.saveEntityVersion(getTenantId(), entityId, branch, versionName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/version/{entityType}/{entityId}")
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    public List<EntityVersion> listEntityVersions(@PathVariable EntityType entityType,
 | 
			
		||||
                                                  @PathVariable("entityId") UUID entityUuid,
 | 
			
		||||
                                                  @RequestParam String branch) throws Exception {
 | 
			
		||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
 | 
			
		||||
        return versionControlService.listEntityVersions(getTenantId(), entityId, branch, Integer.MAX_VALUE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/entity/{entityType}/{entityId}/{versionId}")
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    public EntityExportData<ExportableEntity<EntityId>> getEntityAtVersion(@PathVariable EntityType entityType,
 | 
			
		||||
                                                                           @PathVariable("entityId") UUID entityUuid,
 | 
			
		||||
                                                                           @PathVariable String versionId) throws Exception {
 | 
			
		||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
 | 
			
		||||
        return versionControlService.getEntityAtVersion(getTenantId(), entityId, versionId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/entity/{entityType}/{entityId}/{versionId}")
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    public EntityImportResult<ExportableEntity<EntityId>> loadEntityVersion(@PathVariable EntityType entityType,
 | 
			
		||||
                                                                            @PathVariable("entityId") UUID entityUuid,
 | 
			
		||||
                                                                            @PathVariable String versionId) throws Exception {
 | 
			
		||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
 | 
			
		||||
        return versionControlService.loadEntityVersion(getTenantId(), entityId, versionId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/branches")
 | 
			
		||||
    public Set<String> getAllowedBranches() throws ThingsboardException {
 | 
			
		||||
        return versionControlService.getAllowedBranches(getTenantId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/settings")
 | 
			
		||||
    @PreAuthorize("hasAuthority('SYS_ADMIN')")
 | 
			
		||||
    public void saveSettings(@RequestBody EntitiesVersionControlSettings settings) throws Exception {
 | 
			
		||||
        versionControlService.saveSettings(settings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/settings")
 | 
			
		||||
    @PreAuthorize("hasAuthority('SYS_ADMIN')")
 | 
			
		||||
    public EntitiesVersionControlSettings getSettings() {
 | 
			
		||||
        return versionControlService.getSettings();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/repository/reset")
 | 
			
		||||
    @PreAuthorize("hasAuthority('SYS_ADMIN')")
 | 
			
		||||
    public void resetLocalRepository() throws Exception {
 | 
			
		||||
        versionControlService.resetRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,274 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.sync.vcs;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectWriter;
 | 
			
		||||
import com.fasterxml.jackson.databind.SerializationFeature;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.io.FileUtils;
 | 
			
		||||
import org.springframework.scheduling.annotation.Scheduled;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.server.common.data.AdminSettings;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.ExportableEntity;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
 | 
			
		||||
import org.thingsboard.server.dao.tenant.TenantService;
 | 
			
		||||
import org.thingsboard.server.queue.util.AfterStartUp;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
import org.thingsboard.server.service.sync.EntitiesExportImportService;
 | 
			
		||||
import org.thingsboard.server.service.sync.exporting.EntityExportSettings;
 | 
			
		||||
import org.thingsboard.server.service.sync.exporting.data.EntityExportData;
 | 
			
		||||
import org.thingsboard.server.service.sync.importing.EntityImportResult;
 | 
			
		||||
import org.thingsboard.server.service.sync.importing.EntityImportSettings;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntitiesVersionControlSettings;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntityVersion;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.GitSettings;
 | 
			
		||||
import org.thingsboard.server.utils.git.Repository;
 | 
			
		||||
import org.thingsboard.server.utils.git.data.Commit;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.locks.Lock;
 | 
			
		||||
import java.util.concurrent.locks.ReentrantLock;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@TbCoreComponent
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class DefaultEntitiesVersionControlService implements EntitiesVersionControlService {
 | 
			
		||||
    // TODO [viacheslav]: start up only on one of the cores
 | 
			
		||||
 | 
			
		||||
    private final TenantService tenantService;
 | 
			
		||||
    private final EntitiesExportImportService exportImportService;
 | 
			
		||||
    private final AdminSettingsService adminSettingsService;
 | 
			
		||||
 | 
			
		||||
    private final ObjectWriter jsonWriter = new ObjectMapper().writer(SerializationFeature.INDENT_OUTPUT);
 | 
			
		||||
    private static final String SETTINGS_KEY = "vc";
 | 
			
		||||
 | 
			
		||||
    private Repository repository;
 | 
			
		||||
    private final ReentrantLock fetchLock = new ReentrantLock();
 | 
			
		||||
    private final Lock writeLock = new ReentrantLock();
 | 
			
		||||
 | 
			
		||||
    @AfterStartUp
 | 
			
		||||
    public void init() throws Exception {
 | 
			
		||||
        try {
 | 
			
		||||
            EntitiesVersionControlSettings settings = getSettings();
 | 
			
		||||
            if (settings != null && settings.getGitSettings() != null) {
 | 
			
		||||
                this.repository = initRepository(settings.getGitSettings());
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("Failed to initialize entities version control service", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Scheduled(initialDelay = 10 * 1000, fixedDelay = 10 * 1000)
 | 
			
		||||
    public void fetch() throws Exception {
 | 
			
		||||
        if (repository == null) return;
 | 
			
		||||
 | 
			
		||||
        if (fetchLock.tryLock()) {
 | 
			
		||||
            try {
 | 
			
		||||
                log.info("Fetching remote repository");
 | 
			
		||||
                repository.fetch();
 | 
			
		||||
            } finally {
 | 
			
		||||
                fetchLock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public EntityVersion saveEntityVersion(TenantId tenantId, EntityId entityId, String branch, String versionName) throws Exception {
 | 
			
		||||
        return saveEntitiesVersion(tenantId, List.of(entityId), branch, versionName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public EntityVersion saveEntitiesVersion(TenantId tenantId, List<EntityId> entitiesIds, String branch, String versionName) throws Exception {
 | 
			
		||||
        checkRepository();
 | 
			
		||||
        checkBranch(tenantId, branch);
 | 
			
		||||
 | 
			
		||||
        EntityExportSettings exportSettings = EntityExportSettings.builder()
 | 
			
		||||
                .exportInboundRelations(false)
 | 
			
		||||
                .exportOutboundRelations(false)
 | 
			
		||||
                .build();
 | 
			
		||||
        List<EntityExportData<ExportableEntity<EntityId>>> entityDataList = entitiesIds.stream()
 | 
			
		||||
                .map(entityId -> {
 | 
			
		||||
                    return exportImportService.exportEntity(tenantId, entityId, exportSettings);
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        if (fetchLock.tryLock()) {
 | 
			
		||||
            try {
 | 
			
		||||
                repository.fetch();
 | 
			
		||||
            } finally {
 | 
			
		||||
                fetchLock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        writeLock.lock();
 | 
			
		||||
        try {
 | 
			
		||||
            if (repository.listBranches().contains(branch)) {
 | 
			
		||||
                repository.checkout(branch);
 | 
			
		||||
                repository.merge(branch);
 | 
			
		||||
            } else {
 | 
			
		||||
                repository.createAndCheckoutOrphanBranch(branch);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (EntityExportData<ExportableEntity<EntityId>> entityData : entityDataList) {
 | 
			
		||||
                String entityDataJson = jsonWriter.writeValueAsString(entityData);
 | 
			
		||||
                FileUtils.write(new File(repository.getDirectory() + "/" + getRelativePathForEntity(entityData.getEntity().getId())),
 | 
			
		||||
                        entityDataJson, StandardCharsets.UTF_8);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Commit commit = repository.commit(versionName, ".", "Tenant " + tenantId);
 | 
			
		||||
            repository.push();
 | 
			
		||||
            return new EntityVersion(commit.getId(), commit.getMessage(), commit.getAuthorName());
 | 
			
		||||
        } finally {
 | 
			
		||||
            writeLock.unlock();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<EntityVersion> listEntityVersions(TenantId tenantId, EntityId entityId, String branch, int limit) throws Exception {
 | 
			
		||||
        checkRepository();
 | 
			
		||||
        checkBranch(tenantId, branch);
 | 
			
		||||
 | 
			
		||||
        return repository.listCommits(branch, getRelativePathForEntity(entityId), limit).stream()
 | 
			
		||||
                .map(commit -> new EntityVersion(commit.getId(), commit.getMessage(), commit.getAuthorName()))
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<EntityVersion> listEntityTypeVersions(TenantId tenantId, EntityType entityType, String branch, int limit) throws Exception {
 | 
			
		||||
        checkRepository();
 | 
			
		||||
        checkBranch(tenantId, branch);
 | 
			
		||||
 | 
			
		||||
        return repository.listCommits(branch, getRelativePathForEntityType(entityType), limit).stream()
 | 
			
		||||
                .map(commit -> new EntityVersion(commit.getId(), commit.getMessage(), commit.getAuthorName()))
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> getEntityAtVersion(TenantId tenantId, I entityId, String versionId) throws Exception {
 | 
			
		||||
        checkRepository();
 | 
			
		||||
        // FIXME [viacheslav]: validate access
 | 
			
		||||
 | 
			
		||||
        String entityDataJson = repository.getFileContentAtCommit(getRelativePathForEntity(entityId), versionId);
 | 
			
		||||
        return JacksonUtil.fromString(entityDataJson, new TypeReference<EntityExportData<E>>() {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> loadEntityVersion(TenantId tenantId, I entityId, String versionId) throws Exception {
 | 
			
		||||
        EntityExportData<E> entityData = getEntityAtVersion(tenantId, entityId, versionId);
 | 
			
		||||
        return exportImportService.importEntity(tenantId, entityData, EntityImportSettings.builder()
 | 
			
		||||
                .importInboundRelations(false)
 | 
			
		||||
                .importOutboundRelations(false)
 | 
			
		||||
                .updateReferencesToOtherEntities(true)
 | 
			
		||||
                .build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private String getRelativePathForEntity(EntityId entityId) {
 | 
			
		||||
        return getRelativePathForEntityType(entityId.getEntityType())
 | 
			
		||||
                + "/" + entityId.getId() + ".json";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getRelativePathForEntityType(EntityType entityType) {
 | 
			
		||||
        return entityType.name().toLowerCase();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void checkBranch(TenantId tenantId, String branch) {
 | 
			
		||||
        if (!getAllowedBranches(tenantId).contains(branch)) {
 | 
			
		||||
            throw new IllegalArgumentException("Tenant does not have access to this branch");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Set<String> getAllowedBranches(TenantId tenantId) {
 | 
			
		||||
        return Optional.ofNullable(getSettings())
 | 
			
		||||
                .flatMap(settings -> Optional.ofNullable(settings.getAllowedBranches()))
 | 
			
		||||
                .flatMap(tenantsAllowedBranches -> Optional.ofNullable(tenantsAllowedBranches.get(tenantId.getId())))
 | 
			
		||||
                .orElse(Collections.emptySet());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveSettings(EntitiesVersionControlSettings settings) throws Exception {
 | 
			
		||||
        this.repository = initRepository(settings.getGitSettings());
 | 
			
		||||
 | 
			
		||||
        AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, SETTINGS_KEY))
 | 
			
		||||
                .orElseGet(() -> {
 | 
			
		||||
                    AdminSettings newSettings = new AdminSettings();
 | 
			
		||||
                    newSettings.setKey(SETTINGS_KEY);
 | 
			
		||||
                    return newSettings;
 | 
			
		||||
                });
 | 
			
		||||
        adminSettings.setJsonValue(JacksonUtil.valueToTree(settings));
 | 
			
		||||
        adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public EntitiesVersionControlSettings getSettings() {
 | 
			
		||||
        return Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, SETTINGS_KEY))
 | 
			
		||||
                .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), EntitiesVersionControlSettings.class))
 | 
			
		||||
                .orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void checkRepository() {
 | 
			
		||||
        if (repository == null) {
 | 
			
		||||
            throw new IllegalStateException("Repository is not initialized");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Repository initRepository(GitSettings gitSettings) throws Exception {
 | 
			
		||||
        if (Files.exists(Path.of(gitSettings.getRepositoryDirectory()))) {
 | 
			
		||||
            return Repository.open(gitSettings.getRepositoryDirectory(),
 | 
			
		||||
                    gitSettings.getUsername(), gitSettings.getPassword());
 | 
			
		||||
        } else {
 | 
			
		||||
            Files.createDirectories(Path.of(gitSettings.getRepositoryDirectory()));
 | 
			
		||||
            return Repository.clone(gitSettings.getRepositoryUri(), gitSettings.getRepositoryDirectory(),
 | 
			
		||||
                    gitSettings.getUsername(), gitSettings.getPassword());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void resetRepository() throws Exception {
 | 
			
		||||
        if (this.repository != null) {
 | 
			
		||||
            FileUtils.deleteDirectory(new File(repository.getDirectory()));
 | 
			
		||||
            this.repository = null;
 | 
			
		||||
        }
 | 
			
		||||
        EntitiesVersionControlSettings settings = getSettings();
 | 
			
		||||
        this.repository = initRepository(settings.getGitSettings());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.sync.vcs;
 | 
			
		||||
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.ExportableEntity;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.service.sync.exporting.data.EntityExportData;
 | 
			
		||||
import org.thingsboard.server.service.sync.importing.EntityImportResult;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntitiesVersionControlSettings;
 | 
			
		||||
import org.thingsboard.server.service.sync.vcs.data.EntityVersion;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public interface EntitiesVersionControlService {
 | 
			
		||||
 | 
			
		||||
    EntityVersion saveEntityVersion(TenantId tenantId, EntityId entityId, String branch, String versionName) throws Exception;
 | 
			
		||||
 | 
			
		||||
    EntityVersion saveEntitiesVersion(TenantId tenantId, List<EntityId> entitiesIds, String branch, String versionName) throws Exception;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    List<EntityVersion> listEntityVersions(TenantId tenantId, EntityId entityId, String branch, int limit) throws Exception;
 | 
			
		||||
 | 
			
		||||
    List<EntityVersion> listEntityTypeVersions(TenantId tenantId, EntityType entityType, String branch, int limit) throws Exception;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> getEntityAtVersion(TenantId tenantId, I entityId, String versionId) throws Exception;
 | 
			
		||||
 | 
			
		||||
    <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> loadEntityVersion(TenantId tenantId, I entityId, String versionId) throws Exception;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    void saveSettings(EntitiesVersionControlSettings settings) throws Exception;
 | 
			
		||||
 | 
			
		||||
    EntitiesVersionControlSettings getSettings();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.sync.vcs.data;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class EntitiesVersionControlSettings {
 | 
			
		||||
    private Map<UUID, Set<String>> allowedBranches;
 | 
			
		||||
    private GitSettings gitSettings;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.sync.vcs.data;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class EntityVersion {
 | 
			
		||||
    private String id;
 | 
			
		||||
    private String name;
 | 
			
		||||
    private String authorName;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.sync.vcs.data;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@Builder
 | 
			
		||||
public class GitSettings {
 | 
			
		||||
    private String repositoryUri;
 | 
			
		||||
    private String repositoryDirectory;
 | 
			
		||||
    private String username;
 | 
			
		||||
    private String password;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,256 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.utils.git;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Streams;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.eclipse.jgit.api.Git;
 | 
			
		||||
import org.eclipse.jgit.api.GitCommand;
 | 
			
		||||
import org.eclipse.jgit.api.ListBranchCommand;
 | 
			
		||||
import org.eclipse.jgit.api.LogCommand;
 | 
			
		||||
import org.eclipse.jgit.api.RmCommand;
 | 
			
		||||
import org.eclipse.jgit.api.TransportCommand;
 | 
			
		||||
import org.eclipse.jgit.api.errors.GitAPIException;
 | 
			
		||||
import org.eclipse.jgit.lib.Constants;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectLoader;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectReader;
 | 
			
		||||
import org.eclipse.jgit.revwalk.RevCommit;
 | 
			
		||||
import org.eclipse.jgit.revwalk.filter.RevFilter;
 | 
			
		||||
import org.eclipse.jgit.transport.CredentialsProvider;
 | 
			
		||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
 | 
			
		||||
import org.eclipse.jgit.treewalk.TreeWalk;
 | 
			
		||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
 | 
			
		||||
import org.thingsboard.server.utils.git.data.Commit;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class Repository {
 | 
			
		||||
 | 
			
		||||
    private final Git git;
 | 
			
		||||
    private final CredentialsProvider credentialsProvider;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final String directory;
 | 
			
		||||
 | 
			
		||||
    private Repository(Git git, CredentialsProvider credentialsProvider, String directory) {
 | 
			
		||||
        this.git = git;
 | 
			
		||||
        this.credentialsProvider = credentialsProvider;
 | 
			
		||||
        this.directory = directory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Repository clone(String uri, String directory,
 | 
			
		||||
                                   String username, String password) throws GitAPIException {
 | 
			
		||||
        CredentialsProvider credentialsProvider = newCredentialsProvider(username, password);
 | 
			
		||||
        Git git = Git.cloneRepository()
 | 
			
		||||
                .setURI(uri)
 | 
			
		||||
                .setDirectory(new java.io.File(directory))
 | 
			
		||||
                .setNoCheckout(true)
 | 
			
		||||
                .setCredentialsProvider(credentialsProvider)
 | 
			
		||||
                .call();
 | 
			
		||||
        return new Repository(git, credentialsProvider, directory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Repository open(String directory, String username, String password) throws IOException {
 | 
			
		||||
        Git git = Git.open(new java.io.File(directory));
 | 
			
		||||
        return new Repository(git, newCredentialsProvider(username, password), directory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public void fetch() throws GitAPIException {
 | 
			
		||||
        execute(git.fetch()
 | 
			
		||||
                .setRemoveDeletedRefs(true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<String> listBranches() throws GitAPIException {
 | 
			
		||||
        return execute(git.branchList()
 | 
			
		||||
                .setListMode(ListBranchCommand.ListMode.ALL)).stream()
 | 
			
		||||
                .filter(ref -> !ref.getName().equals(Constants.HEAD))
 | 
			
		||||
                .map(ref -> org.eclipse.jgit.lib.Repository.shortenRefName(ref.getName()))
 | 
			
		||||
                .map(name -> StringUtils.removeStart(name, "origin/"))
 | 
			
		||||
                .distinct().collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Commit> listCommits(String branchName, int limit) throws IOException, GitAPIException {
 | 
			
		||||
        return listCommits(branchName, null, limit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<Commit> listCommits(String branchName, String path, int limit) throws IOException, GitAPIException {
 | 
			
		||||
        ObjectId branchId = resolve("origin/" + branchName);
 | 
			
		||||
        if (branchId == null) {
 | 
			
		||||
            throw new IllegalArgumentException("Branch not found");
 | 
			
		||||
        }
 | 
			
		||||
        LogCommand command = git.log()
 | 
			
		||||
                .add(branchId).setMaxCount(limit)
 | 
			
		||||
                .setRevFilter(RevFilter.NO_MERGES);
 | 
			
		||||
        if (StringUtils.isNotEmpty(path)) {
 | 
			
		||||
            command.addPath(path);
 | 
			
		||||
        }
 | 
			
		||||
        return Streams.stream(execute(command))
 | 
			
		||||
                .map(this::toCommit)
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<String> listFilesAtCommit(Commit commit) throws IOException {
 | 
			
		||||
        return listFilesAtCommit(commit, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> listFilesAtCommit(Commit commit, String path) throws IOException {
 | 
			
		||||
        List<String> files = new ArrayList<>();
 | 
			
		||||
        RevCommit revCommit = resolveCommit(commit.getId());
 | 
			
		||||
        try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) {
 | 
			
		||||
            treeWalk.reset(revCommit.getTree().getId());
 | 
			
		||||
            if (StringUtils.isNotEmpty(path)) {
 | 
			
		||||
                treeWalk.setFilter(PathFilter.create(path));
 | 
			
		||||
            }
 | 
			
		||||
            treeWalk.setRecursive(true);
 | 
			
		||||
            while (treeWalk.next()) {
 | 
			
		||||
                files.add(treeWalk.getPathString());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return files;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public String getFileContentAtCommit(String file, String commitId) throws IOException {
 | 
			
		||||
        RevCommit revCommit = resolveCommit(commitId);
 | 
			
		||||
        try (TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), file, revCommit.getTree())) {
 | 
			
		||||
            if (treeWalk == null) {
 | 
			
		||||
                throw new IllegalArgumentException("Not found");
 | 
			
		||||
            }
 | 
			
		||||
            ObjectId blobId = treeWalk.getObjectId(0);
 | 
			
		||||
            try (ObjectReader objectReader = git.getRepository().newObjectReader()) {
 | 
			
		||||
                ObjectLoader objectLoader = objectReader.open(blobId);
 | 
			
		||||
                byte[] bytes = objectLoader.getBytes();
 | 
			
		||||
                return new String(bytes, StandardCharsets.UTF_8);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public void checkout(String branchName) throws GitAPIException {
 | 
			
		||||
        execute(git.checkout()
 | 
			
		||||
                .setName(branchName));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void merge(String branchName) throws IOException, GitAPIException {
 | 
			
		||||
        ObjectId branchId = resolve("origin/" + branchName);
 | 
			
		||||
        if (branchId == null) {
 | 
			
		||||
            throw new IllegalArgumentException("Branch not found");
 | 
			
		||||
        }
 | 
			
		||||
        execute(git.merge()
 | 
			
		||||
                .include(branchId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void createAndCheckoutOrphanBranch(String name) throws GitAPIException {
 | 
			
		||||
        execute(git.checkout()
 | 
			
		||||
                .setOrphan(true)
 | 
			
		||||
                .setName(name));
 | 
			
		||||
        Set<String> uncommittedChanges = git.status().call().getUncommittedChanges();
 | 
			
		||||
        if (!uncommittedChanges.isEmpty()) {
 | 
			
		||||
            RmCommand rm = git.rm();
 | 
			
		||||
            uncommittedChanges.forEach(rm::addFilepattern);
 | 
			
		||||
            execute(rm);
 | 
			
		||||
        }
 | 
			
		||||
        execute(git.clean());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void clean() throws GitAPIException {
 | 
			
		||||
        execute(git.clean().setCleanDirectories(true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Commit commit(String message, String filePattern, String author) throws GitAPIException {
 | 
			
		||||
        execute(git.add().addFilepattern(filePattern));
 | 
			
		||||
        RevCommit revCommit = execute(git.commit()
 | 
			
		||||
                .setMessage(message)
 | 
			
		||||
                .setAuthor(author, author));
 | 
			
		||||
        return toCommit(revCommit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public void push() throws GitAPIException {
 | 
			
		||||
        execute(git.push());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//    public List<Diff> getCommitChanges(Commit commit) throws IOException, GitAPIException {
 | 
			
		||||
//        RevCommit revCommit = resolveCommit(commit.getId());
 | 
			
		||||
//        if (revCommit.getParentCount() == 0) {
 | 
			
		||||
//            return null; // just takes the first parent of a commit, but should find a parent in branch provided
 | 
			
		||||
//        }
 | 
			
		||||
//        return execute(git.diff()
 | 
			
		||||
//                .setOldTree(prepareTreeParser(git.getRepository().parseCommit(revCommit.getParent(0))))
 | 
			
		||||
//                .setNewTree(prepareTreeParser(revCommit))).stream()
 | 
			
		||||
//                .map(diffEntry -> new Diff(diffEntry.getChangeType().name(), diffEntry.getOldPath(), diffEntry.getNewPath()))
 | 
			
		||||
//                .collect(Collectors.toList());
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
//    private AbstractTreeIterator prepareTreeParser(RevCommit revCommit) throws IOException {
 | 
			
		||||
//        // from the commit we can build the tree which allows us to construct the TreeParser
 | 
			
		||||
//        //noinspection Duplicates
 | 
			
		||||
//        org.eclipse.jgit.lib.Repository repository = git.getRepository();
 | 
			
		||||
//        try (RevWalk walk = new RevWalk(repository)) {
 | 
			
		||||
//            RevTree tree = walk.parseTree(revCommit.getTree().getId());
 | 
			
		||||
//
 | 
			
		||||
//            CanonicalTreeParser treeParser = new CanonicalTreeParser();
 | 
			
		||||
//            try (ObjectReader reader = repository.newObjectReader()) {
 | 
			
		||||
//                treeParser.reset(reader, tree.getId());
 | 
			
		||||
//            }
 | 
			
		||||
//
 | 
			
		||||
//            walk.dispose();
 | 
			
		||||
//
 | 
			
		||||
//            return treeParser;
 | 
			
		||||
//        }
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    private Commit toCommit(RevCommit revCommit) {
 | 
			
		||||
        return new Commit(revCommit.getName(), revCommit.getFullMessage(), revCommit.getAuthorIdent().getName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private RevCommit resolveCommit(String id) throws IOException {
 | 
			
		||||
        return git.getRepository().parseCommit(resolve(id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ObjectId resolve(String rev) throws IOException {
 | 
			
		||||
        return git.getRepository().resolve(rev);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <C extends GitCommand<T>, T> T execute(C command) throws GitAPIException {
 | 
			
		||||
        if (command instanceof TransportCommand) {
 | 
			
		||||
            ((TransportCommand<?, ?>) command).setCredentialsProvider(credentialsProvider);
 | 
			
		||||
//        SshSessionFactory sshSessionFactory = SshSessionFactory.getInstance();
 | 
			
		||||
//        transportCommand.setTransportConfigCallback(transport -> {
 | 
			
		||||
//            ((SshTransport) transport).setSshSessionFactory(sshSessionFactory);
 | 
			
		||||
//        });
 | 
			
		||||
        }
 | 
			
		||||
        return command.call();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static CredentialsProvider newCredentialsProvider(String username, String password) {
 | 
			
		||||
        return new UsernamePasswordCredentialsProvider(username, password);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.utils.git.data;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class Branch {
 | 
			
		||||
    private final String shortName;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.utils.git.data;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class Commit {
 | 
			
		||||
    private final String id;
 | 
			
		||||
    private final String message;
 | 
			
		||||
    private final String authorName;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2022 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.utils.git.data;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class Diff {
 | 
			
		||||
    private final String type;
 | 
			
		||||
    private final String oldPath;
 | 
			
		||||
    private final String newPath;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							@ -134,6 +134,7 @@
 | 
			
		||||
        <!--      BLACKBOX TEST SCOPE     -->
 | 
			
		||||
        <testcontainers.version>1.16.0</testcontainers.version>
 | 
			
		||||
        <zeroturnaround.version>1.12</zeroturnaround.version>
 | 
			
		||||
        <jgit.version>6.1.0.202203080745-r</jgit.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
@ -1875,6 +1876,11 @@
 | 
			
		||||
                <version>${zeroturnaround.version}</version>
 | 
			
		||||
                <scope>test</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.eclipse.jgit</groupId>
 | 
			
		||||
                <artifactId>org.eclipse.jgit</artifactId>
 | 
			
		||||
                <version>${jgit.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
        </dependencies>
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user