summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/google/gerrit/git/PatchSetImporter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/gerrit/git/PatchSetImporter.java')
-rw-r--r--src/main/java/com/google/gerrit/git/PatchSetImporter.java346
1 files changed, 346 insertions, 0 deletions
diff --git a/src/main/java/com/google/gerrit/git/PatchSetImporter.java b/src/main/java/com/google/gerrit/git/PatchSetImporter.java
new file mode 100644
index 0000000000..3a57f70421
--- /dev/null
+++ b/src/main/java/com/google/gerrit/git/PatchSetImporter.java
@@ -0,0 +1,346 @@
+// 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.gerrit.git;
+
+import com.google.gerrit.client.reviewdb.Account;
+import com.google.gerrit.client.reviewdb.AccountExternalId;
+import com.google.gerrit.client.reviewdb.Patch;
+import com.google.gerrit.client.reviewdb.PatchContent;
+import com.google.gerrit.client.reviewdb.PatchSet;
+import com.google.gerrit.client.reviewdb.PatchSetAncestor;
+import com.google.gerrit.client.reviewdb.PatchSetInfo;
+import com.google.gerrit.client.reviewdb.RevId;
+import com.google.gerrit.client.reviewdb.ReviewDb;
+import com.google.gerrit.client.reviewdb.UserIdentity;
+import com.google.gwtorm.client.OrmDuplicateKeyException;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Transaction;
+
+import org.spearce.jgit.lib.Commit;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.ObjectWriter;
+import org.spearce.jgit.lib.PersonIdent;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.lib.Tree;
+import org.spearce.jgit.patch.CombinedFileHeader;
+import org.spearce.jgit.patch.FileHeader;
+import org.spearce.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Imports a {@link PatchSet} from a {@link Commit}. */
+public class PatchSetImporter {
+ private final ReviewDb db;
+ private final Repository repo;
+ private final RevCommit src;
+ private final PatchSet dst;
+ private final boolean isNew;
+ private Transaction txn;
+ private org.spearce.jgit.patch.Patch gitpatch;
+
+ private PatchSetInfo info;
+ private boolean infoIsNew;
+
+ private final MessageDigest contentmd = Constants.newMessageDigest();
+ private final Map<String, Patch> patchExisting = new HashMap<String, Patch>();
+ private final List<Patch> patchInsert = new ArrayList<Patch>();
+ private final List<Patch> patchUpdate = new ArrayList<Patch>();
+ private final Map<PatchContent.Key, String> content =
+ new HashMap<PatchContent.Key, String>();
+
+ private final Map<Integer, PatchSetAncestor> ancestorExisting =
+ new HashMap<Integer, PatchSetAncestor>();
+ private final List<PatchSetAncestor> ancestorInsert =
+ new ArrayList<PatchSetAncestor>();
+ private final List<PatchSetAncestor> ancestorUpdate =
+ new ArrayList<PatchSetAncestor>();
+
+ public PatchSetImporter(final ReviewDb dstDb, final Repository srcRepo,
+ final RevCommit srcCommit, final PatchSet dstPatchSet,
+ final boolean isNewPatchSet) {
+ db = dstDb;
+ repo = srcRepo;
+ src = srcCommit;
+ dst = dstPatchSet;
+ isNew = isNewPatchSet;
+ }
+
+ public void setTransaction(final Transaction t) {
+ txn = t;
+ }
+
+ public PatchSetInfo getPatchSetInfo() {
+ return info;
+ }
+
+ public void run() throws IOException, OrmException {
+ gitpatch = readGitPatch();
+
+ dst.setRevision(toRevId(src));
+
+ if (!isNew) {
+ // If we aren't a new patch set then we need to load the existing
+ // files so we can update or delete them if there are corrections.
+ //
+ info = db.patchSetInfo().get(dst.getId());
+ for (final Patch p : db.patches().byPatchSet(dst.getId())) {
+ patchExisting.put(p.getFileName(), p);
+ }
+ for (final PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(
+ dst.getId())) {
+ ancestorExisting.put(a.getPosition(), a);
+ }
+ }
+
+ importInfo();
+ for (final FileHeader fh : gitpatch.getFiles()) {
+ importFile(fh);
+ }
+
+ // Ensure all content entities exist
+ //
+ putPatchContent();
+
+ final boolean auto = txn == null;
+ if (auto) {
+ txn = db.beginTransaction();
+ }
+ if (isNew) {
+ db.patchSets().insert(Collections.singleton(dst), txn);
+ }
+ if (infoIsNew) {
+ db.patchSetInfo().insert(Collections.singleton(info), txn);
+ } else {
+ db.patchSetInfo().update(Collections.singleton(info), txn);
+ }
+ db.patches().insert(patchInsert, txn);
+ db.patchSetAncestors().insert(ancestorInsert, txn);
+ if (!isNew) {
+ db.patches().update(patchUpdate, txn);
+ db.patches().delete(patchExisting.values(), txn);
+
+ db.patchSetAncestors().update(ancestorUpdate, txn);
+ db.patchSetAncestors().delete(ancestorExisting.values(), txn);
+ }
+ if (auto) {
+ txn.commit();
+ txn = null;
+ }
+ }
+
+ private void importInfo() throws OrmException {
+ if (info == null) {
+ info = new PatchSetInfo(dst.getId());
+ infoIsNew = true;
+ }
+
+ info.setSubject(src.getShortMessage());
+ info.setMessage(src.getFullMessage());
+ info.setAuthor(toUserIdentity(src.getAuthorIdent()));
+ info.setCommitter(toUserIdentity(src.getCommitterIdent()));
+
+ for (int p = 0; p < src.getParentCount(); p++) {
+ PatchSetAncestor a = ancestorExisting.remove(p + 1);
+ if (a == null) {
+ a = new PatchSetAncestor(new PatchSetAncestor.Id(dst.getId(), p + 1));
+ ancestorInsert.add(a);
+ } else {
+ ancestorUpdate.add(a);
+ }
+ a.setAncestorRevision(toRevId(src.getParent(p)));
+ }
+ }
+
+ private UserIdentity toUserIdentity(final PersonIdent who)
+ throws OrmException {
+ final UserIdentity u = new UserIdentity();
+ u.setName(who.getName());
+ u.setEmail(who.getEmailAddress());
+ u.setDate(new Timestamp(who.getWhen().getTime()));
+ u.setTimeZone(who.getTimeZoneOffset());
+
+ if (u.getEmail() != null) {
+ // If only one account has access to this email address, select it
+ // as the identity of the user.
+ //
+ final Set<Account.Id> a = new HashSet<Account.Id>();
+ for (final AccountExternalId e : db.accountExternalIds().byEmailAddress(
+ u.getEmail())) {
+ a.add(e.getAccountId());
+ }
+ if (a.size() == 1) {
+ u.setAccount(a.iterator().next());
+ }
+ }
+
+ return u;
+ }
+
+ private void importFile(final FileHeader fh)
+ throws UnsupportedEncodingException {
+ final String path;
+ if (fh.getChangeType() == FileHeader.ChangeType.DELETE) {
+ path = fh.getOldName();
+ } else {
+ path = fh.getNewName();
+ }
+
+ Patch p = patchExisting.remove(path);
+ if (p == null) {
+ p = new Patch(new Patch.Key(dst.getId(), path));
+ patchInsert.add(p);
+ } else {
+ p.setSourceFileName(null);
+ patchUpdate.add(p);
+ }
+
+ // Convert the ChangeType
+ //
+ if (fh.getChangeType() == FileHeader.ChangeType.ADD) {
+ p.setChangeType(Patch.ChangeType.ADD);
+
+ } else if (fh.getChangeType() == FileHeader.ChangeType.MODIFY) {
+ p.setChangeType(Patch.ChangeType.MODIFIED);
+
+ } else if (fh.getChangeType() == FileHeader.ChangeType.DELETE) {
+ p.setChangeType(Patch.ChangeType.DELETED);
+
+ } else if (fh.getChangeType() == FileHeader.ChangeType.RENAME) {
+ p.setChangeType(Patch.ChangeType.RENAMED);
+ p.setSourceFileName(fh.getOldName());
+
+ } else if (fh.getChangeType() == FileHeader.ChangeType.COPY) {
+ p.setChangeType(Patch.ChangeType.COPIED);
+ p.setSourceFileName(fh.getOldName());
+ }
+
+ // Convert the PatchType
+ //
+ if (fh instanceof CombinedFileHeader) {
+ p.setPatchType(Patch.PatchType.N_WAY);
+
+ } else if (fh.getPatchType() == FileHeader.PatchType.GIT_BINARY) {
+ p.setPatchType(Patch.PatchType.BINARY);
+
+ } else if (fh.getPatchType() == FileHeader.PatchType.BINARY) {
+ p.setPatchType(Patch.PatchType.BINARY);
+ }
+
+ String contentStr = fh.getScriptText();
+ if (p.getPatchType() != Patch.PatchType.BINARY
+ && contentStr.indexOf('\0') >= 0) {
+ // Its really binary, but Git couldn't see the nul early enough
+ // to realize its binary, and instead produced the diff. Some
+ // databases (PostgreSQL) won't allow us to store a nul into a
+ // text field. Force it to be a binary; it really should have
+ // been that in the first place.
+ //
+ p.setPatchType(Patch.PatchType.BINARY);
+ final int lfatat = contentStr.indexOf("\n@@");
+ final StringBuilder b = new StringBuilder();
+ b.append(contentStr.substring(0, lfatat + 1));
+ b.append("Binary files ");
+ b.append(path);
+ b.append(" and ");
+ b.append(path);
+ b.append(" differ\n");
+ contentStr = b.toString();
+ }
+
+ // Hash the content.
+ //
+ contentmd.reset();
+ contentmd.update(contentStr.getBytes("UTF-8"));
+ final PatchContent.Key contentKey =
+ new PatchContent.Key(ObjectId.fromRaw(contentmd.digest()).name());
+ content.put(contentKey, contentStr);
+ p.setContent(contentKey);
+ }
+
+ private void putPatchContent() throws OrmException {
+ for (final Iterator<Map.Entry<PatchContent.Key, String>> i =
+ content.entrySet().iterator(); i.hasNext();) {
+ final Map.Entry<PatchContent.Key, String> e = i.next();
+ final PatchContent pc = new PatchContent(e.getKey(), e.getValue());
+ try {
+ db.patchContents().insert(Collections.singleton(pc));
+ } catch (OrmDuplicateKeyException err) {
+ // Should be fine; someone else beat us to the insertion.
+ }
+ i.remove();
+ }
+ }
+
+ private static RevId toRevId(final RevCommit src) {
+ return new RevId(src.getId().name());
+ }
+
+ private org.spearce.jgit.patch.Patch readGitPatch() throws IOException {
+ final List<String> args = new ArrayList<String>();
+ args.add("git");
+ args.add("--git-dir=.");
+ args.add("diff-tree");
+ args.add("-M");
+ args.add("--full-index");
+
+ switch (src.getParentCount()) {
+ case 0:
+ args.add("--unified=5");
+ args.add(new ObjectWriter(repo).writeTree(new Tree(repo)).name());
+ args.add(src.getTree().getId().name());
+ break;
+ case 1:
+ args.add("--unified=5");
+ args.add(src.getParent(0).getId().name());
+ args.add(src.getId().name());
+ break;
+ default:
+ args.add("--cc");
+ args.add(src.getId().name());
+ break;
+ }
+
+ final Process proc =
+ Runtime.getRuntime().exec(args.toArray(new String[args.size()]), null,
+ repo.getDirectory());
+ try {
+ final org.spearce.jgit.patch.Patch p = new org.spearce.jgit.patch.Patch();
+ proc.getOutputStream().close();
+ proc.getErrorStream().close();
+ p.parse(proc.getInputStream());
+ proc.getInputStream().close();
+ return p;
+ } finally {
+ try {
+ if (proc.waitFor() != 0) {
+ throw new IOException("git diff-tree exited abnormally");
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+}