Evaluate short refnames into full names during push
[egit/imyousuf.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob8132e27b265023c72e5a40c582d1d40a1940c790
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.Arrays;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.Vector;
60 import org.spearce.jgit.errors.IncorrectObjectTypeException;
61 import org.spearce.jgit.errors.RevisionSyntaxException;
62 import org.spearce.jgit.util.FS;
64 /**
65 * Represents a Git repository. A repository holds all objects and refs used for
66 * managing source code (could by any type of file, but source code is what
67 * SCM's are typically used for).
69 * In Git terms all data is stored in GIT_DIR, typically a directory called
70 * .git. A work tree is maintained unless the repository is a bare repository.
71 * Typically the .git directory is located at the root of the work dir.
73 * <ul>
74 * <li>GIT_DIR
75 * <ul>
76 * <li>objects/ - objects</li>
77 * <li>refs/ - tags and heads</li>
78 * <li>config - configuration</li>
79 * <li>info/ - more configurations</li>
80 * </ul>
81 * </li>
82 * </ul>
83 * <p>
84 * This class is thread-safe.
85 * <p>
86 * This implementation only handles a subtly undocumented subset of git features.
89 public class Repository {
90 private final File gitDir;
92 private final RepositoryConfig config;
94 private final RefDatabase refs;
96 private File[] objectDirectoryList;
98 private PackFile[] packFileList;
100 private GitIndex index;
102 private List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
103 static private List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
106 * Construct a representation of a Git repository.
108 * @param d
109 * GIT_DIR (the location of the repository metadata).
110 * @throws IOException
111 * the repository appears to already exist but cannot be
112 * accessed.
114 public Repository(final File d) throws IOException {
115 gitDir = d.getAbsoluteFile();
116 try {
117 objectDirectoryList = readObjectsDirs(
118 FS.resolve(gitDir, "objects"), new ArrayList<File>())
119 .toArray(new File[0]);
120 } catch (IOException e) {
121 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
122 ex.initCause(e);
123 throw ex;
125 refs = new RefDatabase(this);
126 packFileList = new PackFile[0];
127 config = new RepositoryConfig(this);
129 final boolean isExisting = objectDirectoryList[0].exists();
130 if (isExisting) {
131 getConfig().load();
132 final String repositoryFormatVersion = getConfig().getString(
133 "core", null, "repositoryFormatVersion");
134 if (!"0".equals(repositoryFormatVersion)) {
135 throw new IOException("Unknown repository format \""
136 + repositoryFormatVersion + "\"; expected \"0\".");
138 } else {
139 getConfig().create();
141 if (isExisting)
142 scanForPacks();
145 private static Collection<File> readObjectsDirs(File objectsDir,
146 Collection<File> ret) throws IOException {
147 ret.add(objectsDir);
148 final File altFile = FS.resolve(objectsDir, "info/alternates");
149 if (altFile.exists()) {
150 BufferedReader ar = new BufferedReader(new FileReader(altFile));
151 try {
152 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
153 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
155 } catch (Exception e) {
156 ar.close();
159 return ret;
163 * Create a new Git repository initializing the necessary files and
164 * directories.
166 * @throws IOException
168 public synchronized void create() throws IOException {
169 if (gitDir.exists()) {
170 throw new IllegalStateException("Repository already exists: "
171 + gitDir);
174 gitDir.mkdirs();
175 refs.create();
177 objectDirectoryList[0].mkdirs();
178 new File(objectDirectoryList[0], "pack").mkdir();
179 new File(objectDirectoryList[0], "info").mkdir();
181 new File(gitDir, "branches").mkdir();
182 new File(gitDir, "remotes").mkdir();
183 final String master = Constants.R_HEADS + Constants.MASTER;
184 refs.link(Constants.HEAD, master);
186 getConfig().create();
187 getConfig().save();
190 private synchronized File[] objectsDirs(){
191 return objectDirectoryList;
194 private synchronized PackFile[] packs(){
195 return packFileList;
199 * @return GIT_DIR
201 public File getDirectory() {
202 return gitDir;
206 * @return the directory containing the objects owned by this repository.
208 public File getObjectsDirectory() {
209 return objectsDirs()[0];
213 * @return the configuration of this repository
215 public RepositoryConfig getConfig() {
216 return config;
220 * Construct a filename where the loose object having a specified SHA-1
221 * should be stored. If the object is stored in a shared repository the path
222 * to the alternative repo will be returned. If the object is not yet store
223 * a usable path in this repo will be returned. It is assumed that callers
224 * will look for objects in a pack first.
226 * @param objectId
227 * @return suggested file name
229 public File toFile(final AnyObjectId objectId) {
230 final String n = objectId.name();
231 String d=n.substring(0, 2);
232 String f=n.substring(2);
233 final File[] objectsDirs = objectsDirs();
234 for (File objectsDir : objectsDirs) {
235 File ret = new File(new File(objectsDir, d), f);
236 if (ret.exists())
237 return ret;
239 return new File(new File(objectsDirs[0], d), f);
243 * @param objectId
244 * @return true if the specified object is stored in this repo or any of the
245 * known shared repositories.
247 public boolean hasObject(final AnyObjectId objectId) {
248 final PackFile[] packs = packs();
249 int k = packs.length;
250 while (k > 0) {
251 try {
252 if (packs[--k].hasObject(objectId))
253 return true;
254 } catch (IOException e) {
255 // Assume that means the pack is invalid, and such
256 // packs are treated as though they are empty.
258 continue;
261 return toFile(objectId).isFile();
265 * @param id
266 * SHA-1 of an object.
268 * @return a {@link ObjectLoader} for accessing the data of the named
269 * object, or null if the object does not exist.
270 * @throws IOException
272 public ObjectLoader openObject(final AnyObjectId id)
273 throws IOException {
274 final WindowCursor wc = new WindowCursor();
275 try {
276 return openObject(wc, id);
277 } finally {
278 wc.release();
283 * @param curs
284 * temporary working space associated with the calling thread.
285 * @param id
286 * SHA-1 of an object.
288 * @return a {@link ObjectLoader} for accessing the data of the named
289 * object, or null if the object does not exist.
290 * @throws IOException
292 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
293 throws IOException {
294 final PackFile[] packs = packs();
295 int k = packs.length;
296 while (k > 0) {
297 final ObjectLoader ol = packs[--k].get(curs, id);
298 if (ol != null)
299 return ol;
301 try {
302 return new UnpackedObjectLoader(this, id);
303 } catch (FileNotFoundException fnfe) {
304 return null;
309 * Open object in all packs containing specified object.
311 * @param objectId
312 * id of object to search for
313 * @param curs
314 * temporary working space associated with the calling thread.
315 * @return collection of loaders for this object, from all packs containing
316 * this object
317 * @throws IOException
319 public Collection<PackedObjectLoader> openObjectInAllPacks(
320 final AnyObjectId objectId, final WindowCursor curs)
321 throws IOException {
322 Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
323 openObjectInAllPacks(objectId, result, curs);
324 return result;
328 * Open object in all packs containing specified object.
330 * @param objectId
331 * id of object to search for
332 * @param resultLoaders
333 * result collection of loaders for this object, filled with
334 * loaders from all packs containing specified object
335 * @param curs
336 * temporary working space associated with the calling thread.
337 * @throws IOException
339 void openObjectInAllPacks(final AnyObjectId objectId,
340 final Collection<PackedObjectLoader> resultLoaders,
341 final WindowCursor curs) throws IOException {
342 for (PackFile pack : packs()) {
343 final PackedObjectLoader loader = pack.get(curs, objectId);
344 if (loader != null)
345 resultLoaders.add(loader);
350 * @param id
351 * SHA'1 of a blob
352 * @return an {@link ObjectLoader} for accessing the data of a named blob
353 * @throws IOException
355 public ObjectLoader openBlob(final ObjectId id) throws IOException {
356 return openObject(id);
360 * @param id
361 * SHA'1 of a tree
362 * @return an {@link ObjectLoader} for accessing the data of a named tree
363 * @throws IOException
365 public ObjectLoader openTree(final ObjectId id) throws IOException {
366 return openObject(id);
370 * Access a Commit object using a symbolic reference. This reference may
371 * be a SHA-1 or ref in combination with a number of symbols translating
372 * from one ref or SHA1-1 to another, such as HEAD^ etc.
374 * @param revstr a reference to a git commit object
375 * @return a Commit named by the specified string
376 * @throws IOException for I/O error or unexpected object type.
378 * @see #resolve(String)
380 public Commit mapCommit(final String revstr) throws IOException {
381 final ObjectId id = resolve(revstr);
382 return id != null ? mapCommit(id) : null;
386 * Access any type of Git object by id and
388 * @param id
389 * SHA-1 of object to read
390 * @param refName optional, only relevant for simple tags
391 * @return The Git object if found or null
392 * @throws IOException
394 public Object mapObject(final ObjectId id, final String refName) throws IOException {
395 final ObjectLoader or = openObject(id);
396 if (or == null)
397 return null;
398 final byte[] raw = or.getBytes();
399 switch (or.getType()) {
400 case Constants.OBJ_TREE:
401 return makeTree(id, raw);
403 case Constants.OBJ_COMMIT:
404 return makeCommit(id, raw);
406 case Constants.OBJ_TAG:
407 return makeTag(id, refName, raw);
409 case Constants.OBJ_BLOB:
410 return raw;
412 default:
413 throw new IncorrectObjectTypeException(id,
414 "COMMIT nor TREE nor BLOB nor TAG");
419 * Access a Commit by SHA'1 id.
420 * @param id
421 * @return Commit or null
422 * @throws IOException for I/O error or unexpected object type.
424 public Commit mapCommit(final ObjectId id) throws IOException {
425 final ObjectLoader or = openObject(id);
426 if (or == null)
427 return null;
428 final byte[] raw = or.getBytes();
429 if (Constants.OBJ_COMMIT == or.getType())
430 return new Commit(this, id, raw);
431 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
434 private Commit makeCommit(final ObjectId id, final byte[] raw) {
435 Commit ret = new Commit(this, id, raw);
436 return ret;
440 * Access a Tree object using a symbolic reference. This reference may
441 * be a SHA-1 or ref in combination with a number of symbols translating
442 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
444 * @param revstr a reference to a git commit object
445 * @return a Tree named by the specified string
446 * @throws IOException
448 * @see #resolve(String)
450 public Tree mapTree(final String revstr) throws IOException {
451 final ObjectId id = resolve(revstr);
452 return id != null ? mapTree(id) : null;
456 * Access a Tree by SHA'1 id.
457 * @param id
458 * @return Tree or null
459 * @throws IOException for I/O error or unexpected object type.
461 public Tree mapTree(final ObjectId id) throws IOException {
462 final ObjectLoader or = openObject(id);
463 if (or == null)
464 return null;
465 final byte[] raw = or.getBytes();
466 switch (or.getType()) {
467 case Constants.OBJ_TREE:
468 return new Tree(this, id, raw);
470 case Constants.OBJ_COMMIT:
471 return mapTree(ObjectId.fromString(raw, 5));
473 default:
474 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
478 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
479 Tree ret = new Tree(this, id, raw);
480 return ret;
483 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
484 Tag ret = new Tag(this, id, refName, raw);
485 return ret;
489 * Access a tag by symbolic name.
491 * @param revstr
492 * @return a Tag or null
493 * @throws IOException on I/O error or unexpected type
495 public Tag mapTag(String revstr) throws IOException {
496 final ObjectId id = resolve(revstr);
497 return id != null ? mapTag(revstr, id) : null;
501 * Access a Tag by SHA'1 id
502 * @param refName
503 * @param id
504 * @return Commit or null
505 * @throws IOException for I/O error or unexpected object type.
507 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
508 final ObjectLoader or = openObject(id);
509 if (or == null)
510 return null;
511 final byte[] raw = or.getBytes();
512 if (Constants.OBJ_TAG == or.getType())
513 return new Tag(this, id, refName, raw);
514 return new Tag(this, id, refName, null);
518 * Create a command to update, create or delete a ref in this repository.
520 * @param ref
521 * name of the ref the caller wants to modify.
522 * @return an update command. The caller must finish populating this command
523 * and then invoke one of the update methods to actually make a
524 * change.
525 * @throws IOException
526 * a symbolic ref was passed in and could not be resolved back
527 * to the base ref, as the symbolic ref could not be read.
529 public RefUpdate updateRef(final String ref) throws IOException {
530 return refs.newUpdate(ref);
534 * Parse a git revision string and return an object id.
536 * Currently supported is combinations of these.
537 * <ul>
538 * <li>SHA-1 - a SHA-1</li>
539 * <li>refs/... - a ref name</li>
540 * <li>ref^n - nth parent reference</li>
541 * <li>ref~n - distance via parent reference</li>
542 * <li>ref@{n} - nth version of ref</li>
543 * <li>ref^{tree} - tree references by ref</li>
544 * <li>ref^{commit} - commit references by ref</li>
545 * </ul>
547 * Not supported is
548 * <ul>
549 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
550 * <li>abbreviated SHA-1's</li>
551 * </ul>
553 * @param revstr A git object references expression
554 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
555 * @throws IOException on serious errors
557 public ObjectId resolve(final String revstr) throws IOException {
558 char[] rev = revstr.toCharArray();
559 Object ref = null;
560 ObjectId refId = null;
561 for (int i = 0; i < rev.length; ++i) {
562 switch (rev[i]) {
563 case '^':
564 if (refId == null) {
565 String refstr = new String(rev,0,i);
566 refId = resolveSimple(refstr);
567 if (refId == null)
568 return null;
570 if (i + 1 < rev.length) {
571 switch (rev[i + 1]) {
572 case '0':
573 case '1':
574 case '2':
575 case '3':
576 case '4':
577 case '5':
578 case '6':
579 case '7':
580 case '8':
581 case '9':
582 int j;
583 ref = mapObject(refId, null);
584 while (ref instanceof Tag) {
585 Tag tag = (Tag)ref;
586 refId = tag.getObjId();
587 ref = mapObject(refId, null);
589 if (!(ref instanceof Commit))
590 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
591 for (j=i+1; j<rev.length; ++j) {
592 if (!Character.isDigit(rev[j]))
593 break;
595 String parentnum = new String(rev, i+1, j-i-1);
596 int pnum;
597 try {
598 pnum = Integer.parseInt(parentnum);
599 } catch (NumberFormatException e) {
600 throw new RevisionSyntaxException(
601 "Invalid commit parent number",
602 revstr);
604 if (pnum != 0) {
605 final ObjectId parents[] = ((Commit) ref)
606 .getParentIds();
607 if (pnum > parents.length)
608 refId = null;
609 else
610 refId = parents[pnum - 1];
612 i = j - 1;
613 break;
614 case '{':
615 int k;
616 String item = null;
617 for (k=i+2; k<rev.length; ++k) {
618 if (rev[k] == '}') {
619 item = new String(rev, i+2, k-i-2);
620 break;
623 i = k;
624 if (item != null)
625 if (item.equals("tree")) {
626 ref = mapObject(refId, null);
627 while (ref instanceof Tag) {
628 Tag t = (Tag)ref;
629 refId = t.getObjId();
630 ref = mapObject(refId, null);
632 if (ref instanceof Treeish)
633 refId = ((Treeish)ref).getTreeId();
634 else
635 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
637 else if (item.equals("commit")) {
638 ref = mapObject(refId, null);
639 while (ref instanceof Tag) {
640 Tag t = (Tag)ref;
641 refId = t.getObjId();
642 ref = mapObject(refId, null);
644 if (!(ref instanceof Commit))
645 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
647 else if (item.equals("blob")) {
648 ref = mapObject(refId, null);
649 while (ref instanceof Tag) {
650 Tag t = (Tag)ref;
651 refId = t.getObjId();
652 ref = mapObject(refId, null);
654 if (!(ref instanceof byte[]))
655 throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB);
657 else if (item.equals("")) {
658 ref = mapObject(refId, null);
659 while (ref instanceof Tag) {
660 Tag t = (Tag)ref;
661 refId = t.getObjId();
662 ref = mapObject(refId, null);
665 else
666 throw new RevisionSyntaxException(revstr);
667 else
668 throw new RevisionSyntaxException(revstr);
669 break;
670 default:
671 ref = mapObject(refId, null);
672 if (ref instanceof Commit) {
673 final ObjectId parents[] = ((Commit) ref)
674 .getParentIds();
675 if (parents.length == 0)
676 refId = null;
677 else
678 refId = parents[0];
679 } else
680 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
683 } else {
684 ref = mapObject(refId, null);
685 while (ref instanceof Tag) {
686 Tag tag = (Tag)ref;
687 refId = tag.getObjId();
688 ref = mapObject(refId, null);
690 if (ref instanceof Commit) {
691 final ObjectId parents[] = ((Commit) ref)
692 .getParentIds();
693 if (parents.length == 0)
694 refId = null;
695 else
696 refId = parents[0];
697 } else
698 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
700 break;
701 case '~':
702 if (ref == null) {
703 String refstr = new String(rev,0,i);
704 refId = resolveSimple(refstr);
705 if (refId == null)
706 return null;
707 ref = mapObject(refId, null);
709 while (ref instanceof Tag) {
710 Tag tag = (Tag)ref;
711 refId = tag.getObjId();
712 ref = mapObject(refId, null);
714 if (!(ref instanceof Commit))
715 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
716 int l;
717 for (l = i + 1; l < rev.length; ++l) {
718 if (!Character.isDigit(rev[l]))
719 break;
721 String distnum = new String(rev, i+1, l-i-1);
722 int dist;
723 try {
724 dist = Integer.parseInt(distnum);
725 } catch (NumberFormatException e) {
726 throw new RevisionSyntaxException(
727 "Invalid ancestry length", revstr);
729 while (dist > 0) {
730 final ObjectId[] parents = ((Commit) ref).getParentIds();
731 if (parents.length == 0) {
732 refId = null;
733 break;
735 refId = parents[0];
736 ref = mapCommit(refId);
737 --dist;
739 i = l - 1;
740 break;
741 case '@':
742 int m;
743 String time = null;
744 for (m=i+2; m<rev.length; ++m) {
745 if (rev[m] == '}') {
746 time = new String(rev, i+2, m-i-2);
747 break;
750 if (time != null)
751 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
752 i = m - 1;
753 break;
754 default:
755 if (refId != null)
756 throw new RevisionSyntaxException(revstr);
759 if (refId == null)
760 refId = resolveSimple(revstr);
761 return refId;
764 private ObjectId resolveSimple(final String revstr) throws IOException {
765 if (ObjectId.isId(revstr))
766 return ObjectId.fromString(revstr);
767 final Ref r = refs.readRef(revstr);
768 return r != null ? r.getObjectId() : null;
772 * Close all resources used by this repository
774 public void close() {
775 closePacks();
778 synchronized void closePacks() {
779 for (int k = packFileList.length - 1; k >= 0; k--)
780 packFileList[k].close();
781 packFileList = new PackFile[0];
785 * Add a single existing pack to the list of available pack files.
787 * @param pack
788 * path of the pack file to open.
789 * @param idx
790 * path of the corresponding index file.
791 * @throws IOException
792 * index file could not be opened, read, or is not recognized as
793 * a Git pack file index.
795 public void openPack(final File pack, final File idx) throws IOException {
796 final String p = pack.getName();
797 final String i = idx.getName();
798 if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack"))
799 throw new IllegalArgumentException("Not a valid pack " + pack);
800 if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx"))
801 throw new IllegalArgumentException("Not a valid pack " + idx);
802 if (!p.substring(0,45).equals(i.substring(0,45)))
803 throw new IllegalArgumentException("Pack " + pack
804 + "does not match index " + idx);
806 synchronized (this) {
807 final PackFile[] cur = packFileList;
808 final PackFile[] arr = new PackFile[cur.length + 1];
809 System.arraycopy(cur, 0, arr, 1, cur.length);
810 arr[0] = new PackFile(idx, pack);
811 packFileList = arr;
816 * Scan the object dirs, including alternates for packs
817 * to use.
819 public void scanForPacks() {
820 final ArrayList<PackFile> p = new ArrayList<PackFile>();
821 p.addAll(Arrays.asList(packs()));
822 for (final File d : objectsDirs())
823 scanForPacks(new File(d, "pack"), p);
824 final PackFile[] arr = new PackFile[p.size()];
825 p.toArray(arr);
826 Arrays.sort(arr, PackFile.SORT);
827 synchronized (this) {
828 packFileList = arr;
832 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
833 final String[] idxList = packDir.list(new FilenameFilter() {
834 public boolean accept(final File baseDir, final String n) {
835 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
836 return n.length() == 49 && n.endsWith(".idx")
837 && n.startsWith("pack-");
840 if (idxList != null) {
841 SCAN: for (final String indexName : idxList) {
842 final String n = indexName.substring(0, indexName.length() - 4);
843 final File idxFile = new File(packDir, n + ".idx");
844 final File packFile = new File(packDir, n + ".pack");
846 if (!packFile.isFile()) {
847 // Sometimes C Git's http fetch transport leaves a
848 // .idx file behind and does not download the .pack.
849 // We have to skip over such useless indexes.
851 continue;
854 for (final PackFile p : packList) {
855 if (packFile.equals(p.getPackFile()))
856 continue SCAN;
859 packList.add(new PackFile(idxFile, packFile));
865 * Writes a symref (e.g. HEAD) to disk
867 * @param name symref name
868 * @param target pointed to ref
869 * @throws IOException
871 public void writeSymref(final String name, final String target)
872 throws IOException {
873 refs.link(name, target);
876 public String toString() {
877 return "Repository[" + getDirectory() + "]";
881 * @return name of current branch
882 * @throws IOException
884 public String getFullBranch() throws IOException {
885 final File ptr = new File(getDirectory(),Constants.HEAD);
886 final BufferedReader br = new BufferedReader(new FileReader(ptr));
887 String ref;
888 try {
889 ref = br.readLine();
890 } finally {
891 br.close();
893 if (ref.startsWith("ref: "))
894 ref = ref.substring(5);
895 return ref;
899 * @return name of current branch.
900 * @throws IOException
902 public String getBranch() throws IOException {
903 try {
904 final File ptr = new File(getDirectory(), Constants.HEAD);
905 final BufferedReader br = new BufferedReader(new FileReader(ptr));
906 String ref;
907 try {
908 ref = br.readLine();
909 } finally {
910 br.close();
912 if (ref.startsWith("ref: "))
913 ref = ref.substring(5);
914 if (ref.startsWith("refs/heads/"))
915 ref = ref.substring(11);
916 return ref;
917 } catch (FileNotFoundException e) {
918 final File ptr = new File(getDirectory(),"head-name");
919 final BufferedReader br = new BufferedReader(new FileReader(ptr));
920 String ref;
921 try {
922 ref = br.readLine();
923 } finally {
924 br.close();
926 return ref;
931 * Get a ref by name.
933 * @param name
934 * the name of the ref to lookup. May be a short-hand form, e.g.
935 * "master" which is is automatically expanded to
936 * "refs/heads/master" if "refs/heads/master" already exists.
937 * @return the Ref with the given name, or null if it does not exist
938 * @throws IOException
940 public Ref getRef(final String name) throws IOException {
941 return refs.readRef(name);
945 * @return all known refs (heads, tags, remotes).
947 public Map<String, Ref> getAllRefs() {
948 return refs.getAllRefs();
952 * @return all tags; key is short tag name ("v1.0") and value of the entry
953 * contains the ref with the full tag name ("refs/tags/v1.0").
955 public Map<String, Ref> getTags() {
956 return refs.getTags();
960 * Peel a possibly unpeeled ref and updates it.
961 * <p>
962 * If the ref cannot be peeled (as it does not refer to an annotated tag)
963 * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
965 * @param ref
966 * The ref to peel
967 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
968 * new Ref object representing the same data as Ref, but isPeeled()
969 * will be true and getPeeledObjectId will contain the peeled object
970 * (or null).
972 public Ref peel(final Ref ref) {
973 return refs.peel(ref);
977 * @return a map with all objects referenced by a peeled ref.
979 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
980 Map<String, Ref> allRefs = getAllRefs();
981 Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
982 for (Ref ref : allRefs.values()) {
983 if (!ref.isPeeled())
984 ref = peel(ref);
985 AnyObjectId target = ref.getPeeledObjectId();
986 if (target == null)
987 target = ref.getObjectId();
988 // We assume most Sets here are singletons
989 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
990 if (oset != null) {
991 // that was not the case (rare)
992 if (oset.size() == 1) {
993 // Was a read-only singleton, we must copy to a new Set
994 oset = new HashSet<Ref>(oset);
996 ret.put(target, oset);
997 oset.add(ref);
1000 return ret;
1003 /** Clean up stale caches */
1004 public void refreshFromDisk() {
1005 refs.clearCache();
1009 * @return a representation of the index associated with this repo
1010 * @throws IOException
1012 public GitIndex getIndex() throws IOException {
1013 if (index == null) {
1014 index = new GitIndex(this);
1015 index.read();
1016 } else {
1017 index.rereadIfNecessary();
1019 return index;
1022 static byte[] gitInternalSlash(byte[] bytes) {
1023 if (File.separatorChar == '/')
1024 return bytes;
1025 for (int i=0; i<bytes.length; ++i)
1026 if (bytes[i] == File.separatorChar)
1027 bytes[i] = '/';
1028 return bytes;
1032 * @return an important state
1034 public RepositoryState getRepositoryState() {
1035 // Pre Git-1.6 logic
1036 if (new File(getWorkDir(), ".dotest").exists())
1037 return RepositoryState.REBASING;
1038 if (new File(gitDir,".dotest-merge").exists())
1039 return RepositoryState.REBASING_INTERACTIVE;
1041 // From 1.6 onwards
1042 if (new File(getDirectory(),"rebase-apply/rebasing").exists())
1043 return RepositoryState.REBASING_REBASING;
1044 if (new File(getDirectory(),"rebase-apply/applying").exists())
1045 return RepositoryState.APPLY;
1046 if (new File(getDirectory(),"rebase-apply").exists())
1047 return RepositoryState.REBASING;
1049 if (new File(getDirectory(),"rebase-merge/interactive").exists())
1050 return RepositoryState.REBASING_INTERACTIVE;
1051 if (new File(getDirectory(),"rebase-merge").exists())
1052 return RepositoryState.REBASING_MERGE;
1054 // Both versions
1055 if (new File(gitDir,"MERGE_HEAD").exists())
1056 return RepositoryState.MERGING;
1057 if (new File(gitDir,"BISECT_LOG").exists())
1058 return RepositoryState.BISECTING;
1060 return RepositoryState.SAFE;
1064 * Check validity of a ref name. It must not contain character that has
1065 * a special meaning in a Git object reference expression. Some other
1066 * dangerous characters are also excluded.
1068 * @param refName
1070 * @return true if refName is a valid ref name
1072 public static boolean isValidRefName(final String refName) {
1073 final int len = refName.length();
1074 if (len == 0)
1075 return false;
1077 char p = '\0';
1078 for (int i=0; i<len; ++i) {
1079 char c = refName.charAt(i);
1080 if (c <= ' ')
1081 return false;
1082 switch(c) {
1083 case '.':
1084 if (i == 0)
1085 return false;
1086 if (p == '/')
1087 return false;
1088 if (p == '.')
1089 return false;
1090 break;
1091 case '/':
1092 if (i == 0)
1093 return false;
1094 if (i == len -1)
1095 return false;
1096 break;
1097 case '~': case '^': case ':':
1098 case '?': case '[':
1099 return false;
1100 case '*':
1101 return false;
1103 p = c;
1105 return true;
1109 * Strip work dir and return normalized repository path
1111 * @param wd Work dir
1112 * @param f File whose path shall be stripped of its workdir
1113 * @return normalized repository relative path
1115 public static String stripWorkDir(File wd, File f) {
1116 String relName = f.getPath().substring(wd.getPath().length() + 1);
1117 relName = relName.replace(File.separatorChar, '/');
1118 return relName;
1122 * @return the workdir file, i.e. where the files are checked out
1124 public File getWorkDir() {
1125 return getDirectory().getParentFile();
1129 * Register a {@link RepositoryListener} which will be notified
1130 * when ref changes are detected.
1132 * @param l
1134 public void addRepositoryChangedListener(final RepositoryListener l) {
1135 listeners.add(l);
1139 * Remove a registered {@link RepositoryListener}
1140 * @param l
1142 public void removeRepositoryChangedListener(final RepositoryListener l) {
1143 listeners.remove(l);
1147 * Register a global {@link RepositoryListener} which will be notified
1148 * when a ref changes in any repository are detected.
1150 * @param l
1152 public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
1153 allListeners.add(l);
1157 * Remove a globally registered {@link RepositoryListener}
1158 * @param l
1160 public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
1161 allListeners.remove(l);
1164 void fireRefsMaybeChanged() {
1165 if (refs.lastRefModification != refs.lastNotifiedRefModification) {
1166 refs.lastNotifiedRefModification = refs.lastRefModification;
1167 final RefsChangedEvent event = new RefsChangedEvent(this);
1168 List<RepositoryListener> all;
1169 synchronized (listeners) {
1170 all = new ArrayList<RepositoryListener>(listeners);
1172 synchronized (allListeners) {
1173 all.addAll(allListeners);
1175 for (final RepositoryListener l : all) {
1176 l.refsChanged(event);
1181 void fireIndexChanged() {
1182 final IndexChangedEvent event = new IndexChangedEvent(this);
1183 List<RepositoryListener> all;
1184 synchronized (listeners) {
1185 all = new ArrayList<RepositoryListener>(listeners);
1187 synchronized (allListeners) {
1188 all.addAll(allListeners);
1190 for (final RepositoryListener l : all) {
1191 l.indexChanged(event);
1196 * Force a scan for changed refs.
1198 * @throws IOException
1200 public void scanForRepoChanges() throws IOException {
1201 getAllRefs(); // This will look for changes to refs
1202 getIndex(); // This will detect changes in the index