| | |
| | | import java.io.IOException;
|
| | | import java.io.InputStream;
|
| | | import java.io.OutputStream;
|
| | | import java.nio.charset.Charset;
|
| | | import java.text.MessageFormat;
|
| | | import java.util.ArrayList;
|
| | | import java.util.Arrays;
|
| | |
| | | import java.util.List;
|
| | | import java.util.Map;
|
| | | import java.util.Map.Entry;
|
| | | import java.util.regex.Pattern;
|
| | | import java.util.zip.ZipEntry;
|
| | | import java.util.zip.ZipOutputStream;
|
| | |
|
| | | import org.eclipse.jgit.api.CloneCommand;
|
| | | import org.eclipse.jgit.api.FetchCommand;
|
| | | import org.eclipse.jgit.api.Git;
|
| | | import org.eclipse.jgit.api.ResetCommand;
|
| | | import org.eclipse.jgit.api.ResetCommand.ResetType;
|
| | | 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.errors.IncorrectObjectTypeException;
|
| | | import org.eclipse.jgit.errors.MissingObjectException;
|
| | | import org.eclipse.jgit.errors.StopWalkException;
|
| | | import org.eclipse.jgit.lib.BlobBasedConfig;
|
| | | import org.eclipse.jgit.lib.CommitBuilder;
|
| | | import org.eclipse.jgit.lib.Constants;
|
| | | import org.eclipse.jgit.lib.FileMode;
|
| | |
| | | import org.eclipse.jgit.lib.RefUpdate.Result;
|
| | | import org.eclipse.jgit.lib.Repository;
|
| | | import org.eclipse.jgit.lib.RepositoryCache.FileKey;
|
| | | import org.eclipse.jgit.lib.StoredConfig;
|
| | | import org.eclipse.jgit.lib.TreeFormatter;
|
| | | import org.eclipse.jgit.revwalk.RevBlob;
|
| | | import org.eclipse.jgit.revwalk.RevCommit;
|
| | |
| | | import com.gitblit.models.PathModel;
|
| | | import com.gitblit.models.PathModel.PathChangeModel;
|
| | | import com.gitblit.models.RefModel;
|
| | | import com.gitblit.models.SubmoduleModel;
|
| | |
|
| | | /**
|
| | | * Collection of static methods for retrieving information from a repository.
|
| | |
| | | if (credentialsProvider != null) {
|
| | | clone.setCredentialsProvider(credentialsProvider);
|
| | | }
|
| | | clone.call();
|
| | | Repository repository = clone.call().getRepository();
|
| | | |
| | | // Now we have to fetch because CloneCommand doesn't fetch
|
| | | // refs/notes nor does it allow manual RefSpec.
|
| | | File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
|
| | | FileRepository repository = new FileRepository(gitDir);
|
| | | result.createdRepository = true;
|
| | | result.fetchResult = fetchRepository(credentialsProvider, repository);
|
| | | repository.close();
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * Reset HEAD to the latest remote tracking commit.
|
| | | * |
| | | * @param repository
|
| | | * @param remoteRef
|
| | | * the remote tracking reference (e.g. origin/master)
|
| | | * @return Ref
|
| | | * @throws Exception
|
| | | */
|
| | | public static Ref resetHEAD(Repository repository, String remoteRef) throws Exception {
|
| | | if (!remoteRef.startsWith(Constants.R_REMOTES)) {
|
| | | remoteRef = Constants.R_REMOTES + remoteRef;
|
| | | }
|
| | | Git git = new Git(repository);
|
| | | ResetCommand reset = git.reset();
|
| | | reset.setMode(ResetType.SOFT);
|
| | | reset.setRef(remoteRef);
|
| | | Ref result = reset.call();
|
| | | return result;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Creates a bare repository.
|
| | | *
|
| | | * @param repositoriesFolder
|
| | |
| | | * @return Repository
|
| | | */
|
| | | public static Repository createRepository(File repositoriesFolder, String name) {
|
| | | Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
|
| | | return git.getRepository();
|
| | | try {
|
| | | Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
|
| | | return git.getRepository();
|
| | | } catch (GitAPIException e) {
|
| | | throw new RuntimeException(e);
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | * false all repositories are included.
|
| | | * @param searchSubfolders
|
| | | * recurse into subfolders to find grouped repositories
|
| | | * @param depth
|
| | | * optional recursion depth, -1 = infinite recursion
|
| | | * @param exclusions
|
| | | * list of regex exclusions for matching to folder names
|
| | | * @return list of repository names
|
| | | */
|
| | | public static List<String> getRepositoryList(File repositoriesFolder, boolean onlyBare,
|
| | | boolean searchSubfolders) {
|
| | | boolean searchSubfolders, int depth, List<String> exclusions) {
|
| | | List<String> list = new ArrayList<String>();
|
| | | if (repositoriesFolder == null || !repositoriesFolder.exists()) {
|
| | | return list;
|
| | | }
|
| | | List<Pattern> patterns = new ArrayList<Pattern>();
|
| | | if (!ArrayUtils.isEmpty(exclusions)) {
|
| | | for (String regex : exclusions) {
|
| | | patterns.add(Pattern.compile(regex));
|
| | | }
|
| | | }
|
| | | list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
|
| | | onlyBare, searchSubfolders));
|
| | | onlyBare, searchSubfolders, depth, patterns));
|
| | | StringUtils.sortRepositorynames(list);
|
| | | return list;
|
| | | }
|
| | |
| | | * repositories are included.
|
| | | * @param searchSubfolders
|
| | | * recurse into subfolders to find grouped repositories
|
| | | * @param depth
|
| | | * recursion depth, -1 = infinite recursion
|
| | | * @param patterns
|
| | | * list of regex patterns for matching to folder names
|
| | | * @return
|
| | | */
|
| | | private static List<String> getRepositoryList(String basePath, File searchFolder,
|
| | | boolean onlyBare, boolean searchSubfolders) {
|
| | | boolean onlyBare, boolean searchSubfolders, int depth, List<Pattern> patterns) {
|
| | | File baseFile = new File(basePath);
|
| | | List<String> list = new ArrayList<String>();
|
| | | if (depth == 0) {
|
| | | return list;
|
| | | }
|
| | | |
| | | int nextDepth = (depth == -1) ? -1 : depth - 1;
|
| | | for (File file : searchFolder.listFiles()) {
|
| | | if (file.isDirectory()) {
|
| | | boolean exclude = false;
|
| | | for (Pattern pattern : patterns) {
|
| | | String path = FileUtils.getRelativePath(baseFile, file).replace('\\', '/');
|
| | | if (pattern.matcher(path).matches()) {
|
| | | LOGGER.debug(MessageFormat.format("excluding {0} because of rule {1}", path, pattern.pattern()));
|
| | | exclude = true;
|
| | | break;
|
| | | }
|
| | | }
|
| | | if (exclude) {
|
| | | // skip to next file
|
| | | continue;
|
| | | }
|
| | |
|
| | | File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
|
| | | if (gitDir != null) {
|
| | | if (onlyBare && gitDir.getName().equals(".git")) {
|
| | | continue;
|
| | | }
|
| | | // determine repository name relative to base path
|
| | | String repository = StringUtils.getRelativePath(basePath,
|
| | | file.getAbsolutePath());
|
| | | list.add(repository);
|
| | | if (gitDir.equals(file) || gitDir.getParentFile().equals(file)) {
|
| | | // determine repository name relative to base path
|
| | | String repository = FileUtils.getRelativePath(baseFile, file);
|
| | | list.add(repository);
|
| | | } else if (searchSubfolders && file.canRead()) {
|
| | | // look for repositories in subfolders
|
| | | list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
|
| | | nextDepth, patterns));
|
| | | }
|
| | | } else if (searchSubfolders && file.canRead()) {
|
| | | // look for repositories in subfolders
|
| | | list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders));
|
| | | list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
|
| | | nextDepth, patterns));
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | }
|
| | | ObjectId entid = tw.getObjectId(0);
|
| | | FileMode entmode = tw.getFileMode(0);
|
| | | RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
|
| | | rw.parseBody(ro);
|
| | | ByteArrayOutputStream os = new ByteArrayOutputStream();
|
| | | ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
|
| | | byte[] tmp = new byte[4096];
|
| | | InputStream in = ldr.openStream();
|
| | | int n;
|
| | | while ((n = in.read(tmp)) > 0) {
|
| | | os.write(tmp, 0, n);
|
| | | if (entmode != FileMode.GITLINK) {
|
| | | RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
|
| | | rw.parseBody(ro);
|
| | | ByteArrayOutputStream os = new ByteArrayOutputStream();
|
| | | ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
|
| | | byte[] tmp = new byte[4096];
|
| | | InputStream in = ldr.openStream();
|
| | | int n;
|
| | | while ((n = in.read(tmp)) > 0) {
|
| | | os.write(tmp, 0, n);
|
| | | }
|
| | | in.close();
|
| | | content = os.toByteArray();
|
| | | }
|
| | | in.close();
|
| | | content = os.toByteArray();
|
| | | }
|
| | | } catch (Throwable t) {
|
| | | error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
|
| | |
| | | * @param tree
|
| | | * if null, the RevTree from HEAD is assumed.
|
| | | * @param blobPath
|
| | | * @param charsets optional
|
| | | * @return UTF-8 string content
|
| | | */
|
| | | public static String getStringContent(Repository repository, RevTree tree, String blobPath) {
|
| | | public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
|
| | | byte[] content = getByteContent(repository, tree, blobPath);
|
| | | if (content == null) {
|
| | | return null;
|
| | | }
|
| | | return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
|
| | | return StringUtils.decodeString(content, charsets);
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | *
|
| | | * @param repository
|
| | | * @param objectId
|
| | | * @param charsets optional
|
| | | * @return UTF-8 string content
|
| | | */
|
| | | public static String getStringContent(Repository repository, String objectId) {
|
| | | public static String getStringContent(Repository repository, String objectId, String... charsets) {
|
| | | byte[] content = getByteContent(repository, objectId);
|
| | | if (content == null) {
|
| | | return null;
|
| | | }
|
| | | return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
|
| | | return StringUtils.decodeString(content, charsets);
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | tw.addTree(commit.getTree());
|
| | | while (tw.next()) {
|
| | | list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
|
| | | .getRawMode(0), commit.getId().getName(), ChangeType.ADD));
|
| | | .getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
|
| | | ChangeType.ADD));
|
| | | }
|
| | | tw.release();
|
| | | } else {
|
| | |
| | | df.setDetectRenames(true);
|
| | | List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
|
| | | for (DiffEntry diff : diffs) {
|
| | | String objectId = null;
|
| | | if (FileMode.GITLINK.equals(diff.getNewMode())) {
|
| | | objectId = diff.getNewId().name();
|
| | | }
|
| | |
|
| | | if (diff.getChangeType().equals(ChangeType.DELETE)) {
|
| | | list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
|
| | | .getNewMode().getBits(), commit.getId().getName(), diff
|
| | | .getNewMode().getBits(), objectId, commit.getId().getName(), diff
|
| | | .getChangeType()));
|
| | | } else if (diff.getChangeType().equals(ChangeType.RENAME)) {
|
| | | list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
|
| | | .getNewMode().getBits(), objectId, commit.getId().getName(), diff
|
| | | .getChangeType()));
|
| | | } else {
|
| | | list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
|
| | | .getNewMode().getBits(), commit.getId().getName(), diff
|
| | | .getNewMode().getBits(), objectId, commit.getId().getName(), diff
|
| | | .getChangeType()));
|
| | | }
|
| | | }
|
| | |
| | | } else {
|
| | | name = tw.getPathString().substring(basePath.length() + 1);
|
| | | }
|
| | | ObjectId objectId = tw.getObjectId(0);
|
| | | try {
|
| | | if (!tw.isSubtree()) {
|
| | | size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
|
| | | if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
|
| | | size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
|
| | | }
|
| | | } catch (Throwable t) {
|
| | | error(t, null, "failed to retrieve blob size for " + tw.getPathString());
|
| | | }
|
| | | return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
|
| | | commit.getName());
|
| | | objectId.getName(), commit.getName());
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
|
| | | return "-rwxr-xr-x";
|
| | | } else if (FileMode.SYMLINK.equals(mode)) {
|
| | | // FIXME symlink permissions
|
| | | return "symlink";
|
| | | } else if (FileMode.GITLINK.equals(mode)) {
|
| | | // FIXME gitlink permissions
|
| | | return "gitlink";
|
| | | return "submodule";
|
| | | }
|
| | | // FIXME missing permissions
|
| | | return "missing";
|
| | | }
|
| | |
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * Sets the local branch ref to point to the specified commit id.
|
| | | *
|
| | | * @param repository
|
| | | * @param branch
|
| | | * @param commitId
|
| | | * @return true if successful
|
| | | */
|
| | | public static boolean setBranchRef(Repository repository, String branch, String commitId) {
|
| | | String branchName = branch;
|
| | | if (!branchName.startsWith(Constants.R_HEADS)) {
|
| | | branchName = Constants.R_HEADS + branch;
|
| | | }
|
| | |
|
| | | try {
|
| | | RefUpdate refUpdate = repository.updateRef(branchName, false);
|
| | | refUpdate.setNewObjectId(ObjectId.fromString(commitId));
|
| | | RefUpdate.Result result = refUpdate.forceUpdate();
|
| | |
|
| | | switch (result) {
|
| | | case NEW:
|
| | | case FORCED:
|
| | | case NO_CHANGE:
|
| | | case FAST_FORWARD:
|
| | | return true; |
| | | default:
|
| | | LOGGER.error(MessageFormat.format("{0} {1} update to {2} returned result {3}",
|
| | | repository.getDirectory().getAbsolutePath(), branchName, commitId, result));
|
| | | }
|
| | | } catch (Throwable t) {
|
| | | error(t, repository, "{0} failed to set {1} to {2}", branchName, commitId);
|
| | | }
|
| | | return false;
|
| | | }
|
| | | |
| | | /**
|
| | | * Deletes the specified branch ref.
|
| | | * |
| | | * @param repository
|
| | | * @param branch
|
| | | * @return true if successful
|
| | | */
|
| | | public static boolean deleteBranchRef(Repository repository, String branch) {
|
| | | String branchName = branch;
|
| | | if (!branchName.startsWith(Constants.R_HEADS)) {
|
| | | branchName = Constants.R_HEADS + branch;
|
| | | }
|
| | |
|
| | | try {
|
| | | RefUpdate refUpdate = repository.updateRef(branchName, false);
|
| | | refUpdate.setForceUpdate(true);
|
| | | RefUpdate.Result result = refUpdate.delete();
|
| | | switch (result) {
|
| | | case NEW:
|
| | | case FORCED:
|
| | | case NO_CHANGE:
|
| | | case FAST_FORWARD:
|
| | | return true; |
| | | default:
|
| | | LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
|
| | | repository.getDirectory().getAbsolutePath(), branchName, result));
|
| | | }
|
| | | } catch (Throwable t) {
|
| | | error(t, repository, "{0} failed to delete {1}", branchName);
|
| | | }
|
| | | return false;
|
| | | }
|
| | | |
| | | /**
|
| | | * Get the full branch and tag ref names for any potential HEAD targets.
|
| | | *
|
| | | * @param repository
|
| | |
| | | * @return all refs grouped by their referenced object id
|
| | | */
|
| | | public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) {
|
| | | return getAllRefs(repository, true);
|
| | | }
|
| | | |
| | | /**
|
| | | * Returns all refs grouped by their associated object id.
|
| | | * |
| | | * @param repository
|
| | | * @param includeRemoteRefs
|
| | | * @return all refs grouped by their referenced object id
|
| | | */
|
| | | public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) {
|
| | | List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
|
| | | Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
|
| | | for (RefModel ref : list) {
|
| | | if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) {
|
| | | continue;
|
| | | }
|
| | | ObjectId objectid = ref.getReferencedObjectId();
|
| | | if (!refs.containsKey(objectid)) {
|
| | | refs.put(objectid, new ArrayList<RefModel>());
|
| | |
| | | }
|
| | | return branch;
|
| | | }
|
| | | |
| | | /**
|
| | | * Returns the list of submodules for this repository.
|
| | | * |
| | | * @param repository
|
| | | * @param commit
|
| | | * @return list of submodules
|
| | | */
|
| | | public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {
|
| | | RevCommit commit = getCommit(repository, commitId);
|
| | | return getSubmodules(repository, commit.getTree());
|
| | | }
|
| | | |
| | | /**
|
| | | * Returns the list of submodules for this repository.
|
| | | * |
| | | * @param repository
|
| | | * @param commit
|
| | | * @return list of submodules
|
| | | */
|
| | | public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
|
| | | List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
|
| | | byte [] blob = getByteContent(repository, tree, ".gitmodules");
|
| | | if (blob == null) {
|
| | | return list;
|
| | | }
|
| | | try {
|
| | | BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);
|
| | | for (String module : config.getSubsections("submodule")) {
|
| | | String path = config.getString("submodule", module, "path");
|
| | | String url = config.getString("submodule", module, "url");
|
| | | list.add(new SubmoduleModel(module, path, url));
|
| | | }
|
| | | } catch (ConfigInvalidException e) {
|
| | | LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);
|
| | | }
|
| | | return list;
|
| | | }
|
| | | |
| | | /**
|
| | | * Returns the submodule definition for the specified path at the specified
|
| | | * commit. If no module is defined for the path, null is returned.
|
| | | * |
| | | * @param repository
|
| | | * @param commit
|
| | | * @param path
|
| | | * @return a submodule definition or null if there is no submodule
|
| | | */
|
| | | public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {
|
| | | for (SubmoduleModel model : getSubmodules(repository, commitId)) {
|
| | | if (model.path.equals(path)) {
|
| | | return model;
|
| | | }
|
| | | }
|
| | | return null;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns the list of notes entered about the commit from the refs/notes
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * Returns a StoredConfig object for the repository.
|
| | | * |
| | | * @param repository
|
| | | * @return the StoredConfig of the repository
|
| | | */
|
| | | public static StoredConfig readConfig(Repository repository) {
|
| | | StoredConfig c = repository.getConfig();
|
| | | try {
|
| | | c.load();
|
| | | } catch (ConfigInvalidException cex) {
|
| | | error(cex, repository, "{0} configuration is invalid!");
|
| | | } catch (IOException cex) {
|
| | | error(cex, repository, "Could not open configuration for {0}!");
|
| | | }
|
| | | return c;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Zips the contents of the tree at the (optionally) specified revision and
|
| | | * the (optionally) specified basepath to the supplied outputstream.
|
| | | *
|
| | |
| | | }
|
| | | tw.setRecursive(true);
|
| | | while (tw.next()) {
|
| | | if (tw.getFileMode(0) == FileMode.GITLINK) {
|
| | | continue;
|
| | | }
|
| | | ZipEntry entry = new ZipEntry(tw.getPathString());
|
| | | entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
|
| | | Constants.OBJ_BLOB));
|