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>
This commit is contained in:
Shawn O. Pearce 2011-05-20 13:10:13 -07:00
parent a83bb1c344
commit 1b8810f411
9 changed files with 524 additions and 83 deletions

View File

@ -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]

View File

@ -18,6 +18,9 @@ link:pgm-daemon.html[daemon]::
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.

View File

@ -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();
}
}
}
}

View File

@ -29,7 +29,7 @@ import java.util.ArrayList;
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

View File

@ -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 @@ public class CodeReviewNoteCreationException extends Exception {
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);
}
}

View File

@ -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.gwtorm.client.ResultSet;
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.Ref;
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 org.eclipse.jgit.revwalk.RevWalk;
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 @@ import java.util.List;
*/
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 @@ public class CreateCodeReviewNotes {
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 @@ public class CreateCodeReviewNotes {
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 @@ public class CreateCodeReviewNotes {
}
}
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 @@ public class CreateCodeReviewNotes {
}
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) {

View File

@ -31,6 +31,9 @@ import java.io.IOException;
* 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";

View File

@ -992,14 +992,15 @@ public class MergeOp {
}
}
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) {

View File

@ -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);
}
}