Repository.mapObject broke after merge with revised engine
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blobe2fd0f3f591ce7334b7ec4a25dcf7056efcc9e0d
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.stgit.StGitPatch;
34 import org.spearce.jgit.util.FS;
36 /**
37 * Represents a Git repository. A repository holds all objects and refs used for
38 * managing source code (could by any type of file, but source code is what
39 * SCM's are typically used for).
41 * In Git terms all data is stored in GIT_DIR, typically a directory called
42 * .git. A work tree is maintained unless the repository is a bare repository.
43 * Typically the .git directory is located at the root of the work dir.
45 * <ul>
46 * <li>GIT_DIR
47 * <ul>
48 * <li>objects/ - objects</li>
49 * <li>refs/ - tags and heads</li>
50 * <li>config - configuration</li>
51 * <li>info/ - more configurations</li>
52 * </ul>
53 * </li>
54 * </ul>
56 * This implementation only handles a subtly undocumented subset of git features.
59 public class Repository {
60 private static final String[] refSearchPaths = { "", "refs/", "refs/tags/",
61 Constants.HEADS_PREFIX + "/", "refs/" + Constants.REMOTES_PREFIX + "/" };
63 private final File gitDir;
65 private final File[] objectsDirs;
67 private final File refsDir;
69 private final File packedRefsFile;
71 private final RepositoryConfig config;
73 private PackFile[] packs;
75 private final WindowCache windows;
77 private GitIndex index;
79 /**
80 * Construct a representation of this git repo managing a Git repository.
82 * @param d
83 * GIT_DIR
84 * @throws IOException
86 public Repository(final File d) throws IOException {
87 this(null, d);
90 /**
91 * Construct a representation of a Git repository.
93 * @param wc
94 * cache this repository's data will be cached through during
95 * access. May be shared with another repository, or null to
96 * indicate this repository should allocate its own private
97 * cache.
98 * @param d
99 * GIT_DIR (the location of the repository metadata).
100 * @throws IOException
101 * the repository appears to already exist but cannot be
102 * accessed.
104 public Repository(final WindowCache wc, final File d) throws IOException {
105 gitDir = d.getAbsoluteFile();
106 try {
107 objectsDirs = readObjectsDirs(FS.resolve(gitDir, "objects"),
108 new ArrayList<File>()).toArray(new File[0]);
109 } catch (IOException e) {
110 IOException ex = new IOException("Cannot find all object dirs for " + gitDir);
111 ex.initCause(e);
112 throw ex;
114 refsDir = FS.resolve(gitDir, "refs");
115 packedRefsFile = FS.resolve(gitDir, "packed-refs");
116 packs = new PackFile[0];
117 config = new RepositoryConfig(this);
119 final boolean isExisting = objectsDirs[0].exists();
120 if (isExisting) {
121 getConfig().load();
122 final String repositoryFormatVersion = getConfig().getString(
123 "core", null, "repositoryFormatVersion");
124 if (!"0".equals(repositoryFormatVersion)) {
125 throw new IOException("Unknown repository format \""
126 + repositoryFormatVersion + "\"; expected \"0\".");
128 } else {
129 getConfig().create();
131 windows = wc != null ? wc : new WindowCache(getConfig());
132 if (isExisting)
133 scanForPacks();
136 private Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {
137 ret.add(objectsDir);
138 final File altFile = FS.resolve(objectsDir, "info/alternates");
139 if (altFile.exists()) {
140 BufferedReader ar = new BufferedReader(new FileReader(altFile));
141 for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
142 readObjectsDirs(FS.resolve(objectsDir, alt), ret);
144 ar.close();
146 return ret;
150 * Create a new Git repository initializing the necessary files and
151 * directories.
153 * @throws IOException
155 public void create() throws IOException {
156 if (gitDir.exists()) {
157 throw new IllegalStateException("Repository already exists: "
158 + gitDir);
161 gitDir.mkdirs();
163 objectsDirs[0].mkdirs();
164 new File(objectsDirs[0], "pack").mkdir();
165 new File(objectsDirs[0], "info").mkdir();
167 refsDir.mkdir();
168 new File(refsDir, "heads").mkdir();
169 new File(refsDir, "tags").mkdir();
171 new File(gitDir, "branches").mkdir();
172 new File(gitDir, "remotes").mkdir();
173 writeSymref("HEAD", "refs/heads/master");
175 getConfig().create();
176 getConfig().save();
180 * @return GIT_DIR
182 public File getDirectory() {
183 return gitDir;
187 * @return the directory containg the objects owned by this repository.
189 public File getObjectsDirectory() {
190 return objectsDirs[0];
194 * @return the configuration of this repository
196 public RepositoryConfig getConfig() {
197 return config;
201 * @return the cache needed for accessing packed objects in this repository.
203 public WindowCache getWindowCache() {
204 return windows;
208 * Construct a filename where the loose object having a specified SHA-1
209 * should be stored. If the object is stored in a shared repository the path
210 * to the alternative repo will be returned. If the object is not yet store
211 * a usable path in this repo will be returned. It is assumed that callers
212 * will look for objects in a pack first.
214 * @param objectId
215 * @return suggested file name
217 public File toFile(final AnyObjectId objectId) {
218 final String n = objectId.toString();
219 String d=n.substring(0, 2);
220 String f=n.substring(2);
221 for (int i=0; i<objectsDirs.length; ++i) {
222 File ret = new File(new File(objectsDirs[i], d), f);
223 if (ret.exists())
224 return ret;
226 return new File(new File(objectsDirs[0], d), f);
230 * @param objectId
231 * @return true if the specified object is stored in this repo or any of the
232 * known shared repositories.
234 public boolean hasObject(final AnyObjectId objectId) {
235 int k = packs.length;
236 if (k > 0) {
237 do {
238 if (packs[--k].hasObject(objectId))
239 return true;
240 } while (k > 0);
242 return toFile(objectId).isFile();
246 * @param id
247 * SHA-1 of an object.
249 * @return a {@link ObjectLoader} for accessing the data of the named
250 * object, or null if the object does not exist.
251 * @throws IOException
253 public ObjectLoader openObject(final AnyObjectId id)
254 throws IOException {
255 return openObject(new WindowCursor(),id);
259 * @param curs
260 * temporary working space associated with the calling thread.
261 * @param id
262 * SHA-1 of an object.
264 * @return a {@link ObjectLoader} for accessing the data of the named
265 * object, or null if the object does not exist.
266 * @throws IOException
268 public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
269 throws IOException {
270 int k = packs.length;
271 if (k > 0) {
272 do {
273 try {
274 final ObjectLoader ol = packs[--k].get(curs, id);
275 if (ol != null)
276 return ol;
277 } catch (IOException ioe) {
278 // This shouldn't happen unless the pack was corrupted
279 // after we opened it or the VM runs out of memory. This is
280 // a know problem with memory mapped I/O in java and have
281 // been noticed with JDK < 1.6. Tell the gc that now is a good
282 // time to collect and try once more.
283 try {
284 curs.release();
285 System.gc();
286 final ObjectLoader ol = packs[k].get(curs, id);
287 if (ol != null)
288 return ol;
289 } catch (IOException ioe2) {
290 ioe2.printStackTrace();
291 ioe.printStackTrace();
292 // Still fails.. that's BAD, maybe the pack has
293 // been corrupted after all, or the gc didn't manage
294 // to release enough previously mmaped areas.
297 } while (k > 0);
299 try {
300 return new UnpackedObjectLoader(this, id.toObjectId());
301 } catch (FileNotFoundException fnfe) {
302 return null;
307 * @param id
308 * SHA'1 of a blob
309 * @return an {@link ObjectLoader} for accessing the data of a named blob
310 * @throws IOException
312 public ObjectLoader openBlob(final ObjectId id) throws IOException {
313 return openObject(id);
317 * @param id
318 * SHA'1 of a tree
319 * @return an {@link ObjectLoader} for accessing the data of a named tree
320 * @throws IOException
322 public ObjectLoader openTree(final ObjectId id) throws IOException {
323 return openObject(id);
327 * Access a Commit object using a symbolic reference. This reference may
328 * be a SHA-1 or ref in combination with a number of symbols translating
329 * from one ref or SHA1-1 to another, such as HEAD^ etc.
331 * @param revstr a reference to a git commit object
332 * @return a Commit named by the specified string
333 * @throws IOException for I/O error or unexpected object type.
335 * @see #resolve(String)
337 public Commit mapCommit(final String revstr) throws IOException {
338 final ObjectId id = resolve(revstr);
339 return id != null ? mapCommit(id) : null;
343 * Access any type of Git object by id and
345 * @param id
346 * SHA-1 of object to read
347 * @param refName optional, only relevant for simple tags
348 * @return The Git object if found or null
349 * @throws IOException
351 public Object mapObject(final ObjectId id, final String refName) throws IOException {
352 final ObjectLoader or = openObject(id);
353 final byte[] raw = or.getBytes();
354 if (or.getType() == Constants.OBJ_TREE)
355 return makeTree(id, raw);
356 if (or.getType() == Constants.OBJ_COMMIT)
357 return makeCommit(id, raw);
358 if (or.getType() == Constants.OBJ_TAG)
359 return makeTag(id, refName, raw);
360 if (or.getType() == Constants.OBJ_BLOB)
361 return raw;
362 return null;
366 * Access a Commit by SHA'1 id.
367 * @param id
368 * @return Commit or null
369 * @throws IOException for I/O error or unexpected object type.
371 public Commit mapCommit(final ObjectId id) throws IOException {
372 final ObjectLoader or = openObject(id);
373 if (or == null)
374 return null;
375 final byte[] raw = or.getBytes();
376 if (Constants.OBJ_COMMIT == or.getType())
377 return new Commit(this, id, raw);
378 throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
381 private Commit makeCommit(final ObjectId id, final byte[] raw) {
382 Commit ret = new Commit(this, id, raw);
383 return ret;
387 * Access a Tree object using a symbolic reference. This reference may
388 * be a SHA-1 or ref in combination with a number of symbols translating
389 * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
391 * @param revstr a reference to a git commit object
392 * @return a Tree named by the specified string
393 * @throws IOException
395 * @see #resolve(String)
397 public Tree mapTree(final String revstr) throws IOException {
398 final ObjectId id = resolve(revstr);
399 return id != null ? mapTree(id) : null;
403 * Access a Tree by SHA'1 id.
404 * @param id
405 * @return Tree or null
406 * @throws IOException for I/O error or unexpected object type.
408 public Tree mapTree(final ObjectId id) throws IOException {
409 final ObjectLoader or = openObject(id);
410 if (or == null)
411 return null;
412 final byte[] raw = or.getBytes();
413 if (Constants.OBJ_TREE == or.getType()) {
414 return new Tree(this, id, raw);
416 if (Constants.OBJ_COMMIT == or.getType())
417 return mapTree(ObjectId.fromString(raw, 5));
418 throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
421 private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
422 Tree ret = new Tree(this, id, raw);
423 return ret;
426 private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
427 Tag ret = new Tag(this, id, refName, raw);
428 return ret;
432 * Access a tag by symbolic name.
434 * @param revstr
435 * @return a Tag or null
436 * @throws IOException on I/O error or unexpected type
438 public Tag mapTag(String revstr) throws IOException {
439 final ObjectId id = resolve(revstr);
440 return id != null ? mapTag(revstr, id) : null;
444 * Access a Tag by SHA'1 id
445 * @param refName
446 * @param id
447 * @return Commit or null
448 * @throws IOException for I/O error or unexpected object type.
450 public Tag mapTag(final String refName, final ObjectId id) throws IOException {
451 final ObjectLoader or = openObject(id);
452 if (or == null)
453 return null;
454 final byte[] raw = or.getBytes();
455 if (Constants.OBJ_TAG == or.getType())
456 return new Tag(this, id, refName, raw);
457 return new Tag(this, id, refName, null);
461 * Get a locked handle to a ref suitable for updating or creating.
463 * @param ref name to lock
464 * @return a locked ref
465 * @throws IOException
467 public LockFile lockRef(final String ref) throws IOException {
468 final Ref r = readRef(ref, true);
469 final LockFile l = new LockFile(fileForRef(r.getName()));
470 return l.lock() ? l : null;
474 * Parse a git revision string and return an object id.
476 * Currently supported is combinations of these.
477 * <ul>
478 * <li>SHA-1 - a SHA-1</li>
479 * <li>refs/... - a ref name</li>
480 * <li>ref^n - nth parent reference</li>
481 * <li>ref~n - distance via parent reference</li>
482 * <li>ref@{n} - nth version of ref</li>
483 * <li>ref^{tree} - tree references by ref</li>
484 * <li>ref^{commit} - commit references by ref</li>
485 * </ul>
487 * Not supported is
488 * <ul>
489 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
490 * <li>abbreviated SHA-1's</li>
491 * </ul>
493 * @param revstr A git object references expression
494 * @return an ObjectId
495 * @throws IOException on serious errors
497 public ObjectId resolve(final String revstr) throws IOException {
498 char[] rev = revstr.toCharArray();
499 ObjectId ret = null;
500 Commit ref = null;
501 for (int i = 0; i < rev.length; ++i) {
502 switch (rev[i]) {
503 case '^':
504 if (ref == null) {
505 String refstr = new String(rev,0,i);
506 ObjectId refId = resolveSimple(refstr);
507 if (refId == null)
508 return null;
509 ref = mapCommit(refId);
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 for (j=i+1; j<rev.length; ++j) {
525 if (!Character.isDigit(rev[j]))
526 break;
528 String parentnum = new String(rev, i+1, j-i-1);
529 int pnum = Integer.parseInt(parentnum);
530 if (pnum != 0)
531 ref = mapCommit(ref.getParentIds()[pnum - 1]);
532 i = j - 1;
533 break;
534 case '{':
535 int k;
536 String item = null;
537 for (k=i+2; k<rev.length; ++k) {
538 if (rev[k] == '}') {
539 item = new String(rev, i+2, k-i-2);
540 break;
543 i = k;
544 if (item != null)
545 if (item.equals("tree"))
546 ret = ref.getTreeId();
547 else if (item.equals("commit"))
548 ; // just reference self
549 else
550 return null; // invalid
551 else
552 return null; // invalid
553 break;
554 default:
555 ref = mapCommit(ref.getParentIds()[0]);
557 } else {
558 ref = mapCommit(ref.getParentIds()[0]);
560 break;
561 case '~':
562 if (ref == null) {
563 String refstr = new String(rev,0,i);
564 ObjectId refId = resolveSimple(refstr);
565 ref = mapCommit(refId);
567 int l;
568 for (l = i + 1; l < rev.length; ++l) {
569 if (!Character.isDigit(rev[l]))
570 break;
572 String distnum = new String(rev, i+1, l-i-1);
573 int dist = Integer.parseInt(distnum);
574 while (dist >= 0) {
575 ref = mapCommit(ref.getParentIds()[0]);
576 --dist;
578 i = l - 1;
579 break;
580 case '@':
581 int m;
582 String time = null;
583 for (m=i+2; m<rev.length; ++m) {
584 if (rev[m] == '}') {
585 time = new String(rev, i+2, m-i-2);
586 break;
589 if (time != null)
590 throw new IllegalArgumentException("reflogs not yet supprted");
591 i = m - 1;
592 break;
593 default:
594 if (ref != null)
595 return null; // cannot parse, return null
598 if (ret == null)
599 if (ref != null)
600 ret = ref.getCommitId();
601 else
602 ret = resolveSimple(revstr);
603 return ret;
606 private ObjectId resolveSimple(final String revstr) throws IOException {
607 if (ObjectId.isId(revstr))
608 return ObjectId.fromString(revstr);
609 final Ref r = readRef(revstr, false);
610 if (r != null) {
611 return r.getObjectId();
613 return null;
617 * Close all resources used by this repository
619 public void close() {
620 closePacks();
623 void closePacks() {
624 for (int k = packs.length - 1; k >= 0; k--) {
625 packs[k].close();
627 packs = new PackFile[0];
631 * Scan the object dirs, including alternates for packs
632 * to use.
634 public void scanForPacks() {
635 final ArrayList<PackFile> p = new ArrayList<PackFile>();
636 for (int i=0; i<objectsDirs.length; ++i)
637 scanForPacks(new File(objectsDirs[i], "pack"), p);
638 final PackFile[] arr = new PackFile[p.size()];
639 p.toArray(arr);
640 packs = arr;
643 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
644 final String[] idxList = packDir.list(new FilenameFilter() {
645 public boolean accept(final File baseDir, final String n) {
646 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
647 return n.length() == 49 && n.endsWith(".idx")
648 && n.startsWith("pack-");
651 if (idxList != null) {
652 for (final String indexName : idxList) {
653 final String n = indexName.substring(0, indexName.length() - 4);
654 final File idxFile = new File(packDir, n + ".idx");
655 final File packFile = new File(packDir, n + ".pack");
656 try {
657 packList.add(new PackFile(this, idxFile, packFile));
658 } catch (IOException ioe) {
659 // Whoops. That's not a pack!
661 ioe.printStackTrace();
668 * Writes a symref (e.g. HEAD) to disk
670 * @param name symref name
671 * @param target pointed to ref
672 * @throws IOException
674 public void writeSymref(final String name, final String target)
675 throws IOException {
676 final byte[] content = ("ref: " + target + "\n").getBytes("UTF-8");
677 final LockFile lck = new LockFile(fileForRef(name));
678 if (!lck.lock())
679 throw new ObjectWritingException("Unable to lock " + name);
680 try {
681 lck.write(content);
682 } catch (IOException ioe) {
683 throw new ObjectWritingException("Unable to write " + name, ioe);
685 if (!lck.commit())
686 throw new ObjectWritingException("Unable to write " + name);
689 private Ref readRef(final String revstr, final boolean missingOk)
690 throws IOException {
691 refreshPackedRefsCache();
692 for (int k = 0; k < refSearchPaths.length; k++) {
693 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
694 if (missingOk || r.getObjectId() != null) {
695 return r;
698 return null;
701 private Ref readRefBasic(String name) throws IOException {
702 int depth = 0;
703 REF_READING: do {
704 // prefer unpacked ref to packed ref
705 final File f = fileForRef(name);
706 if (!f.isFile()) {
707 // look for packed ref, since this one doesn't exist
708 ObjectId id = packedRefs.get(name);
709 if (id != null)
710 return new Ref(name, id);
712 // no packed ref found, return blank one
713 return new Ref(name, null);
716 final BufferedReader br = new BufferedReader(new FileReader(f));
717 try {
718 final String line = br.readLine();
719 if (line == null || line.length() == 0)
720 return new Ref(name, null);
721 else if (line.startsWith("ref: ")) {
722 name = line.substring("ref: ".length());
723 continue REF_READING;
724 } else if (ObjectId.isId(line))
725 return new Ref(name, ObjectId.fromString(line));
726 throw new IOException("Not a ref: " + name + ": " + line);
727 } finally {
728 br.close();
730 } while (depth++ < 5);
731 throw new IOException("Exceed maximum ref depth. Circular reference?");
734 private File fileForRef(final String name) {
735 if (name.startsWith("refs/"))
736 return new File(refsDir, name.substring("refs/".length()));
737 return new File(gitDir, name);
740 public String toString() {
741 return "Repository[" + getDirectory() + "]";
745 * @return name of topmost Stacked Git patch.
746 * @throws IOException
748 public String getPatch() throws IOException {
749 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
750 final BufferedReader br = new BufferedReader(new FileReader(ptr));
751 String last=null;
752 try {
753 String line;
754 while ((line=br.readLine())!=null) {
755 last = line;
757 } finally {
758 br.close();
760 return last;
764 * @return name of current branch
765 * @throws IOException
767 public String getFullBranch() throws IOException {
768 final File ptr = new File(getDirectory(),"HEAD");
769 final BufferedReader br = new BufferedReader(new FileReader(ptr));
770 String ref;
771 try {
772 ref = br.readLine();
773 } finally {
774 br.close();
776 if (ref.startsWith("ref: "))
777 ref = ref.substring(5);
778 return ref;
782 * @return name of current branch.
783 * @throws IOException
785 public String getBranch() throws IOException {
786 try {
787 final File ptr = new File(getDirectory(),"HEAD");
788 final BufferedReader br = new BufferedReader(new FileReader(ptr));
789 String ref;
790 try {
791 ref = br.readLine();
792 } finally {
793 br.close();
795 if (ref.startsWith("ref: "))
796 ref = ref.substring(5);
797 if (ref.startsWith("refs/heads/"))
798 ref = ref.substring(11);
799 return ref;
800 } catch (FileNotFoundException e) {
801 final File ptr = new File(getDirectory(),"head-name");
802 final BufferedReader br = new BufferedReader(new FileReader(ptr));
803 String ref;
804 try {
805 ref = br.readLine();
806 } finally {
807 br.close();
809 return ref;
814 * @return names of all local branches
816 public Collection<String> getBranches() {
817 return listRefs("heads");
821 * @return the names of all refs (local and remotes branches, tags)
823 public Collection<String> getAllRefs() {
824 return listRefs("");
827 private Collection<String> listRefs(String refSubDir) {
828 // add / to end, unless empty
829 if (refSubDir.length() > 0 && refSubDir.charAt(refSubDir.length() -1 ) != '/')
830 refSubDir += "/";
832 Collection<String> branchesRaw = listFilesRecursively(new File(refsDir, refSubDir), null);
833 ArrayList<String> branches = new ArrayList<String>();
834 for (String b : branchesRaw) {
835 branches.add("refs/" + refSubDir + b);
838 refreshPackedRefsCache();
839 Set<String> keySet = packedRefs.keySet();
840 for (String s : keySet)
841 if (s.startsWith("refs/" + refSubDir) && !branches.contains(s))
842 branches.add(s);
843 return branches;
847 * @return all git tags
849 public Collection<String> getTags() {
850 return listRefs("tags");
853 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
854 private long packedrefstime = 0;
856 private void refreshPackedRefsCache() {
857 if (!packedRefsFile.exists()) {
858 if (packedRefs.size() > 0)
859 packedRefs = new HashMap<String,ObjectId>();
860 return;
862 if (packedRefsFile.lastModified() == packedrefstime)
863 return;
864 Map<String,ObjectId> newPackedRefs = new HashMap<String,ObjectId>();
865 FileReader fileReader = null;
866 try {
867 fileReader = new FileReader(packedRefsFile);
868 BufferedReader b=new BufferedReader(fileReader);
869 String p;
870 while ((p = b.readLine()) != null) {
871 if (p.charAt(0) == '#')
872 continue;
873 if (p.charAt(0) == '^') {
874 continue;
876 int spos = p.indexOf(' ');
877 ObjectId id = ObjectId.fromString(p.substring(0,spos));
878 String name = p.substring(spos+1);
879 newPackedRefs.put(name, id);
881 } catch (IOException e) {
882 throw new Error("Cannot read packed refs",e);
883 } finally {
884 if (fileReader != null) {
885 try {
886 fileReader.close();
887 } catch (IOException e) {
888 // Cannot do anything more here
889 e.printStackTrace();
893 packedRefs = newPackedRefs;
897 * @return true if HEAD points to a StGit patch.
899 public boolean isStGitMode() {
900 try {
901 File file = new File(getDirectory(), "HEAD");
902 BufferedReader reader = new BufferedReader(new FileReader(file));
903 String string = reader.readLine();
904 if (!string.startsWith("ref: refs/heads/"))
905 return false;
906 String branch = string.substring("ref: refs/heads/".length());
907 File currentPatches = new File(new File(new File(getDirectory(),
908 "patches"), branch), "applied");
909 if (!currentPatches.exists())
910 return false;
911 if (currentPatches.length() == 0)
912 return false;
913 return true;
915 } catch (IOException e) {
916 e.printStackTrace();
917 return false;
922 * @return applied patches in a map indexed on current commit id
923 * @throws IOException
925 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
926 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
927 if (isStGitMode()) {
928 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
929 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
930 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
931 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
932 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
933 String objectId = tfr.readLine();
934 ObjectId id = ObjectId.fromString(objectId);
935 ret.put(id, new StGitPatch(patchName, id));
936 tfr.close();
938 apr.close();
940 return ret;
943 private Collection<String> listFilesRecursively(File root, File start) {
944 if (start == null)
945 start = root;
946 Collection<String> ret = new ArrayList<String>();
947 File[] files = start.listFiles();
948 for (int i = 0; i < files.length; ++i) {
949 if (files[i].isDirectory())
950 ret.addAll(listFilesRecursively(root, files[i]));
951 else if (files[i].length() == 41) {
952 String name = files[i].toString().substring(
953 root.toString().length() + 1);
954 if (File.separatorChar != '/')
955 name = name.replace(File.separatorChar, '/');
956 ret.add(name);
959 return ret;
962 /** Clean up stale caches */
963 public void refreshFromDisk() {
964 packedRefs = null;
968 * @return a representation of the index associated with this repo
969 * @throws IOException
971 public GitIndex getIndex() throws IOException {
972 if (index == null) {
973 index = new GitIndex(this);
974 index.read();
975 } else {
976 index.rereadIfNecessary();
978 return index;
981 static byte[] gitInternalSlash(byte[] bytes) {
982 if (File.separatorChar == '/')
983 return bytes;
984 for (int i=0; i<bytes.length; ++i)
985 if (bytes[i] == File.separatorChar)
986 bytes[i] = '/';
987 return bytes;
991 * @return an important state
993 public RepositoryState getRepositoryState() {
994 if (new File(getWorkDir(), ".dotest").exists())
995 return RepositoryState.REBASING;
996 if (new File(gitDir,".dotest-merge").exists())
997 return RepositoryState.REBASING_INTERACTIVE;
998 if (new File(gitDir,"MERGE_HEAD").exists())
999 return RepositoryState.MERGING;
1000 if (new File(gitDir,"BISECT_LOG").exists())
1001 return RepositoryState.BISECTING;
1002 return RepositoryState.SAFE;
1006 * Check validty of a ref name. It must not contain character that has
1007 * a special meaning in a Git object reference expression. Some other
1008 * dangerous characters are also excluded.
1010 * @param refName
1012 * @return true if refName is a valid ref name
1014 public static boolean isValidRefName(final String refName) {
1015 final int len = refName.length();
1016 char p = '\0';
1017 for (int i=0; i<len; ++i) {
1018 char c = refName.charAt(i);
1019 if (c <= ' ')
1020 return false;
1021 switch(c) {
1022 case '.':
1023 if (i == 0)
1024 return false;
1025 if (p == '/')
1026 return false;
1027 if (p == '.')
1028 return false;
1029 break;
1030 case '/':
1031 if (i == 0)
1032 return false;
1033 if (i == len -1)
1034 return false;
1035 break;
1036 case '~': case '^': case ':':
1037 case '?': case '[':
1038 return false;
1039 case '*':
1040 return false;
1042 p = c;
1044 return true;
1048 * String work dir and return normalized repository path
1050 * @param wd Work dir
1051 * @param f File whose path shall be stripp off it's workdir
1052 * @return normalized repository relative path
1054 public static String stripWorkDir(File wd, File f) {
1055 String relName = f.getPath().substring(wd.getPath().length() + 1);
1056 relName = relName.replace(File.separatorChar, '/');
1057 return relName;
1061 * @param name
1062 * The "remote" name in this repo
1063 * @return information about how a remote repository is beging tracked
1065 public RemoteSpec getRemoteSpec(String name) {
1066 String url = getConfig().getString("remote."+name, null, "url");
1067 String fetchPattern = getConfig().getString("remote."+name, null, "fetch");
1068 String pushPattern = getConfig().getString("remote."+name, null, "push");
1069 return new RemoteSpec(name, url, fetchPattern, pushPattern);
1073 * Setup repository configuration for a new remote
1075 * @param remote
1076 * remote name, e.g. "origin"
1077 * @param url
1078 * fetch url, e.g. "git://repo.or.cz/egit.git"
1079 * @param branch
1080 * local branch name, e.g. "master"
1082 public void configureDefaultBranch(final String remote, final String url, final String branch) {
1083 config.putString(RepositoryConfig.REMOTE_SECTION, remote, "url",
1084 url);
1085 config.putString(RepositoryConfig.REMOTE_SECTION, remote, "fetch",
1086 "+" + Constants.HEADS_PREFIX + "/*:" + Constants.REMOTES_PREFIX + "/" + remote + "/*");
1087 config.putString(RepositoryConfig.BRANCH_SECTION, branch, "remote",
1088 remote);
1089 config.putString(RepositoryConfig.BRANCH_SECTION, Constants.MASTER, "merge",
1090 Constants.HEADS_PREFIX + "/" + branch);
1094 * @return the workdir file, i.e. where the files are checked out
1096 public File getWorkDir() {
1097 return getDirectory().getParentFile();
1101 * Setup HEAD and "master" refs for a new repository.
1103 * @param remoteBranch
1104 * The remote branch to start with
1105 * @param branch
1106 * The local branch to configure, initially starting at
1107 * remoteBranch
1108 * @return the commit references by the new HEAD
1109 * @throws IOException
1111 public Commit setupHEADRef(final String remoteBranch, final String branch) throws IOException {
1112 Commit mapCommit = mapCommit(remoteBranch);
1113 String refName = Constants.HEADS_PREFIX + "/" + branch;
1114 LockFile masterRef = lockRef(refName);
1115 try {
1116 masterRef.write(mapCommit.getCommitId());
1117 masterRef.commit();
1118 } finally {
1119 masterRef.unlock();
1121 writeSymref(Constants.HEAD, refName);
1122 return mapCommit;