Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.git;
16 :
17 : import static com.google.common.base.MoreObjects.firstNonNull;
18 :
19 : import com.google.gerrit.entities.Project;
20 : import com.google.gerrit.git.LockFailureException;
21 : import com.google.gerrit.git.RefUpdateUtil;
22 : import com.google.gerrit.server.GerritPersonIdent;
23 : import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
24 : import com.google.inject.Inject;
25 : import com.google.inject.assistedinject.Assisted;
26 : import java.io.IOException;
27 : import org.eclipse.jgit.lib.BatchRefUpdate;
28 : import org.eclipse.jgit.lib.CommitBuilder;
29 : import org.eclipse.jgit.lib.ObjectId;
30 : import org.eclipse.jgit.lib.ObjectInserter;
31 : import org.eclipse.jgit.lib.ObjectReader;
32 : import org.eclipse.jgit.lib.PersonIdent;
33 : import org.eclipse.jgit.lib.Ref;
34 : import org.eclipse.jgit.lib.Repository;
35 : import org.eclipse.jgit.notes.Note;
36 : import org.eclipse.jgit.notes.NoteMap;
37 : import org.eclipse.jgit.notes.NoteMerger;
38 : import org.eclipse.jgit.revwalk.RevCommit;
39 : import org.eclipse.jgit.revwalk.RevWalk;
40 : import org.eclipse.jgit.transport.ReceiveCommand;
41 :
42 : /** A utility class for updating a notes branch with automatic merge of note trees. */
43 : public class NotesBranchUtil {
44 : public interface Factory {
45 : NotesBranchUtil create(Project.NameKey project, Repository db, ObjectInserter inserter);
46 : }
47 :
48 : private final PersonIdent gerritIdent;
49 : private final GitReferenceUpdated gitRefUpdated;
50 : private final Project.NameKey project;
51 : private final Repository db;
52 : private final ObjectInserter inserter;
53 :
54 : private RevCommit baseCommit;
55 : private NoteMap base;
56 :
57 : private RevCommit oursCommit;
58 : private NoteMap ours;
59 :
60 : private RevWalk revWalk;
61 : private ObjectReader reader;
62 : private boolean overwrite;
63 :
64 : private ReviewNoteMerger noteMerger;
65 :
66 : @Inject
67 : public NotesBranchUtil(
68 : @GerritPersonIdent PersonIdent gerritIdent,
69 : GitReferenceUpdated gitRefUpdated,
70 : @Assisted Project.NameKey project,
71 : @Assisted Repository db,
72 2 : @Assisted ObjectInserter inserter) {
73 2 : this.gerritIdent = gerritIdent;
74 2 : this.gitRefUpdated = gitRefUpdated;
75 2 : this.project = project;
76 2 : this.db = db;
77 2 : this.inserter = inserter;
78 2 : }
79 :
80 : /**
81 : * Create a new commit in the {@code notesBranch} by updating existing or creating new notes from
82 : * the {@code notes} map.
83 : *
84 : * <p>Does not retry in the case of lock failure; callers may use {@link
85 : * com.google.gerrit.server.update.RetryHelper}.
86 : *
87 : * @param notes map of notes
88 : * @param notesBranch notes branch to update
89 : * @param commitAuthor author of the commit in the notes branch
90 : * @param commitMessage for the commit in the notes branch
91 : * @throws LockFailureException if committing the notes failed due to a lock failure on the notes
92 : * branch
93 : * @throws IOException if committing the notes failed for any other reason
94 : */
95 : public final void commitAllNotes(
96 : NoteMap notes, String notesBranch, PersonIdent commitAuthor, String commitMessage)
97 : throws IOException {
98 0 : this.overwrite = true;
99 0 : commitNotes(notes, notesBranch, commitAuthor, commitMessage);
100 0 : }
101 :
102 : /**
103 : * Create a new commit in the {@code notesBranch} by creating not yet existing notes from the
104 : * {@code notes} map. The notes from the {@code notes} map which already exist in the note-tree of
105 : * the tip of the {@code notesBranch} will not be updated.
106 : *
107 : * <p>Does not retry in the case of lock failure; callers may use {@link
108 : * com.google.gerrit.server.update.RetryHelper}.
109 : *
110 : * @param notes map of notes
111 : * @param notesBranch notes branch to update
112 : * @param commitAuthor author of the commit in the notes branch
113 : * @param commitMessage for the commit in the notes branch
114 : * @return map with those notes from the {@code notes} that were newly created
115 : * @throws LockFailureException if committing the notes failed due to a lock failure on the notes
116 : * branch
117 : * @throws IOException if committing the notes failed for any other reason
118 : */
119 : public final NoteMap commitNewNotes(
120 : NoteMap notes, String notesBranch, PersonIdent commitAuthor, String commitMessage)
121 : throws IOException {
122 2 : this.overwrite = false;
123 2 : commitNotes(notes, notesBranch, commitAuthor, commitMessage);
124 2 : NoteMap newlyCreated = NoteMap.newEmptyMap();
125 2 : for (Note n : notes) {
126 2 : if (base == null || !base.contains(n)) {
127 2 : newlyCreated.set(n, n.getData());
128 : }
129 2 : }
130 2 : return newlyCreated;
131 : }
132 :
133 : private void commitNotes(
134 : NoteMap notes, String notesBranch, PersonIdent commitAuthor, String commitMessage)
135 : throws LockFailureException, IOException {
136 : try {
137 2 : revWalk = new RevWalk(db);
138 2 : reader = db.newObjectReader();
139 2 : loadBase(notesBranch);
140 2 : if (overwrite) {
141 0 : addAllNotes(notes);
142 : } else {
143 2 : addNewNotes(notes);
144 : }
145 2 : if (base != null) {
146 1 : oursCommit = createCommit(ours, commitAuthor, commitMessage, baseCommit);
147 : } else {
148 2 : oursCommit = createCommit(ours, commitAuthor, commitMessage);
149 : }
150 2 : updateRef(notesBranch);
151 : } finally {
152 2 : revWalk.close();
153 2 : reader.close();
154 : }
155 2 : }
156 :
157 : private void addNewNotes(NoteMap notes) throws IOException {
158 2 : for (Note n : notes) {
159 2 : if (!ours.contains(n)) {
160 2 : ours.set(n, n.getData());
161 : }
162 2 : }
163 2 : }
164 :
165 : private void addAllNotes(NoteMap notes) throws IOException {
166 0 : for (Note n : notes) {
167 0 : if (ours.contains(n)) {
168 : // Merge the existing and the new note as if they are both new,
169 : // means: base == null
170 : // There is no really a common ancestry for these two note revisions
171 0 : ObjectId noteContent =
172 0 : getNoteMerger().merge(null, n, ours.getNote(n), reader, inserter).getData();
173 0 : ours.set(n, noteContent);
174 0 : } else {
175 0 : ours.set(n, n.getData());
176 : }
177 0 : }
178 0 : }
179 :
180 : private NoteMerger getNoteMerger() {
181 0 : if (noteMerger == null) {
182 0 : noteMerger = new ReviewNoteMerger();
183 : }
184 0 : return noteMerger;
185 : }
186 :
187 : private void loadBase(String notesBranch) throws IOException {
188 2 : Ref branch = db.getRefDatabase().exactRef(notesBranch);
189 2 : if (branch != null) {
190 1 : baseCommit = revWalk.parseCommit(branch.getObjectId());
191 1 : base = NoteMap.read(revWalk.getObjectReader(), baseCommit);
192 : }
193 2 : if (baseCommit != null) {
194 1 : ours = NoteMap.read(revWalk.getObjectReader(), baseCommit);
195 : } else {
196 2 : ours = NoteMap.newEmptyMap();
197 : }
198 2 : }
199 :
200 : private RevCommit createCommit(
201 : NoteMap map, PersonIdent author, String message, RevCommit... parents) throws IOException {
202 2 : CommitBuilder b = new CommitBuilder();
203 2 : b.setTreeId(map.writeTree(inserter));
204 2 : b.setAuthor(author != null ? author : gerritIdent);
205 2 : b.setCommitter(gerritIdent);
206 2 : if (parents.length > 0) {
207 1 : b.setParentIds(parents);
208 : }
209 2 : b.setMessage(message);
210 2 : ObjectId commitId = inserter.insert(b);
211 2 : inserter.flush();
212 2 : return revWalk.parseCommit(commitId);
213 : }
214 :
215 : private void updateRef(String notesBranch) throws LockFailureException, IOException {
216 2 : if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) {
217 : // If the trees are identical, there is no change in the notes.
218 : // Avoid saving this commit as it has no new information.
219 1 : return;
220 : }
221 2 : BatchRefUpdate bru = db.getRefDatabase().newBatchUpdate();
222 2 : bru.addCommand(
223 2 : new ReceiveCommand(firstNonNull(baseCommit, ObjectId.zeroId()), oursCommit, notesBranch));
224 2 : RefUpdateUtil.executeChecked(bru, revWalk);
225 2 : gitRefUpdated.fire(project, bru, null);
226 2 : }
227 : }
|