summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt19
-rw-r--r--examples/diff.c50
-rw-r--r--include/git2/diff.h70
-rw-r--r--include/git2/merge.h59
-rw-r--r--include/git2/oid.h21
-rw-r--r--include/git2/refs.h13
-rw-r--r--include/git2/repository.h9
-rw-r--r--include/git2/tag.h31
-rw-r--r--include/git2/types.h3
-rw-r--r--src/checkout.c29
-rw-r--r--src/config.c60
-rw-r--r--src/diff.c15
-rw-r--r--src/diff.h12
-rw-r--r--src/diff_output.c133
-rw-r--r--src/diff_tform.c561
-rw-r--r--src/fileops.c26
-rw-r--r--src/fileops.h4
-rw-r--r--src/merge.c595
-rw-r--r--src/merge.h22
-rw-r--r--src/oid.c50
-rw-r--r--src/refdb_fs.c2
-rw-r--r--src/repository.c17
-rw-r--r--src/tag.c13
-rw-r--r--src/util.h7
-rw-r--r--src/win32/git2.rc.cmake (renamed from src/win32/git2.rc)6
-rw-r--r--tests-clar/diff/diff_helpers.c5
-rw-r--r--tests-clar/diff/diff_helpers.h2
-rw-r--r--tests-clar/diff/rename.c375
-rw-r--r--tests-clar/merge/workdir/setup.c856
-rw-r--r--tests-clar/object/raw/convert.c37
-rw-r--r--tests-clar/object/raw/short.c39
-rw-r--r--tests-clar/object/tag/write.c38
-rw-r--r--tests-clar/repo/config.c129
-rw-r--r--tests-clar/repo/shallow.c33
-rw-r--r--tests-clar/resources/shallow.git/HEAD1
-rw-r--r--tests-clar/resources/shallow.git/config8
-rw-r--r--tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idxbin0 -> 1324 bytes
-rw-r--r--tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.packbin0 -> 791 bytes
-rw-r--r--tests-clar/resources/shallow.git/packed-refs2
-rw-r--r--tests-clar/resources/shallow.git/refs/.gitkeep0
-rw-r--r--tests-clar/resources/shallow.git/shallow1
41 files changed, 2973 insertions, 380 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3aa3770b8..016d77ad1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,8 +106,8 @@ ENDIF()
# Specify sha1 implementation
IF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin")
- ADD_DEFINITIONS(-DWIN32_SHA1)
- FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
+ ADD_DEFINITIONS(-DWIN32_SHA1)
+ FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin")
ADD_DEFINITIONS(-DOPENSSL_SHA1)
ELSE()
@@ -155,7 +155,7 @@ ENDIF()
# Platform specific compilation flags
IF (MSVC)
- STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+ STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
# /GF - String pooling
# /MP - Parallel build
@@ -170,7 +170,7 @@ IF (MSVC)
SET(CRT_FLAG_DEBUG "/MTd")
SET(CRT_FLAG_RELEASE "/MT")
ELSE()
- SET(CRT_FLAG_DEBUG "/MDd")
+ SET(CRT_FLAG_DEBUG "/MDd")
SET(CRT_FLAG_RELEASE "/MD")
ENDIF()
@@ -321,8 +321,19 @@ IF (SONAME)
SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME "git2-${SONAME_APPEND}")
ENDIF()
ENDIF()
+
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
+IF (NOT BUILD_SHARED_LIBS)
+ SET(LIBGIT2_NAME_PREFIX "lib")
+ENDIF()
+
+IF (SONAME_APPEND)
+ SET(LIBGIT2_NAME_SUFFIX "-${SONAME_APPEND}")
+ENDIF()
+
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/src/win32/git2.rc.cmake ${WIN_RC} @ONLY)
+
IF (MSVC_IDE)
# Precompiled headers
SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
diff --git a/examples/diff.c b/examples/diff.c
index bb4f0ec21..11efa21ec 100644
--- a/examples/diff.c
+++ b/examples/diff.c
@@ -84,6 +84,10 @@ static int check_uint16_param(const char *arg, const char *pattern, uint16_t *va
char *endptr = NULL;
if (strncmp(arg, pattern, len))
return 0;
+ if (arg[len] == '\0' && pattern[len - 1] != '=')
+ return 1;
+ if (arg[len] == '=')
+ len++;
strval = strtoul(arg + len, &endptr, 0);
if (endptr == arg)
return 0;
@@ -110,13 +114,20 @@ static void usage(const char *message, const char *arg)
exit(1);
}
+enum {
+ FORMAT_PATCH = 0,
+ FORMAT_COMPACT = 1,
+ FORMAT_RAW = 2
+};
+
int main(int argc, char *argv[])
{
git_repository *repo = NULL;
git_tree *t1 = NULL, *t2 = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
git_diff_list *diff;
- int i, color = -1, compact = 0, cached = 0;
+ int i, color = -1, format = FORMAT_PATCH, cached = 0;
char *a, *treeish1 = NULL, *treeish2 = NULL;
const char *dir = ".";
@@ -137,11 +148,13 @@ int main(int argc, char *argv[])
}
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
!strcmp(a, "--patch"))
- compact = 0;
+ format = FORMAT_PATCH;
else if (!strcmp(a, "--cached"))
cached = 1;
else if (!strcmp(a, "--name-status"))
- compact = 1;
+ format = FORMAT_COMPACT;
+ else if (!strcmp(a, "--raw"))
+ format = FORMAT_RAW;
else if (!strcmp(a, "--color"))
color = 0;
else if (!strcmp(a, "--no-color"))
@@ -160,6 +173,20 @@ int main(int argc, char *argv[])
opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
else if (!strcmp(a, "--untracked"))
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+ else if (check_uint16_param(a, "-M", &findopts.rename_threshold) ||
+ check_uint16_param(a, "--find-renames",
+ &findopts.rename_threshold))
+ findopts.flags |= GIT_DIFF_FIND_RENAMES;
+ else if (check_uint16_param(a, "-C", &findopts.copy_threshold) ||
+ check_uint16_param(a, "--find-copies",
+ &findopts.copy_threshold))
+ findopts.flags |= GIT_DIFF_FIND_COPIES;
+ else if (!strcmp(a, "--find-copies-harder"))
+ findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
+ else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) {
+ /* TODO: parse thresholds */
+ findopts.flags |= GIT_DIFF_FIND_REWRITES;
+ }
else if (!check_uint16_param(a, "-U", &opts.context_lines) &&
!check_uint16_param(a, "--unified=", &opts.context_lines) &&
!check_uint16_param(a, "--inter-hunk-context=",
@@ -204,13 +231,24 @@ int main(int argc, char *argv[])
else
check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff");
+ if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0)
+ check(git_diff_find_similar(diff, &findopts),
+ "finding renames and copies ");
+
if (color >= 0)
fputs(colors[0], stdout);
- if (compact)
- check(git_diff_print_compact(diff, printer, &color), "Displaying diff");
- else
+ switch (format) {
+ case FORMAT_PATCH:
check(git_diff_print_patch(diff, printer, &color), "Displaying diff");
+ break;
+ case FORMAT_COMPACT:
+ check(git_diff_print_compact(diff, printer, &color), "Displaying diff");
+ break;
+ case FORMAT_RAW:
+ check(git_diff_print_raw(diff, printer, &color), "Displaying diff");
+ break;
+ }
if (color >= 0)
fputs(colors[0], stdout);
diff --git a/include/git2/diff.h b/include/git2/diff.h
index 1feddd7a2..0d4875b43 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -243,6 +243,19 @@ typedef struct {
* `NOT_BINARY` flag set to avoid examining file contents if you do not pass
* in hunk and/or line callbacks to the diff foreach iteration function. It
* will just use the git attributes for those files.
+ *
+ * The similarity score is zero unless you call `git_diff_find_similar()`
+ * which does a similarity analysis of files in the diff. Use that
+ * function to do rename and copy detection, and to split heavily modified
+ * files in add/delete pairs. After that call, deltas with a status of
+ * GIT_DELTA_RENAMED or GIT_DELTA_COPIED will have a similarity score
+ * between 0 and 100 indicating how similar the old and new sides are.
+ *
+ * If you ask `git_diff_find_similar` to find heavily modified files to
+ * break, but to not *actually* break the records, then GIT_DELTA_MODIFIED
+ * records may have a non-zero similarity score if the self-similarity is
+ * below the split threshold. To display this value like core Git, invert
+ * the score (a la `printf("M%03d", 100 - delta->similarity)`).
*/
typedef struct {
git_diff_file old_file;
@@ -408,18 +421,28 @@ typedef enum {
/** consider unmodified as copy sources? (`--find-copies-harder`) */
GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3),
- /** split large rewrites into delete/add pairs (`--break-rewrites=/M`) */
- GIT_DIFF_FIND_AND_BREAK_REWRITES = (1 << 4),
+ /** mark large rewrites for split (`--break-rewrites=/M`) */
+ GIT_DIFF_FIND_REWRITES = (1 << 4),
+ /** actually split large rewrites into delete/add pairs */
+ GIT_DIFF_BREAK_REWRITES = (1 << 5),
+ /** mark rewrites for split and break into delete/add pairs */
+ GIT_DIFF_FIND_AND_BREAK_REWRITES =
+ (GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES),
+
+ /** find renames/copies for untracked items in working directory */
+ GIT_DIFF_FIND_FOR_UNTRACKED = (1 << 6),
/** turn on all finding features */
- GIT_DIFF_FIND_ALL = (0x1f),
+ GIT_DIFF_FIND_ALL = (0x0ff),
/** measure similarity ignoring leading whitespace (default) */
GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,
/** measure similarity ignoring all whitespace */
- GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 6),
+ GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 12),
/** measure similarity including all data */
- GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 7),
+ GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13),
+ /** measure similarity only by comparing SHAs (fast and cheap) */
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14),
} git_diff_find_t;
/**
@@ -446,7 +469,10 @@ typedef struct {
* - `copy_threshold` is the same as the -C option with a value
* - `rename_from_rewrite_threshold` matches the top of the -B option
* - `break_rewrite_threshold` matches the bottom of the -B option
- * - `target_limit` matches the -l option
+ * - `rename_limit` is the maximum number of matches to consider for
+ * a particular file. This is a little different from the `-l` option
+ * to regular Git because we will still process up to this many matches
+ * before abandoning the search.
*
* The `metric` option allows you to plug in a custom similarity metric.
* Set it to NULL for the default internal metric which is based on sampling
@@ -458,21 +484,21 @@ typedef struct {
unsigned int version;
/** Combination of git_diff_find_t values (default FIND_RENAMES) */
- unsigned int flags;
+ uint32_t flags;
/** Similarity to consider a file renamed (default 50) */
- unsigned int rename_threshold;
+ uint16_t rename_threshold;
/** Similarity of modified to be eligible rename source (default 50) */
- unsigned int rename_from_rewrite_threshold;
+ uint16_t rename_from_rewrite_threshold;
/** Similarity to consider a file a copy (default 50) */
- unsigned int copy_threshold;
+ uint16_t copy_threshold;
/** Similarity to split modify into delete/add pair (default 60) */
- unsigned int break_rewrite_threshold;
+ uint16_t break_rewrite_threshold;
- /** Maximum similarity sources to examine (a la diff's `-l` option or
- * the `diff.renameLimit` config) (default 200)
+ /** Maximum similarity sources to examine for a file (somewhat like
+ * git-diff's `-l` option or `diff.renameLimit` config) (default 200)
*/
- unsigned int target_limit;
+ size_t rename_limit;
/** Pluggable similarity metric; pass NULL to use internal metric */
git_diff_similarity_metric *metric;
@@ -689,6 +715,22 @@ GIT_EXTERN(int) git_diff_print_compact(
void *payload);
/**
+ * Iterate over a diff generating text output like "git diff --raw".
+ *
+ * Returning a non-zero value from the callbacks will terminate the
+ * iteration and cause this return `GIT_EUSER`.
+ *
+ * @param diff A git_diff_list generated by one of the above functions.
+ * @param print_cb Callback to make per line of diff text.
+ * @param payload Reference pointer that will be passed to your callback.
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
+ */
+GIT_EXTERN(int) git_diff_print_raw(
+ git_diff_list *diff,
+ git_diff_data_cb print_cb,
+ void *payload);
+
+/**
* Look up the single character abbreviation for a delta status code.
*
* When you call `git_diff_print_compact` it prints single letter codes into
diff --git a/include/git2/merge.h b/include/git2/merge.h
index 955840569..af8f36063 100644
--- a/include/git2/merge.h
+++ b/include/git2/merge.h
@@ -23,13 +23,13 @@
GIT_BEGIN_DECL
/**
- * Flags for tree_many diff options. A combination of these flags can be
- * passed in via the `flags` value in the `git_diff_tree_many_options`.
+ * Flags for `git_mrege_tree` options. A combination of these flags can be
+ * passed in via the `flags` vlaue in the `git_merge_tree_opts`.
*/
typedef enum {
/** Detect renames */
GIT_MERGE_TREE_FIND_RENAMES = (1 << 0),
-} git_merge_tree_flags;
+} git_merge_tree_flag_t;
/**
* Automerge options for `git_merge_trees_opts`.
@@ -44,7 +44,7 @@ typedef enum {
typedef struct {
unsigned int version;
- git_merge_tree_flags flags;
+ git_merge_tree_flag_t flags;
/** Similarity to consider a file renamed (default 50) */
unsigned int rename_threshold;
@@ -96,6 +96,57 @@ GIT_EXTERN(int) git_merge_base_many(
size_t length);
/**
+ * Creates a `git_merge_head` from the given reference
+ *
+ * @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 zero on success, -1 on failure.
+ */
+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
+ *
+ * @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 zero on success, -1 on failure.
+ */
+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
+ *
+ * @param out pointer to store the git_merge_head result in
+ * @param repo repository that contains the given commit
+ * @param oid the commit object id to use as a merge input
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_head_from_oid(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_oid *oid);
+
+/**
+ * Frees a `git_merge_head`
+ *
+ * @param the 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.
*
diff --git a/include/git2/oid.h b/include/git2/oid.h
index b20bb221a..018cead4a 100644
--- a/include/git2/oid.h
+++ b/include/git2/oid.h
@@ -90,6 +90,17 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw);
GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
/**
+ * Format a git_oid into a partial hex string.
+ *
+ * @param out output hex string; you say how many bytes to write.
+ * If the number of bytes is > GIT_OID_HEXSZ, extra bytes
+ * will be zeroed; if not, a '\0' terminator is NOT added.
+ * @param n number of characters to write into out string
+ * @param oid oid structure to format.
+ */
+GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id);
+
+/**
* Format a git_oid into a loose-object path string.
*
* The resulting string is "aa/...", where "aa" is the first two
@@ -117,10 +128,12 @@ GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *id);
* Format a git_oid into a buffer as a hex format c-string.
*
* If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting
- * oid c-string will be truncated to n-1 characters. If there are
- * any input parameter errors (out == NULL, n == 0, oid == NULL),
- * then a pointer to an empty string is returned, so that the return
- * value can always be printed.
+ * oid c-string will be truncated to n-1 characters (but will still be
+ * NUL-byte terminated).
+ *
+ * If there are any input parameter errors (out == NULL, n == 0, oid ==
+ * NULL), then a pointer to an empty string is returned, so that the
+ * return value can always be printed.
*
* @param out the buffer into which the oid string is output.
* @param n the size of the out buffer.
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 754bda785..468d0f930 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -48,7 +48,7 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo,
*
* @param out Pointer to oid to be filled in
* @param repo The repository in which to look up the reference
- * @param name The long name for the reference
+ * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
* @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code.
*/
GIT_EXTERN(int) git_reference_name_to_id(
@@ -354,6 +354,17 @@ GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2);
GIT_EXTERN(int) git_reference_iterator_new(git_reference_iterator **out, git_repository *repo);
/**
+ * Create an iterator for the repo's references that match the
+ * specified glob
+ *
+ * @param out pointer in which to store the iterator
+ * @param repo the repository
+ * @param glob the glob to match against the reference names
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_iterator_glob_new(git_reference_iterator **out, git_repository *repo, const char *glob);
+
+/**
* Get the next reference name
*
* @param out pointer in which to store the string
diff --git a/include/git2/repository.h b/include/git2/repository.h
index bb2b3db83..4fbd913b1 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -657,6 +657,15 @@ GIT_EXTERN(int) git_repository_set_namespace(git_repository *repo, const char *n
*/
GIT_EXTERN(const char *) git_repository_get_namespace(git_repository *repo);
+
+/**
+ * Determine if the repository was a shallow clone
+ *
+ * @param repo The repository
+ * @return 1 if shallow, zero if not
+ */
+GIT_EXTERN(int) git_repository_is_shallow(git_repository *repo);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/tag.h b/include/git2/tag.h
index 469b1d72b..c822cee7c 100644
--- a/include/git2/tag.h
+++ b/include/git2/tag.h
@@ -178,6 +178,37 @@ GIT_EXTERN(int) git_tag_create(
int force);
/**
+ * Create a new tag in the object database pointing to a git_object
+ *
+ * The message will not be cleaned up. This can be achieved
+ * through `git_message_prettify()`.
+ *
+ * @param oid Pointer where to store the OID of the
+ * newly created tag
+ *
+ * @param repo Repository where to store the tag
+ *
+ * @param tag_name Name for the tag
+ *
+ * @param target Object to which this tag points. This object
+ * must belong to the given `repo`.
+ *
+ * @param tagger Signature of the tagger for this tag, and
+ * of the tagging time
+ *
+ * @param message Full message for this tag
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message);
+
+/**
* Create a new tag in the repository from a buffer
*
* @param oid Pointer where to store the OID of the newly created tag
diff --git a/include/git2/types.h b/include/git2/types.h
index a589cdbaf..1bfa73be6 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -171,6 +171,9 @@ typedef struct git_reference git_reference;
/** Iterator for references */
typedef struct git_reference_iterator git_reference_iterator;
+/** Merge heads, the input to merge */
+typedef struct git_merge_head git_merge_head;
+
/** Basic type of any Git reference. */
typedef enum {
diff --git a/src/checkout.c b/src/checkout.c
index 5820f626a..c28fcdee0 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -676,33 +676,26 @@ static int buffer_to_file(
int file_open_flags,
mode_t file_mode)
{
- int fd, error;
+ int error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
return error;
- if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
- giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
- return fd;
- }
-
- if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
- giterr_set(GITERR_OS, "Could not write to '%s'", path);
- (void)p_close(fd);
- } else {
- if ((error = p_close(fd)) < 0)
- giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ if ((error = git_futils_writebuffer(
+ buffer, path, file_open_flags, file_mode)) < 0)
+ return error;
- if ((error = p_stat(path, st)) < 0)
- giterr_set(GITERR_OS, "Error while statting '%s'", path);
+ if (st != NULL && (error = p_stat(path, st)) < 0) {
+ giterr_set(GITERR_OS, "Error while statting '%s'", path);
+ return error;
}
- if (!error &&
- (file_mode & 0100) != 0 &&
- (error = p_chmod(path, file_mode)) < 0)
+ if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) {
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+ return error;
+ }
- return error;
+ return 0;
}
static int blob_content_to_file(
diff --git a/src/config.c b/src/config.c
index dc8c7024e..e436a31ad 100644
--- a/src/config.c
+++ b/src/config.c
@@ -339,17 +339,6 @@ int git_config_foreach_match(
return ret;
}
-int git_config_delete_entry(git_config *cfg, const char *name)
-{
- git_config_backend *file;
- file_internal *internal;
-
- internal = git_vector_get(&cfg->files, 0);
- file = internal->file;
-
- return file->del(file, name);
-}
-
/**************
* Setters
**************/
@@ -361,6 +350,19 @@ static int config_error_nofiles(const char *name)
return GIT_ENOTFOUND;
}
+int git_config_delete_entry(git_config *cfg, const char *name)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ return file->del(file, name);
+}
+
int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
{
char str_value[32]; /* All numbers should fit in here */
@@ -390,7 +392,7 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
}
internal = git_vector_get(&cfg->files, 0);
- if (!internal)
+ if (!internal || !internal->file)
return config_error_nofiles(name);
file = internal->file;
@@ -465,10 +467,13 @@ static int get_string(const char **out, const git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
+ int res;
git_vector_foreach(&cfg->files, i, internal) {
- int res = get_string_at_file(out, internal->file, name);
+ if (!internal || !internal->file)
+ continue;
+ res = get_string_at_file(out, internal->file, name);
if (res != GIT_ENOTFOUND)
return res;
}
@@ -503,12 +508,17 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co
{
file_internal *internal;
unsigned int i;
+ git_config_backend *file;
+ int ret;
*out = NULL;
git_vector_foreach(&cfg->files, i, internal) {
- git_config_backend *file = internal->file;
- int ret = file->get(file, name, out);
+ if (!internal || !internal->file)
+ continue;
+ file = internal->file;
+
+ ret = file->get(file, name, out);
if (ret != GIT_ENOTFOUND)
return ret;
}
@@ -516,8 +526,9 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co
return config_error_notfound(name);
}
-int git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp,
- git_config_foreach_cb cb, void *payload)
+int git_config_get_multivar(
+ const git_config *cfg, const char *name, const char *regexp,
+ git_config_foreach_cb cb, void *payload)
{
file_internal *internal;
git_config_backend *file;
@@ -530,7 +541,10 @@ int git_config_get_multivar(const git_config *cfg, const char *name, const char
*/
for (i = cfg->files.length; i > 0; --i) {
internal = git_vector_get(&cfg->files, i - 1);
+ if (!internal || !internal->file)
+ continue;
file = internal->file;
+
ret = file->get_multivar(file, name, regexp, cb, payload);
if (ret < 0 && ret != GIT_ENOTFOUND)
return ret;
@@ -545,7 +559,7 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex
file_internal *internal;
internal = git_vector_get(&cfg->files, 0);
- if (!internal)
+ if (!internal || !internal->file)
return config_error_nofiles(name);
file = internal->file;
@@ -640,13 +654,12 @@ int git_config_open_default(git_config **out)
git_config *cfg = NULL;
git_buf buf = GIT_BUF_INIT;
- error = git_config_new(&cfg);
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
- if (!error && (!git_config_find_global_r(&buf) ||
- !git_config__global_location(&buf))) {
+ if (!git_config_find_global_r(&buf) || !git_config__global_location(&buf)) {
error = git_config_add_file_ondisk(cfg, buf.ptr,
GIT_CONFIG_LEVEL_GLOBAL, 0);
- } else {
}
if (!error && !git_config_find_xdg_r(&buf))
@@ -659,7 +672,7 @@ int git_config_open_default(git_config **out)
git_buf_free(&buf);
- if (error && cfg) {
+ if (error) {
git_config_free(cfg);
cfg = NULL;
}
@@ -672,6 +685,7 @@ int git_config_open_default(git_config **out)
/***********
* Parsers
***********/
+
int git_config_lookup_map_value(
int *out,
const git_cvar_map *maps,
diff --git a/src/diff.c b/src/diff.c
index d93506984..d2389f103 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -231,10 +231,23 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
return git_pool_strndup(pool, prefix, len + 1);
}
+GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
+{
+ const char *str = delta->old_file.path;
+
+ if (!str ||
+ delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_COPIED)
+ str = delta->new_file.path;
+
+ return str;
+}
+
int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
- int val = strcmp(da->old_file.path, db->old_file.path);
+ int val = strcmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
diff --git a/src/diff.h b/src/diff.h
index 16df431ed..ac8ab2aed 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -34,10 +34,18 @@ enum {
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
- GIT_DIFF_FLAG__TO_DELETE = (1 << 11), /* delete entry during rename det. */
- GIT_DIFF_FLAG__TO_SPLIT = (1 << 12), /* split entry during rename det. */
+
+ GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
+ GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
+ GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
+ GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
};
+#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
+
+#define GIT_DIFF__VERBOSE (1 << 30)
+
struct git_diff_list {
git_refcount rc;
git_repository *repo;
diff --git a/src/diff_output.c b/src/diff_output.c
index be1ff56e7..8dd110cbf 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -236,9 +236,8 @@ static int get_blob_content(
char oidstr[GIT_OID_HEXSZ+1];
git_buf content = GIT_BUF_INIT;
- git_oid_fmt(oidstr, &file->oid);
- oidstr[GIT_OID_HEXSZ] = 0;
- git_buf_printf(&content, "Subproject commit %s\n", oidstr );
+ git_oid_tostr(oidstr, sizeof(oidstr), &file->oid);
+ git_buf_printf(&content, "Subproject commit %s\n", oidstr);
map->data = git_buf_detach(&content);
map->len = strlen(map->data);
@@ -318,14 +317,13 @@ static int get_workdir_sm_content(
}
}
- git_oid_fmt(oidstr, &file->oid);
- oidstr[GIT_OID_HEXSZ] = '\0';
+ git_oid_tostr(oidstr, sizeof(oidstr), &file->oid);
if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
sm_status_text = "-dirty";
- git_buf_printf(&content, "Subproject commit %s%s\n",
- oidstr, sm_status_text);
+ git_buf_printf(
+ &content, "Subproject commit %s%s\n", oidstr, sm_status_text);
map->data = git_buf_detach(&content);
map->len = strlen(map->data);
@@ -1021,8 +1019,33 @@ typedef struct {
git_diff_data_cb print_cb;
void *payload;
git_buf *buf;
+ int oid_strlen;
} diff_print_info;
+static int diff_print_info_init(
+ diff_print_info *pi,
+ git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload)
+{
+ assert(diff && diff->repo);
+
+ pi->diff = diff;
+ pi->print_cb = cb;
+ pi->payload = payload;
+ pi->buf = out;
+
+ if (git_repository__cvar(&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
+ return -1;
+
+ pi->oid_strlen += 1; /* for NUL byte */
+
+ if (pi->oid_strlen < 2)
+ pi->oid_strlen = 2;
+ else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
+ pi->oid_strlen = GIT_OID_HEXSZ + 1;
+
+ return 0;
+}
+
static char pick_suffix(int mode)
{
if (S_ISDIR(mode))
@@ -1106,34 +1129,79 @@ int git_diff_print_compact(
git_buf buf = GIT_BUF_INIT;
diff_print_info pi;
- pi.diff = diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &buf;
-
- error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
git_buf_free(&buf);
return error;
}
-static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
+static int print_raw(
+ const git_diff_delta *delta, float progress, void *data)
{
- int abbrevlen;
+ diff_print_info *pi = data;
+ char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
- if (git_repository__cvar(&abbrevlen, pi->diff->repo, GIT_CVAR_ABBREV) < 0)
+ GIT_UNUSED(progress);
+
+ if (code == ' ')
+ return 0;
+
+ git_buf_clear(pi->buf);
+
+ git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
+ git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
+
+ git_buf_printf(
+ pi->buf, ":%06o %06o %s... %s... %c",
+ delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
+
+ if (delta->similarity > 0)
+ git_buf_printf(pi->buf, "%03u", delta->similarity);
+
+ if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED)
+ git_buf_printf(
+ pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
+ else
+ git_buf_printf(
+ pi->buf, "\t%s\n", delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path);
+
+ if (git_buf_oom(pi->buf))
return -1;
- abbrevlen += 1; /* for NUL byte */
- if (abbrevlen < 2)
- abbrevlen = 2;
- else if (abbrevlen > (int)sizeof(start_oid))
- abbrevlen = (int)sizeof(start_oid);
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ return callback_error();
+
+ return 0;
+}
- git_oid_tostr(start_oid, abbrevlen, &delta->old_file.oid);
- git_oid_tostr(end_oid, abbrevlen, &delta->new_file.oid);
+int git_diff_print_raw(
+ git_diff_list *diff,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
+{
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
+ git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
/* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
@@ -1289,13 +1357,9 @@ int git_diff_print_patch(
git_buf buf = GIT_BUF_INIT;
diff_print_info pi;
- pi.diff = diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &buf;
-
- error = git_diff_foreach(
- diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(
+ diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
git_buf_free(&buf);
@@ -1736,12 +1800,9 @@ int git_diff_patch_print(
assert(patch && print_cb);
- pi.diff = patch->diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &temp;
-
- error = print_patch_file(patch->delta, 0, &pi);
+ if (!(error = diff_print_info_init(
+ &pi, &temp, patch->diff, print_cb, payload)))
+ error = print_patch_file(patch->delta, 0, &pi);
for (h = 0; h < patch->hunks_size && !error; ++h) {
diff_patch_hunk *hunk = &patch->hunks[h];
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 84650a37b..bc3acae1d 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -18,12 +18,15 @@ static git_diff_delta *diff_delta__dup(
return NULL;
memcpy(delta, d, sizeof(git_diff_delta));
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
- delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
- if (delta->old_file.path == NULL)
- goto fail;
+ if (d->old_file.path != NULL) {
+ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
+ if (delta->old_file.path == NULL)
+ goto fail;
+ }
- if (d->new_file.path != d->old_file.path) {
+ if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) {
delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
if (delta->new_file.path == NULL)
goto fail;
@@ -220,7 +223,7 @@ int git_diff_find_similar__calc_similarity(
#define DEFAULT_THRESHOLD 50
#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
-#define DEFAULT_TARGET_LIMIT 200
+#define DEFAULT_RENAME_LIMIT 200
static int normalize_find_opts(
git_diff_list *diff,
@@ -253,12 +256,25 @@ static int normalize_find_opts(
/* some flags imply others */
+ if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) {
+ /* if we are only looking for exact matches, then don't turn
+ * MODIFIED items into ADD/DELETE pairs because it's too picky
+ */
+ opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES);
+
+ /* similarly, don't look for self-rewrites to split */
+ opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+ }
+
if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
opts->flags |= GIT_DIFF_FIND_RENAMES;
if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
opts->flags |= GIT_DIFF_FIND_COPIES;
+ if (opts->flags & GIT_DIFF_BREAK_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_REWRITES;
+
#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
if (USE_DEFAULT(opts->rename_threshold))
@@ -275,15 +291,15 @@ static int normalize_find_opts(
#undef USE_DEFAULT
- if (!opts->target_limit) {
+ if (!opts->rename_limit) {
int32_t limit = 0;
- opts->target_limit = DEFAULT_TARGET_LIMIT;
+ opts->rename_limit = DEFAULT_RENAME_LIMIT;
if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)
giterr_clear();
else if (limit > 0)
- opts->target_limit = limit;
+ opts->rename_limit = limit;
}
/* assign the internal metric with whitespace flag as payload */
@@ -307,11 +323,12 @@ static int normalize_find_opts(
return 0;
}
-static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
+static int apply_splits_and_deletes(
+ git_diff_list *diff, size_t expected_size, bool actually_split)
{
git_vector onto = GIT_VECTOR_INIT;
size_t i;
- git_diff_delta *delta;
+ git_diff_delta *delta, *deleted;
if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
return -1;
@@ -321,9 +338,11 @@ static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
continue;
- if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
- git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
- if (!deleted)
+ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) {
+ delta->similarity = 0;
+
+ /* make new record for DELETED side of split */
+ if (!(deleted = diff_delta__dup(delta, &diff->pool)))
goto on_error;
deleted->status = GIT_DELTA_DELETED;
@@ -334,32 +353,46 @@ static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
if (git_vector_insert(&onto, deleted) < 0)
goto on_error;
- delta->status = GIT_DELTA_ADDED;
+ if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR)
+ delta->status = GIT_DELTA_UNTRACKED;
+ else
+ delta->status = GIT_DELTA_ADDED;
memset(&delta->old_file, 0, sizeof(delta->old_file));
delta->old_file.path = delta->new_file.path;
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
}
+ /* clean up delta before inserting into new list */
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (delta->status != GIT_DELTA_COPIED &&
+ delta->status != GIT_DELTA_RENAMED &&
+ (delta->status != GIT_DELTA_MODIFIED || actually_split))
+ delta->similarity = 0;
+
+ /* insert into new list */
if (git_vector_insert(&onto, delta) < 0)
goto on_error;
}
/* cannot return an error past this point */
- git_vector_foreach(&diff->deltas, i, delta)
+
+ /* free deltas from old list that didn't make it to the new one */
+ git_vector_foreach(&diff->deltas, i, delta) {
if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
git__free(delta);
+ }
/* swap new delta list into place */
- git_vector_sort(&onto);
git_vector_swap(&diff->deltas, &onto);
git_vector_free(&onto);
+ git_vector_sort(&diff->deltas);
return 0;
on_error:
git_vector_foreach(&onto, i, delta)
git__free(delta);
-
git_vector_free(&onto);
return -1;
@@ -373,13 +406,13 @@ GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx)
static int similarity_calc(
git_diff_list *diff,
- git_diff_find_options *opts,
+ const git_diff_find_options *opts,
size_t file_idx,
void **cache)
{
int error = 0;
git_diff_file *file = similarity_get_file(diff, file_idx);
- git_iterator_type_t src = (file_idx & 1) ? diff->old_src : diff->new_src;
+ git_iterator_type_t src = (file_idx & 1) ? diff->new_src : diff->old_src;
if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */
git_buf path = GIT_BUF_INIT;
@@ -422,22 +455,56 @@ static int similarity_calc(
return error;
}
+#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
+
+/* - score < 0 means files cannot be compared
+ * - score >= 100 means files are exact match
+ * - score == 0 means files are completely different
+ */
static int similarity_measure(
+ int *score,
git_diff_list *diff,
- git_diff_find_options *opts,
+ const git_diff_find_options *opts,
void **cache,
size_t a_idx,
size_t b_idx)
{
- int score = 0;
git_diff_file *a_file = similarity_get_file(diff, a_idx);
git_diff_file *b_file = similarity_get_file(diff, b_idx);
+ bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
+
+ *score = -1;
+ /* don't try to compare files of different types */
if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
return 0;
- if (git_oid__cmp(&a_file->oid, &b_file->oid) == 0)
- return 100;
+ /* if exact match is requested, force calculation of missing OIDs */
+ if (exact_match) {
+ if (git_oid_iszero(&a_file->oid) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(diff->repo, a_file->path,
+ a_file->mode, a_file->size, &a_file->oid))
+ a_file->flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (git_oid_iszero(&b_file->oid) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(diff->repo, b_file->path,
+ b_file->mode, b_file->size, &b_file->oid))
+ b_file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ /* check OID match as a quick test */
+ if (git_oid__cmp(&a_file->oid, &b_file->oid) == 0) {
+ *score = 100;
+ return 0;
+ }
+
+ /* don't calculate signatures if we are doing exact match */
+ if (exact_match) {
+ *score = 0;
+ return 0;
+ }
/* update signature cache if needed */
if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0)
@@ -450,231 +517,369 @@ static int similarity_measure(
return 0;
/* compare signatures */
- if (opts->metric->similarity(
- &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)
- return -1;
+ return opts->metric->similarity(
+ score, cache[a_idx], cache[b_idx], opts->metric->payload);
+}
+
+static int calc_self_similarity(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ int error, similarity = -1;
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0)
+ return 0;
+
+ error = similarity_measure(
+ &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1);
+ if (error < 0)
+ return error;
+
+ if (similarity >= 0) {
+ delta->similarity = (uint32_t)similarity;
+ delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY;
+ }
+
+ return 0;
+}
+
+static bool is_rename_target(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't plain blobs */
+ if (!GIT_MODE_ISBLOB(delta->new_file.mode))
+ return false;
+
+ /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as
+ * targets; maybe include UNTRACKED and IGNORED if requested.
+ */
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ return false;
+
+ case GIT_DELTA_MODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_IGNORED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
+ return false;
+ break;
+
+ default: /* all other status values should be checked */
+ break;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET;
+ return true;
+}
+
+static bool is_rename_source(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't blobs */
+ if (!GIT_MODE_ISBLOB(delta->old_file.mode))
+ return false;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_IGNORED:
+ return false;
+
+ case GIT_DELTA_DELETED:
+ case GIT_DELTA_TYPECHANGE:
+ break;
+
+ case GIT_DELTA_UNMODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ return false;
+ break;
+
+ default: /* MODIFIED, RENAMED, COPIED */
+ /* if we're finding copies, this could be a source */
+ if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ break;
+
+ /* otherwise, this is only a source if we can split it */
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
- /* clip score */
- if (score < 0)
- score = 0;
- else if (score > 100)
- score = 100;
+ return false;
+ }
- return score;
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE;
+ return true;
}
-#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0)
+GIT_INLINE(bool) delta_is_split(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_TYPECHANGE ||
+ (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0);
+}
+
+GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_UNTRACKED ||
+ delta->status == GIT_DELTA_IGNORED);
+}
+
+typedef struct {
+ uint32_t idx;
+ uint32_t similarity;
+} diff_find_match;
int git_diff_find_similar(
git_diff_list *diff,
git_diff_find_options *given_opts)
{
- size_t i, j, cache_size, *matches;
+ size_t i, j, cache_size;
int error = 0, similarity;
git_diff_delta *from, *to;
git_diff_find_options opts;
- size_t tried_targets, num_rewrites = 0;
- void **cache;
+ size_t num_rewrites = 0, num_updates = 0;
+ void **cache; /* cache of similarity metric file signatures */
+ diff_find_match *matches; /* cache of best matches */
if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
return error;
- /* TODO: maybe abort if deltas.length > target_limit ??? */
+ /* TODO: maybe abort if deltas.length > rename_limit ??? */
+ if (!git__is_uint32(diff->deltas.length))
+ return 0;
cache_size = diff->deltas.length * 2; /* must store b/c length may change */
cache = git__calloc(cache_size, sizeof(void *));
GITERR_CHECK_ALLOC(cache);
- matches = git__calloc(diff->deltas.length, sizeof(size_t));
+ matches = git__calloc(diff->deltas.length, sizeof(diff_find_match));
GITERR_CHECK_ALLOC(matches);
- /* first break MODIFIED records that are too different (if requested) */
-
- if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) {
- git_vector_foreach(&diff->deltas, i, from) {
- if (from->status != GIT_DELTA_MODIFIED)
- continue;
-
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * i + 1);
-
- if (similarity < 0) {
- error = similarity;
- goto cleanup;
- }
-
- if ((unsigned int)similarity < opts.break_rewrite_threshold) {
- from->flags |= GIT_DIFF_FLAG__TO_SPLIT;
- num_rewrites++;
- }
- }
- }
-
/* next find the most similar delta for each rename / copy candidate */
- git_vector_foreach(&diff->deltas, i, from) {
- tried_targets = 0;
+ git_vector_foreach(&diff->deltas, i, to) {
+ size_t tried_sources = 0;
- /* skip things that aren't blobs */
- if (GIT_MODE_TYPE(from->old_file.mode) !=
- GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
- continue;
-
- /* don't check UNMODIFIED files as source unless given option */
- if (from->status == GIT_DELTA_UNMODIFIED &&
- !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
- continue;
+ matches[i].idx = i;
+ matches[i].similarity = 0;
- /* skip all but DELETED files unless copy detection is on */
- if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES) &&
- from->status != GIT_DELTA_DELETED &&
- (from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
+ /* skip things that are not rename targets */
+ if (!is_rename_target(diff, &opts, i, cache))
continue;
- git_vector_foreach(&diff->deltas, j, to) {
+ git_vector_foreach(&diff->deltas, j, from) {
if (i == j)
continue;
- /* skip things that aren't blobs */
- if (GIT_MODE_TYPE(to->new_file.mode) !=
- GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+ /* skip things that are not rename sources */
+ if (!is_rename_source(diff, &opts, j, cache))
continue;
- switch (to->status) {
- case GIT_DELTA_ADDED:
- case GIT_DELTA_UNTRACKED:
- case GIT_DELTA_RENAMED:
- case GIT_DELTA_COPIED:
+ /* cap on maximum targets we'll examine (per "to" file) */
+ if (++tried_sources > opts.rename_limit)
break;
- case GIT_DELTA_MODIFIED:
- if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
- continue;
- break;
- default:
- /* only the above status values should be checked */
- continue;
- }
- /* cap on maximum files we'll examine (per "from" file) */
- if (++tried_targets > opts.target_limit)
- break;
-
- /* calculate similarity and see if this pair beats the
- * similarity score of the current best pair.
- */
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * j + 1);
-
- if (similarity < 0) {
- error = similarity;
+ /* calculate similarity for this pair and find best match */
+ if ((error = similarity_measure(
+ &similarity, diff, &opts, cache, 2 * j, 2 * i + 1)) < 0)
goto cleanup;
+
+ if (similarity < 0) { /* not actually comparable */
+ --tried_sources;
+ continue;
}
- if (to->similarity < (unsigned int)similarity) {
- to->similarity = (unsigned int)similarity;
- matches[j] = i + 1;
+ if (matches[i].similarity < (uint32_t)similarity) {
+ matches[i].similarity = (uint32_t)similarity;
+ matches[i].idx = j;
}
}
}
/* next rewrite the diffs with renames / copies */
- git_vector_foreach(&diff->deltas, j, to) {
- if (!matches[j]) {
- assert(to->similarity == 0);
+ git_vector_foreach(&diff->deltas, i, to) {
+
+ /* check if this delta was matched to another one */
+ if ((similarity = (int)matches[i].similarity) <= 0)
continue;
- }
+ assert(to && (to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) != 0);
+
+ from = GIT_VECTOR_GET(&diff->deltas, matches[i].idx);
+ assert(from && (from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) != 0);
- i = matches[j] - 1;
- from = GIT_VECTOR_GET(&diff->deltas, i);
- assert(from);
-
- /* four possible outcomes here:
- * 1. old DELETED and if over rename threshold,
- * new becomes RENAMED and old goes away
- * 2. old SPLIT and if over rename threshold,
- * new becomes RENAMED and old becomes ADDED (clear SPLIT)
- * 3. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and
- * old is more similar to new than it is to itself, in which
- * case, new becomes RENAMED and old becomed ADDED
- * 4. otherwise if over copy threshold, new becomes COPIED
+ /* possible scenarios:
+ * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
+ * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE
+ * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME
+ * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT
+ * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
*/
if (from->status == GIT_DELTA_DELETED) {
- if (to->similarity < opts.rename_threshold) {
- to->similarity = 0;
- continue;
- }
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ if (delta_is_new_only(to)) {
- from->flags |= GIT_DIFF_FLAG__TO_DELETE;
- num_rewrites++;
+ if (similarity < (int)opts.rename_threshold)
+ continue;
- continue;
- }
+ from->status = GIT_DELTA_RENAMED;
+ from->similarity = (uint32_t)similarity;
+ memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
- if (from->status == GIT_DELTA_MODIFIED &&
- (from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
- {
- if (to->similarity < opts.rename_threshold) {
- to->similarity = 0;
- continue;
- }
+ to->flags |= GIT_DIFF_FLAG__TO_DELETE;
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ num_rewrites++;
+ } else {
+ assert(delta_is_split(to));
- from->status = GIT_DELTA_ADDED;
- from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
- memset(&from->old_file, 0, sizeof(from->old_file));
- num_rewrites--;
+ if (similarity < (int)opts.rename_from_rewrite_threshold)
+ continue;
- continue;
- }
+ from->status = GIT_DELTA_RENAMED;
+ from->similarity = (uint32_t)similarity;
+ memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
- if (from->status == GIT_DELTA_MODIFIED &&
- FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
- to->similarity > opts.rename_threshold)
- {
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * i + 1);
+ to->status = GIT_DELTA_DELETED;
+ memset(&to->new_file, 0, sizeof(to->new_file));
+ to->new_file.path = to->old_file.path;
+ to->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+ if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
+ to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
- if (similarity < 0) {
- error = similarity;
- goto cleanup;
+ num_updates++;
}
+ }
- if ((unsigned int)similarity < opts.rename_from_rewrite_threshold) {
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ else if (delta_is_split(from)) {
+ git_diff_file swap;
- from->status = GIT_DELTA_ADDED;
- memset(&from->old_file, 0, sizeof(from->old_file));
- from->old_file.path = to->old_file.path;
- from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+ if (delta_is_new_only(to)) {
- continue;
+ if (similarity < (int)opts.rename_threshold)
+ continue;
+
+ memcpy(&swap, &from->new_file, sizeof(swap));
+
+ from->status = GIT_DELTA_RENAMED;
+ from->similarity = (uint32_t)similarity;
+ memcpy(&from->new_file, &to->new_file, sizeof(from->new_file));
+ if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
+ from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+
+ to->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
+ GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
+ memcpy(&to->new_file, &swap, sizeof(to->new_file));
+ to->old_file.path = to->new_file.path;
+
+ num_updates++;
+ } else {
+ assert(delta_is_split(from));
+
+ if (similarity < (int)opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &to->new_file, sizeof(swap));
+
+ to->status = GIT_DELTA_RENAMED;
+ to->similarity = (uint32_t)similarity;
+ memcpy(&to->new_file, &from->new_file, sizeof(to->new_file));
+ if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
+ to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+
+ memcpy(&from->new_file, &swap, sizeof(from->new_file));
+ if ((from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0) {
+ from->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites++;
+ }
+
+ /* in the off chance that we've just swapped the new
+ * element into the correct place, clear the SPLIT flag
+ */
+ if (matches[matches[i].idx].idx == i &&
+ matches[matches[i].idx].similarity >
+ opts.rename_from_rewrite_threshold) {
+
+ from->status = GIT_DELTA_RENAMED;
+ from->similarity =
+ (uint32_t)matches[matches[i].idx].similarity;
+ matches[matches[i].idx].similarity = 0;
+ from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+
+ num_updates++;
}
}
- if (to->similarity < opts.copy_threshold) {
- to->similarity = 0;
- continue;
- }
+ else if (delta_is_new_only(to)) {
+ if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES) ||
+ similarity < (int)opts.copy_threshold)
+ continue;
- /* convert "to" to a COPIED record */
- to->status = GIT_DELTA_COPIED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
- }
+ to->status = GIT_DELTA_COPIED;
+ to->similarity = (uint32_t)similarity;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
- if (num_rewrites > 0) {
- assert(num_rewrites < diff->deltas.length);
+ num_updates++;
+ }
+ }
+ if (num_rewrites > 0 || num_updates > 0)
error = apply_splits_and_deletes(
- diff, diff->deltas.length - num_rewrites);
- }
+ diff, diff->deltas.length - num_rewrites,
+ FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES));
cleanup:
git__free(matches);
diff --git a/src/fileops.c b/src/fileops.c
index 98ab8efe3..a3e43214f 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -202,6 +202,32 @@ int git_futils_readbuffer(git_buf *buf, const char *path)
return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
}
+int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int flags, mode_t mode)
+{
+ int fd, error = 0;
+
+ if (flags <= 0)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+ if (!mode)
+ mode = GIT_FILEMODE_BLOB;
+
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ }
+
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ return error;
+}
+
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{
if (git_futils_mkpath2file(to, dirmode) < 0)
diff --git a/src/fileops.h b/src/fileops.h
index 627a6923d..f4e059c83 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -22,6 +22,9 @@ extern int git_futils_readbuffer_updated(
git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
+extern int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int open_flags, mode_t mode);
+
/**
* File utils
*
@@ -223,6 +226,7 @@ extern git_off_t git_futils_filesize(git_file fd);
#define GIT_MODE_PERMS_MASK 0777
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
+#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
/**
* Convert a mode_t from the OS to a legal git mode_t value.
diff --git a/src/merge.c b/src/merge.c
index de5d65ac0..11345587c 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -54,39 +54,6 @@ struct merge_diff_df_data {
};
-int git_repository_merge_cleanup(git_repository *repo)
-{
- int error = 0;
- git_buf merge_head_path = GIT_BUF_INIT,
- merge_mode_path = GIT_BUF_INIT,
- merge_msg_path = GIT_BUF_INIT;
-
- assert(repo);
-
- if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
- git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
- git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
- return -1;
-
- if (git_path_isfile(merge_head_path.ptr)) {
- if ((error = p_unlink(merge_head_path.ptr)) < 0)
- goto cleanup;
- }
-
- if (git_path_isfile(merge_mode_path.ptr))
- (void)p_unlink(merge_mode_path.ptr);
-
- if (git_path_isfile(merge_msg_path.ptr))
- (void)p_unlink(merge_msg_path.ptr);
-
-cleanup:
- git_buf_free(&merge_msg_path);
- git_buf_free(&merge_mode_path);
- git_buf_free(&merge_head_path);
-
- return error;
-}
-
/* Merge base computation */
int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)
@@ -1380,6 +1347,18 @@ git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
return diff_list;
}
+void git_merge_diff_list__free(git_merge_diff_list *diff_list)
+{
+ if (!diff_list)
+ return;
+
+ git_vector_free(&diff_list->staged);
+ git_vector_free(&diff_list->conflicts);
+ git_vector_free(&diff_list->resolved);
+ git_pool_clear(&diff_list->pool);
+ git__free(diff_list);
+}
+
static int merge_tree_normalize_opts(
git_repository *repo,
git_merge_tree_opts *opts,
@@ -1617,14 +1596,550 @@ done:
return error;
}
-void git_merge_diff_list__free(git_merge_diff_list *diff_list)
+/* Merge setup / cleanup */
+
+static int write_orig_head(
+ git_repository *repo,
+ const git_merge_head *our_head)
{
- if (!diff_list)
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char orig_oid_str[GIT_OID_HEXSZ + 1];
+ int error = 0;
+
+ assert(repo && our_head);
+
+ git_oid_tostr(orig_oid_str, GIT_OID_HEXSZ+1, &our_head->oid);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) == 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", orig_oid_str)) == 0)
+ error = git_filebuf_commit(&file, 0666);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_head(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char merge_oid_str[GIT_OID_HEXSZ + 1];
+ size_t i;
+ int error = 0;
+
+ assert(repo && heads);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ git_oid_tostr(merge_oid_str, GIT_OID_HEXSZ+1, &heads[i]->oid);
+
+ if ((error = git_filebuf_printf(&file, "%s\n", merge_oid_str)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&file, 0666);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_mode(git_repository *repo, unsigned int flags)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ /* For future expansion */
+ GIT_UNUSED(flags);
+
+ assert(repo);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file, 0666);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+struct merge_msg_entry {
+ const git_merge_head *merge_head;
+ bool written;
+};
+
+static int msg_entry_is_branch(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0);
+}
+
+static int msg_entry_is_tracking(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0);
+}
+
+static int msg_entry_is_tag(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0);
+}
+
+static int msg_entry_is_remote(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ if (entry->written == 0 &&
+ entry->merge_head->remote_url != NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0)
+ {
+ struct merge_msg_entry *existing;
+
+ /* Match only branches from the same remote */
+ if (entries->length == 0)
+ return 1;
+
+ existing = git_vector_get(entries, 0);
+
+ return (git__strcmp(existing->merge_head->remote_url,
+ entry->merge_head->remote_url) == 0);
+ }
+
+ return 0;
+}
+
+static int msg_entry_is_oid(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 0 &&
+ merge_msg_entry->merge_head->ref_name == NULL &&
+ merge_msg_entry->merge_head->remote_url == NULL);
+}
+
+static int merge_msg_entry_written(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 1);
+}
+
+static int merge_msg_entries(
+ git_vector *v,
+ const struct merge_msg_entry *entries,
+ size_t len,
+ int (*match)(const struct merge_msg_entry *entry, git_vector *entries))
+{
+ size_t i;
+ int matches, total = 0;
+
+ git_vector_clear(v);
+
+ for (i = 0; i < len; i++) {
+ if ((matches = match(&entries[i], v)) < 0)
+ return matches;
+ else if (!matches)
+ continue;
+
+ git_vector_insert(v, (struct merge_msg_entry *)&entries[i]);
+ total++;
+ }
+
+ return total;
+}
+
+static int merge_msg_write_entries(
+ git_filebuf *file,
+ git_vector *entries,
+ const char *item_name,
+ const char *item_plural_name,
+ size_t ref_name_skip,
+ const char *source,
+ char sep)
+{
+ struct merge_msg_entry *entry;
+ size_t i;
+ int error = 0;
+
+ if (entries->length == 0)
+ return 0;
+
+ if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "%s ",
+ (entries->length == 1) ? item_name : item_plural_name)) < 0)
+ goto done;
+
+ git_vector_foreach(entries, i, entry) {
+ if (i > 0 &&
+ (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0)
+ goto done;
+
+ entry->written = 1;
+ }
+
+ if (source)
+ error = git_filebuf_printf(file, " of %s", source);
+
+done:
+ return error;
+}
+
+static int merge_msg_write_branches(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_tracking(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "remote-tracking branch", "remote-tracking branches", 0, NULL, sep);
+}
+
+static int merge_msg_write_tags(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_remotes(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ const char *source;
+
+ if (entries->length == 0)
+ return 0;
+
+ source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url;
+
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep);
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char oid_str[GIT_OID_HEXSZ + 1];
+ struct merge_msg_entry *entries;
+ git_vector matching = GIT_VECTOR_INIT;
+ size_t i;
+ char sep = 0;
+ int error = 0;
+
+ assert(repo && heads);
+
+ entries = git__calloc(heads_len, sizeof(struct merge_msg_entry));
+ GITERR_CHECK_ALLOC(entries);
+
+ if (git_vector_init(&matching, heads_len, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < heads_len; i++)
+ entries[i].merge_head = heads[i];
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0 ||
+ (error = git_filebuf_write(&file, "Merge ", 6)) < 0)
+ goto cleanup;
+
+ /*
+ * This is to emulate the format of MERGE_MSG by core git.
+ *
+ * Core git will write all the commits specified by OID, in the order
+ * provided, until the first named branch or tag is reached, at which
+ * point all branches will be written in the order provided, then all
+ * tags, then all remote tracking branches and finally all commits that
+ * were specified by OID that were not already written.
+ *
+ * Yes. Really.
+ */
+ for (i = 0; i < heads_len; i++) {
+ if (!msg_entry_is_oid(&entries[i]))
+ break;
+
+ git_oid_fmt(oid_str, &entries[i].merge_head->oid);
+ oid_str[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git_filebuf_printf(&file, "%scommit '%s'", (i > 0) ? "; " : "", oid_str)) < 0)
+ goto cleanup;
+
+ entries[i].written = 1;
+ }
+
+ if (i)
+ sep = ';';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 ||
+ (error = merge_msg_write_branches(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 ||
+ (error = merge_msg_write_tracking(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 ||
+ (error = merge_msg_write_tags(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ /* We should never be called with multiple remote branches, but handle
+ * it in case we are... */
+ while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) {
+ if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if (merge_msg_entry_written(&entries[i]))
+ continue;
+
+ git_oid_fmt(oid_str, &entries[i].merge_head->oid);
+ oid_str[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git_filebuf_printf(&file, "; commit '%s'", oid_str)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_filebuf_printf(&file, "\n")) < 0 ||
+ (error = git_filebuf_commit(&file, 0666)) < 0)
+ goto cleanup;
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ git_vector_free(&matching);
+ git__free(entries);
+
+ return error;
+}
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *heads[],
+ size_t heads_len,
+ unsigned int flags)
+{
+ int error = 0;
+
+ assert (repo && our_head && heads);
+
+ if ((error = write_orig_head(repo, our_head)) == 0 &&
+ (error = write_merge_head(repo, heads, heads_len)) == 0 &&
+ (error = write_merge_mode(repo, flags)) == 0) {
+ error = write_merge_msg(repo, heads, heads_len);
+ }
+
+ return error;
+}
+
+int git_repository_merge_cleanup(git_repository *repo)
+{
+ int error = 0;
+ git_buf merge_head_path = GIT_BUF_INIT,
+ merge_mode_path = GIT_BUF_INIT,
+ merge_msg_path = GIT_BUF_INIT;
+
+ assert(repo);
+
+ if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
+ git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if (git_path_isfile(merge_head_path.ptr)) {
+ if ((error = p_unlink(merge_head_path.ptr)) < 0)
+ goto cleanup;
+ }
+
+ if (git_path_isfile(merge_mode_path.ptr))
+ (void)p_unlink(merge_mode_path.ptr);
+
+ if (git_path_isfile(merge_msg_path.ptr))
+ (void)p_unlink(merge_msg_path.ptr);
+
+cleanup:
+ git_buf_free(&merge_msg_path);
+ git_buf_free(&merge_mode_path);
+ git_buf_free(&merge_head_path);
+
+ return error;
+}
+
+/* Merge heads are the input to merge */
+
+static int merge_head_init(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *ref_name,
+ const char *remote_url,
+ const git_oid *oid)
+{
+ git_merge_head *head;
+ int error = 0;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ head = git__calloc(1, sizeof(git_merge_head));
+ GITERR_CHECK_ALLOC(head);
+
+ if (ref_name) {
+ head->ref_name = git__strdup(ref_name);
+ GITERR_CHECK_ALLOC(head->ref_name);
+ }
+
+ if (remote_url) {
+ head->remote_url = git__strdup(remote_url);
+ GITERR_CHECK_ALLOC(head->remote_url);
+ }
+
+ git_oid_cpy(&head->oid, oid);
+
+ if ((error = git_commit_lookup(&head->commit, repo, &head->oid)) < 0) {
+ git_merge_head_free(head);
+ return error;
+ }
+
+ *out = head;
+ return error;
+}
+
+int git_merge_head_from_ref(
+ git_merge_head **out,
+ git_repository *repo,
+ git_reference *ref)
+{
+ git_reference *resolved;
+ int error = 0;
+
+ assert(out && repo && ref);
+
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return error;
+
+ error = merge_head_init(out, repo, git_reference_name(ref), NULL,
+ git_reference_target(resolved));
+
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_merge_head_from_oid(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_oid *oid)
+{
+ assert(out && repo && oid);
+
+ return merge_head_init(out, repo, NULL, NULL, oid);
+}
+
+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)
+{
+ assert(repo && branch_name && remote_url && oid);
+
+ return merge_head_init(out, repo, branch_name, remote_url, oid);
+}
+
+void git_merge_head_free(git_merge_head *head)
+{
+ if (head == NULL)
return;
- git_vector_free(&diff_list->staged);
- git_vector_free(&diff_list->conflicts);
- git_vector_free(&diff_list->resolved);
- git_pool_clear(&diff_list->pool);
- git__free(diff_list);
+ if (head->commit != NULL)
+ git_object_free((git_object *)head->commit);
+
+ if (head->ref_name != NULL)
+ git__free(head->ref_name);
+
+ if (head->remote_url != NULL)
+ git__free(head->remote_url);
+
+ git__free(head);
}
diff --git a/src/merge.h b/src/merge.h
index 50538b12b..ba6725de9 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -107,12 +107,25 @@ typedef struct {
git_delta_t their_status;
} git_merge_diff;
+/** Internal structure for merge inputs */
+struct git_merge_head {
+ char *ref_name;
+ char *remote_url;
+
+ git_oid oid;
+ git_commit *commit;
+};
+
int git_merge__bases_many(
git_commit_list **out,
git_revwalk *walk,
git_commit_list_node *one,
git_vector *twos);
+/*
+ * Three-way tree differencing
+ */
+
git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);
int git_merge_diff_list__find_differences(git_merge_diff_list *merge_diff_list,
@@ -124,4 +137,13 @@ int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list
void git_merge_diff_list__free(git_merge_diff_list *diff_list);
+/* Merge metadata setup */
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *their_heads[],
+ size_t their_heads_len,
+ unsigned int flags);
+
#endif
diff --git a/src/oid.c b/src/oid.c
index e74640c57..8300e46c1 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -68,12 +68,31 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
return str;
}
-void git_oid_fmt(char *str, const git_oid *oid)
+void git_oid_nfmt(char *str, size_t n, const git_oid *oid)
{
- size_t i;
+ size_t i, max_i;
+
+ if (!oid) {
+ memset(str, 0, n);
+ return;
+ }
+ if (n > GIT_OID_HEXSZ) {
+ memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
+ n = GIT_OID_HEXSZ;
+ }
+
+ max_i = n / 2;
- for (i = 0; i < sizeof(oid->id); i++)
+ for (i = 0; i < max_i; i++)
str = fmt_one(str, oid->id[i]);
+
+ if (n & 1)
+ *str++ = to_hex[oid->id[i] >> 4];
+}
+
+void git_oid_fmt(char *str, const git_oid *oid)
+{
+ git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
}
void git_oid_pathfmt(char *str, const git_oid *oid)
@@ -91,31 +110,20 @@ char *git_oid_allocfmt(const git_oid *oid)
char *str = git__malloc(GIT_OID_HEXSZ + 1);
if (!str)
return NULL;
- git_oid_fmt(str, oid);
- str[GIT_OID_HEXSZ] = '\0';
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
return str;
}
char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
{
- char str[GIT_OID_HEXSZ];
-
if (!out || n == 0)
return "";
- n--; /* allow room for terminating NUL */
-
- if (oid == NULL)
- n = 0;
-
- if (n > 0) {
- git_oid_fmt(str, oid);
- if (n > GIT_OID_HEXSZ)
- n = GIT_OID_HEXSZ;
- memcpy(out, str, n);
- }
+ if (n > GIT_OID_HEXSZ + 1)
+ n = GIT_OID_HEXSZ + 1;
- out[n] = '\0';
+ git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
+ out[n - 1] = '\0';
return out;
}
@@ -269,8 +277,10 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co
idx_leaf = (node_index)os->node_count++;
- if (os->node_count == SHRT_MAX)
+ if (os->node_count == SHRT_MAX) {
os->full = 1;
+ return NULL;
+ }
node = &os->nodes[idx];
node->children[push_at] = -idx_leaf;
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index f964c4182..7de56a1a0 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -976,7 +976,7 @@ static int refdb_fs_backend__delete(
struct packref *pack_ref;
khiter_t pack_ref_pos;
int error = 0, pack_error;
- bool loose_deleted;
+ bool loose_deleted = 0;
assert(_backend);
assert(ref);
diff --git a/src/repository.c b/src/repository.c
index 9957f32b7..28505e822 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1822,3 +1822,20 @@ int git_repository_state(git_repository *repo)
git_buf_free(&repo_path);
return state;
}
+
+int git_repository_is_shallow(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ git_buf_joinpath(&path, repo->path_repository, "shallow");
+ error = git_path_lstat(path.ptr, &st);
+ git_buf_free(&path);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+ if (error < 0)
+ return -1;
+ return st.st_size == 0 ? 0 : 1;
+}
diff --git a/src/tag.c b/src/tag.c
index f81956de7..ecf3876cb 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -291,6 +291,19 @@ int git_tag_create(
return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1);
}
+int git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message)
+{
+ assert(oid && repo && tag_name && target && tagger && message);
+
+ return write_tag_annotation(oid, repo, tag_name, target, tagger, message);
+}
+
int git_tag_create_lightweight(
git_oid *oid,
git_repository *repo,
diff --git a/src/util.h b/src/util.h
index 6f876d012..5ae87ac10 100644
--- a/src/util.h
+++ b/src/util.h
@@ -109,6 +109,13 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
return p == (git_off_t)r;
}
+/** @return true if p fits into the range of a uint32_t */
+GIT_INLINE(int) git__is_uint32(size_t p)
+{
+ uint32_t r = (uint32_t)p;
+ return p == (size_t)r;
+}
+
/* 32-bit cross-platform rotl */
#ifdef _MSC_VER /* use built-in method in MSVC */
# define git__rotl(v, s) (uint32_t)_rotl(v, s)
diff --git a/src/win32/git2.rc b/src/win32/git2.rc.cmake
index 436913228..dc9b3e6eb 100644
--- a/src/win32/git2.rc
+++ b/src/win32/git2.rc.cmake
@@ -1,11 +1,7 @@
#include <winver.h>
#include "../../include/git2/version.h"
-#ifndef INCLUDE_LIB
-#define LIBGIT2_FILENAME "git2.dll"
-#else
-#define LIBGIT2_FILENAME "libgit2.dll"
-#endif
+#define LIBGIT2_FILENAME "@LIBGIT2_NAME_PREFIX@git2@LIBGIT2_NAME_SUFFIX@.dll"
VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE
FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index 75eda0520..4e23792a6 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -213,3 +213,8 @@ void diff_print(FILE *fp, git_diff_list *diff)
{
cl_git_pass(git_diff_print_patch(diff, diff_print_cb, fp ? fp : stderr));
}
+
+void diff_print_raw(FILE *fp, git_diff_list *diff)
+{
+ cl_git_pass(git_diff_print_raw(diff, diff_print_cb, fp ? fp : stderr));
+}
diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h
index b39a69d1d..bb76d0076 100644
--- a/tests-clar/diff/diff_helpers.h
+++ b/tests-clar/diff/diff_helpers.h
@@ -65,4 +65,4 @@ extern int diff_foreach_via_iterator(
void *data);
extern void diff_print(FILE *fp, git_diff_list *diff);
-
+extern void diff_print_raw(FILE *fp, git_diff_list *diff);
diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c
index 1349d4013..8bff96cf2 100644
--- a/tests-clar/diff/rename.c
+++ b/tests-clar/diff/rename.c
@@ -1,5 +1,6 @@
#include "clar_libgit2.h"
#include "diff_helpers.h"
+#include "buf_text.h"
static git_repository *g_repo = NULL;
@@ -71,8 +72,10 @@ void test_diff_rename__match_oid(void)
/* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
* 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ * don't use NULL opts to avoid config `diff.renames` contamination
*/
- cl_git_pass(git_diff_find_similar(diff, NULL));
+ opts.flags = GIT_DIFF_FIND_RENAMES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
@@ -242,8 +245,8 @@ void test_diff_rename__not_exact_match(void)
cl_assert_equal_i(5, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
- cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
git_diff_list_free(diff);
@@ -377,7 +380,8 @@ void test_diff_rename__handles_small_files(void)
*/
cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
- opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | GIT_DIFF_FIND_AND_BREAK_REWRITES;
+ opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
+ GIT_DIFF_FIND_AND_BREAK_REWRITES;
cl_git_pass(git_diff_find_similar(diff, &opts));
git_diff_list_free(diff);
@@ -387,9 +391,160 @@ void test_diff_rename__handles_small_files(void)
void test_diff_rename__working_directory_changes(void)
{
- /* let's rewrite some files in the working directory on demand */
+ const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba";
+ git_oid id;
+ git_tree *tree;
+ git_blob *blob;
+ git_diff_list *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+ git_buf old_content = GIT_BUF_INIT, content = GIT_BUF_INIT;;
+
+ tree = resolve_commit_oid_to_tree(g_repo, sha0);
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /*
+ $ git cat-file -p 2bc7f351d20b53f1c72c16c4b036e491c478c49a^{tree}
+
+ 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba sevencities.txt
+ 100644 blob ad0a8e55a104ac54a8a29ed4b84b49e76837a113 sixserving.txt
+ 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba songofseven.txt
+
+ $ for f in *.txt; do
+ echo `git hash-object -t blob $f` $f
+ done
+
+ eaf4a3e3bfe68585e90cada20736ace491cd100b ikeepsix.txt
+ f90d4fc20ecddf21eebe6a37e9225d244339d2b5 sixserving.txt
+ 4210ffd5c390b21dd5483375e75288dea9ede512 songof7cities.txt
+ 9a69d960ae94b060f56c2a8702545e2bb1abb935 untimely.txt
+ */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ /* git diff --no-renames 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_list_free(diff);
+
+ /* rewrite files in the working directory with / without CRLF changes */
+
+ cl_git_pass(
+ git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));
+ cl_git_pass(
+ git_buf_text_lf_to_crlf(&content, &old_content));
+ cl_git_pass(
+ git_futils_writebuffer(&content, "renames/songof7cities.txt", 0, 0));
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_list_free(diff);
+
+ /* try a different whitespace option */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_list_free(diff);
+
+ /* try a different matching option */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+
+ git_diff_list_free(diff);
+
+ /* again with exact match blob */
+
+ cl_git_pass(git_oid_fromstr(&id, blobsha));
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
+ cl_git_pass(git_buf_set(
+ &content, git_blob_rawcontent(blob), git_blob_rawsize(blob)));
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+ git_blob_free(blob);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ /*
+ fprintf(stderr, "\n\n");
+ diff_print_raw(stderr, diff);
+ */
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_list_free(diff);
- /* and with / without CRLF changes */
+ git_tree_free(tree);
+ git_buf_free(&content);
+ git_buf_free(&old_content);
}
void test_diff_rename__patch(void)
@@ -446,3 +601,213 @@ void test_diff_rename__patch(void)
git_tree_free(old_tree);
git_tree_free(new_tree);
}
+
+void test_diff_rename__file_exchange(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff_list *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
+ cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_list_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__file_partial_exchange(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff_list *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+ int i;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
+ for (i = 0; i < 100; ++i)
+ cl_git_pass(git_buf_puts(&c2, "this is not the content you are looking for\n"));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+
+ git_diff_list_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__file_split(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff_list *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ /* put the first 2/3 of file into one new place
+ * and the second 2/3 of file into another new place
+ */
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_buf_set(&c2, c1.ptr, c1.size));
+ git_buf_truncate(&c1, c1.size * 2 / 3);
+ git_buf_consume(&c2, ((char *)c2.ptr) + (c2.size / 3));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/song_a.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/song_b.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "song_a.txt"));
+ cl_git_pass(git_index_add_bypath(index, "song_b.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_list_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__from_deleted_to_split(void)
+{
+ git_buf c1 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff_list *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ /* old file is missing, new file is actually old file renamed */
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_list_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+}
diff --git a/tests-clar/merge/workdir/setup.c b/tests-clar/merge/workdir/setup.c
index 6ae9dbb14..1c8403221 100644
--- a/tests-clar/merge/workdir/setup.c
+++ b/tests-clar/merge/workdir/setup.c
@@ -8,28 +8,28 @@
static git_repository *repo;
static git_index *repo_index;
-#define TEST_REPO_PATH "testrepo"
-#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
-#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452"
+#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452"
-#define THEIRS_SIMPLE_BRANCH "branch"
-#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520"
+#define THEIRS_SIMPLE_BRANCH "branch"
+#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520"
-#define OCTO1_BRANCH "octo1"
-#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061"
+#define OCTO1_BRANCH "octo1"
+#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061"
-#define OCTO2_BRANCH "octo2"
-#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c"
+#define OCTO2_BRANCH "octo2"
+#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c"
-#define OCTO3_BRANCH "octo3"
-#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272"
+#define OCTO3_BRANCH "octo3"
+#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272"
-#define OCTO4_BRANCH "octo4"
-#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae"
+#define OCTO4_BRANCH "octo4"
+#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae"
-#define OCTO5_BRANCH "octo5"
-#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f"
+#define OCTO5_BRANCH "octo5"
+#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f"
// Fixture setup and teardown
void test_merge_workdir_setup__initialize(void)
@@ -44,6 +44,22 @@ void test_merge_workdir_setup__cleanup(void)
cl_git_sandbox_cleanup();
}
+static bool test_file_contents(const char *filename, const char *expected)
+{
+ git_buf file_path_buf = GIT_BUF_INIT, file_buf = GIT_BUF_INIT;
+ bool equals;
+
+ git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo), filename);
+
+ cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr));
+ equals = (strcmp(file_buf.ptr, expected) == 0);
+
+ git_buf_free(&file_path_buf);
+ git_buf_free(&file_buf);
+
+ return equals;
+}
+
static void write_file_contents(const char *filename, const char *output)
{
git_buf file_path_buf = GIT_BUF_INIT;
@@ -55,6 +71,816 @@ static void write_file_contents(const char *filename, const char *output)
git_buf_free(&file_path_buf);
}
+/* git merge --no-ff octo1 */
+void test_merge_workdir_setup__one_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */
+void test_merge_workdir_setup__one_oid(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge octo1 octo2 */
+void test_merge_workdir_setup__two_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 octo2 octo3 */
+void test_merge_workdir_setup__three_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 158dc7bedb202f5b26502bf3574faa7f4238d56c 50ce7d7d01217679e26c55939eef119e0c93e272 */
+void test_merge_workdir_setup__three_oids(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO2_OID "'; commit '" OCTO3_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c */
+void test_merge_workdir_setup__branches_and_oids_1(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_oid octo2_oid;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'; commit '" OCTO2_OID "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c octo3 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae */
+void test_merge_workdir_setup__branches_and_oids_2(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_oid octo2_oid;
+ git_reference *octo3_ref;
+ git_oid octo4_oid;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[3], repo, &octo4_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "'; commit '" OCTO2_OID "'; commit '" OCTO4_OID "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 */
+void test_merge_workdir_setup__branches_and_oids_3(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_reference *octo2_ref;
+ git_oid octo3_oid;
+ git_reference *octo4_ref;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "'; commit '" OCTO3_OID "'\n"));
+
+ git_reference_free(octo2_ref);
+ git_reference_free(octo4_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 octo5 */
+void test_merge_workdir_setup__branches_and_oids_4(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_reference *octo2_ref;
+ git_oid octo3_oid;
+ git_reference *octo4_ref;
+ git_reference *octo5_ref;
+ git_merge_head *our_head, *their_heads[5];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_reference_lookup(&octo5_ref, repo, GIT_REFS_HEADS_DIR OCTO5_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[4], repo, octo5_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 5, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n" OCTO5_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "', '" OCTO4_BRANCH "' and '" OCTO5_BRANCH "'; commit '" OCTO3_OID "'\n"));
+
+ git_reference_free(octo2_ref);
+ git_reference_free(octo4_ref);
+ git_reference_free(octo5_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+ git_merge_head_free(their_heads[4]);
+}
+
+/* git merge octo1 octo1 octo1 */
+void test_merge_workdir_setup__three_same_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_1_ref;
+ git_reference *octo1_2_ref;
+ git_reference *octo1_3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo1_2_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo1_2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo1_3_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo1_3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO1_BRANCH "' and '" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_1_ref);
+ git_reference_free(octo1_2_ref);
+ git_reference_free(octo1_3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 */
+void test_merge_workdir_setup__three_same_oids(void)
+{
+ git_oid our_oid;
+ git_oid octo1_1_oid;
+ git_oid octo1_2_oid;
+ git_oid octo1_3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_2_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo1_2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_3_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo1_3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO1_OID "'; commit '" OCTO1_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+static int create_remote_tracking_branch(const char *branch_name, const char *oid_str)
+{
+ int error = 0;
+
+ git_buf remotes_path = GIT_BUF_INIT,
+ origin_path = GIT_BUF_INIT,
+ filename = GIT_BUF_INIT,
+ data = GIT_BUF_INIT;
+
+ if ((error = git_buf_puts(&remotes_path, git_repository_path(repo))) < 0 ||
+ (error = git_buf_puts(&remotes_path, GIT_REFS_REMOTES_DIR)) < 0)
+ goto done;
+
+ if (!git_path_exists(git_buf_cstr(&remotes_path)) &&
+ (error = p_mkdir(git_buf_cstr(&remotes_path), 0777)) < 0)
+ goto done;
+
+ if ((error = git_buf_puts(&origin_path, git_buf_cstr(&remotes_path))) < 0 ||
+ (error = git_buf_puts(&origin_path, "origin")) < 0)
+ goto done;
+
+ if (!git_path_exists(git_buf_cstr(&origin_path)) &&
+ (error = p_mkdir(git_buf_cstr(&origin_path), 0777)) < 0)
+ goto done;
+
+ if ((error = git_buf_puts(&filename, git_buf_cstr(&origin_path))) < 0 ||
+ (error = git_buf_puts(&filename, "/")) < 0 ||
+ (error = git_buf_puts(&filename, branch_name)) < 0 ||
+ (error = git_buf_puts(&data, oid_str)) < 0 ||
+ (error = git_buf_puts(&data, "\n")) < 0)
+ goto done;
+
+ cl_git_rewritefile(git_buf_cstr(&filename), git_buf_cstr(&data));
+
+done:
+ git_buf_free(&remotes_path);
+ git_buf_free(&origin_path);
+ git_buf_free(&filename);
+ git_buf_free(&data);
+
+ return error;
+}
+
+/* git merge refs/remotes/origin/octo1 */
+void test_merge_workdir_setup__remote_tracking_one_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 */
+void test_merge_workdir_setup__remote_tracking_two_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "' and 'refs/remotes/origin/" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 refs/remotes/origin/octo3 */
+void test_merge_workdir_setup__remote_tracking_three_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO3_BRANCH, OCTO3_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "', 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO3_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge octo1 refs/remotes/origin/octo2 */
+void test_merge_workdir_setup__normal_branch_and_remote_tracking_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge refs/remotes/origin/octo1 octo2 */
+void test_merge_workdir_setup__remote_tracking_branch_and_normal_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO2_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 refs/remotes/origin/octo2 octo3 refs/remotes/origin/octo4 */
+void test_merge_workdir_setup__two_remote_tracking_branch_and_two_normal_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_reference *octo4_ref;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO4_BRANCH, OCTO4_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "', remote-tracking branches 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO4_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+ git_reference_free(octo4_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git pull origin branch octo1 */
+void test_merge_workdir_setup__pull_one(void)
+{
+ git_oid our_oid;
+ git_oid octo1_1_oid;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_1_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch 'octo1' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git pull origin octo1 octo2 */
+void test_merge_workdir_setup__pull_two(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git pull origin octo1 octo2 octo3 */
+void test_merge_workdir_setup__pull_three(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.url/repo.git", &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+void test_merge_workdir_setup__three_remotes(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.third/repo.git", &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "' of http://remote.first/repo.git, branch '" OCTO2_BRANCH "' of http://remote.second/repo.git, branch '" OCTO3_BRANCH "' of http://remote.third/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+void test_merge_workdir_setup__two_remotes(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_oid octo4_oid;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.first/repo.git", &octo3_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[3], repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH, "http://remote.second/repo.git", &octo4_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, ""));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.first/repo.git, branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "' of http://remote.second/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
struct merge_head_cb_data {
const char **oid_str;
unsigned int len;
diff --git a/tests-clar/object/raw/convert.c b/tests-clar/object/raw/convert.c
index 74442c153..88b1380a4 100644
--- a/tests-clar/object/raw/convert.c
+++ b/tests-clar/object/raw/convert.c
@@ -73,3 +73,40 @@ void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void)
cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y');
cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z');
}
+
+static void check_partial_oid(
+ char *buffer, size_t count, const git_oid *oid, const char *expected)
+{
+ git_oid_nfmt(buffer, count, oid);
+ buffer[count] = '\0';
+ cl_assert_equal_s(expected, buffer);
+}
+
+void test_object_raw_convert__convert_oid_partially(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ git_oid_nfmt(big, sizeof(big), &in);
+ cl_assert_equal_s(exp, big);
+
+ git_oid_nfmt(big, GIT_OID_HEXSZ + 1, &in);
+ cl_assert_equal_s(exp, big);
+
+ check_partial_oid(big, 1, &in, "1");
+ check_partial_oid(big, 2, &in, "16");
+ check_partial_oid(big, 3, &in, "16a");
+ check_partial_oid(big, 4, &in, "16a0");
+ check_partial_oid(big, 5, &in, "16a01");
+
+ check_partial_oid(big, GIT_OID_HEXSZ, &in, exp);
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 1, &in, "16a0123456789abcdef4b775213c23a8bd74f5e");
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 2, &in, "16a0123456789abcdef4b775213c23a8bd74f5");
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 3, &in, "16a0123456789abcdef4b775213c23a8bd74f");
+}
diff --git a/tests-clar/object/raw/short.c b/tests-clar/object/raw/short.c
index 93c79b6a5..b4019936a 100644
--- a/tests-clar/object/raw/short.c
+++ b/tests-clar/object/raw/short.c
@@ -92,3 +92,42 @@ void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void)
#undef MAX_OIDS
}
+
+void test_object_raw_short__oid_shortener_too_much_oids(void) {
+ /* The magic number of oids at which an oid_shortener will fail.
+ * This was experimentally established. */
+#define MAX_OIDS 24556
+
+ git_oid_shorten *os;
+ char number_buffer[16];
+ git_oid oid;
+ size_t i;
+
+ int min_len = 0;
+
+ os = git_oid_shorten_new(0);
+ cl_assert(os != NULL);
+
+ for (i = 0; i < MAX_OIDS; ++i) {
+ char *oid_text;
+
+ p_snprintf(number_buffer, 16, "%u", (unsigned int)i);
+ git_hash_buf(&oid, number_buffer, strlen(number_buffer));
+
+ oid_text = git__malloc(GIT_OID_HEXSZ + 1);
+ git_oid_fmt(oid_text, &oid);
+ oid_text[GIT_OID_HEXSZ] = 0;
+
+ min_len = git_oid_shorten_add(os, oid_text);
+ /* All but the last oid should give a non-negative min_len. At the
+ * last oid, git_oid_shorten_add should fail, returning a negative
+ * value */
+ if (i < MAX_OIDS - 1)
+ cl_assert(min_len >= 0);
+ else
+ cl_assert(min_len < 0);
+ }
+
+ git_oid_shorten_free(os);
+
+}
diff --git a/tests-clar/object/tag/write.c b/tests-clar/object/tag/write.c
index cd69bea89..68e4b6c61 100644
--- a/tests-clar/object/tag/write.c
+++ b/tests-clar/object/tag/write.c
@@ -220,3 +220,41 @@ void test_object_tag_write__deleting_with_an_invalid_name_returns_EINVALIDSPEC(v
{
cl_assert_equal_i(GIT_EINVALIDSPEC, git_tag_delete(g_repo, "Inv@{id"));
}
+
+void create_annotation(git_oid *tag_id, const char *name)
+{
+ git_object *target;
+ git_oid target_id;
+ git_signature *tagger;
+
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_tag_annotation_create(tag_id, g_repo, name, target, tagger, "boom!"));
+ git_object_free(target);
+ git_signature_free(tagger);
+}
+
+void test_object_tag_write__creating_an_annotation_stores_the_new_object_in_the_odb(void)
+{
+ git_oid tag_id;
+ git_tag *tag;
+
+ create_annotation(&tag_id, "new_tag");
+
+ cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id));
+ cl_assert_equal_s("new_tag", git_tag_name(tag));
+
+ git_tag_free(tag);
+}
+
+void test_object_tag_write__creating_an_annotation_does_not_create_a_reference(void)
+{
+ git_oid tag_id;
+ git_reference *tag_ref;
+
+ create_annotation(&tag_id, "new_tag");
+ cl_git_fail_with(git_reference_lookup(&tag_ref, g_repo, "refs/tags/new_tag"), GIT_ENOTFOUND);
+}
diff --git a/tests-clar/repo/config.c b/tests-clar/repo/config.c
index 086fb5e4f..b8971bb6b 100644
--- a/tests-clar/repo/config.c
+++ b/tests-clar/repo/config.c
@@ -13,14 +13,15 @@ void test_repo_config__initialize(void)
cl_must_pass(p_mkdir("alternate", 0777));
cl_git_pass(git_path_prettify(&path, "alternate", NULL));
-
}
void test_repo_config__cleanup(void)
{
+ cl_git_pass(git_path_prettify(&path, "alternate", NULL));
cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES));
-
git_buf_free(&path);
+ cl_assert(!git_path_isdir("alternate"));
+
cl_fixture_cleanup("empty_standard_repo");
}
@@ -73,3 +74,127 @@ void test_repo_config__open_missing_global_with_separators(void)
git_config_free(config);
git_repository_free(repo);
}
+
+#include "repository.h"
+
+void test_repo_config__read_no_configs(void)
+{
+ git_repository *repo;
+ int val;
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ /* with none */
+
+ cl_must_pass(p_unlink("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(GIT_ABBREV_DEFAULT, val);
+ git_repository_free(repo);
+
+ /* with just system */
+
+ cl_must_pass(p_mkdir("alternate/1", 0777));
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, "1"));
+ cl_git_rewritefile("alternate/1/gitconfig", "[core]\n\tabbrev = 10\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(10, val);
+ git_repository_free(repo);
+
+ /* with xdg + system */
+
+ cl_must_pass(p_mkdir("alternate/2", 0777));
+ path.ptr[path.size - 1] = '2';
+ cl_git_rewritefile("alternate/2/config", "[core]\n\tabbrev = 20\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(20, val);
+ git_repository_free(repo);
+
+ /* with global + xdg + system */
+
+ cl_must_pass(p_mkdir("alternate/3", 0777));
+ path.ptr[path.size - 1] = '3';
+ cl_git_rewritefile("alternate/3/.gitconfig", "[core]\n\tabbrev = 30\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(30, val);
+ git_repository_free(repo);
+
+ /* with all configs */
+
+ cl_git_rewritefile("empty_standard_repo/.git/config", "[core]\n\tabbrev = 40\n");
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+ git_repository_free(repo);
+
+ /* with all configs but delete the files ? */
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+
+ cl_must_pass(p_unlink("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+
+ cl_must_pass(p_unlink("alternate/1/gitconfig"));
+ cl_assert(!git_path_isfile("alternate/1/gitconfig"));
+
+ cl_must_pass(p_unlink("alternate/2/config"));
+ cl_assert(!git_path_isfile("alternate/2/config"));
+
+ cl_must_pass(p_unlink("alternate/3/.gitconfig"));
+ cl_assert(!git_path_isfile("alternate/3/.gitconfig"));
+
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+ git_repository_free(repo);
+
+ /* reopen */
+
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("alternate/3/.gitconfig"));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(7, val);
+ git_repository_free(repo);
+
+ cl_assert(!git_path_exists("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_exists("alternate/3/.gitconfig"));
+}
diff --git a/tests-clar/repo/shallow.c b/tests-clar/repo/shallow.c
new file mode 100644
index 000000000..1cc66ae40
--- /dev/null
+++ b/tests-clar/repo/shallow.c
@@ -0,0 +1,33 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+
+void test_repo_shallow__initialize(void)
+{
+}
+
+void test_repo_shallow__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_repo_shallow__no_shallow_file(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_assert_equal_i(0, git_repository_is_shallow(g_repo));
+}
+
+void test_repo_shallow__empty_shallow_file(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_mkfile("testrepo.git/shallow", "");
+ cl_assert_equal_i(0, git_repository_is_shallow(g_repo));
+}
+
+void test_repo_shallow__shallow_repo(void)
+{
+ g_repo = cl_git_sandbox_init("shallow.git");
+ cl_assert_equal_i(1, git_repository_is_shallow(g_repo));
+}
+
diff --git a/tests-clar/resources/shallow.git/HEAD b/tests-clar/resources/shallow.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/shallow.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/shallow.git/config b/tests-clar/resources/shallow.git/config
new file mode 100644
index 000000000..a88b74b69
--- /dev/null
+++ b/tests-clar/resources/shallow.git/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ ignorecase = true
+ precomposeunicode = false
+[remote "origin"]
+ url = file://testrepo.git
diff --git a/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx b/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx
new file mode 100644
index 000000000..bfc7d24ff
--- /dev/null
+++ b/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx
Binary files differ
diff --git a/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack b/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack
new file mode 100644
index 000000000..ccc6932fc
--- /dev/null
+++ b/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack
Binary files differ
diff --git a/tests-clar/resources/shallow.git/packed-refs b/tests-clar/resources/shallow.git/packed-refs
new file mode 100644
index 000000000..97eed743b
--- /dev/null
+++ b/tests-clar/resources/shallow.git/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/heads/master
diff --git a/tests-clar/resources/shallow.git/refs/.gitkeep b/tests-clar/resources/shallow.git/refs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests-clar/resources/shallow.git/refs/.gitkeep
diff --git a/tests-clar/resources/shallow.git/shallow b/tests-clar/resources/shallow.git/shallow
new file mode 100644
index 000000000..9536ad89c
--- /dev/null
+++ b/tests-clar/resources/shallow.git/shallow
@@ -0,0 +1 @@
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644