From cbe6840efecf87e53a687cdce6fbdf84c6ab8a46 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 19 Sep 2013 08:40:59 -0400
Subject: [PATCH] Documentation
---
src/main/java/com/gitblit/utils/JGitUtils.java | 362 ++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 341 insertions(+), 21 deletions(-)
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index 1f2ae94..cf6ec26 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -19,20 +19,24 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
+import org.apache.commons.io.filefilter.TrueFileFilter;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -55,6 +59,7 @@
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;
@@ -64,7 +69,7 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.eclipse.jgit.storage.file.FileRepository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
@@ -195,7 +200,7 @@
File folder = new File(repositoriesFolder, name);
if (folder.exists()) {
File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
- FileRepository repository = new FileRepository(gitDir);
+ Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
result.fetchResult = fetchRepository(credentialsProvider, repository);
repository.close();
} else {
@@ -256,13 +261,187 @@
* @return Repository
*/
public static Repository createRepository(File repositoriesFolder, String name) {
+ return createRepository(repositoriesFolder, name, "FALSE");
+ }
+
+ /**
+ * Creates a bare, shared repository.
+ *
+ * @param repositoriesFolder
+ * @param name
+ * @param shared
+ * the setting for the --shared option of "git init".
+ * @return Repository
+ */
+ public static Repository createRepository(File repositoriesFolder, String name, String shared) {
try {
- Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
- return git.getRepository();
- } catch (GitAPIException e) {
+ Repository repo = null;
+ try {
+ Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
+ repo = git.getRepository();
+ } catch (GitAPIException e) {
+ throw new RuntimeException(e);
+ }
+
+ GitConfigSharedRepository sharedRepository = new GitConfigSharedRepository(shared);
+ if (sharedRepository.isShared()) {
+ StoredConfig config = repo.getConfig();
+ config.setString("core", null, "sharedRepository", sharedRepository.getValue());
+ config.setBoolean("receive", null, "denyNonFastforwards", true);
+ config.save();
+
+ if (! JnaUtils.isWindows()) {
+ Iterator<File> iter = org.apache.commons.io.FileUtils.iterateFilesAndDirs(repo.getDirectory(),
+ TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
+ // Adjust permissions on file/directory
+ while (iter.hasNext()) {
+ adjustSharedPerm(iter.next(), sharedRepository);
+ }
+ }
+ }
+
+ return repo;
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
+
+ private enum GitConfigSharedRepositoryValue
+ {
+ UMASK("0", 0), FALSE("0", 0), OFF("0", 0), NO("0", 0),
+ GROUP("1", 0660), TRUE("1", 0660), ON("1", 0660), YES("1", 0660),
+ ALL("2", 0664), WORLD("2", 0664), EVERYBODY("2", 0664),
+ Oxxx(null, -1);
+
+ private String configValue;
+ private int permValue;
+ private GitConfigSharedRepositoryValue(String config, int perm) { configValue = config; permValue = perm; };
+
+ public String getConfigValue() { return configValue; };
+ public int getPerm() { return permValue; };
+
+ }
+
+ private static class GitConfigSharedRepository
+ {
+ private int intValue;
+ private GitConfigSharedRepositoryValue enumValue;
+
+ GitConfigSharedRepository(String s) {
+ if ( s == null || s.trim().isEmpty() ) {
+ enumValue = GitConfigSharedRepositoryValue.GROUP;
+ }
+ else {
+ try {
+ // Try one of the string values
+ enumValue = GitConfigSharedRepositoryValue.valueOf(s.trim().toUpperCase());
+ } catch (IllegalArgumentException iae) {
+ try {
+ // Try if this is an octal number
+ int i = Integer.parseInt(s, 8);
+ if ( (i & 0600) != 0600 ) {
+ String msg = String.format("Problem with core.sharedRepository filemode value (0%03o).\nThe owner of files must always have read and write permissions.", i);
+ throw new IllegalArgumentException(msg);
+ }
+ intValue = i & 0666;
+ enumValue = GitConfigSharedRepositoryValue.Oxxx;
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Bad configuration value for 'shared': '" + s + "'");
+ }
+ }
+ }
+ }
+
+ String getValue() {
+ if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) {
+ if (intValue == 0) return "0";
+ return String.format("0%o", intValue);
+ }
+ return enumValue.getConfigValue();
+ }
+
+ int getPerm() {
+ if ( enumValue == GitConfigSharedRepositoryValue.Oxxx ) return intValue;
+ return enumValue.getPerm();
+ }
+
+ boolean isCustom() {
+ return enumValue == GitConfigSharedRepositoryValue.Oxxx;
+ }
+
+ boolean isShared() {
+ return (enumValue.getPerm() > 0) || enumValue == GitConfigSharedRepositoryValue.Oxxx;
+ }
+ }
+
+
+ /**
+ * Adjust file permissions of a file/directory for shared repositories
+ *
+ * @param path
+ * File that should get its permissions changed.
+ * @param configShared
+ * Configuration string value for the shared mode.
+ * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
+ */
+ public static int adjustSharedPerm(File path, String configShared) {
+ return adjustSharedPerm(path, new GitConfigSharedRepository(configShared));
+ }
+
+
+ /**
+ * Adjust file permissions of a file/directory for shared repositories
+ *
+ * @param path
+ * File that should get its permissions changed.
+ * @param configShared
+ * Configuration setting for the shared mode.
+ * @return Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned.
+ */
+ public static int adjustSharedPerm(File path, GitConfigSharedRepository configShared) {
+ if (! configShared.isShared()) return 0;
+ if (! path.exists()) return -1;
+
+ int perm = configShared.getPerm();
+ JnaUtils.Filestat stat = JnaUtils.getFilestat(path);
+ if (stat == null) return -1;
+ int mode = stat.mode;
+ if (mode < 0) return -1;
+
+ // Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process'
+ // effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in
+ // that case, we decide to rather not touch is and getting the right permissions will have to be achieved
+ // in a different way, e.g. by using an appropriate umask for the Gitblit process.
+ if (System.getProperty("os.name").toLowerCase().startsWith("linux")) {
+ if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0)
+ && stat.gid != JnaUtils.getegid() ) {
+ LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" );
+ return 0;
+ }
+ }
+
+ // If the owner has no write access, delete it from group and other, too.
+ if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222;
+ // If the owner has execute access, set it for all blocks that have read access.
+ if ((mode & JnaUtils.S_IXUSR) == JnaUtils.S_IXUSR) perm |= (perm & 0444) >> 2;
+
+ if (configShared.isCustom()) {
+ // Use the custom value for access permissions.
+ mode = (mode & ~0777) | perm;
+ }
+ else {
+ // Just add necessary bits to existing permissions.
+ mode |= perm;
+ }
+
+ if (path.isDirectory()) {
+ mode |= (mode & 0444) >> 2;
+ mode |= JnaUtils.S_ISGID;
+ }
+
+ return JnaUtils.setFilemode(path, mode);
+ }
+
/**
* Returns a list of repository names in the specified folder.
@@ -294,6 +473,7 @@
list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
onlyBare, searchSubfolders, depth, patterns));
StringUtils.sortRepositorynames(list);
+ list.remove(".git"); // issue-256
return list;
}
@@ -435,39 +615,56 @@
}
return false;
}
+
+ /**
+ * Encapsulates the result of cloning or pulling from a repository.
+ */
+ public static class LastChange {
+ public Date when;
+ public String who;
+
+ LastChange() {
+ when = new Date(0);
+ }
+
+ LastChange(long lastModified) {
+ this.when = new Date(lastModified);
+ }
+ }
/**
- * Returns the date of the most recent commit on a branch. If the repository
- * does not exist Date(0) is returned. If it does exist but is empty, the
- * last modified date of the repository folder is returned.
+ * Returns the date and author of the most recent commit on a branch. If the
+ * repository does not exist Date(0) is returned. If it does exist but is
+ * empty, the last modified date of the repository folder is returned.
*
* @param repository
- * @return
+ * @return a LastChange object
*/
- public static Date getLastChange(Repository repository) {
+ public static LastChange getLastChange(Repository repository) {
if (!hasCommits(repository)) {
// null repository
if (repository == null) {
- return new Date(0);
+ return new LastChange();
}
// fresh repository
- return new Date(repository.getDirectory().lastModified());
+ return new LastChange(repository.getDirectory().lastModified());
}
List<RefModel> branchModels = getLocalBranches(repository, true, -1);
if (branchModels.size() > 0) {
// find most recent branch update
- Date lastChange = new Date(0);
+ LastChange lastChange = new LastChange();
for (RefModel branchModel : branchModels) {
- if (branchModel.getDate().after(lastChange)) {
- lastChange = branchModel.getDate();
+ if (branchModel.getDate().after(lastChange.when)) {
+ lastChange.when = branchModel.getDate();
+ lastChange.who = branchModel.getAuthorIdent().getName();
}
}
return lastChange;
}
// default to the repository folder modification date
- return new Date(repository.getDirectory().lastModified());
+ return new LastChange(repository.getDirectory().lastModified());
}
/**
@@ -545,6 +742,8 @@
try {
if (tree == null) {
ObjectId object = getDefaultBranch(repository);
+ if (object == null)
+ return null;
RevCommit commit = rw.parseCommit(object);
tree = commit.getTree();
}
@@ -768,6 +967,51 @@
}
/**
+ * Returns the list of files changed in a specified commit. If the
+ * repository does not exist or is empty, an empty list is returned.
+ *
+ * @param repository
+ * @param startCommit
+ * earliest commit
+ * @param endCommit
+ * most recent commit. if null, HEAD is assumed.
+ * @return list of files changed in a commit range
+ */
+ public static List<PathChangeModel> getFilesInRange(Repository repository, RevCommit startCommit, RevCommit endCommit) {
+ List<PathChangeModel> list = new ArrayList<PathChangeModel>();
+ if (!hasCommits(repository)) {
+ return list;
+ }
+ try {
+ DiffFormatter df = new DiffFormatter(null);
+ df.setRepository(repository);
+ df.setDiffComparator(RawTextComparator.DEFAULT);
+ df.setDetectRenames(true);
+
+ List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree());
+ for (DiffEntry diff : diffEntries) {
+
+ if (diff.getChangeType().equals(ChangeType.DELETE)) {
+ list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+ .getNewMode().getBits(), diff.getOldId().name(), null, diff
+ .getChangeType()));
+ } else if (diff.getChangeType().equals(ChangeType.RENAME)) {
+ list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
+ .getNewMode().getBits(), diff.getNewId().name(), null, diff
+ .getChangeType()));
+ } else {
+ list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+ .getNewMode().getBits(), diff.getNewId().name(), null, diff
+ .getChangeType()));
+ }
+ }
+ Collections.sort(list);
+ } catch (Throwable t) {
+ error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
+ }
+ return list;
+ }
+ /**
* Returns the list of files in the repository on the default branch that
* match one of the specified extensions. This is a CASE-SENSITIVE search.
* If the repository does not exist or is empty, an empty list is returned.
@@ -978,18 +1222,30 @@
}
try {
// resolve branch
- ObjectId branchObject;
+ ObjectId startRange = null;
+ ObjectId endRange;
if (StringUtils.isEmpty(objectId)) {
- branchObject = getDefaultBranch(repository);
+ endRange = getDefaultBranch(repository);
} else {
- branchObject = repository.resolve(objectId);
+ if( objectId.contains("..") ) {
+ // range expression
+ String[] parts = objectId.split("\\.\\.");
+ startRange = repository.resolve(parts[0]);
+ endRange = repository.resolve(parts[1]);
+ } else {
+ // objectid
+ endRange= repository.resolve(objectId);
+ }
}
- if (branchObject == null) {
+ if (endRange == null) {
return list;
}
RevWalk rw = new RevWalk(repository);
- rw.markStart(rw.parseCommit(branchObject));
+ rw.markStart(rw.parseCommit(endRange));
+ if (startRange != null) {
+ rw.markUninteresting(rw.parseCommit(startRange));
+ }
if (!StringUtils.isEmpty(path)) {
TreeFilter filter = AndTreeFilter.create(
PathFilterGroup.createFromStrings(Collections.singleton(path)),
@@ -1689,6 +1945,70 @@
}
/**
+ * this method creates an incremental revision number as a tag according to
+ * the amount of already existing tags, which start with a defined prefix.
+ *
+ * @param repository
+ * @param objectId
+ * @param tagger
+ * @param prefix
+ * @param intPattern
+ * @param message
+ * @return true if operation was successful, otherwise false
+ */
+ public static boolean createIncrementalRevisionTag(Repository repository,
+ String objectId, PersonIdent tagger, String prefix, String intPattern, String message) {
+ boolean result = false;
+ Iterator<Entry<String, Ref>> iterator = repository.getTags().entrySet().iterator();
+ long lastRev = 0;
+ while (iterator.hasNext()) {
+ Entry<String, Ref> entry = iterator.next();
+ if (entry.getKey().startsWith(prefix)) {
+ try {
+ long val = Long.parseLong(entry.getKey().substring(prefix.length()));
+ if (val > lastRev) {
+ lastRev = val;
+ }
+ } catch (Exception e) {
+ // this tag is NOT an incremental revision tag
+ }
+ }
+ }
+ DecimalFormat df = new DecimalFormat(intPattern);
+ result = createTag(repository, objectId, tagger, prefix + df.format((lastRev + 1)), message);
+ return result;
+ }
+
+ /**
+ * creates a tag in a repository
+ *
+ * @param repository
+ * @param objectId, the ref the tag points towards
+ * @param tagger, the person tagging the object
+ * @param tag, the string label
+ * @param message, the string message
+ * @return boolean, true if operation was successful, otherwise false
+ */
+ public static boolean createTag(Repository repository, String objectId, PersonIdent tagger, String tag, String message) {
+ try {
+ Git gitClient = Git.open(repository.getDirectory());
+ TagCommand tagCommand = gitClient.tag();
+ tagCommand.setTagger(tagger);
+ tagCommand.setMessage(message);
+ if (objectId != null) {
+ RevObject revObj = getCommit(repository, objectId);
+ tagCommand.setObjectId(revObj);
+ }
+ tagCommand.setName(tag);
+ Ref call = tagCommand.call();
+ return call != null ? true : false;
+ } catch (Exception e) {
+ error(e, repository, "Failed to create tag {1} in repository {0}", objectId, tag);
+ }
+ return false;
+ }
+
+ /**
* Create an orphaned branch in a repository.
*
* @param repository
--
Gitblit v1.9.1