ExportReviewNotes: Dump submitted changes to refs/notes/review
This program allows site administrators to dump their existing notes
out to the refs/notes/review branch, making the prior data available
to Git clients.
Change-Id: Iebaf1e4b2fb4620443e80d2a8f840cb30ae1e389
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/pgm-ExportReviewNotes.txt b/Documentation/pgm-ExportReviewNotes.txt
new file mode 100644
index 0000000..b43989f
--- /dev/null
+++ b/Documentation/pgm-ExportReviewNotes.txt
@@ -0,0 +1,51 @@
+ExportReviewNotes
+=================
+
+NAME
+----
+ExportReviewNotes - Export successful reviews to refs/notes/review
+
+SYNOPSIS
+--------
+[verse]
+'java' -jar gerrit.war 'ExportReviewNotes' -d <SITE_PATH>
+
+DESCRIPTION
+-----------
+Scans every submitted change and creates an initial notes
+branch detailing the previous submission information for
+each merged changed.
+
+This task can take quite some time, but can run in the background
+concurrently to the server if the database is MySQL or PostgreSQL.
+If the database is H2, this task must be run by itself.
+
+OPTIONS
+-------
+
+-d::
+\--site-path::
+ Location of the gerrit.config file, and all other per-site
+ configuration data, supporting libaries and log files.
+
+\--threads::
+ Number of threads to perform the scan work with. Defaults to
+ twice the number of CPUs available.
+
+CONTEXT
+-------
+This command can only be run on a server which has direct
+connectivity to the metadata database, and local access to the
+managed Git repositories.
+
+EXAMPLES
+--------
+To generate all review information:
+
+====
+ $ java -jar gerrit.war ExportReviewNotes -d site_path --threads 16
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index c6430ad..ce9de71 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -18,6 +18,9 @@
link:pgm-gsql.html[gsql]::
Administrative interface to idle database.
+link:pgm-ExportReviewNotes.html[ExportReviewNotes]::
+ Export submitted review information to refs/notes/review.
+
link:pgm-ScanTrackingIds.html[ScanTrackingIds]::
Rescan all changes after configuring trackingids.
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
new file mode 100644
index 0000000..b8e4160
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ExportReviewNotes.java
@@ -0,0 +1,264 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.cache.CachePool;
+import com.google.gerrit.server.config.ApprovalTypesProvider;
+import com.google.gerrit.server.config.AuthConfigModule;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.git.CodeReviewNoteCreationException;
+import com.google.gerrit.server.git.CreateCodeReviewNotes;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Scopes;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
+import org.eclipse.jgit.util.BlockList;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** Export review notes for all submitted changes in all projects. */
+public class ExportReviewNotes extends SiteProgram {
+ @Option(name = "--threads", usage = "Number of concurrent threads to run")
+ private int threads = 2 * Runtime.getRuntime().availableProcessors();
+
+ private final LifecycleManager manager = new LifecycleManager();
+ private final TextProgressMonitor textMonitor = new TextProgressMonitor();
+ private final ThreadSafeProgressMonitor monitor =
+ new ThreadSafeProgressMonitor(textMonitor);
+
+ private Injector dbInjector;
+ private Injector gitInjector;
+
+ @Inject
+ private GitRepositoryManager gitManager;
+
+ @Inject
+ private SchemaFactory<ReviewDb> database;
+
+ @Inject
+ private CreateCodeReviewNotes.Factory codeReviewNotesFactory;
+
+ private Map<Project.NameKey, List<Change>> changes;
+
+ @Override
+ public int run() throws Exception {
+ if (threads <= 0) {
+ threads = 1;
+ }
+
+ dbInjector = createDbInjector(MULTI_USER);
+ gitInjector = dbInjector.createChildInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
+ bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in(
+ Scopes.SINGLETON);
+ bind(String.class).annotatedWith(CanonicalWebUrl.class)
+ .toProvider(CanonicalWebUrlProvider.class).in(Scopes.SINGLETON);
+ bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
+ .toProvider(GerritPersonIdentProvider.class).in(Scopes.SINGLETON);
+ bind(CachePool.class);
+
+ install(AccountCacheImpl.module());
+ install(GroupCacheImpl.module());
+ install(new AuthConfigModule());
+ install(new FactoryModule() {
+ @Override
+ protected void configure() {
+ factory(CreateCodeReviewNotes.Factory.class);
+ }
+ });
+ install(new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(CachePool.Lifecycle.class);
+ listener().to(LocalDiskRepositoryManager.Lifecycle.class);
+ }
+ });
+ }
+ });
+
+ manager.add(dbInjector, gitInjector);
+ manager.start();
+ gitInjector.injectMembers(this);
+
+ List<Change> allChangeList = allChanges();
+ monitor.beginTask("Scanning changes", allChangeList.size());
+ changes = cluster(allChangeList);
+ allChangeList = null;
+
+ monitor.startWorkers(threads);
+ for (int tid = 0; tid < threads; tid++) {
+ new Worker().start();
+ }
+ monitor.waitForCompletion();
+ monitor.endTask();
+ manager.stop();
+ return 0;
+ }
+
+ private List<Change> allChanges() throws OrmException {
+ final ReviewDb db = database.open();
+ try {
+ return db.changes().all().toList();
+ } finally {
+ db.close();
+ }
+ }
+
+ private Map<Project.NameKey, List<Change>> cluster(List<Change> changes) {
+ HashMap<Project.NameKey, List<Change>> m =
+ new HashMap<Project.NameKey, List<Change>>();
+ for (Change change : changes) {
+ if (change.getStatus() == Change.Status.MERGED) {
+ List<Change> l = m.get(change.getProject());
+ if (l == null) {
+ l = new BlockList<Change>();
+ m.put(change.getProject(), l);
+ }
+ l.add(change);
+ } else {
+ monitor.update(1);
+ }
+ }
+ return m;
+ }
+
+ private void export(ReviewDb db, Project.NameKey project, List<Change> changes)
+ throws IOException, OrmException, CodeReviewNoteCreationException,
+ InterruptedException {
+ final Repository git;
+ try {
+ git = gitManager.openRepository(project);
+ } catch (RepositoryNotFoundException e) {
+ return;
+ }
+ try {
+ CreateCodeReviewNotes notes = codeReviewNotesFactory.create(db, git);
+ try {
+ notes.loadBase();
+ for (Change change : changes) {
+ monitor.update(1);
+ PatchSet ps = db.patchSets().get(change.currentPatchSetId());
+ if (ps == null) {
+ continue;
+ }
+ notes.add(change, ObjectId.fromString(ps.getRevision().get()));
+ }
+ notes.commit("Exported prior reviews from Gerrit Code Review\n");
+ notes.updateRef();
+ } finally {
+ notes.release();
+ }
+ } finally {
+ git.close();
+ }
+ }
+
+ private Map.Entry<Project.NameKey, List<Change>> next() {
+ synchronized (changes) {
+ if (changes.isEmpty()) {
+ return null;
+ }
+
+ final Project.NameKey name = changes.keySet().iterator().next();
+ final List<Change> list = changes.remove(name);
+ return new Map.Entry<Project.NameKey, List<Change>>() {
+ @Override
+ public Project.NameKey getKey() {
+ return name;
+ }
+
+ @Override
+ public List<Change> getValue() {
+ return list;
+ }
+
+ @Override
+ public List<Change> setValue(List<Change> value) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+ private class Worker extends Thread {
+ @Override
+ public void run() {
+ ReviewDb db;
+ try {
+ db = database.open();
+ } catch (OrmException e) {
+ e.printStackTrace();
+ return;
+ }
+ try {
+ for (;;) {
+ Entry<Project.NameKey, List<Change>> next = next();
+ if (next != null) {
+ try {
+ export(db, next.getKey(), next.getValue());
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (OrmException e) {
+ e.printStackTrace();
+ } catch (CodeReviewNoteCreationException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ break;
+ }
+ }
+ } finally {
+ monitor.endWorker();
+ db.close();
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
index 25ab239..db5bceb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ApprovalTypesProvider.java
@@ -29,7 +29,7 @@
import java.util.Collections;
import java.util.List;
-class ApprovalTypesProvider implements Provider<ApprovalTypes> {
+public class ApprovalTypesProvider implements Provider<ApprovalTypes> {
private final SchemaFactory<ReviewDb> schema;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewNoteCreationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewNoteCreationException.java
index 90de785..367ed56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewNoteCreationException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewNoteCreationException.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.gerrit.server.git;
+import org.eclipse.jgit.revwalk.RevCommit;
+
/**
* Thrown when creation of a code review note fails.
*/
@@ -27,9 +29,9 @@
super(why);
}
- public CodeReviewNoteCreationException(final CodeReviewCommit commit,
+ public CodeReviewNoteCreationException(final RevCommit commit,
final Throwable cause) {
super("Couldn't create code review note for the following commit: "
- + commit, cause);
+ + commit.name(), cause);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
index 67705e4..976ec2e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
@@ -14,8 +14,11 @@
package com.google.gerrit.server.git;
+import static com.google.gerrit.server.git.GitRepositoryManager.REFS_NOTES_REVIEW;
+
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -26,6 +29,9 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -36,10 +42,10 @@
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.DefaultNoteMerger;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.notes.NoteMapMerger;
+import org.eclipse.jgit.notes.NoteMerger;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -47,6 +53,8 @@
import java.io.IOException;
import java.util.List;
+import javax.annotation.Nullable;
+
/**
* This class create code review notes for given {@link CodeReviewCommit}s.
* <p>
@@ -55,10 +63,9 @@
*/
public class CreateCodeReviewNotes {
public interface Factory {
- CreateCodeReviewNotes create(Repository db);
+ CreateCodeReviewNotes create(ReviewDb reviewDb, Repository db);
}
- static final String REFS_NOTES_REVIEW = "refs/notes/review";
private static final int MAX_LOCK_FAILURE_CALLS = 10;
private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
@@ -83,13 +90,15 @@
private PersonIdent author;
@Inject
- CreateCodeReviewNotes(final ReviewDb reviewDb,
+ CreateCodeReviewNotes(
@GerritPersonIdent final PersonIdent gerritIdent,
final AccountCache accountCache,
final ApprovalTypes approvalTypes,
- @CanonicalWebUrl final String canonicalWebUrl,
+ @Nullable @CanonicalWebUrl final String canonicalWebUrl,
+ @Assisted ReviewDb reviewDb,
@Assisted final Repository db) {
schema = reviewDb;
+ this.author = gerritIdent;
this.gerritIdent = gerritIdent;
this.accountCache = accountCache;
this.approvalTypes = approvalTypes;
@@ -106,110 +115,86 @@
try {
this.commits = commits;
this.author = author;
- setBase();
- setOurs();
-
- int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
- RefUpdate refUpdate = createRefUpdate(oursCommit, baseCommit);
-
- for (;;) {
- Result result = refUpdate.update();
-
- if (result == Result.LOCK_FAILURE) {
- if (--remainingLockFailureCalls > 0) {
- Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
- } else {
- throw new CodeReviewNoteCreationException(
- "Failed to lock the ref: " + REFS_NOTES_REVIEW);
- }
-
- } else if (result == Result.REJECTED) {
- RevCommit theirsCommit =
- revWalk.parseCommit(refUpdate.getOldObjectId());
- NoteMap theirs =
- NoteMap.read(revWalk.getObjectReader(), theirsCommit);
- NoteMapMerger merger = new NoteMapMerger(db);
- NoteMap merged = merger.merge(base, ours, theirs);
- RevCommit mergeCommit =
- createCommit(merged, gerritIdent, "Merged note commits\n",
- theirsCommit, oursCommit);
- refUpdate = createRefUpdate(mergeCommit, theirsCommit);
- remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
-
- } else if (result == Result.IO_FAILURE) {
- throw new CodeReviewNoteCreationException(
- "Couldn't create code review notes because of IO_FAILURE");
- } else {
- break;
- }
- }
-
+ loadBase();
+ applyNotes();
+ updateRef();
} catch (IOException e) {
throw new CodeReviewNoteCreationException(e);
} catch (InterruptedException e) {
throw new CodeReviewNoteCreationException(e);
} finally {
- reader.release();
- inserter.release();
- revWalk.release();
+ release();
}
}
- private void setBase() throws IOException {
+ public void loadBase() throws IOException {
Ref notesBranch = db.getRef(REFS_NOTES_REVIEW);
if (notesBranch != null) {
baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
}
- }
-
- private void setOurs() throws IOException, CodeReviewNoteCreationException {
if (baseCommit != null) {
ours = NoteMap.read(db.newObjectReader(), baseCommit);
} else {
ours = NoteMap.newEmptyMap();
}
+ }
+ private void applyNotes() throws IOException, CodeReviewNoteCreationException {
StringBuilder message =
new StringBuilder("Update notes for submitted changes\n\n");
for (CodeReviewCommit c : commits) {
- ObjectId noteContent = createNoteContent(c);
- if (ours.contains(c)) {
- // merge the existing and the new note as if they are both new
- // means: base == null
- // there is not really a common ancestry for these two note revisions
- // use the same NoteMerger that is used from the NoteMapMerger
- DefaultNoteMerger noteMerger = new DefaultNoteMerger();
- Note newNote = new Note(c, noteContent);
- noteContent =
- noteMerger.merge(null, newNote, base.getNote(c), reader, inserter)
- .getData();
- }
- ours.set(c, noteContent);
-
+ add(c.change, c);
message.append("* ").append(c.getShortMessage()).append("\n");
}
+ commit(message.toString());
+ }
+ public void commit(String message) throws IOException {
if (baseCommit != null) {
- oursCommit = createCommit(ours, author, message.toString(), baseCommit);
+ oursCommit = createCommit(ours, author, message, baseCommit);
} else {
- oursCommit = createCommit(ours, author, message.toString());
+ oursCommit = createCommit(ours, author, message);
}
}
- private ObjectId createNoteContent(CodeReviewCommit commit)
+ public void add(Change change, ObjectId commit)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException,
+ CodeReviewNoteCreationException {
+ if (!(commit instanceof RevCommit)) {
+ commit = revWalk.parseCommit(commit);
+ }
+
+ RevCommit c = (RevCommit) commit;
+ ObjectId noteContent = createNoteContent(change, c);
+ if (ours.contains(c)) {
+ // merge the existing and the new note as if they are both new
+ // means: base == null
+ // there is not really a common ancestry for these two note revisions
+ // use the same NoteMerger that is used from the NoteMapMerger
+ NoteMerger noteMerger = new ReviewNoteMerger();
+ Note newNote = new Note(c, noteContent);
+ noteContent = noteMerger.merge(null, newNote, ours.getNote(c),
+ reader, inserter).getData();
+ }
+ ours.set(c, noteContent);
+ }
+
+ private ObjectId createNoteContent(Change change, RevCommit commit)
throws CodeReviewNoteCreationException, IOException {
try {
ReviewNoteHeaderFormatter formatter =
new ReviewNoteHeaderFormatter(author.getTimeZone());
final List<String> idList = commit.getFooterLines(CHANGE_ID);
if (idList.isEmpty())
- formatter.appendChangeId(commit.change.getKey());
+ formatter.appendChangeId(change.getKey());
ResultSet<PatchSetApproval> approvals =
- schema.patchSetApprovals().byPatchSet(commit.patchsetId);
+ schema.patchSetApprovals().byPatchSet(change.currentPatchSetId());
PatchSetApproval submit = null;
for (PatchSetApproval a : approvals) {
- if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+ if (a.getValue() == 0) {
+ // Ignore 0 values.
+ } else if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
submit = a;
} else {
formatter.appendApproval(
@@ -219,23 +204,77 @@
}
}
- formatter.appendSubmittedBy(accountCache.get(submit.getAccountId()).getAccount());
- formatter.appendSubmittedAt(submit.getGranted());
-
- formatter.appendReviewedOn(canonicalWebUrl, commit.change.getId());
- formatter.appendProject(commit.change.getProject().get());
- formatter.appendBranch(commit.change.getDest());
+ if (submit != null) {
+ formatter.appendSubmittedBy(accountCache.get(submit.getAccountId()).getAccount());
+ formatter.appendSubmittedAt(submit.getGranted());
+ }
+ if (canonicalWebUrl != null) {
+ formatter.appendReviewedOn(canonicalWebUrl, change.getId());
+ }
+ formatter.appendProject(change.getProject().get());
+ formatter.appendBranch(change.getDest());
return inserter.insert(Constants.OBJ_BLOB, formatter.toString().getBytes("UTF-8"));
} catch (OrmException e) {
throw new CodeReviewNoteCreationException(commit, e);
}
}
+ public void updateRef() throws IOException, InterruptedException,
+ CodeReviewNoteCreationException, MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException {
+ if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
+ // If the trees are identical, there is no change in the notes.
+ // Avoid saving this commit as it has no new information.
+ return;
+ }
+
+ int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+ RefUpdate refUpdate = createRefUpdate(oursCommit, baseCommit);
+
+ for (;;) {
+ Result result = refUpdate.update();
+
+ if (result == Result.LOCK_FAILURE) {
+ if (--remainingLockFailureCalls > 0) {
+ Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+ } else {
+ throw new CodeReviewNoteCreationException(
+ "Failed to lock the ref: " + REFS_NOTES_REVIEW);
+ }
+
+ } else if (result == Result.REJECTED) {
+ RevCommit theirsCommit =
+ revWalk.parseCommit(refUpdate.getOldObjectId());
+ NoteMap theirs =
+ NoteMap.read(revWalk.getObjectReader(), theirsCommit);
+ NoteMapMerger merger = new NoteMapMerger(db);
+ NoteMap merged = merger.merge(base, ours, theirs);
+ RevCommit mergeCommit =
+ createCommit(merged, gerritIdent, "Merged note commits\n",
+ theirsCommit, oursCommit);
+ refUpdate = createRefUpdate(mergeCommit, theirsCommit);
+ remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+
+ } else if (result == Result.IO_FAILURE) {
+ throw new CodeReviewNoteCreationException(
+ "Couldn't create code review notes because of IO_FAILURE");
+ } else {
+ break;
+ }
+ }
+ }
+
+ public void release() {
+ reader.release();
+ inserter.release();
+ revWalk.release();
+ }
+
private RevCommit createCommit(NoteMap map, PersonIdent author,
String message, RevCommit... parents) throws IOException {
CommitBuilder b = new CommitBuilder();
b.setTreeId(map.writeTree(inserter));
- b.setAuthor(author);
+ b.setAuthor(author != null ? author : gerritIdent);
b.setCommitter(gerritIdent);
if (parents.length > 0) {
b.setParentIds(parents);
@@ -247,7 +286,8 @@
}
- private RefUpdate createRefUpdate(ObjectId newObjectId, ObjectId expectedOldObjectId) throws IOException {
+ private RefUpdate createRefUpdate(ObjectId newObjectId,
+ ObjectId expectedOldObjectId) throws IOException {
RefUpdate refUpdate = db.updateRef(REFS_NOTES_REVIEW);
refUpdate.setNewObjectId(newObjectId);
if (expectedOldObjectId == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index 701716d..a19b722 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -31,6 +31,9 @@
* environment.
*/
public interface GitRepositoryManager {
+ /** Notes branch successful reviews are written to after being merged. */
+ public static final String REFS_NOTES_REVIEW = "refs/notes/review";
+
/** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 60016e5..b66fcb59 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -992,14 +992,15 @@
}
}
- CreateCodeReviewNotes codeReviewNotes = codeReviewNotesFactory.create(db);
+ CreateCodeReviewNotes codeReviewNotes =
+ codeReviewNotesFactory.create(schema, db);
try {
codeReviewNotes.create(merged, computeAuthor(merged));
} catch (CodeReviewNoteCreationException e) {
log.error(e.getMessage());
}
replication.scheduleUpdate(destBranch.getParentKey(),
- CreateCodeReviewNotes.REFS_NOTES_REVIEW);
+ GitRepositoryManager.REFS_NOTES_REVIEW);
}
private void dependencyError(final CodeReviewCommit commit) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
new file mode 100644
index 0000000..60f1d0d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> and other copyright
+ * owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v1.0 which accompanies this
+ * distribution, is reproduced below, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.gerrit.server.git;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMerger;
+import org.eclipse.jgit.util.io.UnionInputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+class ReviewNoteMerger implements NoteMerger {
+ public Note merge(Note base, Note ours, Note theirs, ObjectReader reader,
+ ObjectInserter inserter) throws IOException {
+ if (ours == null) {
+ return theirs;
+ }
+ if (theirs == null) {
+ return ours;
+ }
+ if (ours.getData().equals(theirs.getData())) {
+ return ours;
+ }
+
+ ObjectLoader lo = reader.open(ours.getData());
+ byte[] sep = new byte[] {'\n'};
+ ObjectLoader lt = reader.open(theirs.getData());
+ UnionInputStream union = new UnionInputStream(
+ lo.openStream(),
+ new ByteArrayInputStream(sep),
+ lt.openStream());
+ ObjectId noteData = inserter.insert(Constants.OBJ_BLOB,
+ lo.getSize() + sep.length + lt.getSize(), union);
+ return new Note(ours, noteData);
+ }
+}