Browse Source

Removed unused Issues classes

tags/v1.4.0
James Moger 10 years ago
parent
commit
a04808340a

+ 7
- 130
src/main/java/com/gitblit/LuceneExecutor.java View File

@@ -56,7 +56,6 @@ import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
@@ -86,14 +85,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.SearchObjectType;
import com.gitblit.models.IssueModel;
import com.gitblit.models.IssueModel.Attachment;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SearchResult;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.IssueUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
@@ -109,7 +105,6 @@ public class LuceneExecutor implements Runnable {
private static final int INDEX_VERSION = 5;
private static final String FIELD_OBJECT_TYPE = "type";
private static final String FIELD_ISSUE = "issue";
private static final String FIELD_PATH = "path";
private static final String FIELD_COMMIT = "commit";
private static final String FIELD_BRANCH = "branch";
@@ -120,7 +115,6 @@ public class LuceneExecutor implements Runnable {
private static final String FIELD_DATE = "date";
private static final String FIELD_TAG = "tag";
private static final String FIELD_LABEL = "label";
private static final String FIELD_ATTACHMENT = "attachment";
private static final String CONF_FILE = "lucene.conf";
private static final String LUCENE_DIR = "lucene";
@@ -475,9 +469,8 @@ public class LuceneExecutor implements Runnable {
&& branch.equals(defaultBranch)) {
// indexing "default" branch
indexBranch = true;
} else if (IssueUtils.GB_ISSUES.equals(branch)) {
// skip the GB_ISSUES branch because it is indexed later
// note: this is different than updateIndex
} else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) {
// skip Gitblit internal branches
indexBranch = false;
} else {
// normal explicit branch check
@@ -617,19 +610,6 @@ public class LuceneExecutor implements Runnable {
// finished
reader.release();
// this repository has a gb-issues branch, index all issues
if (IssueUtils.getIssuesBranch(repository) != null) {
List<IssueModel> issues = IssueUtils.getIssues(repository, null);
if (issues.size() > 0) {
result.branchCount += 1;
}
for (IssueModel issue : issues) {
result.issueCount++;
Document doc = createDocument(issue);
writer.addDocument(doc);
}
}
// commit all changes and reset the searcher
config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION);
config.save();
@@ -722,55 +702,6 @@ public class LuceneExecutor implements Runnable {
return result;
}
/**
* Incrementally update the index with the specified issue for the
* repository.
*
* @param repositoryName
* @param issue
* @return true, if successful
*/
public boolean index(String repositoryName, IssueModel issue) {
try {
// delete the old issue from the index, if exists
deleteIssue(repositoryName, issue.id);
Document doc = createDocument(issue);
return index(repositoryName, doc);
} catch (Exception e) {
logger.error(MessageFormat.format("Error while indexing issue {0} in {1}", issue.id, repositoryName), e);
}
return false;
}
/**
* Delete an issue from the repository index.
*
* @param repositoryName
* @param issueId
* @throws Exception
* @return true, if deleted, false if no record was deleted
*/
private boolean deleteIssue(String repositoryName, String issueId) throws Exception {
BooleanQuery query = new BooleanQuery();
Term objectTerm = new Term(FIELD_OBJECT_TYPE, SearchObjectType.issue.name());
query.add(new TermQuery(objectTerm), Occur.MUST);
Term issueidTerm = new Term(FIELD_ISSUE, issueId);
query.add(new TermQuery(issueidTerm), Occur.MUST);
IndexWriter writer = getIndexWriter(repositoryName);
int numDocsBefore = writer.numDocs();
writer.deleteDocuments(query);
writer.commit();
int numDocsAfter = writer.numDocs();
if (numDocsBefore == numDocsAfter) {
logger.debug(MessageFormat.format("no records found to delete {0}", query.toString()));
return false;
} else {
logger.debug(MessageFormat.format("deleted {0} records with {1}", numDocsBefore - numDocsAfter, query.toString()));
return true;
}
}
/**
* Delete a blob from the specified branch of the repository index.
*
@@ -870,10 +801,9 @@ public class LuceneExecutor implements Runnable {
&& branch.equals(defaultBranch)) {
// indexing "default" branch
indexBranch = true;
} else if (IssueUtils.GB_ISSUES.equals(branch)) {
// update issues modified on the GB_ISSUES branch
// note: this is different than reindex
indexBranch = true;
} else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) {
// ignore internal Gitblit branches
indexBranch = false;
} else {
// normal explicit branch check
indexBranch = model.indexedBranches.contains(branch.getName());
@@ -904,35 +834,11 @@ public class LuceneExecutor implements Runnable {
result.branchCount += 1;
}
// track the issue ids that we have already indexed
Set<String> indexedIssues = new TreeSet<String>();
// reverse the list of commits so we start with the first commit
Collections.reverse(revs);
for (RevCommit commit : revs) {
if (IssueUtils.GB_ISSUES.equals(branch)) {
// only index an issue once during updateIndex
String issueId = commit.getShortMessage().substring(2).trim();
if (indexedIssues.contains(issueId)) {
continue;
}
indexedIssues.add(issueId);
IssueModel issue = IssueUtils.getIssue(repository, issueId);
if (issue == null) {
// issue was deleted, remove from index
if (!deleteIssue(model.name, issueId)) {
logger.error(MessageFormat.format("Failed to delete issue {0} from Lucene index!", issueId));
}
} else {
// issue was updated
index(model.name, issue);
result.issueCount++;
}
} else {
// index a commit
result.add(index(model.name, repository, branchName, commit));
}
// index a commit
result.add(index(model.name, repository, branchName, commit));
}
// update the config
@@ -958,34 +864,6 @@ public class LuceneExecutor implements Runnable {
return result;
}
/**
* Creates a Lucene document from an issue.
*
* @param issue
* @return a Lucene document
*/
private Document createDocument(IssueModel issue) {
Document doc = new Document();
doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.issue.name(), Store.YES,
Field.Index.NOT_ANALYZED));
doc.add(new Field(FIELD_ISSUE, issue.id, Store.YES, Index.ANALYZED));
doc.add(new Field(FIELD_BRANCH, IssueUtils.GB_ISSUES, Store.YES, Index.ANALYZED));
doc.add(new Field(FIELD_DATE, DateTools.dateToString(issue.created, Resolution.MINUTE),
Store.YES, Field.Index.NO));
doc.add(new Field(FIELD_AUTHOR, issue.reporter, Store.YES, Index.ANALYZED));
List<String> attachments = new ArrayList<String>();
for (Attachment attachment : issue.getAttachments()) {
attachments.add(attachment.name.toLowerCase());
}
doc.add(new Field(FIELD_ATTACHMENT, StringUtils.flattenStrings(attachments), Store.YES,
Index.ANALYZED));
doc.add(new Field(FIELD_SUMMARY, issue.summary, Store.YES, Index.ANALYZED));
doc.add(new Field(FIELD_CONTENT, issue.toString(), Store.YES, Index.ANALYZED));
doc.add(new Field(FIELD_LABEL, StringUtils.flattenStrings(issue.getLabels()), Store.YES,
Index.ANALYZED));
return doc;
}
/**
* Creates a Lucene document for a commit
*
@@ -1042,7 +920,6 @@ public class LuceneExecutor implements Runnable {
result.type = SearchObjectType.fromName(doc.get(FIELD_OBJECT_TYPE));
result.branch = doc.get(FIELD_BRANCH);
result.commitId = doc.get(FIELD_COMMIT);
result.issueId = doc.get(FIELD_ISSUE);
result.path = doc.get(FIELD_PATH);
if (doc.get(FIELD_TAG) != null) {
result.tags = StringUtils.getStringsFromValue(doc.get(FIELD_TAG));

+ 0
- 532
src/main/java/com/gitblit/models/IssueModel.java View File

@@ -1,532 +0,0 @@
/*
* Copyright 2012 gitblit.com.
*
* 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.gitblit.models;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
* The Gitblit Issue model, its component classes, and enums.
*
* @author James Moger
*
*/
public class IssueModel implements Serializable, Comparable<IssueModel> {
private static final long serialVersionUID = 1L;
public String id;
public Type type;
public Status status;
public Priority priority;
public Date created;
public String summary;
public String description;
public String reporter;
public String owner;
public String milestone;
public List<Change> changes;
public IssueModel() {
// the first applied change set the date appropriately
created = new Date(0);
type = Type.Defect;
status = Status.New;
priority = Priority.Medium;
changes = new ArrayList<Change>();
}
public String getStatus() {
String s = status.toString();
if (!StringUtils.isEmpty(owner))
s += " (" + owner + ")";
return s;
}
public boolean hasLabel(String label) {
return getLabels().contains(label);
}
public List<String> getLabels() {
List<String> list = new ArrayList<String>();
String labels = null;
for (Change change : changes) {
if (change.hasField(Field.Labels)) {
labels = change.getString(Field.Labels);
}
}
if (!StringUtils.isEmpty(labels)) {
list.addAll(StringUtils.getStringsFromValue(labels, " "));
}
return list;
}
public Attachment getAttachment(String name) {
Attachment attachment = null;
for (Change change : changes) {
if (change.hasAttachments()) {
Attachment a = change.getAttachment(name);
if (a != null) {
attachment = a;
}
}
}
return attachment;
}
public List<Attachment> getAttachments() {
List<Attachment> list = new ArrayList<Attachment>();
for (Change change : changes) {
if (change.hasAttachments()) {
list.addAll(change.attachments);
}
}
return list;
}
public void applyChange(Change change) {
if (changes.size() == 0) {
// first change created the issue
created = change.created;
}
changes.add(change);
if (change.hasFieldChanges()) {
for (FieldChange fieldChange : change.fieldChanges) {
switch (fieldChange.field) {
case Id:
id = fieldChange.value.toString();
break;
case Type:
type = IssueModel.Type.fromObject(fieldChange.value);
break;
case Status:
status = IssueModel.Status.fromObject(fieldChange.value);
break;
case Priority:
priority = IssueModel.Priority.fromObject(fieldChange.value);
break;
case Summary:
summary = fieldChange.value.toString();
break;
case Description:
description = fieldChange.value.toString();
break;
case Reporter:
reporter = fieldChange.value.toString();
break;
case Owner:
owner = fieldChange.value.toString();
break;
case Milestone:
milestone = fieldChange.value.toString();
break;
}
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("issue ");
sb.append(id.substring(0, 8));
sb.append(" (" + summary + ")\n");
for (Change change : changes) {
sb.append(change);
sb.append('\n');
}
return sb.toString();
}
@Override
public int compareTo(IssueModel o) {
return o.created.compareTo(created);
}
@Override
public boolean equals(Object o) {
if (o instanceof IssueModel)
return id.equals(((IssueModel) o).id);
return super.equals(o);
}
@Override
public int hashCode() {
return id.hashCode();
}
public static class Change implements Serializable, Comparable<Change> {
private static final long serialVersionUID = 1L;
public final Date created;
public final String author;
public String id;
public char code;
public Comment comment;
public Set<FieldChange> fieldChanges;
public Set<Attachment> attachments;
public Change(String author) {
this.created = new Date((System.currentTimeMillis() / 1000) * 1000);
this.author = author;
this.id = StringUtils.getSHA1(created.toString() + author);
}
public boolean hasComment() {
return comment != null && !comment.deleted;
}
public void comment(String text) {
comment = new Comment(text);
comment.id = StringUtils.getSHA1(created.toString() + author + text);
}
public boolean hasAttachments() {
return !ArrayUtils.isEmpty(attachments);
}
public void addAttachment(Attachment attachment) {
if (attachments == null) {
attachments = new LinkedHashSet<Attachment>();
}
attachments.add(attachment);
}
public Attachment getAttachment(String name) {
for (Attachment attachment : attachments) {
if (attachment.name.equalsIgnoreCase(name)) {
return attachment;
}
}
return null;
}
public boolean hasField(Field field) {
return !StringUtils.isEmpty(getString(field));
}
public boolean hasFieldChanges() {
return !ArrayUtils.isEmpty(fieldChanges);
}
public Object getField(Field field) {
if (fieldChanges != null) {
for (FieldChange fieldChange : fieldChanges) {
if (fieldChange.field == field) {
return fieldChange.value;
}
}
}
return null;
}
public void setField(Field field, Object value) {
FieldChange fieldChange = new FieldChange(field, value);
if (fieldChanges == null) {
fieldChanges = new LinkedHashSet<FieldChange>();
}
fieldChanges.add(fieldChange);
}
public String getString(Field field) {
Object value = getField(field);
if (value == null) {
return null;
}
return value.toString();
}
@Override
public int compareTo(Change c) {
return created.compareTo(c.created);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof Change) {
return id.equals(((Change) o).id);
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(new TimeUtils().timeAgo(created));
switch (code) {
case '+':
sb.append(" created by ");
break;
default:
if (hasComment()) {
sb.append(" commented on by ");
} else {
sb.append(" changed by ");
}
}
sb.append(author).append(" - ");
if (hasComment()) {
if (comment.deleted) {
sb.append("(deleted) ");
}
sb.append(comment.text).append(" ");
}
if (hasFieldChanges()) {
switch (code) {
case '+':
break;
default:
for (FieldChange fieldChange : fieldChanges) {
sb.append("\n ");
sb.append(fieldChange);
}
break;
}
}
return sb.toString();
}
}
public static class Comment implements Serializable {
private static final long serialVersionUID = 1L;
public String text;
public String id;
public boolean deleted;
Comment(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}
public static class FieldChange implements Serializable {
private static final long serialVersionUID = 1L;
public final Field field;
public final Object value;
FieldChange(Field field, Object value) {
this.field = field;
this.value = value;
}
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof FieldChange) {
return field.equals(((FieldChange) o).field);
}
return false;
}
@Override
public String toString() {
return field + ": " + value;
}
}
public static class Attachment implements Serializable {
private static final long serialVersionUID = 1L;
public final String name;
public String id;
public long size;
public byte[] content;
public boolean deleted;
public Attachment(String name) {
this.name = name;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof Attachment) {
return name.equalsIgnoreCase(((Attachment) o).name);
}
return false;
}
@Override
public String toString() {
return name;
}
}
public static enum Field {
Id, Summary, Description, Reporter, Owner, Type, Status, Priority, Milestone, Component, Labels;
}
public static enum Type {
Defect, Enhancement, Task, Review, Other;
public static Type fromObject(Object o) {
if (o instanceof Type) {
// cast and return
return (Type) o;
} else if (o instanceof String) {
// find by name
for (Type type : values()) {
String str = o.toString();
if (type.toString().equalsIgnoreCase(str)) {
return type;
}
}
} else if (o instanceof Number) {
// by ordinal
int id = ((Number) o).intValue();
if (id >= 0 && id < values().length) {
return values()[id];
}
}
return null;
}
}
public static enum Priority {
Low, Medium, High, Critical;
public static Priority fromObject(Object o) {
if (o instanceof Priority) {
// cast and return
return (Priority) o;
} else if (o instanceof String) {
// find by name
for (Priority priority : values()) {
String str = o.toString();
if (priority.toString().equalsIgnoreCase(str)) {
return priority;
}
}
} else if (o instanceof Number) {
// by ordinal
int id = ((Number) o).intValue();
if (id >= 0 && id < values().length) {
return values()[id];
}
}
return null;
}
}
public static enum Status {
New, Accepted, Started, Review, Queued, Testing, Done, Fixed, WontFix, Duplicate, Invalid;
public static Status fromObject(Object o) {
if (o instanceof Status) {
// cast and return
return (Status) o;
} else if (o instanceof String) {
// find by name
for (Status status : values()) {
String str = o.toString();
if (status.toString().equalsIgnoreCase(str)) {
return status;
}
}
} else if (o instanceof Number) {
// by ordinal
int id = ((Number) o).intValue();
if (id >= 0 && id < values().length) {
return values()[id];
}
}
return null;
}
public boolean atLeast(Status status) {
return ordinal() >= status.ordinal();
}
public boolean exceeds(Status status) {
return ordinal() > status.ordinal();
}
public boolean isClosed() {
return ordinal() >= Done.ordinal();
}
public Status next() {
switch (this) {
case New:
return Started;
case Accepted:
return Started;
case Started:
return Testing;
case Review:
return Testing;
case Queued:
return Testing;
case Testing:
return Done;
}
return Accepted;
}
}
}

+ 0
- 829
src/main/java/com/gitblit/utils/IssueUtils.java View File

@@ -1,829 +0,0 @@
/*
* Copyright 2012 gitblit.com.
*
* 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.gitblit.utils;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
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.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
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.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
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.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.IssueModel;
import com.gitblit.models.IssueModel.Attachment;
import com.gitblit.models.IssueModel.Change;
import com.gitblit.models.IssueModel.Field;
import com.gitblit.models.IssueModel.Status;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JsonUtils.ExcludeField;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
/**
* Utility class for reading Gitblit issues.
*
* @author James Moger
*
*/
public class IssueUtils {
public static interface IssueFilter {
public abstract boolean accept(IssueModel issue);
}
public static final String GB_ISSUES = "refs/gitblit/issues";
static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
/**
* Log an error message and exception.
*
* @param t
* @param repository
* if repository is not null it MUST be the {0} parameter in the
* pattern.
* @param pattern
* @param objects
*/
private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
List<Object> parameters = new ArrayList<Object>();
if (objects != null && objects.length > 0) {
for (Object o : objects) {
parameters.add(o);
}
}
if (repository != null) {
parameters.add(0, repository.getDirectory().getAbsolutePath());
}
LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
}
/**
* Returns a RefModel for the gb-issues branch in the repository. If the
* branch can not be found, null is returned.
*
* @param repository
* @return a refmodel for the gb-issues branch or null
*/
public static RefModel getIssuesBranch(Repository repository) {
List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT);
for (RefModel ref : refs) {
if (ref.reference.getName().equals(GB_ISSUES)) {
return ref;
}
}
return null;
}
/**
* Returns all the issues in the repository. Querying issues from the
* repository requires deserializing all changes for all issues. This is an
* expensive process and not recommended. Issues should be indexed by Lucene
* and queries should be executed against that index.
*
* @param repository
* @param filter
* optional issue filter to only return matching results
* @return a list of issues
*/
public static List<IssueModel> getIssues(Repository repository, IssueFilter filter) {
List<IssueModel> list = new ArrayList<IssueModel>();
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
return list;
}
// Collect the set of all issue paths
Set<String> issuePaths = new HashSet<String>();
final TreeWalk tw = new TreeWalk(repository);
try {
RevCommit head = JGitUtils.getCommit(repository, GB_ISSUES);
tw.addTree(head.getTree());
tw.setRecursive(false);
while (tw.next()) {
if (tw.getDepth() < 2 && tw.isSubtree()) {
tw.enterSubtree();
if (tw.getDepth() == 2) {
issuePaths.add(tw.getPathString());
}
}
}
} catch (IOException e) {
error(e, repository, "{0} failed to query issues");
} finally {
tw.release();
}
// Build each issue and optionally filter out unwanted issues
for (String issuePath : issuePaths) {
RevWalk rw = new RevWalk(repository);
try {
RevCommit start = rw.parseCommit(repository.resolve(GB_ISSUES));
rw.markStart(start);
} catch (Exception e) {
error(e, repository, "Failed to find {1} in {0}", GB_ISSUES);
}
TreeFilter treeFilter = AndTreeFilter.create(
PathFilterGroup.createFromStrings(issuePath), TreeFilter.ANY_DIFF);
rw.setTreeFilter(treeFilter);
Iterator<RevCommit> revlog = rw.iterator();
List<RevCommit> commits = new ArrayList<RevCommit>();
while (revlog.hasNext()) {
commits.add(revlog.next());
}
// release the revwalk
rw.release();
if (commits.size() == 0) {
LOGGER.warn("Failed to find changes for issue " + issuePath);
continue;
}
// sort by commit order, first commit first
Collections.reverse(commits);
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (RevCommit commit : commits) {
if (!first) {
sb.append(',');
}
String message = commit.getFullMessage();
// commit message is formatted: C ISSUEID\n\nJSON
// C is an single char commit code
// ISSUEID is an SHA-1 hash
String json = message.substring(43);
sb.append(json);
first = false;
}
sb.append(']');
// Deserialize the JSON array as a Collection<Change>, this seems
// slightly faster than deserializing each change by itself.
Collection<Change> changes = JsonUtils.fromJsonString(sb.toString(),
new TypeToken<Collection<Change>>() {
}.getType());
// create an issue object form the changes
IssueModel issue = buildIssue(changes, true);
// add the issue, conditionally, to the list
if (filter == null) {
list.add(issue);
} else {
if (filter.accept(issue)) {
list.add(issue);
}
}
}
// sort the issues by creation
Collections.sort(list);
return list;
}
/**
* Retrieves the specified issue from the repository with all changes
* applied to build the effective issue.
*
* @param repository
* @param issueId
* @return an issue, if it exists, otherwise null
*/
public static IssueModel getIssue(Repository repository, String issueId) {
return getIssue(repository, issueId, true);
}
/**
* Retrieves the specified issue from the repository.
*
* @param repository
* @param issueId
* @param effective
* if true, the effective issue is built by processing comment
* changes, deletions, etc. if false, the raw issue is built
* without consideration for comment changes, deletions, etc.
* @return an issue, if it exists, otherwise null
*/
public static IssueModel getIssue(Repository repository, String issueId, boolean effective) {
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
return null;
}
if (StringUtils.isEmpty(issueId)) {
return null;
}
String issuePath = getIssuePath(issueId);
// Collect all changes as JSON array from commit messages
List<RevCommit> commits = JGitUtils.getRevLog(repository, GB_ISSUES, issuePath, 0, -1);
// sort by commit order, first commit first
Collections.reverse(commits);
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (RevCommit commit : commits) {
if (!first) {
sb.append(',');
}
String message = commit.getFullMessage();
// commit message is formatted: C ISSUEID\n\nJSON
// C is an single char commit code
// ISSUEID is an SHA-1 hash
String json = message.substring(43);
sb.append(json);
first = false;
}
sb.append(']');
// Deserialize the JSON array as a Collection<Change>, this seems
// slightly faster than deserializing each change by itself.
Collection<Change> changes = JsonUtils.fromJsonString(sb.toString(),
new TypeToken<Collection<Change>>() {
}.getType());
// create an issue object and apply the changes to it
IssueModel issue = buildIssue(changes, effective);
return issue;
}
/**
* Builds an issue from a set of changes.
*
* @param changes
* @param effective
* if true, the effective issue is built which accounts for
* comment changes, comment deletions, etc. if false, the raw
* issue is built.
* @return an issue
*/
private static IssueModel buildIssue(Collection<Change> changes, boolean effective) {
IssueModel issue;
if (effective) {
List<Change> effectiveChanges = new ArrayList<Change>();
Map<String, Change> comments = new HashMap<String, Change>();
for (Change change : changes) {
if (change.comment != null) {
if (comments.containsKey(change.comment.id)) {
Change original = comments.get(change.comment.id);
Change clone = DeepCopier.copy(original);
clone.comment.text = change.comment.text;
clone.comment.deleted = change.comment.deleted;
int idx = effectiveChanges.indexOf(original);
effectiveChanges.remove(original);
effectiveChanges.add(idx, clone);
comments.put(clone.comment.id, clone);
} else {
effectiveChanges.add(change);
comments.put(change.comment.id, change);
}
} else {
effectiveChanges.add(change);
}
}
// effective issue
issue = new IssueModel();
for (Change change : effectiveChanges) {
issue.applyChange(change);
}
} else {
// raw issue
issue = new IssueModel();
for (Change change : changes) {
issue.applyChange(change);
}
}
return issue;
}
/**
* Retrieves the specified attachment from an issue.
*
* @param repository
* @param issueId
* @param filename
* @return an attachment, if found, null otherwise
*/
public static Attachment getIssueAttachment(Repository repository, String issueId,
String filename) {
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
return null;
}
if (StringUtils.isEmpty(issueId)) {
return null;
}
// deserialize the issue model so that we have the attachment metadata
IssueModel issue = getIssue(repository, issueId, true);
Attachment attachment = issue.getAttachment(filename);
// attachment not found
if (attachment == null) {
return null;
}
// retrieve the attachment content
String issuePath = getIssuePath(issueId);
RevTree tree = JGitUtils.getCommit(repository, GB_ISSUES).getTree();
byte[] content = JGitUtils
.getByteContent(repository, tree, issuePath + "/" + attachment.id, false);
attachment.content = content;
attachment.size = content.length;
return attachment;
}
/**
* Creates an issue in the gb-issues branch of the repository. The branch is
* automatically created if it does not already exist. Your change must
* include an author, summary, and description, at a minimum. If your change
* does not have those minimum requirements a RuntimeException will be
* thrown.
*
* @param repository
* @param change
* @return true if successful
*/
public static IssueModel createIssue(Repository repository, Change change) {
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
JGitUtils.createOrphanBranch(repository, GB_ISSUES, null);
}
if (StringUtils.isEmpty(change.author)) {
throw new RuntimeException("Must specify a change author!");
}
if (!change.hasField(Field.Summary)) {
throw new RuntimeException("Must specify a summary!");
}
if (!change.hasField(Field.Description)) {
throw new RuntimeException("Must specify a description!");
}
change.setField(Field.Reporter, change.author);
String issueId = StringUtils.getSHA1(change.created.toString() + change.author
+ change.getString(Field.Summary) + change.getField(Field.Description));
change.setField(Field.Id, issueId);
change.code = '+';
boolean success = commit(repository, issueId, change);
if (success) {
return getIssue(repository, issueId, false);
}
return null;
}
/**
* Updates an issue in the gb-issues branch of the repository.
*
* @param repository
* @param issueId
* @param change
* @return true if successful
*/
public static boolean updateIssue(Repository repository, String issueId, Change change) {
boolean success = false;
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
throw new RuntimeException("gb-issues branch does not exist!");
}
if (change == null) {
throw new RuntimeException("change can not be null!");
}
if (StringUtils.isEmpty(change.author)) {
throw new RuntimeException("must specify a change author!");
}
// determine update code
// default update code is '=' for a general change
change.code = '=';
if (change.hasField(Field.Status)) {
Status status = Status.fromObject(change.getField(Field.Status));
if (status.isClosed()) {
// someone closed the issue
change.code = 'x';
}
}
success = commit(repository, issueId, change);
return success;
}
/**
* Deletes an issue from the repository.
*
* @param repository
* @param issueId
* @return true if successful
*/
public static boolean deleteIssue(Repository repository, String issueId, String author) {
boolean success = false;
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
throw new RuntimeException(GB_ISSUES + " does not exist!");
}
if (StringUtils.isEmpty(issueId)) {
throw new RuntimeException("must specify an issue id!");
}
String issuePath = getIssuePath(issueId);
String message = "- " + issueId;
try {
ObjectId headId = repository.resolve(GB_ISSUES + "^{commit}");
ObjectInserter odi = repository.newObjectInserter();
try {
// Create the in-memory index of the new/updated issue
DirCache index = DirCache.newInCore();
DirCacheBuilder dcBuilder = index.builder();
// Traverse HEAD to add all other paths
TreeWalk treeWalk = new TreeWalk(repository);
int hIdx = -1;
if (headId != null)
hIdx = treeWalk.addTree(new RevWalk(repository).parseTree(headId));
treeWalk.setRecursive(true);
while (treeWalk.next()) {
String path = treeWalk.getPathString();
CanonicalTreeParser hTree = null;
if (hIdx != -1)
hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
if (!path.startsWith(issuePath)) {
// add entries from HEAD for all other paths
if (hTree != null) {
// create a new DirCacheEntry with data retrieved
// from HEAD
final DirCacheEntry dcEntry = new DirCacheEntry(path);
dcEntry.setObjectId(hTree.getEntryObjectId());
dcEntry.setFileMode(hTree.getEntryFileMode());
// add to temporary in-core index
dcBuilder.add(dcEntry);
}
}
}
// release the treewalk
treeWalk.release();
// finish temporary in-core index used for this commit
dcBuilder.finish();
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.CHARACTER_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(repository);
try {
RevCommit revCommit = revWalk.parseCommit(commitId);
RefUpdate ru = repository.updateRef(GB_ISSUES);
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, GB_ISSUES, commitId.toString(),
rc));
}
} finally {
revWalk.release();
}
} finally {
odi.release();
}
} catch (Throwable t) {
error(t, repository, "Failed to delete issue {1} to {0}", issueId);
}
return success;
}
/**
* Changes the text of an issue comment.
*
* @param repository
* @param issue
* @param change
* the change with the comment to change
* @param author
* the author of the revision
* @param comment
* the revised comment
* @return true, if the change was successful
*/
public static boolean changeComment(Repository repository, IssueModel issue, Change change,
String author, String comment) {
Change revision = new Change(author);
revision.comment(comment);
revision.comment.id = change.comment.id;
return updateIssue(repository, issue.id, revision);
}
/**
* Deletes a comment from an issue.
*
* @param repository
* @param issue
* @param change
* the change with the comment to delete
* @param author
* @return true, if the deletion was successful
*/
public static boolean deleteComment(Repository repository, IssueModel issue, Change change,
String author) {
Change deletion = new Change(author);
deletion.comment(change.comment.text);
deletion.comment.id = change.comment.id;
deletion.comment.deleted = true;
return updateIssue(repository, issue.id, deletion);
}
/**
* Commit a change to the repository. Each issue is composed on changes.
* Issues are built from applying the changes in the order they were
* committed to the repository. The changes are actually specified in the
* commit messages and not in the RevTrees which allows for clean,
* distributed merging.
*
* @param repository
* @param issueId
* @param change
* @return true, if the change was committed
*/
private static boolean commit(Repository repository, String issueId, Change change) {
boolean success = false;
try {
// assign ids to new attachments
// attachments are stored by an SHA1 id
if (change.hasAttachments()) {
for (Attachment attachment : change.attachments) {
if (!ArrayUtils.isEmpty(attachment.content)) {
byte[] prefix = (change.created.toString() + change.author).getBytes();
byte[] bytes = new byte[prefix.length + attachment.content.length];
System.arraycopy(prefix, 0, bytes, 0, prefix.length);
System.arraycopy(attachment.content, 0, bytes, prefix.length,
attachment.content.length);
attachment.id = "attachment-" + StringUtils.getSHA1(bytes);
}
}
}
// serialize the change as json
// exclude any attachment from json serialization
Gson gson = JsonUtils.gson(new ExcludeField(
"com.gitblit.models.IssueModel$Attachment.content"));
String json = gson.toJson(change);
// include the json change in the commit message
String issuePath = getIssuePath(issueId);
String message = change.code + " " + issueId + "\n\n" + json;
// Create a commit file. This is required for a proper commit and
// ensures we can retrieve the commit log of the issue path.
//
// This file is NOT serialized as part of the Change object.
switch (change.code) {
case '+': {
// New Issue.
Attachment placeholder = new Attachment("issue");
placeholder.id = placeholder.name;
placeholder.content = "DO NOT REMOVE".getBytes(Constants.CHARACTER_ENCODING);
change.addAttachment(placeholder);
break;
}
default: {
// Update Issue.
String changeId = StringUtils.getSHA1(json);
Attachment placeholder = new Attachment("change-" + changeId);
placeholder.id = placeholder.name;
placeholder.content = "REMOVABLE".getBytes(Constants.CHARACTER_ENCODING);
change.addAttachment(placeholder);
break;
}
}
ObjectId headId = repository.resolve(GB_ISSUES + "^{commit}");
ObjectInserter odi = repository.newObjectInserter();
try {
// Create the in-memory index of the new/updated issue
DirCache index = createIndex(repository, headId, issuePath, change);
ObjectId indexTreeId = index.writeTree(odi);
// Create a commit object
PersonIdent ident = new PersonIdent(change.author, "gitblit@localhost");
CommitBuilder commit = new CommitBuilder();
commit.setAuthor(ident);
commit.setCommitter(ident);
commit.setEncoding(Constants.CHARACTER_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(repository);
try {
RevCommit revCommit = revWalk.parseCommit(commitId);
RefUpdate ru = repository.updateRef(GB_ISSUES);
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, GB_ISSUES, commitId.toString(),
rc));
}
} finally {
revWalk.release();
}
} finally {
odi.release();
}
} catch (Throwable t) {
error(t, repository, "Failed to commit issue {1} to {0}", issueId);
}
return success;
}
/**
* Returns the issue path. This follows the same scheme as Git's object
* store path where the first two characters of the hash id are the root
* folder with the remaining characters as a subfolder within that folder.
*
* @param issueId
* @return the root path of the issue content on the gb-issues branch
*/
static String getIssuePath(String issueId) {
return issueId.substring(0, 2) + "/" + issueId.substring(2);
}
/**
* Creates an in-memory index of the issue change.
*
* @param repo
* @param headId
* @param change
* @return an in-memory index
* @throws IOException
*/
private static DirCache createIndex(Repository repo, ObjectId headId, String issuePath,
Change change) throws IOException {
DirCache inCoreIndex = DirCache.newInCore();
DirCacheBuilder dcBuilder = inCoreIndex.builder();
ObjectInserter inserter = repo.newObjectInserter();
Set<String> ignorePaths = new TreeSet<String>();
try {
// Add any attachments to the temporary index
if (change.hasAttachments()) {
for (Attachment attachment : change.attachments) {
// build a path name for the attachment and mark as ignored
String path = issuePath + "/" + attachment.id;
ignorePaths.add(path);
// create an index entry for this attachment
final DirCacheEntry dcEntry = new DirCacheEntry(path);
dcEntry.setLength(attachment.content.length);
dcEntry.setLastModified(change.created.getTime());
dcEntry.setFileMode(FileMode.REGULAR_FILE);
// insert object
dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, attachment.content));
// add to temporary in-core index
dcBuilder.add(dcEntry);
}
}
// Traverse HEAD to add all other paths
TreeWalk treeWalk = new TreeWalk(repo);
int hIdx = -1;
if (headId != null)
hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
treeWalk.setRecursive(true);
while (treeWalk.next()) {
String path = treeWalk.getPathString();
CanonicalTreeParser hTree = null;
if (hIdx != -1)
hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
if (!ignorePaths.contains(path)) {
// add entries from HEAD for all other paths
if (hTree != null) {
// create a new DirCacheEntry with data retrieved from
// HEAD
final DirCacheEntry dcEntry = new DirCacheEntry(path);
dcEntry.setObjectId(hTree.getEntryObjectId());
dcEntry.setFileMode(hTree.getEntryFileMode());
// add to temporary in-core index
dcBuilder.add(dcEntry);
}
}
}
// release the treewalk
treeWalk.release();
// finish temporary in-core index used for this commit
dcBuilder.finish();
} finally {
inserter.release();
}
return inCoreIndex;
}
}

+ 1
- 1
src/test/java/com/gitblit/tests/GitBlitSuite.java View File

@@ -59,7 +59,7 @@ import com.gitblit.utils.JGitUtils;
MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
DiffUtilsTest.class, MetricUtilsTest.class, X509UtilsTest.class,
GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class,
GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdUserServiceTest.class,
ModelUtilsTest.class, JnaUtilsTest.class })
public class GitBlitSuite {

+ 0
- 231
src/test/java/com/gitblit/tests/IssuesTest.java View File

@@ -1,231 +0,0 @@
/*
* Copyright 2012 gitblit.com.
*
* 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.gitblit.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.bouncycastle.util.Arrays;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
import com.gitblit.LuceneExecutor;
import com.gitblit.models.IssueModel;
import com.gitblit.models.IssueModel.Attachment;
import com.gitblit.models.IssueModel.Change;
import com.gitblit.models.IssueModel.Field;
import com.gitblit.models.IssueModel.Priority;
import com.gitblit.models.IssueModel.Status;
import com.gitblit.models.SearchResult;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.IssueUtils;
import com.gitblit.utils.IssueUtils.IssueFilter;
/**
* Tests the mechanics of distributed issue management on the gb-issues branch.
*
* @author James Moger
*
*/
public class IssuesTest {
@Test
public void testLifecycle() throws Exception {
Repository repository = GitBlitSuite.getIssuesTestRepository();
String name = FileUtils.getRelativePath(GitBlitSuite.REPOSITORIES, repository.getDirectory());
// create and insert an issue
Change c1 = newChange("testCreation() " + Long.toHexString(System.currentTimeMillis()));
IssueModel issue = IssueUtils.createIssue(repository, c1);
assertNotNull(issue.id);
// retrieve issue and compare
IssueModel constructed = IssueUtils.getIssue(repository, issue.id);
compare(issue, constructed);
assertEquals(1, constructed.changes.size());
// C1: create the issue
c1 = newChange("testUpdates() " + Long.toHexString(System.currentTimeMillis()));
issue = IssueUtils.createIssue(repository, c1);
assertNotNull(issue.id);
constructed = IssueUtils.getIssue(repository, issue.id);
compare(issue, constructed);
assertEquals(1, constructed.changes.size());
// C2: set owner
Change c2 = new Change("C2");
c2.comment("I'll fix this");
c2.setField(Field.Owner, c2.author);
assertTrue(IssueUtils.updateIssue(repository, issue.id, c2));
constructed = IssueUtils.getIssue(repository, issue.id);
assertEquals(2, constructed.changes.size());
assertEquals(c2.author, constructed.owner);
// C3: add a note
Change c3 = new Change("C3");
c3.comment("yeah, this is working");
assertTrue(IssueUtils.updateIssue(repository, issue.id, c3));
constructed = IssueUtils.getIssue(repository, issue.id);
assertEquals(3, constructed.changes.size());
// C4: add attachment
Change c4 = new Change("C4");
Attachment a = newAttachment();
c4.addAttachment(a);
assertTrue(IssueUtils.updateIssue(repository, issue.id, c4));
Attachment a1 = IssueUtils.getIssueAttachment(repository, issue.id, a.name);
assertEquals(a.content.length, a1.content.length);
assertTrue(Arrays.areEqual(a.content, a1.content));
// C5: close the issue
Change c5 = new Change("C5");
c5.comment("closing issue");
c5.setField(Field.Status, Status.Fixed);
assertTrue(IssueUtils.updateIssue(repository, issue.id, c5));
// retrieve issue again
constructed = IssueUtils.getIssue(repository, issue.id);
assertEquals(5, constructed.changes.size());
assertTrue(constructed.status.isClosed());
List<IssueModel> allIssues = IssueUtils.getIssues(repository, null);
List<IssueModel> openIssues = IssueUtils.getIssues(repository, new IssueFilter() {
@Override
public boolean accept(IssueModel issue) {
return !issue.status.isClosed();
}
});
List<IssueModel> closedIssues = IssueUtils.getIssues(repository, new IssueFilter() {
@Override
public boolean accept(IssueModel issue) {
return issue.status.isClosed();
}
});
assertTrue(allIssues.size() > 0);
assertEquals(1, openIssues.size());
assertEquals(1, closedIssues.size());
// build a new Lucene index
LuceneExecutor lucene = new LuceneExecutor(null, GitBlitSuite.REPOSITORIES);
lucene.deleteIndex(name);
for (IssueModel anIssue : allIssues) {
lucene.index(name, anIssue);
}
List<SearchResult> hits = lucene.search("working", 1, 10, name);
assertTrue(hits.size() == 1);
// reindex an issue
issue = allIssues.get(0);
Change change = new Change("reindex");
change.comment("this is a test of reindexing an issue");
IssueUtils.updateIssue(repository, issue.id, change);
issue = IssueUtils.getIssue(repository, issue.id);
lucene.index(name, issue);
hits = lucene.search("working", 1, 10, name);
assertTrue(hits.size() == 1);
// delete all issues
for (IssueModel anIssue : allIssues) {
assertTrue(IssueUtils.deleteIssue(repository, anIssue.id, "D"));
}
lucene.close();
repository.close();
}
@Test
public void testChangeComment() throws Exception {
Repository repository = GitBlitSuite.getIssuesTestRepository();
// C1: create the issue
Change c1 = newChange("testChangeComment() " + Long.toHexString(System.currentTimeMillis()));
IssueModel issue = IssueUtils.createIssue(repository, c1);
assertNotNull(issue.id);
assertTrue(issue.changes.get(0).hasComment());
assertTrue(IssueUtils.changeComment(repository, issue, c1, "E1", "I changed the comment"));
issue = IssueUtils.getIssue(repository, issue.id);
assertTrue(issue.changes.get(0).hasComment());
assertEquals("I changed the comment", issue.changes.get(0).comment.text);
assertTrue(IssueUtils.deleteIssue(repository, issue.id, "D"));
repository.close();
}
@Test
public void testDeleteComment() throws Exception {
Repository repository = GitBlitSuite.getIssuesTestRepository();
// C1: create the issue
Change c1 = newChange("testDeleteComment() " + Long.toHexString(System.currentTimeMillis()));
IssueModel issue = IssueUtils.createIssue(repository, c1);
assertNotNull(issue.id);
assertTrue(issue.changes.get(0).hasComment());
assertTrue(IssueUtils.deleteComment(repository, issue, c1, "D1"));
issue = IssueUtils.getIssue(repository, issue.id);
assertEquals(1, issue.changes.size());
assertFalse(issue.changes.get(0).hasComment());
issue = IssueUtils.getIssue(repository, issue.id, false);
assertEquals(2, issue.changes.size());
assertTrue(issue.changes.get(0).hasComment());
assertFalse(issue.changes.get(1).hasComment());
assertTrue(IssueUtils.deleteIssue(repository, issue.id, "D"));
repository.close();
}
private Change newChange(String summary) {
Change change = new Change("C1");
change.setField(Field.Summary, summary);
change.setField(Field.Description, "this is my description");
change.setField(Field.Priority, Priority.High);
change.setField(Field.Labels, "helpdesk");
change.comment("my comment");
return change;
}
private Attachment newAttachment() {
Attachment attachment = new Attachment(Long.toHexString(System.currentTimeMillis())
+ ".txt");
attachment.content = new byte[] { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a };
return attachment;
}
private void compare(IssueModel issue, IssueModel constructed) {
assertEquals(issue.id, constructed.id);
assertEquals(issue.reporter, constructed.reporter);
assertEquals(issue.owner, constructed.owner);
assertEquals(issue.summary, constructed.summary);
assertEquals(issue.description, constructed.description);
assertEquals(issue.created, constructed.created);
assertTrue(issue.hasLabel("helpdesk"));
}
}

Loading…
Cancel
Save