Java tutorial
/** * Copyright 2012 meltmedia * * 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 com.meltmedia.cadmium.core.git; import com.meltmedia.cadmium.core.FileSystemManager; import com.meltmedia.cadmium.core.commands.GitLocation; import com.meltmedia.cadmium.core.config.ConfigManager; import com.meltmedia.cadmium.core.history.HistoryEntry.EntryType; import com.meltmedia.cadmium.core.history.HistoryManager; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.*; import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.ListBranchCommand.ListMode; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.TagOpt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class GitService implements Closeable { private static final Logger log = LoggerFactory.getLogger(GitService.class); protected Git git; public GitService(Repository repository) { git = new Git(repository); } protected GitService(Git gitRepo) { this.git = gitRepo; } public static void setupSsh(String sshDir) { if (SshSessionFactory.getInstance() == null || !SshSessionFactory.getInstance().getClass().getName() .equals(GithubConfigSessionFactory.class.getName())) { log.debug("Setting SSH session factory for ssh dir path {}", sshDir); SshSessionFactory.setInstance(new GithubConfigSessionFactory(sshDir)); } } public Git getGit() { return git; } public static void setupLocalSsh(String sshDir) { setupLocalSsh(sshDir, false); } public static void setupLocalSsh(String sshDir, boolean noPrompt) { SshSessionFactory.setInstance(new LocalConfigSessionFactory(sshDir, noPrompt)); } public static GitService createGitService(String repositoryDirectory) throws Exception { if (repositoryDirectory != null) { if (!repositoryDirectory.endsWith(".git")) { String gitDir = FileSystemManager.getChildDirectoryIfExists(repositoryDirectory, ".git"); if (gitDir != null) { repositoryDirectory = gitDir; } else { repositoryDirectory = null; } } if (repositoryDirectory != null && FileSystemManager.isDirector(repositoryDirectory) && FileSystemManager.canRead(repositoryDirectory) && FileSystemManager.canWrite(repositoryDirectory)) { return new GitService(new FileRepository(repositoryDirectory)); } } throw new Exception("Invalid git repo"); } public static GitService cloneRepo(String uri, String dir) throws Exception { if (dir == null || !FileSystemManager.exists(dir)) { log.debug("Cloning \"" + uri + "\" to \"" + dir + "\""); CloneCommand clone = Git.cloneRepository(); clone.setCloneAllBranches(false); clone.setCloneSubmodules(false); if (dir != null) { clone.setDirectory(new File(dir)); } clone.setURI(uri); try { return new GitService(clone.call()); } catch (Exception e) { if (new File(dir).exists()) { FileUtils.forceDelete(new File(dir)); } throw e; } } return null; } public static String moveContentToBranch(String source, GitService gitRepo, String targetBranch, String comment) throws Exception { if (!gitRepo.getBranchName().equals(targetBranch)) { File tmpDir = File.createTempFile("git_", targetBranch); if (tmpDir.delete()) { log.info("Cloning repository @ " + gitRepo.getBaseDirectory() + " for branch " + targetBranch); GitService forBranch = GitService.cloneRepo(gitRepo.getBaseDirectory(), tmpDir.getAbsolutePath()); String returnVal = null; try { forBranch.switchBranch(targetBranch); forBranch.pull(); returnVal = moveContentToGit(source, forBranch, comment); log.info("Pushing branch modifications back to repository @ " + gitRepo.getBaseDirectory()); forBranch.git.push().call(); } finally { if (forBranch != null) { forBranch.close(); } } return returnVal; } else { throw new Exception("Failed to delete temp file for temp dir creation."); } } else if (!new File(gitRepo.getBaseDirectory()).getAbsoluteFile().getAbsolutePath() .equals(new File(source).getAbsoluteFile().getAbsolutePath())) { String rev = moveContentToGit(source, gitRepo, comment); return rev; } else { throw new Exception("Source must not be the same as the target."); } } private static String moveContentToGit(String source, GitService git, String comment) throws Exception { return git.checkinNewContent(source, comment); } /** * Initializes war configuration directory for a Cadmium war. * @param uri The remote Git repository ssh URI. * @param branch The remote branch to checkout. * @param root The shared root. * @param warName The name of the war file. * @param historyManager The history manager to log the initialization event. * @return A GitService object the points to the freshly cloned Git repository. * @throws RefNotFoundException * @throws Exception */ public static GitService initializeConfigDirectory(String uri, String branch, String root, String warName, HistoryManager historyManager, ConfigManager configManager) throws Exception { initializeBaseDirectoryStructure(root, warName); String warDir = FileSystemManager.getChildDirectoryIfExists(root, warName); Properties configProperties = configManager.getDefaultProperties(); GitLocation gitLocation = new GitLocation(uri, branch, configProperties.getProperty("config.git.ref.sha")); GitService cloned = initializeRepo(gitLocation, warDir, "git-config-checkout"); try { String renderedContentDir = initializeSnapshotDirectory(warDir, configProperties, "com.meltmedia.cadmium.config.lastUpdated", "git-config-checkout", "config"); boolean hasExisting = configProperties.containsKey("com.meltmedia.cadmium.config.lastUpdated") && renderedContentDir != null && renderedContentDir .equals(configProperties.getProperty("com.meltmedia.cadmium.config.lastUpdated")); if (renderedContentDir != null) { configProperties.setProperty("com.meltmedia.cadmium.config.lastUpdated", renderedContentDir); } configProperties.setProperty("config.branch", cloned.getBranchName()); configProperties.setProperty("config.git.ref.sha", cloned.getCurrentRevision()); configProperties.setProperty("config.repo", cloned.getRemoteRepository()); configManager.persistDefaultProperties(); ExecutorService pool = null; if (historyManager == null) { pool = Executors.newSingleThreadExecutor(); historyManager = new HistoryManager(warDir, pool); } try { if (historyManager != null && !hasExisting) { historyManager.logEvent(EntryType.CONFIG, // NOTE: We should integrate the git pointer into this class. new GitLocation(cloned.getRemoteRepository(), cloned.getBranchName(), cloned.getCurrentRevision()), "AUTO", renderedContentDir, "", "Initial config pull.", true, true); } } finally { if (pool != null) { pool.shutdownNow(); } } return cloned; } catch (Throwable e) { cloned.close(); throw new Exception(e); } } /** * Initializes war content directory for a Cadmium war. * @param uri The remote Git repository ssh URI. * @param branch The remote branch to checkout. * @param root The shared content root. * @param warName The name of the war file. * @param historyManager The history manager to log the initialization event. * @return A GitService object the points to the freshly cloned Git repository. * @throws RefNotFoundException * @throws Exception */ public static GitService initializeContentDirectory(String uri, String branch, String root, String warName, HistoryManager historyManager, ConfigManager configManager) throws Exception { initializeBaseDirectoryStructure(root, warName); String warDir = FileSystemManager.getChildDirectoryIfExists(root, warName); Properties configProperties = configManager.getDefaultProperties(); GitLocation gitLocation = new GitLocation(uri, branch, configProperties.getProperty("git.ref.sha")); GitService cloned = initializeRepo(gitLocation, warDir, "git-checkout"); try { String renderedContentDir = initializeSnapshotDirectory(warDir, configProperties, "com.meltmedia.cadmium.lastUpdated", "git-checkout", "renderedContent"); boolean hasExisting = configProperties.containsKey("com.meltmedia.cadmium.lastUpdated") && renderedContentDir != null && renderedContentDir.equals(configProperties.getProperty("com.meltmedia.cadmium.lastUpdated")); if (renderedContentDir != null) { configProperties.setProperty("com.meltmedia.cadmium.lastUpdated", renderedContentDir); } configProperties.setProperty("branch", cloned.getBranchName()); configProperties.setProperty("git.ref.sha", cloned.getCurrentRevision()); configProperties.setProperty("repo", cloned.getRemoteRepository()); if (renderedContentDir != null) { String sourceFilePath = renderedContentDir + File.separator + "MET-INF" + File.separator + "source"; if (sourceFilePath != null && FileSystemManager.canRead(sourceFilePath)) { try { configProperties.setProperty("source", FileSystemManager.getFileContents(sourceFilePath)); } catch (Exception e) { log.warn("Failed to read source file {}", sourceFilePath); } } else if (!configProperties.containsKey("source")) { configProperties.setProperty("source", "{}"); } } else if (!configProperties.containsKey("source")) { configProperties.setProperty("source", "{}"); } configManager.persistDefaultProperties(); ExecutorService pool = null; if (historyManager == null) { pool = Executors.newSingleThreadExecutor(); historyManager = new HistoryManager(warDir, pool); } try { if (historyManager != null && !hasExisting) { historyManager.logEvent(EntryType.CONTENT, new GitLocation(cloned.getRemoteRepository(), cloned.getBranchName(), cloned.getCurrentRevision()), "AUTO", renderedContentDir, "", "Initial content pull.", true, true); } } finally { if (pool != null) { pool.shutdownNow(); } } return cloned; } catch (Throwable e) { cloned.close(); throw new Exception(e); } } private static String initializeSnapshotDirectory(String warDir, Properties configProperties, String key, String checkoutDir, String snapshotDir) throws Exception, IOException { String renderedContentDir = configProperties.getProperty(key); log.debug("Making sure {} exists.", renderedContentDir); if (renderedContentDir == null || !FileSystemManager.exists(renderedContentDir)) { log.info(snapshotDir + " directory does not exist. Creating!!!"); GitService rendered = null; File snapshotFile = new File(warDir, snapshotDir); if (snapshotFile.exists()) { log.warn("{} exists @ {}, Deleting and recreating. ", snapshotDir, snapshotFile); FileUtils.forceDelete(snapshotFile); } try { rendered = GitService.cloneRepo( new File(FileSystemManager.getChildDirectoryIfExists(warDir, checkoutDir), ".git") .getAbsolutePath(), snapshotFile.getAbsolutePath()); renderedContentDir = rendered.getBaseDirectory(); log.info("Removing .git directory from freshly cloned " + snapshotDir + " directory @ {}.", renderedContentDir); String gitDir = FileSystemManager.getChildDirectoryIfExists(snapshotFile.getAbsolutePath(), ".git"); if (gitDir != null) { FileSystemManager.deleteDeep(gitDir); } } finally { if (rendered != null) { rendered.close(); } } } return renderedContentDir; } private static GitService initializeRepo(GitLocation gitLocation, String warDir, String checkoutDir) throws Exception, RefNotFoundException { GitService cloned = null; String gitDir = FileSystemManager.getChildDirectoryIfExists(warDir, checkoutDir); if (gitDir != null) { try { cloned = createGitService(gitDir); cloned.getCurrentRevision(); } catch (Throwable t) { if (cloned != null) { cloned.close(); cloned = null; FileSystemManager.deleteDeep(gitDir); gitDir = null; } } } if (gitDir == null) { log.info("Cloning remote git repository to " + checkoutDir); cloned = cloneRepo(gitLocation.getRepository(), new File(warDir, checkoutDir).getAbsolutePath()); try { log.info("Switching to branch {}", gitLocation.getBranch()); cloned.switchBranch(gitLocation.getBranch()); if (StringUtils.isNotBlank(gitLocation.getRevision())) { cloned.resetToRev(gitLocation.getRevision()); } } catch (Throwable t) { cloned.close(); throw new Exception(t); } } else if (cloned == null) { cloned = createGitService(gitDir); } if (cloned == null) { throw new Exception("Failed to clone remote github repo from " + gitLocation.getRepository()); } return cloned; } private static void initializeBaseDirectoryStructure(String root, String warName) throws Exception { if (!FileSystemManager.exists(root)) { log.info("Content Root directory [{}] does not exist. Creating!!!", root); if (!new File(root).mkdirs()) { throw new Exception("Failed to create cadmium root @ " + root); } } String warDir = FileSystemManager.getChildDirectoryIfExists(root, warName); if (warDir == null) { log.info("War directory [{}] does not exist. Creating!!!", warName); if (!new File(root, warName).mkdirs()) { throw new Exception("Failed to create war content directory @ " + warDir); } } } public static GitService init(String site, String dir) throws Exception { String repoPath = dir + "/" + site; log.debug("Repository Path :" + repoPath); Repository repo = new FileRepository(repoPath + "/.git"); Git git = null; try { repo.create(); git = new Git(repo); File localGitRepo = new File(repoPath); localGitRepo.mkdirs(); new File(localGitRepo, "delete.me").createNewFile(); git.add().addFilepattern("delete.me").call(); git.commit().setMessage("initial commit").call(); return new GitService(git); } catch (IllegalStateException e) { log.debug("Repo Already exists locally"); if (repo != null) { repo.close(); } } return null; } public String getRepositoryDirectory() throws Exception { return git.getRepository().getDirectory().getAbsolutePath(); } public String getBaseDirectory() throws Exception { return FileSystemManager.getParent(getRepositoryDirectory()); } public boolean pull() throws Exception { log.debug("Pulling latest updates from remote git repo"); return git.pull().call().isSuccessful(); } public void push(boolean tags) throws Exception { PushCommand push = git.push(); if (tags) { push.setPushTags(); } push.call(); } public void switchBranch(String branchName) throws RefNotFoundException, Exception { Repository repository = git.getRepository(); if (branchName != null && !repository.getBranch().equals(branchName)) { log.info("Switching branch from {} to {}", repository.getBranch(), branchName); CheckoutCommand checkout = git.checkout(); if (isTag(branchName)) { checkout.setName(branchName); } else { checkout.setName("refs/heads/" + branchName); if (repository.getRef("refs/heads/" + branchName) == null) { CreateBranchCommand create = git.branchCreate(); create.setName(branchName); create.setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM); create.setStartPoint("origin/" + branchName); create.call(); } } checkout.call(); } } public void resetToRev(String revision) throws Exception { if (revision != null) { log.info("Resetting to sha {}", revision); git.reset().setMode(ResetType.HARD).setRef(revision).call(); } } public boolean checkRevision(String revision) throws Exception { String oldRevision = null; try { log.info("Checking if revision is on current branch."); oldRevision = getCurrentRevision(); resetToRev("refs/remotes/origin/" + getBranchName()); boolean found = false; for (RevCommit commit : git.log().call()) { if (commit.getId().getName().equals(revision)) { found = true; break; } } return found; } catch (Exception e) { log.error("Failed to read repository state.", e); throw e; } finally { if (oldRevision != null && !getCurrentRevision().equals(oldRevision)) { resetToRev(oldRevision); } } } private boolean checkForRemoteBranch(String branchName) throws Exception { log.info("Getting list of existing branches."); List<Ref> refs = git.branchList().setListMode(ListMode.ALL).call(); boolean branchExists = false; if (refs != null) { for (Ref ref : refs) { if (ref.getName().endsWith("/" + branchName)) { branchExists = true; break; } } } return branchExists; } public boolean newRemoteBranch(String branchName) throws Exception { try { log.info("Purging branches that no longer have remotes."); git.fetch().setRemoveDeletedRefs(true).call(); } catch (Exception e) { log.warn("Tried to fetch from remote when there is no remote."); return false; } boolean branchExists = checkForRemoteBranch(branchName); if (!branchExists) { Ref ref = newLocalBranch(branchName); git.push().add(ref).call(); return true; } else { log.info(branchName + " already exists."); } return false; } public Ref newLocalBranch(String branchName) throws Exception { return git.branchCreate().setName(branchName).call(); } public boolean newEmtpyRemoteBranch(String branchName) throws Exception { try { log.info("Purging branches that no longer have remotes."); git.fetch().setRemoveDeletedRefs(true).call(); } catch (Exception e) { log.warn("Tried to fetch from remote when there is no remote."); return false; } boolean branchExists = checkForRemoteBranch(branchName); if (!branchExists) { Ref ref = newEmptyLocalBranch(branchName); git.push().add(ref).call(); return true; } else { log.info(branchName + " already exists."); } return false; } public Ref newEmptyLocalBranch(String branchName) throws Exception { Iterable<RevCommit> revItr = git.log().all().call(); RevCommit oldestRev = null; for (RevCommit rev : revItr) { if (oldestRev == null || rev.getCommitTime() < oldestRev.getCommitTime()) { oldestRev = rev; } } Ref newBranch = null; String currentBranch = getBranchName(); if (oldestRev != null) { newBranch = git.checkout().setName(branchName).setCreateBranch(true).setStartPoint(oldestRev.getName()) .call(); } else { newBranch = git.checkout().setName(branchName).setCreateBranch(true).call(); } switchBranch(currentBranch); return newBranch; } public void deleteLocalBranch(String branchName) throws Exception { git.branchDelete().setForce(true).setBranchNames(branchName).call(); } /** * Checks in content from a source directory into the current git repository. * @param sourceDirectory The directory to pull content in from. * @param message The commit message to use. * @return The new SHA revision. * @throws Exception */ public String checkinNewContent(String sourceDirectory, String message) throws Exception { RmCommand remove = git.rm(); boolean hasFiles = false; for (String filename : new File(getBaseDirectory()).list()) { if (!filename.equals(".git")) { remove.addFilepattern(filename); hasFiles = true; } } if (hasFiles) { log.info("Removing old content."); remove.call(); } log.info("Copying in new content."); FileSystemManager.copyAllContent(sourceDirectory, getBaseDirectory(), true); log.info("Adding new content."); AddCommand add = git.add(); for (String filename : new File(getBaseDirectory()).list()) { if (!filename.equals(".git")) { add.addFilepattern(filename); } } add.call(); log.info("Committing new content."); git.commit().setMessage(message).call(); return getCurrentRevision(); } public boolean tag(String tagname, String comment) throws Exception { try { git.fetch().setTagOpt(TagOpt.FETCH_TAGS).call(); } catch (Exception e) { log.debug("Fetch from origin failed.", e); } List<RevTag> tags = git.tagList().call(); if (tags != null && tags.size() > 0) { for (RevTag tag : tags) { if (tag.getTagName().equals(tagname)) { throw new Exception("Tag already exists."); } } } boolean success = git.tag().setMessage(comment).setName(tagname).call() != null; try { git.push().setPushTags().call(); } catch (Exception e) { log.debug("Failed to push changes.", e); } return success; } public boolean isTag(String tagname) throws Exception { Map<String, Ref> allRefs = git.getRepository().getTags(); for (String key : allRefs.keySet()) { Ref ref = allRefs.get(key); log.trace("Checking tag key{}, ref{}", key, ref.getName()); if (key.equals(tagname) && ref.getName().equals("refs/tags/" + tagname)) { return true; } } return false; } public void fetchRemotes() throws Exception { git.fetch().setTagOpt(TagOpt.FETCH_TAGS).setCheckFetchedObjects(false).setRemoveDeletedRefs(true).call(); } public boolean isBranch(String branchname) throws Exception { log.trace("Checking if {} is a branch on {}", branchname, getRemoteRepository()); for (Ref ref : git.branchList().setListMode(ListMode.ALL).call()) { log.trace("Checking {}, {}", ref.getName(), branchname); if (ref.getName().equals("refs/heads/" + branchname) || ref.getName().equals("refs/remotes/origin/" + branchname)) { return true; } } return false; } public String getBranchName() throws Exception { Repository repository = git.getRepository(); if (ObjectId.isId(repository.getFullBranch()) && repository.getFullBranch().equals(repository.resolve("HEAD").getName())) { RevWalk revs = null; try { log.trace("Trying to resolve tagname: {}", repository.getFullBranch()); ObjectId tagRef = ObjectId.fromString(repository.getFullBranch()); revs = new RevWalk(repository); RevCommit commit = revs.parseCommit(tagRef); Map<String, Ref> allTags = repository.getTags(); for (String key : allTags.keySet()) { Ref ref = allTags.get(key); RevTag tag = revs.parseTag(ref.getObjectId()); log.trace("Checking ref {}, {}", commit.getName(), tag.getObject()); if (tag.getObject().equals(commit)) { return key; } } } catch (Exception e) { log.warn("Invalid id: {}", repository.getFullBranch(), e); } finally { revs.release(); } } return repository.getBranch(); } public String getCurrentRevision() throws Exception { return git.getRepository().getRef(getBranchName()).getObjectId().getName(); } public String getRemoteRepository() { return git.getRepository().getConfig().getString("remote", "origin", "url"); } public void close() throws IOException { git.getRepository().close(); git = null; } }