LCOV - code coverage report
Current view: top level - server/git/meta - VersionedMetaData.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 202 215 94.0 %
Date: 2022-11-19 15:00:39 Functions: 36 40 90.0 %

          Line data    Source code
       1             : // Copyright (C) 2010 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.git.meta;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : 
      19             : import com.google.common.base.MoreObjects;
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.common.Nullable;
      22             : import com.google.gerrit.entities.Project;
      23             : import com.google.gerrit.git.GitUpdateFailureException;
      24             : import com.google.gerrit.git.LockFailureException;
      25             : import com.google.gerrit.git.ObjectIds;
      26             : import com.google.gerrit.server.InvalidConfigFileException;
      27             : import com.google.gerrit.server.logging.Metadata;
      28             : import com.google.gerrit.server.logging.TraceContext;
      29             : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
      30             : import com.google.gerrit.server.util.CommitMessageUtil;
      31             : import java.io.BufferedReader;
      32             : import java.io.File;
      33             : import java.io.IOException;
      34             : import java.io.StringReader;
      35             : import java.util.ArrayList;
      36             : import java.util.List;
      37             : import java.util.Objects;
      38             : import java.util.Optional;
      39             : import org.eclipse.jgit.dircache.DirCache;
      40             : import org.eclipse.jgit.dircache.DirCacheBuilder;
      41             : import org.eclipse.jgit.dircache.DirCacheEditor;
      42             : import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
      43             : import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
      44             : import org.eclipse.jgit.dircache.DirCacheEntry;
      45             : import org.eclipse.jgit.errors.ConfigInvalidException;
      46             : import org.eclipse.jgit.errors.IncorrectObjectTypeException;
      47             : import org.eclipse.jgit.errors.MissingObjectException;
      48             : import org.eclipse.jgit.lib.AnyObjectId;
      49             : import org.eclipse.jgit.lib.BatchRefUpdate;
      50             : import org.eclipse.jgit.lib.CommitBuilder;
      51             : import org.eclipse.jgit.lib.Config;
      52             : import org.eclipse.jgit.lib.Constants;
      53             : import org.eclipse.jgit.lib.FileMode;
      54             : import org.eclipse.jgit.lib.ObjectId;
      55             : import org.eclipse.jgit.lib.ObjectInserter;
      56             : import org.eclipse.jgit.lib.ObjectLoader;
      57             : import org.eclipse.jgit.lib.ObjectReader;
      58             : import org.eclipse.jgit.lib.Ref;
      59             : import org.eclipse.jgit.lib.RefUpdate;
      60             : import org.eclipse.jgit.lib.Repository;
      61             : import org.eclipse.jgit.revwalk.RevCommit;
      62             : import org.eclipse.jgit.revwalk.RevTree;
      63             : import org.eclipse.jgit.revwalk.RevWalk;
      64             : import org.eclipse.jgit.transport.ReceiveCommand;
      65             : import org.eclipse.jgit.treewalk.TreeWalk;
      66             : import org.eclipse.jgit.util.ChangeIdUtil;
      67             : import org.eclipse.jgit.util.RawParseUtils;
      68             : 
      69             : /**
      70             :  * Support for metadata stored within a version controlled branch.
      71             :  *
      72             :  * <p>Implementors are responsible for supplying implementations of the onLoad and onSave methods to
      73             :  * read from the repository, or format an update that can later be written back to the repository.
      74             :  */
      75         153 : public abstract class VersionedMetaData {
      76         153 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      77             : 
      78             :   /**
      79             :    * Path information that does not hold references to any repository data structures, allowing the
      80             :    * application to retain this object for long periods of time.
      81             :    */
      82             :   public static class PathInfo {
      83             :     public final FileMode fileMode;
      84             :     public final String path;
      85             :     public final ObjectId objectId;
      86             : 
      87         151 :     protected PathInfo(TreeWalk tw) {
      88         151 :       fileMode = tw.getFileMode(0);
      89         151 :       path = tw.getPathString();
      90         151 :       objectId = tw.getObjectId(0);
      91         151 :     }
      92             :   }
      93             : 
      94             :   /** The revision at which the data was loaded. Is null for data yet to be created. */
      95             :   @Nullable protected RevCommit revision;
      96             : 
      97             :   protected Project.NameKey projectName;
      98             :   protected RevWalk rw;
      99             :   protected ObjectReader reader;
     100             :   protected ObjectInserter inserter;
     101             :   protected DirCache newTree;
     102             : 
     103             :   /** Returns name of the reference storing this configuration. */
     104             :   protected abstract String getRefName();
     105             : 
     106             :   /** Set up the metadata, parsing any state from the loaded revision. */
     107             :   protected abstract void onLoad() throws IOException, ConfigInvalidException;
     108             : 
     109             :   /**
     110             :    * Save any changes to the metadata in a commit.
     111             :    *
     112             :    * @return true if the commit should proceed, false to abort.
     113             :    */
     114             :   protected abstract boolean onSave(CommitBuilder commit)
     115             :       throws IOException, ConfigInvalidException;
     116             : 
     117             :   /** Returns revision of the metadata that was loaded. */
     118             :   @Nullable
     119             :   public ObjectId getRevision() {
     120         151 :     return ObjectIds.copyOrNull(revision);
     121             :   }
     122             : 
     123             :   /**
     124             :    * Load the current version from the branch.
     125             :    *
     126             :    * <p>The repository is not held after the call completes, allowing the application to retain this
     127             :    * object for long periods of time.
     128             :    *
     129             :    * @param projectName the name of the project
     130             :    * @param db repository to access.
     131             :    */
     132             :   public void load(Project.NameKey projectName, Repository db)
     133             :       throws IOException, ConfigInvalidException {
     134         153 :     Ref ref = db.getRefDatabase().exactRef(getRefName());
     135         153 :     load(projectName, db, ref != null ? ref.getObjectId() : null);
     136         153 :   }
     137             : 
     138             :   /**
     139             :    * Load a specific version from the repository.
     140             :    *
     141             :    * <p>This method is primarily useful for applying updates to a specific revision that was shown
     142             :    * to an end-user in the user interface. If there are conflicts with another user's concurrent
     143             :    * changes, these will be automatically detected at commit time.
     144             :    *
     145             :    * <p>The repository is not held after the call completes, allowing the application to retain this
     146             :    * object for long periods of time.
     147             :    *
     148             :    * @param projectName the name of the project
     149             :    * @param db repository to access.
     150             :    * @param id revision to load.
     151             :    */
     152             :   public void load(Project.NameKey projectName, Repository db, @Nullable ObjectId id)
     153             :       throws IOException, ConfigInvalidException {
     154         153 :     try (RevWalk walk = new RevWalk(db)) {
     155         153 :       load(projectName, walk, id);
     156             :     }
     157         153 :   }
     158             : 
     159             :   /**
     160             :    * Load a specific version from an open walk.
     161             :    *
     162             :    * <p>This method is primarily useful for applying updates to a specific revision that was shown
     163             :    * to an end-user in the user interface. If there are conflicts with another user's concurrent
     164             :    * changes, these will be automatically detected at commit time.
     165             :    *
     166             :    * <p>The caller retains ownership of the walk and is responsible for closing it. However, this
     167             :    * instance does not hold a reference to the walk or the repository after the call completes,
     168             :    * allowing the application to retain this object for long periods of time.
     169             :    *
     170             :    * @param projectName the name of the project
     171             :    * @param walk open walk to access to access.
     172             :    * @param id revision to load.
     173             :    */
     174             :   public void load(Project.NameKey projectName, RevWalk walk, ObjectId id)
     175             :       throws IOException, ConfigInvalidException {
     176         153 :     this.projectName = projectName;
     177         153 :     this.rw = walk;
     178         153 :     this.reader = walk.getObjectReader();
     179             :     try {
     180         153 :       revision = id != null ? walk.parseCommit(id) : null;
     181         153 :       onLoad();
     182             :     } finally {
     183         153 :       this.rw = null;
     184         153 :       this.reader = null;
     185             :     }
     186         153 :   }
     187             : 
     188             :   public void load(MetaDataUpdate update) throws IOException, ConfigInvalidException {
     189         151 :     load(update.getProjectName(), update.getRepository());
     190         151 :   }
     191             : 
     192             :   public void load(MetaDataUpdate update, ObjectId id) throws IOException, ConfigInvalidException {
     193           0 :     load(update.getProjectName(), update.getRepository(), id);
     194           0 :   }
     195             : 
     196             :   /**
     197             :    * Update this metadata branch, recording a new commit on its reference. This method mutates its
     198             :    * receiver.
     199             :    *
     200             :    * @param update helper information to define the update that will occur.
     201             :    * @return the commit that was created
     202             :    * @throws IOException if there is a storage problem and the update cannot be executed as
     203             :    *     requested or if it failed because of a concurrent update to the same reference
     204             :    */
     205             :   public RevCommit commit(MetaDataUpdate update) throws IOException {
     206         152 :     try (BatchMetaDataUpdate batch = openUpdate(update)) {
     207         152 :       batch.write(update.getCommitBuilder());
     208         152 :       return batch.commit();
     209             :     }
     210             :   }
     211             : 
     212             :   /**
     213             :    * Update this metadata branch, recording a new commit on its reference. This method mutates its
     214             :    * receiver.
     215             :    *
     216             :    * @param update helper information to define the update that will occur.
     217             :    * @param objInserter Shared object inserter.
     218             :    * @param objReader Shared object reader.
     219             :    * @param revWalk Shared rev walk.
     220             :    * @return the commit that was created
     221             :    * @throws IOException if there is a storage problem and the update cannot be executed as
     222             :    *     requested or if it failed because of a concurrent update to the same reference
     223             :    */
     224             :   public RevCommit commit(
     225             :       MetaDataUpdate update, ObjectInserter objInserter, ObjectReader objReader, RevWalk revWalk)
     226             :       throws IOException {
     227           0 :     try (BatchMetaDataUpdate batch = openUpdate(update, objInserter, objReader, revWalk)) {
     228           0 :       batch.write(update.getCommitBuilder());
     229           0 :       return batch.commit();
     230             :     }
     231             :   }
     232             : 
     233             :   /**
     234             :    * Creates a new commit and a new ref based on this commit. This method mutates its receiver.
     235             :    *
     236             :    * @param update helper information to define the update that will occur.
     237             :    * @param refName name of the ref that should be created
     238             :    * @return the commit that was created
     239             :    * @throws IOException if there is a storage problem and the update cannot be executed as
     240             :    *     requested or if it failed because of a concurrent update to the same reference
     241             :    */
     242             :   public RevCommit commitToNewRef(MetaDataUpdate update, String refName) throws IOException {
     243         151 :     try (BatchMetaDataUpdate batch = openUpdate(update)) {
     244         151 :       batch.write(update.getCommitBuilder());
     245         151 :       return batch.createRef(refName);
     246             :     }
     247             :   }
     248             : 
     249             :   public interface BatchMetaDataUpdate extends AutoCloseable {
     250             :     void write(CommitBuilder commit) throws IOException;
     251             : 
     252             :     void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
     253             : 
     254             :     RevCommit createRef(String refName) throws IOException;
     255             : 
     256             :     RevCommit commit() throws IOException;
     257             : 
     258             :     RevCommit commitAt(ObjectId revision) throws IOException;
     259             : 
     260             :     @Override
     261             :     void close();
     262             :   }
     263             : 
     264             :   /**
     265             :    * Open a batch of updates to the same metadata ref.
     266             :    *
     267             :    * <p>This allows making multiple commits to a single metadata ref, at the end of which is a
     268             :    * single ref update. For batching together updates to multiple refs (each consisting of one or
     269             :    * more commits against their respective refs), create the {@link MetaDataUpdate} with a {@link
     270             :    * BatchRefUpdate}.
     271             :    *
     272             :    * <p>A ref update produced by this {@link BatchMetaDataUpdate} is only committed if there is no
     273             :    * associated {@link BatchRefUpdate}. As a result, the configured ref updated event is not fired
     274             :    * if there is an associated batch.
     275             :    *
     276             :    * @param update helper info about the update.
     277             :    * @throws IOException if the update failed.
     278             :    */
     279             :   public BatchMetaDataUpdate openUpdate(MetaDataUpdate update) throws IOException {
     280         152 :     return openUpdate(update, null, null, null);
     281             :   }
     282             : 
     283             :   /**
     284             :    * Open a batch of updates to the same metadata ref.
     285             :    *
     286             :    * <p>This allows making multiple commits to a single metadata ref, at the end of which is a
     287             :    * single ref update. For batching together updates to multiple refs (each consisting of one or
     288             :    * more commits against their respective refs), create the {@link MetaDataUpdate} with a {@link
     289             :    * BatchRefUpdate}.
     290             :    *
     291             :    * <p>A ref update produced by this {@link BatchMetaDataUpdate} is only committed if there is no
     292             :    * associated {@link BatchRefUpdate}. As a result, the configured ref updated event is not fired
     293             :    * if there is an associated batch.
     294             :    *
     295             :    * <p>If object inserter, reader and revwalk are provided, then the updates are not flushed,
     296             :    * allowing callers the flexibility to flush only once after several updates.
     297             :    *
     298             :    * @param update helper info about the update.
     299             :    * @param objInserter Shared object inserter.
     300             :    * @param objReader Shared object reader.
     301             :    * @param revWalk Shared rev walk.
     302             :    * @throws IOException if the update failed.
     303             :    */
     304             :   public BatchMetaDataUpdate openUpdate(
     305             :       MetaDataUpdate update, ObjectInserter objInserter, ObjectReader objReader, RevWalk revWalk)
     306             :       throws IOException {
     307         152 :     final Repository db = update.getRepository();
     308             : 
     309         152 :     inserter = objInserter == null ? db.newObjectInserter() : objInserter;
     310         152 :     reader = objReader == null ? inserter.newReader() : objReader;
     311         152 :     final RevWalk rw = revWalk == null ? new RevWalk(reader) : revWalk;
     312             : 
     313         152 :     final RevTree tree = revision != null ? rw.parseTree(revision) : null;
     314         152 :     newTree = readTree(tree);
     315         152 :     return new BatchMetaDataUpdate() {
     316         152 :       RevCommit src = revision;
     317         152 :       AnyObjectId srcTree = tree;
     318             : 
     319             :       @Override
     320             :       public void write(CommitBuilder commit) throws IOException {
     321         152 :         write(VersionedMetaData.this, commit);
     322         152 :       }
     323             : 
     324             :       private boolean doSave(VersionedMetaData config, CommitBuilder commit) throws IOException {
     325         152 :         DirCache nt = config.newTree;
     326         152 :         ObjectReader r = config.reader;
     327         152 :         ObjectInserter i = config.inserter;
     328         152 :         RevCommit c = config.revision;
     329             :         try {
     330         152 :           config.newTree = newTree;
     331         152 :           config.reader = reader;
     332         152 :           config.inserter = inserter;
     333         152 :           config.revision = src;
     334         152 :           return config.onSave(commit);
     335           2 :         } catch (ConfigInvalidException e) {
     336           2 :           throw new IOException(
     337           2 :               "Cannot update " + getRefName() + " in " + db.getDirectory() + ": " + e.getMessage(),
     338             :               e);
     339             :         } finally {
     340         152 :           config.newTree = nt;
     341         152 :           config.reader = r;
     342         152 :           config.inserter = i;
     343         152 :           config.revision = c;
     344             :         }
     345             :       }
     346             : 
     347             :       @Override
     348             :       public void write(VersionedMetaData config, CommitBuilder commit) throws IOException {
     349         152 :         checkSameRef(config);
     350         152 :         if (!doSave(config, commit)) {
     351          37 :           return;
     352             :         }
     353             : 
     354         152 :         ObjectId res = newTree.writeTree(inserter);
     355         152 :         if (res.equals(srcTree) && !update.allowEmpty() && (commit.getTreeId() == null)) {
     356             :           // If there are no changes to the content, don't create the commit.
     357          18 :           return;
     358             :         }
     359             : 
     360             :         // If changes are made to the DirCache and those changes are written as
     361             :         // a commit and then the tree ID is set for the CommitBuilder, then
     362             :         // those previous DirCache changes will be ignored and the commit's
     363             :         // tree will be replaced with the ID in the CommitBuilder. The same is
     364             :         // true if you explicitly set tree ID in a commit and then make changes
     365             :         // to the DirCache; that tree ID will be ignored and replaced by that of
     366             :         // the tree for the updated DirCache.
     367         152 :         if (commit.getTreeId() == null) {
     368         152 :           commit.setTreeId(res);
     369             :         } else {
     370             :           // In this case, the caller populated the tree without using DirCache.
     371         152 :           res = commit.getTreeId();
     372             :         }
     373             : 
     374         152 :         if (src != null) {
     375         152 :           commit.addParentId(src);
     376             :         }
     377             : 
     378         152 :         if (update.insertChangeId()) {
     379           1 :           commit.setMessage(
     380           1 :               ChangeIdUtil.insertId(commit.getMessage(), CommitMessageUtil.generateChangeId()));
     381             :         }
     382             : 
     383         152 :         src = rw.parseCommit(inserter.insert(commit));
     384         152 :         srcTree = res;
     385         152 :       }
     386             : 
     387             :       private void checkSameRef(VersionedMetaData other) {
     388         152 :         String thisRef = VersionedMetaData.this.getRefName();
     389         152 :         String otherRef = other.getRefName();
     390         152 :         checkArgument(
     391         152 :             otherRef.equals(thisRef),
     392             :             "cannot add %s for %s to %s on %s",
     393         152 :             other.getClass().getSimpleName(),
     394             :             otherRef,
     395         152 :             BatchMetaDataUpdate.class.getSimpleName(),
     396             :             thisRef);
     397         152 :       }
     398             : 
     399             :       @Override
     400             :       public RevCommit createRef(String refName) throws IOException {
     401         151 :         if (Objects.equals(src, revision)) {
     402           2 :           return revision;
     403             :         }
     404         151 :         return updateRef(ObjectId.zeroId(), src, refName);
     405             :       }
     406             : 
     407             :       @Override
     408             :       public RevCommit commit() throws IOException {
     409         152 :         return commitAt(revision);
     410             :       }
     411             : 
     412             :       @Override
     413             :       public RevCommit commitAt(ObjectId expected) throws IOException {
     414         152 :         if (Objects.equals(src, expected)) {
     415          42 :           return revision;
     416             :         }
     417         152 :         return updateRef(MoreObjects.firstNonNull(expected, ObjectId.zeroId()), src, getRefName());
     418             :       }
     419             : 
     420             :       @Override
     421             :       public void close() {
     422         152 :         newTree = null;
     423             : 
     424         152 :         if (revWalk == null) {
     425         152 :           rw.close();
     426             :         }
     427             : 
     428         152 :         if (objInserter == null && inserter != null) {
     429         152 :           inserter.close();
     430         152 :           inserter = null;
     431             :         }
     432             : 
     433         152 :         if (objReader == null && reader != null) {
     434         152 :           reader.close();
     435         152 :           reader = null;
     436             :         }
     437         152 :       }
     438             : 
     439             :       private RevCommit updateRef(AnyObjectId oldId, AnyObjectId newId, String refName)
     440             :           throws IOException {
     441         152 :         BatchRefUpdate bru = update.getBatch();
     442         152 :         if (bru != null) {
     443         151 :           bru.addCommand(new ReceiveCommand(oldId.toObjectId(), newId.toObjectId(), refName));
     444         151 :           if (objInserter == null) {
     445         151 :             inserter.flush();
     446             :           }
     447         151 :           revision = rw.parseCommit(newId);
     448         151 :           return revision;
     449             :         }
     450             : 
     451         152 :         RefUpdate ru = db.updateRef(refName);
     452         152 :         ru.setExpectedOldObjectId(oldId);
     453         152 :         ru.setNewObjectId(newId);
     454         152 :         ru.setRefLogIdent(update.getCommitBuilder().getAuthor());
     455         152 :         String message = update.getCommitBuilder().getMessage();
     456         152 :         if (message == null) {
     457           2 :           message = "meta data update";
     458             :         }
     459         152 :         try (BufferedReader reader = new BufferedReader(new StringReader(message))) {
     460             :           // read the subject line and use it as reflog message
     461         152 :           ru.setRefLogMessage("commit: " + reader.readLine(), true);
     462             :         }
     463         152 :         logger.atFine().log("Saving commit '%s' on project '%s'", message.trim(), projectName);
     464         152 :         inserter.flush();
     465         152 :         RefUpdate.Result result = ru.update();
     466         152 :         switch (result) {
     467             :           case NEW:
     468             :           case FAST_FORWARD:
     469         152 :             revision = rw.parseCommit(ru.getNewObjectId());
     470         152 :             update.fireGitRefUpdatedEvent(ru);
     471         152 :             logger.atFine().log(
     472             :                 "Saved commit '%s' as revision '%s' on project '%s'",
     473         152 :                 message.trim(), revision.name(), projectName);
     474         152 :             return revision;
     475             :           case LOCK_FAILURE:
     476           0 :             throw new LockFailureException(errorMsg(ru, db.getDirectory()), ru);
     477             :           case FORCED:
     478             :           case IO_FAILURE:
     479             :           case NOT_ATTEMPTED:
     480             :           case NO_CHANGE:
     481             :           case REJECTED:
     482             :           case REJECTED_CURRENT_BRANCH:
     483             :           case RENAMED:
     484             :           case REJECTED_MISSING_OBJECT:
     485             :           case REJECTED_OTHER_REASON:
     486             :           default:
     487           0 :             throw new GitUpdateFailureException(errorMsg(ru, db.getDirectory()), ru);
     488             :         }
     489             :       }
     490             :     };
     491             :   }
     492             : 
     493             :   private String errorMsg(RefUpdate ru, File location) {
     494           0 :     return String.format(
     495             :         "Cannot update %s in %s: %s (%s)",
     496           0 :         ru.getName(), location, ru.getResult(), ru.getRefLogMessage());
     497             :   }
     498             : 
     499             :   protected DirCache readTree(RevTree tree)
     500             :       throws IOException, MissingObjectException, IncorrectObjectTypeException {
     501         152 :     DirCache dc = DirCache.newInCore();
     502         152 :     if (tree != null) {
     503         152 :       DirCacheBuilder b = dc.builder();
     504         152 :       b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, tree);
     505         152 :       b.finish();
     506             :     }
     507         152 :     return dc;
     508             :   }
     509             : 
     510             :   protected Config readConfig(String fileName) throws IOException, ConfigInvalidException {
     511         152 :     return readConfig(fileName, Optional.empty());
     512             :   }
     513             : 
     514             :   protected Config readConfig(String fileName, Optional<? extends Config> baseConfig)
     515             :       throws IOException, ConfigInvalidException {
     516         153 :     Config rc = new Config(baseConfig.isPresent() ? baseConfig.get() : null);
     517         153 :     String text = readUTF8(fileName);
     518         153 :     if (!text.isEmpty()) {
     519             :       try {
     520         153 :         rc.fromText(text);
     521           4 :       } catch (ConfigInvalidException err) {
     522           4 :         throw new InvalidConfigFileException(projectName, getRefName(), revision, fileName, err);
     523         153 :       }
     524             :     }
     525         153 :     return rc;
     526             :   }
     527             : 
     528             :   protected String readUTF8(String fileName) throws IOException {
     529         153 :     byte[] raw = readFile(fileName);
     530         153 :     return raw.length != 0 ? RawParseUtils.decode(raw) : "";
     531             :   }
     532             : 
     533             :   protected byte[] readFile(String fileName) throws IOException {
     534         153 :     if (revision == null) {
     535         153 :       return new byte[] {};
     536             :     }
     537             : 
     538         153 :     try (TraceTimer timer =
     539         153 :             TraceContext.newTimer(
     540             :                 "Read file",
     541         153 :                 Metadata.builder()
     542         153 :                     .projectName(projectName.get())
     543         153 :                     .noteDbRefName(getRefName())
     544         153 :                     .revision(revision.name())
     545         153 :                     .noteDbFilePath(fileName)
     546         153 :                     .build());
     547         153 :         TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree())) {
     548         153 :       if (tw != null) {
     549         153 :         ObjectLoader obj = reader.open(tw.getObjectId(0), Constants.OBJ_BLOB);
     550         153 :         return obj.getCachedBytes(Integer.MAX_VALUE);
     551             :       }
     552         153 :     }
     553         153 :     return new byte[] {};
     554             :   }
     555             : 
     556             :   @Nullable
     557             :   protected ObjectId getObjectId(String fileName) throws IOException {
     558         151 :     if (revision == null) {
     559         151 :       return null;
     560             :     }
     561             : 
     562         151 :     try (TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree())) {
     563         151 :       if (tw != null) {
     564           5 :         return tw.getObjectId(0);
     565             :       }
     566           5 :     }
     567             : 
     568         151 :     return null;
     569             :   }
     570             : 
     571             :   public List<PathInfo> getPathInfos(boolean recursive) throws IOException {
     572         151 :     try (TreeWalk tw = new TreeWalk(reader)) {
     573         151 :       tw.addTree(revision.getTree());
     574         151 :       tw.setRecursive(recursive);
     575         151 :       List<PathInfo> paths = new ArrayList<>();
     576         151 :       while (tw.next()) {
     577         151 :         paths.add(new PathInfo(tw));
     578             :       }
     579         151 :       return paths;
     580             :     }
     581             :   }
     582             : 
     583             :   protected static void set(
     584             :       Config rc, String section, String subsection, String name, String value) {
     585         151 :     if (value != null) {
     586         144 :       rc.setString(section, subsection, name, value);
     587             :     } else {
     588         151 :       rc.unset(section, subsection, name);
     589             :     }
     590         151 :   }
     591             : 
     592             :   protected static void set(
     593             :       Config rc, String section, String subsection, String name, boolean value) {
     594           0 :     if (value) {
     595           0 :       rc.setBoolean(section, subsection, name, value);
     596             :     } else {
     597           0 :       rc.unset(section, subsection, name);
     598             :     }
     599           0 :   }
     600             : 
     601             :   protected static <E extends Enum<?>> void set(
     602             :       Config rc, String section, String subsection, String name, E value, E defaultValue) {
     603         151 :     if (value != defaultValue) {
     604         151 :       rc.setEnum(section, subsection, name, value);
     605             :     } else {
     606         151 :       rc.unset(section, subsection, name);
     607             :     }
     608         151 :   }
     609             : 
     610             :   protected void saveConfig(String fileName, Config cfg) throws IOException {
     611         152 :     saveUTF8(fileName, cfg.toText());
     612         152 :   }
     613             : 
     614             :   protected void saveUTF8(String fileName, String text) throws IOException {
     615         152 :     saveFile(fileName, text != null ? Constants.encode(text) : null);
     616         152 :   }
     617             : 
     618             :   protected void saveFile(String fileName, byte[] raw) throws IOException {
     619         152 :     try (TraceTimer timer =
     620         152 :         TraceContext.newTimer(
     621             :             "Save file",
     622         152 :             Metadata.builder()
     623         152 :                 .projectName(projectName.get())
     624         152 :                 .noteDbRefName(getRefName())
     625         152 :                 .noteDbFilePath(fileName)
     626         152 :                 .build())) {
     627         152 :       DirCacheEditor editor = newTree.editor();
     628         152 :       if (raw != null && 0 < raw.length) {
     629         152 :         final ObjectId blobId = inserter.insert(Constants.OBJ_BLOB, raw);
     630         152 :         editor.add(
     631         152 :             new PathEdit(fileName) {
     632             :               @Override
     633             :               public void apply(DirCacheEntry ent) {
     634         152 :                 ent.setFileMode(FileMode.REGULAR_FILE);
     635         152 :                 ent.setObjectId(blobId);
     636         152 :               }
     637             :             });
     638         152 :       } else {
     639         149 :         editor.add(new DeletePath(fileName));
     640             :       }
     641         152 :       editor.finish();
     642             :     }
     643         152 :   }
     644             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750