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.dfs;
45
46 import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
47 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
48 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
49 import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
50 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
51 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
52 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
53 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
54 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
55
56 import java.io.ByteArrayOutputStream;
57 import java.io.IOException;
58 import java.io.OutputStream;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Set;
66 import java.util.concurrent.locks.ReentrantLock;
67
68 import org.eclipse.jgit.annotations.Nullable;
69 import org.eclipse.jgit.errors.MissingObjectException;
70 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
71 import org.eclipse.jgit.internal.storage.io.BlockSource;
72 import org.eclipse.jgit.internal.storage.pack.PackExt;
73 import org.eclipse.jgit.internal.storage.reftable.RefCursor;
74 import org.eclipse.jgit.internal.storage.reftable.Reftable;
75 import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
76 import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
77 import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
78 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
79 import org.eclipse.jgit.lib.AnyObjectId;
80 import org.eclipse.jgit.lib.BatchRefUpdate;
81 import org.eclipse.jgit.lib.ObjectId;
82 import org.eclipse.jgit.lib.ObjectIdRef;
83 import org.eclipse.jgit.lib.PersonIdent;
84 import org.eclipse.jgit.lib.ProgressMonitor;
85 import org.eclipse.jgit.lib.Ref;
86 import org.eclipse.jgit.lib.ReflogEntry;
87 import org.eclipse.jgit.lib.SymbolicRef;
88 import org.eclipse.jgit.revwalk.RevObject;
89 import org.eclipse.jgit.revwalk.RevTag;
90 import org.eclipse.jgit.revwalk.RevWalk;
91 import org.eclipse.jgit.transport.ReceiveCommand;
92
93
94
95
96
97 public class ReftableBatchRefUpdate extends BatchRefUpdate {
98 private static final int AVG_BYTES = 36;
99
100 private final DfsReftableDatabase refdb;
101
102 private final DfsObjDatabase odb;
103
104 private final ReentrantLock lock;
105
106 private final ReftableConfig reftableConfig;
107
108
109
110
111
112
113
114
115
116 protected ReftableBatchRefUpdate(DfsReftableDatabase refdb,
117 DfsObjDatabase odb) {
118 super(refdb);
119 this.refdb = refdb;
120 this.odb = odb;
121 lock = refdb.getLock();
122 reftableConfig = refdb.getReftableConfig();
123 }
124
125
126 @Override
127 public void execute(RevWalk rw, ProgressMonitor pm, List<String> options) {
128 List<ReceiveCommand> pending = getPending();
129 if (pending.isEmpty()) {
130 return;
131 }
132 if (options != null) {
133 setPushOptions(options);
134 }
135 try {
136 if (!checkObjectExistence(rw, pending)) {
137 return;
138 }
139 if (!checkNonFastForwards(rw, pending)) {
140 return;
141 }
142
143 lock.lock();
144 try {
145 Reftable table = refdb.reader();
146 if (!checkExpected(table, pending)) {
147 return;
148 }
149 if (!checkConflicting(pending)) {
150 return;
151 }
152 if (!blockUntilTimestamps(MAX_WAIT)) {
153 return;
154 }
155 applyUpdates(rw, pending);
156 for (ReceiveCommand cmd : pending) {
157 cmd.setResult(OK);
158 }
159 } finally {
160 lock.unlock();
161 }
162 } catch (IOException e) {
163 pending.get(0).setResult(LOCK_FAILURE, "io error");
164 ReceiveCommand.abort(pending);
165 }
166 }
167
168 private List<ReceiveCommand> getPending() {
169 return ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
170 }
171
172 private boolean checkObjectExistence(RevWalk rw,
173 List<ReceiveCommand> pending) throws IOException {
174 for (ReceiveCommand cmd : pending) {
175 try {
176 if (!cmd.getNewId().equals(ObjectId.zeroId())) {
177 rw.parseAny(cmd.getNewId());
178 }
179 } catch (MissingObjectException e) {
180
181
182
183
184 cmd.setResult(REJECTED_MISSING_OBJECT);
185 ReceiveCommand.abort(pending);
186 return false;
187 }
188 }
189 return true;
190 }
191
192 private boolean checkNonFastForwards(RevWalk rw,
193 List<ReceiveCommand> pending) throws IOException {
194 if (isAllowNonFastForwards()) {
195 return true;
196 }
197 for (ReceiveCommand cmd : pending) {
198 cmd.updateType(rw);
199 if (cmd.getType() == UPDATE_NONFASTFORWARD) {
200 cmd.setResult(REJECTED_NONFASTFORWARD);
201 ReceiveCommand.abort(pending);
202 return false;
203 }
204 }
205 return true;
206 }
207
208 private boolean checkConflicting(List<ReceiveCommand> pending)
209 throws IOException {
210 Set<String> names = new HashSet<>();
211 for (ReceiveCommand cmd : pending) {
212 names.add(cmd.getRefName());
213 }
214
215 boolean ok = true;
216 for (ReceiveCommand cmd : pending) {
217 String name = cmd.getRefName();
218 if (refdb.isNameConflicting(name)) {
219 cmd.setResult(LOCK_FAILURE);
220 ok = false;
221 } else {
222 int s = name.lastIndexOf('/');
223 while (0 < s) {
224 if (names.contains(name.substring(0, s))) {
225 cmd.setResult(LOCK_FAILURE);
226 ok = false;
227 break;
228 }
229 s = name.lastIndexOf('/', s - 1);
230 }
231 }
232 }
233 if (!ok && isAtomic()) {
234 ReceiveCommand.abort(pending);
235 return false;
236 }
237 return ok;
238 }
239
240 private boolean checkExpected(Reftable table, List<ReceiveCommand> pending)
241 throws IOException {
242 for (ReceiveCommand cmd : pending) {
243 Ref ref;
244 try (RefCursor rc = table.seekRef(cmd.getRefName())) {
245 ref = rc.next() ? rc.getRef() : null;
246 }
247 if (!matchOld(cmd, ref)) {
248 cmd.setResult(LOCK_FAILURE);
249 if (isAtomic()) {
250 ReceiveCommand.abort(pending);
251 return false;
252 }
253 }
254 }
255 return true;
256 }
257
258 private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) {
259 if (ref == null) {
260 return AnyObjectId.equals(ObjectId.zeroId(), cmd.getOldId())
261 && cmd.getOldSymref() == null;
262 } else if (ref.isSymbolic()) {
263 return ref.getTarget().getName().equals(cmd.getOldSymref());
264 }
265 ObjectId id = ref.getObjectId();
266 if (id == null) {
267 id = ObjectId.zeroId();
268 }
269 return cmd.getOldId().equals(id);
270 }
271
272 private void applyUpdates(RevWalk rw, List<ReceiveCommand> pending)
273 throws IOException {
274 List<Ref> newRefs = toNewRefs(rw, pending);
275 long updateIndex = nextUpdateIndex();
276 Set<DfsPackDescription> prune = Collections.emptySet();
277 DfsPackDescription pack = odb.newPack(PackSource.INSERT);
278 try (DfsOutputStream out = odb.writeFile(pack, REFTABLE)) {
279 ReftableConfig cfg = DfsPackCompactor
280 .configureReftable(reftableConfig, out);
281
282 ReftableWriter.Stats stats;
283 if (refdb.compactDuringCommit()
284 && newRefs.size() * AVG_BYTES <= cfg.getRefBlockSize()
285 && canCompactTopOfStack(cfg)) {
286 ByteArrayOutputStream tmp = new ByteArrayOutputStream();
287 write(tmp, cfg, updateIndex, newRefs, pending);
288 stats = compactTopOfStack(out, cfg, tmp.toByteArray());
289 prune = toPruneTopOfStack();
290 } else {
291 stats = write(out, cfg, updateIndex, newRefs, pending);
292 }
293 pack.addFileExt(REFTABLE);
294 pack.setReftableStats(stats);
295 }
296
297 odb.commitPack(Collections.singleton(pack), prune);
298 odb.addReftable(pack, prune);
299 refdb.clearCache();
300 }
301
302 private ReftableWriter.Stats write(OutputStream os, ReftableConfig cfg,
303 long updateIndex, List<Ref> newRefs, List<ReceiveCommand> pending)
304 throws IOException {
305 ReftableWriter writer = new ReftableWriter(cfg)
306 .setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex)
307 .begin(os).sortAndWriteRefs(newRefs);
308 if (!isRefLogDisabled()) {
309 writeLog(writer, updateIndex, pending);
310 }
311 writer.finish();
312 return writer.getStats();
313 }
314
315 private void writeLog(ReftableWriter writer, long updateIndex,
316 List<ReceiveCommand> pending) throws IOException {
317 Map<String, ReceiveCommand> cmds = new HashMap<>();
318 List<String> byName = new ArrayList<>(pending.size());
319 for (ReceiveCommand cmd : pending) {
320 cmds.put(cmd.getRefName(), cmd);
321 byName.add(cmd.getRefName());
322 }
323 Collections.sort(byName);
324
325 PersonIdent ident = getRefLogIdent();
326 if (ident == null) {
327 ident = new PersonIdent(refdb.getRepository());
328 }
329 for (String name : byName) {
330 ReceiveCommand cmd = cmds.get(name);
331 if (isRefLogDisabled(cmd)) {
332 continue;
333 }
334 String msg = getRefLogMessage(cmd);
335 if (isRefLogIncludingResult(cmd)) {
336 String strResult = toResultString(cmd);
337 if (strResult != null) {
338 msg = msg.isEmpty() ? strResult : msg + ": " + strResult;
339 }
340 }
341 writer.writeLog(name, updateIndex, ident, cmd.getOldId(),
342 cmd.getNewId(), msg);
343 }
344 }
345
346 private String toResultString(ReceiveCommand cmd) {
347 switch (cmd.getType()) {
348 case CREATE:
349 return ReflogEntry.PREFIX_CREATED;
350 case UPDATE:
351
352
353
354
355
356
357
358
359
360 return isAllowNonFastForwards() ? ReflogEntry.PREFIX_FORCED_UPDATE
361 : ReflogEntry.PREFIX_FAST_FORWARD;
362 case UPDATE_NONFASTFORWARD:
363 return ReflogEntry.PREFIX_FORCED_UPDATE;
364 default:
365 return null;
366 }
367 }
368
369 private static List<Ref> toNewRefs(RevWalk rw, List<ReceiveCommand> pending)
370 throws IOException {
371 List<Ref> refs = new ArrayList<>(pending.size());
372 for (ReceiveCommand cmd : pending) {
373 String name = cmd.getRefName();
374 ObjectId newId = cmd.getNewId();
375 String newSymref = cmd.getNewSymref();
376 if (AnyObjectId.equals(ObjectId.zeroId(), newId)
377 && newSymref == null) {
378 refs.add(new ObjectIdRef.Unpeeled(NEW, name, null));
379 continue;
380 } else if (newSymref != null) {
381 refs.add(new SymbolicRef(name,
382 new ObjectIdRef.Unpeeled(NEW, newSymref, null)));
383 continue;
384 }
385
386 RevObject obj = rw.parseAny(newId);
387 RevObject peel = null;
388 if (obj instanceof RevTag) {
389 peel = rw.peel(obj);
390 }
391 if (peel != null) {
392 refs.add(new ObjectIdRef.PeeledTag(PACKED, name, newId,
393 peel.copy()));
394 } else {
395 refs.add(new ObjectIdRef.PeeledNonTag(PACKED, name, newId));
396 }
397 }
398 return refs;
399 }
400
401 private long nextUpdateIndex() throws IOException {
402 long updateIndex = 0;
403 for (Reftable r : refdb.stack().readers()) {
404 if (r instanceof ReftableReader) {
405 updateIndex = Math.max(updateIndex,
406 ((ReftableReader) r).maxUpdateIndex());
407 }
408 }
409 return updateIndex + 1;
410 }
411
412 private boolean canCompactTopOfStack(ReftableConfig cfg)
413 throws IOException {
414 ReftableStack stack = refdb.stack();
415 List<Reftable> readers = stack.readers();
416 if (readers.isEmpty()) {
417 return false;
418 }
419
420 int lastIdx = readers.size() - 1;
421 DfsReftable last = stack.files().get(lastIdx);
422 DfsPackDescription desc = last.getPackDescription();
423 if (desc.getPackSource() != PackSource.INSERT
424 || !packOnlyContainsReftable(desc)) {
425 return false;
426 }
427
428 Reftable table = readers.get(lastIdx);
429 int bs = cfg.getRefBlockSize();
430 return table instanceof ReftableReader
431 && ((ReftableReader) table).size() <= 3 * bs;
432 }
433
434 private ReftableWriter.Stats compactTopOfStack(OutputStream out,
435 ReftableConfig cfg, byte[] newTable) throws IOException {
436 List<Reftable> stack = refdb.stack().readers();
437 Reftable last = stack.get(stack.size() - 1);
438
439 List<Reftable> tables = new ArrayList<>(2);
440 tables.add(last);
441 tables.add(new ReftableReader(BlockSource.from(newTable)));
442
443 ReftableCompactor compactor = new ReftableCompactor();
444 compactor.setConfig(cfg);
445 compactor.setIncludeDeletes(true);
446 compactor.addAll(tables);
447 compactor.compact(out);
448 return compactor.getStats();
449 }
450
451 private Set<DfsPackDescription> toPruneTopOfStack() throws IOException {
452 List<DfsReftable> stack = refdb.stack().files();
453 DfsReftable last = stack.get(stack.size() - 1);
454 return Collections.singleton(last.getPackDescription());
455 }
456
457 private boolean packOnlyContainsReftable(DfsPackDescription desc) {
458 for (PackExt ext : PackExt.values()) {
459 if (ext != REFTABLE && desc.hasFileExt(ext)) {
460 return false;
461 }
462 }
463 return true;
464 }
465 }