Java tutorial
/** * Copyright (C) 2013 the original author or authors. * See the notice.md file distributed with this work for additional * information regarding copyright ownership. * * 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 io.hawt.git; import io.hawt.util.*; import io.hawt.util.Objects; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.io.DisabledOutputStream; import org.gitective.core.BlobUtils; import org.gitective.core.CommitFinder; import org.gitective.core.CommitUtils; import org.gitective.core.PathFilterUtils; import org.gitective.core.filter.commit.CommitLimitFilter; import org.gitective.core.filter.commit.CommitListFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.*; import static io.hawt.git.GitFacade.trimLeadingSlash; /** * A based class for implementations of {@link GitFacadeMXBean} */ public abstract class GitFacadeSupport extends MBeanSupport implements GitFacadeMXBean, GitFileManager { private static final transient Logger LOG = LoggerFactory.getLogger(GitFacadeSupport.class); private int shortCommitIdLength = 6; private String repositoryLabel = "Wiki"; public void setRepositoryLabel(String repositoryLabel) { this.repositoryLabel = repositoryLabel; } public String getRepositoryLabel() { return repositoryLabel; } protected String doDiff(Git git, String objectId, String baseObjectId, String pathOrBlobPath) { Repository r = git.getRepository(); String blobPath = trimLeadingSlash(pathOrBlobPath); /* RevCommit commit = JGitUtils.getCommit(r, objectId); ObjectId current; if (isNotBlank(objectId)) { current = BlobUtils.getId(r, objectId, blobPath); } else { current = CommitUtils.getHead(r).getId(); } ObjectId previous; if (isNotBlank(baseObjectId)) { previous = BlobUtils.getId(r, baseObjectId, blobPath); } else { RevCommit revCommit = CommitUtils.getCommit(r, current); RevCommit[] parents = revCommit.getParents(); if (parents.length == 0) { throw new IllegalArgumentException("No parent commits!"); } else { previous = parents[0]; } } Collection<Edit> changes = BlobUtils.diff(r, previous, current); // no idea how to format Collection<Edit> :) */ RevCommit commit; if (Strings.isNotBlank(objectId)) { commit = CommitUtils.getCommit(r, objectId); } else { commit = CommitUtils.getHead(r); } RevCommit baseCommit = null; if (Strings.isNotBlank(baseObjectId)) { baseCommit = CommitUtils.getCommit(r, baseObjectId); } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); RawTextComparator cmp = RawTextComparator.DEFAULT; DiffFormatter formatter = new DiffFormatter(buffer); formatter.setRepository(r); formatter.setDiffComparator(cmp); formatter.setDetectRenames(true); RevTree commitTree = commit.getTree(); RevTree baseTree; try { if (baseCommit == null) { if (commit.getParentCount() > 0) { final RevWalk rw = new RevWalk(r); RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); rw.dispose(); baseTree = parent.getTree(); } else { // FIXME initial commit. no parent?! baseTree = commitTree; } } else { baseTree = baseCommit.getTree(); } List<DiffEntry> diffEntries = formatter.scan(baseTree, commitTree); if (blobPath != null && blobPath.length() > 0) { for (DiffEntry diffEntry : diffEntries) { if (diffEntry.getNewPath().equalsIgnoreCase(blobPath)) { formatter.format(diffEntry); break; } } } else { formatter.format(diffEntries); } formatter.flush(); return buffer.toString(); } catch (IOException e) { throw new RuntimeIOException(e); } } protected abstract void checkoutBranch(Git git, String branch) throws GitAPIException; protected abstract boolean isPushOnCommit(); protected void doCreateBranch(Git git, String fromBranch, String newBranch) throws GitAPIException { checkoutBranch(git, fromBranch); git.branchCreate().setName(newBranch).call(); checkoutBranch(git, newBranch); } /** * Returns the file changes in a commit */ protected List<CommitTreeInfo> doGetCommitTree(Git git, String commitId) { Repository repository = git.getRepository(); List<CommitTreeInfo> list = new ArrayList<CommitTreeInfo>(); RevCommit commit = CommitUtils.getCommit(repository, commitId); if (commit != null) { RevWalk rw = new RevWalk(repository); try { if (commit.getParentCount() == 0) { TreeWalk treeWalk = new TreeWalk(repository); treeWalk.reset(); treeWalk.setRecursive(true); treeWalk.addTree(commit.getTree()); while (treeWalk.next()) { String pathString = treeWalk.getPathString(); ObjectId objectId = treeWalk.getObjectId(0); int rawMode = treeWalk.getRawMode(0); list.add(new CommitTreeInfo(pathString, pathString, 0, rawMode, objectId.getName(), commit.getId().getName(), ChangeType.ADD)); } treeWalk.close(); } else { RevCommit parent = rw.parseCommit(commit.getParent(0).getId()); DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE); df.setRepository(repository); df.setDiffComparator(RawTextComparator.DEFAULT); df.setDetectRenames(true); List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree()); for (DiffEntry diff : diffs) { String objectId = diff.getNewId().name(); if (diff.getChangeType().equals(ChangeType.DELETE)) { list.add(new CommitTreeInfo(diff.getOldPath(), diff.getOldPath(), 0, diff.getNewMode().getBits(), objectId, commit.getId().getName(), diff.getChangeType())); } else if (diff.getChangeType().equals(ChangeType.RENAME)) { list.add(new CommitTreeInfo(diff.getOldPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), objectId, commit.getId().getName(), diff.getChangeType())); } else { list.add(new CommitTreeInfo(diff.getNewPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), objectId, commit.getId().getName(), diff.getChangeType())); } } } } catch (Throwable e) { LOG.warn("Failed to walk tree for commit " + commitId + ". " + e, e); } finally { rw.dispose(); } } return list; } protected CommitInfo doGetCommitInfo(Git git, String commitId) { Repository repository = git.getRepository(); RevCommit commit = CommitUtils.getCommit(repository, commitId); if (commit == null) { return null; } else { return createCommitInfo(commit); } } protected abstract Iterable<PushResult> doPush(Git git) throws Exception; protected CommitInfo doCreateDirectory(Git git, File rootDir, String branch, String path, PersonIdent personIdent, String commitMessage) throws Exception { File file = getFile(rootDir, path); if (file.exists()) { return null; } file.mkdirs(); String filePattern = getFilePattern(path); AddCommand add = git.add().addFilepattern(filePattern).addFilepattern("."); add.call(); CommitCommand commit = git.commit().setAll(true).setAuthor(personIdent).setMessage(commitMessage); RevCommit revCommit = commitThenPush(git, branch, commit); return createCommitInfo(revCommit); } protected Void doRevert(Git git, File rootDir, String branch, String objectId, String blobPath, String commitMessage, PersonIdent personIdent) throws Exception { String contents = doGetContent(git, objectId, blobPath); if (contents != null) { doWrite(git, rootDir, branch, blobPath, contents.getBytes(), personIdent, commitMessage); } return null; } protected RevCommit doRename(Git git, File rootDir, String branch, String oldPath, String newPath, String commitMessage, PersonIdent personIdent) throws Exception { File file = getFile(rootDir, oldPath); File newFile = getFile(rootDir, newPath); if (file.exists()) { File parentFile = newFile.getParentFile(); parentFile.mkdirs(); if (!parentFile.exists()) { throw new IOException("Could not create directory " + parentFile + " when trying to move " + file + " to " + newFile + ". Maybe a file permission issue?"); } file.renameTo(newFile); String filePattern = getFilePattern(newPath); git.add().addFilepattern(filePattern).call(); CommitCommand commit = git.commit().setAll(true).setAuthor(personIdent).setMessage(commitMessage); return commitThenPush(git, branch, commit); } else { return null; } } protected RevCommit doRemove(Git git, File rootDir, String branch, String path, String commitMessage, PersonIdent personIdent) throws Exception { File file = getFile(rootDir, path); if (file.exists()) { Files.recursiveDelete(file); String filePattern = getFilePattern(path); git.rm().addFilepattern(filePattern).call(); CommitCommand commit = git.commit().setAll(true).setAuthor(personIdent).setMessage(commitMessage); return commitThenPush(git, branch, commit); } else { return null; } } protected List<String> doListBranches(Git git) throws GitAPIException { SortedSet<String> names = new TreeSet<String>(); List<Ref> call = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call(); for (Ref ref : call) { String name = ref.getName(); int idx = name.lastIndexOf('/'); if (idx >= 0) { name = name.substring(idx + 1); } if (name.length() > 0) { names.add(name); } } return new ArrayList<String>(names); } protected String doGetHead(Git git) { RevCommit commit = CommitUtils.getHead(git.getRepository()); return commit.getName(); } protected List<CommitInfo> doHistory(Git git, String branch, String objectId, String pathOrBlobPath, int limit) { List<CommitInfo> results = new ArrayList<CommitInfo>(); Repository r = git.getRepository(); try { String head = getHEAD(); } catch (Exception e) { LOG.error("Cannot find HEAD of this git repository! " + e, e); return results; } String path = trimLeadingSlash(pathOrBlobPath); CommitFinder finder = new CommitFinder(r); CommitListFilter filter = new CommitListFilter(); if (Strings.isNotBlank(path)) { finder.setFilter(PathFilterUtils.and(path)); } finder.setFilter(filter); if (limit > 0) { finder.setFilter(new CommitLimitFilter(limit).setStop(true)); } if (Strings.isNotBlank(objectId)) { finder.findFrom(objectId); } else { if (Strings.isNotBlank(branch)) { ObjectId branchObjectId = getBranchObjectId(git, branch); if (branchObjectId != null) { finder = finder.findFrom(branchObjectId); } else { finder = finder.findInBranches(); } } else { finder.find(); } } List<RevCommit> commits = filter.getCommits(); for (RevCommit entry : commits) { CommitInfo commitInfo = createCommitInfo(entry); results.add(commitInfo); } return results; } protected ObjectId getBranchObjectId(Git git, String branch) { Ref branchRef = null; try { String branchRevName = "refs/heads/" + branch; List<Ref> branches = git.branchList().call(); for (Ref ref : branches) { String revName = ref.getName(); if (Objects.equals(branchRevName, revName)) { branchRef = ref; break; } } } catch (GitAPIException e) { LOG.warn("Failed to find branches " + e, e); } ObjectId branchObjectId = null; if (branchRef != null) { branchObjectId = branchRef.getObjectId(); } return branchObjectId; } @Override protected String getDefaultObjectName() { return "hawtio:type=GitFacade"; } protected String doGetContent(Git git, String objectId, String pathOrBlobPath) { objectId = defaultObjectId(git, objectId); Repository r = git.getRepository(); String blobPath = trimLeadingSlash(pathOrBlobPath); return BlobUtils.getContent(r, objectId, blobPath); } protected String defaultObjectId(Git git, String objectId) { if (objectId == null || objectId.trim().length() == 0) { RevCommit commit = CommitUtils.getHead(git.getRepository()); objectId = commit.getName(); } return objectId; } /** * Reads the file contents from the currently checked out branch */ protected FileContents doRead(Git git, File rootDir, String branch, String pathOrEmpty) throws IOException, GitAPIException { checkoutBranch(git, branch); String path = Strings.isBlank(pathOrEmpty) ? "/" : pathOrEmpty; File file = getFile(rootDir, path); if (file.isFile()) { String contents = IOHelper.readFully(file); return new FileContents(false, contents, null); } else { List<FileInfo> children = new ArrayList<FileInfo>(); if (file.exists()) { File[] files = file.listFiles(); for (File child : files) { if (!isIgnoreFile(child)) { children.add(FileInfo.createFileInfo(rootDir, child, branch)); } } } return new FileContents(file.isDirectory(), null, children); } } /** * Performs a read only operation on the file */ protected <T> T doReadFile(Git git, File rootDir, String branch, String pathOrEmpty, Function<File, T> callback) throws IOException, GitAPIException { checkoutBranch(git, branch); String path = Strings.isBlank(pathOrEmpty) ? "/" : pathOrEmpty; File file = getFile(rootDir, path); T results = callback.apply(file); return results; } /** * Performs a write operation on the file */ protected <T> T doWriteFile(Git git, File rootDir, String branch, String pathOrEmpty, WriteCallback callback) throws Exception { checkoutBranch(git, branch); String path = Strings.isBlank(pathOrEmpty) ? "/" : pathOrEmpty; File file = getFile(rootDir, path); WriteContext context = new WriteContext(git, rootDir, file); T results = (T) callback.apply(context); if (context.isRequiresCommit()) { PersonIdent author = context.getAuthor(); String message = context.getMessage(); if (Strings.isBlank(message)) { message = "Updated " + Files.getRelativePath(rootDir, file); } CommitCommand command = git.commit().setAll(true).setMessage(message); if (author != null) { command = command.setAuthor(author); } RevCommit revCommit = commitThenPush(git, branch, command); createCommitInfo(revCommit); } return results; } protected FileInfo doExists(Git git, File rootDir, String branch, String pathOrEmpty) throws GitAPIException { return doExists(git, rootDir, branch, pathOrEmpty, false); } protected FileInfo doExists(Git git, File rootDir, String branch, String pathOrEmpty, final boolean caseSensitive) throws GitAPIException { checkoutBranch(git, branch); final String path = Strings.isBlank(pathOrEmpty) ? "/" : pathOrEmpty; File file = getFile(rootDir, path); File parent = file.getParentFile(); // need to list the files, so we can grab the actual file name File[] files = parent.listFiles(new FileFilter() { String match = new File(caseSensitive ? path : path.toLowerCase(Locale.US)).getName(); @Override public boolean accept(File pathname) { String name = caseSensitive ? pathname.getName() : pathname.getName().toLowerCase(Locale.US); return match.equals(name); } }); if (files != null && files.length == 1) { return FileInfo.createFileInfo(rootDir, files[0], branch); } return null; } protected List<String> doCompletePath(Git git, File rootDir, String branch, String completionText, boolean directoriesOnly) throws GitAPIException { checkoutBranch(git, branch); boolean empty = Strings.isBlank(completionText); String pattern = completionText; File file = getFile(rootDir, completionText); String prefix = completionText; if (file.exists()) { pattern = ""; } else { String startPath = "."; if (!empty) { int idx = completionText.lastIndexOf('/'); if (idx >= 0) { startPath = completionText.substring(0, idx); if (startPath.length() == 0) { startPath = "/"; } pattern = completionText.substring(idx + 1); } } file = getFile(rootDir, startPath); prefix = startPath; } if (prefix.length() > 0 && !prefix.endsWith("/")) { prefix += "/"; } if (prefix.equals("./")) { prefix = ""; } File[] list = file.listFiles(); List<String> answer = new ArrayList<String>(); for (File aFile : list) { String name = aFile.getName(); if (pattern.length() == 0 || name.contains(pattern)) { if (!isIgnoreFile(aFile) && (!directoriesOnly || aFile.isDirectory())) { answer.add(prefix + name); } } } return answer; } protected String doReadJsonChildContent(Git git, File rootDir, String branch, String path, String fileNameWildcard, String search) throws GitAPIException, IOException { checkoutBranch(git, branch); File file = getFile(rootDir, path); FileFilter filter = FileFilters.createFileFilter(fileNameWildcard); boolean first = true; StringBuilder buffer = new StringBuilder("{\n"); List<FileInfo> children = new ArrayList<FileInfo>(); if (file.isDirectory()) { if (file.exists()) { File[] files = file.listFiles(); for (File child : files) { if (!isIgnoreFile(child) && child.isFile()) { String text = IOHelper.readFully(child); if (!Strings.isNotBlank(search) || text.contains(search)) { if (first) { first = false; } else { buffer.append(",\n"); } buffer.append("\""); buffer.append(child.getName()); buffer.append("\": "); buffer.append(text); children.add(FileInfo.createFileInfo(rootDir, child, branch)); } } } } } buffer.append("\n}"); return buffer.toString(); } protected CommitInfo doWrite(Git git, File rootDir, String branch, String path, byte[] contents, PersonIdent personIdent, String commitMessage) throws Exception { File file = getFile(rootDir, path); file.getParentFile().mkdirs(); IOHelper.write(file, contents); String filePattern = getFilePattern(path); AddCommand add = git.add().addFilepattern(filePattern).addFilepattern("."); add.call(); CommitCommand commit = git.commit().setAll(true).setAuthor(personIdent).setMessage(commitMessage); RevCommit revCommit = commitThenPush(git, branch, commit); return createCommitInfo(revCommit); } protected static String getFilePattern(String path) { String filePattern = path; if (filePattern.startsWith("/")) filePattern = filePattern.substring(1); return filePattern; } protected RevCommit commitThenPush(Git git, String branch, CommitCommand commit) throws Exception { RevCommit answer = commit.call(); if (LOG.isDebugEnabled()) { LOG.debug("Committed " + answer.getId() + " " + answer.getFullMessage()); } if (isPushOnCommit()) { Iterable<PushResult> results = doPush(git); for (PushResult result : results) { if (LOG.isDebugEnabled()) { LOG.debug("Pushed " + result.getMessages() + " " + result.getURI() + " branch: " + branch + " updates: " + toString(result.getRemoteUpdates())); } } } return answer; } protected String toString(Collection<RemoteRefUpdate> updates) { StringBuilder builder = new StringBuilder(); for (RemoteRefUpdate update : updates) { if (builder.length() > 0) { builder.append(" "); } builder.append(update.getMessage() + " " + update.getRemoteName() + " " + update.getNewObjectId()); } return builder.toString(); } public CommitInfo createCommitInfo(RevCommit entry) { final Date date = GitFacade.getCommitDate(entry); String author = entry.getAuthorIdent().getName(); boolean merge = entry.getParentCount() > 1; String shortMessage = entry.getShortMessage(); String trimmedMessage = Strings.trimString(shortMessage, 78); String name = entry.getName(); String commitHashText = getShortCommitHash(name); return new CommitInfo(commitHashText, name, author, date, merge, trimmedMessage, shortMessage); } protected String getShortCommitHash(String name) { final int hashLen = shortCommitIdLength; return name.substring(0, hashLen); } protected String removeLeadingSlash(String path) { if (path.startsWith("/")) { return path.substring(1); } else { return path; } } protected boolean isIgnoreFile(File child) { return child.getName().startsWith("."); } /** * Returns the file for the given path relative to the git root directory */ protected File getFile(File rootDir, String path) { return new File(rootDir, removeLeadingSlash(path)); } }