123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392 |
- /*
- * Copyright (C) 2009-2010, Google Inc. and others
- *
- * 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.junit;
-
- import static java.nio.charset.StandardCharsets.UTF_8;
- import static org.junit.Assert.assertEquals;
- import static org.junit.Assert.fail;
-
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.security.MessageDigest;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.Date;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- import java.util.TimeZone;
-
- import org.eclipse.jgit.api.Git;
- import org.eclipse.jgit.dircache.DirCache;
- import org.eclipse.jgit.dircache.DirCacheBuilder;
- import org.eclipse.jgit.dircache.DirCacheEditor;
- import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
- import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree;
- import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
- import org.eclipse.jgit.dircache.DirCacheEntry;
- import org.eclipse.jgit.errors.IncorrectObjectTypeException;
- import org.eclipse.jgit.errors.MissingObjectException;
- import org.eclipse.jgit.errors.ObjectWritingException;
- import org.eclipse.jgit.internal.storage.file.FileRepository;
- import org.eclipse.jgit.internal.storage.file.LockFile;
- import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
- import org.eclipse.jgit.internal.storage.file.Pack;
- import org.eclipse.jgit.internal.storage.file.PackFile;
- import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
- import org.eclipse.jgit.internal.storage.pack.PackExt;
- import org.eclipse.jgit.internal.storage.pack.PackWriter;
- import org.eclipse.jgit.lib.AnyObjectId;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.lib.FileMode;
- import org.eclipse.jgit.lib.NullProgressMonitor;
- import org.eclipse.jgit.lib.ObjectChecker;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectInserter;
- import org.eclipse.jgit.lib.PersonIdent;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.lib.RefUpdate;
- import org.eclipse.jgit.lib.RefWriter;
- import org.eclipse.jgit.lib.Repository;
- import org.eclipse.jgit.lib.TagBuilder;
- import org.eclipse.jgit.merge.MergeStrategy;
- import org.eclipse.jgit.merge.ThreeWayMerger;
- import org.eclipse.jgit.revwalk.ObjectWalk;
- import org.eclipse.jgit.revwalk.RevBlob;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.revwalk.RevObject;
- import org.eclipse.jgit.revwalk.RevTag;
- import org.eclipse.jgit.revwalk.RevTree;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.treewalk.TreeWalk;
- import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
- import org.eclipse.jgit.util.ChangeIdUtil;
- import org.eclipse.jgit.util.FileUtils;
-
- /**
- * Wrapper to make creating test data easier.
- *
- * @param <R>
- * type of Repository the test data is stored on.
- */
- public class TestRepository<R extends Repository> implements AutoCloseable {
-
- /** Constant <code>AUTHOR="J. Author"</code> */
- public static final String AUTHOR = "J. Author";
-
- /** Constant <code>AUTHOR_EMAIL="jauthor@example.com"</code> */
- public static final String AUTHOR_EMAIL = "jauthor@example.com";
-
- /** Constant <code>COMMITTER="J. Committer"</code> */
- public static final String COMMITTER = "J. Committer";
-
- /** Constant <code>COMMITTER_EMAIL="jcommitter@example.com"</code> */
- public static final String COMMITTER_EMAIL = "jcommitter@example.com";
-
- private final PersonIdent defaultAuthor;
-
- private final PersonIdent defaultCommitter;
-
- private final R db;
-
- private final Git git;
-
- private final RevWalk pool;
-
- private final ObjectInserter inserter;
-
- private final MockSystemReader mockSystemReader;
-
- /**
- * Wrap a repository with test building tools.
- *
- * @param db
- * the test repository to write into.
- * @throws IOException
- */
- public TestRepository(R db) throws IOException {
- this(db, new RevWalk(db), new MockSystemReader());
- }
-
- /**
- * Wrap a repository with test building tools.
- *
- * @param db
- * the test repository to write into.
- * @param rw
- * the RevObject pool to use for object lookup.
- * @throws IOException
- */
- public TestRepository(R db, RevWalk rw) throws IOException {
- this(db, rw, new MockSystemReader());
- }
-
- /**
- * Wrap a repository with test building tools.
- *
- * @param db
- * the test repository to write into.
- * @param rw
- * the RevObject pool to use for object lookup.
- * @param reader
- * the MockSystemReader to use for clock and other system
- * operations.
- * @throws IOException
- * @since 4.2
- */
- public TestRepository(R db, RevWalk rw, MockSystemReader reader)
- throws IOException {
- this.db = db;
- this.git = Git.wrap(db);
- this.pool = rw;
- this.inserter = db.newObjectInserter();
- this.mockSystemReader = reader;
- long now = mockSystemReader.getCurrentTime();
- int tz = mockSystemReader.getTimezone(now);
- defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz);
- defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz);
- }
-
- /**
- * Get repository
- *
- * @return the repository this helper class operates against.
- */
- public R getRepository() {
- return db;
- }
-
- /**
- * Get RevWalk
- *
- * @return get the RevWalk pool all objects are allocated through.
- */
- public RevWalk getRevWalk() {
- return pool;
- }
-
- /**
- * Return Git API wrapper
- *
- * @return an API wrapper for the underlying repository. This wrapper does
- * not allocate any new resources and need not be closed (but
- * closing it is harmless).
- */
- public Git git() {
- return git;
- }
-
- /**
- * Get date
- *
- * @return current date.
- * @since 4.2
- */
- public Date getDate() {
- return new Date(mockSystemReader.getCurrentTime());
- }
-
- /**
- * Get timezone
- *
- * @return timezone used for default identities.
- */
- public TimeZone getTimeZone() {
- return mockSystemReader.getTimeZone();
- }
-
- /**
- * Adjust the current time that will used by the next commit.
- *
- * @param secDelta
- * number of seconds to add to the current time.
- */
- public void tick(int secDelta) {
- mockSystemReader.tick(secDelta);
- }
-
- /**
- * Set the author and committer using {@link #getDate()}.
- *
- * @param c
- * the commit builder to store.
- */
- public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
- c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
- c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
- }
-
- /**
- * Create a new blob object in the repository.
- *
- * @param content
- * file content, will be UTF-8 encoded.
- * @return reference to the blob.
- * @throws Exception
- */
- public RevBlob blob(String content) throws Exception {
- return blob(content.getBytes(UTF_8));
- }
-
- /**
- * Create a new blob object in the repository.
- *
- * @param content
- * binary file content.
- * @return the new, fully parsed blob.
- * @throws Exception
- */
- public RevBlob blob(byte[] content) throws Exception {
- ObjectId id;
- try (ObjectInserter ins = inserter) {
- id = ins.insert(Constants.OBJ_BLOB, content);
- ins.flush();
- }
- return (RevBlob) pool.parseAny(id);
- }
-
- /**
- * Construct a regular file mode tree entry.
- *
- * @param path
- * path of the file.
- * @param blob
- * a blob, previously constructed in the repository.
- * @return the entry.
- * @throws Exception
- */
- public DirCacheEntry file(String path, RevBlob blob)
- throws Exception {
- final DirCacheEntry e = new DirCacheEntry(path);
- e.setFileMode(FileMode.REGULAR_FILE);
- e.setObjectId(blob);
- return e;
- }
-
- /**
- * Construct a tree from a specific listing of file entries.
- *
- * @param entries
- * the files to include in the tree. The collection does not need
- * to be sorted properly and may be empty.
- * @return the new, fully parsed tree specified by the entry list.
- * @throws Exception
- */
- public RevTree tree(DirCacheEntry... entries) throws Exception {
- final DirCache dc = DirCache.newInCore();
- final DirCacheBuilder b = dc.builder();
- for (DirCacheEntry e : entries) {
- b.add(e);
- }
- b.finish();
- ObjectId root;
- try (ObjectInserter ins = inserter) {
- root = dc.writeTree(ins);
- ins.flush();
- }
- return pool.parseTree(root);
- }
-
- /**
- * Lookup an entry stored in a tree, failing if not present.
- *
- * @param tree
- * the tree to search.
- * @param path
- * the path to find the entry of.
- * @return the parsed object entry at this path, never null.
- * @throws Exception
- */
- public RevObject get(RevTree tree, String path)
- throws Exception {
- try (TreeWalk tw = new TreeWalk(pool.getObjectReader())) {
- tw.setFilter(PathFilterGroup.createFromStrings(Collections
- .singleton(path)));
- tw.reset(tree);
- while (tw.next()) {
- if (tw.isSubtree() && !path.equals(tw.getPathString())) {
- tw.enterSubtree();
- continue;
- }
- final ObjectId entid = tw.getObjectId(0);
- final FileMode entmode = tw.getFileMode(0);
- return pool.lookupAny(entid, entmode.getObjectType());
- }
- }
- fail("Can't find " + path + " in tree " + tree.name());
- return null; // never reached.
- }
-
- /**
- * Create a new, unparsed commit.
- * <p>
- * See {@link #unparsedCommit(int, RevTree, ObjectId...)}. The tree is the
- * empty tree (no files or subdirectories).
- *
- * @param parents
- * zero or more IDs of the commit's parents.
- * @return the ID of the new commit.
- * @throws Exception
- */
- public ObjectId unparsedCommit(ObjectId... parents) throws Exception {
- return unparsedCommit(1, tree(), parents);
- }
-
- /**
- * Create a new commit.
- * <p>
- * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
- * tree (no files or subdirectories).
- *
- * @param parents
- * zero or more parents of the commit.
- * @return the new commit.
- * @throws Exception
- */
- public RevCommit commit(RevCommit... parents) throws Exception {
- return commit(1, tree(), parents);
- }
-
- /**
- * Create a new commit.
- * <p>
- * See {@link #commit(int, RevTree, RevCommit...)}.
- *
- * @param tree
- * the root tree for the commit.
- * @param parents
- * zero or more parents of the commit.
- * @return the new commit.
- * @throws Exception
- */
- public RevCommit commit(RevTree tree, RevCommit... parents)
- throws Exception {
- return commit(1, tree, parents);
- }
-
- /**
- * Create a new commit.
- * <p>
- * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
- * tree (no files or subdirectories).
- *
- * @param secDelta
- * number of seconds to advance {@link #tick(int)} by.
- * @param parents
- * zero or more parents of the commit.
- * @return the new commit.
- * @throws Exception
- */
- public RevCommit commit(int secDelta, RevCommit... parents)
- throws Exception {
- return commit(secDelta, tree(), parents);
- }
-
- /**
- * Create a new commit.
- * <p>
- * The author and committer identities are stored using the current
- * timestamp, after being incremented by {@code secDelta}. The message body
- * is empty.
- *
- * @param secDelta
- * number of seconds to advance {@link #tick(int)} by.
- * @param tree
- * the root tree for the commit.
- * @param parents
- * zero or more parents of the commit.
- * @return the new, fully parsed commit.
- * @throws Exception
- */
- public RevCommit commit(final int secDelta, final RevTree tree,
- final RevCommit... parents) throws Exception {
- ObjectId id = unparsedCommit(secDelta, tree, parents);
- return pool.parseCommit(id);
- }
-
- /**
- * Create a new, unparsed commit.
- * <p>
- * The author and committer identities are stored using the current
- * timestamp, after being incremented by {@code secDelta}. The message body
- * is empty.
- *
- * @param secDelta
- * number of seconds to advance {@link #tick(int)} by.
- * @param tree
- * the root tree for the commit.
- * @param parents
- * zero or more IDs of the commit's parents.
- * @return the ID of the new commit.
- * @throws Exception
- */
- public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
- final ObjectId... parents) throws Exception {
- tick(secDelta);
-
- final org.eclipse.jgit.lib.CommitBuilder c;
-
- c = new org.eclipse.jgit.lib.CommitBuilder();
- c.setTreeId(tree);
- c.setParentIds(parents);
- c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
- c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
- c.setMessage("");
- ObjectId id;
- try (ObjectInserter ins = inserter) {
- id = ins.insert(c);
- ins.flush();
- }
- return id;
- }
-
- /**
- * Create commit builder
- *
- * @return a new commit builder.
- */
- public CommitBuilder commit() {
- return new CommitBuilder();
- }
-
- /**
- * Construct an annotated tag object pointing at another object.
- * <p>
- * The tagger is the committer identity, at the current time as specified by
- * {@link #tick(int)}. The time is not increased.
- * <p>
- * The tag message is empty.
- *
- * @param name
- * name of the tag. Traditionally a tag name should not start
- * with {@code refs/tags/}.
- * @param dst
- * object the tag should be pointed at.
- * @return the new, fully parsed annotated tag object.
- * @throws Exception
- */
- public RevTag tag(String name, RevObject dst) throws Exception {
- final TagBuilder t = new TagBuilder();
- t.setObjectId(dst);
- t.setTag(name);
- t.setTagger(new PersonIdent(defaultCommitter, getDate()));
- t.setMessage("");
- ObjectId id;
- try (ObjectInserter ins = inserter) {
- id = ins.insert(t);
- ins.flush();
- }
- return pool.parseTag(id);
- }
-
- /**
- * Update a reference to point to an object.
- *
- * @param ref
- * the name of the reference to update to. If {@code ref} does
- * not start with {@code refs/} and is not the magic names
- * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
- * {@code refs/heads/} will be prefixed in front of the given
- * name, thereby assuming it is a branch.
- * @param to
- * the target object.
- * @return the target object.
- * @throws Exception
- */
- public RevCommit update(String ref, CommitBuilder to) throws Exception {
- return update(ref, to.create());
- }
-
- /**
- * Amend an existing ref.
- *
- * @param ref
- * the name of the reference to amend, which must already exist.
- * If {@code ref} does not start with {@code refs/} and is not the
- * magic names {@code HEAD} {@code FETCH_HEAD} or {@code
- * MERGE_HEAD}, then {@code refs/heads/} will be prefixed in front
- * of the given name, thereby assuming it is a branch.
- * @return commit builder that amends the branch on commit.
- * @throws Exception
- */
- public CommitBuilder amendRef(String ref) throws Exception {
- String name = normalizeRef(ref);
- Ref r = db.exactRef(name);
- if (r == null)
- throw new IOException("Not a ref: " + ref);
- return amend(pool.parseCommit(r.getObjectId()), branch(name).commit());
- }
-
- /**
- * Amend an existing commit.
- *
- * @param id
- * the id of the commit to amend.
- * @return commit builder.
- * @throws Exception
- */
- public CommitBuilder amend(AnyObjectId id) throws Exception {
- return amend(pool.parseCommit(id), commit());
- }
-
- private CommitBuilder amend(RevCommit old, CommitBuilder b) throws Exception {
- pool.parseBody(old);
- b.author(old.getAuthorIdent());
- b.committer(old.getCommitterIdent());
- b.message(old.getFullMessage());
- // Use the committer name from the old commit, but update it after ticking
- // the clock in CommitBuilder#create().
- b.updateCommitterTime = true;
-
- // Reset parents to original parents.
- b.noParents();
- for (int i = 0; i < old.getParentCount(); i++)
- b.parent(old.getParent(i));
-
- // Reset tree to original tree; resetting parents reset tree contents to the
- // first parent.
- b.tree.clear();
- try (TreeWalk tw = new TreeWalk(db)) {
- tw.reset(old.getTree());
- tw.setRecursive(true);
- while (tw.next()) {
- b.edit(new PathEdit(tw.getPathString()) {
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(tw.getFileMode(0));
- ent.setObjectId(tw.getObjectId(0));
- }
- });
- }
- }
-
- return b;
- }
-
- /**
- * Update a reference to point to an object.
- *
- * @param <T>
- * type of the target object.
- * @param ref
- * the name of the reference to update to. If {@code ref} does
- * not start with {@code refs/} and is not the magic names
- * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
- * {@code refs/heads/} will be prefixed in front of the given
- * name, thereby assuming it is a branch.
- * @param obj
- * the target object.
- * @return the target object.
- * @throws Exception
- */
- public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
- ref = normalizeRef(ref);
- RefUpdate u = db.updateRef(ref);
- u.setNewObjectId(obj);
- switch (u.forceUpdate()) {
- case FAST_FORWARD:
- case FORCED:
- case NEW:
- case NO_CHANGE:
- updateServerInfo();
- return obj;
-
- default:
- throw new IOException("Cannot write " + ref + " " + u.getResult());
- }
- }
-
- /**
- * Delete a reference.
- *
- * @param ref
- * the name of the reference to delete. This is normalized
- * in the same way as {@link #update(String, AnyObjectId)}.
- * @throws Exception
- * @since 4.4
- */
- public void delete(String ref) throws Exception {
- ref = normalizeRef(ref);
- RefUpdate u = db.updateRef(ref);
- u.setForceUpdate(true);
- switch (u.delete()) {
- case FAST_FORWARD:
- case FORCED:
- case NEW:
- case NO_CHANGE:
- updateServerInfo();
- return;
-
- default:
- throw new IOException("Cannot delete " + ref + " " + u.getResult());
- }
- }
-
- private static String normalizeRef(String ref) {
- if (Constants.HEAD.equals(ref)) {
- // nothing
- } else if ("FETCH_HEAD".equals(ref)) {
- // nothing
- } else if ("MERGE_HEAD".equals(ref)) {
- // nothing
- } else if (ref.startsWith(Constants.R_REFS)) {
- // nothing
- } else
- ref = Constants.R_HEADS + ref;
- return ref;
- }
-
- /**
- * Soft-reset HEAD to a detached state.
- *
- * @param id
- * ID of detached head.
- * @throws Exception
- * @see #reset(String)
- */
- public void reset(AnyObjectId id) throws Exception {
- RefUpdate ru = db.updateRef(Constants.HEAD, true);
- ru.setNewObjectId(id);
- RefUpdate.Result result = ru.forceUpdate();
- switch (result) {
- case FAST_FORWARD:
- case FORCED:
- case NEW:
- case NO_CHANGE:
- break;
- default:
- throw new IOException(String.format(
- "Checkout \"%s\" failed: %s", id.name(), result));
- }
- }
-
- /**
- * Soft-reset HEAD to a different commit.
- * <p>
- * This is equivalent to {@code git reset --soft} in that it modifies HEAD but
- * not the index or the working tree of a non-bare repository.
- *
- * @param name
- * revision string; either an existing ref name, or something that
- * can be parsed to an object ID.
- * @throws Exception
- */
- public void reset(String name) throws Exception {
- RefUpdate.Result result;
- ObjectId id = db.resolve(name);
- if (id == null)
- throw new IOException("Not a revision: " + name);
- RefUpdate ru = db.updateRef(Constants.HEAD, false);
- ru.setNewObjectId(id);
- result = ru.forceUpdate();
- switch (result) {
- case FAST_FORWARD:
- case FORCED:
- case NEW:
- case NO_CHANGE:
- break;
- default:
- throw new IOException(String.format(
- "Checkout \"%s\" failed: %s", name, result));
- }
- }
-
- /**
- * Cherry-pick a commit onto HEAD.
- * <p>
- * This differs from {@code git cherry-pick} in that it works in a bare
- * repository. As a result, any merge failure results in an exception, as
- * there is no way to recover.
- *
- * @param id
- * commit-ish to cherry-pick.
- * @return the new, fully parsed commit, or null if no work was done due to
- * the resulting tree being identical.
- * @throws Exception
- */
- public RevCommit cherryPick(AnyObjectId id) throws Exception {
- RevCommit commit = pool.parseCommit(id);
- pool.parseBody(commit);
- if (commit.getParentCount() != 1)
- throw new IOException(String.format(
- "Expected 1 parent for %s, found: %s",
- id.name(), Arrays.asList(commit.getParents())));
- RevCommit parent = commit.getParent(0);
- pool.parseHeaders(parent);
-
- Ref headRef = db.exactRef(Constants.HEAD);
- if (headRef == null)
- throw new IOException("Missing HEAD");
- RevCommit head = pool.parseCommit(headRef.getObjectId());
-
- ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
- merger.setBase(parent.getTree());
- if (merger.merge(head, commit)) {
- if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId()))
- return null;
- tick(1);
- org.eclipse.jgit.lib.CommitBuilder b =
- new org.eclipse.jgit.lib.CommitBuilder();
- b.setParentId(head);
- b.setTreeId(merger.getResultTreeId());
- b.setAuthor(commit.getAuthorIdent());
- b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
- b.setMessage(commit.getFullMessage());
- ObjectId result;
- try (ObjectInserter ins = inserter) {
- result = ins.insert(b);
- ins.flush();
- }
- update(Constants.HEAD, result);
- return pool.parseCommit(result);
- }
- throw new IOException("Merge conflict");
- }
-
- /**
- * Update the dumb client server info files.
- *
- * @throws Exception
- */
- public void updateServerInfo() throws Exception {
- if (db instanceof FileRepository) {
- final FileRepository fr = (FileRepository) db;
- RefWriter rw = new RefWriter(fr.getRefDatabase().getRefs()) {
- @Override
- protected void writeFile(String name, byte[] bin)
- throws IOException {
- File path = new File(fr.getDirectory(), name);
- TestRepository.this.writeFile(path, bin);
- }
- };
- rw.writePackedRefs();
- rw.writeInfoRefs();
-
- final StringBuilder w = new StringBuilder();
- for (Pack p : fr.getObjectDatabase().getPacks()) {
- w.append("P ");
- w.append(p.getPackFile().getName());
- w.append('\n');
- }
- writeFile(new File(new File(fr.getObjectDatabase().getDirectory(),
- "info"), "packs"), Constants.encodeASCII(w.toString()));
- }
- }
-
- /**
- * Ensure the body of the given object has been parsed.
- *
- * @param <T>
- * type of object, e.g. {@link org.eclipse.jgit.revwalk.RevTag}
- * or {@link org.eclipse.jgit.revwalk.RevCommit}.
- * @param object
- * reference to the (possibly unparsed) object to force body
- * parsing of.
- * @return {@code object}
- * @throws Exception
- */
- public <T extends RevObject> T parseBody(T object) throws Exception {
- pool.parseBody(object);
- return object;
- }
-
- /**
- * Create a new branch builder for this repository.
- *
- * @param ref
- * name of the branch to be constructed. If {@code ref} does not
- * start with {@code refs/} the prefix {@code refs/heads/} will
- * be added.
- * @return builder for the named branch.
- */
- public BranchBuilder branch(String ref) {
- if (Constants.HEAD.equals(ref)) {
- // nothing
- } else if (ref.startsWith(Constants.R_REFS)) {
- // nothing
- } else
- ref = Constants.R_HEADS + ref;
- return new BranchBuilder(ref);
- }
-
- /**
- * Tag an object using a lightweight tag.
- *
- * @param name
- * the tag name. The /refs/tags/ prefix will be added if the name
- * doesn't start with it
- * @param obj
- * the object to tag
- * @return the tagged object
- * @throws Exception
- */
- public ObjectId lightweightTag(String name, ObjectId obj) throws Exception {
- if (!name.startsWith(Constants.R_TAGS))
- name = Constants.R_TAGS + name;
- return update(name, obj);
- }
-
- /**
- * Run consistency checks against the object database.
- * <p>
- * This method completes silently if the checks pass. A temporary revision
- * pool is constructed during the checking.
- *
- * @param tips
- * the tips to start checking from; if not supplied the refs of
- * the repository are used instead.
- * @throws MissingObjectException
- * @throws IncorrectObjectTypeException
- * @throws IOException
- */
- public void fsck(RevObject... tips) throws MissingObjectException,
- IncorrectObjectTypeException, IOException {
- try (ObjectWalk ow = new ObjectWalk(db)) {
- if (tips.length != 0) {
- for (RevObject o : tips)
- ow.markStart(ow.parseAny(o));
- } else {
- for (Ref r : db.getRefDatabase().getRefs())
- ow.markStart(ow.parseAny(r.getObjectId()));
- }
-
- ObjectChecker oc = new ObjectChecker();
- for (;;) {
- final RevCommit o = ow.next();
- if (o == null)
- break;
-
- final byte[] bin = db.open(o, o.getType()).getCachedBytes();
- oc.checkCommit(o, bin);
- assertHash(o, bin);
- }
-
- for (;;) {
- final RevObject o = ow.nextObject();
- if (o == null)
- break;
-
- final byte[] bin = db.open(o, o.getType()).getCachedBytes();
- oc.check(o, o.getType(), bin);
- assertHash(o, bin);
- }
- }
- }
-
- private static void assertHash(RevObject id, byte[] bin) {
- MessageDigest md = Constants.newMessageDigest();
- md.update(Constants.encodedTypeString(id.getType()));
- md.update((byte) ' ');
- md.update(Constants.encodeASCII(bin.length));
- md.update((byte) 0);
- md.update(bin);
- assertEquals(id, ObjectId.fromRaw(md.digest()));
- }
-
- /**
- * Pack all reachable objects in the repository into a single pack file.
- * <p>
- * All loose objects are automatically pruned. Existing packs however are
- * not removed.
- *
- * @throws Exception
- */
- public void packAndPrune() throws Exception {
- if (db.getObjectDatabase() instanceof ObjectDirectory) {
- ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
- NullProgressMonitor m = NullProgressMonitor.INSTANCE;
-
- final PackFile pack, idx;
- try (PackWriter pw = new PackWriter(db)) {
- Set<ObjectId> all = new HashSet<>();
- for (Ref r : db.getRefDatabase().getRefs())
- all.add(r.getObjectId());
- pw.preparePack(m, all, PackWriter.NONE);
-
- pack = new PackFile(odb.getPackDirectory(), pw.computeName(),
- PackExt.PACK);
- try (OutputStream out =
- new BufferedOutputStream(new FileOutputStream(pack))) {
- pw.writePack(m, m, out);
- }
- pack.setReadOnly();
-
- idx = pack.create(PackExt.INDEX);
- try (OutputStream out =
- new BufferedOutputStream(new FileOutputStream(idx))) {
- pw.writeIndex(out);
- }
- idx.setReadOnly();
- }
-
- odb.openPack(pack);
- updateServerInfo();
- prunePacked(odb);
- }
- }
-
- /**
- * Closes the underlying {@link Repository} object and any other internal
- * resources.
- * <p>
- * {@link AutoCloseable} resources that may escape this object, such as
- * those returned by the {@link #git} and {@link #getRevWalk()} methods are
- * not closed.
- */
- @Override
- public void close() {
- try {
- inserter.close();
- } finally {
- db.close();
- }
- }
-
- private static void prunePacked(ObjectDirectory odb) throws IOException {
- for (Pack p : odb.getPacks()) {
- for (MutableEntry e : p)
- FileUtils.delete(odb.fileFor(e.toObjectId()));
- }
- }
-
- private void writeFile(File p, byte[] bin) throws IOException,
- ObjectWritingException {
- final LockFile lck = new LockFile(p);
- if (!lck.lock())
- throw new ObjectWritingException("Can't write " + p);
- try {
- lck.write(bin);
- } catch (IOException ioe) {
- throw new ObjectWritingException("Can't write " + p, ioe);
- }
- if (!lck.commit())
- throw new ObjectWritingException("Can't write " + p);
- }
-
- /** Helper to build a branch with one or more commits */
- public class BranchBuilder {
- private final String ref;
-
- BranchBuilder(String ref) {
- this.ref = ref;
- }
-
- /**
- * @return construct a new commit builder that updates this branch. If
- * the branch already exists, the commit builder will have its
- * first parent as the current commit and its tree will be
- * initialized to the current files.
- * @throws Exception
- * the commit builder can't read the current branch state
- */
- public CommitBuilder commit() throws Exception {
- return new CommitBuilder(this);
- }
-
- /**
- * Forcefully update this branch to a particular commit.
- *
- * @param to
- * the commit to update to.
- * @return {@code to}.
- * @throws Exception
- */
- public RevCommit update(CommitBuilder to) throws Exception {
- return update(to.create());
- }
-
- /**
- * Forcefully update this branch to a particular commit.
- *
- * @param to
- * the commit to update to.
- * @return {@code to}.
- * @throws Exception
- */
- public RevCommit update(RevCommit to) throws Exception {
- return TestRepository.this.update(ref, to);
- }
-
- /**
- * Delete this branch.
- * @throws Exception
- * @since 4.4
- */
- public void delete() throws Exception {
- TestRepository.this.delete(ref);
- }
- }
-
- /** Helper to generate a commit. */
- public class CommitBuilder {
- private final BranchBuilder branch;
-
- private final DirCache tree = DirCache.newInCore();
-
- private ObjectId topLevelTree;
-
- private final List<RevCommit> parents = new ArrayList<>(2);
-
- private int tick = 1;
-
- private String message = "";
-
- private RevCommit self;
-
- private PersonIdent author;
- private PersonIdent committer;
-
- private String changeId;
-
- private boolean updateCommitterTime;
-
- CommitBuilder() {
- branch = null;
- }
-
- CommitBuilder(BranchBuilder b) throws Exception {
- branch = b;
-
- Ref ref = db.exactRef(branch.ref);
- if (ref != null && ref.getObjectId() != null)
- parent(pool.parseCommit(ref.getObjectId()));
- }
-
- CommitBuilder(CommitBuilder prior) throws Exception {
- branch = prior.branch;
-
- DirCacheBuilder b = tree.builder();
- for (int i = 0; i < prior.tree.getEntryCount(); i++)
- b.add(prior.tree.getEntry(i));
- b.finish();
-
- parents.add(prior.create());
- }
-
- /**
- * set parent commit
- *
- * @param p
- * parent commit
- * @return this commit builder
- * @throws Exception
- */
- public CommitBuilder parent(RevCommit p) throws Exception {
- if (parents.isEmpty()) {
- DirCacheBuilder b = tree.builder();
- parseBody(p);
- b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool
- .getObjectReader(), p.getTree());
- b.finish();
- }
- parents.add(p);
- return this;
- }
-
- /**
- * Get parent commits
- *
- * @return parent commits
- */
- public List<RevCommit> parents() {
- return Collections.unmodifiableList(parents);
- }
-
- /**
- * Remove parent commits
- *
- * @return this commit builder
- */
- public CommitBuilder noParents() {
- parents.clear();
- return this;
- }
-
- /**
- * Remove files
- *
- * @return this commit builder
- */
- public CommitBuilder noFiles() {
- tree.clear();
- return this;
- }
-
- /**
- * Set top level tree
- *
- * @param treeId
- * the top level tree
- * @return this commit builder
- */
- public CommitBuilder setTopLevelTree(ObjectId treeId) {
- topLevelTree = treeId;
- return this;
- }
-
- /**
- * Add file with given content
- *
- * @param path
- * path of the file
- * @param content
- * the file content
- * @return this commit builder
- * @throws Exception
- */
- public CommitBuilder add(String path, String content) throws Exception {
- return add(path, blob(content));
- }
-
- /**
- * Add file with given path and blob
- *
- * @param path
- * path of the file
- * @param id
- * blob for this file
- * @return this commit builder
- * @throws Exception
- */
- public CommitBuilder add(String path, RevBlob id)
- throws Exception {
- return edit(new PathEdit(path) {
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(FileMode.REGULAR_FILE);
- ent.setObjectId(id);
- }
- });
- }
-
- /**
- * Edit the index
- *
- * @param edit
- * the index record update
- * @return this commit builder
- */
- public CommitBuilder edit(PathEdit edit) {
- DirCacheEditor e = tree.editor();
- e.add(edit);
- e.finish();
- return this;
- }
-
- /**
- * Remove a file
- *
- * @param path
- * path of the file
- * @return this commit builder
- */
- public CommitBuilder rm(String path) {
- DirCacheEditor e = tree.editor();
- e.add(new DeletePath(path));
- e.add(new DeleteTree(path));
- e.finish();
- return this;
- }
-
- /**
- * Set commit message
- *
- * @param m
- * the message
- * @return this commit builder
- */
- public CommitBuilder message(String m) {
- message = m;
- return this;
- }
-
- /**
- * Get the commit message
- *
- * @return the commit message
- */
- public String message() {
- return message;
- }
-
- /**
- * Tick the clock
- *
- * @param secs
- * number of seconds
- * @return this commit builder
- */
- public CommitBuilder tick(int secs) {
- tick = secs;
- return this;
- }
-
- /**
- * Set author and committer identity
- *
- * @param ident
- * identity to set
- * @return this commit builder
- */
- public CommitBuilder ident(PersonIdent ident) {
- author = ident;
- committer = ident;
- return this;
- }
-
- /**
- * Set the author identity
- *
- * @param a
- * the author's identity
- * @return this commit builder
- */
- public CommitBuilder author(PersonIdent a) {
- author = a;
- return this;
- }
-
- /**
- * Get the author identity
- *
- * @return the author identity
- */
- public PersonIdent author() {
- return author;
- }
-
- /**
- * Set the committer identity
- *
- * @param c
- * the committer identity
- * @return this commit builder
- */
- public CommitBuilder committer(PersonIdent c) {
- committer = c;
- return this;
- }
-
- /**
- * Get the committer identity
- *
- * @return the committer identity
- */
- public PersonIdent committer() {
- return committer;
- }
-
- /**
- * Insert changeId
- *
- * @return this commit builder
- */
- public CommitBuilder insertChangeId() {
- changeId = "";
- return this;
- }
-
- /**
- * Insert given changeId
- *
- * @param c
- * changeId
- * @return this commit builder
- */
- public CommitBuilder insertChangeId(String c) {
- // Validate, but store as a string so we can use "" as a sentinel.
- ObjectId.fromString(c);
- changeId = c;
- return this;
- }
-
- /**
- * Create the commit
- *
- * @return the new commit
- * @throws Exception
- * if creation failed
- */
- public RevCommit create() throws Exception {
- if (self == null) {
- TestRepository.this.tick(tick);
-
- final org.eclipse.jgit.lib.CommitBuilder c;
-
- c = new org.eclipse.jgit.lib.CommitBuilder();
- c.setParentIds(parents);
- setAuthorAndCommitter(c);
- if (author != null)
- c.setAuthor(author);
- if (committer != null) {
- if (updateCommitterTime)
- committer = new PersonIdent(committer, getDate());
- c.setCommitter(committer);
- }
-
- ObjectId commitId;
- try (ObjectInserter ins = inserter) {
- if (topLevelTree != null)
- c.setTreeId(topLevelTree);
- else
- c.setTreeId(tree.writeTree(ins));
- insertChangeId(c);
- c.setMessage(message);
- commitId = ins.insert(c);
- ins.flush();
- }
- self = pool.parseCommit(commitId);
-
- if (branch != null)
- branch.update(self);
- }
- return self;
- }
-
- private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
- if (changeId == null)
- return;
- int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
- if (idx >= 0)
- return;
-
- ObjectId firstParentId = null;
- if (!parents.isEmpty())
- firstParentId = parents.get(0);
-
- ObjectId cid;
- if (changeId.isEmpty())
- cid = ChangeIdUtil.computeChangeId(c.getTreeId(), firstParentId,
- c.getAuthor(), c.getCommitter(), message);
- else
- cid = ObjectId.fromString(changeId);
- message = ChangeIdUtil.insertId(message, cid);
- if (cid != null)
- message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
- + ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
- + cid.getName() + "\n"); //$NON-NLS-1$
- }
-
- /**
- * Create child commit builder
- *
- * @return child commit builder
- * @throws Exception
- */
- public CommitBuilder child() throws Exception {
- return new CommitBuilder(this);
- }
- }
- }
|