Merged issues/lucene branch
6 files added
5 files modified
| | |
| | | <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-1.2.0.201112221803-r.jar" sourcepath="ext/org.eclipse.jgit.http.server-1.2.0.201112221803-r-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/groovy-all-1.8.5.jar" sourcepath="ext/groovy-all-1.8.5-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/jetty-ajp-7.4.3.v20110701.jar" sourcepath="ext/jetty-ajp-7.4.3.v20110701-sources.jar"/> |
| | | <classpathentry kind="lib" path="ext/lucene-core-3.5.0.jar" sourcepath="ext/lucene-core-3.5.0-sources.jar"/> |
| | | <classpathentry kind="output" path="bin"/> |
| | | </classpath> |
| | |
| | | downloadFromApache(MavenObject.GSON, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.MAIL, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.GROOVY, BuildType.RUNTIME);
|
| | | downloadFromApache(MavenObject.LUCENE, BuildType.RUNTIME);
|
| | |
|
| | | downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
|
| | | downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
|
| | |
| | | downloadFromApache(MavenObject.GSON, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.MAIL, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.GROOVY, BuildType.COMPILETIME);
|
| | | downloadFromApache(MavenObject.LUCENE, BuildType.COMPILETIME);
|
| | |
|
| | | downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
|
| | | downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
|
| | |
| | | "1.8.5", 6143000, 2290000, 4608000, "3be3914c49ca7d8e8afb29a7772a74c30a1f1b28",
|
| | | "1435cc8c90e3a91e5fee7bb53e83aad96e93aeb7", "5a214b52286523f9e2a4b5fed526506c763fa6f1");
|
| | |
|
| | | public static final MavenObject LUCENE = new MavenObject("lucene", "org/apache/lucene", "lucene-core",
|
| | | "3.5.0", 1470000, 1347000, 3608000, "90ff0731fafb05c01fee4f2247140d56e9c30a3b",
|
| | | "0757113199f9c8c18c678c96d61c2c4160b9baa6", "19f8e80e5e7f6ec88a41d4f63495994692e31bf1");
|
| | |
|
| | | public final String name;
|
| | | public final String group;
|
| | | public final String artifact;
|
New file |
| | |
| | | /*
|
| | | * 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(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;
|
| | | }
|
| | | }
|
| | | }
|
New file |
| | |
| | | package com.gitblit.models;
|
| | |
|
| | | import java.io.Serializable;
|
| | | import java.util.Date;
|
| | | import java.util.List;
|
| | |
|
| | | import com.gitblit.utils.LuceneUtils.ObjectType;
|
| | |
|
| | | /**
|
| | | * Model class that represents a search result.
|
| | | * |
| | | * @author James Moger
|
| | | * |
| | | */
|
| | | public class SearchResult implements Serializable {
|
| | |
|
| | | private static final long serialVersionUID = 1L;
|
| | |
|
| | | public float score;
|
| | |
|
| | | public Date date;
|
| | |
|
| | | public String author;
|
| | |
|
| | | public String committer;
|
| | |
|
| | | public String summary;
|
| | | |
| | | public String repository;
|
| | | |
| | | public String branch;
|
| | |
|
| | | public String id;
|
| | |
|
| | | public List<String> labels;
|
| | |
|
| | | public ObjectType type;
|
| | |
|
| | | public SearchResult() {
|
| | | }
|
| | |
|
| | | @Override
|
| | | public String toString() {
|
| | | return score + " : " + type.name() + " : " + repository + " : " + id + " (" + branch + ")";
|
| | | }
|
| | | } |
New file |
| | |
| | | /*
|
| | | * 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.JGitText;
|
| | | 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.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/heads/gb-issues";
|
| | |
|
| | | static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.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) {
|
| | | return JGitUtils.getBranch(repository, "gb-issues");
|
| | | }
|
| | |
|
| | | /**
|
| | | * 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);
|
| | | 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 issue
|
| | | * @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 branch 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 issue
|
| | | * @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;
|
| | | }
|
| | | } |
| | |
| | | import java.text.MessageFormat;
|
| | | import java.util.ArrayList;
|
| | | import java.util.Arrays;
|
| | | import java.util.Collection;
|
| | | import java.util.Collections;
|
| | | import java.util.Date;
|
| | | import java.util.HashMap;
|
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * Returns the list of files in the repository 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.
|
| | | * 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.
|
| | | *
|
| | | * @param repository
|
| | | * @param extensions
|
| | | * @return list of files in repository with a matching extension
|
| | | */
|
| | | public static List<PathModel> getDocuments(Repository repository, List<String> extensions) {
|
| | | return getDocuments(repository, extensions, null);
|
| | | }
|
| | |
|
| | | /**
|
| | | * Returns the list of files in the repository in the specified commit 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.
|
| | | * |
| | | * @param repository
|
| | | * @param extensions
|
| | | * @param objectId
|
| | | * @return list of files in repository with a matching extension
|
| | | */
|
| | | public static List<PathModel> getDocuments(Repository repository, List<String> extensions,
|
| | | String objectId) {
|
| | | List<PathModel> list = new ArrayList<PathModel>();
|
| | | if (!hasCommits(repository)) {
|
| | | return list;
|
| | | }
|
| | | RevCommit commit = getCommit(repository, null);
|
| | | RevCommit commit = getCommit(repository, objectId);
|
| | | final TreeWalk tw = new TreeWalk(repository);
|
| | | try {
|
| | | tw.addTree(commit.getTree());
|
| | | if (extensions != null && extensions.size() > 0) {
|
| | | Collection<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
|
| | | List<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
|
| | | for (String extension : extensions) {
|
| | | if (extension.charAt(0) == '.') {
|
| | | suffixFilters.add(PathSuffixFilter.create("\\" + extension));
|
| | |
| | | suffixFilters.add(PathSuffixFilter.create("\\." + extension));
|
| | | }
|
| | | }
|
| | | TreeFilter filter = OrTreeFilter.create(suffixFilters);
|
| | | TreeFilter filter;
|
| | | if (suffixFilters.size() == 1) {
|
| | | filter = suffixFilters.get(0);
|
| | | } else {
|
| | | filter = OrTreeFilter.create(suffixFilters);
|
| | | }
|
| | | tw.setFilter(filter);
|
| | | tw.setRecursive(true);
|
| | | }
|
| | |
| | |
|
| | | // Create a tree object to reference from a commit
|
| | | TreeFormatter tree = new TreeFormatter();
|
| | | tree.append("NEWBRANCH", FileMode.REGULAR_FILE, blobId);
|
| | | tree.append(".branch", FileMode.REGULAR_FILE, blobId);
|
| | | ObjectId treeId = odi.insert(tree);
|
| | |
|
| | | // Create a commit object
|
| | |
| | | import com.gitblit.GitBlitException.UnknownRequestException;
|
| | | import com.gitblit.models.RepositoryModel;
|
| | | import com.gitblit.models.UserModel;
|
| | | import com.google.gson.ExclusionStrategy;
|
| | | import com.google.gson.FieldAttributes;
|
| | | import com.google.gson.Gson;
|
| | | import com.google.gson.GsonBuilder;
|
| | | import com.google.gson.JsonDeserializationContext;
|
| | |
| | | UnauthorizedException {
|
| | | return retrieveJson(url, type, null, null);
|
| | | }
|
| | | |
| | |
|
| | | /**
|
| | | * Reads a gson object from the specified url.
|
| | | *
|
| | |
| | | */
|
| | | public static String retrieveJsonString(String url, String username, char[] password)
|
| | | throws IOException {
|
| | | try { |
| | | try {
|
| | | URLConnection conn = ConnectionUtils.openReadConnection(url, username, password);
|
| | | InputStream is = conn.getInputStream();
|
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(is, ConnectionUtils.CHARSET));
|
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(is,
|
| | | ConnectionUtils.CHARSET));
|
| | | StringBuilder json = new StringBuilder();
|
| | | char[] buffer = new char[4096];
|
| | | int len = 0;
|
| | |
| | |
|
| | | // build custom gson instance with GMT date serializer/deserializer
|
| | | // http://code.google.com/p/google-gson/issues/detail?id=281
|
| | | private static Gson gson() {
|
| | | public static Gson gson(ExclusionStrategy... strategies) {
|
| | | GsonBuilder builder = new GsonBuilder();
|
| | | builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
|
| | | builder.setPrettyPrinting();
|
| | | if (!ArrayUtils.isEmpty(strategies)) {
|
| | | builder.setExclusionStrategies(strategies);
|
| | | }
|
| | | return builder.create();
|
| | | }
|
| | |
|
| | |
| | | JsonDeserializationContext jsonDeserializationContext) {
|
| | | try {
|
| | | synchronized (dateFormat) {
|
| | | return dateFormat.parse(jsonElement.getAsString());
|
| | | Date date = dateFormat.parse(jsonElement.getAsString()); |
| | | return new Date((date.getTime() / 1000) * 1000);
|
| | | }
|
| | | } catch (ParseException e) {
|
| | | throw new JsonSyntaxException(jsonElement.getAsString(), e);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | public static class ExcludeField implements ExclusionStrategy {
|
| | |
|
| | | private Class<?> c;
|
| | | private String fieldName;
|
| | |
|
| | | public ExcludeField(String fqfn) throws SecurityException, NoSuchFieldException,
|
| | | ClassNotFoundException {
|
| | | this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
|
| | | this.fieldName = fqfn.substring(fqfn.lastIndexOf(".") + 1);
|
| | | }
|
| | |
|
| | | public boolean shouldSkipClass(Class<?> arg0) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | public boolean shouldSkipField(FieldAttributes f) {
|
| | | return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
|
| | | }
|
| | | }
|
| | | }
|
New file |
| | |
| | | package com.gitblit.utils;
|
| | |
|
| | | import java.io.ByteArrayOutputStream;
|
| | | import java.io.File;
|
| | | import java.io.IOException;
|
| | | import java.io.InputStream;
|
| | | import java.text.ParseException;
|
| | | import java.util.ArrayList;
|
| | | import java.util.Arrays;
|
| | | import java.util.HashMap;
|
| | | import java.util.LinkedHashSet;
|
| | | import java.util.List;
|
| | | import java.util.Map;
|
| | | import java.util.Set;
|
| | | import java.util.TreeSet;
|
| | | import java.util.concurrent.ConcurrentHashMap;
|
| | |
|
| | | import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
| | | import org.apache.lucene.document.DateTools;
|
| | | import org.apache.lucene.document.DateTools.Resolution;
|
| | | import org.apache.lucene.document.Document;
|
| | | import org.apache.lucene.document.Field;
|
| | | import org.apache.lucene.document.Field.Index;
|
| | | import org.apache.lucene.document.Field.Store;
|
| | | import org.apache.lucene.index.IndexReader;
|
| | | import org.apache.lucene.index.IndexWriter;
|
| | | import org.apache.lucene.index.IndexWriterConfig;
|
| | | import org.apache.lucene.index.IndexWriterConfig.OpenMode;
|
| | | import org.apache.lucene.index.MultiReader;
|
| | | import org.apache.lucene.index.Term;
|
| | | import org.apache.lucene.queryParser.QueryParser;
|
| | | import org.apache.lucene.search.BooleanClause.Occur;
|
| | | 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.TopScoreDocCollector;
|
| | | import org.apache.lucene.store.Directory;
|
| | | import org.apache.lucene.store.FSDirectory;
|
| | | import org.apache.lucene.util.Version;
|
| | | import org.eclipse.jgit.diff.DiffEntry.ChangeType;
|
| | | import org.eclipse.jgit.lib.Constants;
|
| | | import org.eclipse.jgit.lib.FileMode;
|
| | | import org.eclipse.jgit.lib.ObjectId;
|
| | | import org.eclipse.jgit.lib.ObjectLoader;
|
| | | import org.eclipse.jgit.lib.Repository;
|
| | | import org.eclipse.jgit.revwalk.RevCommit;
|
| | | import org.eclipse.jgit.revwalk.RevObject;
|
| | | import org.eclipse.jgit.revwalk.RevWalk;
|
| | | import org.eclipse.jgit.treewalk.TreeWalk;
|
| | |
|
| | | 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.SearchResult;
|
| | |
|
| | | /**
|
| | | * A collection of utility methods for indexing and querying a Lucene repository
|
| | | * index.
|
| | | * |
| | | * @author James Moger
|
| | | * |
| | | */
|
| | | public class LuceneUtils {
|
| | |
|
| | | /**
|
| | | * The types of objects that can be indexed and queried.
|
| | | */
|
| | | public static enum ObjectType {
|
| | | commit, blob, issue;
|
| | |
|
| | | static ObjectType fromName(String name) {
|
| | | for (ObjectType value : values()) {
|
| | | if (value.name().equals(name)) {
|
| | | return value;
|
| | | }
|
| | | }
|
| | | return null;
|
| | | }
|
| | | }
|
| | |
|
| | | private static final Version LUCENE_VERSION = Version.LUCENE_35;
|
| | |
|
| | | private static final String FIELD_OBJECT_TYPE = "type";
|
| | | private static final String FIELD_OBJECT_ID = "id";
|
| | | private static final String FIELD_BRANCH = "branch";
|
| | | private static final String FIELD_REPOSITORY = "repository";
|
| | | private static final String FIELD_SUMMARY = "summary";
|
| | | private static final String FIELD_CONTENT = "content";
|
| | | private static final String FIELD_AUTHOR = "author";
|
| | | private static final String FIELD_COMMITTER = "committer";
|
| | | private static final String FIELD_DATE = "date";
|
| | | private static final String FIELD_LABEL = "label";
|
| | | private static final String FIELD_ATTACHMENT = "attachment";
|
| | |
|
| | | private static Set<String> excludedExtensions = new TreeSet<String>(
|
| | | Arrays.asList("7z", "arc", "arj", "bin", "bmp", "dll", "doc",
|
| | | "docx", "exe", "gif", "gz", "jar", "jpg", "lib", "lzh", |
| | | "odg", "pdf", "ppt", "png", "so", "swf", "xcf", "xls",
|
| | | "xlsx", "zip"));
|
| | |
|
| | | private static Set<String> excludedBranches = new TreeSet<String>(
|
| | | Arrays.asList("/refs/heads/gb-issues"));
|
| | |
|
| | | private static final Map<File, IndexSearcher> SEARCHERS = new ConcurrentHashMap<File, IndexSearcher>();
|
| | | private static final Map<File, IndexWriter> WRITERS = new ConcurrentHashMap<File, IndexWriter>();
|
| | |
|
| | | /**
|
| | | * Returns the name of the repository.
|
| | | * |
| | | * @param repository
|
| | | * @return the repository name
|
| | | */
|
| | | private static String getName(Repository repository) {
|
| | | if (repository.isBare()) {
|
| | | return repository.getDirectory().getName();
|
| | | } else {
|
| | | return repository.getDirectory().getParentFile().getName();
|
| | | }
|
| | | }
|
| | | |
| | | /**
|
| | | * Deletes the Lucene index for the specified repository.
|
| | | * |
| | | * @param repository
|
| | | * @return true, if successful
|
| | | */
|
| | | public static boolean deleteIndex(Repository repository) {
|
| | | try {
|
| | | File luceneIndex = new File(repository.getDirectory(), "lucene");
|
| | | if (luceneIndex.exists()) {
|
| | | org.eclipse.jgit.util.FileUtils.delete(luceneIndex,
|
| | | org.eclipse.jgit.util.FileUtils.RECURSIVE);
|
| | | }
|
| | | return true;
|
| | | } catch (IOException e) {
|
| | | throw new RuntimeException(e);
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | | * This completely indexes the repository and will destroy any existing
|
| | | * index.
|
| | | * |
| | | * @param repository
|
| | | * @return true if the indexing has succeeded
|
| | | */
|
| | | public static boolean index(Repository repository) {
|
| | | try {
|
| | | String repositoryName = getName(repository);
|
| | | Set<String> indexedCommits = new TreeSet<String>();
|
| | | IndexWriter writer = getIndexWriter(repository, true);
|
| | | // build a quick lookup of tags
|
| | | Map<String, List<String>> tags = new HashMap<String, List<String>>();
|
| | | for (RefModel tag : JGitUtils.getTags(repository, false, -1)) {
|
| | | if (!tags.containsKey(tag.getObjectId())) {
|
| | | tags.put(tag.getReferencedObjectId().getName(), new ArrayList<String>());
|
| | | }
|
| | | tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName);
|
| | | }
|
| | |
|
| | | // walk through each branch
|
| | | List<RefModel> branches = JGitUtils.getLocalBranches(repository, true, -1);
|
| | | for (RefModel branch : branches) {
|
| | | if (excludedBranches.contains(branch.getName())) {
|
| | | continue;
|
| | | }
|
| | | String branchName = branch.getName();
|
| | | RevWalk revWalk = new RevWalk(repository);
|
| | | RevCommit rev = revWalk.parseCommit(branch.getObjectId());
|
| | |
|
| | | // index the blob contents of the tree
|
| | | ByteArrayOutputStream os = new ByteArrayOutputStream();
|
| | | byte[] tmp = new byte[32767];
|
| | | TreeWalk treeWalk = new TreeWalk(repository);
|
| | | treeWalk.addTree(rev.getTree());
|
| | | treeWalk.setRecursive(true);
|
| | | String revDate = DateTools.timeToString(rev.getCommitTime() * 1000L,
|
| | | Resolution.MINUTE);
|
| | | while (treeWalk.next()) {
|
| | | Document doc = new Document();
|
| | | doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.blob.name(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_BRANCH, branchName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_OBJECT_ID, treeWalk.getPathString(), Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_DATE, revDate, Store.YES, Index.NO));
|
| | | doc.add(new Field(FIELD_AUTHOR, rev.getAuthorIdent().getName(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_COMMITTER, rev.getCommitterIdent().getName(),
|
| | | Store.YES, Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_LABEL, branch.getName(), Store.YES, Index.ANALYZED));
|
| | |
|
| | | // determine extension to compare to the extension
|
| | | // blacklist
|
| | | String ext = null;
|
| | | String name = treeWalk.getPathString().toLowerCase();
|
| | | if (name.indexOf('.') > -1) {
|
| | | ext = name.substring(name.lastIndexOf('.') + 1);
|
| | | }
|
| | |
|
| | | if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) {
|
| | | // read the blob content
|
| | | ObjectId entid = treeWalk.getObjectId(0);
|
| | | FileMode entmode = treeWalk.getFileMode(0);
|
| | | RevObject ro = revWalk.lookupAny(entid, entmode.getObjectType());
|
| | | revWalk.parseBody(ro);
|
| | | ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
|
| | | InputStream in = ldr.openStream();
|
| | | os.reset();
|
| | | int n = 0;
|
| | | while ((n = in.read(tmp)) > 0) {
|
| | | os.write(tmp, 0, n);
|
| | | }
|
| | | in.close();
|
| | | byte[] content = os.toByteArray();
|
| | | String str = new String(content, "UTF-8");
|
| | | doc.add(new Field(FIELD_CONTENT, str, Store.NO, Index.ANALYZED));
|
| | | writer.addDocument(doc);
|
| | | }
|
| | | }
|
| | |
|
| | | os.close();
|
| | | treeWalk.release();
|
| | |
|
| | | // index the head commit object
|
| | | String head = rev.getId().getName();
|
| | | if (indexedCommits.add(head)) {
|
| | | Document doc = createDocument(rev, tags.get(head));
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_BRANCH, branchName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | writer.addDocument(doc);
|
| | | }
|
| | |
|
| | | // traverse the log and index the previous commit objects
|
| | | revWalk.markStart(rev);
|
| | | while ((rev = revWalk.next()) != null) {
|
| | | String hash = rev.getId().getName();
|
| | | if (indexedCommits.add(hash)) {
|
| | | Document doc = createDocument(rev, tags.get(hash));
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_BRANCH, branchName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | writer.addDocument(doc);
|
| | | }
|
| | | }
|
| | |
|
| | | // finished
|
| | | revWalk.dispose();
|
| | | }
|
| | |
|
| | | // this repository has a gb-issues branch, index all issues
|
| | | if (IssueUtils.getIssuesBranch(repository) != null) {
|
| | | List<IssueModel> issues = IssueUtils.getIssues(repository, null);
|
| | | for (IssueModel issue : issues) {
|
| | | Document doc = createDocument(issue);
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | writer.addDocument(doc);
|
| | | }
|
| | | }
|
| | |
|
| | | // commit all changes and reset the searcher
|
| | | resetIndexSearcher(repository);
|
| | | writer.commit();
|
| | | return true;
|
| | | } catch (Exception e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Incrementally update the index with the specified commit for the
|
| | | * repository.
|
| | | * |
| | | * @param repository
|
| | | * @param branch
|
| | | * the fully qualified branch name (e.g. refs/heads/master)
|
| | | * @param commit
|
| | | * @return true, if successful
|
| | | */
|
| | | public static boolean index(Repository repository, String branch, RevCommit commit) {
|
| | | try { |
| | | if (excludedBranches.contains(branch)) {
|
| | | if (IssueUtils.GB_ISSUES.equals(branch)) {
|
| | | // index an issue
|
| | | String issueId = commit.getShortMessage().substring(2).trim();
|
| | | IssueModel issue = IssueUtils.getIssue(repository, issueId);
|
| | | return index(repository, issue, true);
|
| | | }
|
| | | return false;
|
| | | }
|
| | | List<PathChangeModel> changedPaths = JGitUtils.getFilesInCommit(repository, commit);
|
| | | String repositoryName = getName(repository);
|
| | | String revDate = DateTools.timeToString(commit.getCommitTime() * 1000L,
|
| | | Resolution.MINUTE);
|
| | | IndexWriter writer = getIndexWriter(repository, false);
|
| | | for (PathChangeModel path : changedPaths) {
|
| | | // delete the indexed blob
|
| | | writer.deleteDocuments(new Term(FIELD_OBJECT_TYPE, ObjectType.blob.name()),
|
| | | new Term(FIELD_BRANCH, branch),
|
| | | new Term(FIELD_OBJECT_ID, path.path));
|
| | | |
| | | // re-index the blob
|
| | | if (!ChangeType.DELETE.equals(path.changeType)) {
|
| | | Document doc = new Document();
|
| | | doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.blob.name(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_OBJECT_ID, path.path, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_DATE, revDate, Store.YES, Index.NO));
|
| | | doc.add(new Field(FIELD_AUTHOR, commit.getAuthorIdent().getName(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_COMMITTER, commit.getCommitterIdent().getName(),
|
| | | Store.YES, Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_LABEL, branch, Store.YES, Index.ANALYZED));
|
| | |
|
| | | // determine extension to compare to the extension
|
| | | // blacklist
|
| | | String ext = null;
|
| | | String name = path.name.toLowerCase();
|
| | | if (name.indexOf('.') > -1) {
|
| | | ext = name.substring(name.lastIndexOf('.') + 1);
|
| | | }
|
| | |
|
| | | if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) {
|
| | | // read the blob content
|
| | | String str = JGitUtils.getStringContent(repository, |
| | | commit.getTree(), path.path);
|
| | | doc.add(new Field(FIELD_CONTENT, str, Store.NO, Index.ANALYZED));
|
| | | writer.addDocument(doc);
|
| | | }
|
| | | }
|
| | | }
|
| | | writer.commit();
|
| | | |
| | | Document doc = createDocument(commit, null);
|
| | | return index(repository, doc);
|
| | | } catch (Exception e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Incrementally update the index with the specified issue for the
|
| | | * repository.
|
| | | * |
| | | * @param repository
|
| | | * @param issue
|
| | | * @param reindex
|
| | | * if true, the old index entry for this issue will be deleted.
|
| | | * This is only appropriate for pre-existing/indexed issues.
|
| | | * @return true, if successful
|
| | | */
|
| | | public static boolean index(Repository repository, IssueModel issue, boolean reindex) {
|
| | | try {
|
| | | Document doc = createDocument(issue);
|
| | | if (reindex) {
|
| | | // delete the old issue from the index, if exists
|
| | | IndexWriter writer = getIndexWriter(repository, false);
|
| | | writer.deleteDocuments(new Term(FIELD_OBJECT_TYPE, ObjectType.issue.name()),
|
| | | new Term(FIELD_OBJECT_ID, String.valueOf(issue.id)));
|
| | | writer.commit();
|
| | | }
|
| | | return index(repository, doc);
|
| | | } catch (Exception e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Creates a Lucene document from an issue.
|
| | | * |
| | | * @param issue
|
| | | * @return a Lucene document
|
| | | */
|
| | | private static Document createDocument(IssueModel issue) {
|
| | | Document doc = new Document();
|
| | | doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.issue.name(), Store.YES,
|
| | | Field.Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_OBJECT_ID, issue.id, Store.YES, Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_BRANCH, IssueUtils.GB_ISSUES, Store.YES, Index.NOT_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.NOT_ANALYZED_NO_NORMS));
|
| | | 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.NO, 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
|
| | | * |
| | | * @param commit
|
| | | * @param tags
|
| | | * @return a Lucene document
|
| | | */
|
| | | private static Document createDocument(RevCommit commit, List<String> tags) {
|
| | | Document doc = new Document();
|
| | | doc.add(new Field(FIELD_OBJECT_TYPE, ObjectType.commit.name(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_OBJECT_ID, commit.getName(), Store.YES, Index.NOT_ANALYZED));
|
| | | doc.add(new Field(FIELD_DATE, DateTools.timeToString(commit.getCommitTime() * 1000L,
|
| | | Resolution.MINUTE), Store.YES, Index.NO));
|
| | | doc.add(new Field(FIELD_AUTHOR, commit.getCommitterIdent().getName(), Store.YES,
|
| | | Index.NOT_ANALYZED_NO_NORMS));
|
| | | doc.add(new Field(FIELD_SUMMARY, commit.getShortMessage(), Store.YES, Index.ANALYZED));
|
| | | doc.add(new Field(FIELD_CONTENT, commit.getFullMessage(), Store.NO, Index.ANALYZED));
|
| | | if (!ArrayUtils.isEmpty(tags)) {
|
| | | if (!ArrayUtils.isEmpty(tags)) {
|
| | | doc.add(new Field(FIELD_LABEL, StringUtils.flattenStrings(tags), Store.YES,
|
| | | Index.ANALYZED));
|
| | | }
|
| | | }
|
| | | return doc;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Incrementally index an object for the repository.
|
| | | * |
| | | * @param repository
|
| | | * @param doc
|
| | | * @return true, if successful
|
| | | */
|
| | | private static boolean index(Repository repository, Document doc) {
|
| | | try {
|
| | | String repositoryName = getName(repository);
|
| | | doc.add(new Field(FIELD_REPOSITORY, repositoryName, Store.YES,
|
| | | Index.NOT_ANALYZED));
|
| | | IndexWriter writer = getIndexWriter(repository, false);
|
| | | writer.addDocument(doc);
|
| | | resetIndexSearcher(repository);
|
| | | writer.commit();
|
| | | return true;
|
| | | } catch (Exception e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
| | | private static SearchResult createSearchResult(Document doc, float score) throws ParseException {
|
| | | SearchResult result = new SearchResult();
|
| | | result.score = score;
|
| | | result.date = DateTools.stringToDate(doc.get(FIELD_DATE));
|
| | | result.summary = doc.get(FIELD_SUMMARY);
|
| | | result.author = doc.get(FIELD_AUTHOR);
|
| | | result.committer = doc.get(FIELD_COMMITTER);
|
| | | result.type = ObjectType.fromName(doc.get(FIELD_OBJECT_TYPE));
|
| | | result.repository = doc.get(FIELD_REPOSITORY);
|
| | | result.branch = doc.get(FIELD_BRANCH);
|
| | | result.id = doc.get(FIELD_OBJECT_ID);
|
| | | if (doc.get(FIELD_LABEL) != null) {
|
| | | result.labels = StringUtils.getStringsFromValue(doc.get(FIELD_LABEL));
|
| | | }
|
| | | return result;
|
| | | }
|
| | |
|
| | | private static void resetIndexSearcher(Repository repository) throws IOException {
|
| | | IndexSearcher searcher = SEARCHERS.get(repository.getDirectory());
|
| | | if (searcher != null) {
|
| | | SEARCHERS.remove(repository.getDirectory());
|
| | | searcher.close();
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | | * Gets an index searcher for the repository.
|
| | | * |
| | | * @param repository
|
| | | * @return
|
| | | * @throws IOException
|
| | | */
|
| | | private static IndexSearcher getIndexSearcher(Repository repository) throws IOException {
|
| | | IndexSearcher searcher = SEARCHERS.get(repository.getDirectory());
|
| | | if (searcher == null) {
|
| | | IndexWriter writer = getIndexWriter(repository, false);
|
| | | searcher = new IndexSearcher(IndexReader.open(writer, true));
|
| | | SEARCHERS.put(repository.getDirectory(), searcher);
|
| | | }
|
| | | return searcher;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Gets an index writer for the repository. The index will be created if it
|
| | | * does not already exist or if forceCreate is specified.
|
| | | * |
| | | * @param repository
|
| | | * @param forceCreate
|
| | | * @return an IndexWriter
|
| | | * @throws IOException
|
| | | */
|
| | | private static IndexWriter getIndexWriter(Repository repository, boolean forceCreate)
|
| | | throws IOException {
|
| | | IndexWriter indexWriter = WRITERS.get(repository.getDirectory());
|
| | | File indexFolder = new File(repository.getDirectory(), "lucene");
|
| | | Directory directory = FSDirectory.open(indexFolder);
|
| | | if (forceCreate || !indexFolder.exists()) {
|
| | | // if the writer is going to blow away the existing index and create
|
| | | // a new one then it should not be cached. instead, close any open
|
| | | // writer, create a new one, and return.
|
| | | if (indexWriter != null) {
|
| | | indexWriter.close();
|
| | | indexWriter = null;
|
| | | WRITERS.remove(repository.getDirectory());
|
| | | }
|
| | | indexFolder.mkdirs();
|
| | | IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, new StandardAnalyzer(
|
| | | LUCENE_VERSION));
|
| | | config.setOpenMode(OpenMode.CREATE);
|
| | | IndexWriter writer = new IndexWriter(directory, config);
|
| | | writer.close();
|
| | | }
|
| | |
|
| | | if (indexWriter == null) {
|
| | | IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, new StandardAnalyzer(
|
| | | LUCENE_VERSION));
|
| | | config.setOpenMode(OpenMode.APPEND);
|
| | | indexWriter = new IndexWriter(directory, config);
|
| | | WRITERS.put(repository.getDirectory(), indexWriter);
|
| | | }
|
| | | return indexWriter;
|
| | | }
|
| | |
|
| | | /**
|
| | | * Searches the specified repositories for the given text or query
|
| | | * |
| | | * @param text
|
| | | * if the text is null or empty, null is returned
|
| | | * @param maximumHits
|
| | | * the maximum number of hits to collect
|
| | | * @param repositories
|
| | | * a list of repositories to search. if no repositories are
|
| | | * specified null is returned.
|
| | | * @return a list of SearchResults in order from highest to the lowest score
|
| | | * |
| | | */
|
| | | public static List<SearchResult> search(String text, int maximumHits,
|
| | | Repository... repositories) {
|
| | | if (StringUtils.isEmpty(text)) {
|
| | | return null;
|
| | | }
|
| | | if (repositories.length == 0) {
|
| | | return null;
|
| | | }
|
| | | Set<SearchResult> results = new LinkedHashSet<SearchResult>();
|
| | | StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION);
|
| | | try {
|
| | | // default search checks summary and content
|
| | | BooleanQuery query = new BooleanQuery();
|
| | | QueryParser qp;
|
| | | qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer);
|
| | | qp.setAllowLeadingWildcard(true);
|
| | | query.add(qp.parse(text), Occur.SHOULD);
|
| | |
|
| | | qp = new QueryParser(LUCENE_VERSION, FIELD_CONTENT, analyzer);
|
| | | qp.setAllowLeadingWildcard(true);
|
| | | query.add(qp.parse(text), Occur.SHOULD);
|
| | |
|
| | | IndexSearcher searcher;
|
| | | if (repositories.length == 1) {
|
| | | // single repository search
|
| | | searcher = getIndexSearcher(repositories[0]);
|
| | | } else {
|
| | | // multiple repository search
|
| | | List<IndexReader> readers = new ArrayList<IndexReader>();
|
| | | for (Repository repository : repositories) {
|
| | | IndexSearcher repositoryIndex = getIndexSearcher(repository);
|
| | | readers.add(repositoryIndex.getIndexReader());
|
| | | } |
| | | IndexReader [] rdrs = readers.toArray(new IndexReader[readers.size()]);
|
| | | MultiReader reader = new MultiReader(rdrs); |
| | | searcher = new IndexSearcher(reader);
|
| | | }
|
| | | Query rewrittenQuery = searcher.rewrite(query);
|
| | | TopScoreDocCollector collector = TopScoreDocCollector.create(maximumHits, true);
|
| | | searcher.search(rewrittenQuery, collector);
|
| | | ScoreDoc[] hits = collector.topDocs().scoreDocs;
|
| | | for (int i = 0; i < hits.length; i++) {
|
| | | int docId = hits[i].doc;
|
| | | Document doc = searcher.doc(docId);
|
| | | SearchResult result = createSearchResult(doc, hits[i].score);
|
| | | results.add(result);
|
| | | }
|
| | | } catch (Exception e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | return new ArrayList<SearchResult>(results);
|
| | | } |
| | |
|
| | | /**
|
| | | * Close all the index writers and searchers
|
| | | */
|
| | | public static void close() {
|
| | | // close writers
|
| | | for (File file : WRITERS.keySet()) {
|
| | | try {
|
| | | WRITERS.get(file).close(true);
|
| | | } catch (Throwable t) {
|
| | | t.printStackTrace();
|
| | | }
|
| | | }
|
| | | WRITERS.clear();
|
| | |
|
| | | // close searchers
|
| | | for (File file : SEARCHERS.keySet()) {
|
| | | try {
|
| | | SEARCHERS.get(file).close();
|
| | | } catch (Throwable t) {
|
| | | t.printStackTrace();
|
| | | }
|
| | | }
|
| | | SEARCHERS.clear();
|
| | | }
|
| | | }
|
| | |
| | | ObjectCacheTest.class, UserServiceTest.class, MarkdownUtilsTest.class, JGitUtilsTest.class,
|
| | | SyndicationUtilsTest.class, DiffUtilsTest.class, MetricUtilsTest.class,
|
| | | TicgitUtilsTest.class, GitBlitTest.class, FederationTests.class, RpcTests.class,
|
| | | GitServletTest.class, GroovyScriptTest.class })
|
| | | GitServletTest.class, GroovyScriptTest.class, LuceneUtilsTest.class, IssuesTest.class })
|
| | | public class GitBlitSuite {
|
| | |
|
| | | public static final File REPOSITORIES = new File("git");
|
| | |
| | |
|
| | | public static Repository getTheoreticalPhysicsRepository() throws Exception {
|
| | | return new FileRepository(new File(REPOSITORIES, "test/theoretical-physics.git"));
|
| | | }
|
| | |
|
| | | public static Repository getIssuesTestRepository() throws Exception {
|
| | | return new FileRepository(new File(REPOSITORIES, "gb-issues.git"));
|
| | | }
|
| | |
|
| | | public static boolean startGitblit() throws Exception {
|
| | |
| | | cloneOrFetch("test/ambition.git", "https://github.com/defunkt/ambition.git");
|
| | | cloneOrFetch("test/theoretical-physics.git", "https://github.com/certik/theoretical-physics.git");
|
| | |
|
| | | JGitUtils.createRepository(REPOSITORIES, "gb-issues.git").close();
|
| | |
|
| | | enableTickets("ticgit.git");
|
| | | enableDocs("ticgit.git");
|
| | | showRemoteBranches("ticgit.git");
|
New file |
| | |
| | | /*
|
| | | * 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.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.IssueUtils;
|
| | | import com.gitblit.utils.IssueUtils.IssueFilter;
|
| | | import com.gitblit.utils.LuceneUtils;
|
| | |
|
| | | /**
|
| | | * Tests the mechanics of distributed issue management on the gb-issues branch.
|
| | | * |
| | | * @author James Moger
|
| | | * |
| | | */
|
| | | public class IssuesTest {
|
| | |
|
| | | @Test
|
| | | public void testCreation() throws Exception {
|
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | // create and insert the 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());
|
| | | }
|
| | |
|
| | | @Test
|
| | | public void testUpdates() throws Exception {
|
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | // C1: create the issue
|
| | | Change c1 = newChange("testUpdates() " + Long.toHexString(System.currentTimeMillis()));
|
| | | IssueModel issue = IssueUtils.createIssue(repository, c1);
|
| | | assertNotNull(issue.id);
|
| | |
|
| | | IssueModel constructed = IssueUtils.getIssue(repository, issue.id);
|
| | | compare(issue, constructed);
|
| | |
|
| | | // 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());
|
| | |
|
| | | repository.close();
|
| | | }
|
| | |
|
| | | @Test
|
| | | public void testQuery() throws Exception {
|
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | 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();
|
| | | }
|
| | | });
|
| | |
|
| | | repository.close();
|
| | | assertTrue(allIssues.size() > 0);
|
| | | assertEquals(1, openIssues.size());
|
| | | assertEquals(1, closedIssues.size());
|
| | | }
|
| | |
|
| | | @Test
|
| | | public void testLuceneIndexAndQuery() throws Exception { |
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | LuceneUtils.deleteIndex(repository);
|
| | | List<IssueModel> allIssues = IssueUtils.getIssues(repository, null);
|
| | | assertTrue(allIssues.size() > 0);
|
| | | for (IssueModel issue : allIssues) {
|
| | | LuceneUtils.index(repository, issue, false);
|
| | | }
|
| | | List<SearchResult> hits = LuceneUtils.search("working", 10, repository);
|
| | | assertTrue(hits.size() > 0);
|
| | | |
| | | // reindex an issue
|
| | | IssueModel 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);
|
| | | LuceneUtils.index(repository, issue, true);
|
| | | |
| | | LuceneUtils.close();
|
| | | repository.close();
|
| | | }
|
| | | |
| | | @Test
|
| | | public void testLuceneQuery() throws Exception {
|
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | List<SearchResult> hits = LuceneUtils.search("working", 10, repository);
|
| | | LuceneUtils.close();
|
| | | repository.close();
|
| | | assertTrue(hits.size() > 0);
|
| | | }
|
| | |
|
| | |
|
| | | @Test
|
| | | public void testDelete() throws Exception {
|
| | | Repository repository = GitBlitSuite.getIssuesTestRepository();
|
| | | List<IssueModel> allIssues = IssueUtils.getIssues(repository, null);
|
| | | // delete all issues
|
| | | for (IssueModel issue : allIssues) {
|
| | | assertTrue(IssueUtils.deleteIssue(repository, issue.id, "D"));
|
| | | }
|
| | | 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"));
|
| | | }
|
| | | } |
New file |
| | |
| | | /*
|
| | | * 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 java.util.List;
|
| | |
|
| | | import org.eclipse.jgit.lib.Repository;
|
| | | import org.junit.Test;
|
| | |
|
| | | import com.gitblit.models.SearchResult;
|
| | | import com.gitblit.utils.LuceneUtils;
|
| | |
|
| | | /**
|
| | | * Tests Lucene indexing and querying.
|
| | | * |
| | | * @author James Moger
|
| | | * |
| | | */
|
| | | public class LuceneUtilsTest {
|
| | |
|
| | | @Test
|
| | | public void testFullIndex() throws Exception {
|
| | | // reindex helloworld
|
| | | Repository repository = GitBlitSuite.getHelloworldRepository();
|
| | | LuceneUtils.index(repository);
|
| | | repository.close();
|
| | |
|
| | | // reindex theoretical physics
|
| | | repository = GitBlitSuite.getTheoreticalPhysicsRepository();
|
| | | LuceneUtils.index(repository);
|
| | | repository.close();
|
| | |
|
| | | // reindex bluez-gnome
|
| | | repository = GitBlitSuite.getBluezGnomeRepository();
|
| | | LuceneUtils.index(repository);
|
| | | repository.close();
|
| | |
|
| | | LuceneUtils.close();
|
| | | }
|
| | |
|
| | | @Test
|
| | | public void testQuery() throws Exception {
|
| | | // 2 occurrences on the master branch
|
| | | Repository repository = GitBlitSuite.getHelloworldRepository();
|
| | | List<SearchResult> results = LuceneUtils.search("ada", 10, repository);
|
| | | assertEquals(2, results.size());
|
| | |
|
| | | // author test
|
| | | results = LuceneUtils.search("author: tinogomes", 10, repository);
|
| | | assertEquals(2, results.size());
|
| | |
|
| | | repository.close();
|
| | | // blob test
|
| | | results = LuceneUtils.search("type: blob AND \"import std.stdio\"", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | | assertEquals("d.D", results.get(0).id);
|
| | | |
| | | // 1 occurrence on the gh-pages branch
|
| | | repository = GitBlitSuite.getTheoreticalPhysicsRepository();
|
| | | results = LuceneUtils.search("\"add the .nojekyll file\"", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | | assertEquals("Ondrej Certik", results.get(0).author);
|
| | | assertEquals("2648c0c98f2101180715b4d432fc58d0e21a51d7", results.get(0).id);
|
| | | |
| | | // tag test
|
| | | results = LuceneUtils.search("\"qft split\"", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | | assertEquals("Ondrej Certik", results.get(0).author);
|
| | | assertEquals("57c4f26f157ece24b02f4f10f5f68db1d2ce7ff5", results.get(0).id);
|
| | | assertEquals("[1st-edition]", results.get(0).labels.toString());
|
| | |
|
| | | results = LuceneUtils.search("type:blob AND \"src/intro.rst\"", 10, repository);
|
| | | assertEquals(4, results.size());
|
| | | |
| | | // hash id tests
|
| | | results = LuceneUtils.search("id:57c4f26f157ece24b02f4f10f5f68db1d2ce7ff5", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | |
|
| | | results = LuceneUtils.search("id:57c4f26f157*", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | |
|
| | | repository.close();
|
| | | |
| | | // annotated tag test
|
| | | repository = GitBlitSuite.getBluezGnomeRepository();
|
| | | results = LuceneUtils.search("\"release 1.8\"", 10, repository);
|
| | | assertEquals(1, results.size());
|
| | | assertEquals("[1.8]", results.get(0).labels.toString());
|
| | |
|
| | | repository.close();
|
| | | |
| | | LuceneUtils.close();
|
| | | }
|
| | | |
| | | @Test
|
| | | public void testMultiSearch() throws Exception {
|
| | | List<SearchResult> results = LuceneUtils.search("test", 10,
|
| | | GitBlitSuite.getHelloworldRepository(), |
| | | GitBlitSuite.getBluezGnomeRepository());
|
| | | LuceneUtils.close();
|
| | | assertEquals(10, results.size());
|
| | | }
|
| | | } |