| /* |
| * Copyright (C) 2022, Tencent. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.eclipse.jgit.internal.storage.commitgraph; |
| |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_DATA; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_INDEX; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_COMMIT_DATA; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_EXTRA_EDGE_LIST; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_OID_FANOUT; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_OID_LOOKUP; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_LOOKUP_WIDTH; |
| import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.COMMIT_GRAPH_MAGIC; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.internal.JGitText; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.util.IO; |
| import org.eclipse.jgit.util.NB; |
| import org.eclipse.jgit.util.SystemReader; |
| import org.eclipse.jgit.util.io.SilentFileInputStream; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The loader returns the representation of the commit-graph file content. |
| */ |
| public class CommitGraphLoader { |
| |
| private final static Logger LOG = LoggerFactory |
| .getLogger(CommitGraphLoader.class); |
| |
| /** |
| * Open an existing commit-graph file for reading. |
| * <p> |
| * The format of the file will be automatically detected and a proper access |
| * implementation for that format will be constructed and returned to the |
| * caller. The file may or may not be held open by the returned instance. |
| * |
| * @param graphFile |
| * existing commit-graph to read. |
| * @return a copy of the commit-graph file in memory |
| * @throws FileNotFoundException |
| * the file does not exist. |
| * @throws CommitGraphFormatException |
| * commit-graph file's format is different from we expected. |
| * @throws java.io.IOException |
| * the file exists but could not be read due to security errors |
| * or unexpected data corruption. |
| */ |
| public static CommitGraph open(File graphFile) throws FileNotFoundException, |
| CommitGraphFormatException, IOException { |
| try (SilentFileInputStream fd = new SilentFileInputStream(graphFile)) { |
| try { |
| return read(fd); |
| } catch (CommitGraphFormatException fe) { |
| throw fe; |
| } catch (IOException ioe) { |
| throw new IOException(MessageFormat.format( |
| JGitText.get().unreadableCommitGraph, |
| graphFile.getAbsolutePath()), ioe); |
| } |
| } |
| } |
| |
| /** |
| * Read an existing commit-graph file from a buffered stream. |
| * <p> |
| * The format of the file will be automatically detected and a proper access |
| * implementation for that format will be constructed and returned to the |
| * caller. The file may or may not be held open by the returned instance. |
| * |
| * @param fd |
| * stream to read the commit-graph file from. The stream must be |
| * buffered as some small IOs are performed against the stream. |
| * The caller is responsible for closing the stream. |
| * |
| * @return a copy of the commit-graph file in memory |
| * @throws CommitGraphFormatException |
| * the commit-graph file's format is different from we expected. |
| * @throws java.io.IOException |
| * the stream cannot be read. |
| */ |
| public static CommitGraph read(InputStream fd) |
| throws CommitGraphFormatException, IOException { |
| byte[] hdr = new byte[8]; |
| IO.readFully(fd, hdr, 0, hdr.length); |
| |
| int magic = NB.decodeInt32(hdr, 0); |
| if (magic != COMMIT_GRAPH_MAGIC) { |
| throw new CommitGraphFormatException( |
| JGitText.get().notACommitGraph); |
| } |
| |
| // Read the hash version (1 byte) |
| // 1 => SHA-1 |
| // 2 => SHA-256 nonsupport now |
| int hashVersion = hdr[5]; |
| if (hashVersion != 1) { |
| throw new CommitGraphFormatException( |
| JGitText.get().incorrectOBJECT_ID_LENGTH); |
| } |
| |
| // Check commit-graph version |
| int v = hdr[4]; |
| if (v != 1) { |
| throw new CommitGraphFormatException(MessageFormat.format( |
| JGitText.get().unsupportedCommitGraphVersion, |
| Integer.valueOf(v))); |
| } |
| |
| // Read the number of "chunkOffsets" (1 byte) |
| int numberOfChunks = hdr[6]; |
| |
| // hdr[7] is the number of base commit-graphs, which is not supported in |
| // current version |
| |
| byte[] lookupBuffer = new byte[CHUNK_LOOKUP_WIDTH |
| * (numberOfChunks + 1)]; |
| IO.readFully(fd, lookupBuffer, 0, lookupBuffer.length); |
| List<ChunkSegment> chunks = new ArrayList<>(numberOfChunks + 1); |
| for (int i = 0; i <= numberOfChunks; i++) { |
| // chunks[numberOfChunks] is just a marker, in order to record the |
| // length of the last chunk. |
| int id = NB.decodeInt32(lookupBuffer, i * 12); |
| long offset = NB.decodeInt64(lookupBuffer, i * 12 + 4); |
| chunks.add(new ChunkSegment(id, offset)); |
| } |
| |
| boolean readChangedPathFilters; |
| try { |
| readChangedPathFilters = SystemReader.getInstance() |
| .getJGitConfig() |
| .getBoolean(ConfigConstants.CONFIG_COMMIT_GRAPH_SECTION, |
| ConfigConstants.CONFIG_KEY_READ_CHANGED_PATHS, false); |
| } catch (ConfigInvalidException e) { |
| // Use the default value if, for some reason, the config couldn't be read. |
| readChangedPathFilters = false; |
| } |
| |
| CommitGraphBuilder builder = CommitGraphBuilder.builder(); |
| for (int i = 0; i < numberOfChunks; i++) { |
| long chunkOffset = chunks.get(i).offset; |
| int chunkId = chunks.get(i).id; |
| long len = chunks.get(i + 1).offset - chunkOffset; |
| |
| if (len > Integer.MAX_VALUE - 8) { // http://stackoverflow.com/a/8381338 |
| throw new CommitGraphFormatException( |
| JGitText.get().commitGraphFileIsTooLargeForJgit); |
| } |
| |
| byte buffer[] = new byte[(int) len]; |
| IO.readFully(fd, buffer, 0, buffer.length); |
| |
| switch (chunkId) { |
| case CHUNK_ID_OID_FANOUT: |
| builder.addOidFanout(buffer); |
| break; |
| case CHUNK_ID_OID_LOOKUP: |
| builder.addOidLookUp(buffer); |
| break; |
| case CHUNK_ID_COMMIT_DATA: |
| builder.addCommitData(buffer); |
| break; |
| case CHUNK_ID_EXTRA_EDGE_LIST: |
| builder.addExtraList(buffer); |
| break; |
| case CHUNK_ID_BLOOM_FILTER_INDEX: |
| if (readChangedPathFilters) { |
| builder.addBloomFilterIndex(buffer); |
| } |
| break; |
| case CHUNK_ID_BLOOM_FILTER_DATA: |
| if (readChangedPathFilters) { |
| builder.addBloomFilterData(buffer); |
| } |
| break; |
| default: |
| LOG.warn(MessageFormat.format( |
| JGitText.get().commitGraphChunkUnknown, |
| Integer.toHexString(chunkId))); |
| } |
| } |
| return builder.build(); |
| } |
| |
| private static class ChunkSegment { |
| final int id; |
| |
| final long offset; |
| |
| private ChunkSegment(int id, long offset) { |
| this.id = id; |
| this.offset = offset; |
| } |
| } |
| } |