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