Make revision parser more string and manage more cases
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob455a68e26ad18228912081882f1b8894fd872f2b
1 /*
2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.jgit.lib;
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileReader;
23 import java.io.FilenameFilter;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Set;
31 import org.spearce.jgit.errors.IncorrectObjectTypeException;
32 import org.spearce.jgit.errors.ObjectWritingException;
33 import org.spearce.jgit.errors.RevisionSyntaxException;
34 import org.spearce.jgit.stgit.StGitPatch;
35 import org.spearce.jgit.util.FS;
37 /**
38 * Represents a Git repository. A repository holds all objects and refs used for
39 * managing source code (could by any type of file, but source code is what
40 * SCM's are typically used for).
42 * In Git terms all data is stored in GIT_DIR, typically a directory called
43 * .git. A work tree is maintained unless the repository is a bare repository.
44 * Typically the .git directory is located at the root of the work dir.
46 * <ul>
47 * <li>GIT_DIR
48 * <ul>
49 * <li>objects/ - objects</li>
50 * <li>refs/ - tags and heads</li>
51 * <li>config - configuration</li>
52 * <li>info/ - more configurations</li>
53 * </ul>
54 * </li>
55 * </ul>
57 * This implementation only handles a subtly undocumented subset of git features.
60 public class Repository {
61 private static final String[] refSearchPaths = { "", "refs/", "refs/tags/",
62 Constants.HEADS_PREFIX + "/", "refs/" + Constants.REMOTES_PREFIX + "/" };
64 private final File gitDir;
66 private final File[] objectsDirs;
68 private final File refsDir;
70 private final File packedRefsFile;
72 private final RepositoryConfig config;
74 private PackFile[] packs;
76 private final WindowCache windows;
78 private GitIndex index;
80 /**
81 * Construct a representation of this git repo managing a Git repository.
83 * @param d
84 * GIT_DIR
85 * @throws IOException
87 public Repository(final File d) throws IOException {
88 this(null, d);
91 /**
92 * Construct a representation of a Git repository.
94 * @param wc
95 * cache this repository's data will be cached through during
96 * access. May be shared with another repository, or null to
97 * indicate this repository should allocate its own private
98 * cache.
99 * @param d
100 * GIT_DIR (the location of the repository metadata).
101 * @throws IOException
102 * the repository appears to already exist but cannot be
103 * accessed.
105 public Repository(final WindowCache wc, final File d) throws IOException {
106 gitDir = d.getAbsoluteFile();
107 try {
108 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
109 new ArrayList<File>()).toArray(new File[0]);
110 } catch (IOException e) {
111 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
112 ex.initCause(e);
113 throw ex;
115 refsDir = FS.resolve(gitDir, "refs");
116 packedRefsFile = FS.resolve(gitDir, "packed-refs");
117 packs = new PackFile[0];
118 config = new RepositoryConfig(this);
120 final boolean isExisting = objectsDirs[0].exists();
121 if (isExisting) {
122 getConfig().load();
123 final String repositoryFormatVersion = getConfig().getString(
124 "core", null, "repositoryFormatVersion");
125 if (!"0".equals(repositoryFormatVersion)) {
126 throw new IOException("Unknown repository format \""
127 + repositoryFormatVersion + "\"; expected \"0\".");
129 } else {
130 getConfig().create();
132 windows = wc != null ? wc : new WindowCache(getConfig());
133 if (isExisting)
134 scanForPacks();
137 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
138 ret.add(objectsDir);
139 final File altFile = FS.resolve(objectsDir, "info/alternates");
140 if (altFile.exists()) {
141 BufferedReader ar = new BufferedReader(new FileReader(altFile));
142 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
143 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
145 ar.close();
147 return ret;
151 * Create a new Git repository initializing the necessary files and
152 * directories.
154 * @throws IOException
156 public void create() throws IOException {
157 if (gitDir.exists()) {
158 throw new IllegalStateException("Repository already exists: "
159 + gitDir);
162 gitDir.mkdirs();
164 objectsDirs[0].mkdirs();
165 new File(objectsDirs[0], "pack").mkdir();
166 new File(objectsDirs[0], "info").mkdir();
168 refsDir.mkdir();
169 new File(refsDir, "heads").mkdir();
170 new File(refsDir, "tags").mkdir();
172 new File(gitDir, "branches").mkdir();
173 new File(gitDir, "remotes").mkdir();
174 writeSymref("HEAD", "refs/heads/master");
176 getConfig().create();
177 getConfig().save();
181 * @return GIT_DIR
183 public File getDirectory() {
184 return gitDir;
188 * @return the directory containg the objects owned by this repository.
190 public File getObjectsDirectory() {
191 return objectsDirs[0];
195 * @return the configuration of this repository
197 public RepositoryConfig getConfig() {
198 return config;
202 * @return the cache needed for accessing packed objects in this repository.
204 public WindowCache getWindowCache() {
205 return windows;
209 * Construct a filename where the loose object having a specified SHA-1
210 * should be stored. If the object is stored in a shared repository the path
211 * to the alternative repo will be returned. If the object is not yet store
212 * a usable path in this repo will be returned. It is assumed that callers
213 * will look for objects in a pack first.
215 * @param objectId
216 * @return suggested file name
218 public File toFile(final AnyObjectId objectId) {
219 final String n = objectId.toString();
220 String d=n.substring(0, 2);
221 String f=n.substring(2);
222 for (int i=0; i<objectsDirs.length; ++i) {
223 File ret = new File(new File(objectsDirs[i], d), f);
224 if (ret.exists())
225 return ret;
227 return new File(new File(objectsDirs[0], d), f);
231 * @param objectId
232 * @return true if the specified object is stored in this repo or any of the
233 * known shared repositories.
235 public boolean hasObject(final AnyObjectId objectId) {
236 int k = packs.length;
237 if (k > 0) {
238 do {
239 if (packs[--k].hasObject(objectId))
240 return true;
241 } while (k > 0);
243 return toFile(objectId).isFile();
247 * @param id
248 * SHA-1 of an object.
250 * @return a {@link ObjectLoader} for accessing the data of the named
251 * object, or null if the object does not exist.
252 * @throws IOException
254 public ObjectLoader openObject(final AnyObjectId id)
255 throws IOException {
256 return openObject(new WindowCursor(),id);
260 * @param curs
261 * temporary working space associated with the calling thread.
262 * @param id
263 * SHA-1 of an object.
265 * @return a {@link ObjectLoader} for accessing the data of the named
266 * object, or null if the object does not exist.
267 * @throws IOException
269 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
270 throws IOException {
271 int k = packs.length;
272 if (k > 0) {
273 do {
274 try {
275 final ObjectLoader ol = packs[--k].get(curs, id);
276 if (ol != null)
277 return ol;
278 } catch (IOException ioe) {
279 // This shouldn't happen unless the pack was corrupted
280 // after we opened it or the VM runs out of memory. This is
281 // a know problem with memory mapped I/O in java and have
282 // been noticed with JDK < 1.6. Tell the gc that now is a good
283 // time to collect and try once more.
284 try {
285 curs.release();
286 System.gc();
287 final ObjectLoader ol = packs[k].get(curs, id);
288 if (ol != null)
289 return ol;
290 } catch (IOException ioe2) {
291 ioe2.printStackTrace();
292 ioe.printStackTrace();
293 // Still fails.. that's BAD, maybe the pack has
294 // been corrupted after all, or the gc didn't manage
295 // to release enough previously mmaped areas.
298 } while (k > 0);
300 try {
301 return new UnpackedObjectLoader(this, id.toObjectId());
302 } catch (FileNotFoundException fnfe) {
303 return null;
308 * @param id
309 * SHA'1 of a blob
310 * @return an {@link ObjectLoader} for accessing the data of a named blob
311 * @throws IOException
313 public ObjectLoader openBlob(final ObjectId id) throws IOException {
314 return openObject(id);
318 * @param id
319 * SHA'1 of a tree
320 * @return an {@link ObjectLoader} for accessing the data of a named tree
321 * @throws IOException
323 public ObjectLoader openTree(final ObjectId id) throws IOException {
324 return openObject(id);
328 * Access a Commit object using a symbolic reference. This reference may
329 * be a SHA-1 or ref in combination with a number of symbols translating
330 * from one ref or SHA1-1 to another, such as HEAD^ etc.
332 * @param revstr a reference to a git commit object
333 * @return a Commit named by the specified string
334 * @throws IOException for I/O error or unexpected object type.
336 * @see #resolve(String)
338 public Commit mapCommit(final String revstr) throws IOException {
339 final ObjectId id = resolve(revstr);
340 return id != null ? mapCommit(id) : null;
344 * Access any type of Git object by id and
346 * @param id
347 * SHA-1 of object to read
348 * @param refName optional, only relevant for simple tags
349 * @return The Git object if found or null
350 * @throws IOException
352 public Object mapObject(final ObjectId id, final String refName) throws IOException {
353 final ObjectLoader or = openObject(id);
354 final byte[] raw = or.getBytes();
355 if (or.getType() == Constants.OBJ_TREE)
356 return makeTree(id, raw);
357 if (or.getType() == Constants.OBJ_COMMIT)
358 return makeCommit(id, raw);
359 if (or.getType() == Constants.OBJ_TAG)
360 return makeTag(id, refName, raw);
361 if (or.getType() == Constants.OBJ_BLOB)
362 return raw;
363 return null;
367 * Access a Commit by SHA'1 id.
368 * @param id
369 * @return Commit or null
370 * @throws IOException for I/O error or unexpected object type.
372 public Commit mapCommit(final ObjectId id) throws IOException {
373 final ObjectLoader or = openObject(id);
374 if (or == null)
375 return null;
376 final byte[] raw = or.getBytes();
377 if (Constants.OBJ_COMMIT == or.getType())
378 return new Commit(this, id, raw);
379 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
382 private Commit makeCommit(final ObjectId id, final byte[] raw) {
383 Commit ret = new Commit(this, id, raw);
384 return ret;
388 * Access a Tree object using a symbolic reference. This reference may
389 * be a SHA-1 or ref in combination with a number of symbols translating
390 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
392 * @param revstr a reference to a git commit object
393 * @return a Tree named by the specified string
394 * @throws IOException
396 * @see #resolve(String)
398 public Tree mapTree(final String revstr) throws IOException {
399 final ObjectId id = resolve(revstr);
400 return id != null ? mapTree(id) : null;
404 * Access a Tree by SHA'1 id.
405 * @param id
406 * @return Tree or null
407 * @throws IOException for I/O error or unexpected object type.
409 public Tree mapTree(final ObjectId id) throws IOException {
410 final ObjectLoader or = openObject(id);
411 if (or == null)
412 return null;
413 final byte[] raw = or.getBytes();
414 if (Constants.OBJ_TREE == or.getType()) {
415 return new Tree(this, id, raw);
417 if (Constants.OBJ_COMMIT == or.getType())
418 return mapTree(ObjectId.fromString(raw, 5));
419 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
422 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
423 Tree ret = new Tree(this, id, raw);
424 return ret;
427 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
428 Tag ret = new Tag(this, id, refName, raw);
429 return ret;
433 * Access a tag by symbolic name.
435 * @param revstr
436 * @return a Tag or null
437 * @throws IOException on I/O error or unexpected type
439 public Tag mapTag(String revstr) throws IOException {
440 final ObjectId id = resolve(revstr);
441 return id != null ? mapTag(revstr, id) : null;
445 * Access a Tag by SHA'1 id
446 * @param refName
447 * @param id
448 * @return Commit or null
449 * @throws IOException for I/O error or unexpected object type.
451 public Tag mapTag(final String refName, 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_TAG == or.getType())
457 return new Tag(this, id, refName, raw);
458 return new Tag(this, id, refName, null);
462 * Get a locked handle to a ref suitable for updating or creating.
464 * @param ref name to lock
465 * @return a locked ref
466 * @throws IOException
468 public LockFile lockRef(final String ref) throws IOException {
469 final Ref r = readRef(ref, true);
470 final LockFile l = new LockFile(fileForRef(r.getName()));
471 return l.lock() ? l : null;
475 * Parse a git revision string and return an object id.
477 * Currently supported is combinations of these.
478 * <ul>
479 * <li>SHA-1 - a SHA-1</li>
480 * <li>refs/... - a ref name</li>
481 * <li>ref^n - nth parent reference</li>
482 * <li>ref~n - distance via parent reference</li>
483 * <li>ref@{n} - nth version of ref</li>
484 * <li>ref^{tree} - tree references by ref</li>
485 * <li>ref^{commit} - commit references by ref</li>
486 * </ul>
488 * Not supported is
489 * <ul>
490 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
491 * <li>abbreviated SHA-1's</li>
492 * </ul>
494 * @param revstr A git object references expression
495 * @return an ObjectId
496 * @throws IOException on serious errors
498 public ObjectId resolve(final String revstr) throws IOException {
499 char[] rev = revstr.toCharArray();
500 Object ref = null;
501 ObjectId refId = null;
502 for (int i = 0; i < rev.length; ++i) {
503 switch (rev[i]) {
504 case '^':
505 if (refId == null) {
506 String refstr = new String(rev,0,i);
507 refId = resolveSimple(refstr);
508 if (refId == null)
509 return null;
511 if (i + 1 < rev.length) {
512 switch (rev[i + 1]) {
513 case '0':
514 case '1':
515 case '2':
516 case '3':
517 case '4':
518 case '5':
519 case '6':
520 case '7':
521 case '8':
522 case '9':
523 int j;
524 ref = mapObject(refId, null);
525 if (!(ref instanceof Commit))
526 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
527 for (j=i+1; j<rev.length; ++j) {
528 if (!Character.isDigit(rev[j]))
529 break;
531 String parentnum = new String(rev, i+1, j-i-1);
532 int pnum = Integer.parseInt(parentnum);
533 if (pnum != 0)
534 refId = ((Commit)ref).getParentIds()[pnum - 1];
535 i = j - 1;
536 break;
537 case '{':
538 int k;
539 String item = null;
540 for (k=i+2; k<rev.length; ++k) {
541 if (rev[k] == '}') {
542 item = new String(rev, i+2, k-i-2);
543 break;
546 i = k;
547 if (item != null)
548 if (item.equals("tree")) {
549 ref = mapObject(refId, null);
550 while (ref instanceof Tag) {
551 Tag t = (Tag)ref;
552 refId = t.getObjId();
553 ref = mapObject(refId, null);
555 if (ref instanceof Treeish)
556 refId = ((Treeish)ref).getTreeId();
557 else
558 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
560 else if (item.equals("commit")) {
561 ref = mapObject(refId, null);
562 while (ref instanceof Tag) {
563 Tag t = (Tag)ref;
564 refId = t.getObjId();
565 ref = mapObject(refId, null);
567 if (!(ref instanceof Commit))
568 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
570 else if (item.equals("blob")) {
571 ref = mapObject(refId, null);
572 while (ref instanceof Tag) {
573 Tag t = (Tag)ref;
574 refId = t.getObjId();
575 ref = mapObject(refId, null);
577 if (!(ref instanceof byte[]))
578 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
580 else if (item.equals("")) {
581 ref = mapObject(refId, null);
582 if (ref instanceof Tag)
583 refId = ((Tag)ref).getObjId();
584 else {
585 // self
588 else
589 throw new RevisionSyntaxException(revstr);
590 else
591 throw new RevisionSyntaxException(revstr);
592 break;
593 default:
594 ref = mapObject(refId, null);
595 if (ref instanceof Commit)
596 refId = ((Commit)ref).getParentIds()[0];
597 else
598 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
601 } else {
602 ref = mapObject(refId, null);
603 if (ref instanceof Commit)
604 refId = ((Commit)ref).getParentIds()[0];
605 else
606 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
608 break;
609 case '~':
610 if (ref == null) {
611 String refstr = new String(rev,0,i);
612 refId = resolveSimple(refstr);
613 ref = mapCommit(refId);
615 int l;
616 for (l = i + 1; l < rev.length; ++l) {
617 if (!Character.isDigit(rev[l]))
618 break;
620 String distnum = new String(rev, i+1, l-i-1);
621 int dist = Integer.parseInt(distnum);
622 while (dist >= 0) {
623 refId = ((Commit)ref).getParentIds()[0];
624 ref = mapCommit(refId);
625 --dist;
627 i = l - 1;
628 break;
629 case '@':
630 int m;
631 String time = null;
632 for (m=i+2; m<rev.length; ++m) {
633 if (rev[m] == '}') {
634 time = new String(rev, i+2, m-i-2);
635 break;
638 if (time != null)
639 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
640 i = m - 1;
641 break;
642 default:
643 if (refId != null)
644 throw new RevisionSyntaxException(revstr);
647 if (refId == null)
648 refId = resolveSimple(revstr);
649 return refId;
652 private ObjectId resolveSimple(final String revstr) throws IOException {
653 if (ObjectId.isId(revstr))
654 return ObjectId.fromString(revstr);
655 final Ref r = readRef(revstr, false);
656 if (r != null) {
657 return r.getObjectId();
659 return null;
663 * Close all resources used by this repository
665 public void close() {
666 closePacks();
669 void closePacks() {
670 for (int k = packs.length - 1; k >= 0; k--) {
671 packs[k].close();
673 packs = new PackFile[0];
677 * Scan the object dirs, including alternates for packs
678 * to use.
680 public void scanForPacks() {
681 final ArrayList<PackFile> p = new ArrayList<PackFile>();
682 for (int i=0; i<objectsDirs.length; ++i)
683 scanForPacks(new File(objectsDirs[i], "pack"), p);
684 final PackFile[] arr = new PackFile[p.size()];
685 p.toArray(arr);
686 packs = arr;
689 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
690 final String[] idxList = packDir.list(new FilenameFilter() {
691 public boolean accept(final File baseDir, final String n) {
692 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
693 return n.length() == 49 && n.endsWith(".idx")
694 && n.startsWith("pack-");
697 if (idxList != null) {
698 for (final String indexName : idxList) {
699 final String n = indexName.substring(0, indexName.length() - 4);
700 final File idxFile = new File(packDir, n + ".idx");
701 final File packFile = new File(packDir, n + ".pack");
702 try {
703 packList.add(new PackFile(this, idxFile, packFile));
704 } catch (IOException ioe) {
705 // Whoops. That's not a pack!
707 ioe.printStackTrace();
714 * Writes a symref (e.g. HEAD) to disk
716 * @param name symref name
717 * @param target pointed to ref
718 * @throws IOException
720 public void writeSymref(final String name, final String target)
721 throws IOException {
722 final byte[] content = ("ref: " + target + "\n").getBytes("UTF-8");
723 final LockFile lck = new LockFile(fileForRef(name));
724 if (!lck.lock())
725 throw new ObjectWritingException("Unable to lock " + name);
726 try {
727 lck.write(content);
728 } catch (IOException ioe) {
729 throw new ObjectWritingException("Unable to write " + name, ioe);
731 if (!lck.commit())
732 throw new ObjectWritingException("Unable to write " + name);
735 private Ref readRef(final String revstr, final boolean missingOk)
736 throws IOException {
737 refreshPackedRefsCache();
738 for (int k = 0; k < refSearchPaths.length; k++) {
739 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
740 if (missingOk || r.getObjectId() != null) {
741 return r;
744 return null;
747 private Ref readRefBasic(String name) throws IOException {
748 int depth = 0;
749 REF_READING: do {
750 // prefer unpacked ref to packed ref
751 final File f = fileForRef(name);
752 if (!f.isFile()) {
753 // look for packed ref, since this one doesn't exist
754 ObjectId id = packedRefs.get(name);
755 if (id != null)
756 return new Ref(name, id);
758 // no packed ref found, return blank one
759 return new Ref(name, null);
762 final BufferedReader br = new BufferedReader(new FileReader(f));
763 try {
764 final String line = br.readLine();
765 if (line == null || line.length() == 0)
766 return new Ref(name, null);
767 else if (line.startsWith("ref: ")) {
768 name = line.substring("ref: ".length());
769 continue REF_READING;
770 } else if (ObjectId.isId(line))
771 return new Ref(name, ObjectId.fromString(line));
772 throw new IOException("Not a ref: " + name + ": " + line);
773 } finally {
774 br.close();
776 } while (depth++ < 5);
777 throw new IOException("Exceed maximum ref depth. Circular reference?");
780 private File fileForRef(final String name) {
781 if (name.startsWith("refs/"))
782 return new File(refsDir, name.substring("refs/".length()));
783 return new File(gitDir, name);
786 public String toString() {
787 return "Repository[" + getDirectory() + "]";
791 * @return name of topmost Stacked Git patch.
792 * @throws IOException
794 public String getPatch() throws IOException {
795 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
796 final BufferedReader br = new BufferedReader(new FileReader(ptr));
797 String last=null;
798 try {
799 String line;
800 while ((line=br.readLine())!=null) {
801 last = line;
803 } finally {
804 br.close();
806 return last;
810 * @return name of current branch
811 * @throws IOException
813 public String getFullBranch() throws IOException {
814 final File ptr = new File(getDirectory(),"HEAD");
815 final BufferedReader br = new BufferedReader(new FileReader(ptr));
816 String ref;
817 try {
818 ref = br.readLine();
819 } finally {
820 br.close();
822 if (ref.startsWith("ref: "))
823 ref = ref.substring(5);
824 return ref;
828 * @return name of current branch.
829 * @throws IOException
831 public String getBranch() throws IOException {
832 try {
833 final File ptr = new File(getDirectory(),"HEAD");
834 final BufferedReader br = new BufferedReader(new FileReader(ptr));
835 String ref;
836 try {
837 ref = br.readLine();
838 } finally {
839 br.close();
841 if (ref.startsWith("ref: "))
842 ref = ref.substring(5);
843 if (ref.startsWith("refs/heads/"))
844 ref = ref.substring(11);
845 return ref;
846 } catch (FileNotFoundException e) {
847 final File ptr = new File(getDirectory(),"head-name");
848 final BufferedReader br = new BufferedReader(new FileReader(ptr));
849 String ref;
850 try {
851 ref = br.readLine();
852 } finally {
853 br.close();
855 return ref;
860 * @return names of all local branches
862 public Collection<String> getBranches() {
863 return listRefs("heads");
867 * @return the names of all refs (local and remotes branches, tags)
869 public Collection<String> getAllRefs() {
870 return listRefs("");
873 private Collection<String> listRefs(String refSubDir) {
874 // add / to end, unless empty
875 if (refSubDir.length() > 0 && refSubDir.charAt(refSubDir.length() -1 ) != '/')
876 refSubDir += "/";
878 Collection<String> branchesRaw = listFilesRecursively(new File(refsDir, refSubDir), null);
879 ArrayList<String> branches = new ArrayList<String>();
880 for (String b : branchesRaw) {
881 branches.add("refs/" + refSubDir + b);
884 refreshPackedRefsCache();
885 Set<String> keySet = packedRefs.keySet();
886 for (String s : keySet)
887 if (s.startsWith("refs/" + refSubDir) && !branches.contains(s))
888 branches.add(s);
889 return branches;
893 * @return all git tags
895 public Collection<String> getTags() {
896 return listRefs("tags");
899 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
900 private long packedrefstime = 0;
902 private void refreshPackedRefsCache() {
903 if (!packedRefsFile.exists()) {
904 if (packedRefs.size() > 0)
905 packedRefs = new HashMap<String,ObjectId>();
906 return;
908 if (packedRefsFile.lastModified() == packedrefstime)
909 return;
910 Map<String,ObjectId> newPackedRefs = new HashMap<String,ObjectId>();
911 FileReader fileReader = null;
912 try {
913 fileReader = new FileReader(packedRefsFile);
914 BufferedReader b=new BufferedReader(fileReader);
915 String p;
916 while ((p = b.readLine()) != null) {
917 if (p.charAt(0) == '#')
918 continue;
919 if (p.charAt(0) == '^') {
920 continue;
922 int spos = p.indexOf(' ');
923 ObjectId id = ObjectId.fromString(p.substring(0,spos));
924 String name = p.substring(spos+1);
925 newPackedRefs.put(name, id);
927 } catch (IOException e) {
928 throw new Error("Cannot read packed refs",e);
929 } finally {
930 if (fileReader != null) {
931 try {
932 fileReader.close();
933 } catch (IOException e) {
934 // Cannot do anything more here
935 e.printStackTrace();
939 packedRefs = newPackedRefs;
943 * @return true if HEAD points to a StGit patch.
945 public boolean isStGitMode() {
946 try {
947 File file = new File(getDirectory(), "HEAD");
948 BufferedReader reader = new BufferedReader(new FileReader(file));
949 String string = reader.readLine();
950 if (!string.startsWith("ref: refs/heads/"))
951 return false;
952 String branch = string.substring("ref: refs/heads/".length());
953 File currentPatches = new File(new File(new File(getDirectory(),
954 "patches"), branch), "applied");
955 if (!currentPatches.exists())
956 return false;
957 if (currentPatches.length() == 0)
958 return false;
959 return true;
961 } catch (IOException e) {
962 e.printStackTrace();
963 return false;
968 * @return applied patches in a map indexed on current commit id
969 * @throws IOException
971 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
972 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
973 if (isStGitMode()) {
974 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
975 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
976 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
977 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
978 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
979 String objectId = tfr.readLine();
980 ObjectId id = ObjectId.fromString(objectId);
981 ret.put(id, new StGitPatch(patchName, id));
982 tfr.close();
984 apr.close();
986 return ret;
989 private Collection<String> listFilesRecursively(File root, File start) {
990 if (start == null)
991 start = root;
992 Collection<String> ret = new ArrayList<String>();
993 File[] files = start.listFiles();
994 for (int i = 0; i < files.length; ++i) {
995 if (files[i].isDirectory())
996 ret.addAll(listFilesRecursively(root, files[i]));
997 else if (files[i].length() == 41) {
998 String name = files[i].toString().substring(
999 root.toString().length() + 1);
1000 if (File.separatorChar != '/')
1001 name = name.replace(File.separatorChar, '/');
1002 ret.add(name);
1005 return ret;
1008 /** Clean up stale caches */
1009 public void refreshFromDisk() {
1010 packedRefs = null;
1014 * @return a representation of the index associated with this repo
1015 * @throws IOException
1017 public GitIndex getIndex() throws IOException {
1018 if (index == null) {
1019 index = new GitIndex(this);
1020 index.read();
1021 } else {
1022 index.rereadIfNecessary();
1024 return index;
1027 static byte[] gitInternalSlash(byte[] bytes) {
1028 if (File.separatorChar == '/')
1029 return bytes;
1030 for (int i=0; i<bytes.length; ++i)
1031 if (bytes[i] == File.separatorChar)
1032 bytes[i] = '/';
1033 return bytes;
1037 * @return an important state
1039 public RepositoryState getRepositoryState() {
1040 if (new File(getWorkDir(), ".dotest").exists())
1041 return RepositoryState.REBASING;
1042 if (new File(gitDir,".dotest-merge").exists())
1043 return RepositoryState.REBASING_INTERACTIVE;
1044 if (new File(gitDir,"MERGE_HEAD").exists())
1045 return RepositoryState.MERGING;
1046 if (new File(gitDir,"BISECT_LOG").exists())
1047 return RepositoryState.BISECTING;
1048 return RepositoryState.SAFE;
1052 * Check validty of a ref name. It must not contain character that has
1053 * a special meaning in a Git object reference expression. Some other
1054 * dangerous characters are also excluded.
1056 * @param refName
1058 * @return true if refName is a valid ref name
1060 public static boolean isValidRefName(final String refName) {
1061 final int len = refName.length();
1062 char p = '\0';
1063 for (int i=0; i<len; ++i) {
1064 char c = refName.charAt(i);
1065 if (c <= ' ')
1066 return false;
1067 switch(c) {
1068 case '.':
1069 if (i == 0)
1070 return false;
1071 if (p == '/')
1072 return false;
1073 if (p == '.')
1074 return false;
1075 break;
1076 case '/':
1077 if (i == 0)
1078 return false;
1079 if (i == len -1)
1080 return false;
1081 break;
1082 case '~': case '^': case ':':
1083 case '?': case '[':
1084 return false;
1085 case '*':
1086 return false;
1088 p = c;
1090 return true;
1094 * String work dir and return normalized repository path
1096 * @param wd Work dir
1097 * @param f File whose path shall be stripp off it's workdir
1098 * @return normalized repository relative path
1100 public static String stripWorkDir(File wd, File f) {
1101 String relName = f.getPath().substring(wd.getPath().length() + 1);
1102 relName = relName.replace(File.separatorChar, '/');
1103 return relName;
1107 * @param name
1108 * The "remote" name in this repo
1109 * @return information about how a remote repository is beging tracked
1111 public RemoteSpec getRemoteSpec(String name) {
1112 String url = getConfig().getString("remote."+name, null, "url");
1113 String fetchPattern = getConfig().getString("remote."+name, null, "fetch");
1114 String pushPattern = getConfig().getString("remote."+name, null, "push");
1115 return new RemoteSpec(name, url, fetchPattern, pushPattern);
1119 * Setup repository configuration for a new remote
1121 * @param remote
1122 * remote name, e.g. "origin"
1123 * @param url
1124 * fetch url, e.g. "git://repo.or.cz/egit.git"
1125 * @param branch
1126 * local branch name, e.g. "master"
1128 public void configureDefaultBranch(final String remote, final String url, final String branch) {
1129 config.putString(RepositoryConfig.REMOTE_SECTION, remote, "url",
1130 url);
1131 config.putString(RepositoryConfig.REMOTE_SECTION, remote, "fetch",
1132 "+" + Constants.HEADS_PREFIX + "/*:" + Constants.REMOTES_PREFIX + "/" + remote + "/*");
1133 config.putString(RepositoryConfig.BRANCH_SECTION, branch, "remote",
1134 remote);
1135 config.putString(RepositoryConfig.BRANCH_SECTION, Constants.MASTER, "merge",
1136 Constants.HEADS_PREFIX + "/" + branch);
1140 * @return the workdir file, i.e. where the files are checked out
1142 public File getWorkDir() {
1143 return getDirectory().getParentFile();
1147 * Setup HEAD and "master" refs for a new repository.
1149 * @param remoteBranch
1150 * The remote branch to start with
1151 * @param branch
1152 * The local branch to configure, initially starting at
1153 * remoteBranch
1154 * @return the commit references by the new HEAD
1155 * @throws IOException
1157 public Commit setupHEADRef(final String remoteBranch, final String branch) throws IOException {
1158 Commit mapCommit = mapCommit(remoteBranch);
1159 String refName = Constants.HEADS_PREFIX + "/" + branch;
1160 LockFile masterRef = lockRef(refName);
1161 try {
1162 masterRef.write(mapCommit.getCommitId());
1163 masterRef.commit();
1164 } finally {
1165 masterRef.unlock();
1167 writeSymref(Constants.HEAD, refName);
1168 return mapCommit;