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