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,
370 Comparator.comparing(ReceiveCommand::getRefName));
371
372 int delta = 0;
373 for (ReceiveCommand c : commands) {
374 switch (c.getType()) {
375 case DELETE:
376 delta--;
377 break;
378 case CREATE:
379 delta++;
380 break;
381 default:
382 }
383 }
384
385 RefList.Builder<Ref> b = new RefList.Builder<>(refs.size() + delta);
386 int refIdx = 0;
387 int cmdIdx = 0;
388 while (refIdx < refs.size() || cmdIdx < commands.size()) {
389 Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null;
390 ReceiveCommand cmd = (cmdIdx < commands.size())
391 ? commands.get(cmdIdx)
392 : null;
393 int cmp = 0;
394 if (ref != null && cmd != null) {
395 cmp = ref.getName().compareTo(cmd.getRefName());
396 } else if (ref == null) {
397 cmp = 1;
398 } else if (cmd == null) {
399 cmp = -1;
400 }
401
402 if (cmp < 0) {
403 b.add(ref);
404 refIdx++;
405 } else if (cmp > 0) {
406 assert cmd != null;
407 if (cmd.getType() != ReceiveCommand.Type.CREATE) {
408 lockFailure(cmd, commands);
409 return null;
410 }
411
412 b.add(peeledRef(walk, cmd));
413 cmdIdx++;
414 } else {
415 assert cmd != null;
416 assert ref != null;
417 if (!cmd.getOldId().equals(ref.getObjectId())) {
418 lockFailure(cmd, commands);
419 return null;
420 }
421
422 if (cmd.getType() != ReceiveCommand.Type.DELETE) {
423 b.add(peeledRef(walk, cmd));
424 }
425 cmdIdx++;
426 refIdx++;
427 }
428 }
429 return b.toRefList();
430 }
431
432 private void writeReflog(List<ReceiveCommand> commands) {
433 PersonIdent ident = getRefLogIdent();
434 if (ident == null) {
435 ident = new PersonIdent(refdb.getRepository());
436 }
437 for (ReceiveCommand cmd : commands) {
438
439 if (cmd.getResult() != ReceiveCommand.Result.OK) {
440 continue;
441 }
442 String name = cmd.getRefName();
443
444 if (cmd.getType() == ReceiveCommand.Type.DELETE) {
445 try {
446 RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
447 } catch (IOException e) {
448
449 }
450 continue;
451 }
452
453 if (isRefLogDisabled(cmd)) {
454 continue;
455 }
456
457 String msg = getRefLogMessage(cmd);
458 if (isRefLogIncludingResult(cmd)) {
459 String strResult = toResultString(cmd);
460 if (strResult != null) {
461 msg = msg.isEmpty()
462 ? strResult : msg + ": " + strResult;
463 }
464 }
465 try {
466 new ReflogWriter(refdb, isForceRefLog(cmd))
467 .log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
468 } catch (IOException e) {
469
470
471
472
473
474
475
476
477
478
479
480
481
482 }
483 }
484 }
485
486 private String toResultString(ReceiveCommand cmd) {
487 switch (cmd.getType()) {
488 case CREATE:
489 return ReflogEntry.PREFIX_CREATED;
490 case UPDATE:
491
492
493
494
495
496
497
498 return isAllowNonFastForwards()
499 ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD;
500 case UPDATE_NONFASTFORWARD:
501 return ReflogEntry.PREFIX_FORCED_UPDATE;
502 default:
503 return null;
504 }
505 }
506
507 private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
508 throws IOException {
509 ObjectId newId = cmd.getNewId().copy();
510 RevObject obj = walk.parseAny(newId);
511 if (obj instanceof RevTag) {
512 return new ObjectIdRef.PeeledTag(
513 Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy());
514 }
515 return new ObjectIdRef.PeeledNonTag(
516 Ref.Storage.PACKED, cmd.getRefName(), newId);
517 }
518
519 private static void unlockAll(@Nullable Map<?, LockFile> locks) {
520 if (locks != null) {
521 locks.values().forEach(LockFile::unlock);
522 }
523 }
524
525 private static void lockFailure(ReceiveCommand cmd,
526 List<ReceiveCommand> commands) {
527 reject(cmd, LOCK_FAILURE, commands);
528 }
529
530 private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
531 List<ReceiveCommand> commands) {
532 reject(cmd, result, null, commands);
533 }
534
535 private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
536 String why, List<ReceiveCommand> commands) {
537 cmd.setResult(result, why);
538 for (ReceiveCommand c2 : commands) {
539 if (c2.getResult() == ReceiveCommand.Result.OK) {
540
541
542 c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
543 }
544 }
545 ReceiveCommand.abort(commands);
546 }
547 }