package milkman.plugin.sync.git; import static milkman.plugin.sync.git.JGitUtil.initWith; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefNotAdvertisedException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import de.danielbechler.diff.node.DiffNode; import lombok.SneakyThrows; import milkman.PlatformUtil; import milkman.domain.Collection; import milkman.domain.Environment; import milkman.domain.Workspace; import milkman.persistence.UnknownPluginHandler; import milkman.ui.plugin.WorkspaceSynchronizer; /** * some simplistic diff-sync way of synchronizing workspace with remote * * @author peter * */ public class GitWorkspaceSync implements WorkspaceSynchronizer { @Override public boolean supportSyncOf(Workspace workspace) { return workspace.getSyncDetails() instanceof GitSyncDetails; } @Override @SneakyThrows public void synchronize(Workspace workspace) { GitSyncDetails syncDetails = (GitSyncDetails) workspace.getSyncDetails(); ObjectMapper mapper = createMapper(); CollectionDiffer diffMerger = new CollectionDiffer(); File syncDir = new File(PlatformUtil.getWritableLocationForFile("sync/"+workspace.getWorkspaceId()+"/")); File collectionFile = new File(syncDir, "collections.json"); List<Collection> collectionCommonCopy = new LinkedList<Collection>(); if (collectionFile.exists()) collectionCommonCopy = mapper.readValue(collectionFile, new TypeReference<List<Collection>>() {}); File environmentFile = new File(syncDir, "environments.json"); List<Environment> environmentCommonCopy = new LinkedList<Environment>(); if (environmentFile.exists()) environmentCommonCopy = mapper.readValue(environmentFile, new TypeReference<List<Environment>>() {}); Git repo = refreshRepository(syncDetails, syncDir); List<Collection> collectionServerCopy = new LinkedList<Collection>(); if (collectionFile.exists()) collectionServerCopy = mapper.readValue(collectionFile, new TypeReference<List<Collection>>() {}); List<Environment> environmentServerCopy = new LinkedList<Environment>(); if (environmentFile.exists()) environmentServerCopy = mapper.readValue(environmentFile, new TypeReference<List<Environment>>() {}); List<Collection> collectionWorkingCopy = workspace.getCollections(); List<Environment> environmentWorkingCopy = workspace.getEnvironments(); //step1: compute diff against common copy DiffNode collectionWorkingCopyChanges = diffMerger.compare(collectionWorkingCopy, collectionCommonCopy); DiffNode environmentWorkingCopyChanges = diffMerger.compareEnvs(environmentWorkingCopy, environmentCommonCopy); //step2: merge diffs to server copy if (collectionWorkingCopyChanges.isChanged() || environmentWorkingCopyChanges.isChanged()) { diffMerger.mergeDiffs(collectionWorkingCopy, collectionServerCopy, collectionWorkingCopyChanges); diffMerger.mergeDiffsEnvs(environmentWorkingCopy, environmentServerCopy, environmentWorkingCopyChanges); mapper.writeValue(collectionFile, collectionServerCopy); mapper.writeValue(environmentFile, environmentServerCopy); repo.add() .addFilepattern(".") .call(); repo.commit() .setMessage("milkman sync") .call(); initWith(repo.push(), syncDetails) .call(); } //step3: merge server-diffs to working copy workspace.setCollections(collectionServerCopy); workspace.setEnvironments(environmentServerCopy); } private ObjectMapper createMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.addHandler(new UnknownPluginHandler()); mapper.enable(SerializationFeature.INDENT_OUTPUT); //allows git line-by-line diffs return mapper; } private Git refreshRepository(GitSyncDetails syncDetails, File syncDir) throws GitAPIException, InvalidRemoteException, TransportException, IOException, WrongRepositoryStateException, InvalidConfigurationException, CanceledException, RefNotFoundException, RefNotAdvertisedException, NoHeadException { Git repo; if (!syncDir.exists()) { syncDir.mkdirs(); repo = initWith(Git.cloneRepository(), syncDetails) .setURI(syncDetails.getGitUrl()) .setDirectory(syncDir) .setCloneAllBranches(true) .setBranch("master") .call(); } else { repo = Git.open(syncDir); initWith(repo.pull(), syncDetails) .call(); } return repo; } @Override public SynchronizationDetailFactory getDetailFactory() { return new GitSyncDetailFactory(); } }