Define a more powerful RefUpdate utlity
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Repository.java
blob1db75b6907c835f5fc13b9d82ecf214eadb2f685
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 * Create a command to update (or create) a ref in this repository.
477 * @param ref
478 * name of the ref the caller wants to modify.
479 * @return an update command. The caller must finish populating this command
480 * and then invoke one of the update methods to actually make a
481 * change.
482 * @throws IOException
483 * a symbolic ref was passed in and could not be resolved back
484 * to the base ref, as the symbolic ref could not be read.
486 public RefUpdate updateRef(final String ref) throws IOException {
487 final Ref r = readRef(ref, true);
488 return new RefUpdate(this, r, fileForRef(r.getName()));
492 * Parse a git revision string and return an object id.
494 * Currently supported is combinations of these.
495 * <ul>
496 * <li>SHA-1 - a SHA-1</li>
497 * <li>refs/... - a ref name</li>
498 * <li>ref^n - nth parent reference</li>
499 * <li>ref~n - distance via parent reference</li>
500 * <li>ref@{n} - nth version of ref</li>
501 * <li>ref^{tree} - tree references by ref</li>
502 * <li>ref^{commit} - commit references by ref</li>
503 * </ul>
505 * Not supported is
506 * <ul>
507 * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
508 * <li>abbreviated SHA-1's</li>
509 * </ul>
511 * @param revstr A git object references expression
512 * @return an ObjectId
513 * @throws IOException on serious errors
515 public ObjectId resolve(final String revstr) throws IOException {
516 char[] rev = revstr.toCharArray();
517 Object ref = null;
518 ObjectId refId = null;
519 for (int i = 0; i < rev.length; ++i) {
520 switch (rev[i]) {
521 case '^':
522 if (refId == null) {
523 String refstr = new String(rev,0,i);
524 refId = resolveSimple(refstr);
525 if (refId == null)
526 return null;
528 if (i + 1 < rev.length) {
529 switch (rev[i + 1]) {
530 case '0':
531 case '1':
532 case '2':
533 case '3':
534 case '4':
535 case '5':
536 case '6':
537 case '7':
538 case '8':
539 case '9':
540 int j;
541 ref = mapObject(refId, null);
542 if (!(ref instanceof Commit))
543 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
544 for (j=i+1; j<rev.length; ++j) {
545 if (!Character.isDigit(rev[j]))
546 break;
548 String parentnum = new String(rev, i+1, j-i-1);
549 int pnum = Integer.parseInt(parentnum);
550 if (pnum != 0)
551 refId = ((Commit)ref).getParentIds()[pnum - 1];
552 i = j - 1;
553 break;
554 case '{':
555 int k;
556 String item = null;
557 for (k=i+2; k<rev.length; ++k) {
558 if (rev[k] == '}') {
559 item = new String(rev, i+2, k-i-2);
560 break;
563 i = k;
564 if (item != null)
565 if (item.equals("tree")) {
566 ref = mapObject(refId, null);
567 while (ref instanceof Tag) {
568 Tag t = (Tag)ref;
569 refId = t.getObjId();
570 ref = mapObject(refId, null);
572 if (ref instanceof Treeish)
573 refId = ((Treeish)ref).getTreeId();
574 else
575 throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
577 else if (item.equals("commit")) {
578 ref = mapObject(refId, null);
579 while (ref instanceof Tag) {
580 Tag t = (Tag)ref;
581 refId = t.getObjId();
582 ref = mapObject(refId, null);
584 if (!(ref instanceof Commit))
585 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
587 else if (item.equals("blob")) {
588 ref = mapObject(refId, null);
589 while (ref instanceof Tag) {
590 Tag t = (Tag)ref;
591 refId = t.getObjId();
592 ref = mapObject(refId, null);
594 if (!(ref instanceof byte[]))
595 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
597 else if (item.equals("")) {
598 ref = mapObject(refId, null);
599 if (ref instanceof Tag)
600 refId = ((Tag)ref).getObjId();
601 else {
602 // self
605 else
606 throw new RevisionSyntaxException(revstr);
607 else
608 throw new RevisionSyntaxException(revstr);
609 break;
610 default:
611 ref = mapObject(refId, null);
612 if (ref instanceof Commit)
613 refId = ((Commit)ref).getParentIds()[0];
614 else
615 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
618 } else {
619 ref = mapObject(refId, null);
620 if (ref instanceof Commit)
621 refId = ((Commit)ref).getParentIds()[0];
622 else
623 throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
625 break;
626 case '~':
627 if (ref == null) {
628 String refstr = new String(rev,0,i);
629 refId = resolveSimple(refstr);
630 ref = mapCommit(refId);
632 int l;
633 for (l = i + 1; l < rev.length; ++l) {
634 if (!Character.isDigit(rev[l]))
635 break;
637 String distnum = new String(rev, i+1, l-i-1);
638 int dist = Integer.parseInt(distnum);
639 while (dist >= 0) {
640 refId = ((Commit)ref).getParentIds()[0];
641 ref = mapCommit(refId);
642 --dist;
644 i = l - 1;
645 break;
646 case '@':
647 int m;
648 String time = null;
649 for (m=i+2; m<rev.length; ++m) {
650 if (rev[m] == '}') {
651 time = new String(rev, i+2, m-i-2);
652 break;
655 if (time != null)
656 throw new RevisionSyntaxException("reflogs not yet supported by revision parser yet", revstr);
657 i = m - 1;
658 break;
659 default:
660 if (refId != null)
661 throw new RevisionSyntaxException(revstr);
664 if (refId == null)
665 refId = resolveSimple(revstr);
666 return refId;
669 private ObjectId resolveSimple(final String revstr) throws IOException {
670 if (ObjectId.isId(revstr))
671 return ObjectId.fromString(revstr);
672 final Ref r = readRef(revstr, false);
673 if (r != null) {
674 return r.getObjectId();
676 return null;
680 * Close all resources used by this repository
682 public void close() {
683 closePacks();
686 void closePacks() {
687 for (int k = packs.length - 1; k >= 0; k--) {
688 packs[k].close();
690 packs = new PackFile[0];
694 * Scan the object dirs, including alternates for packs
695 * to use.
697 public void scanForPacks() {
698 final ArrayList<PackFile> p = new ArrayList<PackFile>();
699 for (int i=0; i<objectsDirs.length; ++i)
700 scanForPacks(new File(objectsDirs[i], "pack"), p);
701 final PackFile[] arr = new PackFile[p.size()];
702 p.toArray(arr);
703 packs = arr;
706 private void scanForPacks(final File packDir, Collection<PackFile> packList) {
707 final String[] idxList = packDir.list(new FilenameFilter() {
708 public boolean accept(final File baseDir, final String n) {
709 // Must match "pack-[0-9a-f]{40}.idx" to be an index.
710 return n.length() == 49 && n.endsWith(".idx")
711 && n.startsWith("pack-");
714 if (idxList != null) {
715 for (final String indexName : idxList) {
716 final String n = indexName.substring(0, indexName.length() - 4);
717 final File idxFile = new File(packDir, n + ".idx");
718 final File packFile = new File(packDir, n + ".pack");
719 try {
720 packList.add(new PackFile(this, idxFile, packFile));
721 } catch (IOException ioe) {
722 // Whoops. That's not a pack!
724 ioe.printStackTrace();
731 * Writes a symref (e.g. HEAD) to disk
733 * @param name symref name
734 * @param target pointed to ref
735 * @throws IOException
737 public void writeSymref(final String name, final String target)
738 throws IOException {
739 final byte[] content = ("ref: " + target + "\n").getBytes("UTF-8");
740 final LockFile lck = new LockFile(fileForRef(name));
741 if (!lck.lock())
742 throw new ObjectWritingException("Unable to lock " + name);
743 try {
744 lck.write(content);
745 } catch (IOException ioe) {
746 throw new ObjectWritingException("Unable to write " + name, ioe);
748 if (!lck.commit())
749 throw new ObjectWritingException("Unable to write " + name);
752 private Ref readRef(final String revstr, final boolean missingOk)
753 throws IOException {
754 refreshPackedRefsCache();
755 for (int k = 0; k < refSearchPaths.length; k++) {
756 final Ref r = readRefBasic(refSearchPaths[k] + revstr);
757 if (missingOk || r.getObjectId() != null) {
758 return r;
761 return null;
764 private Ref readRefBasic(String name) throws IOException {
765 int depth = 0;
766 REF_READING: do {
767 // prefer unpacked ref to packed ref
768 final File f = fileForRef(name);
769 if (!f.isFile()) {
770 // look for packed ref, since this one doesn't exist
771 ObjectId id = packedRefs.get(name);
772 if (id != null)
773 return new Ref(name, id);
775 // no packed ref found, return blank one
776 return new Ref(name, null);
779 final BufferedReader br = new BufferedReader(new FileReader(f));
780 try {
781 final String line = br.readLine();
782 if (line == null || line.length() == 0)
783 return new Ref(name, null);
784 else if (line.startsWith("ref: ")) {
785 name = line.substring("ref: ".length());
786 continue REF_READING;
787 } else if (ObjectId.isId(line))
788 return new Ref(name, ObjectId.fromString(line));
789 throw new IOException("Not a ref: " + name + ": " + line);
790 } finally {
791 br.close();
793 } while (depth++ < 5);
794 throw new IOException("Exceed maximum ref depth. Circular reference?");
797 private File fileForRef(final String name) {
798 if (name.startsWith("refs/"))
799 return new File(refsDir, name.substring("refs/".length()));
800 return new File(gitDir, name);
803 public String toString() {
804 return "Repository[" + getDirectory() + "]";
808 * @return name of topmost Stacked Git patch.
809 * @throws IOException
811 public String getPatch() throws IOException {
812 final File ptr = new File(getDirectory(),"patches/"+getBranch()+"/applied");
813 final BufferedReader br = new BufferedReader(new FileReader(ptr));
814 String last=null;
815 try {
816 String line;
817 while ((line=br.readLine())!=null) {
818 last = line;
820 } finally {
821 br.close();
823 return last;
827 * @return name of current branch
828 * @throws IOException
830 public String getFullBranch() throws IOException {
831 final File ptr = new File(getDirectory(),"HEAD");
832 final BufferedReader br = new BufferedReader(new FileReader(ptr));
833 String ref;
834 try {
835 ref = br.readLine();
836 } finally {
837 br.close();
839 if (ref.startsWith("ref: "))
840 ref = ref.substring(5);
841 return ref;
845 * @return name of current branch.
846 * @throws IOException
848 public String getBranch() throws IOException {
849 try {
850 final File ptr = new File(getDirectory(),"HEAD");
851 final BufferedReader br = new BufferedReader(new FileReader(ptr));
852 String ref;
853 try {
854 ref = br.readLine();
855 } finally {
856 br.close();
858 if (ref.startsWith("ref: "))
859 ref = ref.substring(5);
860 if (ref.startsWith("refs/heads/"))
861 ref = ref.substring(11);
862 return ref;
863 } catch (FileNotFoundException e) {
864 final File ptr = new File(getDirectory(),"head-name");
865 final BufferedReader br = new BufferedReader(new FileReader(ptr));
866 String ref;
867 try {
868 ref = br.readLine();
869 } finally {
870 br.close();
872 return ref;
877 * @return names of all local branches
879 public Collection<String> getBranches() {
880 return listRefs("heads");
884 * @return the names of all refs (local and remotes branches, tags)
886 public Collection<String> getAllRefs() {
887 return listRefs("");
890 private Collection<String> listRefs(String refSubDir) {
891 // add / to end, unless empty
892 if (refSubDir.length() > 0 && refSubDir.charAt(refSubDir.length() -1 ) != '/')
893 refSubDir += "/";
895 Collection<String> branchesRaw = listFilesRecursively(new File(refsDir, refSubDir), null);
896 ArrayList<String> branches = new ArrayList<String>();
897 for (String b : branchesRaw) {
898 branches.add("refs/" + refSubDir + b);
901 refreshPackedRefsCache();
902 Set<String> keySet = packedRefs.keySet();
903 for (String s : keySet)
904 if (s.startsWith("refs/" + refSubDir) && !branches.contains(s))
905 branches.add(s);
906 return branches;
910 * @return all git tags
912 public Collection<String> getTags() {
913 return listRefs("tags");
916 private Map<String,ObjectId> packedRefs = new HashMap<String,ObjectId>();
917 private long packedrefstime = 0;
919 private void refreshPackedRefsCache() {
920 if (!packedRefsFile.exists()) {
921 if (packedRefs.size() > 0)
922 packedRefs = new HashMap<String,ObjectId>();
923 return;
925 if (packedRefsFile.lastModified() == packedrefstime)
926 return;
927 Map<String,ObjectId> newPackedRefs = new HashMap<String,ObjectId>();
928 FileReader fileReader = null;
929 try {
930 fileReader = new FileReader(packedRefsFile);
931 BufferedReader b=new BufferedReader(fileReader);
932 String p;
933 while ((p = b.readLine()) != null) {
934 if (p.charAt(0) == '#')
935 continue;
936 if (p.charAt(0) == '^') {
937 continue;
939 int spos = p.indexOf(' ');
940 ObjectId id = ObjectId.fromString(p.substring(0,spos));
941 String name = p.substring(spos+1);
942 newPackedRefs.put(name, id);
944 } catch (IOException e) {
945 throw new Error("Cannot read packed refs",e);
946 } finally {
947 if (fileReader != null) {
948 try {
949 fileReader.close();
950 } catch (IOException e) {
951 // Cannot do anything more here
952 e.printStackTrace();
956 packedRefs = newPackedRefs;
960 * @return true if HEAD points to a StGit patch.
962 public boolean isStGitMode() {
963 try {
964 File file = new File(getDirectory(), "HEAD");
965 BufferedReader reader = new BufferedReader(new FileReader(file));
966 String string = reader.readLine();
967 if (!string.startsWith("ref: refs/heads/"))
968 return false;
969 String branch = string.substring("ref: refs/heads/".length());
970 File currentPatches = new File(new File(new File(getDirectory(),
971 "patches"), branch), "applied");
972 if (!currentPatches.exists())
973 return false;
974 if (currentPatches.length() == 0)
975 return false;
976 return true;
978 } catch (IOException e) {
979 e.printStackTrace();
980 return false;
985 * @return applied patches in a map indexed on current commit id
986 * @throws IOException
988 public Map<ObjectId,StGitPatch> getAppliedPatches() throws IOException {
989 Map<ObjectId,StGitPatch> ret = new HashMap<ObjectId,StGitPatch>();
990 if (isStGitMode()) {
991 File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
992 BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
993 for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
994 File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
995 BufferedReader tfr = new BufferedReader(new FileReader(topFile));
996 String objectId = tfr.readLine();
997 ObjectId id = ObjectId.fromString(objectId);
998 ret.put(id, new StGitPatch(patchName, id));
999 tfr.close();
1001 apr.close();
1003 return ret;
1006 private Collection<String> listFilesRecursively(File root, File start) {
1007 if (start == null)
1008 start = root;
1009 Collection<String> ret = new ArrayList<String>();
1010 File[] files = start.listFiles();
1011 for (int i = 0; i < files.length; ++i) {
1012 if (files[i].isDirectory())
1013 ret.addAll(listFilesRecursively(root, files[i]));
1014 else if (files[i].length() == 41) {
1015 String name = files[i].toString().substring(
1016 root.toString().length() + 1);
1017 if (File.separatorChar != '/')
1018 name = name.replace(File.separatorChar, '/');
1019 ret.add(name);
1022 return ret;
1025 /** Clean up stale caches */
1026 public void refreshFromDisk() {
1027 packedRefs = null;
1031 * @return a representation of the index associated with this repo
1032 * @throws IOException
1034 public GitIndex getIndex() throws IOException {
1035 if (index == null) {
1036 index = new GitIndex(this);
1037 index.read();
1038 } else {
1039 index.rereadIfNecessary();
1041 return index;
1044 static byte[] gitInternalSlash(byte[] bytes) {
1045 if (File.separatorChar == '/')
1046 return bytes;
1047 for (int i=0; i<bytes.length; ++i)
1048 if (bytes[i] == File.separatorChar)
1049 bytes[i] = '/';
1050 return bytes;
1054 * @return an important state
1056 public RepositoryState getRepositoryState() {
1057 if (new File(getWorkDir(), ".dotest").exists())
1058 return RepositoryState.REBASING;
1059 if (new File(gitDir,".dotest-merge").exists())
1060 return RepositoryState.REBASING_INTERACTIVE;
1061 if (new File(gitDir,"MERGE_HEAD").exists())
1062 return RepositoryState.MERGING;
1063 if (new File(gitDir,"BISECT_LOG").exists())
1064 return RepositoryState.BISECTING;
1065 return RepositoryState.SAFE;
1069 * Check validty of a ref name. It must not contain character that has
1070 * a special meaning in a Git object reference expression. Some other
1071 * dangerous characters are also excluded.
1073 * @param refName
1075 * @return true if refName is a valid ref name
1077 public static boolean isValidRefName(final String refName) {
1078 final int len = refName.length();
1079 char p = '\0';
1080 for (int i=0; i<len; ++i) {
1081 char c = refName.charAt(i);
1082 if (c <= ' ')
1083 return false;
1084 switch(c) {
1085 case '.':
1086 if (i == 0)
1087 return false;
1088 if (p == '/')
1089 return false;
1090 if (p == '.')
1091 return false;
1092 break;
1093 case '/':
1094 if (i == 0)
1095 return false;
1096 if (i == len -1)
1097 return false;
1098 break;
1099 case '~': case '^': case ':':
1100 case '?': case '[':
1101 return false;
1102 case '*':
1103 return false;
1105 p = c;
1107 return true;
1111 * String work dir and return normalized repository path
1113 * @param wd Work dir
1114 * @param f File whose path shall be stripp off it's workdir
1115 * @return normalized repository relative path
1117 public static String stripWorkDir(File wd, File f) {
1118 String relName = f.getPath().substring(wd.getPath().length() + 1);
1119 relName = relName.replace(File.separatorChar, '/');
1120 return relName;
1124 * @param name
1125 * The "remote" name in this repo
1126 * @return information about how a remote repository is beging tracked
1128 public RemoteSpec getRemoteSpec(String name) {
1129 String url = getConfig().getString("remote."+name, null, "url");
1130 String fetchPattern = getConfig().getString("remote."+name, null, "fetch");
1131 String pushPattern = getConfig().getString("remote."+name, null, "push");
1132 return new RemoteSpec(name, url, fetchPattern, pushPattern);
1136 * Setup repository configuration for a new remote
1138 * @param remote
1139 * remote name, e.g. "origin"
1140 * @param url
1141 * fetch url, e.g. "git://repo.or.cz/egit.git"
1142 * @param branch
1143 * local branch name, e.g. "master"
1145 public void configureDefaultBranch(final String remote, final String url, final String branch) {
1146 config.setString(RepositoryConfig.REMOTE_SECTION, remote, "url",
1147 url);
1148 config.setString(RepositoryConfig.REMOTE_SECTION, remote, "fetch",
1149 "+" + Constants.HEADS_PREFIX + "/*:" + Constants.REMOTES_PREFIX + "/" + remote + "/*");
1150 config.setString(RepositoryConfig.BRANCH_SECTION, branch, "remote",
1151 remote);
1152 config.setString(RepositoryConfig.BRANCH_SECTION, Constants.MASTER, "merge",
1153 Constants.HEADS_PREFIX + "/" + branch);
1157 * @return the workdir file, i.e. where the files are checked out
1159 public File getWorkDir() {
1160 return getDirectory().getParentFile();
1164 * Setup HEAD and "master" refs for a new repository.
1166 * @param remoteBranch
1167 * The remote branch to start with
1168 * @param branch
1169 * The local branch to configure, initially starting at
1170 * remoteBranch
1171 * @return the commit references by the new HEAD
1172 * @throws IOException
1174 public Commit setupHEADRef(final String remoteBranch, final String branch) throws IOException {
1175 Commit mapCommit = mapCommit(remoteBranch);
1176 String refName = Constants.HEADS_PREFIX + "/" + branch;
1177 LockFile masterRef = lockRef(refName);
1178 try {
1179 masterRef.write(mapCommit.getCommitId());
1180 masterRef.commit();
1181 } finally {
1182 masterRef.unlock();
1184 writeSymref(Constants.HEAD, refName);
1185 return mapCommit;