James Moger
2011-04-21 a645ba09d693495c50ab0ee0d1fc2734407b73a4
commit | author | age
5fe7df 1 package com.gitblit.utils;
JM 2
3 import java.io.ByteArrayOutputStream;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.RandomAccessFile;
fc8426 8 import java.text.DateFormat;
232890 9 import java.text.ParseException;
fc8426 10 import java.text.SimpleDateFormat;
5fe7df 11 import java.util.ArrayList;
JM 12 import java.util.Collections;
13 import java.util.Date;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
a645ba 18 import java.util.concurrent.atomic.AtomicInteger;
5fe7df 19
87c3d7 20 import org.eclipse.jgit.diff.DiffEntry;
JM 21 import org.eclipse.jgit.diff.DiffFormatter;
22 import org.eclipse.jgit.diff.RawTextComparator;
9bc17d 23 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
5fe7df 24 import org.eclipse.jgit.errors.ConfigInvalidException;
98ce17 25 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
JM 26 import org.eclipse.jgit.errors.MissingObjectException;
27 import org.eclipse.jgit.errors.StopWalkException;
5fe7df 28 import org.eclipse.jgit.lib.AnyObjectId;
JM 29 import org.eclipse.jgit.lib.Constants;
30 import org.eclipse.jgit.lib.FileMode;
31 import org.eclipse.jgit.lib.ObjectId;
32 import org.eclipse.jgit.lib.ObjectLoader;
33 import org.eclipse.jgit.lib.PersonIdent;
34 import org.eclipse.jgit.lib.Ref;
35 import org.eclipse.jgit.lib.Repository;
36 import org.eclipse.jgit.lib.StoredConfig;
37 import org.eclipse.jgit.revwalk.RevBlob;
38 import org.eclipse.jgit.revwalk.RevCommit;
39 import org.eclipse.jgit.revwalk.RevObject;
45c0d6 40 import org.eclipse.jgit.revwalk.RevSort;
5fe7df 41 import org.eclipse.jgit.revwalk.RevTree;
JM 42 import org.eclipse.jgit.revwalk.RevWalk;
98ce17 43 import org.eclipse.jgit.revwalk.filter.RevFilter;
5fe7df 44 import org.eclipse.jgit.treewalk.TreeWalk;
f602a2 45 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
5fe7df 46 import org.eclipse.jgit.treewalk.filter.PathFilter;
JM 47 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
87c3d7 48 import org.eclipse.jgit.treewalk.filter.TreeFilter;
JM 49 import org.eclipse.jgit.util.io.DisabledOutputStream;
5fe7df 50 import org.slf4j.Logger;
JM 51 import org.slf4j.LoggerFactory;
52
fc8426 53 import com.gitblit.wicket.models.Metric;
5fe7df 54 import com.gitblit.wicket.models.PathModel;
9bc17d 55 import com.gitblit.wicket.models.PathModel.PathChangeModel;
5fe7df 56 import com.gitblit.wicket.models.RefModel;
9802a7 57 import com.gitblit.wicket.models.TicketModel;
JM 58 import com.gitblit.wicket.models.TicketModel.Comment;
5fe7df 59
JM 60 public class JGitUtils {
61
62     /** Prefix for notes refs */
63     public static final String R_NOTES = "refs/notes/";
64
65     /** Standard notes ref */
66     public static final String R_NOTES_COMMITS = R_NOTES + "commits";
67
68     private final static Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
69
70     public static List<String> getRepositoryList(File repositoriesFolder, boolean exportAll, boolean readNested) {
71         List<String> list = new ArrayList<String>();
72         list.addAll(getNestedRepositories(repositoriesFolder, repositoriesFolder, exportAll, readNested));
73         Collections.sort(list);
74         return list;
75     }
76
77     public static List<String> getNestedRepositories(File repositoriesFolder, File folder, boolean exportAll, boolean readNested) {
78         String basefile = repositoriesFolder.getAbsolutePath();
79         List<String> list = new ArrayList<String>();
80         for (File file : folder.listFiles()) {
81             if (file.isDirectory() && !file.getName().equalsIgnoreCase(Constants.DOT_GIT)) {
82                 // if this is a git repository add it to the list
83                 File gitFolder = new File(file, Constants.DOT_GIT);
84                 boolean isGitRepository = gitFolder.exists() && gitFolder.isDirectory();
85                 boolean exportRepository = isGitRepository && (exportAll || new File(gitFolder, "git-daemon-export-ok").exists());
86
87                 if (exportRepository) {
88                     // determine repository name relative to repositories folder
89                     String filename = file.getAbsolutePath();
90                     String repo = filename.substring(basefile.length()).replace('\\', '/');
91                     if (repo.charAt(0) == '/') {
92                         repo = repo.substring(1);
93                     }
94                     list.add(repo);
95                 }
96
97                 // look for nested repositories
98                 if (readNested) {
99                     list.addAll(getNestedRepositories(repositoriesFolder, file, exportAll, readNested));
100                 }
101             }
102         }
103         return list;
45c0d6 104     }
JM 105
106     public static RevCommit getFirstCommit(Repository r, String branch) {
107         if (StringUtils.isEmpty(branch)) {
108             branch = Constants.HEAD;
109         }
110         try {
111             RevWalk walk = new RevWalk(r);
112             walk.sort(RevSort.REVERSE);
113             RevCommit head = walk.parseCommit(r.resolve(branch));
114             walk.markStart(head);
115             RevCommit commit = walk.next();
116             walk.dispose();
117             return commit;
118         } catch (Throwable t) {
119             LOGGER.error("Failed to determine first commit", t);
120         }
121         return null;
122     }
123
124     public static Date getFirstChange(Repository r, String branch) {
125         try {
126             RevCommit commit = getFirstCommit(r, branch);
127             return getCommitDate(commit);
128         } catch (Throwable t) {
129             LOGGER.error("Failed to determine first change", t);
130         }
131         return null;
5fe7df 132     }
JM 133
134     public static Date getLastChange(Repository r) {
135         RevCommit commit = getCommit(r, Constants.HEAD);
136         return getCommitDate(commit);
137     }
138
608ece 139     public static RevCommit getCommit(Repository r, String objectId) {
5fe7df 140         RevCommit commit = null;
JM 141         try {
608ece 142             if (objectId == null || objectId.trim().length() == 0) {
JM 143                 objectId = Constants.HEAD;
144             }
145             ObjectId object = r.resolve(objectId);
5fe7df 146             RevWalk walk = new RevWalk(r);
608ece 147             RevCommit rev = walk.parseCommit(object);
5fe7df 148             commit = rev;
JM 149             walk.dispose();
150         } catch (Throwable t) {
151             LOGGER.error("Failed to determine last change", t);
152         }
153         return commit;
154     }
155
156     public static Map<ObjectId, List<String>> getAllRefs(Repository r) {
157         Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
158         Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
159         for (AnyObjectId id : allRefs.keySet()) {
160             List<String> list = new ArrayList<String>();
161             for (Ref setRef : allRefs.get(id)) {
162                 String name = setRef.getName();
163                 list.add(name);
164             }
165             refs.put(id.toObjectId(), list);
166         }
167         return refs;
168     }
169
170     public static Map<ObjectId, List<String>> getRefs(Repository r, String baseRef) {
171         Map<ObjectId, List<String>> refs = new HashMap<ObjectId, List<String>>();
172         Map<AnyObjectId, Set<Ref>> allRefs = r.getAllRefsByPeeledObjectId();
173         for (AnyObjectId id : allRefs.keySet()) {
174             List<String> list = new ArrayList<String>();
175             for (Ref setRef : allRefs.get(id)) {
176                 String name = setRef.getName();
177                 if (name.startsWith(baseRef)) {
178                     list.add(name);
179                 }
180             }
181             refs.put(id.toObjectId(), list);
182         }
183         return refs;
184     }
185
186     /**
187      * Lookup an entry stored in a tree, failing if not present.
188      * 
189      * @param tree
190      *            the tree to search.
191      * @param path
192      *            the path to find the entry of.
193      * @return the parsed object entry at this path
194      * @throws Exception
195      */
196     public static RevObject getRevObject(Repository r, final RevTree tree, final String path) {
197         RevObject ro = null;
198         RevWalk rw = new RevWalk(r);
199         TreeWalk tw = new TreeWalk(r);
200         tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
201         try {
202             tw.reset(tree);
203             while (tw.next()) {
204                 if (tw.isSubtree() && !path.equals(tw.getPathString())) {
205                     tw.enterSubtree();
206                     continue;
207                 }
208                 ObjectId entid = tw.getObjectId(0);
209                 FileMode entmode = tw.getFileMode(0);
210                 ro = rw.lookupAny(entid, entmode.getObjectType());
211                 rw.parseBody(ro);
212             }
213         } catch (Throwable t) {
214             LOGGER.error("Can't find " + path + " in tree " + tree.name(), t);
215         } finally {
216             if (rw != null) {
217                 rw.dispose();
218             }
219         }
220         return ro;
221     }
222
223     public static byte[] getRawContent(Repository r, RevBlob blob) {
224         ByteArrayOutputStream os = new ByteArrayOutputStream();
225         try {
226             ObjectLoader ldr = r.open(blob.getId(), Constants.OBJ_BLOB);
227             byte[] tmp = new byte[1024];
228             InputStream in = ldr.openStream();
229             int n;
230             while ((n = in.read(tmp)) > 0) {
231                 os.write(tmp, 0, n);
232             }
233             in.close();
234         } catch (Throwable t) {
235             LOGGER.error("Failed to read raw content", t);
236         }
237         return os.toByteArray();
238     }
239
240     public static String getRawContentAsString(Repository r, RevBlob blob) {
241         return new String(getRawContent(r, blob));
242     }
243
244     public static String getRawContentAsString(Repository r, RevCommit commit, String blobPath) {
245         RevObject obj = getRevObject(r, commit.getTree(), blobPath);
246         return new String(getRawContent(r, (RevBlob) obj));
247     }
248
608ece 249     public static List<PathModel> getFilesInPath(Repository r, String basePath, String objectId) {
JM 250         RevCommit commit = getCommit(r, objectId);
5fe7df 251         return getFilesInPath(r, basePath, commit);
JM 252     }
253
254     public static List<PathModel> getFilesInPath(Repository r, String basePath, RevCommit commit) {
255         List<PathModel> list = new ArrayList<PathModel>();
fc8426 256         final TreeWalk walk = new TreeWalk(r);
5fe7df 257         try {
JM 258             walk.addTree(commit.getTree());
259             if (basePath != null && basePath.length() > 0) {
260                 PathFilter f = PathFilter.create(basePath);
261                 walk.setFilter(f);
262                 walk.setRecursive(false);
263                 boolean foundFolder = false;
264                 while (walk.next()) {
265                     if (!foundFolder && walk.isSubtree()) {
fc8426 266                         walk.enterSubtree();
5fe7df 267                     }
JM 268                     if (walk.getPathString().equals(basePath)) {
269                         foundFolder = true;
270                         continue;
271                     }
272                     if (foundFolder) {
273                         list.add(getPathModel(walk, basePath, commit));
274                     }
275                 }
276             } else {
277                 walk.setRecursive(false);
278                 while (walk.next()) {
279                     list.add(getPathModel(walk, null, commit));
280                 }
281             }
282         } catch (IOException e) {
283             LOGGER.error("Failed to get files for commit " + commit.getName(), e);
284         } finally {
285             walk.release();
286         }
287         Collections.sort(list);
288         return list;
289     }
290
9bc17d 291     public static List<PathChangeModel> getFilesInCommit(Repository r, String commitId) {
5fe7df 292         RevCommit commit = getCommit(r, commitId);
87c3d7 293         return getFilesInCommit(r, commit);
5fe7df 294     }
JM 295
9bc17d 296     public static List<PathChangeModel> getFilesInCommit(Repository r, RevCommit commit) {
JM 297         List<PathChangeModel> list = new ArrayList<PathChangeModel>();
5fe7df 298         try {
87c3d7 299             final RevWalk rw = new RevWalk(r);
JM 300             RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
301             RevTree parentTree = parent.getTree();
302             RevTree commitTree = commit.getTree();
5fe7df 303
87c3d7 304             final TreeWalk walk = new TreeWalk(r);
JM 305             walk.reset();
306             walk.setRecursive(true);
307             walk.addTree(parentTree);
308             walk.addTree(commitTree);
309             walk.setFilter(TreeFilter.ANY_DIFF);
310
311             RawTextComparator cmp = RawTextComparator.DEFAULT;
312             DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
313             df.setRepository(r);
314             df.setDiffComparator(cmp);
315             df.setDetectRenames(true);
316             List<DiffEntry> diffs = df.scan(parentTree, commitTree);
317             for (DiffEntry diff : diffs) {
9bc17d 318                 if (diff.getChangeType().equals(ChangeType.DELETE)) {
JM 319                     list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
320                 } else {
321                     list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), commit.getId().getName(), diff.getChangeType()));
322                 }
5fe7df 323             }
87c3d7 324         } catch (Throwable t) {
JM 325             LOGGER.error("failed to determine files in commit!", t);
5fe7df 326         }
JM 327         return list;
87c3d7 328     }
JM 329
a645ba 330     public static Map<ChangeType, AtomicInteger> getChangedPathsStats(List<PathChangeModel> paths) {
JM 331         Map<ChangeType, AtomicInteger> stats = new HashMap<ChangeType, AtomicInteger>();
332         for (PathChangeModel path : paths) {
333             if (!stats.containsKey(path.changeType)) {
334                 stats.put(path.changeType, new AtomicInteger(0));
335             }
336             stats.get(path.changeType).incrementAndGet();
337         }
338         return stats;
339     }
340
3df349 341     public static enum DiffOutputType {
JM 342         PLAIN, GITWEB, GITBLIT;
343
344         public static DiffOutputType forName(String name) {
345             for (DiffOutputType type : values()) {
346                 if (type.name().equalsIgnoreCase(name)) {
347                     return type;
348                 }
349             }
350             return null;
351         }
87c3d7 352     }
ef5c58 353
3df349 354     public static String getCommitDiff(Repository r, RevCommit commit, DiffOutputType outputType) {
JM 355         return getCommitDiff(r, null, commit, null, outputType);
f1dfc2 356     }
JM 357
3df349 358     public static String getCommitDiff(Repository r, RevCommit commit, String path, DiffOutputType outputType) {
JM 359         return getCommitDiff(r, null, commit, path, outputType);
f1dfc2 360     }
JM 361
3df349 362     public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, DiffOutputType outputType) {
JM 363         return getCommitDiff(r, baseCommit, commit, null, outputType);
364     }
365
366     public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, String path, DiffOutputType outputType) {
87c3d7 367         try {
f1dfc2 368             RevTree baseTree;
JM 369             if (baseCommit == null) {
370                 final RevWalk rw = new RevWalk(r);
371                 RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
372                 rw.dispose();
98ce17 373                 baseTree = parent.getTree();
f1dfc2 374             } else {
JM 375                 baseTree = baseCommit.getTree();
376             }
377
87c3d7 378             RevTree commitTree = commit.getTree();
JM 379
380             final TreeWalk walk = new TreeWalk(r);
381             walk.reset();
382             walk.setRecursive(true);
f1dfc2 383             walk.addTree(baseTree);
87c3d7 384             walk.addTree(commitTree);
608ece 385             walk.setFilter(TreeFilter.ANY_DIFF);
87c3d7 386
JM 387             final ByteArrayOutputStream os = new ByteArrayOutputStream();
388             RawTextComparator cmp = RawTextComparator.DEFAULT;
389             DiffFormatter df;
3df349 390             switch (outputType) {
JM 391             case GITWEB:
392                 df = new GitWebDiffFormatter(os);
393                 break;
394             case GITBLIT:
395                 df = new GitBlitDiffFormatter(os);
396                 break;
397             case PLAIN:
398             default:
87c3d7 399                 df = new DiffFormatter(os);
3df349 400                 break;
87c3d7 401             }
JM 402             df.setRepository(r);
403             df.setDiffComparator(cmp);
404             df.setDetectRenames(true);
f1dfc2 405             List<DiffEntry> diffs = df.scan(baseTree, commitTree);
608ece 406             if (path != null && path.length() > 0) {
JM 407                 for (DiffEntry diff : diffs) {
408                     if (diff.getNewPath().equalsIgnoreCase(path)) {
409                         df.format(diff);
410                         break;
411                     }
412                 }
413             } else {
414                 df.format(diffs);
415             }
87c3d7 416             String diff;
3df349 417             if (df instanceof GitWebDiffFormatter) {
87c3d7 418                 // workaround for complex private methods in DiffFormatter
3df349 419                 diff = ((GitWebDiffFormatter) df).getHtml();
87c3d7 420             } else {
JM 421                 diff = os.toString();
422             }
608ece 423             df.flush();
JM 424             return diff;
425         } catch (Throwable t) {
426             LOGGER.error("failed to generate commit diff!", t);
427         }
428         return null;
429     }
155bf7 430
608ece 431     public static String getCommitPatch(Repository r, RevCommit commit) {
JM 432         return getCommitPatch(r, commit);
433     }
155bf7 434
608ece 435     public static String getCommitPatch(Repository r, RevCommit commit, String path) {
ce119a 436         return getCommitPatch(r, null, commit, path);
JM 437     }
98ce17 438
ce119a 439     public static String getCommitPatch(Repository r, RevCommit baseCommit, RevCommit commit, String path) {
608ece 440         try {
ce119a 441             RevTree baseTree;
JM 442             if (baseCommit == null) {
443                 final RevWalk rw = new RevWalk(r);
444                 RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
445                 baseTree = parent.getTree();
446             } else {
447                 baseTree = baseCommit.getTree();
448             }
608ece 449             RevTree commitTree = commit.getTree();
JM 450
451             final TreeWalk walk = new TreeWalk(r);
452             walk.reset();
453             walk.setRecursive(true);
ce119a 454             walk.addTree(baseTree);
608ece 455             walk.addTree(commitTree);
JM 456             walk.setFilter(TreeFilter.ANY_DIFF);
457
458             final ByteArrayOutputStream os = new ByteArrayOutputStream();
459             RawTextComparator cmp = RawTextComparator.DEFAULT;
460             PatchFormatter df = new PatchFormatter(os);
461             df.setRepository(r);
462             df.setDiffComparator(cmp);
463             df.setDetectRenames(true);
ce119a 464             List<DiffEntry> diffs = df.scan(baseTree, commitTree);
608ece 465             if (path != null && path.length() > 0) {
JM 466                 for (DiffEntry diff : diffs) {
467                     if (diff.getNewPath().equalsIgnoreCase(path)) {
468                         df.format(diff);
469                         break;
470                     }
471                 }
472             } else {
473                 df.format(diffs);
474             }
475             String diff = df.getPatch(commit);
87c3d7 476             df.flush();
JM 477             return diff;
478         } catch (Throwable t) {
479             LOGGER.error("failed to generate commit diff!", t);
480         }
481         return null;
5fe7df 482     }
JM 483
fc8426 484     private static PathModel getPathModel(TreeWalk walk, String basePath, RevCommit commit) {
5fe7df 485         String name;
JM 486         long size = 0;
487         if (basePath == null) {
488             name = walk.getPathString();
489         } else {
490             try {
491                 name = walk.getPathString().substring(basePath.length() + 1);
492             } catch (Throwable t) {
493                 name = walk.getPathString();
494             }
495         }
496         try {
497             if (!walk.isSubtree()) {
498                 size = walk.getObjectReader().getObjectSize(walk.getObjectId(0), Constants.OBJ_BLOB);
499             }
500         } catch (Throwable t) {
ce33be 501             LOGGER.error("Failed to retrieve blob size", t);
5fe7df 502         }
JM 503         return new PathModel(name, walk.getPathString(), size, walk.getFileMode(0).getBits(), commit.getName());
504     }
505
506     public static String getPermissionsFromMode(int mode) {
507         if (FileMode.TREE.equals(mode)) {
508             return "drwxr-xr-x";
509         } else if (FileMode.REGULAR_FILE.equals(mode)) {
510             return "-rw-r--r--";
511         } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
512             return "-rwxr-xr-x";
513         } else if (FileMode.SYMLINK.equals(mode)) {
514             // FIXME symlink permissions
515             return "symlink";
516         } else if (FileMode.GITLINK.equals(mode)) {
517             // FIXME gitlink permissions
518             return "gitlink";
519         } else if (FileMode.MISSING.equals(mode)) {
520             // FIXME missing permissions
521             return "missing";
522         }
523         return "" + mode;
524     }
525
526     public static boolean isTreeFromMode(int mode) {
527         return FileMode.TREE.equals(mode);
528     }
529
530     public static List<RevCommit> getRevLog(Repository r, int maxCount) {
ef5c58 531         return getRevLog(r, Constants.HEAD, 0, maxCount);
JM 532     }
533
534     public static List<RevCommit> getRevLog(Repository r, String objectId, int offset, int maxCount) {
f602a2 535         return getRevLog(r, objectId, null, offset, maxCount);
JM 536     }
98ce17 537
f602a2 538     public static List<RevCommit> getRevLog(Repository r, String objectId, String path, int offset, int maxCount) {
5fe7df 539         List<RevCommit> list = new ArrayList<RevCommit>();
JM 540         try {
ef5c58 541             if (objectId == null || objectId.trim().length() == 0) {
JM 542                 objectId = Constants.HEAD;
543             }
544             RevWalk walk = new RevWalk(r);
545             ObjectId object = r.resolve(objectId);
546             walk.markStart(walk.parseCommit(object));
f602a2 547             if (!StringUtils.isEmpty(path)) {
98ce17 548                 TreeFilter filter = AndTreeFilter.create(PathFilterGroup.createFromStrings(Collections.singleton(path)), TreeFilter.ANY_DIFF);
f602a2 549                 walk.setTreeFilter(filter);
JM 550             }
ef5c58 551             Iterable<RevCommit> revlog = walk;
JM 552             if (offset > 0) {
553                 int count = 0;
554                 for (RevCommit rev : revlog) {
555                     count++;
556                     if (count > offset) {
557                         list.add(rev);
558                         if (maxCount > 0 && list.size() == maxCount) {
559                             break;
560                         }
561                     }
562                 }
563             } else {
564                 for (RevCommit rev : revlog) {
565                     list.add(rev);
566                     if (maxCount > 0 && list.size() == maxCount) {
567                         break;
568                     }
5fe7df 569                 }
JM 570             }
ef5c58 571             walk.dispose();
5fe7df 572         } catch (Throwable t) {
JM 573             LOGGER.error("Failed to determine last change", t);
574         }
575         return list;
576     }
577
98ce17 578     public static enum SearchType {
JM 579         AUTHOR, COMMITTER, COMMIT;
580
581         public static SearchType forName(String name) {
582             for (SearchType type : values()) {
583                 if (type.name().equalsIgnoreCase(name)) {
584                     return type;
585                 }
586             }
587             return null;
588         }
45c0d6 589
7203a4 590         public String toString() {
JM 591             return name().toLowerCase();
592         }
98ce17 593     }
JM 594
595     public static List<RevCommit> searchRevlogs(Repository r, String objectId, String value, final SearchType type, int offset, int maxCount) {
596         final String lcValue = value.toLowerCase();
597         List<RevCommit> list = new ArrayList<RevCommit>();
598         try {
599             if (objectId == null || objectId.trim().length() == 0) {
600                 objectId = Constants.HEAD;
601             }
602             RevWalk walk = new RevWalk(r);
603             walk.setRevFilter(new RevFilter() {
604
605                 @Override
606                 public RevFilter clone() {
607                     return this;
608                 }
609
610                 @Override
611                 public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException, MissingObjectException, IncorrectObjectTypeException, IOException {
612                     switch (type) {
613                     case AUTHOR:
614                         return (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getAuthorIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1);
615                     case COMMITTER:
45c0d6 616                         return (commit.getCommitterIdent().getName().toLowerCase().indexOf(lcValue) > -1) || (commit.getCommitterIdent().getEmailAddress().toLowerCase().indexOf(lcValue) > -1);
98ce17 617                     case COMMIT:
JM 618                         return commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
619                     }
620                     return false;
621                 }
622
623             });
624             ObjectId object = r.resolve(objectId);
625             walk.markStart(walk.parseCommit(object));
626             Iterable<RevCommit> revlog = walk;
627             if (offset > 0) {
628                 int count = 0;
629                 for (RevCommit rev : revlog) {
630                     count++;
631                     if (count > offset) {
632                         list.add(rev);
633                         if (maxCount > 0 && list.size() == maxCount) {
634                             break;
635                         }
636                     }
637                 }
638             } else {
639                 for (RevCommit rev : revlog) {
640                     list.add(rev);
641                     if (maxCount > 0 && list.size() == maxCount) {
642                         break;
643                     }
644                 }
645             }
646             walk.dispose();
647         } catch (Throwable t) {
648             LOGGER.error("Failed to determine last change", t);
649         }
650         return list;
651     }
652
5fe7df 653     public static List<RefModel> getTags(Repository r, int maxCount) {
JM 654         return getRefs(r, Constants.R_TAGS, maxCount);
655     }
656
232890 657     public static List<RefModel> getLocalBranches(Repository r, int maxCount) {
5fe7df 658         return getRefs(r, Constants.R_HEADS, maxCount);
232890 659     }
JM 660
661     public static List<RefModel> getRemoteBranches(Repository r, int maxCount) {
662         return getRefs(r, Constants.R_REMOTES, maxCount);
5fe7df 663     }
JM 664
665     public static List<RefModel> getRefs(Repository r, String refs, int maxCount) {
666         List<RefModel> list = new ArrayList<RefModel>();
667         try {
668             Map<String, Ref> map = r.getRefDatabase().getRefs(refs);
669             for (String name : map.keySet()) {
670                 Ref ref = map.get(name);
671                 RevCommit commit = getCommit(r, ref.getObjectId().getName());
672                 list.add(new RefModel(name, ref, commit));
673             }
674             Collections.sort(list);
675             Collections.reverse(list);
676             if (maxCount > 0 && list.size() > maxCount) {
fb01c9 677                 list = new ArrayList<RefModel>(list.subList(0, maxCount));
5fe7df 678             }
JM 679         } catch (IOException e) {
680             LOGGER.error("Failed to retrieve " + refs, e);
681         }
682         return list;
683     }
684
685     public static Ref getRef(Repository r, String id) {
686         try {
687             Map<String, Ref> map = r.getRefDatabase().getRefs(id);
688             for (String name : map.keySet()) {
689                 return map.get(name);
690             }
691         } catch (IOException e) {
692             LOGGER.error("Failed to retrieve ref " + id, e);
693         }
694         return null;
695     }
696
697     public static Date getCommitDate(RevCommit commit) {
698         return new Date(commit.getCommitTime() * 1000l);
699     }
700
701     public static String getDisplayName(PersonIdent person) {
702         final StringBuilder r = new StringBuilder();
703         r.append(person.getName());
704         r.append(" <");
705         r.append(person.getEmailAddress());
706         r.append(">");
707         return r.toString();
708     }
709
710     public static String getRepositoryDescription(Repository r) {
711         File dir = r.getDirectory();
712         if (dir.exists()) {
713             File description = new File(dir, "description");
714             if (description.exists() && description.length() > 0) {
715                 RandomAccessFile raf = null;
716                 try {
717                     raf = new RandomAccessFile(description, "r");
718                     byte[] buffer = new byte[(int) description.length()];
719                     raf.readFully(buffer);
720                     return new String(buffer);
721                 } catch (Throwable t) {
722                 } finally {
723                     try {
724                         raf.close();
725                     } catch (Throwable t) {
726                     }
727                 }
728             }
729         }
730         return "";
731     }
732
733     public static String getRepositoryOwner(Repository r) {
734         StoredConfig c = readConfig(r);
735         if (c == null) {
736             return "";
737         }
738         String o = c.getString("gitweb", null, "owner");
739         return o == null ? "" : o;
740     }
741
742     private static StoredConfig readConfig(Repository r) {
743         StoredConfig c = r.getConfig();
744         if (c != null) {
745             try {
746                 c.load();
747             } catch (ConfigInvalidException cex) {
748                 LOGGER.error("Repository configuration is invalid!", cex);
749             } catch (IOException cex) {
750                 LOGGER.error("Could not open repository configuration!", cex);
751             }
752             return c;
753         }
754         return null;
755     }
fc8426 756
JM 757     public static List<Metric> getDateMetrics(Repository r) {
608ece 758         final List<RefModel> tags = getTags(r, -1);
45c0d6 759         final Map<ObjectId, RefModel> tagMap = new HashMap<ObjectId, RefModel>();
JM 760         for (RefModel tag : tags) {
761             tagMap.put(tag.getCommitId(), tag);
762         }
763         Metric total = new Metric("TOTAL");
764         final Map<String, Metric> metricMap = new HashMap<String, Metric>();
fc8426 765         try {
ef5c58 766             RevWalk walk = new RevWalk(r);
JM 767             ObjectId object = r.resolve(Constants.HEAD);
45c0d6 768
JM 769             RevCommit firstCommit = getFirstCommit(r, Constants.HEAD);
770             RevCommit lastCommit = walk.parseCommit(object);
771             int diffDays = (lastCommit.getCommitTime() - firstCommit.getCommitTime()) / (60 * 60 * 24);
772             total.duration = diffDays;
773             DateFormat df;
774             if (diffDays <= 90) {
775                 // Days
776                 df = new SimpleDateFormat("yyyy-MM-dd");
777             } else if (diffDays > 90 && diffDays < 365) {
778                 // Weeks
779                 df = new SimpleDateFormat("yyyy-MM (w)");
780             } else {
781                 // Months
782                 df = new SimpleDateFormat("yyyy-MM");
783             }
784             walk.markStart(lastCommit);
3df349 785
ef5c58 786             Iterable<RevCommit> revlog = walk;
fc8426 787             for (RevCommit rev : revlog) {
JM 788                 Date d = getCommitDate(rev);
789                 String p = df.format(d);
45c0d6 790                 if (!metricMap.containsKey(p))
JM 791                     metricMap.put(p, new Metric(p));
3df349 792                 Metric m = metricMap.get(p);
45c0d6 793                 m.count++;
JM 794                 total.count++;
795                 if (tagMap.containsKey(rev.getId())) {
796                     m.tag++;
797                     total.tag++;
798                 }
3df349 799             }
fc8426 800         } catch (Throwable t) {
JM 801             LOGGER.error("Failed to mine log history for metrics", t);
802         }
45c0d6 803         List<String> keys = new ArrayList<String>(metricMap.keySet());
fc8426 804         Collections.sort(keys);
JM 805         List<Metric> metrics = new ArrayList<Metric>();
232890 806         for (String key : keys) {
45c0d6 807             metrics.add(metricMap.get(key));
232890 808         }
45c0d6 809         metrics.add(0, total);
fc8426 810         return metrics;
JM 811     }
232890 812
9802a7 813     public static RefModel getTicketsBranch(Repository r) {
232890 814         RefModel ticgitBranch = null;
JM 815         try {
816             // search for ticgit branch in local heads
817             for (RefModel ref : getLocalBranches(r, -1)) {
9802a7 818                 if (ref.getDisplayName().endsWith("ticgit")) {
232890 819                     ticgitBranch = ref;
JM 820                     break;
821                 }
822             }
823
824             // search for ticgit branch in remote heads
825             if (ticgitBranch == null) {
826                 for (RefModel ref : getRemoteBranches(r, -1)) {
9802a7 827                     if (ref.getDisplayName().endsWith("ticgit")) {
232890 828                         ticgitBranch = ref;
JM 829                         break;
830                     }
831                 }
832             }
833         } catch (Throwable t) {
834             LOGGER.error("Failed to find ticgit branch!", t);
835         }
836         return ticgitBranch;
837     }
838
9802a7 839     public static List<TicketModel> getTickets(Repository r) {
JM 840         RefModel ticgitBranch = getTicketsBranch(r);
232890 841         List<PathModel> paths = getFilesInPath(r, null, ticgitBranch.getCommit());
9802a7 842         List<TicketModel> tickets = new ArrayList<TicketModel>();
232890 843         for (PathModel ticketFolder : paths) {
JM 844             if (ticketFolder.isTree()) {
845                 try {
9802a7 846                     TicketModel t = new TicketModel(ticketFolder.name);
232890 847                     readTicketContents(r, ticgitBranch, t);
JM 848                     tickets.add(t);
849                 } catch (Throwable t) {
9802a7 850                     LOGGER.error("Failed to get a ticket!", t);
232890 851                 }
JM 852             }
853         }
854         Collections.sort(tickets);
855         Collections.reverse(tickets);
856         return tickets;
857     }
858
9802a7 859     public static TicketModel getTicket(Repository r, String ticketFolder) {
JM 860         RefModel ticketsBranch = getTicketsBranch(r);
861         if (ticketsBranch != null) {
232890 862             try {
9802a7 863                 TicketModel ticket = new TicketModel(ticketFolder);
JM 864                 readTicketContents(r, ticketsBranch, ticket);
232890 865                 return ticket;
JM 866             } catch (Throwable t) {
9802a7 867                 LOGGER.error("Failed to get ticket " + ticketFolder, t);
232890 868             }
JM 869         }
870         return null;
871     }
87c3d7 872
9802a7 873     private static void readTicketContents(Repository r, RefModel ticketsBranch, TicketModel ticket) {
JM 874         List<PathModel> ticketFiles = getFilesInPath(r, ticket.name, ticketsBranch.getCommit());
232890 875         for (PathModel file : ticketFiles) {
9802a7 876             String content = getRawContentAsString(r, ticketsBranch.getCommit(), file.path).trim();
232890 877             if (file.name.equals("TICKET_ID")) {
JM 878                 ticket.id = content;
879             } else if (file.name.equals("TITLE")) {
880                 ticket.title = content;
881             } else {
882                 String[] chunks = file.name.split("_");
883                 if (chunks[0].equals("ASSIGNED")) {
884                     ticket.handler = content;
885                 } else if (chunks[0].equals("COMMENT")) {
886                     try {
887                         Comment c = new Comment(file.name, content);
888                         ticket.comments.add(c);
889                     } catch (ParseException e) {
890                         e.printStackTrace();
891                     }
892                 } else if (chunks[0].equals("TAG")) {
893                     if (content.startsWith("TAG_")) {
894                         ticket.tags.add(content.substring(4));
895                     } else {
896                         ticket.tags.add(content);
897                     }
898                 } else if (chunks[0].equals("STATE")) {
899                     ticket.state = content;
900                 }
901             }
902         }
903         Collections.sort(ticket.comments);
904     }
905
9802a7 906     public static String getTicketContent(Repository r, String filePath) {
JM 907         RefModel ticketsBranch = getTicketsBranch(r);
908         if (ticketsBranch != null) {
909             return getRawContentAsString(r, ticketsBranch.getCommit(), filePath);
232890 910         }
JM 911         return "";
912     }
5fe7df 913 }