Add a utility method for shortening long ref names to short ones.
[egit/imyousuf.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob5def5d3456ec2b8ff3503cdb6ff1f1019a81d239
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.IOException;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 import java.util.Vector;
58 import org.spearce.jgit.errors.IncorrectObjectTypeException;
59 import org.spearce.jgit.errors.RevisionSyntaxException;
60 import org.spearce.jgit.util.FS;
62 /**
63 * Represents a Git repository. A repository holds all objects and refs used for
64 * managing source code (could by any type of file, but source code is what
65 * SCM's are typically used for).
67 * In Git terms all data is stored in GIT_DIR, typically a directory called
68 * .git. A work tree is maintained unless the repository is a bare repository.
69 * Typically the .git directory is located at the root of the work dir.
71 * <ul>
72 * <li>GIT_DIR
73 * <ul>
74 * <li>objects/ - objects</li>
75 * <li>refs/ - tags and heads</li>
76 * <li>config - configuration</li>
77 * <li>info/ - more configurations</li>
78 * </ul>
79 * </li>
80 * </ul>
81 * <p>
82 * This class is thread-safe.
83 * <p>
84 * This implementation only handles a subtly undocumented subset of git features.
87 public class Repository {
88 private final File gitDir;
90 private final RepositoryConfig config;
92 private final RefDatabase refs;
94 private final ObjectDirectory objectDatabase;
96 private GitIndex index;
98 private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
99 static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
102 * Construct a representation of a Git repository.
104 * @param d
105 * GIT_DIR (the location of the repository metadata).
106 * @throws IOException
107 * the repository appears to already exist but cannot be
108 * accessed.
110 public Repository(final File d) throws IOException {
111 gitDir = d.getAbsoluteFile();
112 refs = new RefDatabase(this);
113 objectDatabase = new ObjectDirectory(FS.resolve(gitDir, "objects"));
114 config = new RepositoryConfig(this);
116 if (objectDatabase.exists()) {
117 getConfig().load();
118 final String repositoryFormatVersion = getConfig().getString(
119 "core", null, "repositoryFormatVersion");
120 if (!"0".equals(repositoryFormatVersion)) {
121 throw new IOException("Unknown repository format \""
122 + repositoryFormatVersion + "\"; expected \"0\".");
124 } else {
125 getConfig().create();
130 * Create a new Git repository initializing the necessary files and
131 * directories. Repository with working tree is created using this method.
133 * @throws IOException
134 * @see #create(boolean)
136 public synchronized void create() throws IOException {
137 create(false);
141 * Create a new Git repository initializing the necessary files and
142 * directories.
144 * @param bare
145 * if true, a bare repository is created.
147 * @throws IOException
148 * in case of IO problem
150 public void create(boolean bare) throws IOException {
151 if ((bare ? new File(gitDir, "config") : gitDir).exists()) {
152 throw new IllegalStateException("Repository already exists: "
153 + gitDir);
155 gitDir.mkdirs();
156 refs.create();
157 objectDatabase.create();
159 new File(gitDir, "branches").mkdir();
160 new File(gitDir, "remotes").mkdir();
161 final String master = Constants.R_HEADS + Constants.MASTER;
162 refs.link(Constants.HEAD, master);
164 RepositoryConfig cfg = getConfig();
165 cfg.create();
166 if (bare) {
167 cfg.setString("core", null, "bare", "true");
169 cfg.save();
173 * @return GIT_DIR
175 public File getDirectory() {
176 return gitDir;
180 * @return the directory containing the objects owned by this repository.
182 public File getObjectsDirectory() {
183 return objectDatabase.getDirectory();
187 * @return the configuration of this repository
189 public RepositoryConfig getConfig() {
190 return config;
194 * Construct a filename where the loose object having a specified SHA-1
195 * should be stored. If the object is stored in a shared repository the path
196 * to the alternative repo will be returned. If the object is not yet store
197 * a usable path in this repo will be returned. It is assumed that callers
198 * will look for objects in a pack first.
200 * @param objectId
201 * @return suggested file name
203 public File toFile(final AnyObjectId objectId) {
204 return objectDatabase.fileFor(objectId);
208 * @param objectId
209 * @return true if the specified object is stored in this repo or any of the
210 * known shared repositories.
212 public boolean hasObject(final AnyObjectId objectId) {
213 return objectDatabase.hasObject(objectId);
217 * @param id
218 * SHA-1 of an object.
220 * @return a {@link ObjectLoader} for accessing the data of the named
221 * object, or null if the object does not exist.
222 * @throws IOException
224 public ObjectLoader openObject(final AnyObjectId id)
225 throws IOException {
226 final WindowCursor wc = new WindowCursor();
227 try {
228 return openObject(wc, id);
229 } finally {
230 wc.release();
235 * @param curs
236 * temporary working space associated with the calling thread.
237 * @param id
238 * SHA-1 of an object.
240 * @return a {@link ObjectLoader} for accessing the data of the named
241 * object, or null if the object does not exist.
242 * @throws IOException
244 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
245 throws IOException {
246 return objectDatabase.openObject(curs, id);
250 * Open object in all packs containing specified object.
252 * @param objectId
253 * id of object to search for
254 * @param curs
255 * temporary working space associated with the calling thread.
256 * @return collection of loaders for this object, from all packs containing
257 * this object
258 * @throws IOException
260 public Collection<PackedObjectLoader> openObjectInAllPacks(
261 final AnyObjectId objectId, final WindowCursor curs)
262 throws IOException {
263 Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
264 openObjectInAllPacks(objectId, result, curs);
265 return result;
269 * Open object in all packs containing specified object.
271 * @param objectId
272 * id of object to search for
273 * @param resultLoaders
274 * result collection of loaders for this object, filled with
275 * loaders from all packs containing specified object
276 * @param curs
277 * temporary working space associated with the calling thread.
278 * @throws IOException
280 void openObjectInAllPacks(final AnyObjectId objectId,
281 final Collection<PackedObjectLoader> resultLoaders,
282 final WindowCursor curs) throws IOException {
283 objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId);
287 * @param id
288 * SHA'1 of a blob
289 * @return an {@link ObjectLoader} for accessing the data of a named blob
290 * @throws IOException
292 public ObjectLoader openBlob(final ObjectId id) throws IOException {
293 return openObject(id);
297 * @param id
298 * SHA'1 of a tree
299 * @return an {@link ObjectLoader} for accessing the data of a named tree
300 * @throws IOException
302 public ObjectLoader openTree(final ObjectId id) throws IOException {
303 return openObject(id);
307 * Access a Commit object using a symbolic reference. This reference may
308 * be a SHA-1 or ref in combination with a number of symbols translating
309 * from one ref or SHA1-1 to another, such as HEAD^ etc.
311 * @param revstr a reference to a git commit object
312 * @return a Commit named by the specified string
313 * @throws IOException for I/O error or unexpected object type.
315 * @see #resolve(String)
317 public Commit mapCommit(final String revstr) throws IOException {
318 final ObjectId id = resolve(revstr);
319 return id != null ? mapCommit(id) : null;
323 * Access any type of Git object by id and
325 * @param id
326 * SHA-1 of object to read
327 * @param refName optional, only relevant for simple tags
328 * @return The Git object if found or null
329 * @throws IOException
331 public Object mapObject(final ObjectId id, final String refName) throws IOException {
332 final ObjectLoader or = openObject(id);
333 if (or == null)
334 return null;
335 final byte[] raw = or.getBytes();
336 switch (or.getType()) {
337 case Constants.OBJ_TREE:
338 return makeTree(id, raw);
340 case Constants.OBJ_COMMIT:
341 return makeCommit(id, raw);
343 case Constants.OBJ_TAG:
344 return makeTag(id, refName, raw);
346 case Constants.OBJ_BLOB:
347 return raw;
349 default:
350 throw new IncorrectObjectTypeException(id,
351 "COMMIT nor TREE nor BLOB nor TAG");
356 * Access a Commit by SHA'1 id.
357 * @param id
358 * @return Commit or null
359 * @throws IOException for I/O error or unexpected object type.
361 public Commit mapCommit(final ObjectId id) throws IOException {
362 final ObjectLoader or = openObject(id);
363 if (or == null)
364 return null;
365 final byte[] raw = or.getBytes();
366 if (Constants.OBJ_COMMIT == or.getType())
367 return new Commit(this, id, raw);
368 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
371 private Commit makeCommit(final ObjectId id, final byte[] raw) {
372 Commit ret = new Commit(this, id, raw);
373 return ret;
377 * Access a Tree object using a symbolic reference. This reference may
378 * be a SHA-1 or ref in combination with a number of symbols translating
379 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
381 * @param revstr a reference to a git commit object
382 * @return a Tree named by the specified string
383 * @throws IOException
385 * @see #resolve(String)
387 public Tree mapTree(final String revstr) throws IOException {
388 final ObjectId id = resolve(revstr);
389 return id != null ? mapTree(id) : null;
393 * Access a Tree by SHA'1 id.
394 * @param id
395 * @return Tree or null
396 * @throws IOException for I/O error or unexpected object type.
398 public Tree mapTree(final ObjectId id) throws IOException {
399 final ObjectLoader or = openObject(id);
400 if (or == null)
401 return null;
402 final byte[] raw = or.getBytes();
403 switch (or.getType()) {
404 case Constants.OBJ_TREE:
405 return new Tree(this, id, raw);
407 case Constants.OBJ_COMMIT:
408 return mapTree(ObjectId.fromString(raw, 5));
410 default:
411 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
415 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
416 Tree ret = new Tree(this, id, raw);
417 return ret;
420 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
421 Tag ret = new Tag(this, id, refName, raw);
422 return ret;
426 * Access a tag by symbolic name.
428 * @param revstr
429 * @return a Tag or null
430 * @throws IOException on I/O error or unexpected type
432 public Tag mapTag(String revstr) throws IOException {
433 final ObjectId id = resolve(revstr);
434 return id != null ? mapTag(revstr, id) : null;
438 * Access a Tag by SHA'1 id
439 * @param refName
440 * @param id
441 * @return Commit or null
442 * @throws IOException for I/O error or unexpected object type.
444 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
445 final ObjectLoader or = openObject(id);
446 if (or == null)
447 return null;
448 final byte[] raw = or.getBytes();
449 if (Constants.OBJ_TAG == or.getType())
450 return new Tag(this, id, refName, raw);
451 return new Tag(this, id, refName, null);
455 * Create a command to update, create or delete a ref in this repository.
457 * @param ref
458 * name of the ref the caller wants to modify.
459 * @return an update command. The caller must finish populating this command
460 * and then invoke one of the update methods to actually make a
461 * change.
462 * @throws IOException
463 * a symbolic ref was passed in and could not be resolved back
464 * to the base ref, as the symbolic ref could not be read.
466 public RefUpdate updateRef(final String ref) throws IOException {
467 return refs.newUpdate(ref);
471 * Parse a git revision string and return an object id.
473 * Currently supported is combinations of these.
474 * <ul>
475 * <li>SHA-1 - a SHA-1</li>
476 * <li>refs/... - a ref name</li>
477 * <li>ref^n - nth parent reference</li>
478 * <li>ref~n - distance via parent reference</li>
479 * <li>ref@{n} - nth version of ref</li>
480 * <li>ref^{tree} - tree references by ref</li>
481 * <li>ref^{commit} - commit references by ref</li>
482 * </ul>
484 * Not supported is
485 * <ul>
486 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
487 * <li>abbreviated SHA-1's</li>
488 * </ul>
490 * @param revstr A git object references expression
491 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
492 * @throws IOException on serious errors
494 public ObjectId resolve(final String revstr) throws IOException {
495 char[] rev = revstr.toCharArray();
496 Object ref = null;
497 ObjectId refId = null;
498 for (int i = 0; i < rev.length; ++i) {
499 switch (rev[i]) {
500 case '^':
501 if (refId == null) {
502 String refstr = new String(rev,0,i);
503 refId = resolveSimple(refstr);
504 if (refId == null)
505 return null;
507 if (i + 1 < rev.length) {
508 switch (rev[i + 1]) {
509 case '0':
510 case '1':
511 case '2':
512 case '3':
513 case '4':
514 case '5':
515 case '6':
516 case '7':
517 case '8':
518 case '9':
519 int j;
520 ref = mapObject(refId, null);
521 while (ref instanceof Tag) {
522 Tag tag = (Tag)ref;
523 refId = tag.getObjId();
524 ref = mapObject(refId, null);
526 if (!(ref instanceof Commit))
527 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
528 for (j=i+1; j<rev.length; ++j) {
529 if (!Character.isDigit(rev[j]))
530 break;
532 String parentnum = new String(rev, i+1, j-i-1);
533 int pnum;
534 try {
535 pnum = Integer.parseInt(parentnum);
536 } catch (NumberFormatException e) {
537 throw new RevisionSyntaxException(
538 "Invalid commit parent number",
539 revstr);
541 if (pnum != 0) {
542 final ObjectId parents[] = ((Commit) ref)
543 .getParentIds();
544 if (pnum > parents.length)
545 refId = null;
546 else
547 refId = parents[pnum - 1];
549 i = j - 1;
550 break;
551 case '{':
552 int k;
553 String item = null;
554 for (k=i+2; k<rev.length; ++k) {
555 if (rev[k] == '}') {
556 item = new String(rev, i+2, k-i-2);
557 break;
560 i = k;
561 if (item != null)
562 if (item.equals("tree")) {
563 ref = mapObject(refId, null);
564 while (ref instanceof Tag) {
565 Tag t = (Tag)ref;
566 refId = t.getObjId();
567 ref = mapObject(refId, null);
569 if (ref instanceof Treeish)
570 refId = ((Treeish)ref).getTreeId();
571 else
572 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
574 else if (item.equals("commit")) {
575 ref = mapObject(refId, null);
576 while (ref instanceof Tag) {
577 Tag t = (Tag)ref;
578 refId = t.getObjId();
579 ref = mapObject(refId, null);
581 if (!(ref instanceof Commit))
582 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
584 else if (item.equals("blob")) {
585 ref = mapObject(refId, null);
586 while (ref instanceof Tag) {
587 Tag t = (Tag)ref;
588 refId = t.getObjId();
589 ref = mapObject(refId, null);
591 if (!(ref instanceof byte[]))
592 throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB);
594 else if (item.equals("")) {
595 ref = mapObject(refId, null);
596 while (ref instanceof Tag) {
597 Tag t = (Tag)ref;
598 refId = t.getObjId();
599 ref = mapObject(refId, null);
602 else
603 throw new RevisionSyntaxException(revstr);
604 else
605 throw new RevisionSyntaxException(revstr);
606 break;
607 default:
608 ref = mapObject(refId, null);
609 if (ref instanceof Commit) {
610 final ObjectId parents[] = ((Commit) ref)
611 .getParentIds();
612 if (parents.length == 0)
613 refId = null;
614 else
615 refId = parents[0];
616 } else
617 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
620 } else {
621 ref = mapObject(refId, null);
622 while (ref instanceof Tag) {
623 Tag tag = (Tag)ref;
624 refId = tag.getObjId();
625 ref = mapObject(refId, null);
627 if (ref instanceof Commit) {
628 final ObjectId parents[] = ((Commit) ref)
629 .getParentIds();
630 if (parents.length == 0)
631 refId = null;
632 else
633 refId = parents[0];
634 } else
635 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
637 break;
638 case '~':
639 if (ref == null) {
640 String refstr = new String(rev,0,i);
641 refId = resolveSimple(refstr);
642 if (refId == null)
643 return null;
644 ref = mapObject(refId, null);
646 while (ref instanceof Tag) {
647 Tag tag = (Tag)ref;
648 refId = tag.getObjId();
649 ref = mapObject(refId, null);
651 if (!(ref instanceof Commit))
652 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
653 int l;
654 for (l = i + 1; l < rev.length; ++l) {
655 if (!Character.isDigit(rev[l]))
656 break;
658 String distnum = new String(rev, i+1, l-i-1);
659 int dist;
660 try {
661 dist = Integer.parseInt(distnum);
662 } catch (NumberFormatException e) {
663 throw new RevisionSyntaxException(
664 "Invalid ancestry length", revstr);
666 while (dist > 0) {
667 final ObjectId[] parents = ((Commit) ref).getParentIds();
668 if (parents.length == 0) {
669 refId = null;
670 break;
672 refId = parents[0];
673 ref = mapCommit(refId);
674 --dist;
676 i = l - 1;
677 break;
678 case '@':
679 int m;
680 String time = null;
681 for (m=i+2; m<rev.length; ++m) {
682 if (rev[m] == '}') {
683 time = new String(rev, i+2, m-i-2);
684 break;
687 if (time != null)
688 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
689 i = m - 1;
690 break;
691 default:
692 if (refId != null)
693 throw new RevisionSyntaxException(revstr);
696 if (refId == null)
697 refId = resolveSimple(revstr);
698 return refId;
701 private ObjectId resolveSimple(final String revstr) throws IOException {
702 if (ObjectId.isId(revstr))
703 return ObjectId.fromString(revstr);
704 final Ref r = refs.readRef(revstr);
705 return r != null ? r.getObjectId() : null;
709 * Close all resources used by this repository
711 public void close() {
712 objectDatabase.close();
716 * Add a single existing pack to the list of available pack files.
718 * @param pack
719 * path of the pack file to open.
720 * @param idx
721 * path of the corresponding index file.
722 * @throws IOException
723 * index file could not be opened, read, or is not recognized as
724 * a Git pack file index.
726 public void openPack(final File pack, final File idx) throws IOException {
727 objectDatabase.openPack(pack, idx);
731 * Writes a symref (e.g. HEAD) to disk
733 * @param name symref name
734 * @param target pointed to ref
735 * @throws IOException
737 public void writeSymref(final String name, final String target)
738 throws IOException {
739 refs.link(name, target);
742 public String toString() {
743 return "Repository[" + getDirectory() + "]";
747 * @return name of current branch
748 * @throws IOException
750 public String getFullBranch() throws IOException {
751 final File ptr = new File(getDirectory(),Constants.HEAD);
752 final BufferedReader br = new BufferedReader(new FileReader(ptr));
753 String ref;
754 try {
755 ref = br.readLine();
756 } finally {
757 br.close();
759 if (ref.startsWith("ref: "))
760 ref = ref.substring(5);
761 return ref;
765 * @return name of current branch.
766 * @throws IOException
768 public String getBranch() throws IOException {
769 try {
770 final File ptr = new File(getDirectory(), Constants.HEAD);
771 final BufferedReader br = new BufferedReader(new FileReader(ptr));
772 String ref;
773 try {
774 ref = br.readLine();
775 } finally {
776 br.close();
778 if (ref.startsWith("ref: "))
779 ref = ref.substring(5);
780 if (ref.startsWith("refs/heads/"))
781 ref = ref.substring(11);
782 return ref;
783 } catch (FileNotFoundException e) {
784 final File ptr = new File(getDirectory(),"head-name");
785 final BufferedReader br = new BufferedReader(new FileReader(ptr));
786 String ref;
787 try {
788 ref = br.readLine();
789 } finally {
790 br.close();
792 return ref;
797 * Get a ref by name.
799 * @param name
800 * the name of the ref to lookup. May be a short-hand form, e.g.
801 * "master" which is is automatically expanded to
802 * "refs/heads/master" if "refs/heads/master" already exists.
803 * @return the Ref with the given name, or null if it does not exist
804 * @throws IOException
806 public Ref getRef(final String name) throws IOException {
807 return refs.readRef(name);
811 * @return all known refs (heads, tags, remotes).
813 public Map<String, Ref> getAllRefs() {
814 return refs.getAllRefs();
818 * @return all tags; key is short tag name ("v1.0") and value of the entry
819 * contains the ref with the full tag name ("refs/tags/v1.0").
821 public Map<String, Ref> getTags() {
822 return refs.getTags();
826 * Peel a possibly unpeeled ref and updates it.
827 * <p>
828 * If the ref cannot be peeled (as it does not refer to an annotated tag)
829 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
831 * @param ref
832 * The ref to peel
833 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
834 * new Ref object representing the same data as Ref, but isPeeled()
835 * will be true and getPeeledObjectId will contain the peeled object
836 * (or null).
838 public Ref peel(final Ref ref) {
839 return refs.peel(ref);
843 * @return a map with all objects referenced by a peeled ref.
845 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
846 Map<String, Ref> allRefs = getAllRefs();
847 Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
848 for (Ref ref : allRefs.values()) {
849 if (!ref.isPeeled())
850 ref = peel(ref);
851 AnyObjectId target = ref.getPeeledObjectId();
852 if (target == null)
853 target = ref.getObjectId();
854 // We assume most Sets here are singletons
855 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
856 if (oset != null) {
857 // that was not the case (rare)
858 if (oset.size() == 1) {
859 // Was a read-only singleton, we must copy to a new Set
860 oset = new HashSet<Ref>(oset);
862 ret.put(target, oset);
863 oset.add(ref);
866 return ret;
869 /** Clean up stale caches */
870 public void refreshFromDisk() {
871 refs.clearCache();
875 * @return a representation of the index associated with this repo
876 * @throws IOException
878 public GitIndex getIndex() throws IOException {
879 if (index == null) {
880 index = new GitIndex(this);
881 index.read();
882 } else {
883 index.rereadIfNecessary();
885 return index;
888 static byte[] gitInternalSlash(byte[] bytes) {
889 if (File.separatorChar == '/')
890 return bytes;
891 for (int i=0; i<bytes.length; ++i)
892 if (bytes[i] == File.separatorChar)
893 bytes[i] = '/';
894 return bytes;
898 * @return an important state
900 public RepositoryState getRepositoryState() {
901 // Pre Git-1.6 logic
902 if (new File(getWorkDir(), ".dotest").exists())
903 return RepositoryState.REBASING;
904 if (new File(gitDir,".dotest-merge").exists())
905 return RepositoryState.REBASING_INTERACTIVE;
907 // From 1.6 onwards
908 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
909 return RepositoryState.REBASING_REBASING;
910 if (new File(getDirectory(),"rebase-apply/applying").exists())
911 return RepositoryState.APPLY;
912 if (new File(getDirectory(),"rebase-apply").exists())
913 return RepositoryState.REBASING;
915 if (new File(getDirectory(),"rebase-merge/interactive").exists())
916 return RepositoryState.REBASING_INTERACTIVE;
917 if (new File(getDirectory(),"rebase-merge").exists())
918 return RepositoryState.REBASING_MERGE;
920 // Both versions
921 if (new File(gitDir,"MERGE_HEAD").exists())
922 return RepositoryState.MERGING;
923 if (new File(gitDir,"BISECT_LOG").exists())
924 return RepositoryState.BISECTING;
926 return RepositoryState.SAFE;
930 * Check validity of a ref name. It must not contain character that has
931 * a special meaning in a Git object reference expression. Some other
932 * dangerous characters are also excluded.
934 * For portability reasons '\' is excluded
936 * @param refName
938 * @return true if refName is a valid ref name
940 public static boolean isValidRefName(final String refName) {
941 final int len = refName.length();
942 if (len == 0)
943 return false;
944 if (refName.endsWith(".lock"))
945 return false;
947 int components = 1;
948 char p = '\0';
949 for (int i = 0; i < len; i++) {
950 final char c = refName.charAt(i);
951 if (c <= ' ')
952 return false;
953 switch (c) {
954 case '.':
955 switch (p) {
956 case '\0': case '/': case '.':
957 return false;
959 if (i == len -1)
960 return false;
961 break;
962 case '/':
963 if (i == 0 || i == len - 1)
964 return false;
965 components++;
966 break;
967 case '{':
968 if (p == '@')
969 return false;
970 break;
971 case '~': case '^': case ':':
972 case '?': case '[': case '*':
973 case '\\':
974 return false;
976 p = c;
978 return components > 1;
982 * Strip work dir and return normalized repository path
984 * @param wd Work dir
985 * @param f File whose path shall be stripped of its workdir
986 * @return normalized repository relative path
988 public static String stripWorkDir(File wd, File f) {
989 String relName = f.getPath().substring(wd.getPath().length() + 1);
990 relName = relName.replace(File.separatorChar, '/');
991 return relName;
995 * @return the workdir file, i.e. where the files are checked out
997 public File getWorkDir() {
998 return getDirectory().getParentFile();
1002 * Register a {@link RepositoryListener} which will be notified
1003 * when ref changes are detected.
1005 * @param l
1007 public void addRepositoryChangedListener(final RepositoryListener l) {
1008 listeners.add(l);
1012 * Remove a registered {@link RepositoryListener}
1013 * @param l
1015 public void removeRepositoryChangedListener(final RepositoryListener l) {
1016 listeners.remove(l);
1020 * Register a global {@link RepositoryListener} which will be notified
1021 * when a ref changes in any repository are detected.
1023 * @param l
1025 public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
1026 allListeners.add(l);
1030 * Remove a globally registered {@link RepositoryListener}
1031 * @param l
1033 public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
1034 allListeners.remove(l);
1037 void fireRefsMaybeChanged() {
1038 if (refs.lastRefModification != refs.lastNotifiedRefModification) {
1039 refs.lastNotifiedRefModification = refs.lastRefModification;
1040 final RefsChangedEvent event = new RefsChangedEvent(this);
1041 List<RepositoryListener> all;
1042 synchronized (listeners) {
1043 all = new ArrayList<RepositoryListener>(listeners);
1045 synchronized (allListeners) {
1046 all.addAll(allListeners);
1048 for (final RepositoryListener l : all) {
1049 l.refsChanged(event);
1054 void fireIndexChanged() {
1055 final IndexChangedEvent event = new IndexChangedEvent(this);
1056 List<RepositoryListener> all;
1057 synchronized (listeners) {
1058 all = new ArrayList<RepositoryListener>(listeners);
1060 synchronized (allListeners) {
1061 all.addAll(allListeners);
1063 for (final RepositoryListener l : all) {
1064 l.indexChanged(event);
1069 * Force a scan for changed refs.
1071 * @throws IOException
1073 public void scanForRepoChanges() throws IOException {
1074 getAllRefs(); // This will look for changes to refs
1075 getIndex(); // This will detect changes in the index
1079 * @param refName
1081 * @return a more user friendly ref name
1083 public String shortenRefName(String refName) {
1084 if (refName.startsWith(Constants.R_HEADS))
1085 return refName.substring(Constants.R_HEADS.length());
1086 if (refName.startsWith(Constants.R_TAGS))
1087 return refName.substring(Constants.R_TAGS.length());
1088 if (refName.startsWith(Constants.R_REMOTES))
1089 return refName.substring(Constants.R_REMOTES.length());
1090 return refName;