1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.reftable;
12
13 import org.eclipse.jgit.annotations.Nullable;
14 import org.eclipse.jgit.errors.MissingObjectException;
15 import org.eclipse.jgit.internal.JGitText;
16 import org.eclipse.jgit.lib.AnyObjectId;
17 import org.eclipse.jgit.lib.BatchRefUpdate;
18 import org.eclipse.jgit.lib.ObjectId;
19 import org.eclipse.jgit.lib.ObjectIdRef;
20 import org.eclipse.jgit.lib.PersonIdent;
21 import org.eclipse.jgit.lib.ProgressMonitor;
22 import org.eclipse.jgit.lib.Ref;
23 import org.eclipse.jgit.lib.RefDatabase;
24 import org.eclipse.jgit.lib.ReflogEntry;
25 import org.eclipse.jgit.lib.Repository;
26 import org.eclipse.jgit.lib.SymbolicRef;
27 import org.eclipse.jgit.revwalk.RevObject;
28 import org.eclipse.jgit.revwalk.RevTag;
29 import org.eclipse.jgit.revwalk.RevWalk;
30 import org.eclipse.jgit.transport.ReceiveCommand;
31
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeSet;
40 import java.util.concurrent.locks.Lock;
41 import java.util.stream.Collectors;
42
43 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
44 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
45
46 import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
47 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
48 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
49 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
50 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
51 import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
52 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
53
54
55
56
57 public abstract class ReftableBatchRefUpdate extends BatchRefUpdate {
58 private final Lock lock;
59
60 private final ReftableDatabase refDb;
61
62 private final Repository repository;
63
64
65
66
67
68
69
70
71
72
73
74
75
76 protected ReftableBatchRefUpdate(RefDatabase refdb, ReftableDatabase reftableDb, Lock lock,
77 Repository repository) {
78 super(refdb);
79 this.refDb = reftableDb;
80 this.lock = lock;
81 this.repository = repository;
82 }
83
84
85 @Override
86 public void execute(RevWalk rw, ProgressMonitor pm, List<String> options) {
87 List<ReceiveCommand> pending = getPending();
88 if (pending.isEmpty()) {
89 return;
90 }
91 if (options != null) {
92 setPushOptions(options);
93 }
94 try {
95 if (!checkObjectExistence(rw, pending)) {
96 return;
97 }
98
99
100
101 pending = getPending();
102 if (!checkNonFastForwards(rw, pending)) {
103 return;
104 }
105 pending = getPending();
106
107 lock.lock();
108 try {
109 if (!checkExpected(pending)) {
110 return;
111 }
112 pending = getPending();
113 if (!checkConflicting(pending)) {
114 return;
115 }
116 pending = getPending();
117 if (!blockUntilTimestamps(MAX_WAIT)) {
118 return;
119 }
120
121 List<Ref> newRefs = toNewRefs(rw, pending);
122 applyUpdates(newRefs, pending);
123 for (ReceiveCommand cmd : pending) {
124 if (cmd.getResult() == NOT_ATTEMPTED) {
125
126 cmd.setResult(OK);
127 }
128 }
129 } finally {
130 lock.unlock();
131 }
132 } catch (IOException e) {
133 pending.get(0).setResult(LOCK_FAILURE, "io error");
134 ReceiveCommand.abort(pending);
135 }
136 }
137
138
139
140
141
142
143
144
145
146
147
148 protected abstract void applyUpdates(List<Ref> newRefs,
149 List<ReceiveCommand> pending) throws IOException;
150
151 private List<ReceiveCommand> getPending() {
152 return ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
153 }
154
155 private boolean checkObjectExistence(RevWalk rw,
156 List<ReceiveCommand> pending) throws IOException {
157 for (ReceiveCommand cmd : pending) {
158 try {
159 if (!cmd.getNewId().equals(ObjectId.zeroId())) {
160 rw.parseAny(cmd.getNewId());
161 }
162 } catch (MissingObjectException e) {
163
164
165
166
167 cmd.setResult(REJECTED_MISSING_OBJECT);
168 if (isAtomic()) {
169 ReceiveCommand.abort(pending);
170 return false;
171 }
172 }
173 }
174 return true;
175 }
176
177 private boolean checkNonFastForwards(RevWalk rw,
178 List<ReceiveCommand> pending) throws IOException {
179 if (isAllowNonFastForwards()) {
180 return true;
181 }
182 for (ReceiveCommand cmd : pending) {
183 cmd.updateType(rw);
184 if (cmd.getType() == UPDATE_NONFASTFORWARD) {
185 cmd.setResult(REJECTED_NONFASTFORWARD);
186 if (isAtomic()) {
187 ReceiveCommand.abort(pending);
188 return false;
189 }
190 }
191 }
192 return true;
193 }
194
195 private boolean checkConflicting(List<ReceiveCommand> pending)
196 throws IOException {
197 TreeSet<String> added = new TreeSet<>();
198 Set<String> deleted =
199 pending.stream()
200 .filter(cmd -> cmd.getType() == DELETE)
201 .map(c -> c.getRefName())
202 .collect(Collectors.toSet());
203
204 boolean ok = true;
205 for (ReceiveCommand cmd : pending) {
206 if (cmd.getType() == DELETE) {
207 continue;
208 }
209
210 String name = cmd.getRefName();
211 if (refDb.isNameConflicting(name, added, deleted)) {
212 if (isAtomic()) {
213 cmd.setResult(
214 ReceiveCommand.Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
215 } else {
216 cmd.setResult(LOCK_FAILURE);
217 }
218
219 ok = false;
220 }
221 added.add(name);
222 }
223
224 if (isAtomic()) {
225 if (!ok) {
226 pending.stream()
227 .filter(cmd -> cmd.getResult() == NOT_ATTEMPTED)
228 .forEach(cmd -> cmd.setResult(LOCK_FAILURE));
229 }
230 return ok;
231 }
232
233 for (ReceiveCommand cmd : pending) {
234 if (cmd.getResult() == NOT_ATTEMPTED) {
235 return true;
236 }
237 }
238
239 return false;
240 }
241
242 private boolean checkExpected(List<ReceiveCommand> pending)
243 throws IOException {
244 for (ReceiveCommand cmd : pending) {
245 if (!matchOld(cmd, refDb.exactRef(cmd.getRefName()))) {
246 cmd.setResult(LOCK_FAILURE);
247 if (isAtomic()) {
248 ReceiveCommand.abort(pending);
249 return false;
250 }
251 }
252 }
253 return true;
254 }
255
256 private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) {
257 if (ref == null) {
258 return AnyObjectId.isEqual(ObjectId.zeroId(), cmd.getOldId())
259 && cmd.getOldSymref() == null;
260 } else if (ref.isSymbolic()) {
261 return ref.getTarget().getName().equals(cmd.getOldSymref());
262 }
263 ObjectId id = ref.getObjectId();
264 if (id == null) {
265 id = ObjectId.zeroId();
266 }
267 return cmd.getOldId().equals(id);
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282 protected void write(ReftableWriter writer, List<Ref> newRefs,
283 List<ReceiveCommand> pending) throws IOException {
284 long updateIndex = refDb.nextUpdateIndex();
285 writer.setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex)
286 .begin().sortAndWriteRefs(newRefs);
287 if (!isRefLogDisabled()) {
288 writeLog(writer, updateIndex, pending);
289 }
290 }
291
292 private void writeLog(ReftableWriter writer, long updateIndex,
293 List<ReceiveCommand> pending) throws IOException {
294 Map<String, ReceiveCommand> cmds = new HashMap<>();
295 List<String> byName = new ArrayList<>(pending.size());
296 for (ReceiveCommand cmd : pending) {
297 cmds.put(cmd.getRefName(), cmd);
298 byName.add(cmd.getRefName());
299 }
300 Collections.sort(byName);
301
302 PersonIdent ident = getRefLogIdent();
303 if (ident == null) {
304 ident = new PersonIdent(repository);
305 }
306 for (String name : byName) {
307 ReceiveCommand cmd = cmds.get(name);
308 if (isRefLogDisabled(cmd)) {
309 continue;
310 }
311 String msg = getRefLogMessage(cmd);
312 if (isRefLogIncludingResult(cmd)) {
313 String strResult = toResultString(cmd);
314 if (strResult != null) {
315 msg = msg.isEmpty() ? strResult : msg + ": " + strResult;
316 }
317 }
318 writer.writeLog(name, updateIndex, ident, cmd.getOldId(),
319 cmd.getNewId(), msg);
320 }
321 }
322
323 private String toResultString(ReceiveCommand cmd) {
324 switch (cmd.getType()) {
325 case CREATE:
326 return ReflogEntry.PREFIX_CREATED;
327 case UPDATE:
328
329
330
331
332
333
334
335
336
337 return isAllowNonFastForwards() ? ReflogEntry.PREFIX_FORCED_UPDATE
338 : ReflogEntry.PREFIX_FAST_FORWARD;
339 case UPDATE_NONFASTFORWARD:
340 return ReflogEntry.PREFIX_FORCED_UPDATE;
341 default:
342 return null;
343 }
344 }
345
346
347 private static List<Ref> toNewRefs(RevWalk rw, List<ReceiveCommand> pending)
348 throws IOException {
349 List<Ref> refs = new ArrayList<>(pending.size());
350 for (ReceiveCommand cmd : pending) {
351 if (cmd.getResult() != NOT_ATTEMPTED) {
352 continue;
353 }
354
355 String name = cmd.getRefName();
356 ObjectId newId = cmd.getNewId();
357 String newSymref = cmd.getNewSymref();
358 if (AnyObjectId.isEqual(ObjectId.zeroId(), newId)
359 && newSymref == null) {
360 refs.add(new ObjectIdRef.Unpeeled(NEW, name, null));
361 continue;
362 } else if (newSymref != null) {
363 refs.add(new SymbolicRef(name,
364 new ObjectIdRef.Unpeeled(NEW, newSymref, null)));
365 continue;
366 }
367
368 RevObject obj = rw.parseAny(newId);
369 RevObject peel = null;
370 if (obj instanceof RevTag) {
371 peel = rw.peel(obj);
372 }
373 if (peel != null) {
374 refs.add(new ObjectIdRef.PeeledTag(PACKED, name, newId,
375 peel.copy()));
376 } else {
377 refs.add(new ObjectIdRef.PeeledNonTag(PACKED, name, newId));
378 }
379 }
380 return refs;
381 }
382 }