Drop StGit support
[egit/imyousuf.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blobb63ef182657137981afba0184404153c1fd2665c
1 /*
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
10 * conditions are met:
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
23 * written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org.spearce.jgit.lib;
42 import java.io.BufferedReader;
43 import java.io.File;
44 import java.io.FileNotFoundException;
45 import java.io.FileReader;
46 import java.io.FilenameFilter;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 import java.util.Vector;
59 import org.spearce.jgit.errors.IncorrectObjectTypeException;
60 import org.spearce.jgit.errors.RevisionSyntaxException;
61 import org.spearce.jgit.util.FS;
63 /**
64 * Represents a Git repository. A repository holds all objects and refs used for
65 * managing source code (could by any type of file, but source code is what
66 * SCM's are typically used for).
68 * In Git terms all data is stored in GIT_DIR, typically a directory called
69 * .git. A work tree is maintained unless the repository is a bare repository.
70 * Typically the .git directory is located at the root of the work dir.
72 * <ul>
73 * <li>GIT_DIR
74 * <ul>
75 * <li>objects/ - objects</li>
76 * <li>refs/ - tags and heads</li>
77 * <li>config - configuration</li>
78 * <li>info/ - more configurations</li>
79 * </ul>
80 * </li>
81 * </ul>
83 * This implementation only handles a subtly undocumented subset of git features.
86 public class Repository {
87 private final File gitDir;
89 private final File[] objectsDirs;
91 private final RepositoryConfig config;
93 private final RefDatabase refs;
95 private PackFile[] packs;
97 private GitIndex index;
99 private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
100 static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
103 * Construct a representation of a Git repository.
105 * @param d
106 * GIT_DIR (the location of the repository metadata).
107 * @throws IOException
108 * the repository appears to already exist but cannot be
109 * accessed.
111 public Repository(final File d) throws IOException {
112 gitDir = d.getAbsoluteFile();
113 try {
114 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
115 new ArrayList<File>()).toArray(new File[0]);
116 } catch (IOException e) {
117 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
118 ex.initCause(e);
119 throw ex;
121 refs = new RefDatabase(this);
122 packs = new PackFile[0];
123 config = new RepositoryConfig(this);
125 final boolean isExisting = objectsDirs[0].exists();
126 if (isExisting) {
127 getConfig().load();
128 final String repositoryFormatVersion = getConfig().getString(
129 "core", null, "repositoryFormatVersion");
130 if (!"0".equals(repositoryFormatVersion)) {
131 throw new IOException("Unknown repository format \""
132 + repositoryFormatVersion + "\"; expected \"0\".");
134 } else {
135 getConfig().create();
137 if (isExisting)
138 scanForPacks();
141 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
142 ret.add(objectsDir);
143 final File altFile = FS.resolve(objectsDir, "info/alternates");
144 if (altFile.exists()) {
145 BufferedReader ar = new BufferedReader(new FileReader(altFile));
146 try {
147 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
148 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
150 } catch (Exception e) {
151 ar.close();
154 return ret;
158 * Create a new Git repository initializing the necessary files and
159 * directories.
161 * @throws IOException
163 public void create() throws IOException {
164 if (gitDir.exists()) {
165 throw new IllegalStateException("Repository already exists: "
166 + gitDir);
169 gitDir.mkdirs();
170 refs.create();
172 objectsDirs[0].mkdirs();
173 new File(objectsDirs[0], "pack").mkdir();
174 new File(objectsDirs[0], "info").mkdir();
176 new File(gitDir, "branches").mkdir();
177 new File(gitDir, "remotes").mkdir();
178 final String master = Constants.R_HEADS + Constants.MASTER;
179 refs.link(Constants.HEAD, master);
181 getConfig().create();
182 getConfig().save();
186 * @return GIT_DIR
188 public File getDirectory() {
189 return gitDir;
193 * @return the directory containing the objects owned by this repository.
195 public File getObjectsDirectory() {
196 return objectsDirs[0];
200 * @return the configuration of this repository
202 public RepositoryConfig getConfig() {
203 return config;
207 * Construct a filename where the loose object having a specified SHA-1
208 * should be stored. If the object is stored in a shared repository the path
209 * to the alternative repo will be returned. If the object is not yet store
210 * a usable path in this repo will be returned. It is assumed that callers
211 * will look for objects in a pack first.
213 * @param objectId
214 * @return suggested file name
216 public File toFile(final AnyObjectId objectId) {
217 final String n = objectId.name();
218 String d=n.substring(0, 2);
219 String f=n.substring(2);
220 for (int i=0; i<objectsDirs.length; ++i) {
221 File ret = new File(new File(objectsDirs[i], d), f);
222 if (ret.exists())
223 return ret;
225 return new File(new File(objectsDirs[0], d), f);
229 * @param objectId
230 * @return true if the specified object is stored in this repo or any of the
231 * known shared repositories.
233 public boolean hasObject(final AnyObjectId objectId) {
234 int k = packs.length;
235 if (k > 0) {
236 do {
237 if (packs[--k].hasObject(objectId))
238 return true;
239 } while (k > 0);
241 return toFile(objectId).isFile();
245 * @param id
246 * SHA-1 of an object.
248 * @return a {@link ObjectLoader} for accessing the data of the named
249 * object, or null if the object does not exist.
250 * @throws IOException
252 public ObjectLoader openObject(final AnyObjectId id)
253 throws IOException {
254 return openObject(new WindowCursor(),id);
258 * @param curs
259 * temporary working space associated with the calling thread.
260 * @param id
261 * SHA-1 of an object.
263 * @return a {@link ObjectLoader} for accessing the data of the named
264 * object, or null if the object does not exist.
265 * @throws IOException
267 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
268 throws IOException {
269 int k = packs.length;
270 if (k > 0) {
271 do {
272 try {
273 final ObjectLoader ol = packs[--k].get(curs, id);
274 if (ol != null)
275 return ol;
276 } catch (IOException ioe) {
277 // This shouldn't happen unless the pack was corrupted
278 // after we opened it or the VM runs out of memory. This is
279 // a know problem with memory mapped I/O in java and have
280 // been noticed with JDK < 1.6. Tell the gc that now is a good
281 // time to collect and try once more.
282 try {
283 curs.release();
284 System.gc();
285 final ObjectLoader ol = packs[k].get(curs, id);
286 if (ol != null)
287 return ol;
288 } catch (IOException ioe2) {
289 ioe2.printStackTrace();
290 ioe.printStackTrace();
291 // Still fails.. that's BAD, maybe the pack has
292 // been corrupted after all, or the gc didn't manage
293 // to release enough previously mmaped areas.
296 } while (k > 0);
298 try {
299 return new UnpackedObjectLoader(this, id.toObjectId());
300 } catch (FileNotFoundException fnfe) {
301 return null;
306 * Open object in all packs containing specified object.
308 * @param objectId
309 * id of object to search for
310 * @param curs
311 * temporary working space associated with the calling thread.
312 * @return collection of loaders for this object, from all packs containing
313 * this object
314 * @throws IOException
316 public Collection<PackedObjectLoader> openObjectInAllPacks(
317 final AnyObjectId objectId, final WindowCursor curs)
318 throws IOException {
319 Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
320 openObjectInAllPacks(objectId, result, curs);
321 return result;
325 * Open object in all packs containing specified object.
327 * @param objectId
328 * id of object to search for
329 * @param resultLoaders
330 * result collection of loaders for this object, filled with
331 * loaders from all packs containing specified object
332 * @param curs
333 * temporary working space associated with the calling thread.
334 * @throws IOException
336 void openObjectInAllPacks(final AnyObjectId objectId,
337 final Collection<PackedObjectLoader> resultLoaders,
338 final WindowCursor curs) throws IOException {
339 for (PackFile pack : packs) {
340 final PackedObjectLoader loader = pack.get(curs, objectId);
341 if (loader != null)
342 resultLoaders.add(loader);
347 * @param id
348 * SHA'1 of a blob
349 * @return an {@link ObjectLoader} for accessing the data of a named blob
350 * @throws IOException
352 public ObjectLoader openBlob(final ObjectId id) throws IOException {
353 return openObject(id);
357 * @param id
358 * SHA'1 of a tree
359 * @return an {@link ObjectLoader} for accessing the data of a named tree
360 * @throws IOException
362 public ObjectLoader openTree(final ObjectId id) throws IOException {
363 return openObject(id);
367 * Access a Commit object using a symbolic reference. This reference may
368 * be a SHA-1 or ref in combination with a number of symbols translating
369 * from one ref or SHA1-1 to another, such as HEAD^ etc.
371 * @param revstr a reference to a git commit object
372 * @return a Commit named by the specified string
373 * @throws IOException for I/O error or unexpected object type.
375 * @see #resolve(String)
377 public Commit mapCommit(final String revstr) throws IOException {
378 final ObjectId id = resolve(revstr);
379 return id != null ? mapCommit(id) : null;
383 * Access any type of Git object by id and
385 * @param id
386 * SHA-1 of object to read
387 * @param refName optional, only relevant for simple tags
388 * @return The Git object if found or null
389 * @throws IOException
391 public Object mapObject(final ObjectId id, final String refName) throws IOException {
392 final ObjectLoader or = openObject(id);
393 if (or == null)
394 return null;
395 final byte[] raw = or.getBytes();
396 if (or.getType() == Constants.OBJ_TREE)
397 return makeTree(id, raw);
398 if (or.getType() == Constants.OBJ_COMMIT)
399 return makeCommit(id, raw);
400 if (or.getType() == Constants.OBJ_TAG)
401 return makeTag(id, refName, raw);
402 if (or.getType() == Constants.OBJ_BLOB)
403 return raw;
404 throw new IncorrectObjectTypeException(id,
405 "COMMIT nor TREE nor BLOB nor TAG");
409 * Access a Commit by SHA'1 id.
410 * @param id
411 * @return Commit or null
412 * @throws IOException for I/O error or unexpected object type.
414 public Commit mapCommit(final ObjectId id) throws IOException {
415 final ObjectLoader or = openObject(id);
416 if (or == null)
417 return null;
418 final byte[] raw = or.getBytes();
419 if (Constants.OBJ_COMMIT == or.getType())
420 return new Commit(this, id, raw);
421 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
424 private Commit makeCommit(final ObjectId id, final byte[] raw) {
425 Commit ret = new Commit(this, id, raw);
426 return ret;
430 * Access a Tree object using a symbolic reference. This reference may
431 * be a SHA-1 or ref in combination with a number of symbols translating
432 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
434 * @param revstr a reference to a git commit object
435 * @return a Tree named by the specified string
436 * @throws IOException
438 * @see #resolve(String)
440 public Tree mapTree(final String revstr) throws IOException {
441 final ObjectId id = resolve(revstr);
442 return id != null ? mapTree(id) : null;
446 * Access a Tree by SHA'1 id.
447 * @param id
448 * @return Tree or null
449 * @throws IOException for I/O error or unexpected object type.
451 public Tree mapTree(final ObjectId id) throws IOException {
452 final ObjectLoader or = openObject(id);
453 if (or == null)
454 return null;
455 final byte[] raw = or.getBytes();
456 if (Constants.OBJ_TREE == or.getType()) {
457 return new Tree(this, id, raw);
459 if (Constants.OBJ_COMMIT == or.getType())
460 return mapTree(ObjectId.fromString(raw, 5));
461 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
464 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
465 Tree ret = new Tree(this, id, raw);
466 return ret;
469 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
470 Tag ret = new Tag(this, id, refName, raw);
471 return ret;
475 * Access a tag by symbolic name.
477 * @param revstr
478 * @return a Tag or null
479 * @throws IOException on I/O error or unexpected type
481 public Tag mapTag(String revstr) throws IOException {
482 final ObjectId id = resolve(revstr);
483 return id != null ? mapTag(revstr, id) : null;
487 * Access a Tag by SHA'1 id
488 * @param refName
489 * @param id
490 * @return Commit or null
491 * @throws IOException for I/O error or unexpected object type.
493 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
494 final ObjectLoader or = openObject(id);
495 if (or == null)
496 return null;
497 final byte[] raw = or.getBytes();
498 if (Constants.OBJ_TAG == or.getType())
499 return new Tag(this, id, refName, raw);
500 return new Tag(this, id, refName, null);
504 * Create a command to update, create or delete a ref in this repository.
506 * @param ref
507 * name of the ref the caller wants to modify.
508 * @return an update command. The caller must finish populating this command
509 * and then invoke one of the update methods to actually make a
510 * change.
511 * @throws IOException
512 * a symbolic ref was passed in and could not be resolved back
513 * to the base ref, as the symbolic ref could not be read.
515 public RefUpdate updateRef(final String ref) throws IOException {
516 return refs.newUpdate(ref);
520 * Parse a git revision string and return an object id.
522 * Currently supported is combinations of these.
523 * <ul>
524 * <li>SHA-1 - a SHA-1</li>
525 * <li>refs/... - a ref name</li>
526 * <li>ref^n - nth parent reference</li>
527 * <li>ref~n - distance via parent reference</li>
528 * <li>ref@{n} - nth version of ref</li>
529 * <li>ref^{tree} - tree references by ref</li>
530 * <li>ref^{commit} - commit references by ref</li>
531 * </ul>
533 * Not supported is
534 * <ul>
535 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
536 * <li>abbreviated SHA-1's</li>
537 * </ul>
539 * @param revstr A git object references expression
540 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
541 * @throws IOException on serious errors
543 public ObjectId resolve(final String revstr) throws IOException {
544 char[] rev = revstr.toCharArray();
545 Object ref = null;
546 ObjectId refId = null;
547 for (int i = 0; i < rev.length; ++i) {
548 switch (rev[i]) {
549 case '^':
550 if (refId == null) {
551 String refstr = new String(rev,0,i);
552 refId = resolveSimple(refstr);
553 if (refId == null)
554 return null;
556 if (i + 1 < rev.length) {
557 switch (rev[i + 1]) {
558 case '0':
559 case '1':
560 case '2':
561 case '3':
562 case '4':
563 case '5':
564 case '6':
565 case '7':
566 case '8':
567 case '9':
568 int j;
569 ref = mapObject(refId, null);
570 while (ref instanceof Tag) {
571 Tag tag = (Tag)ref;
572 refId = tag.getObjId();
573 ref = mapObject(refId, null);
575 if (!(ref instanceof Commit))
576 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
577 for (j=i+1; j<rev.length; ++j) {
578 if (!Character.isDigit(rev[j]))
579 break;
581 String parentnum = new String(rev, i+1, j-i-1);
582 int pnum;
583 try {
584 pnum = Integer.parseInt(parentnum);
585 } catch (NumberFormatException e) {
586 throw new RevisionSyntaxException(
587 "Invalid commit parent number",
588 revstr);
590 if (pnum != 0) {
591 final ObjectId parents[] = ((Commit) ref)
592 .getParentIds();
593 if (pnum > parents.length)
594 refId = null;
595 else
596 refId = parents[pnum - 1];
598 i = j - 1;
599 break;
600 case '{':
601 int k;
602 String item = null;
603 for (k=i+2; k<rev.length; ++k) {
604 if (rev[k] == '}') {
605 item = new String(rev, i+2, k-i-2);
606 break;
609 i = k;
610 if (item != null)
611 if (item.equals("tree")) {
612 ref = mapObject(refId, null);
613 while (ref instanceof Tag) {
614 Tag t = (Tag)ref;
615 refId = t.getObjId();
616 ref = mapObject(refId, null);
618 if (ref instanceof Treeish)
619 refId = ((Treeish)ref).getTreeId();
620 else
621 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
623 else if (item.equals("commit")) {
624 ref = mapObject(refId, null);
625 while (ref instanceof Tag) {
626 Tag t = (Tag)ref;
627 refId = t.getObjId();
628 ref = mapObject(refId, null);
630 if (!(ref instanceof Commit))
631 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
633 else if (item.equals("blob")) {
634 ref = mapObject(refId, null);
635 while (ref instanceof Tag) {
636 Tag t = (Tag)ref;
637 refId = t.getObjId();
638 ref = mapObject(refId, null);
640 if (!(ref instanceof byte[]))
641 throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB);
643 else if (item.equals("")) {
644 ref = mapObject(refId, null);
645 while (ref instanceof Tag) {
646 Tag t = (Tag)ref;
647 refId = t.getObjId();
648 ref = mapObject(refId, null);
651 else
652 throw new RevisionSyntaxException(revstr);
653 else
654 throw new RevisionSyntaxException(revstr);
655 break;
656 default:
657 ref = mapObject(refId, null);
658 if (ref instanceof Commit) {
659 final ObjectId parents[] = ((Commit) ref)
660 .getParentIds();
661 if (parents.length == 0)
662 refId = null;
663 else
664 refId = parents[0];
665 } else
666 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
669 } else {
670 ref = mapObject(refId, null);
671 while (ref instanceof Tag) {
672 Tag tag = (Tag)ref;
673 refId = tag.getObjId();
674 ref = mapObject(refId, null);
676 if (ref instanceof Commit) {
677 final ObjectId parents[] = ((Commit) ref)
678 .getParentIds();
679 if (parents.length == 0)
680 refId = null;
681 else
682 refId = parents[0];
683 } else
684 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
686 break;
687 case '~':
688 if (ref == null) {
689 String refstr = new String(rev,0,i);
690 refId = resolveSimple(refstr);
691 if (refId == null)
692 return null;
693 ref = mapObject(refId, null);
695 while (ref instanceof Tag) {
696 Tag tag = (Tag)ref;
697 refId = tag.getObjId();
698 ref = mapObject(refId, null);
700 if (!(ref instanceof Commit))
701 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
702 int l;
703 for (l = i + 1; l < rev.length; ++l) {
704 if (!Character.isDigit(rev[l]))
705 break;
707 String distnum = new String(rev, i+1, l-i-1);
708 int dist;
709 try {
710 dist = Integer.parseInt(distnum);
711 } catch (NumberFormatException e) {
712 throw new RevisionSyntaxException(
713 "Invalid ancestry length", revstr);
715 while (dist > 0) {
716 final ObjectId[] parents = ((Commit) ref).getParentIds();
717 if (parents.length == 0) {
718 refId = null;
719 break;
721 refId = parents[0];
722 ref = mapCommit(refId);
723 --dist;
725 i = l - 1;
726 break;
727 case '@':
728 int m;
729 String time = null;
730 for (m=i+2; m<rev.length; ++m) {
731 if (rev[m] == '}') {
732 time = new String(rev, i+2, m-i-2);
733 break;
736 if (time != null)
737 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
738 i = m - 1;
739 break;
740 default:
741 if (refId != null)
742 throw new RevisionSyntaxException(revstr);
745 if (refId == null)
746 refId = resolveSimple(revstr);
747 return refId;
750 private ObjectId resolveSimple(final String revstr) throws IOException {
751 if (ObjectId.isId(revstr))
752 return ObjectId.fromString(revstr);
753 final Ref r = refs.readRef(revstr);
754 return r != null ? r.getObjectId() : null;
758 * Close all resources used by this repository
760 public void close() {
761 closePacks();
764 void closePacks() {
765 for (int k = packs.length - 1; k >= 0; k--) {
766 packs[k].close();
768 packs = new PackFile[0];
772 * Add a single existing pack to the list of available pack files.
774 * @param pack
775 * path of the pack file to open.
776 * @param idx
777 * path of the corresponding index file.
778 * @throws IOException
779 * index file could not be opened, read, or is not recognized as
780 * a Git pack file index.
782 public void openPack(final File pack, final File idx) throws IOException {
783 final String p = pack.getName();
784 final String i = idx.getName();
785 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
786 throw new IllegalArgumentException("Not a valid pack " + pack);
787 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
788 throw new IllegalArgumentException("Not a valid pack " + idx);
789 if (!p.substring(0,45).equals(i.substring(0,45)))
790 throw new IllegalArgumentException("Pack " + pack
791 + "does not match index " + idx);
793 final PackFile[] cur = packs;
794 final PackFile[] arr = new PackFile[cur.length + 1];
795 System.arraycopy(cur, 0, arr, 1, cur.length);
796 arr[0] = new PackFile(this, idx, pack);
797 packs = arr;
801 * Scan the object dirs, including alternates for packs
802 * to use.
804 public void scanForPacks() {
805 final ArrayList<PackFile> p = new ArrayList<PackFile>();
806 for (int i=0; i<objectsDirs.length; ++i)
807 scanForPacks(new File(objectsDirs[i], "pack"), p);
808 final PackFile[] arr = new PackFile[p.size()];
809 p.toArray(arr);
810 packs = arr;
813 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
814 final String[] idxList = packDir.list(new FilenameFilter() {
815 public boolean accept(final File baseDir, final String n) {
816 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
817 return n.length() == 49 && n.endsWith(".idx")
818 && n.startsWith("pack-");
821 if (idxList != null) {
822 for (final String indexName : idxList) {
823 final String n = indexName.substring(0, indexName.length() - 4);
824 final File idxFile = new File(packDir, n + ".idx");
825 final File packFile = new File(packDir, n + ".pack");
827 if (!packFile.isFile()) {
828 // Sometimes C Git's http fetch transport leaves a
829 // .idx file behind and does not download the .pack.
830 // We have to skip over such useless indexes.
832 continue;
835 try {
836 packList.add(new PackFile(this, idxFile, packFile));
837 } catch (IOException ioe) {
838 // Whoops. That's not a pack!
840 ioe.printStackTrace();
847 * Writes a symref (e.g. HEAD) to disk
849 * @param name symref name
850 * @param target pointed to ref
851 * @throws IOException
853 public void writeSymref(final String name, final String target)
854 throws IOException {
855 refs.link(name, target);
858 public String toString() {
859 return "Repository[" + getDirectory() + "]";
863 * @return name of topmost Stacked Git patch.
864 * @throws IOException
866 public String getPatch() throws IOException {
867 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
868 final BufferedReader br = new BufferedReader(new FileReader(ptr));
869 String last=null;
870 try {
871 String line;
872 while ((line=br.readLine())!=null) {
873 last = line;
875 } finally {
876 br.close();
878 return last;
882 * @return name of current branch
883 * @throws IOException
885 public String getFullBranch() throws IOException {
886 final File ptr = new File(getDirectory(),"HEAD");
887 final BufferedReader br = new BufferedReader(new FileReader(ptr));
888 String ref;
889 try {
890 ref = br.readLine();
891 } finally {
892 br.close();
894 if (ref.startsWith("ref: "))
895 ref = ref.substring(5);
896 return ref;
900 * @return name of current branch.
901 * @throws IOException
903 public String getBranch() throws IOException {
904 try {
905 final File ptr = new File(getDirectory(), Constants.HEAD);
906 final BufferedReader br = new BufferedReader(new FileReader(ptr));
907 String ref;
908 try {
909 ref = br.readLine();
910 } finally {
911 br.close();
913 if (ref.startsWith("ref: "))
914 ref = ref.substring(5);
915 if (ref.startsWith("refs/heads/"))
916 ref = ref.substring(11);
917 return ref;
918 } catch (FileNotFoundException e) {
919 final File ptr = new File(getDirectory(),"head-name");
920 final BufferedReader br = new BufferedReader(new FileReader(ptr));
921 String ref;
922 try {
923 ref = br.readLine();
924 } finally {
925 br.close();
927 return ref;
932 * @return all known refs (heads, tags, remotes).
934 public Map<String, Ref> getAllRefs() {
935 return refs.getAllRefs();
939 * @return all tags; key is short tag name ("v1.0") and value of the entry
940 * contains the ref with the full tag name ("refs/tags/v1.0").
942 public Map<String, Ref> getTags() {
943 return refs.getTags();
947 * Peel a possibly unpeeled ref and updates it.
948 * <p>
949 * If the ref cannot be peeled (as it does not refer to an annotated tag)
950 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
952 * @param ref
953 * The ref to peel
954 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
955 * new Ref object representing the same data as Ref, but isPeeled()
956 * will be true and getPeeledObjectId will contain the peeled object
957 * (or null).
959 public Ref peel(final Ref ref) {
960 return refs.peel(ref);
964 * @return a map with all objects referenced by a peeled ref.
966 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
967 Map<String, Ref> allRefs = getAllRefs();
968 Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
969 for (Ref ref : allRefs.values()) {
970 if (!ref.isPeeled())
971 ref = peel(ref);
972 AnyObjectId target = ref.getPeeledObjectId();
973 if (target == null)
974 target = ref.getObjectId();
975 // We assume most Sets here are singletons
976 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
977 if (oset != null) {
978 // that was not the case (rare)
979 if (oset.size() == 1) {
980 // Was a read-only singleton, we must copy to a new Set
981 oset = new HashSet<Ref>(oset);
983 ret.put(target, oset);
984 oset.add(ref);
987 return ret;
990 /** Clean up stale caches */
991 public void refreshFromDisk() {
992 refs.clearCache();
996 * @return a representation of the index associated with this repo
997 * @throws IOException
999 public GitIndex getIndex() throws IOException {
1000 if (index == null) {
1001 index = new GitIndex(this);
1002 index.read();
1003 } else {
1004 index.rereadIfNecessary();
1006 return index;
1009 static byte[] gitInternalSlash(byte[] bytes) {
1010 if (File.separatorChar == '/')
1011 return bytes;
1012 for (int i=0; i<bytes.length; ++i)
1013 if (bytes[i] == File.separatorChar)
1014 bytes[i] = '/';
1015 return bytes;
1019 * @return an important state
1021 public RepositoryState getRepositoryState() {
1022 // Pre Git-1.6 logic
1023 if (new File(getWorkDir(), ".dotest").exists())
1024 return RepositoryState.REBASING;
1025 if (new File(gitDir,".dotest-merge").exists())
1026 return RepositoryState.REBASING_INTERACTIVE;
1028 // From 1.6 onwards
1029 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1030 return RepositoryState.REBASING_REBASING;
1031 if (new File(getDirectory(),"rebase-apply/applying").exists())
1032 return RepositoryState.APPLY;
1033 if (new File(getDirectory(),"rebase-apply").exists())
1034 return RepositoryState.REBASING;
1036 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1037 return RepositoryState.REBASING_INTERACTIVE;
1038 if (new File(getDirectory(),"rebase-merge").exists())
1039 return RepositoryState.REBASING_MERGE;
1041 // Both versions
1042 if (new File(gitDir,"MERGE_HEAD").exists())
1043 return RepositoryState.MERGING;
1044 if (new File(gitDir,"BISECT_LOG").exists())
1045 return RepositoryState.BISECTING;
1047 return RepositoryState.SAFE;
1051 * Check validity of a ref name. It must not contain character that has
1052 * a special meaning in a Git object reference expression. Some other
1053 * dangerous characters are also excluded.
1055 * @param refName
1057 * @return true if refName is a valid ref name
1059 public static boolean isValidRefName(final String refName) {
1060 final int len = refName.length();
1061 if (len == 0)
1062 return false;
1064 char p = '\0';
1065 for (int i=0; i<len; ++i) {
1066 char c = refName.charAt(i);
1067 if (c <= ' ')
1068 return false;
1069 switch(c) {
1070 case '.':
1071 if (i == 0)
1072 return false;
1073 if (p == '/')
1074 return false;
1075 if (p == '.')
1076 return false;
1077 break;
1078 case '/':
1079 if (i == 0)
1080 return false;
1081 if (i == len -1)
1082 return false;
1083 break;
1084 case '~': case '^': case ':':
1085 case '?': case '[':
1086 return false;
1087 case '*':
1088 return false;
1090 p = c;
1092 return true;
1096 * Strip work dir and return normalized repository path
1098 * @param wd Work dir
1099 * @param f File whose path shall be stripped of its workdir
1100 * @return normalized repository relative path
1102 public static String stripWorkDir(File wd, File f) {
1103 String relName = f.getPath().substring(wd.getPath().length() + 1);
1104 relName = relName.replace(File.separatorChar, '/');
1105 return relName;
1109 * @return the workdir file, i.e. where the files are checked out
1111 public File getWorkDir() {
1112 return getDirectory().getParentFile();
1116 * Register a {@link RepositoryListener} which will be notified
1117 * when ref changes are detected.
1119 * @param l
1121 public void addRepositoryChangedListener(final RepositoryListener l) {
1122 listeners.add(l);
1126 * Remove a registered {@link RepositoryListener}
1127 * @param l
1129 public void removeRepositoryChangedListener(final RepositoryListener l) {
1130 listeners.remove(l);
1134 * Register a global {@link RepositoryListener} which will be notified
1135 * when a ref changes in any repository are detected.
1137 * @param l
1139 public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
1140 allListeners.add(l);
1144 * Remove a globally registered {@link RepositoryListener}
1145 * @param l
1147 public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
1148 allListeners.remove(l);
1151 void fireRefsMaybeChanged() {
1152 if (refs.lastRefModification != refs.lastNotifiedRefModification) {
1153 refs.lastNotifiedRefModification = refs.lastRefModification;
1154 final RefsChangedEvent event = new RefsChangedEvent(this);
1155 List<RepositoryListener> all;
1156 synchronized (listeners) {
1157 all = new ArrayList<RepositoryListener>(listeners);
1159 synchronized (allListeners) {
1160 all.addAll(allListeners);
1162 for (final RepositoryListener l : all) {
1163 l.refsChanged(event);
1168 void fireIndexChanged() {
1169 final IndexChangedEvent event = new IndexChangedEvent(this);
1170 List<RepositoryListener> all;
1171 synchronized (listeners) {
1172 all = new ArrayList<RepositoryListener>(listeners);
1174 synchronized (allListeners) {
1175 all.addAll(allListeners);
1177 for (final RepositoryListener l : all) {
1178 l.indexChanged(event);
1183 * Force a scan for changed refs.
1185 * @throws IOException
1187 public void scanForRepoChanges() throws IOException {
1188 getAllRefs(); // This will look for changes to refs
1189 getIndex(); // This will detect changes in the index