James Moger
2011-09-25 dd9ae71bc1cb13b90dcc8d9689550eb7dfe7d035
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
5fe7df 16 package com.gitblit.utils;
JM 17
18 import java.io.ByteArrayOutputStream;
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
9197d3 22 import java.io.OutputStream;
3e087a 23 import java.nio.charset.Charset;
a2709d 24 import java.text.MessageFormat;
5fe7df 25 import java.util.ArrayList;
168566 26 import java.util.Arrays;
c1c3c6 27 import java.util.Collection;
5fe7df 28 import java.util.Collections;
JM 29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
2a7306 33 import java.util.Map.Entry;
9197d3 34 import java.util.zip.ZipEntry;
JM 35 import java.util.zip.ZipOutputStream;
5fe7df 36
168566 37 import org.eclipse.jgit.api.CloneCommand;
JM 38 import org.eclipse.jgit.api.FetchCommand;
f5d0ad 39 import org.eclipse.jgit.api.Git;
2548a7 40 import org.eclipse.jgit.api.PullCommand;
JM 41 import org.eclipse.jgit.api.PullResult;
42 import org.eclipse.jgit.api.ResetCommand;
43 import org.eclipse.jgit.api.ResetCommand.ResetType;
87c3d7 44 import org.eclipse.jgit.diff.DiffEntry;
f5d0ad 45 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
87c3d7 46 import org.eclipse.jgit.diff.DiffFormatter;
JM 47 import org.eclipse.jgit.diff.RawTextComparator;
5fe7df 48 import org.eclipse.jgit.errors.ConfigInvalidException;
98ce17 49 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
JM 50 import org.eclipse.jgit.errors.MissingObjectException;
51 import org.eclipse.jgit.errors.StopWalkException;
5fe7df 52 import org.eclipse.jgit.lib.Constants;
JM 53 import org.eclipse.jgit.lib.FileMode;
54 import org.eclipse.jgit.lib.ObjectId;
55 import org.eclipse.jgit.lib.ObjectLoader;
56 import org.eclipse.jgit.lib.PersonIdent;
57 import org.eclipse.jgit.lib.Ref;
58 import org.eclipse.jgit.lib.Repository;
a125cf 59 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
5fe7df 60 import org.eclipse.jgit.lib.StoredConfig;
JM 61 import org.eclipse.jgit.revwalk.RevBlob;
62 import org.eclipse.jgit.revwalk.RevCommit;
63 import org.eclipse.jgit.revwalk.RevObject;
45c0d6 64 import org.eclipse.jgit.revwalk.RevSort;
5fe7df 65 import org.eclipse.jgit.revwalk.RevTree;
JM 66 import org.eclipse.jgit.revwalk.RevWalk;
98ce17 67 import org.eclipse.jgit.revwalk.filter.RevFilter;
168566 68 import org.eclipse.jgit.storage.file.FileRepository;
831469 69 import org.eclipse.jgit.transport.CredentialsProvider;
168566 70 import org.eclipse.jgit.transport.FetchResult;
JM 71 import org.eclipse.jgit.transport.RefSpec;
5fe7df 72 import org.eclipse.jgit.treewalk.TreeWalk;
f602a2 73 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
c1c3c6 74 import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
5fe7df 75 import org.eclipse.jgit.treewalk.filter.PathFilter;
JM 76 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
c1c3c6 77 import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
87c3d7 78 import org.eclipse.jgit.treewalk.filter.TreeFilter;
a125cf 79 import org.eclipse.jgit.util.FS;
87c3d7 80 import org.eclipse.jgit.util.io.DisabledOutputStream;
5fe7df 81 import org.slf4j.Logger;
JM 82 import org.slf4j.LoggerFactory;
83
a125cf 84 import com.gitblit.models.GitNote;
1f9dae 85 import com.gitblit.models.PathModel;
f1720c 86 import com.gitblit.models.PathModel.PathChangeModel;
1f9dae 87 import com.gitblit.models.RefModel;
5fe7df 88
88598b 89 /**
JM 90  * Collection of static methods for retrieving information from a repository.
91  * 
92  * @author James Moger
93  * 
94  */
5fe7df 95 public class JGitUtils {
JM 96
424fe1 97     static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
a2709d 98
JM 99     /**
100      * Log an error message and exception.
101      * 
102      * @param t
103      * @param repository
104      *            if repository is not null it MUST be the {0} parameter in the
105      *            pattern.
106      * @param pattern
107      * @param objects
108      */
109     private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
110         List<Object> parameters = new ArrayList<Object>();
111         if (objects != null && objects.length > 0) {
112             for (Object o : objects) {
113                 parameters.add(o);
114             }
115         }
116         if (repository != null) {
117             parameters.add(0, repository.getDirectory().getAbsolutePath());
118         }
119         LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
120     }
a125cf 121
ed21d2 122     /**
JM 123      * Returns the displayable name of the person in the form "Real Name <email
124      * address>".  If the email address is empty, just "Real Name" is returned.
125      * 
126      * @param person
127      * @return "Real Name <email address>" or "Real Name"
128      */
a125cf 129     public static String getDisplayName(PersonIdent person) {
JM 130         if (StringUtils.isEmpty(person.getEmailAddress())) {
131             return person.getName();
132         }
133         final StringBuilder r = new StringBuilder();
134         r.append(person.getName());
135         r.append(" <");
136         r.append(person.getEmailAddress());
137         r.append('>');
138         return r.toString().trim();
139     }
bc9d4a 140
ed21d2 141     /**
831469 142      * Encapsulates the result of cloning or pulling from a repository.
JM 143      */
144     public static class CloneResult {
145         public FetchResult fetchResult;
146         public boolean createdRepository;
147     }
148
149     /**
ed21d2 150      * Clone or Fetch a repository. If the local repository does not exist,
JM 151      * clone is called. If the repository does exist, fetch is called. By
152      * default the clone/fetch retrieves the remote heads, tags, and notes.
153      * 
154      * @param repositoriesFolder
155      * @param name
156      * @param fromUrl
831469 157      * @return CloneResult
ed21d2 158      * @throws Exception
JM 159      */
831469 160     public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
008322 161             throws Exception {
831469 162         return cloneRepository(repositoriesFolder, name, fromUrl, null);
JM 163     }
164
165     /**
166      * Clone or Fetch a repository. If the local repository does not exist,
167      * clone is called. If the repository does exist, fetch is called. By
168      * default the clone/fetch retrieves the remote heads, tags, and notes.
169      * 
170      * @param repositoriesFolder
171      * @param name
172      * @param fromUrl
173      * @param credentialsProvider
174      * @return CloneResult
175      * @throws Exception
176      */
177     public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
178             CredentialsProvider credentialsProvider) throws Exception {
179         CloneResult result = new CloneResult();
168566 180         if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
JM 181             name += Constants.DOT_GIT_EXT;
182         }
183         File folder = new File(repositoriesFolder, name);
184         if (folder.exists()) {
185             File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
186             FileRepository repository = new FileRepository(gitDir);
831469 187             result.fetchResult = fetchRepository(credentialsProvider, repository);
168566 188             repository.close();
JM 189         } else {
190             CloneCommand clone = new CloneCommand();
191             clone.setBare(true);
192             clone.setCloneAllBranches(true);
193             clone.setURI(fromUrl);
194             clone.setDirectory(folder);
831469 195             if (credentialsProvider != null) {
JM 196                 clone.setCredentialsProvider(credentialsProvider);
197             }
168566 198             clone.call();
JM 199             // Now we have to fetch because CloneCommand doesn't fetch
200             // refs/notes nor does it allow manual RefSpec.
201             File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
202             FileRepository repository = new FileRepository(gitDir);
831469 203             result.createdRepository = true;
JM 204             result.fetchResult = fetchRepository(credentialsProvider, repository);
168566 205             repository.close();
JM 206         }
207         return result;
208     }
209
ed21d2 210     /**
JM 211      * Fetch updates from the remote repository. If refSpecs is unspecifed,
212      * remote heads, tags, and notes are retrieved.
213      * 
831469 214      * @param credentialsProvider
ed21d2 215      * @param repository
JM 216      * @param refSpecs
217      * @return FetchResult
218      * @throws Exception
219      */
831469 220     public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
JM 221             Repository repository, RefSpec... refSpecs) throws Exception {
168566 222         Git git = new Git(repository);
JM 223         FetchCommand fetch = git.fetch();
224         List<RefSpec> specs = new ArrayList<RefSpec>();
225         if (refSpecs == null || refSpecs.length == 0) {
226             specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
227             specs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
228             specs.add(new RefSpec("+refs/notes/*:refs/notes/*"));
229         } else {
230             specs.addAll(Arrays.asList(refSpecs));
231         }
831469 232         if (credentialsProvider != null) {
JM 233             fetch.setCredentialsProvider(credentialsProvider);
234         }
168566 235         fetch.setRefSpecs(specs);
2548a7 236         FetchResult fetchRes = fetch.call();
JM 237         return fetchRes;
238     }
239
240     /**
241      * Reset HEAD to the latest remote tracking commit.
242      * 
243      * @param repository
244      * @param remoteRef
245      *            the remote tracking reference (e.g. origin/master)
246      * @return Ref
247      * @throws Exception
248      */
249     public static Ref resetHEAD(Repository repository, String remoteRef) throws Exception {
250         if (!remoteRef.startsWith(Constants.R_REMOTES)) {
251             remoteRef = Constants.R_REMOTES + remoteRef;
252         }
253         Git git = new Git(repository);
254         ResetCommand reset = git.reset();
255         reset.setMode(ResetType.SOFT);
256         reset.setRef(remoteRef);
257         Ref result = reset.call();
168566 258         return result;
JM 259     }
260
ed21d2 261     /**
JM 262      * Creates a bare repository.
263      * 
264      * @param repositoriesFolder
265      * @param name
266      * @return Repository
267      */
168566 268     public static Repository createRepository(File repositoriesFolder, String name) {
JM 269         Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
f5d0ad 270         return git.getRepository();
JM 271     }
5fe7df 272
ed21d2 273     /**
JM 274      * Returns a list of repository names in the specified folder.
275      * 
276      * @param repositoriesFolder
277      * @param exportAll
278      *            if true, all repositories are listed. If false only the
279      *            repositories with a "git-daemon-export-ok" file are included
280      * @param searchSubfolders
281      *            recurse into subfolders to find grouped repositories
282      * @return list of repository names
283      */
2a7306 284     public static List<String> getRepositoryList(File repositoriesFolder, boolean exportAll,
a125cf 285             boolean searchSubfolders) {
5fe7df 286         List<String> list = new ArrayList<String>();
a125cf 287         if (repositoriesFolder == null || !repositoriesFolder.exists()) {
JM 288             return list;
289         }
290         list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
291                 exportAll, searchSubfolders));
5fe7df 292         Collections.sort(list);
JM 293         return list;
294     }
295
ed21d2 296     /**
JM 297      * Recursive function to find git repositories.
298      * 
299      * @param basePath
300      *            basePath is stripped from the repository name as repositories
301      *            are relative to this path
302      * @param searchFolder
303      * @param exportAll
304      *            if true all repositories are listed. If false only the
305      *            repositories with a "git-daemon-export-ok" file are included
306      * @param searchSubfolders
307      *            recurse into subfolders to find grouped repositories
308      * @return
309      */
a125cf 310     private static List<String> getRepositoryList(String basePath, File searchFolder,
JM 311             boolean exportAll, boolean searchSubfolders) {
5fe7df 312         List<String> list = new ArrayList<String>();
a125cf 313         for (File file : searchFolder.listFiles()) {
JM 314             if (file.isDirectory()) {
315                 File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
316                 if (gitDir != null) {
317                     boolean exportRepository = exportAll
318                             || new File(gitDir, "git-daemon-export-ok").exists();
bc9d4a 319
a125cf 320                     if (!exportRepository) {
JM 321                         continue;
f5d0ad 322                     }
a125cf 323                     // determine repository name relative to base path
JM 324                     String repository = StringUtils.getRelativePath(basePath,
325                             file.getAbsolutePath());
326                     list.add(repository);
327                 } else if (searchSubfolders) {
328                     // look for repositories in subfolders
329                     list.addAll(getRepositoryList(basePath, file, exportAll, searchSubfolders));
5fe7df 330                 }
JM 331             }
332         }
333         return list;
45c0d6 334     }
JM 335
ed21d2 336     /**
JM 337      * Returns the first commit on a branch. If the repository does not exist or
338      * is empty, null is returned.
339      * 
340      * @param repository
341      * @param branch
342      *            if unspecified, HEAD is assumed.
343      * @return RevCommit
344      */
345     public static RevCommit getFirstCommit(Repository repository, String branch) {
346         if (!hasCommits(repository)) {
bc9d4a 347             return null;
JM 348         }
a125cf 349         RevCommit commit = null;
45c0d6 350         try {
a2709d 351             // resolve branch
JM 352             ObjectId branchObject;
353             if (StringUtils.isEmpty(branch)) {
354                 branchObject = getDefaultBranch(repository);
355             } else {
356                 branchObject = repository.resolve(branch);
357             }
358
ed21d2 359             RevWalk walk = new RevWalk(repository);
45c0d6 360             walk.sort(RevSort.REVERSE);
a2709d 361             RevCommit head = walk.parseCommit(branchObject);
45c0d6 362             walk.markStart(head);
a125cf 363             commit = walk.next();
45c0d6 364             walk.dispose();
JM 365         } catch (Throwable t) {
a2709d 366             error(t, repository, "{0} failed to determine first commit");
45c0d6 367         }
a125cf 368         return commit;
45c0d6 369     }
JM 370
ed21d2 371     /**
JM 372      * Returns the date of the first commit on a branch. If the repository does
373      * not exist, Date(0) is returned. If the repository does exist bit is
374      * empty, the last modified date of the repository folder is returned.
375      * 
376      * @param repository
377      * @param branch
378      *            if unspecified, HEAD is assumed.
379      * @return Date of the first commit on a branch
380      */
381     public static Date getFirstChange(Repository repository, String branch) {
382         RevCommit commit = getFirstCommit(repository, branch);
f1720c 383         if (commit == null) {
ed21d2 384             if (repository == null || !repository.getDirectory().exists()) {
f1720c 385                 return new Date(0);
f5d0ad 386             }
f1720c 387             // fresh repository
ed21d2 388             return new Date(repository.getDirectory().lastModified());
45c0d6 389         }
f1720c 390         return getCommitDate(commit);
5fe7df 391     }
JM 392
ed21d2 393     /**
JM 394      * Determine if a repository has any commits. This is determined by checking
b1dba7 395      * the for loose and packed objects.
ed21d2 396      * 
JM 397      * @param repository
398      * @return true if the repository has commits
399      */
400     public static boolean hasCommits(Repository repository) {
a2709d 401         if (repository != null && repository.getDirectory().exists()) {
b1dba7 402             return (new File(repository.getDirectory(), "objects").list().length > 2)
330247 403                     || (new File(repository.getDirectory(), "objects/pack").list().length > 0);
1f9dae 404         }
db653a 405         return false;
bc9d4a 406     }
JM 407
ed21d2 408     /**
JM 409      * Returns the date of the most recent commit on a branch. If the repository
410      * does not exist Date(0) is returned. If it does exist but is empty, the
411      * last modified date of the repository folder is returned.
412      * 
413      * @param repository
414      * @param branch
a2709d 415      *            if unspecified, all branches are checked.
ed21d2 416      * @return
JM 417      */
418     public static Date getLastChange(Repository repository, String branch) {
419         if (!hasCommits(repository)) {
1f9dae 420             // null repository
ed21d2 421             if (repository == null) {
1f9dae 422                 return new Date(0);
JM 423             }
f5d0ad 424             // fresh repository
ed21d2 425             return new Date(repository.getDirectory().lastModified());
f5d0ad 426         }
ed21d2 427         if (StringUtils.isEmpty(branch)) {
a2709d 428             List<RefModel> branchModels = getLocalBranches(repository, true, -1);
JM 429             if (branchModels.size() > 0) {
430                 // find most recent branch update
431                 Date lastChange = new Date(0);
432                 for (RefModel branchModel : branchModels) {
433                     if (branchModel.getDate().after(lastChange)) {
434                         lastChange = branchModel.getDate();
435                     }
436                 }
437                 return lastChange;
438             } else {
439                 // try to find head
440                 branch = Constants.HEAD;
441             }
ed21d2 442         }
a2709d 443
JM 444         // lookup specified branch
ed21d2 445         RevCommit commit = getCommit(repository, branch);
5fe7df 446         return getCommitDate(commit);
JM 447     }
448
ed21d2 449     /**
JM 450      * Retrieves a Java Date from a Git commit.
451      * 
452      * @param commit
a2709d 453      * @return date of the commit or Date(0) if the commit is null
ed21d2 454      */
a125cf 455     public static Date getCommitDate(RevCommit commit) {
a2709d 456         if (commit == null) {
JM 457             return new Date(0);
458         }
a125cf 459         return new Date(commit.getCommitTime() * 1000L);
JM 460     }
461
ed21d2 462     /**
JM 463      * Returns the specified commit from the repository. If the repository does
464      * not exist or is empty, null is returned.
465      * 
466      * @param repository
467      * @param objectId
468      *            if unspecified, HEAD is assumed.
469      * @return RevCommit
470      */
471     public static RevCommit getCommit(Repository repository, String objectId) {
472         if (!hasCommits(repository)) {
bc9d4a 473             return null;
JM 474         }
a125cf 475         RevCommit commit = null;
5fe7df 476         try {
a2709d 477             // resolve object id
JM 478             ObjectId branchObject;
a125cf 479             if (StringUtils.isEmpty(objectId)) {
a2709d 480                 branchObject = getDefaultBranch(repository);
JM 481             } else {
482                 branchObject = repository.resolve(objectId);
608ece 483             }
ed21d2 484             RevWalk walk = new RevWalk(repository);
a2709d 485             RevCommit rev = walk.parseCommit(branchObject);
5fe7df 486             commit = rev;
JM 487             walk.dispose();
488         } catch (Throwable t) {
a2709d 489             error(t, repository, "{0} failed to get commit {1}", objectId);
5fe7df 490         }
JM 491         return commit;
492     }
493
ed21d2 494     /**
JM 495      * Retrieves the raw byte content of a file in the specified tree.
496      * 
497      * @param repository
498      * @param tree
499      *            if null, the RevTree from HEAD is assumed.
500      * @param path
501      * @return content as a byte []
502      */
503     public static byte[] getByteContent(Repository repository, RevTree tree, final String path) {
504         RevWalk rw = new RevWalk(repository);
505         TreeWalk tw = new TreeWalk(repository);
5fe7df 506         tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
a125cf 507         byte[] content = null;
5fe7df 508         try {
4ab184 509             if (tree == null) {
a2709d 510                 ObjectId object = getDefaultBranch(repository);
4ab184 511                 RevCommit commit = rw.parseCommit(object);
JM 512                 tree = commit.getTree();
a125cf 513             }
4ab184 514             tw.reset(tree);
5fe7df 515             while (tw.next()) {
JM 516                 if (tw.isSubtree() && !path.equals(tw.getPathString())) {
517                     tw.enterSubtree();
518                     continue;
519                 }
520                 ObjectId entid = tw.getObjectId(0);
521                 FileMode entmode = tw.getFileMode(0);
a125cf 522                 RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
5fe7df 523                 rw.parseBody(ro);
a125cf 524                 ByteArrayOutputStream os = new ByteArrayOutputStream();
ed21d2 525                 ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
a125cf 526                 byte[] tmp = new byte[4096];
JM 527                 InputStream in = ldr.openStream();
528                 int n;
529                 while ((n = in.read(tmp)) > 0) {
530                     os.write(tmp, 0, n);
531                 }
532                 in.close();
533                 content = os.toByteArray();
5fe7df 534             }
JM 535         } catch (Throwable t) {
a2709d 536             error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
5fe7df 537         } finally {
a125cf 538             rw.dispose();
JM 539             tw.release();
5fe7df 540         }
a125cf 541         return content;
5fe7df 542     }
JM 543
ed21d2 544     /**
JM 545      * Returns the UTF-8 string content of a file in the specified tree.
546      * 
547      * @param repository
548      * @param tree
549      *            if null, the RevTree from HEAD is assumed.
550      * @param blobPath
551      * @return UTF-8 string content
552      */
553     public static String getStringContent(Repository repository, RevTree tree, String blobPath) {
554         byte[] content = getByteContent(repository, tree, blobPath);
4ab184 555         if (content == null) {
JM 556             return null;
557         }
558         return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
559     }
560
ed21d2 561     /**
JM 562      * Gets the raw byte content of the specified blob object.
563      * 
564      * @param repository
565      * @param objectId
566      * @return byte [] blob content
567      */
568     public static byte[] getByteContent(Repository repository, String objectId) {
569         RevWalk rw = new RevWalk(repository);
4ab184 570         byte[] content = null;
JM 571         try {
572             RevBlob blob = rw.lookupBlob(ObjectId.fromString(objectId));
573             rw.parseBody(blob);
574             ByteArrayOutputStream os = new ByteArrayOutputStream();
ed21d2 575             ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
4ab184 576             byte[] tmp = new byte[4096];
JM 577             InputStream in = ldr.openStream();
578             int n;
579             while ((n = in.read(tmp)) > 0) {
580                 os.write(tmp, 0, n);
581             }
582             in.close();
583             content = os.toByteArray();
584         } catch (Throwable t) {
a2709d 585             error(t, repository, "{0} can't find blob {1}", objectId);
4ab184 586         } finally {
JM 587             rw.dispose();
588         }
589         return content;
590     }
591
ed21d2 592     /**
JM 593      * Gets the UTF-8 string content of the blob specified by objectId.
594      * 
595      * @param repository
596      * @param objectId
597      * @return UTF-8 string content
598      */
599     public static String getStringContent(Repository repository, String objectId) {
600         byte[] content = getByteContent(repository, objectId);
a125cf 601         if (content == null) {
JM 602             return null;
603         }
166e6a 604         return new String(content, Charset.forName(Constants.CHARACTER_ENCODING));
5fe7df 605     }
JM 606
ed21d2 607     /**
JM 608      * Returns the list of files in the specified folder at the specified
609      * commit. If the repository does not exist or is empty, an empty list is
610      * returned.
611      * 
612      * @param repository
613      * @param path
614      *            if unspecified, root folder is assumed.
615      * @param commit
616      *            if null, HEAD is assumed.
617      * @return list of files in specified path
618      */
619     public static List<PathModel> getFilesInPath(Repository repository, String path,
620             RevCommit commit) {
5fe7df 621         List<PathModel> list = new ArrayList<PathModel>();
ed21d2 622         if (!hasCommits(repository)) {
f5d0ad 623             return list;
JM 624         }
a125cf 625         if (commit == null) {
a2709d 626             commit = getCommit(repository, null);
a125cf 627         }
ed21d2 628         final TreeWalk tw = new TreeWalk(repository);
5fe7df 629         try {
a125cf 630             tw.addTree(commit.getTree());
ed21d2 631             if (!StringUtils.isEmpty(path)) {
JM 632                 PathFilter f = PathFilter.create(path);
a125cf 633                 tw.setFilter(f);
JM 634                 tw.setRecursive(false);
5fe7df 635                 boolean foundFolder = false;
a125cf 636                 while (tw.next()) {
JM 637                     if (!foundFolder && tw.isSubtree()) {
638                         tw.enterSubtree();
5fe7df 639                     }
ed21d2 640                     if (tw.getPathString().equals(path)) {
5fe7df 641                         foundFolder = true;
JM 642                         continue;
643                     }
644                     if (foundFolder) {
ed21d2 645                         list.add(getPathModel(tw, path, commit));
5fe7df 646                     }
JM 647                 }
648             } else {
a125cf 649                 tw.setRecursive(false);
JM 650                 while (tw.next()) {
651                     list.add(getPathModel(tw, null, commit));
5fe7df 652                 }
JM 653             }
654         } catch (IOException e) {
a2709d 655             error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
5fe7df 656         } finally {
a125cf 657             tw.release();
5fe7df 658         }
JM 659         Collections.sort(list);
660         return list;
661     }
662
ed21d2 663     /**
JM 664      * Returns the list of files changed in a specified commit. If the
665      * repository does not exist or is empty, an empty list is returned.
666      * 
667      * @param repository
668      * @param commit
669      *            if null, HEAD is assumed.
670      * @return list of files changed in a commit
671      */
672     public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) {
9bc17d 673         List<PathChangeModel> list = new ArrayList<PathChangeModel>();
ed21d2 674         if (!hasCommits(repository)) {
JM 675             return list;
676         }
677         RevWalk rw = new RevWalk(repository);
5fe7df 678         try {
a125cf 679             if (commit == null) {
a2709d 680                 ObjectId object = getDefaultBranch(repository);
a125cf 681                 commit = rw.parseCommit(object);
85c2e6 682             }
5fe7df 683
f1720c 684             if (commit.getParentCount() == 0) {
ed21d2 685                 TreeWalk tw = new TreeWalk(repository);
5450d0 686                 tw.reset();
JM 687                 tw.setRecursive(true);
688                 tw.addTree(commit.getTree());
a125cf 689                 while (tw.next()) {
JM 690                     list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
691                             .getRawMode(0), commit.getId().getName(), ChangeType.ADD));
f1720c 692                 }
5450d0 693                 tw.release();
f1720c 694             } else {
JM 695                 RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
696                 DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
ed21d2 697                 df.setRepository(repository);
5450d0 698                 df.setDiffComparator(RawTextComparator.DEFAULT);
f1720c 699                 df.setDetectRenames(true);
5450d0 700                 List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
f1720c 701                 for (DiffEntry diff : diffs) {
JM 702                     if (diff.getChangeType().equals(ChangeType.DELETE)) {
703                         list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
704                                 .getNewMode().getBits(), commit.getId().getName(), diff
705                                 .getChangeType()));
706                     } else {
707                         list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
708                                 .getNewMode().getBits(), commit.getId().getName(), diff
709                                 .getChangeType()));
710                     }
9bc17d 711                 }
5fe7df 712             }
87c3d7 713         } catch (Throwable t) {
a2709d 714             error(t, repository, "{0} failed to determine files in commit!");
a125cf 715         } finally {
85c2e6 716             rw.dispose();
5fe7df 717         }
c1c3c6 718         return list;
JM 719     }
bc9d4a 720
ed21d2 721     /**
JM 722      * Returns the list of files in the repository that match one of the
723      * specified extensions. This is a CASE-SENSITIVE search. If the repository
724      * does not exist or is empty, an empty list is returned.
725      * 
726      * @param repository
727      * @param extensions
728      * @return list of files in repository with a matching extension
729      */
730     public static List<PathModel> getDocuments(Repository repository, List<String> extensions) {
c1c3c6 731         List<PathModel> list = new ArrayList<PathModel>();
ed21d2 732         if (!hasCommits(repository)) {
JM 733             return list;
734         }
a2709d 735         RevCommit commit = getCommit(repository, null);
ed21d2 736         final TreeWalk tw = new TreeWalk(repository);
c1c3c6 737         try {
a125cf 738             tw.addTree(commit.getTree());
c1c3c6 739             if (extensions != null && extensions.size() > 0) {
JM 740                 Collection<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
bc9d4a 741                 for (String extension : extensions) {
c1c3c6 742                     if (extension.charAt(0) == '.') {
a125cf 743                         suffixFilters.add(PathSuffixFilter.create("\\" + extension));
c1c3c6 744                     } else {
JM 745                         // escape the . since this is a regexp filter
746                         suffixFilters.add(PathSuffixFilter.create("\\." + extension));
747                     }
748                 }
749                 TreeFilter filter = OrTreeFilter.create(suffixFilters);
a125cf 750                 tw.setFilter(filter);
JM 751                 tw.setRecursive(true);
752             }
753             while (tw.next()) {
754                 list.add(getPathModel(tw, null, commit));
c1c3c6 755             }
JM 756         } catch (IOException e) {
a2709d 757             error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
c1c3c6 758         } finally {
a125cf 759             tw.release();
c1c3c6 760         }
JM 761         Collections.sort(list);
5fe7df 762         return list;
87c3d7 763     }
JM 764
ed21d2 765     /**
JM 766      * Returns a path model of the current file in the treewalk.
767      * 
768      * @param tw
769      * @param basePath
770      * @param commit
771      * @return a path model of the current file in the treewalk
772      */
a125cf 773     private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
5fe7df 774         String name;
JM 775         long size = 0;
a125cf 776         if (StringUtils.isEmpty(basePath)) {
JM 777             name = tw.getPathString();
5fe7df 778         } else {
a125cf 779             name = tw.getPathString().substring(basePath.length() + 1);
5fe7df 780         }
JM 781         try {
a125cf 782             if (!tw.isSubtree()) {
JM 783                 size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
5fe7df 784             }
JM 785         } catch (Throwable t) {
a2709d 786             error(t, null, "failed to retrieve blob size for " + tw.getPathString());
5fe7df 787         }
a125cf 788         return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
2a7306 789                 commit.getName());
5fe7df 790     }
JM 791
ed21d2 792     /**
JM 793      * Returns a permissions representation of the mode bits.
794      * 
795      * @param mode
796      * @return string representation of the mode bits
797      */
5fe7df 798     public static String getPermissionsFromMode(int mode) {
JM 799         if (FileMode.TREE.equals(mode)) {
800             return "drwxr-xr-x";
801         } else if (FileMode.REGULAR_FILE.equals(mode)) {
802             return "-rw-r--r--";
803         } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
804             return "-rwxr-xr-x";
805         } else if (FileMode.SYMLINK.equals(mode)) {
806             // FIXME symlink permissions
807             return "symlink";
808         } else if (FileMode.GITLINK.equals(mode)) {
809             // FIXME gitlink permissions
810             return "gitlink";
811         }
a125cf 812         // FIXME missing permissions
JM 813         return "missing";
5fe7df 814     }
JM 815
ed21d2 816     /**
JM 817      * Returns a list of commits starting from HEAD and working backwards.
818      * 
819      * @param repository
820      * @param maxCount
821      *            if < 0, all commits for the repository are returned.
822      * @return list of commits
823      */
824     public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
a2709d 825         return getRevLog(repository, null, 0, maxCount);
ef5c58 826     }
JM 827
ed21d2 828     /**
JM 829      * Returns a list of commits starting from the specified objectId using an
830      * offset and maxCount for paging. This is similar to LIMIT n OFFSET p in
831      * SQL. If the repository does not exist or is empty, an empty list is
832      * returned.
833      * 
834      * @param repository
835      * @param objectId
836      *            if unspecified, HEAD is assumed.
837      * @param offset
838      * @param maxCount
839      *            if < 0, all commits are returned.
840      * @return a paged list of commits
841      */
842     public static List<RevCommit> getRevLog(Repository repository, String objectId, int offset,
2a7306 843             int maxCount) {
ed21d2 844         return getRevLog(repository, objectId, null, offset, maxCount);
JM 845     }
846
847     /**
848      * Returns a list of commits for the repository or a path within the
849      * repository. Caller may specify ending revision with objectId. Caller may
850      * specify offset and maxCount to achieve pagination of results. If the
851      * repository does not exist or is empty, an empty list is returned.
852      * 
853      * @param repository
854      * @param objectId
855      *            if unspecified, HEAD is assumed.
856      * @param path
857      *            if unspecified, commits for repository are returned. If
858      *            specified, commits for the path are returned.
859      * @param offset
860      * @param maxCount
861      *            if < 0, all commits are returned.
862      * @return a paged list of commits
863      */
864     public static List<RevCommit> getRevLog(Repository repository, String objectId, String path,
865             int offset, int maxCount) {
5fe7df 866         List<RevCommit> list = new ArrayList<RevCommit>();
85c2e6 867         if (maxCount == 0) {
JM 868             return list;
869         }
ed21d2 870         if (!hasCommits(repository)) {
bc9d4a 871             return list;
JM 872         }
5fe7df 873         try {
a2709d 874             // resolve branch
JM 875             ObjectId branchObject;
a125cf 876             if (StringUtils.isEmpty(objectId)) {
a2709d 877                 branchObject = getDefaultBranch(repository);
JM 878             } else {
879                 branchObject = repository.resolve(objectId);
ef5c58 880             }
a2709d 881
ed21d2 882             RevWalk rw = new RevWalk(repository);
a2709d 883             rw.markStart(rw.parseCommit(branchObject));
f602a2 884             if (!StringUtils.isEmpty(path)) {
2a7306 885                 TreeFilter filter = AndTreeFilter.create(
JM 886                         PathFilterGroup.createFromStrings(Collections.singleton(path)),
887                         TreeFilter.ANY_DIFF);
a125cf 888                 rw.setTreeFilter(filter);
f602a2 889             }
a125cf 890             Iterable<RevCommit> revlog = rw;
ef5c58 891             if (offset > 0) {
JM 892                 int count = 0;
893                 for (RevCommit rev : revlog) {
894                     count++;
895                     if (count > offset) {
896                         list.add(rev);
897                         if (maxCount > 0 && list.size() == maxCount) {
898                             break;
899                         }
900                     }
901                 }
902             } else {
903                 for (RevCommit rev : revlog) {
904                     list.add(rev);
905                     if (maxCount > 0 && list.size() == maxCount) {
906                         break;
907                     }
5fe7df 908                 }
JM 909             }
a125cf 910             rw.dispose();
5fe7df 911         } catch (Throwable t) {
a2709d 912             error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
5fe7df 913         }
JM 914         return list;
915     }
916
88598b 917     /**
JM 918      * Enumeration of the search types.
919      */
98ce17 920     public static enum SearchType {
JM 921         AUTHOR, COMMITTER, COMMIT;
922
923         public static SearchType forName(String name) {
924             for (SearchType type : values()) {
925                 if (type.name().equalsIgnoreCase(name)) {
926                     return type;
927                 }
928             }
a125cf 929             return COMMIT;
98ce17 930         }
45c0d6 931
a125cf 932         @Override
7203a4 933         public String toString() {
JM 934             return name().toLowerCase();
935         }
98ce17 936     }
JM 937
ed21d2 938     /**
JM 939      * Search the commit history for a case-insensitive match to the value.
940      * Search results require a specified SearchType of AUTHOR, COMMITTER, or
941      * COMMIT. Results may be paginated using offset and maxCount. If the
942      * repository does not exist or is empty, an empty list is returned.
943      * 
944      * @param repository
945      * @param objectId
946      *            if unspecified, HEAD is assumed.
947      * @param value
948      * @param type
949      *            AUTHOR, COMMITTER, COMMIT
950      * @param offset
951      * @param maxCount
952      *            if < 0, all matches are returned
953      * @return matching list of commits
954      */
955     public static List<RevCommit> searchRevlogs(Repository repository, String objectId,
956             String value, final SearchType type, int offset, int maxCount) {
98ce17 957         final String lcValue = value.toLowerCase();
JM 958         List<RevCommit> list = new ArrayList<RevCommit>();
85c2e6 959         if (maxCount == 0) {
JM 960             return list;
961         }
ed21d2 962         if (!hasCommits(repository)) {
bc9d4a 963             return list;
JM 964         }
98ce17 965         try {
a2709d 966             // resolve branch
JM 967             ObjectId branchObject;
a125cf 968             if (StringUtils.isEmpty(objectId)) {
a2709d 969                 branchObject = getDefaultBranch(repository);
JM 970             } else {
971                 branchObject = repository.resolve(objectId);
98ce17 972             }
a2709d 973
ed21d2 974             RevWalk rw = new RevWalk(repository);
a125cf 975             rw.setRevFilter(new RevFilter() {
98ce17 976
JM 977                 @Override
978                 public RevFilter clone() {
ed21d2 979                     // FindBugs complains about this method name.
JM 980                     // This is part of JGit design and unrelated to Cloneable.
98ce17 981                     return this;
JM 982                 }
983
984                 @Override
2a7306 985                 public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException,
JM 986                         MissingObjectException, IncorrectObjectTypeException, IOException {
a125cf 987                     boolean include = false;
98ce17 988                     switch (type) {
JM 989                     case AUTHOR:
a125cf 990                         include = (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
2a7306 991                                 || (commit.getAuthorIdent().getEmailAddress().toLowerCase()
JM 992                                         .indexOf(lcValue) > -1);
a125cf 993                         break;
98ce17 994                     case COMMITTER:
a125cf 995                         include = (commit.getCommitterIdent().getName().toLowerCase()
JM 996                                 .indexOf(lcValue) > -1)
2a7306 997                                 || (commit.getCommitterIdent().getEmailAddress().toLowerCase()
JM 998                                         .indexOf(lcValue) > -1);
a125cf 999                         break;
98ce17 1000                     case COMMIT:
a125cf 1001                         include = commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
JM 1002                         break;
98ce17 1003                     }
a125cf 1004                     return include;
98ce17 1005                 }
JM 1006
1007             });
a2709d 1008             rw.markStart(rw.parseCommit(branchObject));
a125cf 1009             Iterable<RevCommit> revlog = rw;
98ce17 1010             if (offset > 0) {
JM 1011                 int count = 0;
1012                 for (RevCommit rev : revlog) {
1013                     count++;
1014                     if (count > offset) {
1015                         list.add(rev);
1016                         if (maxCount > 0 && list.size() == maxCount) {
1017                             break;
1018                         }
1019                     }
1020                 }
1021             } else {
1022                 for (RevCommit rev : revlog) {
1023                     list.add(rev);
1024                     if (maxCount > 0 && list.size() == maxCount) {
1025                         break;
1026                     }
1027                 }
1028             }
a125cf 1029             rw.dispose();
98ce17 1030         } catch (Throwable t) {
a2709d 1031             error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
98ce17 1032         }
JM 1033         return list;
a2709d 1034     }
JM 1035
1036     /**
1037      * Returns the default branch to use for a repository. Normally returns
1038      * whatever branch HEAD points to, but if HEAD points to nothing it returns
1039      * the most recently updated branch.
1040      * 
1041      * @param repository
1042      * @return the objectid of a branch
1043      * @throws Exception
1044      */
1045     public static ObjectId getDefaultBranch(Repository repository) throws Exception {
1046         ObjectId object = repository.resolve(Constants.HEAD);
1047         if (object == null) {
1048             // no HEAD
1049             // perhaps non-standard repository, try local branches
1050             List<RefModel> branchModels = getLocalBranches(repository, true, -1);
1051             if (branchModels.size() > 0) {
1052                 // use most recently updated branch
1053                 RefModel branch = null;
1054                 Date lastDate = new Date(0);
1055                 for (RefModel branchModel : branchModels) {
1056                     if (branchModel.getDate().after(lastDate)) {
1057                         branch = branchModel;
1058                         lastDate = branch.getDate();
1059                     }
1060                 }
1061                 object = branch.getReferencedObjectId();
1062             }
1063         }
1064         return object;
98ce17 1065     }
JM 1066
ed21d2 1067     /**
JM 1068      * Returns all refs grouped by their associated object id.
1069      * 
1070      * @param repository
1071      * @return all refs grouped by their referenced object id
1072      */
1073     public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) {
1074         List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
1075         Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
1076         for (RefModel ref : list) {
1077             ObjectId objectid = ref.getReferencedObjectId();
1078             if (!refs.containsKey(objectid)) {
1079                 refs.put(objectid, new ArrayList<RefModel>());
1080             }
1081             refs.get(objectid).add(ref);
1082         }
1083         return refs;
5fe7df 1084     }
JM 1085
ed21d2 1086     /**
JM 1087      * Returns the list of tags in the repository. If repository does not exist
1088      * or is empty, an empty list is returned.
1089      * 
1090      * @param repository
1091      * @param fullName
1092      *            if true, /refs/tags/yadayadayada is returned. If false,
1093      *            yadayadayada is returned.
1094      * @param maxCount
1095      *            if < 0, all tags are returned
1096      * @return list of tags
1097      */
1098     public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount) {
1099         return getRefs(repository, Constants.R_TAGS, fullName, maxCount);
232890 1100     }
JM 1101
ed21d2 1102     /**
JM 1103      * Returns the list of local branches in the repository. If repository does
1104      * not exist or is empty, an empty list is returned.
1105      * 
1106      * @param repository
1107      * @param fullName
1108      *            if true, /refs/heads/yadayadayada is returned. If false,
1109      *            yadayadayada is returned.
1110      * @param maxCount
1111      *            if < 0, all local branches are returned
1112      * @return list of local branches
1113      */
1114     public static List<RefModel> getLocalBranches(Repository repository, boolean fullName,
1115             int maxCount) {
1116         return getRefs(repository, Constants.R_HEADS, fullName, maxCount);
5fe7df 1117     }
JM 1118
ed21d2 1119     /**
JM 1120      * Returns the list of remote branches in the repository. If repository does
1121      * not exist or is empty, an empty list is returned.
1122      * 
1123      * @param repository
1124      * @param fullName
1125      *            if true, /refs/remotes/yadayadayada is returned. If false,
1126      *            yadayadayada is returned.
1127      * @param maxCount
1128      *            if < 0, all remote branches are returned
1129      * @return list of remote branches
1130      */
1131     public static List<RefModel> getRemoteBranches(Repository repository, boolean fullName,
1132             int maxCount) {
1133         return getRefs(repository, Constants.R_REMOTES, fullName, maxCount);
a125cf 1134     }
JM 1135
ed21d2 1136     /**
JM 1137      * Returns the list of note branches. If repository does not exist or is
1138      * empty, an empty list is returned.
1139      * 
1140      * @param repository
1141      * @param fullName
1142      *            if true, /refs/notes/yadayadayada is returned. If false,
1143      *            yadayadayada is returned.
1144      * @param maxCount
1145      *            if < 0, all note branches are returned
1146      * @return list of note branches
1147      */
1148     public static List<RefModel> getNoteBranches(Repository repository, boolean fullName,
1149             int maxCount) {
1150         return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
1151     }
1152
1153     /**
1154      * Returns a list of references in the repository matching "refs". If the
1155      * repository is null or empty, an empty list is returned.
1156      * 
1157      * @param repository
1158      * @param refs
1159      *            if unspecified, all refs are returned
1160      * @param fullName
1161      *            if true, /refs/something/yadayadayada is returned. If false,
1162      *            yadayadayada is returned.
1163      * @param maxCount
1164      *            if < 0, all references are returned
1165      * @return list of references
1166      */
1167     private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
1168             int maxCount) {
5fe7df 1169         List<RefModel> list = new ArrayList<RefModel>();
85c2e6 1170         if (maxCount == 0) {
JM 1171             return list;
1172         }
ed21d2 1173         if (!hasCommits(repository)) {
JM 1174             return list;
1175         }
5fe7df 1176         try {
ed21d2 1177             Map<String, Ref> map = repository.getRefDatabase().getRefs(refs);
JM 1178             RevWalk rw = new RevWalk(repository);
2a7306 1179             for (Entry<String, Ref> entry : map.entrySet()) {
JM 1180                 Ref ref = entry.getValue();
4ab184 1181                 RevObject object = rw.parseAny(ref.getObjectId());
5cc4f2 1182                 String name = entry.getKey();
JM 1183                 if (fullName && !StringUtils.isEmpty(refs)) {
1184                     name = refs + name;
1185                 }
1186                 list.add(new RefModel(name, ref, object));
5fe7df 1187             }
4ab184 1188             rw.dispose();
5fe7df 1189             Collections.sort(list);
JM 1190             Collections.reverse(list);
1191             if (maxCount > 0 && list.size() > maxCount) {
fb01c9 1192                 list = new ArrayList<RefModel>(list.subList(0, maxCount));
5fe7df 1193             }
JM 1194         } catch (IOException e) {
a2709d 1195             error(e, repository, "{0} failed to retrieve {1}", refs);
5fe7df 1196         }
JM 1197         return list;
1198     }
1199
ed21d2 1200     /**
JM 1201      * Returns the list of notes entered about the commit from the refs/notes
1202      * namespace. If the repository does not exist or is empty, an empty list is
1203      * returned.
1204      * 
1205      * @param repository
1206      * @param commit
1207      * @return list of notes
1208      */
a125cf 1209     public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
JM 1210         List<GitNote> list = new ArrayList<GitNote>();
ed21d2 1211         if (!hasCommits(repository)) {
JM 1212             return list;
1213         }
1214         List<RefModel> noteBranches = getNoteBranches(repository, true, -1);
1215         for (RefModel notesRef : noteBranches) {
4ab184 1216             RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree();
a125cf 1217             StringBuilder sb = new StringBuilder(commit.getName());
JM 1218             sb.insert(2, '/');
4ab184 1219             String notePath = sb.toString();
JM 1220             String text = getStringContent(repository, notesTree, notePath);
a125cf 1221             if (!StringUtils.isEmpty(text)) {
4ab184 1222                 List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
JM 1223                 RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
1224                         .size() - 1));
1225                 GitNote gitNote = new GitNote(noteRef, text);
a125cf 1226                 list.add(gitNote);
5fe7df 1227             }
JM 1228         }
a125cf 1229         return list;
5fe7df 1230     }
JM 1231
ed21d2 1232     /**
a2709d 1233      * Create an orphaned branch in a repository. This code does not work.
JM 1234      * 
1235      * @param repository
1236      * @param name
1237      * @return
1238      */
1239     public static boolean createOrphanBranch(Repository repository, String name) {
1240         return true;
1241         // boolean success = false;
1242         // try {
1243         // ObjectId prev = repository.resolve(Constants.HEAD + "^1");
1244         // // create the orphan branch
1245         // RefUpdate orphanRef = repository.updateRef(Constants.R_HEADS + name);
1246         // orphanRef.setNewObjectId(prev);
1247         // orphanRef.setExpectedOldObjectId(ObjectId.zeroId());
1248         // Result updateResult = orphanRef.update();
1249         //
1250         // switch (updateResult) {
1251         // case NEW:
1252         // success = true;
1253         // break;
1254         // case NO_CHANGE:
1255         // default:
1256         // break;
1257         // }
1258         //
1259         // } catch (Throwable t) {
1260         // error(t, repository, "{0} failed to create orphaned branch {1}",
1261         // name);
1262         // }
1263         // return success;
1264     }
1265
1266     /**
ed21d2 1267      * Returns a StoredConfig object for the repository.
JM 1268      * 
1269      * @param repository
1270      * @return the StoredConfig of the repository
1271      */
1272     public static StoredConfig readConfig(Repository repository) {
1273         StoredConfig c = repository.getConfig();
a125cf 1274         try {
JM 1275             c.load();
1276         } catch (ConfigInvalidException cex) {
a2709d 1277             error(cex, repository, "{0} configuration is invalid!");
a125cf 1278         } catch (IOException cex) {
a2709d 1279             error(cex, repository, "Could not open configuration for {0}!");
5fe7df 1280         }
a125cf 1281         return c;
5fe7df 1282     }
fc8426 1283
ed21d2 1284     /**
JM 1285      * Zips the contents of the tree at the (optionally) specified revision and
1286      * the (optionally) specified basepath to the supplied outputstream.
1287      * 
1288      * @param repository
1289      * @param basePath
1290      *            if unspecified, entire repository is assumed.
1291      * @param objectId
1292      *            if unspecified, HEAD is assumed.
1293      * @param os
1294      * @return true if repository was successfully zipped to supplied output
1295      *         stream
1296      */
1297     public static boolean zip(Repository repository, String basePath, String objectId,
1298             OutputStream os) {
1299         RevCommit commit = getCommit(repository, objectId);
9197d3 1300         if (commit == null) {
JM 1301             return false;
1302         }
a125cf 1303         boolean success = false;
ed21d2 1304         RevWalk rw = new RevWalk(repository);
JM 1305         TreeWalk tw = new TreeWalk(repository);
9197d3 1306         try {
a125cf 1307             tw.addTree(commit.getTree());
9197d3 1308             ZipOutputStream zos = new ZipOutputStream(os);
ed21d2 1309             zos.setComment("Generated by Gitblit");
a125cf 1310             if (!StringUtils.isEmpty(basePath)) {
9197d3 1311                 PathFilter f = PathFilter.create(basePath);
a125cf 1312                 tw.setFilter(f);
9197d3 1313             }
a125cf 1314             tw.setRecursive(true);
JM 1315             while (tw.next()) {
1316                 ZipEntry entry = new ZipEntry(tw.getPathString());
1317                 entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
2a7306 1318                         Constants.OBJ_BLOB));
9197d3 1319                 entry.setComment(commit.getName());
JM 1320                 zos.putNextEntry(entry);
1321
a125cf 1322                 ObjectId entid = tw.getObjectId(0);
JM 1323                 FileMode entmode = tw.getFileMode(0);
9197d3 1324                 RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType());
JM 1325                 rw.parseBody(blob);
1326
ed21d2 1327                 ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
9197d3 1328                 byte[] tmp = new byte[4096];
JM 1329                 InputStream in = ldr.openStream();
1330                 int n;
1331                 while ((n = in.read(tmp)) > 0) {
1332                     zos.write(tmp, 0, n);
1333                 }
1334                 in.close();
1335             }
1336             zos.finish();
a125cf 1337             success = true;
9197d3 1338         } catch (IOException e) {
a2709d 1339             error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
9197d3 1340         } finally {
a125cf 1341             tw.release();
9197d3 1342             rw.dispose();
JM 1343         }
a125cf 1344         return success;
9197d3 1345     }
5fe7df 1346 }