summaryrefslogtreecommitdiffstats
path: root/mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java
diff options
context:
space:
mode:
Diffstat (limited to 'mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java')
-rw-r--r--mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java270
1 files changed, 270 insertions, 0 deletions
diff --git a/mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java b/mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java
new file mode 100644
index 0000000000..1682dd6758
--- /dev/null
+++ b/mgrapp/src/com/google/githacks/BrokenShallowRepositoryCreator.java
@@ -0,0 +1,270 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.githacks;
+
+import com.google.codereview.util.GitMetaUtil;
+
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.LockFile;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.PackWriter;
+import org.spearce.jgit.lib.Ref;
+import org.spearce.jgit.lib.RefUpdate;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.TextProgressMonitor;
+import org.spearce.jgit.revwalk.RevCommit;
+import org.spearce.jgit.revwalk.RevObject;
+import org.spearce.jgit.revwalk.RevWalk;
+import org.spearce.jgit.treewalk.TreeWalk;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Creates a broken shallow repository with a populated assumed base.
+ * <p>
+ * Broken shallow repositories contain a handful of commits (the assumed base)
+ * and the trees referenced by those commits, but not the blobs or the commit
+ * ancestors. These repositories are meant to only be used as a source to fetch
+ * an overlay from, where the native Git protocol negotiated the assumed base as
+ * the common ancestor.
+ * <p>
+ * The created repository is not marked as shallow and does not have any grafts
+ * in it. Clients (or applications) which attempt to walk back through history
+ * beyond the assumed base will encounter missing objects and crash. Not
+ * configuring the shallow or grafts file is a "data integrity" feature to
+ * ensure that clients fetching or cloning from this shallow repository will not
+ * be able to succeed, as they do not (and would not receive) the needed but
+ * missing objects.
+ */
+public class BrokenShallowRepositoryCreator {
+ /**
+ * Create (or update) broken shallow repositories, recursively.
+ *
+ * @param srcTop the root of the source tree.
+ * @param dstTop the root of the destination tree.
+ * @throws IOException a repository failed to be converted.
+ */
+ public static void createRecursive(final File srcTop, final File dstTop)
+ throws IOException {
+ final File[] srcEntries = srcTop.listFiles();
+ if (srcEntries == null) {
+ return;
+ }
+
+ for (final File srcEnt : srcEntries) {
+ final String srcName = srcEnt.getName();
+ if (srcName.equals(".") || srcName.equals("..")) {
+ continue;
+ } else if (!srcEnt.isDirectory()) {
+ continue;
+ } else if (GitMetaUtil.isGitRepository(srcEnt)) {
+ create(srcEnt, new File(dstTop, srcEnt.getName()));
+ } else {
+ createRecursive(srcEnt, new File(dstTop, srcEnt.getName()));
+ }
+ }
+ }
+
+ /**
+ * Create (or update) a broken shallow repository.
+ *
+ * @param srcGitDir the source repository, where commits and trees can be
+ * copied from.
+ * @param dstGitDir the destination repository, where the tree data (but not
+ * blob data) will be packed into. If this directory does not exist it
+ * will be automatically created.
+ * @throws IOException there was an error reading from the source or writing
+ * to the destination repository.
+ */
+ public static void create(final File srcGitDir, final File dstGitDir)
+ throws IOException {
+ final Repository srcdb = new Repository(srcGitDir);
+
+ final RevWalk srcwalk = new RevWalk(srcdb);
+ final List<ObjectId> assumed = readAssumedBase(srcdb);
+ final List<RevObject> toCopy = new ArrayList<RevObject>(assumed.size() * 2);
+ final TreeWalk tw = new TreeWalk(srcdb);
+ for (final ObjectId id : assumed) {
+ final RevCommit c = srcwalk.parseCommit(id);
+ toCopy.add(c);
+ toCopy.add(c.getTree());
+
+ tw.reset();
+ tw.addTree(c.getTree());
+
+ while (tw.next()) {
+ switch (tw.getFileMode(0).getObjectType()) {
+ case Constants.OBJ_TREE:
+ toCopy.add(srcwalk.lookupTree(tw.getObjectId(0)));
+ tw.enterSubtree();
+ break;
+ case Constants.OBJ_BLOB:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ final Repository destdb = new Repository(dstGitDir);
+ if (!destdb.getDirectory().exists()) {
+ destdb.create();
+ }
+
+ final List<ObjectId> destAssumed = readAssumedBase(destdb);
+ destAssumed.addAll(assumed);
+ writeAssumedBase(destdb, destAssumed);
+
+ if (destAssumed.isEmpty()) {
+ // Nothing assumed, it doesn't need special processing from us.
+ //
+ return;
+ }
+
+ System.out.println("Packing " + destdb.getDirectory());
+
+ // Prepare pack of the assumed base. Clients wouldn't need to
+ // fetch this pack, as they already have its contents.
+ //
+ PackWriter packer = new PackWriter(srcdb, new TextProgressMonitor());
+ packer.preparePack(toCopy.iterator());
+ storePack(destdb, packer);
+
+ // Prepare a pack of everything else not in the assumed base. This
+ // would need to be fetched. We build it second so it has a newer
+ // timestamp when it goes into the list of packs, and will therefore
+ // be searched first by clients.
+ //
+ final Map<String, Ref> srcrefs = srcdb.getAllRefs();
+ final List<ObjectId> need = new ArrayList<ObjectId>();
+ for (final Ref r : srcrefs.values()) {
+ need.add(r.getObjectId());
+ }
+ packer = new PackWriter(srcdb, new TextProgressMonitor());
+ packer.preparePack(need, destAssumed, false, false);
+ storePack(destdb, packer);
+
+ // Force all of the refs in destdb to match srcdb. We want full
+ // mirroring style semantics now that the objects are in place.
+ //
+ final RevWalk dstwalk = new RevWalk(destdb);
+ destdb.writeSymref(Constants.HEAD, srcdb.getFullBranch());
+ for (final Ref r : destdb.getAllRefs().values()) {
+ if (!srcrefs.containsKey(r.getName())) {
+ final RefUpdate u = destdb.updateRef(r.getName());
+ u.setForceUpdate(true);
+ u.delete(dstwalk);
+ }
+ }
+
+ for (final Ref r : srcrefs.values()) {
+ final RefUpdate u = destdb.updateRef(r.getName());
+ if (u.getOldObjectId() == null
+ || !u.getOldObjectId().equals(r.getObjectId())) {
+ u.setNewObjectId(r.getObjectId());
+ u.setForceUpdate(true);
+ u.update(dstwalk);
+ }
+ }
+
+ srcdb.close();
+ destdb.close();
+ }
+
+ private static void storePack(final Repository destdb, PackWriter packer)
+ throws FileNotFoundException, IOException {
+ final String packName = "pack-" + packer.computeName().name();
+ final File packDir = new File(destdb.getObjectsDirectory(), "pack");
+ final File packPath = new File(packDir, packName + ".pack");
+ final File idxPath = new File(packDir, packName + ".idx");
+
+ if (!packPath.exists() && !idxPath.exists()) {
+ {
+ final OutputStream os = new FileOutputStream(packPath);
+ try {
+ packer.writePack(os);
+ } finally {
+ os.close();
+ }
+ packPath.setReadOnly();
+ }
+ {
+ final OutputStream os = new FileOutputStream(idxPath);
+ try {
+ packer.writeIndex(os);
+ } finally {
+ os.close();
+ }
+ idxPath.setReadOnly();
+ }
+ }
+ }
+
+ private static List<ObjectId> readAssumedBase(final Repository db)
+ throws IOException {
+ final List<ObjectId> list = new ArrayList<ObjectId>();
+ try {
+ final BufferedReader br;
+
+ br = new BufferedReader(new FileReader(infoAssumedBase(db)));
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ list.add(ObjectId.fromString(line));
+ }
+ } finally {
+ br.close();
+ }
+ } catch (FileNotFoundException noList) {
+ // Ignore it. We'll return an empty list to the caller.
+ }
+ return list;
+ }
+
+ private static void writeAssumedBase(final Repository db,
+ final List<ObjectId> newList) throws IOException {
+ if (newList == null || newList.isEmpty()) {
+ infoAssumedBase(db).delete();
+ return;
+ }
+
+ final LockFile lf = new LockFile(infoAssumedBase(db));
+ if (!lf.lock()) {
+ throw new IOException("Cannot lock " + infoAssumedBase(db));
+ }
+
+ final OutputStream ow = lf.getOutputStream();
+ for (final ObjectId id : newList) {
+ id.copyTo(ow);
+ }
+ ow.close();
+ if (!lf.commit()) {
+ throw new IOException("Cannot commit " + infoAssumedBase(db));
+ }
+ }
+
+ private static File infoAssumedBase(final Repository db) {
+ return new File(db.getDirectory(), "info/assumed-base");
+ }
+}