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