package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.throttle.ThrottledResource;
import com.atlassian.bitbucket.mesh.util.Person;
import com.google.common.collect.ImmutableSet;

import java.util.Set;
import java.util.regex.Pattern;

public class GitConstants {

    //Git commands that are guaranteed _not_ to update a ref in the repository the command is executed in
    public static final Set<String> COMMAND_NO_REF_UPDATES = ImmutableSet.<String>builder()
            .add("am", "add", "annotate", "apply", "archive", "bisect", "blame", "bugreport")
            .add("cat-file", "check-attr", "check-ignore", "check-ref-format", "checkout", "checkout-index", "cherry",
                    "clean", "clone", "column", "commit-graph", "commit-tree", "config", "count-objects", "credential",
                    "credential-cache", "credential-store")
            .add("describe", "diff", "diff-files", "diff-index", "diff-tree", "difftool")
            .add("fast-export", "fmt-merge-msg", "for-each-ref", "format-patch", "fsck")
            .add("gc", "get-tar-commit-id", "grep", "gui")
            .add("hash-object", "help", "index-pack", "init", "instaweb", "interpret-trailers")
            .add("log", "ls-files", "ls-remote", "ls-tree")
            .add("mailinfo", "mailsplit", "merge-base", "merge-file", "merge-index", "merge-one-file", "merge-tree",
                    "mktag", "mktree", "multi-pack-index", "mv", "name-rev")
            .add("pack-objects", "pack-redundant", "pack-refs", "parse-remote", "patch-id", "push", "prune",
                    "prune-packed")
            .add("range-diff", "read-tree", "reflog", "remote", "repack", "rerere", "reset", "restore", "rev-list",
                    "rev-parse", "rm")
            .add("send-pack", "shortlog", "show", "show-branch", "show-index", "show-ref", "sparse-checkout", "stash",
                    "status", "stripspace", "submodule", "switch")
            .add("unpack-file", "unpack-objects", "update-index", "update-server-info", "upload-archive", "upload-pack")
            .add("var", "verify-pack", "verify-commit", "verify-tag", "whatchanged", "worktree", "write-tree")
            .build();
    //Git commands that clients are NOT allowed to call directly using PlumbingService/Call and CallStdin
    public static final Set<String> COMMAND_BLOCKED = ImmutableSet.<String>builder()
            .add("citool", "credential", "credential-cache", "credential-store", "daemon", "gc", "gui",
                    "imap-send", "instaweb", "prune", "send-email", "web--browse")
            .build();
    //Git commands that clients are NOT allowed to call for managed repositories
    public static final Set<String> COMMAND_BLOCKED_MANAGED = ImmutableSet.<String>builder()
            .add("add", "apply", "bisect", "checkout", "checkout-index", "clean", "clone", "commit", "commit-tree")
            .add("config", "fast-import", "filter-branch", "init", "init-db", "ls-files", "merge-file", "merge-index")
            .add("merge-one-file", "merge-tree", "read-tree", "reflog", "reset", "restore", "rm") // block reflog --delete
            .add("stash", "show-index", "sparse-checkout", "status", "submodule", "switch", "symbolic-ref")
            .add("update-index", "worktree", "write-tree")
            .addAll(COMMAND_BLOCKED)
            .build();

    public static final String CONFIG_CORE_ABBREV = "core.abbrev";
    public static final String CONFIG_CORE_AUTOCRLF = "core.autocrlf";
    public static final String CONFIG_CORE_HOOKS_PATH = "core.hooksPath";
    public static final String CONFIG_CORE_PACKED_GIT_LIMIT = "core.packedgitlimit";
    public static final String CONFIG_CORE_PACKED_GIT_WINDOW_SIZE = "core.packedgitwindowsize";
    public static final String CONFIG_CORE_SAFECRLF = "core.safecrlf";
    public static final String CONFIG_CORE_SYMLINKS = "core.symlinks";
    public static final String CONFIG_CREDENTIAL_USERNAME = "credential.username";
    public static final String CONFIG_GC_AUTO = "gc.auto";
    public static final String CONFIG_GC_PRUNE_EXPIRE = "gc.pruneexpire";
    /**
     * The value for {@link #CONFIG_GC_PRUNE_EXPIRE gc.pruneexpire} that means unreachable objects should never be
     * pruned.
     */
    public static final String CONFIG_GC_PRUNE_EXPIRE_NEVER = "never";
    public static final String CONFIG_GC_PR_REFS_SECTION = "gc.stash-refs/pull-requests/**";
    public static final String CONFIG_GC_PR_REFS_EXPIRE = CONFIG_GC_PR_REFS_SECTION + ".reflogExpire";
    public static final String CONFIG_GC_PR_REFS_EXPIRE_UNREACHABLE =
            CONFIG_GC_PR_REFS_SECTION + ".reflogExpireUnreachable";
    public static final String CONFIG_HTTP_RECEIVE_PACK = "http.receivepack";
    public static final String CONFIG_PROTOCOL_VERSION = "protocol.version";
    public static final String CONFIG_UPLOAD_PACK_ALLOW_FILTER = "uploadpack.allowFilter";
    public static final String CONFIG_UPLOAD_PACK_PACK_OBJECTS_HOOK = "uploadpack.packobjectshook";
    public static final String CONFIG_USER_EMAIL = "user.email";
    public static final String CONFIG_USER_NAME = "user.name";
    public static final String COORDINATOR_SCRIPT = "hook-coordinator.sh";
    /**
     * This environment variable may be exported to cause {@code git} to look up objects in the specified directory or
     * directories (separated by a colon, even on Windows) <i>in addition to</i> the {@link #PATH_OBJECTS objects}
     * directory within the repository where the command is executed.
     */
    public static final String ENV_ALTERNATE_OBJECT_DIRS = "GIT_ALTERNATE_OBJECT_DIRECTORIES";
    /**
     * An environment variable which overrides the author date when using commands like {@code git commit} or
     * {@code git merge} which create new commits.
     */
    public static final String ENV_AUTHOR_DATE = "GIT_AUTHOR_DATE";
    public static final String ENV_AUTHOR_EMAIL = "GIT_AUTHOR_EMAIL";
    public static final String ENV_AUTHOR_NAME = "GIT_AUTHOR_NAME";
    /**
     * An environment variable used to make the git command being executed available to git hooks, in particular
     * the reference-transaction hook. It's unfortunate, but this is needed to determine whether the git command
     * invokes fake packed-backend hooks before or after the 'real' hooks.
     */
    public static final String ENV_BB_GIT_COMMAND = "BB_GIT_COMMAND";
    /**
     * An environment variable which overrides the committer date when using commands like {@code git commit}
     * or {@code git merge} which create new commits.
     */
    public static final String ENV_COMMITTER_DATE = "GIT_COMMITTER_DATE";
    public static final String ENV_COMMITTER_EMAIL = "GIT_COMMITTER_EMAIL";
    public static final String ENV_COMMITTER_NAME = "GIT_COMMITTER_NAME";
    /**
     * Defines the number of configuration {@link #ENV_CONFIG_KEY key} and {@link #ENV_CONFIG_VALUE value} pairs.
     * <p>
     * <b>Warning</b>: Keys and values <i>must be paired</i>, must be <i>contiguous</i> and must match this count.
     */
    public static final String ENV_CONFIG_COUNT = "GIT_CONFIG_COUNT";
    /**
     * A <i>prefix</i> for exporting a configured key. The <i>zero-based</i> index should be appended to produce
     * the full property.
     */
    public static final String ENV_CONFIG_KEY = "GIT_CONFIG_KEY_";
    /**
     * A <i>prefix</i> for exporting a configured value. The <i>zero-based</i> index should be appended to produce
     * the full property.
     */
    public static final String ENV_CONFIG_VALUE = "GIT_CONFIG_VALUE_";
    /**
     * This environment variable may be exported to cause {@code git} to look up objects in the specified directory
     * <i>instead of</i> the {@link #PATH_OBJECTS objects} directory within the repository where the command is
     * executed.
     */
    public static final String ENV_OBJECT_DIR = "GIT_OBJECT_DIRECTORY";
    public static final String ENV_PROTOCOL = "GIT_PROTOCOL";
    /**
     * This environment variable is exported when running {@code pre-receive} hooks if the incoming objects have
     * been quarantined. Quarantining is a new feature added in Git 2.11.0 where incoming objects are written to
     * a temporary location, instead of directly into the repository, to allow them to be immediately pruned for
     * pushes which are rejected.
     */
    public static final String ENV_QUARANTINE_PATH = "GIT_QUARANTINE_PATH";
    /**
     * This environment variable is used to pass the {@link ThrottledResource#getId() resource ID} that should be
     * used for throttling.
     */
    public static final String ENV_THROTTLED_RESOURCE = "BB_RESOURCE_NAME";
    /**
     * This environment variable defines the location of the root of a non-bare repository's working directory.
     */
    public static final String ENV_WORK_TREE = "GIT_WORK_TREE";

    /**
     * Error message that is written to stderr when a write to a repository that has been migrated to Mesh is detected.
     */
    public static final String ERR_MIGRATED_TO_MESH = "bitbucket: migrated to Mesh";

    /**
     * This file contains packed references. It is created/updated when {@code git gc} runs, and may also be updated by
     * running {@code git pack-refs}. In general, references are contained one per line, and may reference branches and
     * tags. Lines starting with a # represent comments. Entries for tags may span two lines, where the second line is
     * the hash of the tagged reference (and the first is the hash and name of the tag itself).
     * <p>
     * An example of the file's contents looks like this:
     * <pre><code>
     * # pack-refs with: peeled
     * d6edcbf924697ab811a867421dab60d954ccad99 refs/heads/basic_branching
     * 6053a1eaa1c009dd11092d09a72f3c41af1b59ad refs/heads/branch_mod_merge
     * 0a943a29376f2336b78312d99e65da17048951db refs/heads/master
     * 2e10dd2d1d5eea9291b296e78312e8a703964a95 refs/heads/out_of_order_branch
     * 80aa5be5ecf39660f798858482254f7a2ae9110e refs/tags/backdated_annotated_tag
     * ^57150c54c38d6570b2fd5e6d6cfc19476de44e84
     * 9b4e781dea0769888fe270e06ad76675f73851b1 refs/tags/retagged_signed_tag
     * ^0a943a29376f2336b78312d99e65da17048951db
     * 12ebe2a58367347cd39f19f5a72f3cbec7b8f9a9 refs/tags/signed_tag
     * ^0a943a29376f2336b78312d99e65da17048951db
     * 6053a1eaa1c009dd11092d09a72f3c41af1b59ad refs/tags/simple_tag
     *</code></pre>
     * Notice that the last tag only covers one line; that represents a lightweight tag, which has no hash of its own.
     * The hash shown is for the commit that was tagged. The other tags are annotated or signed as indicated in their
     * name--there are no differences in their representation in the packed-refs file.
     */
    public static final String PACKED_REFS = "packed-refs";

    /**
     * The path under {@link #PATH_OBJECTS objects/}{@link #PATH_INFO info/} where alternates are stored. This file
     * lists <i>absolute paths</i> to {@link #PATH_OBJECTS objects/} directories within other Git repositories, allowing
     * objects to be "borrowed" between repositories.
     */
    public static final String PATH_ALTERNATES = "alternates";
    /**
     * The path under the repository directory where application-state is tracked for the repository (such as the
     * gc marker)
     */
    public static final String PATH_APPLICATION_INFO = "app-info";
    /**
     * The path under {@link #PATH_OBJECTS objects/}{@link #PATH_INFO info/} where path attributes are stored. These
     * attributes affect how Git interacts with files in the associated repository. For example, they can be used to
     * control how long conflict markers are or to indicate that specific file types contain text.
     */
    public static final String PATH_ATTRIBUTES = "attributes";
    /**
     * The path under {@link #PATH_OBJECTS objects/}{@link #PATH_INFO info/} where {@code git commit-graph write}
     * stores the commit graph.
     */
    public static final String PATH_COMMIT_GRAPH = "commit-graph";
    /**
     * The path under {@link #PATH_OBJECTS objects/}{@link #PATH_INFO info/} where {@code git commit-graph write
     * --split} stores incremental commit graphs.
     * <p>
     * When using {@code --split}, {@code git commit-graph write} maintains a {@link #PATH_COMMIT_GRAPH_CHAIN
     * commit-graph-chain} file which has an <i>ordered list</i> of hashes for incremental graphs, each of which
     * matches a {@code graph-&lt;hash&gt;.graph} file that contains part of the commit graph.
     */
    public static final String PATH_COMMIT_GRAPHS = "commit-graphs";
    /**
     * The path under {@link #PATH_COMMIT_GRAPHS objects/info/commit-graphs} where {@code git commit-graph write
     * --split} writes the chain of hashes for the incremental commit graph.
     */
    public static final String PATH_COMMIT_GRAPH_CHAIN = "commit-graph-chain";
    /**
     * The path within a Git repository where configuration is written.
     */
    public static final String PATH_CONFIG = "config";
    /**
     * The path within {@link #PATH_APPLICATION_INFO} where the process identifier is written while the GC lock
     * is held. This information can be useful for detecting stale locks during support requests.
     */
    public static final String PATH_GC_PID = "gc.pid";
    /**
     * The path within a non-bare clone to the real Git repository.
     */
    public static final String PATH_GIT = ".git";
    /**
     * The path within a repository where submodule configuration is stored.
     */
    public static final String PATH_GITMODULES = ".gitmodules";
    /**
     * The {@code libexec} path where core binaries are stored.
     */
    public static final String PATH_GIT_CORE = "git-core";
    /**
     * The path within a Git repository where special information files are stored. {@link #PATH_ALTERNATES alternates}
     * and {@link #PATH_ATTRIBUTES attributes} are stored here, in addition to pack details for "dumb" HTTP.
     */
    public static final String PATH_INFO = "info";
    /**
     * The path within a Git repository where reflogs are written.
     */
    public static final String PATH_LOGS = "logs";
    /**
     * The path within a Git repository where objects (loose or packed) are written. Packed objects will be in a
     * {@code pack} subdirectory, with loose objects stored in 2-character subdirectories (to optimise lookup).
     */
    public static final String PATH_OBJECTS = "objects";
    /**
     * The path within the {@link #PATH_OBJECTS objects} directory where pack files are written.
     */
    public static final String PATH_PACK = "pack";
    /**
     * The path within {@link #PATH_APPLICATION_INFO app-info} where the peeled tag cache is written.
     * <p>
     * <b>Note</b>: This cache is no longer written. This constant only remains so the GC code can delete stale
     * caches if they exist. It has been superseded by {@link #PATH_TAG_CACHE}.
     */
    public static final String PATH_PEELED_TAGS = "peeled-tags";
    /**
     * The path under {@link #PATH_REFS refs/} or {@link #PATH_DARK_REFS dark-refs/} where pull request refs
     * are stored.
     */
    public static final String PATH_PULL_REQUESTS = "pull-requests";
    /**
     * The path within a Git repository where all <i>publicly advertised</i> refs are stored. Loose refs are stored
     * under this directory, and {@link #PACKED_REFS packed refs} will have a qualified path beginning with this.
     */
    public static final String PATH_REFS = "refs";
    /**
     * The path within a Git repository where the system stores its "dark" refs. <i>These are not considered refs by
     * Git, and will not be included in ref advertisements, nor will they ever be packed.</i>
     */
    public static final String PATH_DARK_REFS = "dark-" + PATH_REFS;
    /**
     * The path within a Bitbucket Server Git repository where the system stores its "dark" refs. <i>These are not
     * considered refs by Git, and will not be included in ref advertisements, nor will they ever be packed.</i>
     */
    public static final String PATH_STASH_REFS = "stash-" + PATH_REFS;
    /**
     * The path within {@link #PATH_APPLICATION_INFO app-info} where tag hashes are cached. This cache allows RefDb
     * to reuse hashes and avoid forking {@code git cat-file --batch} to find them.
     */
    public static final String PATH_TAG_CACHE = "tags.cache";

    /**
     * A regular expression for matching a section heading in a Git {@code config} file and extracting the section's
     * name and its subsection, if present.
     */
    public static final Pattern PATTERN_CONFIG_SECTION = Pattern.compile("\\[([\\w\\-.]+|)(?: +\"([^\"]+)\")?]");
    /**
     * Defines a set of characters which cannot exist in the filename used to ensure sparse checkouts are never
     * empty, for use with {@code StringUtils.containsAny}.
     * <p>
     * On Linux, the only character that appears to throw off the checkout is a backslash (likely because it's
     * treated as an escape character).
     */
    public static final String SPARSE_FILTER = "\\";
    /**
     * The subpath for the {@link #PATH_ALTERNATES alternates} file within a <i>bare</i> Git repository.
     *
     * @see #PATH_ALTERNATES
     */
    public static final String[] SUBPATH_ALTERNATES = {PATH_OBJECTS, PATH_INFO, PATH_ALTERNATES};

    //TODO: What should these be defined as?
    /**
     * A placeholder display name to use when there is no context user available and an operation is performed which
     * requires author/committer/tagger details.
     */
    public static final String SYSTEM_DISPLAY_NAME = "Bitbucket Mesh";
    /**
     * A placeholder e-mail address to use when there is no context user available and an operation is performed
     * which require author/committer/tagger details.
     */
    public static final String SYSTEM_EMAIL_ADDRESS = "bitbucket.mesh@atlassian.com";
    /**
     * A placeholder {@link Person} to use when no context user is available and an operation is performed which
     * requires author/committer/tagger details.
     */
    public static final Person SYSTEM_GIT_IDENT = new Person(SYSTEM_DISPLAY_NAME, SYSTEM_EMAIL_ADDRESS);
}
