package com.atlassian.bitbucket.scm;

import com.atlassian.bitbucket.commit.Changeset;
import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.CommitCallback;
import com.atlassian.bitbucket.commit.NoSuchCommitException;
import com.atlassian.bitbucket.commit.graph.TraversalCallback;
import com.atlassian.bitbucket.content.*;
import com.atlassian.bitbucket.io.TypeAwareOutputSupplier;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.bulk.PluginBulkContentCommandFactory;
import com.atlassian.bitbucket.scm.compare.PluginCompareCommandFactory;
import com.atlassian.bitbucket.scm.hook.PluginHookHandlerFactory;
import com.atlassian.bitbucket.scm.mirror.PluginMirrorCommandFactory;
import com.atlassian.bitbucket.scm.pull.PluginPullRequestCommandFactory;
import com.atlassian.bitbucket.scm.ref.PluginRefCommandFactory;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.Map;

/**
 * Provides backing functionality for the {@link ScmCommandFactory}.
 * <p>
 * This interface is intended to be <i>implemented</i> by plugin developers. Functionality provided by this interface
 * is intended to be consumed using the {@link ScmCommandFactory} exposed by the {@link ScmService}. Only the system
 * should ever deal with this interface directly.
 * <p>
 * All command types on this interface are <i>required</i> to have a working implementation, as this factory describes
 * the basic SCM commands used by the system to provide day-to-day functionality, unless documented otherwise. The
 * following commands are optional:
 * <ul>
 *     <li>{@link #fork(Repository, Repository) fork} ({@link ScmFeature#FORK})</li>
 *     <li>{@link #merge(Repository, MergeCommandParameters) merge} ({@link ScmFeature#MERGE})</li>
 *     <li>{@link #updateDefaultBranch(Repository, String) updateDefaultBranch} ({@link ScmFeature#UPDATE_DEFAULT_BRANCH})</li>
 * </ul>
 * <p>
 * In addition to these optional commands, {@link ScmFeature#CROSS_REPOSITORY cross-repository} support is also
 * optional. Where factory methods accept secondary repositories, with the exception of {@link #fork(Repository,
 * Repository) fork} which has its own {@link ScmFeature#FORK feature}, SCMs which do not support cross-repository
 * operations should fail fast, throwing an {@code UnsupportedOperationException} <i>rather than</i> returning a
 * {@link Command}; they should <i>not</i> return a command which throws that exception.
 * <p>
 * {@link Scm SCM} implementations may also provide additional functionality to allow the system to offer enhanced
 * features, such as pull requests. Such functionality is exposed by other interfaces:
 * <ul>
 *     <li>{@link PluginBulkContentCommandFactory}</li>
 *     <li>{@link PluginCommandBuilderFactory}</li>
 *     <li>{@link PluginCompareCommandFactory}</li>
 *     <li>{@link PluginHookHandlerFactory}</li>
 *     <li>{@link PluginMirrorCommandFactory}</li>
 *     <li>{@link PluginExtendedCommandFactory}</li>
 *     <li>{@link PluginPullRequestCommandFactory}</li>
 *     <li>{@link PluginRefCommandFactory}</li>
 * </ul>
 * <p>
 * SCMs which support additional functionality, or have custom {@link Command} types, are encouraged to provide a
 * sub-interface extending from this one exposing that functionality. For example:
 * <code><pre>
 *     //Extends {@link Command} to allow canceling it after it has been called and checking whether it's been canceled.
 *     public interface CancelableCommand&lt;T&gt; extends Command&lt;T&gt; {
 *         void cancel();
 *
 *         boolean isCanceled();
 *     }
 *
 *     //Overrides methods on the {@code PluginCommandFactory} interface using covariant return types to return the
 *     //enhanced {@code CancelableCommand} instead of simple {@code Command}
 *     public interface MyScmCommandFactory extends PluginCommandFactory {
 *         &#064;Nonnull
 *         &#064;Override
 *         CancelableCommand&lt;List&lt;Blame&gt;&gt; blame(&#064;Nonnull Repository repository,
 *                                              &#064;Nonnull BlameCommandParameters parameters,
 *                                              &#064;Nonnull PageRequest pageRequest);
 *
 *         //Overrides for other methods as appropriate
 *     }
 * </pre></code>
 * The SCM plugin would then expose the class implementing their custom {@code MyScmCommandFactory} using a
 * {@code component} directive:
 * <code><pre>
 *     &lt;!-- Note: This should be in the SCM <u>provider's</u> atlassian-plugin.xml --&gt;
 *     &lt;component key="myScmCommandFactory"
 *                class="com.example.DefaultMyScmCommandFactory"
 *                public="true"&gt;
 *         &lt;interface&gt;com.example.MyScmCommandFactory&lt;/interface&gt;
 *     &lt;/component&gt;
 * </pre></code>
 * The {@code public="true"} allows other plugins to import the component. This approach allows other plugin developers
 * to import the SCM plugin's command factory and leverage its enhanced functionality with a {@code component-import}
 * directive:
 * <code><pre>
 *     &lt;!-- Note: This should be in the SCM <u>consumer's</u> atlassian-plugin.xml --&gt;
 *     &lt;component-import key="myScmCommandFactory"
 *                       interface="com.example.MyScmCommandFactory"/&gt;
 * </pre></code>
 *
 * @see PluginBulkContentCommandFactory
 * @see PluginCommandBuilderFactory
 * @see PluginCompareCommandFactory
 * @see PluginHookHandlerFactory
 * @see PluginMirrorCommandFactory
 * @see PluginExtendedCommandFactory
 * @see PluginPullRequestCommandFactory
 * @see PluginRefCommandFactory
 */
public interface PluginCommandFactory {

    /**
     * Retrieves a page of {@link Blame}, providing attribution for each line in the specified file, calculated
     * from the specified starting commit.
     *
     * @param repository  the repository to calculate blame in
     * @param parameters  parameters describing the file and starting commit to use
     * @param pageRequest describes the page of blame to retrieve
     * @return a command which, when executed, will return a page of blame
     * @deprecated in 4.6 to note that the return type will change to
     *             {@code Command<Page<Blame>>} in 5.0.
     */
    @Deprecated
    @Nonnull
    Command<List<Blame>> blame(@Nonnull Repository repository, @Nonnull BlameCommandParameters parameters,
                               @Nonnull PageRequest pageRequest);

    /**
     * Streams {@link Branch branches} in the specified repository to the provided {@link BranchCallback callback}.
     * <p>
     * Implementors: The default implementation of this method is inefficient, simply chaining calls to
     * {@link #branches(Repository, BranchesCommandParameters, PageRequest)}, and will be removed in 5.0.
     * SCMs are <i>required</i> to implement this command for themselves; they cannot rely on the default.
     *
     * @param repository the repository to stream branches from
     * @param parameters parameters describing any filter to apply, and the order to stream branches in
     * @param callback   a callback to receive the streamed branches
     * @return a command which, when executed, will stream branches
     * @since 4.6
     */
    @Nonnull
    @SuppressWarnings("deprecation")
    default Command<Void> branches(@Nonnull Repository repository, @Nonnull BranchesCommandParameters parameters,
                                   @Nonnull BranchCallback callback) {
        return new CompatibilityStreamBranchesCommand(this, repository, parameters, callback);
    }

    /**
     * Retrieves a page of {@link Branch branches}.
     *
     * @param repository  the repository to retrieve branches from
     * @param parameters  parameters describing any filter to apply, and the order to retrieve branches in
     * @param pageRequest describes the page of branches to retrieve
     * @return a command which, when called, will retrieve a page of branches
     */
    @Nonnull
    Command<Page<Branch>> branches(@Nonnull Repository repository, @Nonnull BranchesCommandParameters parameters,
                                   @Nonnull PageRequest pageRequest);

    /**
     * Streams {@link Change changes} between two commits to the provided {@link ChangeCallback callback}.
     * <p>
     * If the {@link ChangesCommandParameters#getSinceId() sinceId} is not specified, the <i>first ancestor</i> of
     * the {@link ChangesCommandParameters#getUntilId() untilId} must be used automatically. If the {@code until}
     * commit is the first commit in the repository, and has no ancestors, each file included in that commit should
     * be streamed as newly {@link ChangeType#ADD added}.
     *
     * @param repository the repository to stream changes from
     * @param parameters parameters describing the two commits to compute changes for, as well as limits to apply
     * @param callback   a callback to receive the streamed changes
     * @return a command which, when executed, will stream changes
     */
    @Nonnull
    Command<Void> changes(@Nonnull Repository repository, @Nonnull ChangesCommandParameters parameters,
                          @Nonnull ChangeCallback callback);

    /**
     * Retrieves a page of {@link Change changes} between two commits.
     * <p>
     * If the {@link ChangesCommandParameters#getSinceId() sinceId} is not specified, the <i>first ancestor</i> of
     * the {@link ChangesCommandParameters#getUntilId() untilId} must be used automatically. If the {@code until}
     * commit is the first commit in the repository, and has no ancestors, each file included in that commit should
     * be returned as newly {@link ChangeType#ADD added}.
     *
     * @param repository  the repository to retrieve changes from
     * @param parameters  parameters describing the two commits to compute changes for, as well as limits to apply
     * @param pageRequest describes the page of changes to retrieve
     * @return a command which, when executed, will retrieve a page of changes
     */
    @Nonnull
    Command<Page<Change>> changes(@Nonnull Repository repository, @Nonnull ChangesCommandParameters parameters,
                                  @Nonnull PageRequest pageRequest);

    /**
     * Retrieves a page of {@link Changeset changesets} given a set of {@link ChangesetsCommandParameters#getCommitIds
     * commit IDs}, where each changeset includes the first page of {@link Change changes} between a requested commit
     * and its <i>first parent</i>.
     *
     * @param repository  the repository to retrieve changesets from
     * @param parameters  parameters describing the changesets to retrieve
     * @param pageRequest describes the page of changesets to retrieve
     * @return a command which, when executed, will retrieve a page of changesets
     */
    @Nonnull
    Command<Page<Changeset>> changesets(@Nonnull Repository repository, @Nonnull ChangesetsCommandParameters parameters,
                                        @Nonnull PageRequest pageRequest);

    /**
     * Retrieves the requested commit or, if a path was also supplied, retrieves the <i>first commit</i> to modify
     * that path prior to the requested commit. If the requested commit modified the path, it will be returned.
     *
     * @param repository the repository to retrieve the commit from
     * @param parameters parameters describing the commit to retrieve
     * @return a command which, when executed, will return the specified commit
     */
    @Nonnull
    Command<Commit> commit(@Nonnull Repository repository, @Nonnull CommitCommandParameters parameters);

    /**
     * Streams commits which match the provided {@link CommitsCommandParameters paramters} to the provided callback.
     * <p>
     * Implementors: SCMs are <i>required</i> to support this operation. <i>However</i>, they are <i>not</i> required
     * to support {@link CommitsCommandParameters#getSecondaryRepository() secondary repositories} unless they offer
     * {@link ScmFeature#CROSS_REPOSITORY cross-repository} support. If a secondary repository is provided and cross-
     * repository operations are not supported, implementations should throw an {@code UnsupportedOperationException}.
     *
     * @param repository the repository to retrieve commits from
     * @param parameters parameters describing the commits to retrieve
     * @param callback   a callback to receive the streamed commits
     * @return a command which, when executed, will stream commits to the provided callback
     * @throws UnsupportedOperationException if a {@link CommitsCommandParameters#getSecondaryRepository() secondary
     *                                       repository} is provided an {@link ScmFeature#CROSS_REPOSITORY cross-
     *                                       repository} operations are not supported
     * @see ScmFeature#CROSS_REPOSITORY
     */
    @Nonnull
    Command<Void> commits(@Nonnull Repository repository, @Nonnull CommitsCommandParameters parameters,
                          @Nonnull CommitCallback callback);

    /**
     * Retrieves a page of commits which match the provided {@link CommitsCommandParameters paramters}.
     * <p>
     * Implementors: SCMs are <i>required</i> to support this operation. <i>However</i>, they are <i>not</i> required
     * to support {@link CommitsCommandParameters#getSecondaryRepository() secondary repositories} unless they offer
     * {@link ScmFeature#CROSS_REPOSITORY cross-repository} support. If a secondary repository is provided and cross-
     * repository operations are not supported, implementations should throw an {@code UnsupportedOperationException}.
     *
     * @param repository  the repository to retrieve commits from
     * @param parameters  parameters describing the commits to retrieve
     * @param pageRequest describes the page of commits to retrieve
     * @return a command which, when executed, will retrieve a page of commits
     * @throws UnsupportedOperationException if a {@link CommitsCommandParameters#getSecondaryRepository() secondary
     *                                       repository} is provided an {@link ScmFeature#CROSS_REPOSITORY cross-
     *                                       repository} operations are not supported
     * @see ScmFeature#CROSS_REPOSITORY
     */
    @Nonnull
    Command<Page<Commit>> commits(@Nonnull Repository repository, @Nonnull CommitsCommandParameters parameters,
                                  @Nonnull PageRequest pageRequest);

    /**
     * Creates and initializes the repository on disk, performing any SCM-specific configuration that is appropriate
     * for new repositories. {@code repository} <i>does not exist</i> on disk when this method is called. The SCM is
     * expected to create it with contents that are appropriate for an <i>empty</i>, new repository.
     *
     * @param repository the repository to create
     * @return a command which, when executed, will create and initialize the specified repository
     */
    @Nonnull
    Command<Void> create(@Nonnull Repository repository);

    /**
     * Retrieves the default {@link Branch} for the specified repository.
     * <p>
     * Each repository in an SCM is <i>required</i> to have a default branch. However, that branch is not actually
     * required to <i>exist</i> within the repository. If the default branch does not exist, the returned command
     * <i>must</i> throw {@link NoDefaultBranchException}; it may not return {@code null}.
     *
     * @param repository the repository to retrieve the default branch for
     * @return a command which, when executed, will return the repository's default branch, or throw
     *         {@link NoDefaultBranchException} if the default branch does not exist
     */
    @Nonnull
    Command<Branch> defaultBranch(@Nonnull Repository repository);

    /**
     * Deletes the repository, allowing the underlying SCM to carry out any special processing necessary to clean up
     * repository storage.
     * <p>
     * Note: By the time this method is called, the repository has <i>already</i> been deleted from the database, and
     * the associated database transaction has been committed.
     * <p>
     * <b>Warning</b>: This method is <i>irreversible</i>. It should <i>never</i> be called by a plugin. It is
     * intended solely for use by the system.
     *
     * @param repository the repository to delete
     * @param parameters additional parameters, such as IDs for forks of the deleted repository
     * @return a command which, when executed, will delete the repository's storage
     */
    @Nonnull
    AsyncCommand<Void> delete(@Nonnull Repository repository, @Nonnull DeleteCommandParameters parameters);

    /**
     * Streams the diff between two commits to the provided {@link DiffContentCallback callback}.
     * <p>
     * If the {@link DiffCommandParameters#getSinceId() sinceId} is not specified, the diff must be calculated to
     * the <i>first ancestor</i> of the {@link DiffCommandParameters#getUntilId() untilId}. If the {@code until}
     * commit is the first commit in the repository, and has no ancestors, content for each file included in that
     * commit should be streamed as newly {@link DiffSegmentType#ADDED added}.
     * <p>
     * Diff implementations are <i>required</i> to honor the following settings:
     * <ul>
     *     <li>{@link DiffCommandParameters#getContextLines() context line count}</li>
     *     <li>{@link DiffCommandParameters#getMaxLineLength() line length}</li>
     *     <li>{@link DiffCommandParameters#getMaxLines() line count}</li>
     * </ul>
     * Diff implementations are <i>not required</i> to honor any {@link DiffCommandParameters#getWhitespace()
     * whitespace modes} they do not natively support.
     *
     * @param repository the repository to stream the diff from
     * @param parameters parameters describing the two commits to diff, and additional settings for controlling
     *                   the shape and content of the streamed diff
     * @param callback   a callback to receive the streamed diff
     * @return a command which, when executed, will stream the diff between two commits
     */
    @Nonnull
    Command<Void> diff(@Nonnull Repository repository, @Nonnull DiffCommandParameters parameters,
                       @Nonnull DiffContentCallback callback);

    /**
     * Streams a directory listing for the requested path at the specified commit.
     * <p>
     * When a {@link DirectoryCommandParameters#isRecursive() recursive} listing is requested, implementations
     * <i>must</i> only stream non-{@link ContentTreeNode.Type#DIRECTORY directory} entries. If the SCM tracks
     * empty directories, they <i>must</i> be omitted.
     * <p>
     * For non-recursive listings, implementations are <i>encouraged</i> to collapse subdirectories which only
     * have a single entry. For example, consider the following directory structure:
     * <pre><code>
     * src/
     *   main/
     *     java/
     *       com/
     *         example/
     *           MyClass.java
     *     resources/
     * pom.xml
     * </code></pre>
     * A listing at "/" (or ""), in addition to streaming {@code new SimplePath("pom.xml)}, may stream <i>either</i>
     * of the following values:
     * <ul>
     *     <li>{@code new SimplePath("src")}, or</li>
     *     <li>{@code new SimplePath("src/main"}</li>
     * </ul>
     * Because "src" contains only a single subdirectory, "main", the two may be collapsed if the implementation is
     * capable of doing so <i>efficiently</i>. Implementations are <i>not</i> required to do so, however.
     * <p>
     * A listing at "src/main", in addition to streaming {@code new SimplePath("resources")} may stream <i>either</i>
     * of the following values:
     * <ul>
     *     <li>{@code new SimplePath("java")}, or</li>
     *     <li>{@code new SimplePath("java/com/example/MyClass.java")}</li>
     * </ul>
     * <p>
     * If {@link DirectoryCommandParameters#isWithSizes() file sizes} are requested, SCMs which can <i>efficiently</i>
     * include them should do so. If the SCM's internal representation for directory data makes file sizes expensive
     * to retrieve (such as requiring the entire file to be streamed and its bytes counted, in the worst case), the
     * SCM should omit size data even if it is requested.
     *
     * @param repository  the repository to stream the directory listing from
     * @param parameters  parameters describing the directory to list and the commit to list them at
     * @param callback    a callback to receive the streamed list of files, subdirectories and submodules
     * @param pageRequest describes the page of the directory listing to stream
     * @return a command which, when executed, will stream the requested page of the specified directory listing
     */
    @Nonnull
    Command<Void> directory(@Nonnull Repository repository, @Nonnull DirectoryCommandParameters parameters,
                            @Nonnull ContentTreeCallback callback, @Nonnull PageRequest pageRequest);

    /**
     * Streams lines from the requested path at the specified commit, for <i>non-binary</i> files.
     * <p>
     * When the command is executed, if the requested path identifies a binary file, such as an image or PDF, the
     * SCM should call {@link FileContentCallback#onBinary()}.
     * <p>
     * For SCMs which support multiple file encodings:
     * <ul>
     *     <li>If the encoding is recorded in the SCM's metadata, it should be used</li>
     *     <li>Otherwise, the SCM should perform its own best-effort encoding detection</li>
     * </ul>
     * {@code ContentDetectionUtils} offers static methods for performing binary and encoding detection. It may be
     * accessed from the following Maven dependency:
     * <pre><code>
     * &lt;dependency&gt;
     *     &lt;groupId&gt;com.atlassian.bitbucket.server&lt;/groupId&gt;
     *     &lt;artifactId&gt;bitbucket-scm-common&lt;/artifactId&gt;
     *     &lt;scope&gt;provided&lt;/scope&gt;
     * &lt;/dependency&gt;
     * </code></pre>
     * <p>
     * Implementations are <i>required</i> to honor {@link FileCommandParameters#getMaxLineLength() max line lengths}.
     * If any line in a file exceeds that length, it must be truncated to constrain memory usage.
     * <p>
     * If {@link FileCommandParameters#isAnnotated() annotations} are requested, the implementation <i>must</i>
     * compute the {@link #blame} for each line on the requested page and, after streaming those lines,
     * {@link FileContentCallback#offerBlame offer} the blame to the callback.
     *
     * @param repository  the repository to stream the file from
     * @param parameters  parameters describing the file to stream and the commit to stream it at
     * @param callback    a callback to receive the <i>parsed and limited</i> file content
     * @param pageRequest describes the page of the file to stream
     * @return a command which, when executed, will stream the requested page of the specified file
     * @see #rawFile(Repository, RawFileCommandParameters, TypeAwareOutputSupplier)
     */
    @Nonnull
    Command<Void> file(@Nonnull Repository repository, @Nonnull FileCommandParameters parameters,
                       @Nonnull FileContentCallback callback, @Nonnull PageRequest pageRequest);

    /**
     * Creates a fork of {@code repository}, stored in {@code fork}. Similar to {@link #create(Repository) creating}
     * a repository, {@code fork} does not exist on disk when this method is called. It is expected that the SCM will
     * create it with contents based on {@code repository}, which becomes its {@link Repository#getOrigin() origin}.
     * <p>
     * Implementors: This operation is <i>optional</i>. Implementations which do not support forking should throw an
     * {@code UnsupportedOperationException} and omit the {@link ScmFeature#FORK FORK} feature.
     *
     * @param repository the repository to be forked
     * @param fork       the fork
     * @return a command which, when executed, will create a fork of the specified repository, associating its storage
     *         with the specified {@code fork} repository
     * @throws UnsupportedOperationException if forking is not a supported feature
     * @see ScmFeature#FORK
     * @deprecated in 4.6 for removal in 5.0. SCMs should implement {@link PluginExtendedCommandFactory#fork}
     *             instead
     */
    @Deprecated
    @Nonnull
    Command<Void> fork(@Nonnull Repository repository, @Nonnull Repository fork);

    /**
     * Streams all of the heads in the specified repository.
     * <p>
     * Implementors: A "head" in this context is any type of ref that would <i>normally</i> be retrieved by the
     * SCM. At a minimum, this should be every branch and every tag in the repository. If the SCM has additional
     * standard refs, they <i>should</i> be included. If the SCM supports optional or nonstandard ref namespaces,
     * any refs stored there <i>should not</i> be included.
     *
     * @param repository the repository to stream heads for
     * @param callback   a callback to receive the streamed heads
     * @return a command which, when called, will stream <i>all</i> of the heads in the specified repository
     */
    @Nonnull
    Command<Void> heads(@Nonnull Repository repository, @Nonnull RefCallback callback);

    /**
     * Merges the specified {@code fromCommitId} into the specified {@code toBranch}.
     * <p>
     * SCMs are required to support the following values for {@code fromCommitId}:
     * <ul>
     *     <li>A 40-byte hash (Note: Short hashes are <i>not supported</i>)</li>
     *     <li>A fully-qualified branch name</li>
     *     <li>A short branch name</li>
     * </ul>
     * {@code toBranch}, as the parameter name suggests, <i>must</i> be a branch name, either fully qualified or short,
     * and the SCM should validate that that is the case; using a hash (full or short) is not supported.
     * <p>
     * Implementors: This feature is <i>optional</i>. Implementations which do not support merging should throw an
     * {@code UnsupportedOperationException} and omit the {@link ScmFeature#MERGE MERGE} feature. Additionally, SCMs
     * which support merging are not required to support {@link MergeCommandParameters#getFromRepository() different
     * source repositories} unless they offer {@link ScmFeature#CROSS_REPOSITORY cross-repository} support. If a
     * different from source repository is provided and cross-repository operations are not supported, implementations
     * should throw an {@code UnsupportedOperationException}.
     *
     * @param repository the repository containing <i>both</i> the source commit and the target branch
     * @param parameters parameters describing the branches to merge
     * @return a command which, when executed, will merge the specified branch into the target branch
     * @throws UnsupportedOperationException if a {@link MergeCommandParameters#getFromRepository() source repository}
     *                                       is specified and {@link ScmFeature#CROSS_REPOSITORY cross-repository}
     *                                       operations are not supported
     * @see ScmFeature#CROSS_REPOSITORY
     * @see ScmFeature#MERGE
     * @deprecated in 4.6 for removal in 5.0. SCMs should implement {@link PluginExtendedCommandFactory#merge}
     *             instead
     */
    @Deprecated
    @Nonnull
    Command<Branch> merge(@Nonnull Repository repository, @Nonnull MergeCommandParameters parameters);

    /**
     * Streams the raw bytes for the requested file at the specified commit to {@code OutputStream} returned by the
     * provided {@link TypeAwareOutputSupplier supplier}.
     * <p>
     * Prior to streaming bytes, SCM implementations are required to perform best-effort MIME type detection on the
     * file, and to pass the detected type to the {@link TypeAwareOutputSupplier#getStream supplier}. The Java
     * {@link java.net.URLConnection URLConnection} class offers some static methods to facilitate such detection.
     * {@code ContentDetectionUtils.detectContentType(BufferedInputStream, String)} may also be useful. It may be
     * accessed from the following Maven dependency:
     * <pre><code>
     * &lt;dependency&gt;
     *     &lt;groupId&gt;com.atlassian.bitbucket.server&lt;/groupId&gt;
     *     &lt;artifactId&gt;bitbucket-scm-common&lt;/artifactId&gt;
     *     &lt;scope&gt;provided&lt;/scope&gt;
     * &lt;/dependency&gt;
     * </code></pre>
     *
     * @param repository     the repository to stream the file from
     * @param parameters     parameters describing the file to stream, and the commit to stream it at
     * @param outputSupplier provides an {@code OutputStream} to stream the file to, after its MIME type is supplied
     * @return a command which, when executed, will stream the raw bytes of the requested file at the specified
     *         commit to the provided callback, after first supplying its MIME type
     */
    @Nonnull
    Command<Void> rawFile(@Nonnull Repository repository, @Nonnull RawFileCommandParameters parameters,
                          @Nonnull TypeAwareOutputSupplier outputSupplier);

    /**
     * Resolves the specified {@link ResolveRefCommandParameters#getRefId() refId}, which may be a:
     * <ul>
     *     <li>{@link Branch#getId() branch name}</li>
     *     <li>{@link Tag#getId() tag name}</li>
     *     <li>{@link Commit#getId() commit hash}</li>
     * </ul>
     * If a name is provided, it must be considered as either an ID <i>or</i> a display ID. The <i>most exact</i>
     * match should be preferred. For SCMs which have namespaces, like Git's "refs/heads" and "refs/tags", names
     * which are not fully qualified should be considered in every possible namespace. For example, "master" can
     * be matched to "refs/heads/master" or "refs/tags/master".
     * <p>
     * If a hash is provided, it may <i>only</i> be resolved to a branch or tag if it identifies the tip commit. If the
     * hash identifies a commit that is <i>not</i> the tip of a branch or tag, the returned command <i>must</i> return
     * {@code null}.
     * <p>
     * When a hash or name is provided and resolves to multiple branches, tags or a combination of both, SCM
     * implementors are <i>encouraged</i> to choose a tag over a branch, and to return the "first" branch or
     * tag alphabetically. However, this is not enforced and is <i>not</i> considered part of the contract.
     * <p>
     * If the parameters specify a {@link ResolveRefCommandParameters#getType() ref type}, the provided ID must
     * <i>only</i> be resolved against refs of that type. If a ref of a different type matches, it <i>may not</i>
     * be returned. Implementations are encouraged to optimize their lookups when a type is provided.
     * <p>
     * Implementors: The default implementation of this method relies on {@link #resolveRef(Repository, String)},
     * which is deprecated for removal in 5.0. The default implementation for this method will also be removed in
     * 5.0. SCMs are <i>required</i> to implement this command for themselves; they cannot rely on the default.
     *
     * @param repository the repository to resolve the ref in
     * @param parameters parameters describing the ref to resolve, optionally including a type hint
     * @return a command which, when executed, will attempt to resolve the provided ID to a {@link Branch} or
     *         {@link Tag}
     * @since 4.6
     */
    @SuppressWarnings("deprecation")
    default Command<Ref> resolveRef(@Nonnull Repository repository, @Nonnull ResolveRefCommandParameters parameters) {
        return resolveRef(repository, parameters.getRefId());
    }

    /**
     * Resolves the specified {@code refId}, which may be a:
     * <ul>
     *     <li>{@link Branch#getId() branch name}</li>
     *     <li>{@link Tag#getId() tag name}</li>
     *     <li>{@link Commit#getId() commit hash}</li>
     * </ul>
     * If a name is provided, it must be considered as either an ID <i>or</i> a display ID. The <i>most exact</i>
     * match should be preferred. For SCMs which have namespaces, like Git's "refs/heads" and "refs/tags", names
     * which are not fully qualified should be considered in every possible namespace. For example, "master" can
     * be matched to "refs/heads/master" or "refs/tags/master".
     * <p>
     * If a hash is provided, it may <i>only</i> be resolved to a branch or tag if it identifies the tip commit. If the
     * hash identifies a commit that is <i>not</i> the tip of a branch or tag, the returned command <i>must</i> return
     * {@code null}.
     * <p>
     * When a hash or name is provided and resolves to multiple branches, tags or a combination of both, SCM
     * implementors are <i>encouraged</i> to choose a tag over a branch, and to return the "first" branch or
     * tag alphabetically. However, this is not enforced and is <i>not</i> considered part of the contract.
     *
     * @param repository the repository to resolve the ref in
     * @param refId      the ID to resolve, which may be a branch or tag name or a commit hash
     * @return a command which, when executed, will attempt to resolve the provided ID to a {@link Branch} or
     *         {@link Tag}
     * @deprecated in 4.6 for removal in 5.0. Use {@link #resolveRef(Repository, ResolveRefCommandParameters)}
     *             instead.
     */
    @Deprecated
    @Nonnull
    Command<Ref> resolveRef(@Nonnull Repository repository, @Nonnull String refId);

    /**
     * Resolves one or more branches, tags and/or refs.
     * <p>
     * Implementations may only resolve {@link ResolveRefsCommandParameters#getBranchIds() branch IDs} and
     * {@link ResolveRefsCommandParameters#getTagIds() tag IDs} against {@link Branch branches} and
     * {@link Tag tags}, respectively. If a ref a different type matches, it <i>may not</i> be returned.
     * <p>
     * {@link ResolveRefsCommandParameters#getRefIds Ref IDs} may be resolved against refs of any type. When
     * an ID resolves to multiple branches, tags or a combination of both, SCM implementors are <i>encouraged</i>
     * to choose a tag over a branch, and to return the "first" branch or tag alphabetically. However, this is
     * not enforced, and is <i>not</i> considered part of the contract.
     * <p>
     * Implementors: The default implementation of this method is inefficient, simply chaining calls to
     * {@link #resolveRef(Repository, ResolveRefCommandParameters)}, and will be removed in 5.0. SCMs are
     * <i>required</i> to implement this command for themselves; they cannot rely on the default.
     *
     * @param repository the repository to resolve the refs in
     * @param parameters parameters describing the branches, tags and refs to resolve
     * @return a command which, when executed, will resolve the specified IDs and return a map linking each
     *         successfully-resolved ID to the {@link Ref ref} it resolved to
     * @since 4.5
     */
    @Nonnull
    @SuppressWarnings("deprecation")
    default Command<Map<String, Ref>> resolveRefs(@Nonnull Repository repository,
                                                  @Nonnull ResolveRefsCommandParameters parameters) {
        return new CompatibilityResolveRefsCommand(this, repository, parameters);
    }

    /**
     * Streams {@link Tag tags} in the specified repository to the provided {@link TagCallback callback}.
     *
     * @param repository the repository to stream tags from
     * @param parameters parameters describing any filter to apply, and the order to stream tags in
     * @param callback   a callback to receive the streamed tags
     * @return a command which, when executed, will stream tags
     */
    @Nonnull
    Command<Void> tags(@Nonnull Repository repository, @Nonnull TagsCommandParameters parameters,
                       @Nonnull TagCallback callback);

    /**
     * Retrieves a page of {@link Tag tags}.
     *
     * @param repository  the repository to retrieve tags from
     * @param parameters  parameters describing any filter to apply, and the order to retrieve tags in
     * @param pageRequest describes the page of tags to retrieve
     * @return a command which, when called, will retrieve a page of tags
     */
    @Nonnull
    Command<Page<Tag>> tags(@Nonnull Repository repository, @Nonnull TagsCommandParameters parameters,
                            @Nonnull PageRequest pageRequest);

    /**
     * Streams all of the commits in the repository in <i>topological order</i>. Topological order means no parent
     * is output before <i>all</i> of its descendants have been output. It does <i>not</i> require that descendants
     * be streamed in <i>date</i> order, but SCMs may optionally do so (so long as topological order is retained).
     *
     * @param repository the repository to traverse commits for
     * @param callback   the callback to receive the streamed commits
     * @return a command which, when executed, will stream all of the commits in the repository, topologically ordered
     */
    @Nonnull
    Command<Void> traverseCommits(@Nonnull Repository repository, @Nonnull TraversalCallback callback);

    /**
     * Retrieves the {@link ContentTreeNode.Type type} for the requested path at the specified commit. The same
     * path may have different types at different commits as the repository's contents change.
     * <p>
     * The command may not return {@code null} for any reason. If the specified commit does not exist in the
     * repository, the returned command <i>must</i> throw {@link NoSuchCommitException}. If the requested path
     * does not exist in the specified commit, the returned command <i>must</i> throw {@link NoSuchPathException}.
     *
     * @param repository the repository to retrieve the type from
     * @param parameters parameters describing the commit and path to retrieve the type for
     * @return a command which, when executed, will return the type of the requested path at the specified commit
     */
    @Nonnull
    Command<ContentTreeNode.Type> type(@Nonnull Repository repository, @Nonnull TypeCommandParameters parameters);

    /**
     * Set the default branch for the specified repository to the named branch.
     * <p>
     * Implementors: This feature is <i>optional</i>. Implementations which do not support updating the default branch
     * should throw an {@code UnsupportedOperationException} and omit the {@link ScmFeature#UPDATE_DEFAULT_BRANCH
     * UPDATE_DEFAULT_BRANCH} feature.
     *
     * @param repository the repository whose default branch should be updated
     * @param branchName the branch to make the default
     * @return a command which, when executed, will update the default branch
     * @throws IllegalArgumentException      if the provided {@code branchName} is {@code null} or blank.
     * @throws UnsupportedOperationException if the underlying SCM does not support setting the default branch.
     * @see ScmFeature#UPDATE_DEFAULT_BRANCH
     * @deprecated in 4.6 for removal in 5.0. SCMs should implement
     *             {@link PluginExtendedCommandFactory#updateDefaultBranch} instead
     */
    @Deprecated
    @Nonnull
    Command<Void> updateDefaultBranch(@Nonnull Repository repository, @Nonnull String branchName)
            throws IllegalArgumentException, UnsupportedOperationException;
}
