| | |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | |
| | | import java.util.concurrent.atomic.AtomicLong; |
| | | |
| | | import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; |
| | | import org.eclipse.jgit.api.errors.JGitInternalException; |
| | | import org.eclipse.jgit.dircache.DirCache; |
| | | import org.eclipse.jgit.dircache.DirCacheBuilder; |
| | | import org.eclipse.jgit.dircache.DirCacheEntry; |
| | | import org.eclipse.jgit.events.RefsChangedEvent; |
| | | import org.eclipse.jgit.events.RefsChangedListener; |
| | | import org.eclipse.jgit.internal.JGitText; |
| | | import org.eclipse.jgit.lib.CommitBuilder; |
| | | import org.eclipse.jgit.lib.FileMode; |
| | | import org.eclipse.jgit.lib.ObjectId; |
| | | import org.eclipse.jgit.lib.ObjectInserter; |
| | | import org.eclipse.jgit.lib.PersonIdent; |
| | | import org.eclipse.jgit.lib.Ref; |
| | | import org.eclipse.jgit.lib.RefRename; |
| | | import org.eclipse.jgit.lib.RefUpdate; |
| | | import org.eclipse.jgit.lib.RefUpdate.Result; |
| | | import org.eclipse.jgit.lib.Repository; |
| | | import org.eclipse.jgit.revwalk.RevCommit; |
| | |
| | | import com.gitblit.utils.ArrayUtils; |
| | | import com.gitblit.utils.JGitUtils; |
| | | import com.gitblit.utils.StringUtils; |
| | | import com.google.inject.Inject; |
| | | import com.google.inject.Singleton; |
| | | |
| | | /** |
| | | * Implementation of a ticket service based on an orphan branch. All tickets |
| | |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | @Singleton |
| | | public class BranchTicketService extends ITicketService implements RefsChangedListener { |
| | | |
| | | public static final String BRANCH = "refs/meta/gitblit/tickets"; |
| | |
| | | |
| | | private final Map<String, AtomicLong> lastAssignedId; |
| | | |
| | | @Inject |
| | | public BranchTicketService( |
| | | IRuntimeManager runtimeManager, |
| | | IPluginManager pluginManager, |
| | |
| | | |
| | | @Override |
| | | public BranchTicketService start() { |
| | | log.info("{} started", getClass().getSimpleName()); |
| | | return this; |
| | | } |
| | | |
| | |
| | | log.error("failed to read " + file, e); |
| | | } finally { |
| | | if (rw != null) { |
| | | rw.release(); |
| | | rw.close(); |
| | | } |
| | | } |
| | | return null; |
| | |
| | | Set<String> ignorePaths = new HashSet<String>(); |
| | | ignorePaths.add(file); |
| | | |
| | | for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) { |
| | | for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) { |
| | | builder.add(entry); |
| | | } |
| | | |
| | |
| | | } catch (IOException e) { |
| | | log.error("", e); |
| | | } finally { |
| | | inserter.release(); |
| | | inserter.close(); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the assigned ticket ids. |
| | | * |
| | | * @return the assigned ticket ids |
| | | */ |
| | | @Override |
| | | public synchronized Set<Long> getIds(RepositoryModel repository) { |
| | | Repository db = repositoryManager.getRepository(repository.name); |
| | | try { |
| | | if (getTicketsBranch(db) == null) { |
| | | return Collections.emptySet(); |
| | | } |
| | | Set<Long> ids = new TreeSet<Long>(); |
| | | List<PathModel> paths = JGitUtils.getDocuments(db, Arrays.asList("json"), BRANCH); |
| | | for (PathModel path : paths) { |
| | | String name = path.name.substring(path.name.lastIndexOf('/') + 1); |
| | | if (!JOURNAL.equals(name)) { |
| | | continue; |
| | | } |
| | | String tid = path.path.split("/")[2]; |
| | | long ticketId = Long.parseLong(tid); |
| | | ids.add(ticketId); |
| | | } |
| | | return ids; |
| | | } finally { |
| | | if (db != null) { |
| | | db.close(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Assigns a new ticket id. |
| | | * |
| | | * @param repository |
| | |
| | | } |
| | | AtomicLong lastId = lastAssignedId.get(repository.name); |
| | | if (lastId.get() <= 0) { |
| | | List<PathModel> paths = JGitUtils.getDocuments(db, Arrays.asList("json"), BRANCH); |
| | | for (PathModel path : paths) { |
| | | String name = path.name.substring(path.name.lastIndexOf('/') + 1); |
| | | if (!JOURNAL.equals(name)) { |
| | | continue; |
| | | } |
| | | String tid = path.path.split("/")[2]; |
| | | long ticketId = Long.parseLong(tid); |
| | | if (ticketId > lastId.get()) { |
| | | lastId.set(ticketId); |
| | | Set<Long> ids = getIds(repository); |
| | | for (long id : ids) { |
| | | if (id > lastId.get()) { |
| | | lastId.set(id); |
| | | } |
| | | } |
| | | } |
| | |
| | | ticket.number = ticketId; |
| | | } |
| | | return ticket; |
| | | } finally { |
| | | db.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the journal for the ticket. |
| | | * |
| | | * @param repository |
| | | * @param ticketId |
| | | * @return a journal, if it exists, otherwise null |
| | | */ |
| | | @Override |
| | | protected List<Change> getJournalImpl(RepositoryModel repository, long ticketId) { |
| | | Repository db = repositoryManager.getRepository(repository.name); |
| | | try { |
| | | List<Change> changes = getJournal(db, ticketId); |
| | | if (ArrayUtils.isEmpty(changes)) { |
| | | log.warn("Empty journal for {}:{}", repository, ticketId); |
| | | return null; |
| | | } |
| | | return changes; |
| | | } finally { |
| | | db.close(); |
| | | } |
| | |
| | | ticket.number, db.getDirectory()), t); |
| | | } finally { |
| | | // release the treewalk |
| | | treeWalk.release(); |
| | | if (treeWalk != null) { |
| | | treeWalk.close(); |
| | | } |
| | | } |
| | | } finally { |
| | | db.close(); |
| | |
| | | } |
| | | } |
| | | |
| | | for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) { |
| | | for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) { |
| | | builder.add(entry); |
| | | } |
| | | |
| | | // finish the index |
| | | builder.finish(); |
| | | } finally { |
| | | inserter.release(); |
| | | inserter.close(); |
| | | } |
| | | return newIndex; |
| | | } |
| | | |
| | | /** |
| | | * Returns all tree entries that do not match the ignore paths. |
| | | * |
| | | * @param db |
| | | * @param ignorePaths |
| | | * @param dcBuilder |
| | | * @throws IOException |
| | | */ |
| | | private List<DirCacheEntry> getTreeEntries(Repository db, Collection<String> ignorePaths) throws IOException { |
| | | List<DirCacheEntry> list = new ArrayList<DirCacheEntry>(); |
| | | TreeWalk tw = null; |
| | | try { |
| | | tw = new TreeWalk(db); |
| | | ObjectId treeId = db.resolve(BRANCH + "^{tree}"); |
| | | int hIdx = tw.addTree(treeId); |
| | | tw.setRecursive(true); |
| | | |
| | | while (tw.next()) { |
| | | String path = tw.getPathString(); |
| | | CanonicalTreeParser hTree = null; |
| | | if (hIdx != -1) { |
| | | hTree = tw.getTree(hIdx, CanonicalTreeParser.class); |
| | | } |
| | | if (!ignorePaths.contains(path)) { |
| | | // add all other tree entries |
| | | if (hTree != null) { |
| | | final DirCacheEntry entry = new DirCacheEntry(path); |
| | | entry.setObjectId(hTree.getEntryObjectId()); |
| | | entry.setFileMode(hTree.getEntryFileMode()); |
| | | list.add(entry); |
| | | } |
| | | } |
| | | } |
| | | } finally { |
| | | if (tw != null) { |
| | | tw.release(); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | private boolean commitIndex(Repository db, DirCache index, String author, String message) throws IOException, ConcurrentRefUpdateException { |
| | | final boolean forceCommit = true; |
| | | boolean success = false; |
| | | |
| | | |
| | | ObjectId headId = db.resolve(BRANCH + "^{commit}"); |
| | | if (headId == null) { |
| | | // create the branch |
| | | createTicketsBranch(db); |
| | | headId = db.resolve(BRANCH + "^{commit}"); |
| | | } |
| | | ObjectInserter odi = db.newObjectInserter(); |
| | | try { |
| | | // Create the in-memory index of the new/updated ticket |
| | | ObjectId indexTreeId = index.writeTree(odi); |
| | | |
| | | // Create a commit object |
| | | PersonIdent ident = new PersonIdent(author, "gitblit@localhost"); |
| | | CommitBuilder commit = new CommitBuilder(); |
| | | commit.setAuthor(ident); |
| | | commit.setCommitter(ident); |
| | | commit.setEncoding(Constants.ENCODING); |
| | | commit.setMessage(message); |
| | | commit.setParentId(headId); |
| | | commit.setTreeId(indexTreeId); |
| | | |
| | | // Insert the commit into the repository |
| | | ObjectId commitId = odi.insert(commit); |
| | | odi.flush(); |
| | | |
| | | RevWalk revWalk = new RevWalk(db); |
| | | try { |
| | | RevCommit revCommit = revWalk.parseCommit(commitId); |
| | | RefUpdate ru = db.updateRef(BRANCH); |
| | | ru.setNewObjectId(commitId); |
| | | ru.setExpectedOldObjectId(headId); |
| | | ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); |
| | | Result rc = ru.forceUpdate(); |
| | | switch (rc) { |
| | | case NEW: |
| | | case FORCED: |
| | | case FAST_FORWARD: |
| | | success = true; |
| | | break; |
| | | case REJECTED: |
| | | case LOCK_FAILURE: |
| | | throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, |
| | | ru.getRef(), rc); |
| | | default: |
| | | throw new JGitInternalException(MessageFormat.format( |
| | | JGitText.get().updatingRefFailed, BRANCH, commitId.toString(), |
| | | rc)); |
| | | } |
| | | } finally { |
| | | revWalk.release(); |
| | | } |
| | | } finally { |
| | | odi.release(); |
| | | } |
| | | |
| | | success = JGitUtils.commitIndex(db, BRANCH, index, headId, forceCommit, author, "gitblit@localhost", message); |
| | | |
| | | return success; |
| | | } |
| | | |