mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-02 12:29:08 +00:00
Merge remote-tracking branch 'source/development' into development
This commit is contained in:
commit
a21bb1aa33
1
AUTHORS
1
AUTHORS
@ -6,6 +6,7 @@ Alexei Sholik
|
||||
Andreas Ericsson
|
||||
Ankur Sethi
|
||||
Ben Noordhuis
|
||||
Ben Straub
|
||||
Benjamin C Meyer
|
||||
Brian Lopez
|
||||
Carlos Martín Nieto
|
||||
|
@ -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)")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 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.
|
||||
* 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.
|
||||
*
|
||||
* 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,
|
||||
|
@ -19,6 +19,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
|
||||
*
|
||||
|
44
include/git2/reset.h
Normal file
44
include/git2/reset.h
Normal file
@ -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
|
36
include/git2/revparse.h
Normal file
36
include/git2/revparse.h
Normal file
@ -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
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
62
src/commit.c
62
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;
|
||||
|
||||
|
876
src/date.c
Normal file
876
src/date.c
Normal file
@ -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, ×tamp, &offset)) {
|
||||
*out = timestamp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
p_gettimeofday(&tv, NULL);
|
||||
*out = approxidate_str(date, &tv, &error_ret);
|
||||
return error_ret;
|
||||
}
|
104
src/diff.c
104
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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
10
src/path.c
10
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
59
src/refs.c
59
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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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? */
|
||||
|
103
src/reset.c
Normal file
103
src/reset.c
Normal file
@ -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;
|
||||
}
|
748
src/revparse.c
Normal file
748
src/revparse.c
Normal file
@ -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(®ex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED);
|
||||
if (regex_error != 0) {
|
||||
giterr_set_regex(®ex, regex_error);
|
||||
return 1; /* To be safe */
|
||||
}
|
||||
retcode = regexec(®ex, spec, 0, NULL, 0);
|
||||
regfree(®ex);
|
||||
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(®ex, "checkout: moving from (.*) to .*", REG_EXTENDED);
|
||||
if (regex_error != 0) {
|
||||
giterr_set_regex(®ex, 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(®ex, 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(®ex);
|
||||
}
|
||||
}
|
||||
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(×tamp, 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
17
src/util.h
17
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__ */
|
||||
|
@ -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;
|
||||
}
|
||||
|
1
src/win32/precompiled.c
Normal file
1
src/win32/precompiled.c
Normal file
@ -0,0 +1 @@
|
||||
#include "precompiled.h"
|
19
src/win32/precompiled.h
Normal file
19
src/win32/precompiled.h
Normal file
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -2,59 +2,6 @@
|
||||
#include "fileops.h"
|
||||
#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;
|
||||
|
@ -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));
|
||||
|
15
tests-clar/date/date.c
Normal file
15
tests-clar/date/date.c
Normal file
@ -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
|
||||
}
|
@ -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:
|
||||
|
@ -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 },
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
174
tests-clar/refs/revparse.c
Normal file
174
tests-clar/refs/revparse.c
Normal file
@ -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");
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
47
tests-clar/reset/mixed.c
Normal file
47
tests-clar/reset/mixed.c
Normal file
@ -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);
|
||||
}
|
10
tests-clar/reset/reset_helpers.c
Normal file
10
tests-clar/reset/reset_helpers.c
Normal file
@ -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));
|
||||
}
|
6
tests-clar/reset/reset_helpers.h
Normal file
6
tests-clar/reset/reset_helpers.h
Normal file
@ -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);
|
102
tests-clar/reset/soft.c
Normal file
102
tests-clar/reset/soft.c
Normal file
@ -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));
|
||||
}
|
1
tests-clar/resources/filemodes/.gitted/HEAD
Normal file
1
tests-clar/resources/filemodes/.gitted/HEAD
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
6
tests-clar/resources/filemodes/.gitted/config
Normal file
6
tests-clar/resources/filemodes/.gitted/config
Normal file
@ -0,0 +1,6 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
1
tests-clar/resources/filemodes/.gitted/description
Normal file
1
tests-clar/resources/filemodes/.gitted/description
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
24
tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
Executable file
24
tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
Executable file
@ -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
|
||||
}
|
BIN
tests-clar/resources/filemodes/.gitted/index
Normal file
BIN
tests-clar/resources/filemodes/.gitted/index
Normal file
Binary file not shown.
6
tests-clar/resources/filemodes/.gitted/info/exclude
Normal file
6
tests-clar/resources/filemodes/.gitted/info/exclude
Normal file
@ -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]
|
||||
# *~
|
1
tests-clar/resources/filemodes/.gitted/logs/HEAD
Normal file
1
tests-clar/resources/filemodes/.gitted/logs/HEAD
Normal file
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
|
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
tests-clar/resources/filemodes/.gitted/refs/heads/master
Normal file
1
tests-clar/resources/filemodes/.gitted/refs/heads/master
Normal file
@ -0,0 +1 @@
|
||||
9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
|
1
tests-clar/resources/filemodes/exec_off
Normal file
1
tests-clar/resources/filemodes/exec_off
Normal file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_off2on_staged
Executable file
1
tests-clar/resources/filemodes/exec_off2on_staged
Executable file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_off2on_workdir
Executable file
1
tests-clar/resources/filemodes/exec_off2on_workdir
Executable file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_off_untracked
Normal file
1
tests-clar/resources/filemodes/exec_off_untracked
Normal file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_on
Executable file
1
tests-clar/resources/filemodes/exec_on
Executable file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_on2off_staged
Normal file
1
tests-clar/resources/filemodes/exec_on2off_staged
Normal file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_on2off_workdir
Normal file
1
tests-clar/resources/filemodes/exec_on2off_workdir
Normal file
@ -0,0 +1 @@
|
||||
Howdy
|
1
tests-clar/resources/filemodes/exec_on_untracked
Executable file
1
tests-clar/resources/filemodes/exec_on_untracked
Executable file
@ -0,0 +1 @@
|
||||
Howdy
|
@ -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
|
||||
|
5
tests-clar/resources/testrepo.git/logs/HEAD
Normal file
5
tests-clar/resources/testrepo.git/logs/HEAD
Normal file
@ -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
|
2
tests-clar/resources/testrepo.git/logs/refs/heads/br2
Normal file
2
tests-clar/resources/testrepo.git/logs/refs/heads/br2
Normal file
@ -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
|
2
tests-clar/resources/testrepo.git/logs/refs/heads/master
Normal file
2
tests-clar/resources/testrepo.git/logs/refs/heads/master
Normal file
@ -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
|
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1336761944 -0700 branch: Created from master
|
@ -0,0 +1 @@
|
||||
0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
|
@ -0,0 +1,2 @@
|
||||
xŒM F]s鈆Ÿ41ÆxÝ(I‹ÁéÂÛKݽ/_ÞãP@¡ÚÕø¢<C3B8>!8›)es
|
||||
” ¥N&FGSÆ„¹hÑ{+ßC<>ç‰÷ÆZzvØF¡7ZàÎ-¬Îñó‡k™x\ã¡[PÆ8ï´ôGØK/¥<10>^©l<>Ê>.4
|
1
tests-clar/resources/testrepo.git/refs/heads/not-good
Normal file
1
tests-clar/resources/testrepo.git/refs/heads/not-good
Normal file
@ -0,0 +1 @@
|
||||
a65fedf39aefe402d3bb6e24df4d4f5fe4547750
|
@ -0,0 +1 @@
|
||||
be3563ae3f795b2b4353bcce3a527ad0a4f7f644
|
1
tests-clar/resources/testrepo.git/refs/tags/hard_tag
Normal file
1
tests-clar/resources/testrepo.git/refs/tags/hard_tag
Normal file
@ -0,0 +1 @@
|
||||
849a5e34a26815e821f865b8479f5815a47af0fe
|
1
tests-clar/resources/testrepo.git/refs/tags/wrapped_tag
Normal file
1
tests-clar/resources/testrepo.git/refs/tags/wrapped_tag
Normal file
@ -0,0 +1 @@
|
||||
849a5e34a26815e821f865b8479f5815a47af0fe
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user