/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#ifndef INCLUDE_git_checkout_h__
#define INCLUDE_git_checkout_h__

#include "common.h"
#include "types.h"
#include "diff.h"

/**
 * @file git2/checkout.h
 * @brief Update the contents of the working directory
 * @defgroup git_checkout Git checkout routines
 * @ingroup Git
 *
 * Update the contents of the working directory, or a subset of the
 * files in the working directory, to point to the data in the index
 * or a specific commit.
 * @{
 */
GIT_BEGIN_DECL

/**
 * Checkout behavior flags
 *
 * In libgit2, checkout is used to update the working directory and index
 * to match a target tree.  Unlike git checkout, it does not move the HEAD
 * commit for you - use `git_repository_set_head` or the like to do that.
 *
 * Checkout looks at (up to) four things: the "target" tree you want to
 * check out, the "baseline" tree of what was checked out previously, the
 * working directory for actual files, and the index for staged changes.
 *
 * You give checkout one of two strategies for update:
 *
 * - `GIT_CHECKOUT_SAFE` is the default, and similar to git's default,
 *   which will make modifications that will not lose changes in the
 *   working directory.
 *
 *                         |  target == baseline   |  target != baseline  |
 *    ---------------------|-----------------------|----------------------|
 *     workdir == baseline |       no action       |  create, update, or  |
 *                         |                       |     delete file      |
 *    ---------------------|-----------------------|----------------------|
 *     workdir exists and  |       no action       |   conflict (notify   |
 *       is != baseline    | notify dirty MODIFIED | and cancel checkout) |
 *    ---------------------|-----------------------|----------------------|
 *      workdir missing,   | notify dirty DELETED  |     create file      |
 *      baseline present   |                       |                      |
 *    ---------------------|-----------------------|----------------------|
 *
 * - `GIT_CHECKOUT_FORCE` will take any action to make the working
 *   directory match the target (including potentially discarding
 *   modified files).
 *
 * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
 * notification callback (see below) that displays information about dirty
 * files.  The default behavior will cancel checkout on conflicts.
 *
 * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE` with a
 * notification callback that cancels the operation if a dirty-but-existing
 * file is found in the working directory.  This core git command isn't
 * quite "force" but is sensitive about some types of changes.
 *
 * To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
 *
 *
 * There are some additional flags to modify the behavior of checkout:
 *
 * - `GIT_CHECKOUT_DRY_RUN` is a dry-run strategy that checks for conflicts,
 *   etc., but doesn't make any actual changes.
 *
 * - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
 *   even if there are conflicts (instead of cancelling the checkout).
 *
 * - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not
 *   in target, baseline, or index, and not ignored) from the working dir.
 *
 * - GIT_CHECKOUT_REMOVE_IGNORED means remove ignored files (that are also
 *   untracked) from the working directory as well.
 *
 * - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
 *   already exist.  Files will not be created nor deleted.  This just skips
 *   applying adds, deletes, and typechanges.
 *
 * - GIT_CHECKOUT_DONT_UPDATE_INDEX prevents checkout from writing the
 *   updated files' information to the index.
 *
 * - Normally, checkout will reload the index and git attributes from disk
 *   before any operations.  GIT_CHECKOUT_NO_REFRESH prevents this reload.
 *
 * - Unmerged index entries are conflicts.  GIT_CHECKOUT_SKIP_UNMERGED skips
 *   files with unmerged index entries instead.  GIT_CHECKOUT_USE_OURS and
 *   GIT_CHECKOUT_USE_THEIRS to proceed with the checkout using either the
 *   stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
 *
 * - GIT_CHECKOUT_DONT_OVERWRITE_IGNORED prevents ignored files from being
 *   overwritten.  Normally, files that are ignored in the working directory
 *   are not considered "precious" and may be overwritten if the checkout
 *   target contains that file.
 *
 * - GIT_CHECKOUT_DONT_REMOVE_EXISTING prevents checkout from removing
 *   files or folders that fold to the same name on case insensitive
 *   filesystems.  This can cause files to retain their existing names
 *   and write through existing symbolic links.
 *
 * @flags
 */
typedef enum {
	/**
	 * Allow safe updates that cannot overwrite uncommitted data.
	 * If the uncommitted changes don't conflict with the checked
	 * out files, the checkout will still proceed, leaving the
	 * changes intact.
	 */
	GIT_CHECKOUT_SAFE = 0,

	/**
	 * Allow all updates to force working directory to look like
	 * the index, potentially losing data in the process.
	 */
	GIT_CHECKOUT_FORCE = (1u << 1),

	/** Allow checkout to recreate missing files */
	GIT_CHECKOUT_RECREATE_MISSING = (1u << 2),

	/** Allow checkout to make safe updates even if conflicts are found */
	GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),

	/** Remove untracked files not in index (that are not ignored) */
	GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),

	/** Remove ignored files not in index */
	GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),

	/** Only update existing files, don't create new ones */
	GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),

	/**
	 * Normally checkout updates index entries as it goes; this stops that.
	 * Implies `GIT_CHECKOUT_DONT_WRITE_INDEX`.
	 */
	GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),

	/** Don't refresh index/config/etc before doing checkout */
	GIT_CHECKOUT_NO_REFRESH = (1u << 9),

	/** Allow checkout to skip unmerged files */
	GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
	/** For unmerged files, checkout stage 2 from index */
	GIT_CHECKOUT_USE_OURS = (1u << 11),
	/** For unmerged files, checkout stage 3 from index */
	GIT_CHECKOUT_USE_THEIRS = (1u << 12),

	/** Treat pathspec as simple list of exact match file paths */
	GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),

	/** Ignore directories in use, they will be left empty */
	GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = (1u << 18),

	/** Don't overwrite ignored files that exist in the checkout target */
	GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = (1u << 19),

	/** Write normal merge files for conflicts */
	GIT_CHECKOUT_CONFLICT_STYLE_MERGE = (1u << 20),

	/** Include common ancestor data in diff3 format files for conflicts */
	GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = (1u << 21),

	/** Don't overwrite existing files or folders */
	GIT_CHECKOUT_DONT_REMOVE_EXISTING = (1u << 22),

	/** Normally checkout writes the index upon completion; this prevents that. */
	GIT_CHECKOUT_DONT_WRITE_INDEX = (1u << 23),

	/**
	 * Perform a "dry run", reporting what _would_ be done but
	 * without actually making changes in the working directory
	 * or the index.
	 */
	GIT_CHECKOUT_DRY_RUN = (1u << 24),

	/** Include common ancestor data in zdiff3 format for conflicts */
	GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25),

	/**
	 * Do not do a checkout and do not fire callbacks; this is primarily
	 * useful only for internal functions that will perform the
	 * checkout themselves but need to pass checkout options into
	 * another function, for example, `git_clone`.
	*/
	GIT_CHECKOUT_NONE = (1u << 30),

	/*
	 * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
	 */

	/** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
	GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
	/** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
	GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17)
} git_checkout_strategy_t;

/**
 * Checkout notification flags
 *
 * Checkout will invoke an options notification callback (`notify_cb`) for
 * certain cases - you pick which ones via `notify_flags`:
 *
 * Returning a non-zero value from this callback will cancel the checkout.
 * The non-zero return value will be propagated back and returned by the
 * git_checkout_... call.
 *
 * Notification callbacks are made prior to modifying any files on disk,
 * so canceling on any notification will still happen prior to any files
 * being modified.
 *
 * @flags
 */
typedef enum {
	GIT_CHECKOUT_NOTIFY_NONE      = 0,

	/**
	 * Invokes checkout on conflicting paths.
	 */
	GIT_CHECKOUT_NOTIFY_CONFLICT  = (1u << 0),

	/**
	 * Notifies about "dirty" files, i.e. those that do not need an update
	 * but no longer match the baseline.  Core git displays these files when
	 * checkout runs, but won't stop the checkout.
	 */
	GIT_CHECKOUT_NOTIFY_DIRTY     = (1u << 1),

	/**
	 * Sends notification for any file changed.
	 */
	GIT_CHECKOUT_NOTIFY_UPDATED   = (1u << 2),

	/**
	 * Notifies about untracked files.
	 */
	GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),

	/**
	 * Notifies about ignored files.
	 */
	GIT_CHECKOUT_NOTIFY_IGNORED   = (1u << 4),

	GIT_CHECKOUT_NOTIFY_ALL       = 0x0FFFFu
} git_checkout_notify_t;

/** Checkout performance-reporting structure */
typedef struct {
	size_t mkdir_calls;
	size_t stat_calls;
	size_t chmod_calls;
} git_checkout_perfdata;

/**
 * Checkout notification callback function.
 *
 * @param why the notification reason
 * @param path the path to the file being checked out
 * @param baseline the baseline's diff file information
 * @param target the checkout target diff file information
 * @param workdir the working directory diff file information
 * @param payload the user-supplied callback payload
 * @return 0 on success, or an error code
 */
typedef int GIT_CALLBACK(git_checkout_notify_cb)(
	git_checkout_notify_t why,
	const char *path,
	const git_diff_file *baseline,
	const git_diff_file *target,
	const git_diff_file *workdir,
	void *payload);

/**
 * Checkout progress notification function.
 *
 * @param path the path to the file being checked out
 * @param completed_steps number of checkout steps completed
 * @param total_steps number of total steps in the checkout process
 * @param payload the user-supplied callback payload
 */
typedef void GIT_CALLBACK(git_checkout_progress_cb)(
	const char *path,
	size_t completed_steps,
	size_t total_steps,
	void *payload);

/**
 * Checkout performance data reporting function.
 *
 * @param perfdata the performance data for the checkout
 * @param payload the user-supplied callback payload
 */
typedef void GIT_CALLBACK(git_checkout_perfdata_cb)(
	const git_checkout_perfdata *perfdata,
	void *payload);

/**
 * Checkout options structure
 *
 * Initialize with `GIT_CHECKOUT_OPTIONS_INIT`. Alternatively, you can
 * use `git_checkout_options_init`.
 *
 * @options[version] GIT_CHECKOUT_OPTIONS_VERSION
 * @options[init_macro] GIT_CHECKOUT_OPTIONS_INIT
 * @options[init_function] git_checkout_options_init
 */
typedef struct git_checkout_options {
	unsigned int version; /**< The version */

	/**
	 * Checkout strategy. Default is a safe checkout.
	 *
	 * @type[flags] git_checkout_strategy_t
	 */
	unsigned int checkout_strategy; /**< default will be a safe checkout */

	int disable_filters;    /**< don't apply filters like CRLF conversion */
	unsigned int dir_mode;  /**< default is 0755 */
	unsigned int file_mode; /**< default is 0644 or 0755 as dictated by blob */
	int file_open_flags;    /**< default is O_CREAT | O_TRUNC | O_WRONLY */

	/**
	 * Checkout notification flags specify what operations the notify
	 * callback is invoked for.
	 *
	 * @type[flags] git_checkout_notify_t
	 */
	unsigned int notify_flags;

	/**
	 * Optional callback to get notifications on specific file states.
	 * @see git_checkout_notify_t
	 */
	git_checkout_notify_cb notify_cb;

	/** Payload passed to notify_cb */
	void *notify_payload;

	/** Optional callback to notify the consumer of checkout progress. */
	git_checkout_progress_cb progress_cb;

	/** Payload passed to progress_cb */
	void *progress_payload;

	/**
	 * A list of wildmatch patterns or paths.
	 *
	 * By default, all paths are processed. If you pass an array of wildmatch
	 * patterns, those will be used to filter which paths should be taken into
	 * account.
	 *
	 * Use GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as a simple list.
	 */
	git_strarray paths;

	/**
	 * The expected content of the working directory; defaults to HEAD.
	 *
	 * If the working directory does not match this baseline information,
	 * that will produce a checkout conflict.
	 */
	git_tree *baseline;

	/**
	 * Like `baseline` above, though expressed as an index.  This
	 * option overrides `baseline`.
	 */
	git_index *baseline_index;

	const char *target_directory; /**< alternative checkout path to workdir */

	const char *ancestor_label; /**< the name of the common ancestor side of conflicts */
	const char *our_label; /**< the name of the "our" side of conflicts */
	const char *their_label; /**< the name of the "their" side of conflicts */

	/** Optional callback to notify the consumer of performance data. */
	git_checkout_perfdata_cb perfdata_cb;

	/** Payload passed to perfdata_cb */
	void *perfdata_payload;
} git_checkout_options;


/** Current version for the `git_checkout_options` structure */
#define GIT_CHECKOUT_OPTIONS_VERSION 1

/** Static constructor for `git_checkout_options` */
#define GIT_CHECKOUT_OPTIONS_INIT { GIT_CHECKOUT_OPTIONS_VERSION }

/**
 * Initialize git_checkout_options structure
 *
 * Initializes a `git_checkout_options` with default values. Equivalent to creating
 * an instance with GIT_CHECKOUT_OPTIONS_INIT.
 *
 * @param opts The `git_checkout_options` struct to initialize.
 * @param version The struct version; pass `GIT_CHECKOUT_OPTIONS_VERSION`.
 * @return Zero on success; -1 on failure.
 */
GIT_EXTERN(int) git_checkout_options_init(
	git_checkout_options *opts,
	unsigned int version);

/**
 * Updates files in the index and the working tree to match the content of
 * the commit pointed at by HEAD.
 *
 * Note that this is _not_ the correct mechanism used to switch branches;
 * do not change your `HEAD` and then call this method, that would leave
 * you with checkout conflicts since your working directory would then
 * appear to be dirty.  Instead, checkout the target of the branch and
 * then update `HEAD` using `git_repository_set_head` to point to the
 * branch you checked out.
 *
 * @param repo repository to check out (must be non-bare)
 * @param opts specifies checkout options (may be NULL)
 * @return 0 on success, GIT_EUNBORNBRANCH if HEAD points to a non
 *         existing branch, non-zero value returned by `notify_cb`, or
 *         other error code < 0 (use git_error_last for error details)
 */
GIT_EXTERN(int) git_checkout_head(
	git_repository *repo,
	const git_checkout_options *opts);

/**
 * Updates files in the working tree to match the content of the index.
 *
 * @param repo repository into which to check out (must be non-bare)
 * @param index index to be checked out (or NULL to use repository index)
 * @param opts specifies checkout options (may be NULL)
 * @return 0 on success, non-zero return value from `notify_cb`, or error
 *         code < 0 (use git_error_last for error details)
 */
GIT_EXTERN(int) git_checkout_index(
	git_repository *repo,
	git_index *index,
	const git_checkout_options *opts);

/**
 * Updates files in the index and working tree to match the content of the
 * tree pointed at by the treeish.
 *
 * @param repo repository to check out (must be non-bare)
 * @param treeish a commit, tag or tree which content will be used to update
 * the working directory (or NULL to use HEAD)
 * @param opts specifies checkout options (may be NULL)
 * @return 0 on success, non-zero return value from `notify_cb`, or error
 *         code < 0 (use git_error_last for error details)
 */
GIT_EXTERN(int) git_checkout_tree(
	git_repository *repo,
	const git_object *treeish,
	const git_checkout_options *opts);

/** @} */
GIT_END_DECL

#endif
