1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.file;
12
13 import static java.util.stream.Collectors.toList;
14 import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
15 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
16 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
17 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
18
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import org.eclipse.jgit.annotations.Nullable;
30 import org.eclipse.jgit.errors.LockFailedException;
31 import org.eclipse.jgit.errors.MissingObjectException;
32 import org.eclipse.jgit.internal.JGitText;
33 import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
34 import org.eclipse.jgit.lib.BatchRefUpdate;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.lib.ObjectIdRef;
37 import org.eclipse.jgit.lib.PersonIdent;
38 import org.eclipse.jgit.lib.ProgressMonitor;
39 import org.eclipse.jgit.lib.Ref;
40 import org.eclipse.jgit.lib.RefDatabase;
41 import org.eclipse.jgit.lib.ReflogEntry;
42 import org.eclipse.jgit.revwalk.RevObject;
43 import org.eclipse.jgit.revwalk.RevTag;
44 import org.eclipse.jgit.revwalk.RevWalk;
45 import org.eclipse.jgit.transport.ReceiveCommand;
46 import org.eclipse.jgit.util.RefList;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 class PackedBatchRefUpdate extends BatchRefUpdate {
88 private RefDirectory refdb;
89
90 PackedBatchRefUpdate(RefDirectory refdb) {
91 super(refdb);
92 this.refdb = refdb;
93 }
94
95
96 @Override
97 public void execute(RevWalk walk, ProgressMonitor monitor,
98 List<String> options) throws IOException {
99 if (!isAtomic()) {
100
101 super.execute(walk, monitor, options);
102 return;
103 }
104 List<ReceiveCommand> pending =
105 ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
106 if (pending.isEmpty()) {
107 return;
108 }
109 if (pending.size() == 1) {
110
111 super.execute(walk, monitor, options);
112 return;
113 }
114 if (containsSymrefs(pending)) {
115
116 reject(pending.get(0), REJECTED_OTHER_REASON,
117 JGitText.get().atomicSymRefNotSupported, pending);
118 return;
119 }
120
121
122 if (!blockUntilTimestamps(MAX_WAIT)) {
123 return;
124 }
125 if (options != null) {
126 setPushOptions(options);
127 }
128
129
130
131
132 if (!checkConflictingNames(pending)) {
133 return;
134 }
135
136 if (!checkObjectExistence(walk, pending)) {
137 return;
138 }
139
140 if (!checkNonFastForwards(walk, pending)) {
141 return;
142 }
143
144
145
146 try {
147 refdb.pack(
148 pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
149 } catch (LockFailedException e) {
150 lockFailure(pending.get(0), pending);
151 return;
152 }
153
154 Map<String, LockFile> locks = null;
155 refdb.inProcessPackedRefsLock.lock();
156 try {
157 PackedRefList oldPackedList;
158 if (!refdb.isInClone()) {
159 locks = lockLooseRefs(pending);
160 if (locks == null) {
161 return;
162 }
163 oldPackedList = refdb.pack(locks);
164 } else {
165
166
167
168 oldPackedList = refdb.getPackedRefs();
169 }
170 RefList<Ref> newRefs = applyUpdates(walk, oldPackedList, pending);
171 if (newRefs == null) {
172 return;
173 }
174 LockFile packedRefsLock = refdb.lockPackedRefs();
175 if (packedRefsLock == null) {
176 lockFailure(pending.get(0), pending);
177 return;
178 }
179
180 refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList,
181 true);
182 } finally {
183 try {
184 unlockAll(locks);
185 } finally {
186 refdb.inProcessPackedRefsLock.unlock();
187 }
188 }
189
190 refdb.fireRefsChanged();
191 pending.forEach(c -> c.setResult(ReceiveCommand.Result.OK));
192 writeReflog(pending);
193 }
194
195 private static boolean containsSymrefs(List<ReceiveCommand> commands) {
196 for (ReceiveCommand cmd : commands) {
197 if (cmd.getOldSymref() != null || cmd.getNewSymref() != null) {
198 return true;
199 }
200 }
201 return false;
202 }
203
204 private boolean checkConflictingNames(List<ReceiveCommand> commands)
205 throws IOException {
206 Set<String> takenNames = new HashSet<>();
207 Set<String> takenPrefixes = new HashSet<>();
208 Set<String> deletes = new HashSet<>();
209 for (ReceiveCommand cmd : commands) {
210 if (cmd.getType() != ReceiveCommand.Type.DELETE) {
211 takenNames.add(cmd.getRefName());
212 addPrefixesTo(cmd.getRefName(), takenPrefixes);
213 } else {
214 deletes.add(cmd.getRefName());
215 }
216 }
217 Set<String> initialRefs = refdb.getRefs(RefDatabase.ALL).keySet();
218 for (String name : initialRefs) {
219 if (!deletes.contains(name)) {
220 takenNames.add(name);
221 addPrefixesTo(name, takenPrefixes);
222 }
223 }
224
225 for (ReceiveCommand cmd : commands) {
226 if (cmd.getType() != ReceiveCommand.Type.DELETE &&
227 takenPrefixes.contains(cmd.getRefName())) {
228
229
230
231 lockFailure(cmd, commands);
232 return false;
233 }
234 for (String prefix : getPrefixes(cmd.getRefName())) {
235 if (takenNames.contains(prefix)) {
236
237
238
239
240 lockFailure(cmd, commands);
241 return false;
242 }
243 }
244 }
245 return true;
246 }
247
248 private boolean checkObjectExistence(RevWalk walk,
249 List<ReceiveCommand> commands) throws IOException {
250 for (ReceiveCommand cmd : commands) {
251 try {
252 if (!cmd.getNewId().equals(ObjectId.zeroId())) {
253 walk.parseAny(cmd.getNewId());
254 }
255 } catch (MissingObjectException e) {
256
257
258
259
260 reject(cmd, ReceiveCommand.Result.REJECTED_MISSING_OBJECT, commands);
261 return false;
262 }
263 }
264 return true;
265 }
266
267 private boolean checkNonFastForwards(RevWalk walk,
268 List<ReceiveCommand> commands) throws IOException {
269 if (isAllowNonFastForwards()) {
270 return true;
271 }
272 for (ReceiveCommand cmd : commands) {
273 cmd.updateType(walk);
274 if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
275 reject(cmd, REJECTED_NONFASTFORWARD, commands);
276 return false;
277 }
278 }
279 return true;
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 @Nullable
297 private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
298 throws IOException {
299 ReceiveCommand failed = null;
300 Map<String, LockFile> locks = new HashMap<>();
301 try {
302 RETRY: for (int ms : refdb.getRetrySleepMs()) {
303 failed = null;
304
305 unlockAll(locks);
306 locks.clear();
307 RefDirectory.sleep(ms);
308
309 for (ReceiveCommand c : commands) {
310 String name = c.getRefName();
311 LockFile lock = new LockFile(refdb.fileFor(name));
312 if (locks.put(name, lock) != null) {
313 throw new IOException(
314 MessageFormat.format(JGitText.get().duplicateRef, name));
315 }
316 if (!lock.lock()) {
317 failed = c;
318 continue RETRY;
319 }
320 }
321 Map<String, LockFile> result = locks;
322 locks = null;
323 return result;
324 }
325 } finally {
326 unlockAll(locks);
327 }
328 lockFailure(failed != null ? failed : commands.get(0), commands);
329 return null;
330 }
331
332 private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
333 List<ReceiveCommand> commands) throws IOException {
334
335
336 Collections.sort(commands,
337 Comparator.comparing(ReceiveCommand::getRefName));
338
339 int delta = 0;
340 for (ReceiveCommand c : commands) {
341 switch (c.getType()) {
342 case DELETE:
343 delta--;
344 break;
345 case CREATE:
346 delta++;
347 break;
348 default:
349 }
350 }
351
352 RefList.Builder<Ref> b = new RefList.Builder<>(refs.size() + delta);
353 int refIdx = 0;
354 int cmdIdx = 0;
355 while (refIdx < refs.size() || cmdIdx < commands.size()) {
356 Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null;
357 ReceiveCommand cmd = (cmdIdx < commands.size())
358 ? commands.get(cmdIdx)
359 : null;
360 int cmp = 0;
361 if (ref != null && cmd != null) {
362 cmp = ref.getName().compareTo(cmd.getRefName());
363 } else if (ref == null) {
364 cmp = 1;
365 } else if (cmd == null) {
366 cmp = -1;
367 }
368
369 if (cmp < 0) {
370 b.add(ref);
371 refIdx++;
372 } else if (cmp > 0) {
373 assert cmd != null;
374 if (cmd.getType() != ReceiveCommand.Type.CREATE) {
375 lockFailure(cmd, commands);
376 return null;
377 }
378
379 b.add(peeledRef(walk, cmd));
380 cmdIdx++;
381 } else {
382 assert cmd != null;
383 assert ref != null;
384 if (!cmd.getOldId().equals(ref.getObjectId())) {
385 lockFailure(cmd, commands);
386 return null;
387 }
388
389 if (cmd.getType() != ReceiveCommand.Type.DELETE) {
390 b.add(peeledRef(walk, cmd));
391 }
392 cmdIdx++;
393 refIdx++;
394 }
395 }
396 return b.toRefList();
397 }
398
399 private void writeReflog(List<ReceiveCommand> commands) {
400 PersonIdent ident = getRefLogIdent();
401 if (ident == null) {
402 ident = new PersonIdent(refdb.getRepository());
403 }
404 for (ReceiveCommand cmd : commands) {
405
406 if (cmd.getResult() != ReceiveCommand.Result.OK) {
407 continue;
408 }
409 String name = cmd.getRefName();
410
411 if (cmd.getType() == ReceiveCommand.Type.DELETE) {
412 try {
413 RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
414 } catch (IOException e) {
415
416 }
417 continue;
418 }
419
420 if (isRefLogDisabled(cmd)) {
421 continue;
422 }
423
424 String msg = getRefLogMessage(cmd);
425 if (isRefLogIncludingResult(cmd)) {
426 String strResult = toResultString(cmd);
427 if (strResult != null) {
428 msg = msg.isEmpty()
429 ? strResult : msg + ": " + strResult;
430 }
431 }
432 try {
433 new ReflogWriter(refdb, isForceRefLog(cmd))
434 .log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
435 } catch (IOException e) {
436
437
438
439
440
441
442
443
444
445
446
447
448
449 }
450 }
451 }
452
453 private String toResultString(ReceiveCommand cmd) {
454 switch (cmd.getType()) {
455 case CREATE:
456 return ReflogEntry.PREFIX_CREATED;
457 case UPDATE:
458
459
460
461
462
463
464
465 return isAllowNonFastForwards()
466 ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD;
467 case UPDATE_NONFASTFORWARD:
468 return ReflogEntry.PREFIX_FORCED_UPDATE;
469 default:
470 return null;
471 }
472 }
473
474 private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
475 throws IOException {
476 ObjectId newId = cmd.getNewId().copy();
477 RevObject obj = walk.parseAny(newId);
478 if (obj instanceof RevTag) {
479 return new ObjectIdRef.PeeledTag(
480 Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy());
481 }
482 return new ObjectIdRef.PeeledNonTag(
483 Ref.Storage.PACKED, cmd.getRefName(), newId);
484 }
485
486 private static void unlockAll(@Nullable Map<?, LockFile> locks) {
487 if (locks != null) {
488 locks.values().forEach(LockFile::unlock);
489 }
490 }
491
492 private static void lockFailure(ReceiveCommand cmd,
493 List<ReceiveCommand> commands) {
494 reject(cmd, LOCK_FAILURE, commands);
495 }
496
497 private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
498 List<ReceiveCommand> commands) {
499 reject(cmd, result, null, commands);
500 }
501
502 private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
503 String why, List<ReceiveCommand> commands) {
504 cmd.setResult(result, why);
505 for (ReceiveCommand c2 : commands) {
506 if (c2.getResult() == ReceiveCommand.Result.OK) {
507
508
509 c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
510 }
511 }
512 ReceiveCommand.abort(commands);
513 }
514 }