summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--CMakeLists.txt12
-rw-r--r--examples/diff.c4
-rw-r--r--include/git2.h2
-rw-r--r--include/git2/diff.h93
-rw-r--r--include/git2/refspec.h8
-rw-r--r--include/git2/reset.h44
-rw-r--r--include/git2/revparse.h36
-rw-r--r--include/git2/status.h2
-rw-r--r--include/git2/types.h6
-rw-r--r--src/attr.c2
-rw-r--r--src/branch.c8
-rw-r--r--src/buffer.c3
-rw-r--r--src/cache.c2
-rw-r--r--src/commit.c62
-rw-r--r--src/date.c876
-rw-r--r--src/diff.c104
-rw-r--r--src/diff.h5
-rw-r--r--src/diff_output.c26
-rw-r--r--src/ignore.c2
-rw-r--r--src/indexer.c4
-rw-r--r--src/notes.c4
-rw-r--r--src/path.c10
-rw-r--r--src/posix.h9
-rw-r--r--src/refs.c59
-rw-r--r--src/refs.h1
-rw-r--r--src/remote.c7
-rw-r--r--src/repository.c3
-rw-r--r--src/reset.c103
-rw-r--r--src/revparse.c748
-rw-r--r--src/revwalk.c36
-rw-r--r--src/signature.c16
-rw-r--r--src/tree-cache.c2
-rw-r--r--src/tree.c2
-rw-r--r--src/util.h17
-rw-r--r--src/win32/posix_w32.c78
-rw-r--r--src/win32/precompiled.c1
-rw-r--r--src/win32/precompiled.h19
-rw-r--r--tests-clar/clar_helpers.c81
-rw-r--r--tests-clar/clar_libgit2.h7
-rw-r--r--tests-clar/core/env.c53
-rw-r--r--tests-clar/core/filebuf.c4
-rw-r--r--tests-clar/date/date.c15
-rw-r--r--tests-clar/diff/diff_helpers.c7
-rw-r--r--tests-clar/diff/tree.c2
-rw-r--r--tests-clar/diff/workdir.c304
-rw-r--r--tests-clar/index/tests.c2
-rw-r--r--tests-clar/network/remotelocal.c4
-rw-r--r--tests-clar/object/tree/write.c4
-rw-r--r--tests-clar/refs/branches/listall.c14
-rw-r--r--tests-clar/refs/revparse.c174
-rw-r--r--tests-clar/repo/init.c18
-rw-r--r--tests-clar/reset/mixed.c47
-rw-r--r--tests-clar/reset/reset_helpers.c10
-rw-r--r--tests-clar/reset/reset_helpers.h6
-rw-r--r--tests-clar/reset/soft.c102
-rw-r--r--tests-clar/resources/filemodes/.gitted/HEAD1
-rw-r--r--tests-clar/resources/filemodes/.gitted/config6
-rw-r--r--tests-clar/resources/filemodes/.gitted/description1
-rwxr-xr-xtests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample24
-rw-r--r--tests-clar/resources/filemodes/.gitted/indexbin0 -> 528 bytes
-rw-r--r--tests-clar/resources/filemodes/.gitted/info/exclude6
-rw-r--r--tests-clar/resources/filemodes/.gitted/logs/HEAD1
-rw-r--r--tests-clar/resources/filemodes/.gitted/logs/refs/heads/master1
-rw-r--r--tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964abin0 -> 139 bytes
-rw-r--r--tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1bin0 -> 21 bytes
-rw-r--r--tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182bin0 -> 99 bytes
-rw-r--r--tests-clar/resources/filemodes/.gitted/refs/heads/master1
-rw-r--r--tests-clar/resources/filemodes/exec_off1
-rwxr-xr-xtests-clar/resources/filemodes/exec_off2on_staged1
-rwxr-xr-xtests-clar/resources/filemodes/exec_off2on_workdir1
-rw-r--r--tests-clar/resources/filemodes/exec_off_untracked1
-rwxr-xr-xtests-clar/resources/filemodes/exec_on1
-rw-r--r--tests-clar/resources/filemodes/exec_on2off_staged1
-rw-r--r--tests-clar/resources/filemodes/exec_on2off_workdir1
-rwxr-xr-xtests-clar/resources/filemodes/exec_on_untracked1
-rw-r--r--tests-clar/resources/testrepo.git/config4
-rw-r--r--tests-clar/resources/testrepo.git/logs/HEAD5
-rw-r--r--tests-clar/resources/testrepo.git/logs/refs/heads/br22
-rw-r--r--tests-clar/resources/testrepo.git/logs/refs/heads/master2
-rw-r--r--tests-clar/resources/testrepo.git/logs/refs/heads/not-good1
-rw-r--r--tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe2
-rw-r--r--tests-clar/resources/testrepo.git/refs/heads/not-good1
-rw-r--r--tests-clar/resources/testrepo.git/refs/remotes/test/master1
-rw-r--r--tests-clar/resources/testrepo.git/refs/tags/hard_tag1
-rw-r--r--tests-clar/resources/testrepo.git/refs/tags/wrapped_tag1
-rw-r--r--tests-clar/status/ignore.c14
-rw-r--r--tests-clar/status/worktree.c66
89 files changed, 3138 insertions, 283 deletions
diff --git a/AUTHORS b/AUTHORS
index 03904ff55..9f621b990 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,6 +6,7 @@ Alexei Sholik
Andreas Ericsson
Ankur Sethi
Ben Noordhuis
+Ben Straub
Benjamin C Meyer
Brian Lopez
Carlos Martín Nieto
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fdc103e8a..8f260f025 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,6 +155,12 @@ SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR})
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
+IF (MSVC)
+ # Precompiled headers
+ SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
+ SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h")
+ENDIF ()
+
# Install
INSTALL(TARGETS git2
RUNTIME DESTINATION ${INSTALL_BIN}
@@ -187,6 +193,12 @@ IF (BUILD_CLAR)
)
ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX})
TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES})
+
+ IF (MSVC)
+ # Precompiled headers
+ SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
+ ENDIF ()
+
IF (WIN32)
TARGET_LINK_LIBRARIES(libgit2_clar ws2_32)
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
diff --git a/examples/diff.c b/examples/diff.c
index 1b4ab549b..b72a75e1c 100644
--- a/examples/diff.c
+++ b/examples/diff.c
@@ -185,9 +185,7 @@ int main(int argc, char *argv[])
/* open repo */
- check(git_repository_discover(path, sizeof(path), dir, 0, "/"),
- "Could not discover repository");
- check(git_repository_open(&repo, path),
+ check(git_repository_open_ext(&repo, dir, 0, NULL),
"Could not open repository");
if (treeish1)
diff --git a/include/git2.h b/include/git2.h
index d75387318..34601449c 100644
--- a/include/git2.h
+++ b/include/git2.h
@@ -25,6 +25,7 @@
#include "git2/merge.h"
#include "git2/refs.h"
#include "git2/reflog.h"
+#include "git2/revparse.h"
#include "git2/object.h"
#include "git2/blob.h"
@@ -43,5 +44,6 @@
#include "git2/indexer.h"
#include "git2/submodule.h"
#include "git2/notes.h"
+#include "git2/reset.h"
#endif
diff --git a/include/git2/diff.h b/include/git2/diff.h
index a0e8ad6d2..d4d0eac47 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -29,6 +29,10 @@
*/
GIT_BEGIN_DECL
+/**
+ * Flags for diff options. A combination of these flags can be passed
+ * in via the `flags` value in the `git_diff_options`.
+ */
enum {
GIT_DIFF_NORMAL = 0,
GIT_DIFF_REVERSE = (1 << 0),
@@ -96,9 +100,9 @@ typedef enum {
typedef struct {
git_oid oid;
char *path;
- uint16_t mode;
git_off_t size;
unsigned int flags;
+ uint16_t mode;
} git_diff_file;
/**
@@ -160,15 +164,16 @@ typedef int (*git_diff_hunk_fn)(
* the file or hunk headers.
*/
enum {
- /* these values will be sent to `git_diff_data_fn` along with the line */
+ /* These values will be sent to `git_diff_data_fn` along with the line */
GIT_DIFF_LINE_CONTEXT = ' ',
GIT_DIFF_LINE_ADDITION = '+',
GIT_DIFF_LINE_DELETION = '-',
- GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */
+ GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */
GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */
- /* these values will only be sent to a `git_diff_data_fn` when the content
- * of a diff is being formatted (eg. through git_diff_print_patch() or
- * git_diff_print_compact(), for instance).
+
+ /* The following values will only be sent to a `git_diff_data_fn` when
+ * the content of a diff is being formatted (eg. through
+ * git_diff_print_patch() or git_diff_print_compact(), for instance).
*/
GIT_DIFF_LINE_FILE_HDR = 'F',
GIT_DIFF_LINE_HUNK_HDR = 'H',
@@ -206,6 +211,8 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
/**
* Compute a difference between two tree objects.
*
+ * This is equivalent to `git diff <treeish> <treeish>`
+ *
* @param repo The repository containing the trees.
* @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from.
@@ -222,6 +229,9 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
/**
* Compute a difference between a tree and the index.
*
+ * This is equivalent to `git diff --cached <treeish>` or if you pass
+ * the HEAD tree, then like `git diff --cached`.
+ *
* @param repo The repository containing the tree and index.
* @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from.
@@ -236,6 +246,11 @@ GIT_EXTERN(int) git_diff_index_to_tree(
/**
* Compute a difference between the working directory and the index.
*
+ * This matches the `git diff` command. See the note below on
+ * `git_diff_workdir_to_tree` for a discussion of the difference between
+ * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
+ * using libgit2.
+ *
* @param repo The repository.
* @param opts Structure with options to influence diff or NULL for defaults.
* @param diff A pointer to a git_diff_list pointer that will be allocated.
@@ -248,14 +263,24 @@ GIT_EXTERN(int) git_diff_workdir_to_index(
/**
* Compute a difference between the working directory and a tree.
*
- * This returns strictly the differences between the tree and the
- * files contained in the working directory, regardless of the state
- * of files in the index. There is no direct equivalent in C git.
+ * This is *NOT* the same as `git diff <treeish>`. Running `git diff HEAD`
+ * or the like actually uses information from the index, along with the tree
+ * and workdir dir info.
+ *
+ * This function returns strictly the differences between the tree and the
+ * files contained in the working directory, regardless of the state of
+ * files in the index. It may come as a surprise, but there is no direct
+ * equivalent in core git.
+ *
+ * To emulate `git diff <treeish>`, you should call both
+ * `git_diff_index_to_tree` and `git_diff_workdir_to_index`, then call
+ * `git_diff_merge` on the results. That will yield a `git_diff_list` that
+ * matches the git output.
*
- * This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those
- * commands diff the tree, the index, and the workdir. To emulate those
- * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`,
- * then call `git_diff_merge` on the results.
+ * If this seems confusing, take the case of a file with a staged deletion
+ * where the file has then been put back into the working dir and modified.
+ * The tree-to-workdir diff for that file is 'modified', but core git would
+ * show status 'deleted' since there is a pending deletion in the index.
*
* @param repo The repository containing the tree.
* @param opts Structure with options to influence diff or NULL for defaults.
@@ -298,10 +323,23 @@ GIT_EXTERN(int) git_diff_merge(
/**
* Iterate over a diff list issuing callbacks.
*
- * If the hunk and/or line callbacks are not NULL, then this will calculate
- * text diffs for all files it thinks are not binary. If those are both
- * NULL, then this will not bother with the text diffs, so it can be
- * efficient.
+ * This will iterate through all of the files described in a diff. You
+ * should provide a file callback to learn about each file.
+ *
+ * The "hunk" and "line" callbacks are optional, and the text diff of the
+ * files will only be calculated if they are not NULL. Of course, these
+ * callbacks will not be invoked for binary files on the diff list or for
+ * files whose only changed is a file mode change.
+ *
+ * @param diff A git_diff_list generated by one of the above functions.
+ * @param cb_data Reference pointer that will be passed to your callbacks.
+ * @param file_cb Callback function to make per file in the diff.
+ * @param hunk_cb Optional callback to make per hunk of text diff. This
+ * callback is called to describe a range of lines in the
+ * diff. It will not be issued for binary files.
+ * @param line_cb Optional callback to make per line of diff text. This
+ * same callback will be made for context lines, added, and
+ * removed lines, and even for a deleted trailing newline.
*/
GIT_EXTERN(int) git_diff_foreach(
git_diff_list *diff,
@@ -322,6 +360,14 @@ GIT_EXTERN(int) git_diff_print_compact(
* Iterate over a diff generating text output like "git diff".
*
* This is a super easy way to generate a patch from a diff.
+ *
+ * @param diff A git_diff_list generated by one of the above functions.
+ * @param cb_data Reference pointer that will be passed to your callbacks.
+ * @param print_cb Callback function to output lines of the diff. This
+ * same function will be called for file headers, hunk
+ * headers, and diff lines. Fortunately, you can probably
+ * use various GIT_DIFF_LINE constants to determine what
+ * text you are given.
*/
GIT_EXTERN(int) git_diff_print_patch(
git_diff_list *diff,
@@ -338,13 +384,14 @@ GIT_EXTERN(int) git_diff_print_patch(
/**
* Directly run a text diff on two blobs.
*
- * Compared to a file, a blob lacks some contextual information. As such, the
- * `git_diff_file` parameters of the callbacks will be filled accordingly to the following:
- * `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid`
- * will be set to 0.
+ * Compared to a file, a blob lacks some contextual information. As such,
+ * the `git_diff_file` parameters of the callbacks will be filled
+ * accordingly to the following: `mode` will be set to 0, `path` will be set
+ * to NULL. When dealing with a NULL blob, `oid` will be set to 0.
*
- * When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary
- * attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made.
+ * When at least one of the blobs being dealt with is binary, the
+ * `git_diff_delta` binary attribute will be set to 1 and no call to the
+ * hunk_cb nor line_cb will be made.
*/
GIT_EXTERN(int) git_diff_blobs(
git_blob *old_blob,
diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index 1100e9022..9e84aad99 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -20,6 +20,14 @@
GIT_BEGIN_DECL
/**
+ * Parse a refspec string and create a refspec object
+ *
+ * @param refspec pointer to the refspec structure to be used
+ * @param str the refspec as a string
+ */
+GIT_EXTERN(int) git_refspec_parse(git_refspec *refspec, const char *str);
+
+/**
* Get the source specifier
*
* @param refspec the refspec
diff --git a/include/git2/reset.h b/include/git2/reset.h
new file mode 100644
index 000000000..125178748
--- /dev/null
+++ b/include/git2/reset.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_reset_h__
+#define INCLUDE_git_reset_h__
+
+/**
+ * @file git2/reset.h
+ * @brief Git reset management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Sets the current head to the specified commit oid and optionally
+ * resets the index and working tree to match.
+ *
+ * When specifying a Soft kind of reset, the head will be moved to the commit.
+ *
+ * Specifying a Mixed kind of reset will trigger a Soft reset and the index will
+ * be replaced with the content of the commit tree.
+ *
+ * TODO: Implement remaining kinds of resets.
+ *
+ * @param repo Repository where to perform the reset operation.
+ *
+ * @param target Object to which the Head should be moved to. This object
+ * must belong to the given `repo` and can either be a git_commit or a
+ * git_tag. When a git_tag is being passed, it should be dereferencable
+ * to a git_commit which oid will be used as the target of the branch.
+ *
+ * @param reset_type Kind of reset operation to perform.
+ *
+ * @return GIT_SUCCESS or an error code
+ */
+GIT_EXTERN(int) git_reset(git_repository *repo, const git_object *target, git_reset_type reset_type);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/revparse.h b/include/git2/revparse.h
new file mode 100644
index 000000000..4567027e5
--- /dev/null
+++ b/include/git2/revparse.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_revparse_h__
+#define INCLUDE_git_revparse_h__
+
+#include "common.h"
+#include "types.h"
+
+
+/**
+ * @file git2/revparse.h
+ * @brief Git revision parsing routines
+ * @defgroup git_revparse Git revision parsing routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Find an object, as specified by a revision string. See `man gitrevisions`, or the documentation
+ * for `git rev-parse` for information on the syntax accepted.
+ *
+ * @param out pointer to output object
+ * @param repo the repository to search in
+ * @param spec the textual specification for an object
+ * @return on success, GIT_ERROR otherwise (use git_error_last for information about the error)
+ */
+GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/status.h b/include/git2/status.h
index 6a424dfd6..69b6e47e0 100644
--- a/include/git2/status.h
+++ b/include/git2/status.h
@@ -102,7 +102,7 @@ enum {
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
- GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3),
+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
};
diff --git a/include/git2/types.h b/include/git2/types.h
index 93823abf7..691903005 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -169,6 +169,12 @@ typedef enum {
GIT_BRANCH_REMOTE = 2,
} git_branch_t;
+/** Kinds of reset operation. */
+typedef enum {
+ GIT_RESET_SOFT = 1,
+ GIT_RESET_MIXED = 2,
+} git_reset_type;
+
typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote;
diff --git a/src/attr.c b/src/attr.c
index fb6651196..6fbd005d5 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -415,7 +415,7 @@ int git_attr_cache__push_file(
if (parse && (error = parse(repo, content, file)) < 0)
goto finish;
- git_strmap_insert(cache->files, file->key, file, error);
+ git_strmap_insert(cache->files, file->key, file, error); //-V595
if (error > 0)
error = 0;
diff --git a/src/branch.c b/src/branch.c
index 5d5a24038..8b97a8206 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -149,9 +149,11 @@ static int branch_list_cb(const char *branch_name, void *payload)
{
branch_filter_data *filter = (branch_filter_data *)payload;
- if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
- || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0))
- return git_vector_insert(filter->branchlist, git__strdup(branch_name));
+ if (filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0) {
+ return git_vector_insert(filter->branchlist, git__strdup(branch_name +strlen(GIT_REFS_HEADS_DIR)));
+ } else if (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0) {
+ return git_vector_insert(filter->branchlist, git__strdup(branch_name+strlen(GIT_REFS_DIR)));
+ }
return 0;
}
diff --git a/src/buffer.c b/src/buffer.c
index 783a36eb8..04aaec3df 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -144,8 +144,9 @@ int git_buf_puts(git_buf *buf, const char *string)
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
int len;
+ const size_t expected_size = buf->size + (strlen(format) * 2);
- ENSURE_SIZE(buf, buf->size + (strlen(format) * 2));
+ ENSURE_SIZE(buf, expected_size);
while (1) {
va_list args;
diff --git a/src/cache.c b/src/cache.c
index 31da3c36e..f8d89403b 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -69,7 +69,7 @@ void *git_cache_try_store(git_cache *cache, void *_entry)
git_cached_obj *entry = _entry;
uint32_t hash;
- memcpy(&hash, &entry->oid, sizeof(hash));
+ memcpy(&hash, &entry->oid, sizeof(uint32_t));
/* increase the refcount on this object, because
* the cache now owns it */
diff --git a/src/commit.c b/src/commit.c
index 2f40dc67d..57eafaa2e 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -81,66 +81,6 @@ int git_commit_create_v(
return res;
}
-/* Update the reference named `ref_name` so it points to `oid` */
-static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name)
-{
- git_reference *ref;
- int res;
-
- res = git_reference_lookup(&ref, repo, ref_name);
-
- /* If we haven't found the reference at all, we assume we need to create
- * a new reference and that's it */
- if (res == GIT_ENOTFOUND) {
- giterr_clear();
- return git_reference_create_oid(NULL, repo, ref_name, oid, 1);
- }
-
- if (res < 0)
- return -1;
-
- /* If we have found a reference, but it's symbolic, we need to update
- * the direct reference it points to */
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
- git_reference *aux;
- const char *sym_target;
-
- /* The target pointed at by this reference */
- sym_target = git_reference_target(ref);
-
- /* resolve the reference to the target it points to */
- res = git_reference_resolve(&aux, ref);
-
- /*
- * if the symbolic reference pointed to an inexisting ref,
- * this is means we're creating a new branch, for example.
- * We need to create a new direct reference with that name
- */
- if (res == GIT_ENOTFOUND) {
- giterr_clear();
- res = git_reference_create_oid(NULL, repo, sym_target, oid, 1);
- git_reference_free(ref);
- return res;
- }
-
- /* free the original symbolic reference now; not before because
- * we're using the `sym_target` pointer */
- git_reference_free(ref);
-
- if (res < 0)
- return -1;
-
- /* store the newly found direct reference in its place */
- ref = aux;
- }
-
- /* ref is made to point to `oid`: ref is either the original reference,
- * or the target of the symbolic reference we've looked up */
- res = git_reference_set_oid(ref, oid);
- git_reference_free(ref);
- return res;
-}
-
int git_commit_create(
git_oid *oid,
git_repository *repo,
@@ -192,7 +132,7 @@ int git_commit_create(
git_buf_free(&commit);
if (update_ref != NULL)
- return update_reference(repo, oid, update_ref);
+ return git_reference__update(repo, oid, update_ref);
return 0;
diff --git a/src/date.c b/src/date.c
new file mode 100644
index 000000000..f0e637a45
--- /dev/null
+++ b/src/date.c
@@ -0,0 +1,876 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "common.h"
+
+#ifndef GIT_WIN32
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+#include "cache.h"
+#include "posix.h"
+
+#include <ctype.h>
+#include <time.h>
+
+typedef enum {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822,
+ DATE_RAW
+} date_mode;
+
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+static git_time_t tm_to_time_t(const struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+ return -1;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+ { "Z", 0, 0, }, /* Zulu, alias for UTC */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe, USSR Zone 1 */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast, USSR Zone 7 */
+ { "JST", +9, 0, }, /* Japan Standard, USSR Zone 8 */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+static int match_string(const char *date, const char *str)
+{
+ int i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static int match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ unsigned int i;
+
+ for (i = 0; i < 12; i++) {
+ int match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ int match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+ int match = match_string(date, timezone_names[i].name);
+ if (match >= 3 || match == (int)strlen(timezone_names[i].name)) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
+ return 2;
+ }
+
+ /* BAD CRAP */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ struct tm check = *tm;
+ struct tm *r = (now_tm ? &check : tm);
+ time_t specified;
+
+ r->tm_mon = month - 1;
+ r->tm_mday = day;
+ if (year == -1) {
+ if (!now_tm)
+ return 1;
+ r->tm_year = now_tm->tm_year;
+ }
+ else if (year >= 1970 && year < 2100)
+ r->tm_year = year - 1900;
+ else if (year > 70 && year < 100)
+ r->tm_year = year;
+ else if (year < 38)
+ r->tm_year = year + 100;
+ else
+ return 0;
+ if (!now_tm)
+ return 1;
+
+ specified = tm_to_time_t(r);
+
+ /* Be it commit time or author time, it does not make
+ * sense to specify timestamp way into the future. Make
+ * sure it is not later than ten days from now...
+ */
+ if (now + 10*24*3600 < specified)
+ return 0;
+ tm->tm_mon = r->tm_mon;
+ tm->tm_mday = r->tm_mday;
+ if (year != -1)
+ tm->tm_year = r->tm_year;
+ return 1;
+ }
+ return 0;
+}
+
+static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ time_t now;
+ struct tm now_tm;
+ struct tm *refuse_future;
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ case '.':
+ now = time(NULL);
+ refuse_future = NULL;
+ if (p_gmtime_r(&now, &now_tm))
+ refuse_future = &now_tm;
+
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, refuse_future, now, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, refuse_future, now, tm))
+ break;
+ }
+ /* Our eastern European friends say dd.mm.yy[yy]
+ * is the norm there, so giving precedence to
+ * mm/dd/yy[yy] form only when separator is not '.'
+ */
+ if (c != '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+ if (is_date(num3, num2, num, refuse_future, now, tm))
+ break;
+ /* Funny European mm.dd.yy */
+ if (c == '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
+static int nodate(struct tm *tm)
+{
+ return (tm->tm_year &
+ tm->tm_mon &
+ tm->tm_mday &
+ tm->tm_hour &
+ tm->tm_min &
+ tm->tm_sec) < 0;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ int n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for any numbers with
+ * more than 8 digits. This is because we don't want to rule out
+ * numbers like 20070606 as a YYYYMMDD date.
+ */
+ if (num >= 100000000 && nodate(tm)) {
+ time_t time = num;
+ if (p_gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[-.:/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ int match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1400 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numbers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 13 && tm->tm_mon < 0)
+ tm->tm_mon = num-1;
+
+ return n;
+}
+
+static int match_tz(const char *date, int *offp)
+{
+ char *end;
+ int hour = strtoul(date + 1, &end, 10);
+ int n = end - (date + 1);
+ int min = 0;
+
+ if (n == 4) {
+ /* hhmm */
+ min = hour % 100;
+ hour = hour / 100;
+ } else if (n != 2) {
+ min = 99; /* random crap */
+ } else if (*end == ':') {
+ /* hh:mm? */
+ min = strtoul(end + 1, &end, 10);
+ if (end - (date + 1) != 5)
+ min = 99; /* random crap */
+ } /* otherwise we parsed "hh" */
+
+ /*
+ * Don't accept any random crap. Even though some places have
+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+ * UTC+14), there is something wrong if hour part is much
+ * larger than that. We might also want to check that the
+ * minutes are divisible by 15 or something too. (Offset of
+ * Kathmandu, Nepal is UTC+5:45)
+ */
+ if (min < 60 && hour < 24) {
+ int offset = hour * 60 + min;
+ if (*date == '-')
+ offset = -offset;
+ *offp = offset;
+ }
+ return end - date;
+}
+
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
+{
+ struct tm tm;
+ int tm_gmt;
+ git_time_t dummy_timestamp;
+ int dummy_offset;
+
+ if (!timestamp)
+ timestamp = &dummy_timestamp;
+ if (!offset)
+ offset = &dummy_offset;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ tm.tm_hour = -1;
+ tm.tm_min = -1;
+ tm.tm_sec = -1;
+ *offset = -1;
+ tm_gmt = 0;
+
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
+ for (;;) {
+ int match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, offset);
+
+ if (!match) {
+ /* BAD CRAP */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ *timestamp = tm_to_time_t(&tm);
+ if (*offset == -1)
+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
+
+ if (*timestamp == (git_time_t)-1)
+ return -1;
+
+ if (!tm_gmt)
+ *timestamp -= *offset * 60;
+ return 0; /* success */
+}
+
+
+/*
+ * Relative time update (eg "2 days ago"). If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
+{
+ time_t n;
+
+ if (tm->tm_mday < 0)
+ tm->tm_mday = now->tm_mday;
+ if (tm->tm_mon < 0)
+ tm->tm_mon = now->tm_mon;
+ if (tm->tm_year < 0) {
+ tm->tm_year = now->tm_year;
+ if (tm->tm_mon > now->tm_mon)
+ tm->tm_year--;
+ }
+
+ n = mktime(tm) - sec;
+ p_localtime_r(&n, tm);
+ return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 0);
+}
+
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 24*60*60);
+}
+
+static void date_time(struct tm *tm, struct tm *now, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, now, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 0);
+}
+
+static void date_noon(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 12);
+}
+
+static void date_tea(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 17);
+}
+
+static void date_pm(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, struct tm *now, int *num)
+{
+ time_t n = 0;
+ GIT_UNUSED(now);
+ GIT_UNUSED(num);
+ p_localtime_r(&n, tm);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
+ { "now", date_now },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int i;
+
+ while (isalpha(*++end));
+ ;
+
+ for (i = 0; i < 12; i++) {
+ int match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ *touched = 1;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ int len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, now, num);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ int len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ *touched = 1;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4) {
+ *num = 1;
+ *touched = 1;
+ }
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ int len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, now, tl->length * *num);
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ int match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, now, diff * 24 * 60 * 60);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n;
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ *touched = 1;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ tm->tm_year -= *num;
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+
+ return end;
+}
+
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ int match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
+ return end;
+}
+
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+ int number = *num;
+
+ if (number) {
+ *num = 0;
+ if (tm->tm_mday < 0 && number < 32)
+ tm->tm_mday = number;
+ else if (tm->tm_mon < 0 && number < 13)
+ tm->tm_mon = number-1;
+ else if (tm->tm_year < 0) {
+ if (number > 1969 && number < 2100)
+ tm->tm_year = number - 1900;
+ else if (number > 69 && number < 100)
+ tm->tm_year = number;
+ else if (number < 38)
+ tm->tm_year = 100 + number;
+ /* We screw up for number = 00 ? */
+ }
+ }
+}
+
+static git_time_t approxidate_str(const char *date,
+ const struct timeval *tv,
+ int *error_ret)
+{
+ int number = 0;
+ int touched = 0;
+ struct tm tm = {0}, now;
+ time_t time_sec;
+
+ time_sec = tv->tv_sec;
+ p_localtime_r(&time_sec, &tm);
+ now = tm;
+
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ pending_number(&tm, &number);
+ date = approxidate_digit(date-1, &tm, &number);
+ touched = 1;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
+ }
+ pending_number(&tm, &number);
+ if (!touched)
+ *error_ret = 1;
+ return update_tm(&tm, &now, 0);
+}
+
+int git__date_parse(git_time_t *out, const char *date)
+{
+ struct timeval tv;
+ git_time_t timestamp;
+ int offset, error_ret=0;
+
+ if (!parse_date_basic(date, &timestamp, &offset)) {
+ *out = timestamp;
+ return 0;
+ }
+
+ p_gettimeofday(&tv, NULL);
+ *out = approxidate_str(date, &tv, &error_ret);
+ return error_ret;
+}
diff --git a/src/diff.c b/src/diff.c
index 90baa9588..02b89b46e 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -130,37 +130,50 @@ fail:
static git_diff_delta *diff_delta__merge_like_cgit(
const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
{
- git_diff_delta *dup = diff_delta__dup(a, pool);
- if (!dup)
- return NULL;
-
- if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)
- return dup;
-
- git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);
-
- dup->new_file.mode = b->new_file.mode;
- dup->new_file.size = b->new_file.size;
- dup->new_file.flags = b->new_file.flags;
+ git_diff_delta *dup;
/* Emulate C git for merging two diffs (a la 'git diff <sha>').
*
* When C git does a diff between the work dir and a tree, it actually
* diffs with the index but uses the workdir contents. This emulates
* those choices so we can emulate the type of diff.
+ *
+ * We have three file descriptions here, let's call them:
+ * f1 = a->old_file
+ * f2 = a->new_file AND b->old_file
+ * f3 = b->new_file
*/
- if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {
- if (dup->status == GIT_DELTA_DELETED)
- /* preserve pending delete info */;
- else if (b->status == GIT_DELTA_UNTRACKED ||
- b->status == GIT_DELTA_IGNORED)
- dup->status = b->status;
- else
+
+ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
+ if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
+ return diff_delta__dup(a, pool);
+
+ /* otherwise, base this diff on the 'b' diff */
+ if ((dup = diff_delta__dup(b, pool)) == NULL)
+ return NULL;
+
+ /* If 'a' status is uninteresting, then we're done */
+ if (a->status == GIT_DELTA_UNMODIFIED)
+ return dup;
+
+ assert(a->status != GIT_DELTA_UNMODIFIED);
+ assert(b->status != GIT_DELTA_UNMODIFIED);
+
+ /* A cgit exception is that the diff of a file that is only in the
+ * index (i.e. not in HEAD nor workdir) is given as empty.
+ */
+ if (dup->status == GIT_DELTA_DELETED) {
+ if (a->status == GIT_DELTA_ADDED)
dup->status = GIT_DELTA_UNMODIFIED;
+ /* else don't overwrite DELETE status */
+ } else {
+ dup->status = a->status;
}
- else if (dup->status == GIT_DELTA_UNMODIFIED ||
- b->status == GIT_DELTA_DELETED)
- dup->status = b->status;
+
+ git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
+ dup->old_file.mode = a->old_file.mode;
+ dup->old_file.size = a->old_file.size;
+ dup->old_file.flags = a->old_file.flags;
return dup;
}
@@ -214,7 +227,9 @@ static int diff_delta__from_two(
git_diff_list *diff,
git_delta_t status,
const git_index_entry *old_entry,
+ uint32_t old_mode,
const git_index_entry *new_entry,
+ uint32_t new_mode,
git_oid *new_oid)
{
git_diff_delta *delta;
@@ -224,19 +239,22 @@ static int diff_delta__from_two(
return 0;
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
- const git_index_entry *temp = old_entry;
+ uint32_t temp_mode = old_mode;
+ const git_index_entry *temp_entry = old_entry;
old_entry = new_entry;
- new_entry = temp;
+ new_entry = temp_entry;
+ old_mode = new_mode;
+ new_mode = temp_mode;
}
delta = diff_delta__alloc(diff, status, old_entry->path);
GITERR_CHECK_ALLOC(delta);
- delta->old_file.mode = old_entry->mode;
+ delta->old_file.mode = old_mode;
git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
- delta->new_file.mode = new_entry->mode;
+ delta->new_file.mode = new_mode;
git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
if (new_oid || !git_oid_iszero(&new_entry->oid))
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
@@ -300,7 +318,7 @@ static git_diff_list *git_diff_list_alloc(
if (config_bool(cfg, "core.ignorestat", 0))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
if (config_bool(cfg, "core.filemode", 1))
- diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
if (config_bool(cfg, "core.trustctime", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
@@ -419,7 +437,7 @@ static int oid_for_workdir_item(
return result;
}
-#define EXEC_BIT_MASK 0000111
+#define MODE_BITS_MASK 0000777
static int maybe_modified(
git_iterator *old_iter,
@@ -443,13 +461,13 @@ static int maybe_modified(
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
- /* on platforms with no execmode, clear exec bit from comparisons */
- if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
- omode = omode & ~EXEC_BIT_MASK;
- nmode = nmode & ~EXEC_BIT_MASK;
- }
+ /* on platforms with no execmode, just preserve old mode */
+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
+ (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
+ new_iter->type == GIT_ITERATOR_WORKDIR)
+ nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
- /* support "assume unchanged" (badly, b/c we still stat everything) */
+ /* support "assume unchanged" (poorly, b/c we still stat everything) */
if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
@@ -471,8 +489,13 @@ static int maybe_modified(
omode == nmode)
status = GIT_DELTA_UNMODIFIED;
- /* if we have a workdir item with an unknown oid, check deeper */
- else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) {
+ /* if modes match and we have an unknown OID and a workdir iterator,
+ * then check deeper for matching
+ */
+ else if (omode == nmode &&
+ git_oid_iszero(&nitem->oid) &&
+ new_iter->type == GIT_ITERATOR_WORKDIR)
+ {
/* TODO: add check against index file st_mtime to avoid racy-git */
/* if they files look exactly alike, then we'll assume the same */
@@ -517,7 +540,8 @@ static int maybe_modified(
use_noid = &noid;
}
- return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
+ return diff_delta__from_two(
+ diff, status, oitem, omode, nitem, nmode, use_noid);
}
static int diff_from_iterators(
@@ -772,6 +796,12 @@ int git_diff_merge(
git_vector_swap(&onto->deltas, &onto_new);
git_pool_swap(&onto->pool, &onto_pool);
onto->new_src = from->new_src;
+
+ /* prefix strings also come from old pool, so recreate those.*/
+ onto->opts.old_prefix =
+ git_pool_strdup(&onto->pool, onto->opts.old_prefix);
+ onto->opts.new_prefix =
+ git_pool_strdup(&onto->pool, onto->opts.new_prefix);
}
git_vector_foreach(&onto_new, i, delta)
diff --git a/src/diff.h b/src/diff.h
index ac2457956..6cc854fbd 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -20,7 +20,7 @@
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
- GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */
+ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
@@ -36,5 +36,8 @@ struct git_diff_list {
uint32_t diffcaps;
};
+extern void git_diff__cleanup_modes(
+ uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index 5ffa641c4..92f7f8f2f 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -83,12 +83,13 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)
return -1;
- /* deal with adding and removing newline at EOF */
+ /* This should only happen if we are adding a line that does not
+ * have a newline at the end and the old code did. In that case,
+ * we have a ADD with a DEL_EOFNL as a pair.
+ */
if (len == 3) {
- if (origin == GIT_DIFF_LINE_ADDITION)
- origin = GIT_DIFF_LINE_ADD_EOFNL;
- else
- origin = GIT_DIFF_LINE_DEL_EOFNL;
+ origin = (origin == GIT_DIFF_LINE_ADDITION) ?
+ GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL;
return info->line_cb(
info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);
@@ -359,7 +360,7 @@ int git_diff_foreach(
/* map files */
if (delta->binary != 1 &&
- (hunk_cb || line_cb) &&
+ (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) &&
(delta->status == GIT_DELTA_DELETED ||
delta->status == GIT_DELTA_MODIFIED))
{
@@ -397,7 +398,9 @@ int git_diff_foreach(
/* since we did not have the definitive oid, we may have
* incorrect status and need to skip this item.
*/
- if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) {
+ if (delta->old_file.mode == delta->new_file.mode &&
+ !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
+ {
delta->status = GIT_DELTA_UNMODIFIED;
if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
goto cleanup;
@@ -420,7 +423,8 @@ int git_diff_foreach(
*/
if (file_cb != NULL) {
- error = file_cb(data, delta, (float)info.index / diff->deltas.length);
+ error = file_cb(
+ data, delta, (float)info.index / diff->deltas.length);
if (error < 0)
goto cleanup;
}
@@ -433,6 +437,10 @@ int git_diff_foreach(
if (!old_data.len && !new_data.len)
goto cleanup;
+ /* nothing to do if only diff was a mode change */
+ if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
+ goto cleanup;
+
assert(hunk_cb || line_cb);
info.delta = delta;
@@ -467,7 +475,7 @@ static char pick_suffix(int mode)
{
if (S_ISDIR(mode))
return '/';
- else if (mode & 0100)
+ else if (mode & 0100) //-V536
/* in git, modes are very regular, so we must have 0100755 mode */
return '*';
else
diff --git a/src/ignore.c b/src/ignore.c
index fc6194bb5..f2d08f59e 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -28,6 +28,8 @@ static int parse_ignore_file(
GITERR_CHECK_ALLOC(match);
}
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+
if (!(error = git_attr_fnmatch__parse(
match, ignores->pool, context, &scan)))
{
diff --git a/src/indexer.c b/src/indexer.c
index 5542bfeba..565d9ea0e 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -292,11 +292,13 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
{
int error;
struct git_pack_header hdr;
- size_t processed = stats->processed;
+ size_t processed;
git_mwindow_file *mwf = &idx->pack->mwf;
assert(idx && data && stats);
+ processed = stats->processed;
+
if (git_filebuf_write(&idx->pack_file, data, size) < 0)
return -1;
diff --git a/src/notes.c b/src/notes.c
index 84ad94087..e87ea65fb 100644
--- a/src/notes.c
+++ b/src/notes.c
@@ -125,7 +125,7 @@ static int note_write(git_oid *out, git_repository *repo,
return error;
}
- error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644);
+ error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644); //-V536
if (error < 0) {
/* libgit2 doesn't support object removal (gc) yet */
/* we leave an orphaned blob object behind - TODO */
@@ -154,7 +154,7 @@ static int note_write(git_oid *out, git_repository *repo,
if (error < 0)
return error;
- error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000);
+ error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000); //-V536
if (error < 0) {
git_treebuilder_free(tb);
return error;
diff --git a/src/path.c b/src/path.c
index 596dad164..d48435bd8 100644
--- a/src/path.c
+++ b/src/path.c
@@ -17,6 +17,10 @@
#include <stdio.h>
#include <ctype.h>
+#ifdef GIT_WIN32
+#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+#endif
+
/*
* Based on the Android implementation, BSD licensed.
* Check http://android.git.kernel.org/
@@ -105,7 +109,7 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
'C:/' here */
- if (len == 2 && isalpha(path[0]) && path[1] == ':') {
+ if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
len = 3;
goto Exit;
}
@@ -170,7 +174,7 @@ int git_path_root(const char *path)
#ifdef GIT_WIN32
/* Does the root of the path look like a windows drive ? */
- if (isalpha(path[0]) && (path[1] == ':'))
+ if (LOOKS_LIKE_DRIVE_PREFIX(path))
offset += 2;
/* Are we dealing with a windows network path? */
@@ -210,7 +214,7 @@ int git_path_prettify(git_buf *path_out, const char *path, const char *base)
giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
git_buf_clear(path_out);
-
+
return error;
}
diff --git a/src/posix.h b/src/posix.h
index 35118f968..6b6c53db1 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -59,9 +59,18 @@ extern int p_rename(const char *from, const char *to);
typedef int GIT_SOCKET;
#define INVALID_SOCKET -1
+#define p_localtime_r localtime_r
+#define p_gmtime_r gmtime_r
+#define p_gettimeofday gettimeofday
+
#else
typedef SOCKET GIT_SOCKET;
+struct timezone;
+extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
+extern int p_gettimeofday(struct timeval *tv, struct timezone *tz);
+
#endif
diff --git a/src/refs.c b/src/refs.c
index 1ef3e13a4..104685793 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1705,3 +1705,62 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2)
return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);
}
+/* Update the reference named `ref_name` so it points to `oid` */
+int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name)
+{
+ git_reference *ref;
+ int res;
+
+ res = git_reference_lookup(&ref, repo, ref_name);
+
+ /* If we haven't found the reference at all, we assume we need to create
+ * a new reference and that's it */
+ if (res == GIT_ENOTFOUND) {
+ giterr_clear();
+ return git_reference_create_oid(NULL, repo, ref_name, oid, 1);
+ }
+
+ if (res < 0)
+ return -1;
+
+ /* If we have found a reference, but it's symbolic, we need to update
+ * the direct reference it points to */
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ git_reference *aux;
+ const char *sym_target;
+
+ /* The target pointed at by this reference */
+ sym_target = git_reference_target(ref);
+
+ /* resolve the reference to the target it points to */
+ res = git_reference_resolve(&aux, ref);
+
+ /*
+ * if the symbolic reference pointed to an inexisting ref,
+ * this is means we're creating a new branch, for example.
+ * We need to create a new direct reference with that name
+ */
+ if (res == GIT_ENOTFOUND) {
+ giterr_clear();
+ res = git_reference_create_oid(NULL, repo, sym_target, oid, 1);
+ git_reference_free(ref);
+ return res;
+ }
+
+ /* free the original symbolic reference now; not before because
+ * we're using the `sym_target` pointer */
+ git_reference_free(ref);
+
+ if (res < 0)
+ return -1;
+
+ /* store the newly found direct reference in its place */
+ ref = aux;
+ }
+
+ /* ref is made to point to `oid`: ref is either the original reference,
+ * or the target of the symbolic reference we've looked up */
+ res = git_reference_set_oid(ref, oid);
+ git_reference_free(ref);
+ return res;
+}
diff --git a/src/refs.h b/src/refs.h
index 369e91e1c..082350278 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -54,6 +54,7 @@ void git_repository__refcache_free(git_refcache *refs);
int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name);
int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name);
+int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name);
/**
* Lookup a reference by name and try to resolve to an OID.
diff --git a/src/remote.c b/src/remote.c
index 8d6076107..00e108a0a 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -337,13 +337,16 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co
unsigned int i = 0;
git_buf refname = GIT_BUF_INIT;
git_oid old;
- git_vector *refs = &remote->refs;
+ git_vector *refs;
git_remote_head *head;
git_reference *ref;
- struct git_refspec *spec = &remote->fetch;
+ struct git_refspec *spec;
assert(remote);
+ refs = &remote->refs;
+ spec = &remote->fetch;
+
if (refs->length == 0)
return 0;
diff --git a/src/repository.c b/src/repository.c
index 718170839..4e467e689 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -718,6 +718,9 @@ static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit)
SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);
SET_REPO_CONFIG(bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));
+ if (!is_bare)
+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
+
if (!is_reinit && is_filesystem_case_insensitive(git_dir))
SET_REPO_CONFIG(bool, "core.ignorecase", true);
/* TODO: what other defaults? */
diff --git a/src/reset.c b/src/reset.c
new file mode 100644
index 000000000..14f7a236a
--- /dev/null
+++ b/src/reset.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "git2/reset.h"
+
+#define ERROR_MSG "Cannot perform reset"
+
+static int reset_error_invalid(const char *msg)
+{
+ giterr_set(GITERR_INVALID, "%s - %s", ERROR_MSG, msg);
+ return -1;
+}
+
+int git_reset(
+ git_repository *repo,
+ const git_object *target,
+ git_reset_type reset_type)
+{
+ git_otype target_type = GIT_OBJ_BAD;
+ git_object *commit = NULL;
+ git_index *index = NULL;
+ git_tree *tree = NULL;
+ int error = -1;
+
+ assert(repo && target);
+ assert(reset_type == GIT_RESET_SOFT || reset_type == GIT_RESET_MIXED);
+
+ if (git_object_owner(target) != repo)
+ return reset_error_invalid("The given target does not belong to this repository.");
+
+ if (reset_type == GIT_RESET_MIXED && git_repository_is_bare(repo))
+ return reset_error_invalid("Mixed reset is not allowed in a bare repository.");
+
+ target_type = git_object_type(target);
+
+ switch (target_type)
+ {
+ case GIT_OBJ_TAG:
+ if (git_tag_peel(&commit, (git_tag *)target) < 0)
+ goto cleanup;
+
+ if (git_object_type(commit) != GIT_OBJ_COMMIT) {
+ reset_error_invalid("The given target does not resolve to a commit.");
+ goto cleanup;
+ }
+ break;
+
+ case GIT_OBJ_COMMIT:
+ commit = (git_object *)target;
+ break;
+
+ default:
+ return reset_error_invalid("Only git_tag and git_commit objects are valid targets.");
+ }
+
+ //TODO: Check for unmerged entries
+
+ if (git_reference__update(repo, git_object_id(commit), GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_SOFT) {
+ error = 0;
+ goto cleanup;
+ }
+
+ if (git_commit_tree(&tree, (git_commit *)commit) < 0) {
+ giterr_set(GITERR_OBJECT, "%s - Failed to retrieve the commit tree.", ERROR_MSG);
+ goto cleanup;
+ }
+
+ if (git_repository_index(&index, repo) < 0) {
+ giterr_set(GITERR_OBJECT, "%s - Failed to retrieve the index.", ERROR_MSG);
+ goto cleanup;
+ }
+
+ if (git_index_read_tree(index, tree) < 0) {
+ giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG);
+ goto cleanup;
+ }
+
+ if (git_index_write(index) < 0) {
+ giterr_set(GITERR_INDEX, "%s - Failed to write the index.", ERROR_MSG);
+ goto cleanup;
+ }
+
+ error = 0;
+
+cleanup:
+ if (target_type == GIT_OBJ_TAG)
+ git_object_free(commit);
+
+ git_index_free(index);
+ git_tree_free(tree);
+
+ return error;
+}
diff --git a/src/revparse.c b/src/revparse.c
new file mode 100644
index 000000000..c66a9852e
--- /dev/null
+++ b/src/revparse.c
@@ -0,0 +1,748 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "common.h"
+#include "buffer.h"
+#include "tree.h"
+
+#include "git2.h"
+
+typedef enum {
+ REVPARSE_STATE_INIT,
+ REVPARSE_STATE_CARET,
+ REVPARSE_STATE_LINEAR,
+ REVPARSE_STATE_COLON,
+ REVPARSE_STATE_DONE,
+} revparse_state;
+
+static void set_invalid_syntax_err(const char *spec)
+{
+ giterr_set(GITERR_INVALID, "Refspec '%s' is not valid.", spec);
+}
+
+static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec)
+{
+ git_oid resolved;
+
+ if (git_reference_name_to_oid(&resolved, repo, spec) < 0)
+ return GIT_ERROR;
+
+ return git_object_lookup(out, repo, &resolved, GIT_OBJ_ANY);
+}
+
+/* Returns non-zero if yes */
+static int spec_looks_like_describe_output(const char *spec)
+{
+ regex_t regex;
+ int regex_error, retcode;
+
+ regex_error = regcomp(&regex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED);
+ if (regex_error != 0) {
+ giterr_set_regex(&regex, regex_error);
+ return 1; /* To be safe */
+ }
+ retcode = regexec(&regex, spec, 0, NULL, 0);
+ regfree(&regex);
+ return retcode == 0;
+}
+
+static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+ git_object *obj = NULL;
+ git_oid oid;
+ git_buf refnamebuf = GIT_BUF_INIT;
+ static const char* formatters[] = {
+ "refs/%s",
+ "refs/tags/%s",
+ "refs/heads/%s",
+ "refs/remotes/%s",
+ "refs/remotes/%s/HEAD",
+ NULL
+ };
+ unsigned int i;
+ const char *substr;
+
+ /* "git describe" output; snip everything before/including "-g" */
+ substr = strstr(spec, "-g");
+ if (substr &&
+ spec_looks_like_describe_output(spec) &&
+ !revparse_lookup_object(out, repo, substr+2)) {
+ return 0;
+ }
+
+ /* SHA or prefix */
+ if (!git_oid_fromstrn(&oid, spec, speclen)) {
+ if (!git_object_lookup_prefix(&obj, repo, &oid, speclen, GIT_OBJ_ANY)) {
+ *out = obj;
+ return 0;
+ }
+ }
+
+ /* Fully-named ref */
+ if (!revparse_lookup_fully_qualifed_ref(&obj, repo, spec)) {
+ *out = obj;
+ return 0;
+ }
+
+ /* Partially-named ref; match in this order: */
+ for (i=0; formatters[i]; i++) {
+ git_buf_clear(&refnamebuf);
+ if (git_buf_printf(&refnamebuf, formatters[i], spec) < 0) {
+ return GIT_ERROR;
+ }
+
+ if (!revparse_lookup_fully_qualifed_ref(&obj, repo, git_buf_cstr(&refnamebuf))) {
+ git_buf_free(&refnamebuf);
+ *out = obj;
+ return 0;
+ }
+ }
+ git_buf_free(&refnamebuf);
+
+ giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec);
+ return GIT_ERROR;
+}
+
+
+static int all_chars_are_digits(const char *str, size_t len)
+{
+ size_t i=0;
+ for (i=0; i<len; i++) {
+ if (str[i] < '0' || str[i] > '9') return 0;
+ }
+ return 1;
+}
+
+static void normalize_maybe_empty_refname(git_buf *buf, git_repository *repo, const char *refspec, size_t refspeclen)
+{
+ git_reference *ref;
+
+ if (!refspeclen) {
+ /* Empty refspec means current branch (target of HEAD) */
+ git_reference_lookup(&ref, repo, "HEAD");
+ git_buf_puts(buf, git_reference_target(ref));
+ git_reference_free(ref);
+ } else if (strstr(refspec, "HEAD")) {
+ /* Explicit head */
+ git_buf_puts(buf, refspec);
+ }else {
+ if (git__prefixcmp(refspec, "refs/heads/") != 0) {
+ git_buf_printf(buf, "refs/heads/%s", refspec);
+ } else {
+ git_buf_puts(buf, refspec);
+ }
+ }
+}
+
+static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec)
+{
+ git_reference *ref;
+ git_reflog *reflog = NULL;
+ int n, retcode = GIT_ERROR;
+ int i, refloglen;
+ const git_reflog_entry *entry;
+ git_buf buf = GIT_BUF_INIT;
+ size_t refspeclen = strlen(refspec);
+ size_t reflogspeclen = strlen(reflogspec);
+
+ if (git__prefixcmp(reflogspec, "@{") != 0 ||
+ git__suffixcmp(reflogspec, "}") != 0) {
+ giterr_set(GITERR_INVALID, "Bad reflogspec '%s'", reflogspec);
+ return GIT_ERROR;
+ }
+
+ /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */
+ if (refspeclen == 0 && !git__prefixcmp(reflogspec, "@{-")) {
+ regex_t regex;
+ int regex_error;
+
+ if (git__strtol32(&n, reflogspec+3, NULL, 0) < 0 ||
+ n < 1) {
+ giterr_set(GITERR_INVALID, "Invalid reflogspec %s", reflogspec);
+ return GIT_ERROR;
+ }
+
+ if (!git_reference_lookup(&ref, repo, "HEAD")) {
+ if (!git_reflog_read(&reflog, ref)) {
+ regex_error = regcomp(&regex, "checkout: moving from (.*) to .*", REG_EXTENDED);
+ if (regex_error != 0) {
+ giterr_set_regex(&regex, regex_error);
+ } else {
+ regmatch_t regexmatches[2];
+
+ refloglen = git_reflog_entrycount(reflog);
+ for (i=refloglen-1; i >= 0; i--) {
+ const char *msg;
+ entry = git_reflog_entry_byindex(reflog, i);
+
+ msg = git_reflog_entry_msg(entry);
+ if (!regexec(&regex, msg, 2, regexmatches, 0)) {
+ n--;
+ if (!n) {
+ git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
+ retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf));
+ break;
+ }
+ }
+ }
+ regfree(&regex);
+ }
+ }
+ git_reference_free(ref);
+ }
+ } else {
+ int date_error = 0;
+ git_time_t timestamp;
+ git_buf datebuf = GIT_BUF_INIT;
+
+ git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3);
+ date_error = git__date_parse(&timestamp, git_buf_cstr(&datebuf));
+
+ /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */
+ if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) {
+ git_config *cfg;
+ if (!git_repository_config(&cfg, repo)) {
+ /* Is the ref a tracking branch? */
+ const char *remote;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "branch.%s.remote", refspec);
+ if (!git_config_get_string(&remote, cfg, git_buf_cstr(&buf))) {
+ /* Yes. Find the first merge target name. */
+ const char *mergetarget;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "branch.%s.merge", refspec);
+ if (!git_config_get_string(&mergetarget, cfg, git_buf_cstr(&buf)) &&
+ !git__prefixcmp(mergetarget, "refs/heads/")) {
+ /* Success. Look up the target and fetch the object. */
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "refs/remotes/%s/%s", remote, mergetarget+11);
+ retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf));
+ }
+ }
+ git_config_free(cfg);
+ }
+ }
+
+ /* @{N} -> Nth prior value for the ref (from reflog) */
+ else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) &&
+ !git__strtol32(&n, reflogspec+2, NULL, 0) &&
+ n <= 100000000) { /* Allow integer time */
+ normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen);
+
+ if (n == 0) {
+ retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf));
+ } else if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) {
+ if (!git_reflog_read(&reflog, ref)) {
+ int numentries = git_reflog_entrycount(reflog);
+ if (numentries < n) {
+ giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d",
+ git_buf_cstr(&buf), numentries, n);
+ retcode = GIT_ERROR;
+ } else {
+ const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n);
+ const git_oid *oid = git_reflog_entry_oidold(entry);
+ retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY);
+ }
+ }
+ git_reference_free(ref);
+ }
+ }
+
+ else if (!date_error) {
+ /* Ref as it was on a certain date */
+ normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen);
+
+ if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) {
+ git_reflog *reflog;
+ if (!git_reflog_read(&reflog, ref)) {
+ /* Keep walking until we find an entry older than the given date */
+ int numentries = git_reflog_entrycount(reflog);
+ int i;
+
+ /* TODO: clunky. Factor "now" into a utility */
+ git_signature *sig;
+ git_time as_of;
+
+ git_signature_now(&sig, "blah", "blah");
+ as_of = sig->when;
+ git_signature_free(sig);
+
+ as_of.time = (timestamp > 0)
+ ? timestamp
+ : sig->when.time + timestamp;
+
+ for (i=numentries-1; i>0; i--) {
+ const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i);
+ git_time commit_time = git_reflog_entry_committer(entry)->when;
+ if (git__time_cmp(&commit_time, &as_of) <= 0 ) {
+ retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY);
+ break;
+ }
+ }
+
+ if (!i) {
+ /* Didn't find a match. Use the oldest revision in the reflog. */
+ const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, 0);
+ retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY);
+ }
+
+ git_reflog_free(reflog);
+ }
+
+ git_reference_free(ref);
+ }
+ }
+
+ git_buf_free(&datebuf);
+ }
+
+ if (reflog) git_reflog_free(reflog);
+ git_buf_free(&buf);
+ return retcode;
+}
+
+static git_object* dereference_object(git_object *obj)
+{
+ git_otype type = git_object_type(obj);
+
+ switch (type) {
+ case GIT_OBJ_COMMIT:
+ {
+ git_tree *tree = NULL;
+ if (0 == git_commit_tree(&tree, (git_commit*)obj)) {
+ return (git_object*)tree;
+ }
+ }
+ break;
+ case GIT_OBJ_TAG:
+ {
+ git_object *newobj = NULL;
+ if (0 == git_tag_target(&newobj, (git_tag*)obj)) {
+ return newobj;
+ }
+ }
+ break;
+
+ default:
+ case GIT_OBJ_TREE:
+ case GIT_OBJ_BLOB:
+ case GIT_OBJ_OFS_DELTA:
+ case GIT_OBJ_REF_DELTA:
+ break;
+ }
+
+ /* Can't dereference some types */
+ return NULL;
+}
+
+static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type)
+{
+ int retcode = 1;
+ git_object *obj1 = obj, *obj2 = obj;
+
+ while (retcode > 0) {
+ git_otype this_type = git_object_type(obj1);
+
+ if (this_type == target_type) {
+ *out = obj1;
+ retcode = 0;
+ } else {
+ /* Dereference once, if possible. */
+ obj2 = dereference_object(obj1);
+ if (!obj2) {
+ giterr_set(GITERR_REFERENCE, "Can't dereference to type");
+ retcode = GIT_ERROR;
+ }
+ }
+ if (obj1 != obj && obj1 != obj2) {
+ git_object_free(obj1);
+ }
+ obj1 = obj2;
+ }
+ return retcode;
+}
+
+static git_otype parse_obj_type(const char *str)
+{
+ if (!strcmp(str, "{commit}")) return GIT_OBJ_COMMIT;
+ if (!strcmp(str, "{tree}")) return GIT_OBJ_TREE;
+ if (!strcmp(str, "{blob}")) return GIT_OBJ_BLOB;
+ if (!strcmp(str, "{tag}")) return GIT_OBJ_TAG;
+ return GIT_OBJ_BAD;
+}
+
+static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement)
+{
+ git_commit *commit;
+ size_t movementlen = strlen(movement);
+ int n;
+
+ if (*movement == '{') {
+ if (movement[movementlen-1] != '}') {
+ set_invalid_syntax_err(movement);
+ return GIT_ERROR;
+ }
+
+ /* {} -> Dereference until we reach an object that isn't a tag. */
+ if (movementlen == 2) {
+ git_object *newobj = obj;
+ git_object *newobj2 = newobj;
+ while (git_object_type(newobj2) == GIT_OBJ_TAG) {
+ newobj2 = dereference_object(newobj);
+ if (newobj != obj) git_object_free(newobj);
+ if (!newobj2) {
+ giterr_set(GITERR_REFERENCE, "Couldn't find object of target type.");
+ return GIT_ERROR;
+ }
+ newobj = newobj2;
+ }
+ *out = newobj2;
+ return 0;
+ }
+
+ /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */
+ if (movement[1] == '/') {
+ int retcode = GIT_ERROR;
+ git_revwalk *walk;
+ if (!git_revwalk_new(&walk, repo)) {
+ git_oid oid;
+ regex_t preg;
+ int reg_error;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+ git_revwalk_push(walk, git_object_id(obj));
+
+ /* Extract the regex from the movement string */
+ git_buf_put(&buf, movement+2, strlen(movement)-3);
+
+ reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED);
+ if (reg_error != 0) {
+ giterr_set_regex(&preg, reg_error);
+ } else {
+ while(!git_revwalk_next(&oid, walk)) {
+ git_object *walkobj;
+
+ /* Fetch the commit object, and check for matches in the message */
+ if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) {
+ if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) {
+ /* Found it! */
+ retcode = 0;
+ *out = walkobj;
+ if (obj == walkobj) {
+ /* Avoid leaking an object */
+ git_object_free(walkobj);
+ }
+ break;
+ }
+ git_object_free(walkobj);
+ }
+ }
+ if (retcode < 0) {
+ giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", movement);
+ }
+ regfree(&preg);
+ }
+
+ git_buf_free(&buf);
+ git_revwalk_free(walk);
+ }
+ return retcode;
+ }
+
+ /* {...} -> Dereference until we reach an object of a certain type. */
+ if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) {
+ return GIT_ERROR;
+ }
+ return 0;
+ }
+
+ /* Dereference until we reach a commit. */
+ if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) {
+ /* Can't dereference to a commit; fail */
+ return GIT_ERROR;
+ }
+
+ /* "^" is the same as "^1" */
+ if (movementlen == 0) {
+ n = 1;
+ } else {
+ git__strtol32(&n, movement, NULL, 0);
+ }
+ commit = (git_commit*)obj;
+
+ /* "^0" just returns the input */
+ if (n == 0) {
+ *out = obj;
+ return 0;
+ }
+
+ if (git_commit_parent(&commit, commit, n-1) < 0) {
+ return GIT_ERROR;
+ }
+
+ *out = (git_object*)commit;
+ return 0;
+}
+
+static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement)
+{
+ git_commit *commit1, *commit2;
+ int i, n;
+
+ /* Dereference until we reach a commit. */
+ if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) {
+ /* Can't dereference to a commit; fail */
+ return GIT_ERROR;
+ }
+
+ /* "~" is the same as "~1" */
+ if (*movement == '\0') {
+ n = 1;
+ } else {
+ git__strtol32(&n, movement, NULL, 0);
+ }
+ commit1 = (git_commit*)obj;
+
+ /* "~0" just returns the input */
+ if (n == 0) {
+ *out = obj;
+ return 0;
+ }
+
+ for (i=0; i<n; i++) {
+ if (git_commit_parent(&commit2, commit1, 0) < 0) {
+ return GIT_ERROR;
+ }
+ if (commit1 != (git_commit*)obj) {
+ git_commit_free(commit1);
+ }
+ commit1 = commit2;
+ }
+
+ *out = (git_object*)commit1;
+ return 0;
+}
+
+static int oid_for_tree_path(git_oid *out, git_tree *tree, git_repository *repo, const char *path)
+{
+ char *str = git__strdup(path);
+ char *tok;
+ void *alloc = str;
+ git_tree *tree2 = tree;
+ const git_tree_entry *entry;
+
+ while ((tok = git__strtok(&str, "/\\")) != NULL) {
+ entry = git_tree_entry_byname(tree2, tok);
+ if (tree2 != tree) git_tree_free(tree2);
+ if (git_tree_entry__is_tree(entry)) {
+ if (git_tree_lookup(&tree2, repo, &entry->oid) < 0) {
+ git__free(alloc);
+ return GIT_ERROR;
+ }
+ }
+ }
+
+ git_oid_cpy(out, git_tree_entry_id(entry));
+ git__free(alloc);
+ return 0;
+}
+
+static int handle_colon_syntax(git_object **out,
+ git_repository *repo,
+ git_object *obj,
+ const char *path)
+{
+ git_tree *tree;
+ git_oid oid;
+
+ /* Dereference until we reach a tree. */
+ if (dereference_to_type(&obj, obj, GIT_OBJ_TREE) < 0) {
+ return GIT_ERROR;
+ }
+ tree = (git_tree*)obj;
+
+ /* Find the blob at the given path. */
+ oid_for_tree_path(&oid, tree, repo, path);
+ git_tree_free(tree);
+ return git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
+}
+
+static int revparse_global_grep(git_object **out, git_repository *repo, const char *pattern)
+{
+ git_revwalk *walk;
+ int retcode = GIT_ERROR;
+
+ if (!pattern[0]) {
+ giterr_set(GITERR_REGEX, "Empty pattern");
+ return GIT_ERROR;
+ }
+
+ if (!git_revwalk_new(&walk, repo)) {
+ regex_t preg;
+ int reg_error;
+ git_oid oid;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+ git_revwalk_push_glob(walk, "refs/heads/*");
+
+ reg_error = regcomp(&preg, pattern, REG_EXTENDED);
+ if (reg_error != 0) {
+ giterr_set_regex(&preg, reg_error);
+ } else {
+ git_object *walkobj = NULL, *resultobj = NULL;
+ while(!git_revwalk_next(&oid, walk)) {
+ /* Fetch the commit object, and check for matches in the message */
+ if (walkobj != resultobj) git_object_free(walkobj);
+ if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) {
+ if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) {
+ /* Match! */
+ resultobj = walkobj;
+ retcode = 0;
+ break;
+ }
+ }
+ }
+ if (!resultobj) {
+ giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern);
+ } else {
+ *out = resultobj;
+ }
+ regfree(&preg);
+ git_revwalk_free(walk);
+ }
+ }
+
+ return retcode;
+}
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+{
+ revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT;
+ const char *spec_cur = spec;
+ git_object *cur_obj = NULL, *next_obj = NULL;
+ git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT;
+ int retcode = 0;
+
+ assert(out && repo && spec);
+
+ if (spec[0] == ':') {
+ if (spec[1] == '/') {
+ return revparse_global_grep(out, repo, spec+2);
+ }
+ /* TODO: support merge-stage path lookup (":2:Makefile"). */
+ giterr_set(GITERR_INVALID, "Unimplemented");
+ return GIT_ERROR;
+ }
+
+ while (current_state != REVPARSE_STATE_DONE) {
+ switch (current_state) {
+ case REVPARSE_STATE_INIT:
+ if (!*spec_cur) {
+ /* No operators, just a name. Find it and return. */
+ retcode = revparse_lookup_object(out, repo, spec);
+ next_state = REVPARSE_STATE_DONE;
+ } else if (*spec_cur == '@') {
+ /* '@' syntax doesn't allow chaining */
+ git_buf_puts(&stepbuffer, spec_cur);
+ retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer));
+ next_state = REVPARSE_STATE_DONE;
+ } else if (*spec_cur == '^') {
+ next_state = REVPARSE_STATE_CARET;
+ } else if (*spec_cur == '~') {
+ next_state = REVPARSE_STATE_LINEAR;
+ } else if (*spec_cur == ':') {
+ next_state = REVPARSE_STATE_COLON;
+ } else {
+ git_buf_putc(&specbuffer, *spec_cur);
+ }
+ spec_cur++;
+
+ if (current_state != next_state && next_state != REVPARSE_STATE_DONE) {
+ /* Leaving INIT state, find the object specified, in case that state needs it */
+ if (revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer)) < 0) {
+ retcode = GIT_ERROR;
+ next_state = REVPARSE_STATE_DONE;
+ }
+ }
+ break;
+
+
+ case REVPARSE_STATE_CARET:
+ /* Gather characters until NULL, '~', or '^' */
+ if (!*spec_cur) {
+ retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer));
+ next_state = REVPARSE_STATE_DONE;
+ } else if (*spec_cur == '~') {
+ retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer));
+ git_buf_clear(&stepbuffer);
+ next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE;
+ } else if (*spec_cur == '^') {
+ retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer));
+ git_buf_clear(&stepbuffer);
+ if (retcode < 0) {
+ next_state = REVPARSE_STATE_DONE;
+ }
+ } else {
+ git_buf_putc(&stepbuffer, *spec_cur);
+ }
+ spec_cur++;
+ break;
+
+ case REVPARSE_STATE_LINEAR:
+ if (!*spec_cur) {
+ retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer));
+ next_state = REVPARSE_STATE_DONE;
+ } else if (*spec_cur == '~') {
+ retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer));
+ git_buf_clear(&stepbuffer);
+ if (retcode < 0) {
+ next_state = REVPARSE_STATE_DONE;
+ }
+ } else if (*spec_cur == '^') {
+ retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer));
+ git_buf_clear(&stepbuffer);
+ next_state = !retcode ? REVPARSE_STATE_CARET : REVPARSE_STATE_DONE;
+ } else {
+ git_buf_putc(&stepbuffer, *spec_cur);
+ }
+ spec_cur++;
+ break;
+
+ case REVPARSE_STATE_COLON:
+ if (*spec_cur) {
+ git_buf_putc(&stepbuffer, *spec_cur);
+ } else {
+ retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer));
+ next_state = REVPARSE_STATE_DONE;
+ }
+ spec_cur++;
+ break;
+
+ case REVPARSE_STATE_DONE:
+ if (cur_obj && *out != cur_obj) git_object_free(cur_obj);
+ if (next_obj && *out != next_obj) git_object_free(next_obj);
+ break;
+ }
+
+ current_state = next_state;
+ if (cur_obj != next_obj) {
+ if (cur_obj) git_object_free(cur_obj);
+ cur_obj = next_obj;
+ }
+ }
+
+ if (*out != cur_obj) git_object_free(cur_obj);
+ if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj);
+
+ git_buf_free(&specbuffer);
+ git_buf_free(&stepbuffer);
+ return retcode;
+}
diff --git a/src/revwalk.c b/src/revwalk.c
index e64d93f20..5aa98e3a7 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -169,14 +169,23 @@ static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
return commit;
}
+static int commit_error(commit_object *commit, const char *msg)
+{
+ char commit_oid[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(commit_oid, &commit->oid);
+ commit_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_ODB, "Failed to parse commit %s - %s", commit_oid, msg);
+
+ return -1;
+}
+
static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw)
{
const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;
-
unsigned char *buffer = raw->data;
unsigned char *buffer_end = buffer + raw->len;
unsigned char *parents_start;
-
int i, parents = 0;
int commit_time;
@@ -207,21 +216,18 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo
commit->out_degree = (unsigned short)parents;
- if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Object is corrupted");
- return -1;
- }
+ if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
- buffer = memchr(buffer, '>', buffer_end - buffer);
- if (buffer == NULL) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Can't find author");
- return -1;
- }
+ if ((buffer = memchr(buffer, '<', buffer_end - buffer)) == NULL ||
+ (buffer = memchr(buffer, '>', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "malformed author information");
- if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < 0) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Can't parse commit time");
- return -1;
- }
+ while (*buffer == '>' || git__isspace(*buffer))
+ buffer++;
+
+ if (git__strtol32(&commit_time, (char *)buffer, NULL, 10) < 0)
+ return commit_error(commit, "cannot parse commit time");
commit->time = (time_t)commit_time;
commit->parsed = 1;
diff --git a/src/signature.c b/src/signature.c
index 7d329c4c9..332bdf65f 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -113,26 +113,14 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema
time_t offset;
struct tm *utc_tm, *local_tm;
git_signature *sig;
-
-#ifndef GIT_WIN32
struct tm _utc, _local;
-#endif
*sig_out = NULL;
time(&now);
- /**
- * On Win32, `gmtime_r` doesn't exist but
- * `gmtime` is threadsafe, so we can use that
- */
-#ifdef GIT_WIN32
- utc_tm = gmtime(&now);
- local_tm = localtime(&now);
-#else
- utc_tm = gmtime_r(&now, &_utc);
- local_tm = localtime_r(&now, &_local);
-#endif
+ utc_tm = p_gmtime_r(&now, &_utc);
+ local_tm = p_localtime_r(&now, &_local);
offset = mktime(local_tm) - mktime(utc_tm);
offset /= 60;
diff --git a/src/tree-cache.c b/src/tree-cache.c
index ebc2c6807..8d186d2fb 100644
--- a/src/tree-cache.c
+++ b/src/tree-cache.c
@@ -69,7 +69,7 @@ const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char
return NULL;
}
- if (end == NULL || end + 1 == '\0')
+ if (end == NULL || *end + 1 == '\0')
return tree;
ptr = end + 1;
diff --git a/src/tree.c b/src/tree.c
index 92b1b1e39..d575dc8ff 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -22,7 +22,7 @@ static int valid_attributes(const int attributes)
static int valid_entry_name(const char *filename)
{
- return strlen(filename) > 0 && strchr(filename, '/') == NULL;
+ return *filename != '\0' && strchr(filename, '/') == NULL;
}
static int entry_sort_cmp(const void *a, const void *b)
diff --git a/src/util.h b/src/util.h
index c6851ac7e..eed2bc80c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -209,6 +209,13 @@ GIT_INLINE(bool) git__isspace(int c)
return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');
}
+GIT_INLINE(int) git__time_cmp(const git_time *a, const git_time *b)
+{
+ /* Adjust for time zones. Times are in seconds, offsets are in minutes. */
+ git_time_t adjusted_a = a->time + ((b->offset - a->offset) * 60);
+ return (int)(adjusted_a - b->time);
+}
+
GIT_INLINE(bool) git__iswildcard(int c)
{
return (c == '*' || c == '?' || c == '[');
@@ -223,4 +230,14 @@ GIT_INLINE(bool) git__iswildcard(int c)
*/
extern int git__parse_bool(int *out, const char *value);
+/*
+ * Parse a string into a value as a git_time_t.
+ *
+ * Sample valid input:
+ * - "yesterday"
+ * - "July 17, 2003"
+ * - "2003-7-17 08:23"
+ */
+int git__date_parse(git_time_t *out, const char *date);
+
#endif /* INCLUDE_util_h__ */
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 10de70da8..37956af85 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -414,7 +414,7 @@ int p_mkstemp(char *tmp_path)
return -1;
#endif
- return p_creat(tmp_path, 0744);
+ return p_creat(tmp_path, 0744); //-V536
}
int p_setenv(const char* name, const char* value, int overwrite)
@@ -470,3 +470,79 @@ int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
return send(socket, buffer, (int)length, flags);
}
+
+/**
+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
+ */
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+
+#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
+#else
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
+#endif
+
+struct timezone
+{
+ int tz_minuteswest; /* minutes W of Greenwich */
+ int tz_dsttime; /* type of dst correction */
+};
+
+int p_gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ FILETIME ft;
+ unsigned __int64 tmpres = 0;
+ static int tzflag;
+
+ if (NULL != tv)
+ {
+ GetSystemTimeAsFileTime(&ft);
+
+ tmpres |= ft.dwHighDateTime;
+ tmpres <<= 32;
+ tmpres |= ft.dwLowDateTime;
+
+ /*converting file time to unix epoch*/
+ tmpres /= 10; /*convert into microseconds*/
+ tmpres -= DELTA_EPOCH_IN_MICROSECS;
+ tv->tv_sec = (long)(tmpres / 1000000UL);
+ tv->tv_usec = (long)(tmpres % 1000000UL);
+ }
+
+ if (NULL != tz)
+ {
+ if (!tzflag)
+ {
+ _tzset();
+ tzflag++;
+ }
+ tz->tz_minuteswest = _timezone / 60;
+ tz->tz_dsttime = _daylight;
+ }
+
+ return 0;
+}
diff --git a/src/win32/precompiled.c b/src/win32/precompiled.c
new file mode 100644
index 000000000..c08ca1f13
--- /dev/null
+++ b/src/win32/precompiled.c
@@ -0,0 +1 @@
+#include "precompiled.h" \ No newline at end of file
diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h
new file mode 100644
index 000000000..5de7e6f34
--- /dev/null
+++ b/src/win32/precompiled.h
@@ -0,0 +1,19 @@
+#include "git2.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <regex.h>
+
+#include <io.h>
+#include <direct.h>
+#ifdef GIT_THREADS
+ #include "win32/pthread.h"
+#endif
diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c
index d180285b8..1275d1620 100644
--- a/tests-clar/clar_helpers.c
+++ b/tests-clar/clar_helpers.c
@@ -48,6 +48,62 @@ void cl_git_rewritefile(const char *filename, const char *new_content)
cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_TRUNC);
}
+#ifdef GIT_WIN32
+
+#include "win32/utf-conv.h"
+
+char *cl_getenv(const char *name)
+{
+ wchar_t *name_utf16 = gitwin_to_utf16(name);
+ DWORD value_len, alloc_len;
+ wchar_t *value_utf16;
+ char *value_utf8;
+
+ cl_assert(name_utf16);
+ alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0);
+ if (alloc_len <= 0)
+ return NULL;
+
+ cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t)));
+
+ value_len = GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len);
+ cl_assert_equal_i(value_len, alloc_len - 1);
+
+ cl_assert(value_utf8 = gitwin_from_utf16(value_utf16));
+
+ git__free(value_utf16);
+
+ return value_utf8;
+}
+
+int cl_setenv(const char *name, const char *value)
+{
+ wchar_t *name_utf16 = gitwin_to_utf16(name);
+ wchar_t *value_utf16 = value ? gitwin_to_utf16(value) : NULL;
+
+ cl_assert(name_utf16);
+ cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16));
+
+ git__free(name_utf16);
+ git__free(value_utf16);
+
+ return 0;
+
+}
+#else
+
+#include <stdlib.h>
+char *cl_getenv(const char *name)
+{
+ return getenv(name);
+}
+
+int cl_setenv(const char *name, const char *value)
+{
+ return (value == NULL) ? unsetenv(name) : setenv(name, value, 1);
+}
+#endif
+
static const char *_cl_sandbox = NULL;
static git_repository *_cl_repo = NULL;
@@ -98,3 +154,28 @@ void cl_git_sandbox_cleanup(void)
_cl_sandbox = NULL;
}
}
+
+bool cl_toggle_filemode(const char *filename)
+{
+ struct stat st1, st2;
+
+ cl_must_pass(p_stat(filename, &st1));
+ cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
+ cl_must_pass(p_stat(filename, &st2));
+
+ return (st1.st_mode != st2.st_mode);
+}
+
+bool cl_is_chmod_supported(void)
+{
+ static int _is_supported = -1;
+
+ if (_is_supported < 0) {
+ cl_git_mkfile("filemode.t", "Test if filemode can be modified");
+ _is_supported = cl_toggle_filemode("filemode.t");
+ cl_must_pass(p_unlink("filemode.t"));
+ }
+
+ return _is_supported;
+}
+
diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h
index d250494f5..a3b03bbb3 100644
--- a/tests-clar/clar_libgit2.h
+++ b/tests-clar/clar_libgit2.h
@@ -40,6 +40,13 @@ void cl_git_append2file(const char *filename, const char *new_content);
void cl_git_rewritefile(const char *filename, const char *new_content);
void cl_git_write2file(const char *filename, const char *new_content, int mode);
+bool cl_toggle_filemode(const char *filename);
+bool cl_is_chmod_supported(void);
+
+/* Environment wrappers */
+char *cl_getenv(const char *name);
+int cl_setenv(const char *name, const char *value);
+
/* Git sandbox setup helpers */
git_repository *cl_git_sandbox_init(const char *sandbox);
diff --git a/tests-clar/core/env.c b/tests-clar/core/env.c
index 15d431f01..9361341eb 100644
--- a/tests-clar/core/env.c
+++ b/tests-clar/core/env.c
@@ -3,59 +3,6 @@
#include "path.h"
#ifdef GIT_WIN32
-
-#include "win32/utf-conv.h"
-
-static char *cl_getenv(const char *name)
-{
- wchar_t *name_utf16 = gitwin_to_utf16(name);
- DWORD value_len, alloc_len;
- wchar_t *value_utf16;
- char *value_utf8;
-
- cl_assert(name_utf16);
- alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0);
- if (alloc_len <= 0)
- return NULL;
-
- cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t)));
-
- value_len = GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len);
- cl_assert_equal_i(value_len, alloc_len - 1);
-
- cl_assert(value_utf8 = gitwin_from_utf16(value_utf16));
-
- git__free(value_utf16);
-
- return value_utf8;
-}
-
-static int cl_setenv(const char *name, const char *value)
-{
- wchar_t *name_utf16 = gitwin_to_utf16(name);
- wchar_t *value_utf16 = value ? gitwin_to_utf16(value) : NULL;
-
- cl_assert(name_utf16);
- cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16));
-
- git__free(name_utf16);
- git__free(value_utf16);
-
- return 0;
-
-}
-#else
-
-#include <stdlib.h>
-#define cl_getenv(n) getenv(n)
-
-static int cl_setenv(const char *name, const char *value)
-{
- return (value == NULL) ? unsetenv(name) : setenv(name, value, 1);
-}
-#endif
-
-#ifdef GIT_WIN32
static char *env_userprofile = NULL;
static char *env_programfiles = NULL;
#else
diff --git a/tests-clar/core/filebuf.c b/tests-clar/core/filebuf.c
index eab8a26eb..4451c01c7 100644
--- a/tests-clar/core/filebuf.c
+++ b/tests-clar/core/filebuf.c
@@ -8,7 +8,7 @@ void test_core_filebuf__0(void)
int fd;
char test[] = "test", testlock[] = "test.lock";
- fd = p_creat(testlock, 0744);
+ fd = p_creat(testlock, 0744); //-V536
cl_must_pass(fd);
cl_must_pass(p_close(fd));
@@ -27,7 +27,7 @@ void test_core_filebuf__1(void)
int fd;
char test[] = "test";
- fd = p_creat(test, 0666);
+ fd = p_creat(test, 0666); //-V536
cl_must_pass(fd);
cl_must_pass(p_write(fd, "libgit2 rocks\n", 14));
cl_must_pass(p_close(fd));
diff --git a/tests-clar/date/date.c b/tests-clar/date/date.c
new file mode 100644
index 000000000..88881d1e1
--- /dev/null
+++ b/tests-clar/date/date.c
@@ -0,0 +1,15 @@
+#include "clar_libgit2.h"
+
+#include "util.h"
+
+void test_date_date__overflow(void)
+{
+#ifdef __LP64__
+ git_time_t d2038, d2039;
+
+ /* This is expected to fail on a 32-bit machine. */
+ cl_git_pass(git__date_parse(&d2038, "2038-1-1"));
+ cl_git_pass(git__date_parse(&d2039, "2039-1-1"));
+ cl_assert(d2038 < d2039);
+#endif
+}
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index 8587be9b1..1d9f6121c 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -85,11 +85,16 @@ int diff_line_fn(
e->line_ctxt++;
break;
case GIT_DIFF_LINE_ADDITION:
- case GIT_DIFF_LINE_ADD_EOFNL:
e->line_adds++;
break;
+ case GIT_DIFF_LINE_ADD_EOFNL:
+ assert(0);
+ break;
case GIT_DIFF_LINE_DELETION:
+ e->line_dels++;
+ break;
case GIT_DIFF_LINE_DEL_EOFNL:
+ /* technically not a line delete, but we'll count it as such */
e->line_dels++;
break;
default:
diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c
index b932fa10e..4201ad2a7 100644
--- a/tests-clar/diff/tree.c
+++ b/tests-clar/diff/tree.c
@@ -116,7 +116,7 @@ void test_diff_tree__options(void)
{ 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
{ 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
{ 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
- { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 48, 3 },
+ { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 },
/* c vs d tests */
{ 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
{ 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
index 42152f1ad..0c17eeb4a 100644
--- a/tests-clar/diff/workdir.c
+++ b/tests-clar/diff/workdir.c
@@ -5,7 +5,6 @@ static git_repository *g_repo = NULL;
void test_diff_workdir__initialize(void)
{
- g_repo = cl_git_sandbox_init("status");
}
void test_diff_workdir__cleanup(void)
@@ -19,6 +18,8 @@ void test_diff_workdir__to_index(void)
git_diff_list *diff = NULL;
diff_expects exp;
+ g_repo = cl_git_sandbox_init("status");
+
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -59,13 +60,17 @@ void test_diff_workdir__to_tree(void)
/* grabbed a couple of commit oids from the history of the attr repo */
const char *a_commit = "26a125ee1bf"; /* the current HEAD */
const char *b_commit = "0017bd4ab1ec3"; /* the start */
- git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
- git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
+ git_tree *a, *b;
git_diff_options opts = {0};
git_diff_list *diff = NULL;
git_diff_list *diff2 = NULL;
diff_expects exp;
+ g_repo = cl_git_sandbox_init("status");
+
+ a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ b = resolve_commit_oid_to_tree(g_repo, b_commit);
+
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -87,12 +92,12 @@ void test_diff_workdir__to_tree(void)
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(exp.files == 14);
- cl_assert(exp.file_adds == 0);
- cl_assert(exp.file_dels == 4);
- cl_assert(exp.file_mods == 4);
- cl_assert(exp.file_ignored == 1);
- cl_assert(exp.file_untracked == 5);
+ cl_assert_equal_i(14, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(4, exp.file_dels);
+ cl_assert_equal_i(4, exp.file_mods);
+ cl_assert_equal_i(1, exp.file_ignored);
+ cl_assert_equal_i(5, exp.file_untracked);
/* Since there is no git diff equivalent, let's just assume that the
* text diffs produced by git_diff_foreach are accurate here. We will
@@ -115,19 +120,19 @@ void test_diff_workdir__to_tree(void)
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(exp.files == 15);
- cl_assert(exp.file_adds == 2);
- cl_assert(exp.file_dels == 5);
- cl_assert(exp.file_mods == 4);
- cl_assert(exp.file_ignored == 1);
- cl_assert(exp.file_untracked == 3);
+ cl_assert_equal_i(15, exp.files);
+ cl_assert_equal_i(2, exp.file_adds);
+ cl_assert_equal_i(5, exp.file_dels);
+ cl_assert_equal_i(4, exp.file_mods);
+ cl_assert_equal_i(1, exp.file_ignored);
+ cl_assert_equal_i(3, exp.file_untracked);
- cl_assert(exp.hunks == 11);
+ cl_assert_equal_i(11, exp.hunks);
- cl_assert(exp.lines == 17);
- cl_assert(exp.line_ctxt == 4);
- cl_assert(exp.line_adds == 8);
- cl_assert(exp.line_dels == 5);
+ cl_assert_equal_i(17, exp.lines);
+ cl_assert_equal_i(4, exp.line_ctxt);
+ cl_assert_equal_i(8, exp.line_adds);
+ cl_assert_equal_i(5, exp.line_dels);
git_diff_list_free(diff);
diff = NULL;
@@ -144,19 +149,19 @@ void test_diff_workdir__to_tree(void)
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(exp.files == 16);
- cl_assert(exp.file_adds == 5);
- cl_assert(exp.file_dels == 4);
- cl_assert(exp.file_mods == 3);
- cl_assert(exp.file_ignored == 1);
- cl_assert(exp.file_untracked == 3);
+ cl_assert_equal_i(16, exp.files);
+ cl_assert_equal_i(5, exp.file_adds);
+ cl_assert_equal_i(4, exp.file_dels);
+ cl_assert_equal_i(3, exp.file_mods);
+ cl_assert_equal_i(1, exp.file_ignored);
+ cl_assert_equal_i(3, exp.file_untracked);
- cl_assert(exp.hunks == 12);
+ cl_assert_equal_i(12, exp.hunks);
- cl_assert(exp.lines == 19);
- cl_assert(exp.line_ctxt == 3);
- cl_assert(exp.line_adds == 12);
- cl_assert(exp.line_dels == 4);
+ cl_assert_equal_i(19, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(12, exp.line_adds);
+ cl_assert_equal_i(4, exp.line_dels);
git_diff_list_free(diff);
@@ -171,6 +176,8 @@ void test_diff_workdir__to_index_with_pathspec(void)
diff_expects exp;
char *pathspec = NULL;
+ g_repo = cl_git_sandbox_init("status");
+
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -237,6 +244,241 @@ void test_diff_workdir__to_index_with_pathspec(void)
git_diff_list_free(diff);
}
+void test_diff_workdir__filemode_changes(void)
+{
+ git_config *cfg;
+ git_diff_list *diff = NULL;
+ diff_expects exp;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("issue_592");
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
+
+ /* test once with no mods */
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_list_free(diff);
+
+ /* chmod file and test again */
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_list_free(diff);
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+ git_config_free(cfg);
+}
+
+void test_diff_workdir__filemode_changes_with_filemode_false(void)
+{
+ git_config *cfg;
+ git_diff_list *diff = NULL;
+ diff_expects exp;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("issue_592");
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
+
+ /* test once with no mods */
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_list_free(diff);
+
+ /* chmod file and test again */
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_list_free(diff);
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+ git_config_free(cfg);
+}
+
+void test_diff_workdir__head_index_and_workdir_all_differ(void)
+{
+ git_diff_options opts = {0};
+ git_diff_list *diff_i2t = NULL, *diff_w2i = NULL;
+ diff_expects exp;
+ char *pathspec = "staged_changes_modified_file";
+ git_tree *tree;
+
+ /* For this file,
+ * - head->index diff has 1 line of context, 1 line of diff
+ * - index->workdir diff has 2 lines of context, 1 line of diff
+ * but
+ * - head->workdir diff has 1 line of context, 2 lines of diff
+ * Let's make sure the right one is returned from each fn.
+ */
+
+ g_repo = cl_git_sandbox_init("status");
+
+ tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
+
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t));
+ cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.hunks);
+ cl_assert_equal_i(2, exp.lines);
+ cl_assert_equal_i(1, exp.line_ctxt);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.hunks);
+ cl_assert_equal_i(3, exp.lines);
+ cl_assert_equal_i(2, exp.line_ctxt);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+
+ cl_git_pass(git_diff_merge(diff_i2t, diff_w2i));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.hunks);
+ cl_assert_equal_i(3, exp.lines);
+ cl_assert_equal_i(1, exp.line_ctxt);
+ cl_assert_equal_i(2, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+
+ git_diff_list_free(diff_i2t);
+ git_diff_list_free(diff_w2i);
+}
+
+void test_diff_workdir__eof_newline_changes(void)
+{
+ git_diff_options opts = {0};
+ git_diff_list *diff = NULL;
+ diff_expects exp;
+ char *pathspec = "current_file";
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.hunks);
+ cl_assert_equal_i(0, exp.lines);
+ cl_assert_equal_i(0, exp.line_ctxt);
+ cl_assert_equal_i(0, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+
+ git_diff_list_free(diff);
+
+ cl_git_append2file("status/current_file", "\n");
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.hunks);
+ cl_assert_equal_i(2, exp.lines);
+ cl_assert_equal_i(1, exp.line_ctxt);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+
+ git_diff_list_free(diff);
+
+ cl_git_rewritefile("status/current_file", "current_file");
+
+ cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(0, exp.file_adds);
+ cl_assert_equal_i(0, exp.file_dels);
+ cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.hunks);
+ cl_assert_equal_i(3, exp.lines);
+ cl_assert_equal_i(0, exp.line_ctxt);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(2, exp.line_dels);
+
+ git_diff_list_free(diff);
+}
+
/* PREPARATION OF TEST DATA
*
* Since there is no command line equivalent of git_diff_workdir_to_tree,
diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c
index 3436f8d1e..a535d6815 100644
--- a/tests-clar/index/tests.c
+++ b/tests-clar/index/tests.c
@@ -33,7 +33,7 @@ static void copy_file(const char *src, const char *dst)
cl_git_pass(git_futils_readbuffer(&source_buf, src));
- dst_fd = git_futils_creat_withpath(dst, 0777, 0666);
+ dst_fd = git_futils_creat_withpath(dst, 0777, 0666); //-V536
if (dst_fd < 0)
goto cleanup;
diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c
index 8cee7ce08..e66ea118f 100644
--- a/tests-clar/network/remotelocal.c
+++ b/tests-clar/network/remotelocal.c
@@ -107,7 +107,7 @@ void test_network_remotelocal__retrieve_advertised_references(void)
cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
- cl_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */
+ cl_assert_equal_i(how_many_refs, 20);
}
void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void)
@@ -121,7 +121,7 @@ void test_network_remotelocal__retrieve_advertised_references_from_spaced_reposi
cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
- cl_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */
+ cl_assert_equal_i(how_many_refs, 20);
git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */
remote = NULL;
diff --git a/tests-clar/object/tree/write.c b/tests-clar/object/tree/write.c
index 3911f6f0e..e6dc549a5 100644
--- a/tests-clar/object/tree/write.c
+++ b/tests-clar/object/tree/write.c
@@ -63,14 +63,14 @@ void test_object_tree_write__subtree(void)
//create subtree
cl_git_pass(git_treebuilder_create(&builder, NULL));
- cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644));
+ cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); //-V536
cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder));
git_treebuilder_free(builder);
// create parent tree
cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
cl_git_pass(git_treebuilder_create(&builder, tree));
- cl_git_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000));
+ cl_git_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); //-V536
cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder));
git_treebuilder_free(builder);
git_tree_free(tree);
diff --git a/tests-clar/refs/branches/listall.c b/tests-clar/refs/branches/listall.c
index 0a5634fb4..77f8270fb 100644
--- a/tests-clar/refs/branches/listall.c
+++ b/tests-clar/refs/branches/listall.c
@@ -30,22 +30,22 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count)
{
cl_git_pass(git_branch_list(&branch_list, repo, flags));
- cl_assert_equal_i(expected_count, branch_list.count);
+ cl_assert_equal_i(branch_list.count, expected_count);
}
void test_refs_branches_listall__retrieve_all_branches(void)
{
- assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 6 + 1);
+ assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 9);
}
void test_refs_branches_listall__retrieve_remote_branches(void)
{
- assert_retrieval(GIT_BRANCH_REMOTE, 1);
+ assert_retrieval(GIT_BRANCH_REMOTE, 2);
}
void test_refs_branches_listall__retrieve_local_branches(void)
{
- assert_retrieval(GIT_BRANCH_LOCAL, 6);
+ assert_retrieval(GIT_BRANCH_LOCAL, 7);
}
static void assert_branch_list_contains(git_strarray *branches, const char* expected_branch_name)
@@ -72,7 +72,7 @@ void test_refs_branches_listall__retrieve_remote_symbolic_HEAD_when_present(void
cl_git_pass(git_branch_list(&branch_list, repo, GIT_BRANCH_REMOTE));
- cl_assert_equal_i(2, branch_list.count);
- assert_branch_list_contains(&branch_list, "refs/remotes/nulltoken/HEAD");
- assert_branch_list_contains(&branch_list, "refs/remotes/nulltoken/master");
+ cl_assert_equal_i(3, branch_list.count);
+ assert_branch_list_contains(&branch_list, "remotes/nulltoken/HEAD");
+ assert_branch_list_contains(&branch_list, "remotes/nulltoken/master");
}
diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c
new file mode 100644
index 000000000..fda99e9da
--- /dev/null
+++ b/tests-clar/refs/revparse.c
@@ -0,0 +1,174 @@
+#include "clar_libgit2.h"
+
+#include "git2/revparse.h"
+
+static git_repository *g_repo;
+static git_object *g_obj;
+static char g_orig_tz[16] = {0};
+
+
+
+/* Helpers */
+static void test_object(const char *spec, const char *expected_oid)
+{
+ char objstr[64] = {0};
+
+ cl_git_pass(git_revparse_single(&g_obj, g_repo, spec));
+ git_oid_fmt(objstr, git_object_id(g_obj));
+ cl_assert_equal_s(objstr, expected_oid);
+
+ git_object_free(g_obj);
+ g_obj = NULL;
+}
+
+
+void test_refs_revparse__initialize(void)
+{
+ char *tz = cl_getenv("TZ");
+ if (tz)
+ strcpy(g_orig_tz, tz);
+ cl_setenv("TZ", "UTC");
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_revparse__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_obj = NULL;
+ cl_setenv("TZ", g_orig_tz);
+}
+
+
+void test_refs_revparse__nonexistant_object(void)
+{
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist^1"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist~2"));
+}
+
+void test_refs_revparse__shas(void)
+{
+ test_object("c47800c7266a2be04c571c04d5a6614691ea99bd", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("c47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+}
+
+void test_refs_revparse__head(void)
+{
+ test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__full_refs(void)
+{
+ test_object("refs/heads/master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("refs/heads/test", "e90810b8df3e80c413d903f631643c716887138d");
+ test_object("refs/tags/test", "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
+}
+
+void test_refs_revparse__partial_refs(void)
+{
+ test_object("point_to_blob", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+ test_object("packed-test", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
+ test_object("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+}
+
+void test_refs_revparse__describe_output(void)
+{
+ test_object("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("not-good", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__nth_parent(void)
+{
+ test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
+ test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__not_tag(void)
+{
+ test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+ test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__to_type(void)
+{
+ test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
+ test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}"));
+}
+
+void test_refs_revparse__linear_history(void)
+{
+ test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+}
+
+void test_refs_revparse__chaining(void)
+{
+ test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479");
+}
+
+void test_refs_revparse__reflog(void)
+{
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-xyz}"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-0}"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{1000}"));
+
+ test_object("@{-2}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("@{-1}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__revwalk(void)
+{
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "master^{/not found in any commit}"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "master^{/merge}"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, "master^{/((}"));
+
+ test_object("master^{/anoth}", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("master^{/Merge}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("br2^{/Merge}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object("master^{/fo.rth}", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+}
+
+void test_refs_revparse__date(void)
+{
+ test_object("HEAD@{10 years ago}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master@{2012-4-30 10:23:20 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master@{2012-4-30 18:24 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master@{2012-4-30 23:24 -0300}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+
+ /* Core git gives a65fedf, because they don't take time zones into account. */
+ test_object("master@{1335806640}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__colon(void)
+{
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/not found in any commit"));
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README"));
+
+ test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f");
+ test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b");
+ test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6");
+ test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd");
+ test_object(":/Merge", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9");
+}
diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c
index af54b2266..2e70c511e 100644
--- a/tests-clar/repo/init.c
+++ b/tests-clar/repo/init.c
@@ -166,14 +166,14 @@ void test_repo_init__additional_templates(void)
git_buf_free(&path);
}
-static void assert_config_entry_on_init(const char *config_key, int expected_value)
+static void assert_config_entry_on_init_bytype(const char *config_key, int expected_value, bool is_bare)
{
git_config *config;
int current_value;
cl_set_cleanup(&cleanup_repository, "config_entry");
- cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", 1));
+ cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", is_bare));
git_repository_config(&config, _repo);
if (expected_value >= 0) {
@@ -189,6 +189,14 @@ static void assert_config_entry_on_init(const char *config_key, int expected_val
git_config_free(config);
}
+static void assert_config_entry_on_init(const char *config_key, int expected_value)
+{
+ assert_config_entry_on_init_bytype(config_key, expected_value, true);
+ git_repository_free(_repo);
+
+ assert_config_entry_on_init_bytype(config_key, expected_value, false);
+}
+
void test_repo_init__detect_filemode(void)
{
#ifdef GIT_WIN32
@@ -233,3 +241,9 @@ void test_repo_init__reinit_doesnot_overwrite_ignorecase(void)
git_config_free(config);
}
+
+void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void)
+{
+ assert_config_entry_on_init_bytype("core.logallrefupdates", GIT_ENOTFOUND, true);
+ assert_config_entry_on_init_bytype("core.logallrefupdates", true, false);
+}
diff --git a/tests-clar/reset/mixed.c b/tests-clar/reset/mixed.c
new file mode 100644
index 000000000..7cfff65d4
--- /dev/null
+++ b/tests-clar/reset/mixed.c
@@ -0,0 +1,47 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "reset_helpers.h"
+#include "path.h"
+
+static git_repository *repo;
+static git_object *target;
+
+void test_reset_mixed__initialize(void)
+{
+ repo = cl_git_sandbox_init("attr");
+ target = NULL;
+}
+
+void test_reset_mixed__cleanup(void)
+{
+ git_object_free(target);
+ cl_git_sandbox_cleanup();
+}
+
+void test_reset_mixed__cannot_reset_in_a_bare_repository(void)
+{
+ git_repository *bare;
+
+ cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git")));
+ cl_assert(git_repository_is_bare(bare) == true);
+
+ retrieve_target_from_oid(&target, bare, KNOWN_COMMIT_IN_BARE_REPO);
+
+ cl_git_fail(git_reset(bare, target, GIT_RESET_MIXED));
+
+ git_repository_free(bare);
+}
+
+void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void)
+{
+ unsigned int status;
+
+ cl_git_pass(git_status_file(&status, repo, "macro_bad"));
+ cl_assert(status == GIT_STATUS_CURRENT);
+ retrieve_target_from_oid(&target, repo, "605812ab7fe421fdd325a935d35cb06a9234a7d7");
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED));
+
+ cl_git_pass(git_status_file(&status, repo, "macro_bad"));
+ cl_assert(status == GIT_STATUS_WT_NEW);
+}
diff --git a/tests-clar/reset/reset_helpers.c b/tests-clar/reset/reset_helpers.c
new file mode 100644
index 000000000..17edca4e9
--- /dev/null
+++ b/tests-clar/reset/reset_helpers.c
@@ -0,0 +1,10 @@
+#include "clar_libgit2.h"
+#include "reset_helpers.h"
+
+void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha)
+{
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid, sha));
+ cl_git_pass(git_object_lookup(object_out, repo, &oid, GIT_OBJ_ANY));
+}
diff --git a/tests-clar/reset/reset_helpers.h b/tests-clar/reset/reset_helpers.h
new file mode 100644
index 000000000..5dbe9d2c7
--- /dev/null
+++ b/tests-clar/reset/reset_helpers.h
@@ -0,0 +1,6 @@
+#include "common.h"
+
+#define KNOWN_COMMIT_IN_BARE_REPO "e90810b8df3e80c413d903f631643c716887138d"
+#define KNOWN_COMMIT_IN_ATTR_REPO "217878ab49e1314388ea2e32dc6fdb58a1b969e0"
+
+extern void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha);
diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c
new file mode 100644
index 000000000..3200c1591
--- /dev/null
+++ b/tests-clar/reset/soft.c
@@ -0,0 +1,102 @@
+#include "clar_libgit2.h"
+#include "reset_helpers.h"
+
+static git_repository *repo;
+static git_object *target;
+
+void test_reset_soft__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_reset_soft__cleanup(void)
+{
+ git_object_free(target);
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_reset_soft(bool should_be_detached)
+{
+ git_oid oid;
+
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+ cl_git_fail(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+
+ retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO);
+
+ cl_assert(git_repository_head_detached(repo) == should_be_detached);
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT));
+
+ cl_assert(git_repository_head_detached(repo) == should_be_detached);
+
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+}
+
+void test_reset_soft__can_reset_the_non_detached_Head_to_the_specified_commit(void)
+{
+ assert_reset_soft(false);
+}
+
+static void detach_head(void)
+{
+ git_reference *head;
+ git_oid oid;
+
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+
+ cl_git_pass(git_reference_create_oid(&head, repo, "HEAD", &oid, true));
+ git_reference_free(head);
+}
+
+void test_reset_soft__can_reset_the_detached_Head_to_the_specified_commit(void)
+{
+ detach_head();
+
+ assert_reset_soft(true);
+}
+
+void test_reset_soft__resetting_to_the_commit_pointed_at_by_the_Head_does_not_change_the_target_of_the_Head(void)
+{
+ git_oid oid;
+ char raw_head_oid[GIT_OID_HEXSZ + 1];
+
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+ git_oid_fmt(raw_head_oid, &oid);
+ raw_head_oid[GIT_OID_HEXSZ] = '\0';
+
+ retrieve_target_from_oid(&target, repo, raw_head_oid);
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT));
+
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, raw_head_oid));
+}
+
+void test_reset_soft__resetting_to_a_tag_sets_the_Head_to_the_peeled_commit(void)
+{
+ git_oid oid;
+
+ /* b25fa35 is a tag, pointing to another tag which points to commit e90810b */
+ retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT));
+
+ cl_assert(git_repository_head_detached(repo) == false);
+ cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+}
+
+void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void)
+{
+ /* 53fc32d is the tree of commit e90810b */
+ retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016");
+
+ cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT));
+ git_object_free(target);
+
+ /* 521d87c is an annotated tag pointing to a blob */
+ retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91");
+ cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT));
+}
diff --git a/tests-clar/resources/filemodes/.gitted/HEAD b/tests-clar/resources/filemodes/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/filemodes/.gitted/config b/tests-clar/resources/filemodes/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests-clar/resources/filemodes/.gitted/description b/tests-clar/resources/filemodes/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample b/tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
new file mode 100755
index 000000000..b58d1184a
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
diff --git a/tests-clar/resources/filemodes/.gitted/index b/tests-clar/resources/filemodes/.gitted/index
new file mode 100644
index 000000000..b1b175a9b
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/index
Binary files differ
diff --git a/tests-clar/resources/filemodes/.gitted/info/exclude b/tests-clar/resources/filemodes/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests-clar/resources/filemodes/.gitted/logs/HEAD b/tests-clar/resources/filemodes/.gitted/logs/HEAD
new file mode 100644
index 000000000..1cb6a84c1
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
diff --git a/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master b/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..1cb6a84c1
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
diff --git a/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a b/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
new file mode 100644
index 000000000..cbd2b557a
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
Binary files differ
diff --git a/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 b/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
new file mode 100644
index 000000000..a9eaf2cba
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
Binary files differ
diff --git a/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 b/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
new file mode 100644
index 000000000..98066029c
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
Binary files differ
diff --git a/tests-clar/resources/filemodes/.gitted/refs/heads/master b/tests-clar/resources/filemodes/.gitted/refs/heads/master
new file mode 100644
index 000000000..9822d2d3f
--- /dev/null
+++ b/tests-clar/resources/filemodes/.gitted/refs/heads/master
@@ -0,0 +1 @@
+9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
diff --git a/tests-clar/resources/filemodes/exec_off b/tests-clar/resources/filemodes/exec_off
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_off
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off2on_staged b/tests-clar/resources/filemodes/exec_off2on_staged
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_off2on_staged
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off2on_workdir b/tests-clar/resources/filemodes/exec_off2on_workdir
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_off2on_workdir
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off_untracked b/tests-clar/resources/filemodes/exec_off_untracked
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_off_untracked
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on b/tests-clar/resources/filemodes/exec_on
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_on
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on2off_staged b/tests-clar/resources/filemodes/exec_on2off_staged
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_on2off_staged
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on2off_workdir b/tests-clar/resources/filemodes/exec_on2off_workdir
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_on2off_workdir
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on_untracked b/tests-clar/resources/filemodes/exec_on_untracked
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests-clar/resources/filemodes/exec_on_untracked
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config
index 1a5aacdfa..b4fdac6c2 100644
--- a/tests-clar/resources/testrepo.git/config
+++ b/tests-clar/resources/testrepo.git/config
@@ -6,3 +6,7 @@
[remote "test"]
url = git://github.com/libgit2/libgit2
fetch = +refs/heads/*:refs/remotes/test/*
+
+[branch "master"]
+ remote = test
+ merge = refs/heads/master
diff --git a/tests-clar/resources/testrepo.git/logs/HEAD b/tests-clar/resources/testrepo.git/logs/HEAD
new file mode 100644
index 000000000..b16c9b313
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/logs/HEAD
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0900 commit:
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0900 checkout: moving from master to br2
+c47800c7266a2be04c571c04d5a6614691ea99bd a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0900 commit: checking in
+a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806621 -0900 checkout: moving from br2 to master
diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/br2 b/tests-clar/resources/testrepo.git/logs/refs/heads/br2
new file mode 100644
index 000000000..4e27f6b8d
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/logs/refs/heads/br2
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0700 branch: Created from refs/remotes/origin/br2
+a4a7dce85cf63874e984719f4fdd239f5145052f a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0700 commit: checking in
diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/master b/tests-clar/resources/testrepo.git/logs/refs/heads/master
new file mode 100644
index 000000000..e1c729a45
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0800 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0800 commit: checking in
diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/not-good b/tests-clar/resources/testrepo.git/logs/refs/heads/not-good
new file mode 100644
index 000000000..bfbeacb8a
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/logs/refs/heads/not-good
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1336761944 -0700 branch: Created from master
diff --git a/tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD b/tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..f1aac6d0f
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
diff --git a/tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
new file mode 100644
index 000000000..71019a636
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
@@ -0,0 +1,2 @@
+xM F]s41x(IKݽ/_P@!8)es
+ N&FGSƄh{+CZzvF7Z-kx\[P8GK/^ l>.4 \ No newline at end of file
diff --git a/tests-clar/resources/testrepo.git/refs/heads/not-good b/tests-clar/resources/testrepo.git/refs/heads/not-good
new file mode 100644
index 000000000..3d8f0a402
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/refs/heads/not-good
@@ -0,0 +1 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests-clar/resources/testrepo.git/refs/remotes/test/master b/tests-clar/resources/testrepo.git/refs/remotes/test/master
new file mode 100644
index 000000000..9536ad89c
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/refs/remotes/test/master
@@ -0,0 +1 @@
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644
diff --git a/tests-clar/resources/testrepo.git/refs/tags/hard_tag b/tests-clar/resources/testrepo.git/refs/tags/hard_tag
new file mode 100644
index 000000000..59ce65649
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/refs/tags/hard_tag
@@ -0,0 +1 @@
+849a5e34a26815e821f865b8479f5815a47af0fe
diff --git a/tests-clar/resources/testrepo.git/refs/tags/wrapped_tag b/tests-clar/resources/testrepo.git/refs/tags/wrapped_tag
new file mode 100644
index 000000000..59ce65649
--- /dev/null
+++ b/tests-clar/resources/testrepo.git/refs/tags/wrapped_tag
@@ -0,0 +1 @@
+849a5e34a26815e821f865b8479f5815a47af0fe
diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c
index 369b25bda..0384306c1 100644
--- a/tests-clar/status/ignore.c
+++ b/tests-clar/status/ignore.c
@@ -131,3 +131,17 @@ void test_status_ignore__empty_repo_with_gitignore_rewrite(void)
cl_assert(ignored);
}
+void test_status_ignore__ignore_pattern_contains_space(void)
+{
+ unsigned int flags;
+ const mode_t mode = 0777;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n");
+
+ cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode));
+ cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!");
+
+ cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt"));
+ cl_assert(flags == GIT_STATUS_WT_NEW);
+}
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index b3ebdb781..3670b72a8 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -581,3 +581,69 @@ void test_status_worktree__space_in_filename(void)
git_index_free(index);
git_repository_free(repo);
}
+
+static const char *filemode_paths[] = {
+ "exec_off",
+ "exec_off2on_staged",
+ "exec_off2on_workdir",
+ "exec_off_untracked",
+ "exec_on",
+ "exec_on2off_staged",
+ "exec_on2off_workdir",
+ "exec_on_untracked",
+};
+
+static unsigned int filemode_statuses[] = {
+ GIT_STATUS_CURRENT,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_CURRENT,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+};
+
+static const size_t filemode_count = 8;
+
+void test_status_worktree__filemode_changes(void)
+{
+ git_repository *repo = cl_git_sandbox_init("filemodes");
+ status_entry_counts counts;
+ git_status_options opts;
+ git_config *cfg;
+
+ /* overwrite stored filemode with platform appropriate value */
+ cl_git_pass(git_repository_config(&cfg, repo));
+ if (cl_is_chmod_supported())
+ cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
+ else {
+ unsigned int i;
+ cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
+
+ /* won't trust filesystem mode diffs, so these will appear unchanged */
+ for (i = 0; i < filemode_count; ++i)
+ if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
+ filemode_statuses[i] = GIT_STATUS_CURRENT;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = filemode_count;
+ counts.expected_paths = filemode_paths;
+ counts.expected_statuses = filemode_statuses;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+ git_config_free(cfg);
+}