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 : }
|