/*
 * 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_merge_h__
#define INCLUDE_git_merge_h__

#include "common.h"
#include "types.h"
#include "oid.h"
#include "checkout.h"
#include "index.h"

/**
 * @file git2/merge.h
 * @brief Git merge routines
 * @defgroup git_merge Git merge routines
 * @ingroup Git
 * @{
 */
GIT_BEGIN_DECL

/**
 * Flags for `git_merge_tree` options.  A combination of these flags can be
 * passed in via the `flags` value in the `git_merge_tree_opts`.
 */
typedef enum {
	/**
	 * Detect renames that occur between the common ancestor and the "ours"
	 * side or the common ancestor and the "theirs" side.  This will enable
	 * the ability to merge between a modified and renamed file.
	 */
	GIT_MERGE_TREE_FIND_RENAMES = (1 << 0),
} git_merge_tree_flag_t;

/**
 * Merge file favor options for `git_merge_trees_opts` instruct the file-level
 * merging functionality how to deal with conflicting regions of the files.
 */
typedef enum {
	/**
	 * When a region of a file is changed in both branches, a conflict
	 * will be recorded in the index so that `git_checkout` can produce
	 * a merge file with conflict markers in the working directory.
	 * This is the default.
	 */
	GIT_MERGE_FILE_FAVOR_NORMAL = 0,

	/**
	 * When a region of a file is changed in both branches, the file
	 * created in the index will contain the "ours" side of any conflicting
	 * region.  The index will not record a conflict.
	 */
	GIT_MERGE_FILE_FAVOR_OURS = 1,

	/**
	 * When a region of a file is changed in both branches, the file
	 * created in the index will contain the "theirs" side of any conflicting
	 * region.  The index will not record a conflict.
	 */
	GIT_MERGE_FILE_FAVOR_THEIRS = 2,

	/**
	 * When a region of a file is changed in both branches, the file
	 * created in the index will contain each unique line from each side,
	 * which has the result of combining both files.  The index will not
	 * record a conflict.
	 */
	GIT_MERGE_FILE_FAVOR_UNION = 3,
} git_merge_file_favor_t;


typedef struct {
	unsigned int version;
	git_merge_tree_flag_t flags;

	/**
	 * Similarity to consider a file renamed (default 50).  If
	 * `GIT_MERGE_TREE_FIND_RENAMES` is enabled, added files will be compared
	 * with deleted files to determine their similarity.  Files that are
	 * more similar than the rename threshold (percentage-wise) will be
	 * treated as a rename.
	 */
	unsigned int rename_threshold;

	/**
	 * Maximum similarity sources to examine for renames (default 200).
	 * If the number of rename candidates (add / delete pairs) is greater
	 * than this value, inexact rename detection is aborted.
	 *
	 * This setting overrides the `merge.renameLimit` configuration value.
	 */
	unsigned int target_limit;

	/** Pluggable similarity metric; pass NULL to use internal metric */
	git_diff_similarity_metric *metric;

	/** Flags for handling conflicting content. */
	git_merge_file_favor_t file_favor;
} git_merge_tree_opts;

#define GIT_MERGE_TREE_OPTS_VERSION 1
#define GIT_MERGE_TREE_OPTS_INIT {GIT_MERGE_TREE_OPTS_VERSION}


/**
 * Option flags for `git_merge`.
 */
typedef enum {
	/**
	 * The default behavior is to allow fast-forwards, returning
	 * immediately with the commit ID to fast-forward to.
	 */
	GIT_MERGE_DEFAULT = 0,

	/**
	 * Do not fast-forward; perform a merge and prepare a merge result even
	 * if the inputs are eligible for fast-forwarding.
	 */
	GIT_MERGE_NO_FASTFORWARD = 1,

	/**
	 * Ensure that the inputs are eligible for fast-forwarding, error if
	 * a merge needs to be performed.
	 */
	GIT_MERGE_FASTFORWARD_ONLY = 2,
} git_merge_flags_t;

typedef struct {
	unsigned int version;

	/** Options for handling the commit-level merge. */
	git_merge_flags_t merge_flags;

	/** Options for handling the merges of individual files. */
	git_merge_tree_opts merge_tree_opts;

	/** Options for writing the merge result to the working directory. */
	git_checkout_opts checkout_opts;
} git_merge_opts;

#define GIT_MERGE_OPTS_VERSION 1
#define GIT_MERGE_OPTS_INIT {GIT_MERGE_OPTS_VERSION, 0, GIT_MERGE_TREE_OPTS_INIT, GIT_CHECKOUT_OPTS_INIT}


/**
 * Find a merge base between two commits
 *
 * @param out the OID of a merge base between 'one' and 'two'
 * @param repo the repository where the commits exist
 * @param one one of the commits
 * @param two the other commit
 * @return 0 on success, GIT_ENOTFOUND if not found or error code
 */
GIT_EXTERN(int) git_merge_base(
	git_oid *out,
	git_repository *repo,
	const git_oid *one,
	const git_oid *two);

/**
 * Find a merge base given a list of commits
 *
 * @param out the OID of a merge base considering all the commits
 * @param repo the repository where the commits exist
 * @param length The number of commits in the provided `input_array`
 * @param input_array oids of the commits
 * @return 0 on success, GIT_ENOTFOUND if not found or error code
 */
GIT_EXTERN(int) git_merge_base_many(
	git_oid *out,
	git_repository *repo,
	size_t length,
	const git_oid input_array[]);

/**
 * Creates a `git_merge_head` from the given reference.  The resulting
 * git_merge_head must be freed with `git_merge_head_free`.
 *
 * @param out pointer to store the git_merge_head result in
 * @param repo repository that contains the given reference
 * @param ref reference to use as a merge input
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_head_from_ref(
	git_merge_head **out,
	git_repository *repo,
	git_reference *ref);

/**
 * Creates a `git_merge_head` from the given fetch head data.  The resulting
 * git_merge_head must be freed with `git_merge_head_free`.
 *
 * @param out pointer to store the git_merge_head result in
 * @param repo repository that contains the given commit
 * @param branch_name name of the (remote) branch
 * @param remote_url url of the remote
 * @param oid the commit object id to use as a merge input
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_head_from_fetchhead(
	git_merge_head **out,
	git_repository *repo,
	const char *branch_name,
	const char *remote_url,
	const git_oid *oid);

/**
 * Creates a `git_merge_head` from the given commit id.  The resulting
 * git_merge_head must be freed with `git_merge_head_free`.
 *
 * @param out pointer to store the git_merge_head result in
 * @param repo repository that contains the given commit
 * @param id the commit object id to use as a merge input
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_head_from_id(
	git_merge_head **out,
	git_repository *repo,
	const git_oid *id);

/**
 * Frees a `git_merge_head`.
 *
 * @param head merge head to free
 */
GIT_EXTERN(void) git_merge_head_free(
	git_merge_head *head);

/**
 * Merge two trees, producing a `git_index` that reflects the result of
 * the merge.  The index may be written as-is to the working directory
 * or checked out.  If the index is to be converted to a tree, the caller
 * should resolve any conflicts that arose as part of the merge.
 *
 * The returned index must be freed explicitly with `git_index_free`.
 *
 * @param out pointer to store the index result in
 * @param repo repository that contains the given trees
 * @param ancestor_tree the common ancestor between the trees (or null if none)
 * @param our_tree the tree that reflects the destination tree
 * @param their_tree the tree to merge in to `our_tree`
 * @param opts the merge tree options (or null for defaults)
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_trees(
	git_index **out,
	git_repository *repo,
	const git_tree *ancestor_tree,
	const git_tree *our_tree,
	const git_tree *their_tree,
	const git_merge_tree_opts *opts);

/**
 * Merge two commits, producing a `git_index` that reflects the result of
 * the merge.  The index may be written as-is to the working directory
 * or checked out.  If the index is to be converted to a tree, the caller
 * should resolve any conflicts that arose as part of the merge.
 *
 * The returned index must be freed explicitly with `git_index_free`.
 *
 * @param out pointer to store the index result in
 * @param repo repository that contains the given trees
 * @param our_commit the commit that reflects the destination tree
 * @param their_commit the commit to merge in to `our_commit`
 * @param opts the merge tree options (or null for defaults)
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_commits(
	git_index **out,
	git_repository *repo,
	const git_commit *our_commit,
	const git_commit *their_commit,
	const git_merge_tree_opts *opts);

/**
 * Merges the given commit(s) into HEAD and either returns immediately
 * if there was no merge to perform (the specified commits have already
 * been merged or would produce a fast-forward) or performs the merge
 * and writes the results into the working directory.
 *
 * Callers should inspect the `git_merge_result`:
 *
 * If `git_merge_result_is_uptodate` is true, there is no work to perform.
 *
 * If `git_merge_result_is_fastforward` is true, the caller should update
 * any necessary references to the commit ID returned by
 * `git_merge_result_fastforward_id` and check that out in order to complete
 * the fast-forward.
 * 
 * Otherwise, callers should inspect the resulting index, resolve any
 * conflicts and prepare a commit.
 *
 * The resultant `git_merge_result` should be free with
 * `git_merge_result_free`.
 *
 * @param out the results of the merge
 * @param repo the repository to merge
 * @param merge_heads the heads to merge into
 * @param merge_heads_len the number of heads to merge
 * @param opts merge options
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge(
	git_merge_result **out,
	git_repository *repo,
	const git_merge_head **their_heads,
	size_t their_heads_len,
	const git_merge_opts *opts);

/**
 * Returns true if a merge is "up-to-date", meaning that the commit(s) 
 * that were provided to `git_merge` are already included in `HEAD`
 * and there is no work to do.
 *
 * @return true if the merge is up-to-date, false otherwise
 */
GIT_EXTERN(int) git_merge_result_is_uptodate(git_merge_result *merge_result);

/**
 * Returns true if a merge is eligible to be "fast-forwarded", meaning that
 * the commit that was provided to `git_merge` need not be merged, it can
 * simply be checked out, because the current `HEAD` is the merge base of
 * itself and the given commit.  To perform the fast-forward, the caller
 * should check out the results of `git_merge_result_fastforward_id`.
 * 
 * This will never be true if `GIT_MERGE_NO_FASTFORWARD` is supplied as
 * a merge option.
 *
 * @return true if the merge is fast-forwardable, false otherwise
 */
GIT_EXTERN(int) git_merge_result_is_fastforward(git_merge_result *merge_result);

/**
 * Gets the fast-forward OID if the merge was a fastforward.
 *
 * @param out pointer to populate with the OID of the fast-forward
 * @param merge_result the results of the merge
 * @return 0 on success or error code
 */
GIT_EXTERN(int) git_merge_result_fastforward_id(git_oid *out, git_merge_result *merge_result);

/**
 * Frees a `git_merge_result`.
 *
 * @param result merge result to free
 */
GIT_EXTERN(void) git_merge_result_free(git_merge_result *merge_result);

/** @} */
GIT_END_DECL
#endif
