![JAR search and dependency download from the Maven repository](/logo.png)
org.openrewrite.jgit.lib.Repository Maven / Gradle / Ivy
Show all versions of jgit Show documentation
/*
* Copyright (C) 2007, Dave Watson
* Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2006-2010, Robin Rosenberg
* Copyright (C) 2006-2012, Shawn O. Pearce
* Copyright (C) 2012, Daniel Megert
* Copyright (C) 2017, Wim Jongman 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.openrewrite.jgit.lib;
import static org.openrewrite.jgit.lib.Constants.LOCK_SUFFIX;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import org.openrewrite.jgit.annotations.NonNull;
import org.openrewrite.jgit.annotations.Nullable;
import org.openrewrite.jgit.attributes.AttributesNodeProvider;
import org.openrewrite.jgit.dircache.DirCache;
import org.openrewrite.jgit.errors.AmbiguousObjectException;
import org.openrewrite.jgit.errors.CorruptObjectException;
import org.openrewrite.jgit.errors.IncorrectObjectTypeException;
import org.openrewrite.jgit.errors.MissingObjectException;
import org.openrewrite.jgit.errors.NoWorkTreeException;
import org.openrewrite.jgit.errors.RevisionSyntaxException;
import org.openrewrite.jgit.events.IndexChangedEvent;
import org.openrewrite.jgit.events.IndexChangedListener;
import org.openrewrite.jgit.events.ListenerList;
import org.openrewrite.jgit.events.RepositoryEvent;
import org.openrewrite.jgit.internal.JGitText;
import org.openrewrite.jgit.revwalk.RevBlob;
import org.openrewrite.jgit.revwalk.RevCommit;
import org.openrewrite.jgit.revwalk.RevObject;
import org.openrewrite.jgit.revwalk.RevTree;
import org.openrewrite.jgit.revwalk.RevWalk;
import org.openrewrite.jgit.transport.RefSpec;
import org.openrewrite.jgit.transport.RemoteConfig;
import org.openrewrite.jgit.treewalk.TreeWalk;
import org.openrewrite.jgit.util.FS;
import org.openrewrite.jgit.util.FileUtils;
import org.openrewrite.jgit.util.IO;
import org.openrewrite.jgit.util.RawParseUtils;
import org.openrewrite.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a Git repository.
*
* A repository holds all objects and refs used for managing source code (could
* be any type of file, but source code is what SCM's are typically used for).
*
* The thread-safety of a {@link org.openrewrite.jgit.lib.Repository} very much
* depends on the concrete implementation. Applications working with a generic
* {@code Repository} type must not assume the instance is thread-safe.
*
* - {@code FileRepository} is thread-safe.
*
- {@code DfsRepository} thread-safety is determined by its subclass.
*
*/
public abstract class Repository implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
private static final ListenerList globalListeners = new ListenerList();
/**
* Branch names containing slashes should not have a name component that is
* one of the reserved device names on Windows.
*
* @see #normalizeBranchName(String)
*/
private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern
.compile(
"(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?", //$NON-NLS-1$
Pattern.CASE_INSENSITIVE);
/**
* Get the global listener list observing all events in this JVM.
*
* @return the global listener list observing all events in this JVM.
*/
public static ListenerList getGlobalListenerList() {
return globalListeners;
}
/** Use counter */
final AtomicInteger useCnt = new AtomicInteger(1);
final AtomicLong closedAt = new AtomicLong();
/** Metadata directory holding the repository's critical files. */
private final File gitDir;
/** File abstraction used to resolve paths. */
private final FS fs;
private final ListenerList myListeners = new ListenerList();
/** If not bare, the top level directory of the working files. */
private final File workTree;
/** If not bare, the index file caching the working file states. */
private final File indexFile;
private final String initialBranch;
/**
* Initialize a new repository instance.
*
* @param options
* options to configure the repository.
*/
protected Repository(BaseRepositoryBuilder options) {
gitDir = options.getGitDir();
fs = options.getFS();
workTree = options.getWorkTree();
indexFile = options.getIndexFile();
initialBranch = options.getInitialBranch();
}
/**
* Get listeners observing only events on this repository.
*
* @return listeners observing only events on this repository.
*/
@NonNull
public ListenerList getListenerList() {
return myListeners;
}
/**
* Fire an event to all registered listeners.
*
* The source repository of the event is automatically set to this
* repository, before the event is delivered to any listeners.
*
* @param event
* the event to deliver.
*/
public void fireEvent(RepositoryEvent> event) {
event.setRepository(this);
myListeners.dispatch(event);
globalListeners.dispatch(event);
}
/**
* Create a new Git repository.
*
* Repository with working tree is created using this method. This method is
* the same as {@code create(false)}.
*
* @throws java.io.IOException
* @see #create(boolean)
*/
public void create() throws IOException {
create(false);
}
/**
* Create a new Git repository initializing the necessary files and
* directories.
*
* @param bare
* if true, a bare repository (a repository without a working
* directory) is created.
* @throws java.io.IOException
* in case of IO problem
*/
public abstract void create(boolean bare) throws IOException;
/**
* Get local metadata directory
*
* @return local metadata directory; {@code null} if repository isn't local.
*/
/*
* TODO This method should be annotated as Nullable, because in some
* specific configurations metadata is not located in the local file system
* (for example in memory databases). In "usual" repositories this
* annotation would only cause compiler errors at places where the actual
* directory can never be null.
*/
public File getDirectory() {
return gitDir;
}
/**
* Get repository identifier.
*
* @return repository identifier. The returned identifier has to be unique
* within a given Git server.
* @since 5.4
*/
public abstract String getIdentifier();
/**
* Get the object database which stores this repository's data.
*
* @return the object database which stores this repository's data.
*/
@NonNull
public abstract ObjectDatabase getObjectDatabase();
/**
* Create a new inserter to create objects in {@link #getObjectDatabase()}.
*
* @return a new inserter to create objects in {@link #getObjectDatabase()}.
*/
@NonNull
public ObjectInserter newObjectInserter() {
return getObjectDatabase().newInserter();
}
/**
* Create a new reader to read objects from {@link #getObjectDatabase()}.
*
* @return a new reader to read objects from {@link #getObjectDatabase()}.
*/
@NonNull
public ObjectReader newObjectReader() {
return getObjectDatabase().newReader();
}
/**
* Get the reference database which stores the reference namespace.
*
* @return the reference database which stores the reference namespace.
*/
@NonNull
public abstract RefDatabase getRefDatabase();
/**
* Get the configuration of this repository.
*
* @return the configuration of this repository.
*/
@NonNull
public abstract StoredConfig getConfig();
/**
* Create a new {@link org.openrewrite.jgit.attributes.AttributesNodeProvider}.
*
* @return a new {@link org.openrewrite.jgit.attributes.AttributesNodeProvider}.
* This {@link org.openrewrite.jgit.attributes.AttributesNodeProvider}
* is lazy loaded only once. It means that it will not be updated
* after loading. Prefer creating new instance for each use.
* @since 4.2
*/
@NonNull
public abstract AttributesNodeProvider createAttributesNodeProvider();
/**
* Get the used file system abstraction.
*
* @return the used file system abstraction, or {@code null} if
* repository isn't local.
*/
/*
* TODO This method should be annotated as Nullable, because in some
* specific configurations metadata is not located in the local file system
* (for example in memory databases). In "usual" repositories this
* annotation would only cause compiler errors at places where the actual
* directory can never be null.
*/
public FS getFS() {
return fs;
}
/**
* Whether the specified object is stored in this repo or any of the known
* shared repositories.
*
* @param objectId
* a {@link org.openrewrite.jgit.lib.AnyObjectId} object.
* @return true if the specified object is stored in this repo or any of the
* known shared repositories.
* @deprecated use {@code getObjectDatabase().has(objectId)}
*/
@Deprecated
public boolean hasObject(AnyObjectId objectId) {
try {
return getObjectDatabase().has(objectId);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Open an object from this repository.
*
* This is a one-shot call interface which may be faster than allocating a
* {@link #newObjectReader()} to perform the lookup.
*
* @param objectId
* identity of the object to open.
* @return a {@link org.openrewrite.jgit.lib.ObjectLoader} for accessing the
* object.
* @throws org.openrewrite.jgit.errors.MissingObjectException
* the object does not exist.
* @throws java.io.IOException
* the object store cannot be accessed.
*/
@NonNull
public ObjectLoader open(AnyObjectId objectId)
throws MissingObjectException, IOException {
return getObjectDatabase().open(objectId);
}
/**
* Open an object from this repository.
*
* This is a one-shot call interface which may be faster than allocating a
* {@link #newObjectReader()} to perform the lookup.
*
* @param objectId
* identity of the object to open.
* @param typeHint
* hint about the type of object being requested, e.g.
* {@link org.openrewrite.jgit.lib.Constants#OBJ_BLOB};
* {@link org.openrewrite.jgit.lib.ObjectReader#OBJ_ANY} if the
* object type is not known, or does not matter to the caller.
* @return a {@link org.openrewrite.jgit.lib.ObjectLoader} for accessing the
* object.
* @throws org.openrewrite.jgit.errors.MissingObjectException
* the object does not exist.
* @throws org.openrewrite.jgit.errors.IncorrectObjectTypeException
* typeHint was not OBJ_ANY, and the object's actual type does
* not match typeHint.
* @throws java.io.IOException
* the object store cannot be accessed.
*/
@NonNull
public ObjectLoader open(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
return getObjectDatabase().open(objectId, typeHint);
}
/**
* Create a command to update, create or delete a ref in this repository.
*
* @param ref
* name of the ref the caller wants to modify.
* @return an update command. The caller must finish populating this command
* and then invoke one of the update methods to actually make a
* change.
* @throws java.io.IOException
* a symbolic ref was passed in and could not be resolved back
* to the base ref, as the symbolic ref could not be read.
*/
@NonNull
public RefUpdate updateRef(String ref) throws IOException {
return updateRef(ref, false);
}
/**
* Create a command to update, create or delete a ref in this repository.
*
* @param ref
* name of the ref the caller wants to modify.
* @param detach
* true to create a detached head
* @return an update command. The caller must finish populating this command
* and then invoke one of the update methods to actually make a
* change.
* @throws java.io.IOException
* a symbolic ref was passed in and could not be resolved back
* to the base ref, as the symbolic ref could not be read.
*/
@NonNull
public RefUpdate updateRef(String ref, boolean detach) throws IOException {
return getRefDatabase().newUpdate(ref, detach);
}
/**
* Create a command to rename a ref in this repository
*
* @param fromRef
* name of ref to rename from
* @param toRef
* name of ref to rename to
* @return an update command that knows how to rename a branch to another.
* @throws java.io.IOException
* the rename could not be performed.
*/
@NonNull
public RefRename renameRef(String fromRef, String toRef) throws IOException {
return getRefDatabase().newRename(fromRef, toRef);
}
/**
* Parse a git revision string and return an object id.
*
* Combinations of these operators are supported:
*
* - HEAD, MERGE_HEAD, FETCH_HEAD
* - SHA-1: a complete or abbreviated SHA-1
* - refs/...: a complete reference name
* - short-name: a short reference name under {@code refs/heads},
* {@code refs/tags}, or {@code refs/remotes} namespace
* - tag-NN-gABBREV: output from describe, parsed by treating
* {@code ABBREV} as an abbreviated SHA-1.
* - id^: first parent of commit id, this is the same
* as {@code id^1}
* - id^0: ensure id is a commit
* - id^n: n-th parent of commit id
* - id~n: n-th historical ancestor of id, by first
* parent. {@code id~3} is equivalent to {@code id^1^1^1} or {@code id^^^}.
* - id:path: Lookup path under tree named by id
* - id^{commit}: ensure id is a commit
* - id^{tree}: ensure id is a tree
* - id^{tag}: ensure id is a tag
* - id^{blob}: ensure id is a blob
*
*
*
* The following operators are specified by Git conventions, but are not
* supported by this method:
*
* - ref@{n}: n-th version of ref as given by its reflog
* - ref@{time}: value of ref at the designated time
*
*
* @param revstr
* A git object references expression
* @return an ObjectId or {@code null} if revstr can't be resolved to any
* ObjectId
* @throws org.openrewrite.jgit.errors.AmbiguousObjectException
* {@code revstr} contains an abbreviated ObjectId and this
* repository contains more than one object which match to the
* input abbreviation.
* @throws org.openrewrite.jgit.errors.IncorrectObjectTypeException
* the id parsed does not meet the type required to finish
* applying the operators in the expression.
* @throws org.openrewrite.jgit.errors.RevisionSyntaxException
* the expression is not supported by this implementation, or
* does not meet the standard syntax.
* @throws java.io.IOException
* on serious errors
*/
@Nullable
public ObjectId resolve(String revstr)
throws AmbiguousObjectException, IncorrectObjectTypeException,
RevisionSyntaxException, IOException {
try (RevWalk rw = new RevWalk(this)) {
rw.setRetainBody(false);
Object resolved = resolve(rw, revstr);
if (resolved instanceof String) {
final Ref ref = findRef((String) resolved);
return ref != null ? ref.getLeaf().getObjectId() : null;
}
return (ObjectId) resolved;
}
}
/**
* Simplify an expression, but unlike {@link #resolve(String)} it will not
* resolve a branch passed or resulting from the expression, such as @{-}.
* Thus this method can be used to process an expression to a method that
* expects a branch or revision id.
*
* @param revstr a {@link java.lang.String} object.
* @return object id or ref name from resolved expression or {@code null} if
* given expression cannot be resolved
* @throws org.openrewrite.jgit.errors.AmbiguousObjectException
* @throws java.io.IOException
*/
@Nullable
public String simplify(String revstr)
throws AmbiguousObjectException, IOException {
try (RevWalk rw = new RevWalk(this)) {
rw.setRetainBody(true);
Object resolved = resolve(rw, revstr);
if (resolved != null) {
if (resolved instanceof String) {
return (String) resolved;
}
return ((AnyObjectId) resolved).getName();
}
return null;
}
}
@Nullable
private Object resolve(RevWalk rw, String revstr)
throws IOException {
char[] revChars = revstr.toCharArray();
RevObject rev = null;
String name = null;
int done = 0;
for (int i = 0; i < revChars.length; ++i) {
switch (revChars[i]) {
case '^':
if (rev == null) {
if (name == null)
if (done == 0)
name = new String(revChars, done, i);
else {
done = i + 1;
break;
}
rev = parseSimple(rw, name);
name = null;
if (rev == null)
return null;
}
if (i + 1 < revChars.length) {
switch (revChars[i + 1]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
int j;
rev = rw.parseCommit(rev);
for (j = i + 1; j < revChars.length; ++j) {
if (!Character.isDigit(revChars[j]))
break;
}
String parentnum = new String(revChars, i + 1, j - i
- 1);
int pnum;
try {
pnum = Integer.parseInt(parentnum);
} catch (NumberFormatException e) {
RevisionSyntaxException rse = new RevisionSyntaxException(
JGitText.get().invalidCommitParentNumber,
revstr);
rse.initCause(e);
throw rse;
}
if (pnum != 0) {
RevCommit commit = (RevCommit) rev;
if (pnum > commit.getParentCount())
rev = null;
else
rev = commit.getParent(pnum - 1);
}
i = j - 1;
done = j;
break;
case '{':
int k;
String item = null;
for (k = i + 2; k < revChars.length; ++k) {
if (revChars[k] == '}') {
item = new String(revChars, i + 2, k - i - 2);
break;
}
}
i = k;
if (item != null)
if (item.equals("tree")) { //$NON-NLS-1$
rev = rw.parseTree(rev);
} else if (item.equals("commit")) { //$NON-NLS-1$
rev = rw.parseCommit(rev);
} else if (item.equals("blob")) { //$NON-NLS-1$
rev = rw.peel(rev);
if (!(rev instanceof RevBlob))
throw new IncorrectObjectTypeException(rev,
Constants.TYPE_BLOB);
} else if (item.isEmpty()) {
rev = rw.peel(rev);
} else
throw new RevisionSyntaxException(revstr);
else
throw new RevisionSyntaxException(revstr);
done = k;
break;
default:
rev = rw.peel(rev);
if (rev instanceof RevCommit) {
RevCommit commit = ((RevCommit) rev);
if (commit.getParentCount() == 0)
rev = null;
else
rev = commit.getParent(0);
} else
throw new IncorrectObjectTypeException(rev,
Constants.TYPE_COMMIT);
}
} else {
rev = rw.peel(rev);
if (rev instanceof RevCommit) {
RevCommit commit = ((RevCommit) rev);
if (commit.getParentCount() == 0)
rev = null;
else
rev = commit.getParent(0);
} else
throw new IncorrectObjectTypeException(rev,
Constants.TYPE_COMMIT);
}
done = i + 1;
break;
case '~':
if (rev == null) {
if (name == null)
if (done == 0)
name = new String(revChars, done, i);
else {
done = i + 1;
break;
}
rev = parseSimple(rw, name);
name = null;
if (rev == null)
return null;
}
rev = rw.peel(rev);
if (!(rev instanceof RevCommit))
throw new IncorrectObjectTypeException(rev,
Constants.TYPE_COMMIT);
int l;
for (l = i + 1; l < revChars.length; ++l) {
if (!Character.isDigit(revChars[l]))
break;
}
int dist;
if (l - i > 1) {
String distnum = new String(revChars, i + 1, l - i - 1);
try {
dist = Integer.parseInt(distnum);
} catch (NumberFormatException e) {
RevisionSyntaxException rse = new RevisionSyntaxException(
JGitText.get().invalidAncestryLength, revstr);
rse.initCause(e);
throw rse;
}
} else
dist = 1;
while (dist > 0) {
RevCommit commit = (RevCommit) rev;
if (commit.getParentCount() == 0) {
rev = null;
break;
}
commit = commit.getParent(0);
rw.parseHeaders(commit);
rev = commit;
--dist;
}
i = l - 1;
done = l;
break;
case '@':
if (rev != null)
throw new RevisionSyntaxException(revstr);
if (i + 1 == revChars.length)
continue;
if (i + 1 < revChars.length && revChars[i + 1] != '{')
continue;
int m;
String time = null;
for (m = i + 2; m < revChars.length; ++m) {
if (revChars[m] == '}') {
time = new String(revChars, i + 2, m - i - 2);
break;
}
}
if (time != null) {
if (time.equals("upstream")) { //$NON-NLS-1$
if (name == null)
name = new String(revChars, done, i);
if (name.isEmpty())
// Currently checked out branch, HEAD if
// detached
name = Constants.HEAD;
if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
throw new RevisionSyntaxException(MessageFormat
.format(JGitText.get().invalidRefName,
name),
revstr);
Ref ref = findRef(name);
name = null;
if (ref == null)
return null;
if (ref.isSymbolic())
ref = ref.getLeaf();
name = ref.getName();
RemoteConfig remoteConfig;
try {
remoteConfig = new RemoteConfig(getConfig(),
"origin"); //$NON-NLS-1$
} catch (URISyntaxException e) {
RevisionSyntaxException rse = new RevisionSyntaxException(
revstr);
rse.initCause(e);
throw rse;
}
String remoteBranchName = getConfig()
.getString(
ConfigConstants.CONFIG_BRANCH_SECTION,
Repository.shortenRefName(ref.getName()),
ConfigConstants.CONFIG_KEY_MERGE);
List fetchRefSpecs = remoteConfig
.getFetchRefSpecs();
for (RefSpec refSpec : fetchRefSpecs) {
if (refSpec.matchSource(remoteBranchName)) {
RefSpec expandFromSource = refSpec
.expandFromSource(remoteBranchName);
name = expandFromSource.getDestination();
break;
}
}
if (name == null)
throw new RevisionSyntaxException(revstr);
} else if (time.matches("^-\\d+$")) { //$NON-NLS-1$
if (name != null) {
throw new RevisionSyntaxException(revstr);
}
String previousCheckout = resolveReflogCheckout(
-Integer.parseInt(time));
if (ObjectId.isId(previousCheckout)) {
rev = parseSimple(rw, previousCheckout);
} else {
name = previousCheckout;
}
} else {
if (name == null)
name = new String(revChars, done, i);
if (name.isEmpty())
name = Constants.HEAD;
if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
throw new RevisionSyntaxException(MessageFormat
.format(JGitText.get().invalidRefName,
name),
revstr);
Ref ref = findRef(name);
name = null;
if (ref == null)
return null;
// @{n} means current branch, not HEAD@{1} unless
// detached
if (ref.isSymbolic())
ref = ref.getLeaf();
rev = resolveReflog(rw, ref, time);
}
i = m;
} else
throw new RevisionSyntaxException(revstr);
break;
case ':': {
RevTree tree;
if (rev == null) {
if (name == null)
name = new String(revChars, done, i);
if (name.isEmpty())
name = Constants.HEAD;
rev = parseSimple(rw, name);
name = null;
}
if (rev == null)
return null;
tree = rw.parseTree(rev);
if (i == revChars.length - 1)
return tree.copy();
TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(),
new String(revChars, i + 1, revChars.length - i - 1),
tree);
return tw != null ? tw.getObjectId(0) : null;
}
default:
if (rev != null)
throw new RevisionSyntaxException(revstr);
}
}
if (rev != null)
return rev.copy();
if (name != null)
return name;
if (done == revstr.length())
return null;
name = revstr.substring(done);
if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
throw new RevisionSyntaxException(
MessageFormat.format(JGitText.get().invalidRefName, name),
revstr);
if (findRef(name) != null)
return name;
return resolveSimple(name);
}
private static boolean isHex(char c) {
return ('0' <= c && c <= '9') //
|| ('a' <= c && c <= 'f') //
|| ('A' <= c && c <= 'F');
}
private static boolean isAllHex(String str, int ptr) {
while (ptr < str.length()) {
if (!isHex(str.charAt(ptr++)))
return false;
}
return true;
}
@Nullable
private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
ObjectId id = resolveSimple(revstr);
return id != null ? rw.parseAny(id) : null;
}
@Nullable
private ObjectId resolveSimple(String revstr) throws IOException {
if (ObjectId.isId(revstr))
return ObjectId.fromString(revstr);
if (Repository.isValidRefName("x/" + revstr)) { //$NON-NLS-1$
Ref r = getRefDatabase().findRef(revstr);
if (r != null)
return r.getObjectId();
}
if (AbbreviatedObjectId.isId(revstr))
return resolveAbbreviation(revstr);
int dashg = revstr.indexOf("-g"); //$NON-NLS-1$
if ((dashg + 5) < revstr.length() && 0 <= dashg
&& isHex(revstr.charAt(dashg + 2))
&& isHex(revstr.charAt(dashg + 3))
&& isAllHex(revstr, dashg + 4)) {
// Possibly output from git describe?
String s = revstr.substring(dashg + 2);
if (AbbreviatedObjectId.isId(s))
return resolveAbbreviation(s);
}
return null;
}
@Nullable
private String resolveReflogCheckout(int checkoutNo)
throws IOException {
ReflogReader reader = getReflogReader(Constants.HEAD);
if (reader == null) {
return null;
}
List reflogEntries = reader.getReverseEntries();
for (ReflogEntry entry : reflogEntries) {
CheckoutEntry checkout = entry.parseCheckout();
if (checkout != null)
if (checkoutNo-- == 1)
return checkout.getFromBranch();
}
return null;
}
private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
throws IOException {
int number;
try {
number = Integer.parseInt(time);
} catch (NumberFormatException nfe) {
RevisionSyntaxException rse = new RevisionSyntaxException(
MessageFormat.format(JGitText.get().invalidReflogRevision,
time));
rse.initCause(nfe);
throw rse;
}
assert number >= 0;
ReflogReader reader = getReflogReader(ref.getName());
if (reader == null) {
throw new RevisionSyntaxException(
MessageFormat.format(JGitText.get().reflogEntryNotFound,
Integer.valueOf(number), ref.getName()));
}
ReflogEntry entry = reader.getReverseEntry(number);
if (entry == null)
throw new RevisionSyntaxException(MessageFormat.format(
JGitText.get().reflogEntryNotFound,
Integer.valueOf(number), ref.getName()));
return rw.parseCommit(entry.getNewId());
}
@Nullable
private ObjectId resolveAbbreviation(String revstr) throws IOException,
AmbiguousObjectException {
AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
try (ObjectReader reader = newObjectReader()) {
Collection matches = reader.resolve(id);
if (matches.isEmpty())
return null;
else if (matches.size() == 1)
return matches.iterator().next();
else
throw new AmbiguousObjectException(id, matches);
}
}
/**
* Increment the use counter by one, requiring a matched {@link #close()}.
*/
public void incrementOpen() {
useCnt.incrementAndGet();
}
/**
* {@inheritDoc}
*
* Decrement the use count, and maybe close resources.
*/
@Override
public void close() {
int newCount = useCnt.decrementAndGet();
if (newCount == 0) {
if (RepositoryCache.isCached(this)) {
closedAt.set(System.currentTimeMillis());
} else {
doClose();
}
} else if (newCount == -1) {
// should not happen, only log when useCnt became negative to
// minimize number of log entries
String message = MessageFormat.format(JGitText.get().corruptUseCnt,
toString());
if (LOG.isDebugEnabled()) {
LOG.debug(message, new IllegalStateException());
} else {
LOG.warn(message);
}
if (RepositoryCache.isCached(this)) {
closedAt.set(System.currentTimeMillis());
}
}
}
/**
* Invoked when the use count drops to zero during {@link #close()}.
*
* The default implementation closes the object and ref databases.
*/
protected void doClose() {
getObjectDatabase().close();
getRefDatabase().close();
}
/** {@inheritDoc} */
@Override
@NonNull
public String toString() {
String desc;
File directory = getDirectory();
if (directory != null)
desc = directory.getPath();
else
desc = getClass().getSimpleName() + "-" //$NON-NLS-1$
+ System.identityHashCode(this);
return "Repository[" + desc + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Get the name of the reference that {@code HEAD} points to.
*
* This is essentially the same as doing:
*
*
* return exactRef(Constants.HEAD).getTarget().getName()
*
*
* Except when HEAD is detached, in which case this method returns the
* current ObjectId in hexadecimal string format.
*
* @return name of current branch (for example {@code refs/heads/master}),
* an ObjectId in hex format if the current branch is detached, or
* {@code null} if the repository is corrupt and has no HEAD
* reference.
* @throws java.io.IOException
*/
@Nullable
public String getFullBranch() throws IOException {
Ref head = exactRef(Constants.HEAD);
if (head == null) {
return null;
}
if (head.isSymbolic()) {
return head.getTarget().getName();
}
ObjectId objectId = head.getObjectId();
if (objectId != null) {
return objectId.name();
}
return null;
}
/**
* Get the short name of the current branch that {@code HEAD} points to.
*
* This is essentially the same as {@link #getFullBranch()}, except the
* leading prefix {@code refs/heads/} is removed from the reference before
* it is returned to the caller.
*
* @return name of current branch (for example {@code master}), an ObjectId
* in hex format if the current branch is detached, or {@code null}
* if the repository is corrupt and has no HEAD reference.
* @throws java.io.IOException
*/
@Nullable
public String getBranch() throws IOException {
String name = getFullBranch();
if (name != null)
return shortenRefName(name);
return null;
}
/**
* Get the initial branch name of a new repository
*
* @return the initial branch name of a new repository
* @since 5.11
*/
protected @NonNull String getInitialBranch() {
return initialBranch;
}
/**
* Objects known to exist but not expressed by {@link #getAllRefs()}.
*
* When a repository borrows objects from another repository, it can
* advertise that it safely has that other repository's references, without
* exposing any other details about the other repository. This may help
* a client trying to push changes avoid pushing more than it needs to.
*
* @return unmodifiable collection of other known objects.
*/
@NonNull
public Set getAdditionalHaves() {
return Collections.emptySet();
}
/**
* Get a ref by name.
*
* @param name
* the name of the ref to lookup. Must not be a short-hand
* form; e.g., "master" is not automatically expanded to
* "refs/heads/master".
* @return the Ref with the given name, or {@code null} if it does not exist
* @throws java.io.IOException
* @since 4.2
*/
@Nullable
public final Ref exactRef(String name) throws IOException {
return getRefDatabase().exactRef(name);
}
/**
* Search for a ref by (possibly abbreviated) name.
*
* @param name
* the name of the ref to lookup. May be a short-hand form, e.g.
* "master" which is automatically expanded to
* "refs/heads/master" if "refs/heads/master" already exists.
* @return the Ref with the given name, or {@code null} if it does not exist
* @throws java.io.IOException
* @since 4.2
*/
@Nullable
public final Ref findRef(String name) throws IOException {
return getRefDatabase().findRef(name);
}
/**
* Get mutable map of all known refs, including symrefs like HEAD that may
* not point to any object yet.
*
* @return mutable map of all known refs (heads, tags, remotes).
* @deprecated use {@code getRefDatabase().getRefs()} instead.
*/
@Deprecated
@NonNull
public Map getAllRefs() {
try {
return getRefDatabase().getRefs(RefDatabase.ALL);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Get mutable map of all tags
*
* @return mutable map of all tags; key is short tag name ("v1.0") and value
* of the entry contains the ref with the full tag name
* ("refs/tags/v1.0").
* @deprecated use {@code getRefDatabase().getRefsByPrefix(R_TAGS)} instead
*/
@Deprecated
@NonNull
public Map getTags() {
try {
return getRefDatabase().getRefs(Constants.R_TAGS);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Peel a possibly unpeeled reference to an annotated tag.
*