Commit a4827a5b by Ben Straub

Merge remote-tracking branch 'upstream/development' into test-merge

parents 15445f9e 31637cd5
...@@ -6,8 +6,10 @@ language: erlang ...@@ -6,8 +6,10 @@ language: erlang
# Settings to try # Settings to try
env: env:
- OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" - CC=gcc OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release"
- OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" - CC=clang OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release"
- CC=gcc OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON"
- CC=clang OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON"
- CC=i586-mingw32msvc-gcc OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" - CC=i586-mingw32msvc-gcc OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON"
# Make sure CMake is installed # Make sure CMake is installed
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h> #include <pthread.h>
#include <unistd.h>
struct dl_data { struct dl_data {
git_remote *remote; git_remote *remote;
...@@ -39,7 +40,7 @@ exit: ...@@ -39,7 +40,7 @@ exit:
pthread_exit(&data->ret); pthread_exit(&data->ret);
} }
int update_cb(const char *refname, const git_oid *a, const git_oid *b) int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
{ {
const char *action; const char *action;
char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1];
...@@ -65,6 +66,7 @@ int fetch(git_repository *repo, int argc, char **argv) ...@@ -65,6 +66,7 @@ int fetch(git_repository *repo, int argc, char **argv)
git_indexer_stats stats; git_indexer_stats stats;
pthread_t worker; pthread_t worker;
struct dl_data data; struct dl_data data;
git_remote_callbacks callbacks;
// Figure out whether it's a named remote or a URL // Figure out whether it's a named remote or a URL
printf("Fetching %s\n", argv[1]); printf("Fetching %s\n", argv[1]);
...@@ -73,6 +75,11 @@ int fetch(git_repository *repo, int argc, char **argv) ...@@ -73,6 +75,11 @@ int fetch(git_repository *repo, int argc, char **argv)
return -1; return -1;
} }
// Set up the callbacks (only update_tips for now)
memset(&callbacks, 0, sizeof(callbacks));
callbacks.update_tips = &update_cb;
git_remote_set_callbacks(remote, &callbacks);
// Set up the information for the background worker thread // Set up the information for the background worker thread
data.remote = remote; data.remote = remote;
data.bytes = &bytes; data.bytes = &bytes;
...@@ -101,7 +108,7 @@ int fetch(git_repository *repo, int argc, char **argv) ...@@ -101,7 +108,7 @@ int fetch(git_repository *repo, int argc, char **argv)
// right commits. This may be needed even if there was no packfile // right commits. This may be needed even if there was no packfile
// to download, which can happen e.g. when the branches have been // to download, which can happen e.g. when the branches have been
// changed but all the neede objects are available locally. // changed but all the neede objects are available locally.
if (git_remote_update_tips(remote, update_cb) < 0) if (git_remote_update_tips(remote) < 0)
return -1; return -1;
git_remote_free(remote); git_remote_free(remote);
......
...@@ -26,9 +26,9 @@ GIT_BEGIN_DECL ...@@ -26,9 +26,9 @@ GIT_BEGIN_DECL
* this target commit. If `force` is true and a reference * this target commit. If `force` is true and a reference
* already exists with the given name, it'll be replaced. * already exists with the given name, it'll be replaced.
* *
* @param oid_out Pointer where to store the OID of the target commit. * The returned reference must be freed by the user.
* *
* @param repo Repository where to store the branch. * @param branch_out Pointer where to store the underlying reference.
* *
* @param branch_name Name for the branch; this name is * @param branch_name Name for the branch; this name is
* validated for consistency. It should also not conflict with * validated for consistency. It should also not conflict with
...@@ -46,7 +46,7 @@ GIT_BEGIN_DECL ...@@ -46,7 +46,7 @@ GIT_BEGIN_DECL
* pointing to the provided target commit. * pointing to the provided target commit.
*/ */
GIT_EXTERN(int) git_branch_create( GIT_EXTERN(int) git_branch_create(
git_oid *oid_out, git_reference **branch_out,
git_repository *repo, git_repository *repo,
const char *branch_name, const char *branch_name,
const git_object *target, const git_object *target,
...@@ -97,27 +97,62 @@ GIT_EXTERN(int) git_branch_foreach( ...@@ -97,27 +97,62 @@ GIT_EXTERN(int) git_branch_foreach(
); );
/** /**
* Move/rename an existing branch reference. * Move/rename an existing local branch reference.
* *
* @param repo Repository where lives the branch. * @param branch Current underlying reference of the branch.
*
* @param old_branch_name Current name of the branch to be moved;
* this name is validated for consistency.
* *
* @param new_branch_name Target name of the branch once the move * @param new_branch_name Target name of the branch once the move
* is performed; this name is validated for consistency. * is performed; this name is validated for consistency.
* *
* @param force Overwrite existing branch. * @param force Overwrite existing branch.
* *
* @return 0 on success, GIT_ENOTFOUND if the branch * @return 0 on success, or an error code.
* doesn't exist or an error code.
*/ */
GIT_EXTERN(int) git_branch_move( GIT_EXTERN(int) git_branch_move(
git_repository *repo, git_reference *branch,
const char *old_branch_name,
const char *new_branch_name, const char *new_branch_name,
int force); int force);
/**
* Lookup a branch by its name in a repository.
*
* The generated reference must be freed by the user.
*
* @param branch_out pointer to the looked-up branch reference
*
* @param repo the repository to look up the branch
*
* @param branch_name Name of the branch to be looked-up;
* this name is validated for consistency.
*
* @param branch_type Type of the considered branch. This should
* be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.
*
* @return 0 on success; GIT_ENOTFOUND when no matching branch
* exists, otherwise an error code.
*/
GIT_EXTERN(int) git_branch_lookup(
git_reference **branch_out,
git_repository *repo,
const char *branch_name,
git_branch_t branch_type);
/**
* Return the reference supporting the remote tracking branch,
* given a local branch reference.
*
* @param tracking_out Pointer where to store the retrieved
* reference.
*
* @param branch Current underlying reference of the branch.
*
* @return 0 on success; GIT_ENOTFOUND when no remote tracking
* reference exists, otherwise an error code.
*/
GIT_EXTERN(int) git_branch_tracking(
git_reference **tracking_out,
git_reference *branch);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif
...@@ -46,6 +46,7 @@ enum { ...@@ -46,6 +46,7 @@ enum {
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
}; };
/** /**
......
...@@ -167,6 +167,23 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type); ...@@ -167,6 +167,23 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type);
*/ */
GIT_EXTERN(size_t) git_object__size(git_otype type); GIT_EXTERN(size_t) git_object__size(git_otype type);
/**
* Recursively peel an object until an object of the specified
* type is met
*
* The retrieved `peeled` object is owned by the repository
* and should be closed with the `git_object_free` method.
*
* @param peeled Pointer to the peeled git_object
* @param object The object to be processed
* @param target_type The type of the requested object
* @return 0 or an error code
*/
GIT_EXTERN(int) git_object_peel(
git_object **peeled,
git_object *object,
git_otype target_type);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
......
...@@ -23,6 +23,10 @@ GIT_BEGIN_DECL ...@@ -23,6 +23,10 @@ GIT_BEGIN_DECL
/** /**
* Read the reflog for the given reference * Read the reflog for the given reference
* *
* If there is no reflog file for the given
* reference yet, an empty reflog object will
* be returned.
*
* The reflog must be freed manually by using * The reflog must be freed manually by using
* git_reflog_free(). * git_reflog_free().
* *
...@@ -33,22 +37,26 @@ GIT_BEGIN_DECL ...@@ -33,22 +37,26 @@ GIT_BEGIN_DECL
GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref);
/** /**
* Write a new reflog for the given reference * Write an existing in-memory reflog object back to disk
* * using an atomic file lock.
* If there is no reflog file for the given
* reference yet, it will be created.
* *
* `oid_old` may be NULL in case it's a new reference. * @param reflog an existing reflog object
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reflog_write(git_reflog *reflog);
/**
* Add a new entry to the reflog.
* *
* `msg` is optional and can be NULL. * `msg` is optional and can be NULL.
* *
* @param ref the changed reference * @param reflog an existing reflog object
* @param oid_old the OID the reference was pointing to * @param new_oid the OID the reference is now pointing to
* @param committer the signature of the committer * @param committer the signature of the committer
* @param msg the reflog message * @param msg the reflog message
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg);
/** /**
* Rename the reflog for the given reference * Rename the reflog for the given reference
...@@ -87,6 +95,26 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog); ...@@ -87,6 +95,26 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog);
GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx); GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx);
/** /**
* Remove an entry from the reflog by its index
*
* To ensure there's no gap in the log history, set the `rewrite_previosu_entry` to 1.
* When deleting entry `n`, member old_oid of entry `n-1` (if any) will be updated with
* the value of memeber new_oid of entry `n+1`.
*
* @param reflog a previously loaded reflog.
*
* @param idx the position of the entry to remove.
*
* @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise.
*
* @return 0 on success or an error code.
*/
GIT_EXTERN(int) git_reflog_drop(
git_reflog *reflog,
unsigned int idx,
int rewrite_previous_entry);
/**
* Get the old oid * Get the old oid
* *
* @param entry a reflog entry * @param entry a reflog entry
......
...@@ -363,26 +363,15 @@ GIT_EXTERN(int) git_reference_foreach_glob( ...@@ -363,26 +363,15 @@ GIT_EXTERN(int) git_reference_foreach_glob(
*/ */
GIT_EXTERN(int) git_reference_has_log(git_reference *ref); GIT_EXTERN(int) git_reference_has_log(git_reference *ref);
/** /**
* Return the reference supporting the remote tracking branch, * Check if a reference is a local branch.
* given a reference branch.
*
* The input reference has to be located in the `refs/heads`
* namespace.
*
* @param tracking_ref Pointer where to store the retrieved
* reference.
* *
* @param branch_ref A git local branch reference. * @param ref A git reference
* *
* @return 0 on success; GIT_ENOTFOUND when no remote tracking * @return 1 when the reference lives in the refs/heads
* reference exists, otherwise an error code. * namespace; 0 otherwise.
*/ */
GIT_EXTERN(int) git_reference_remote_tracking_from_branch( GIT_EXTERN(int) git_reference_is_branch(git_reference *ref);
git_reference **tracking_ref,
git_reference *branch_ref
);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
......
...@@ -80,6 +80,36 @@ GIT_EXTERN(const char *) git_remote_name(git_remote *remote); ...@@ -80,6 +80,36 @@ GIT_EXTERN(const char *) git_remote_name(git_remote *remote);
GIT_EXTERN(const char *) git_remote_url(git_remote *remote); GIT_EXTERN(const char *) git_remote_url(git_remote *remote);
/** /**
* Get the remote's url for pushing
*
* @param remote the remote
* @return a pointer to the url or NULL if no special url for pushing is set
*/
GIT_EXTERN(const char *) git_remote_pushurl(git_remote *remote);
/**
* Set the remote's url
*
* Existing connections will not be updated.
*
* @param remote the remote
* @param url the url to set
* @return 0 or an error value
*/
GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url);
/**
* Set the remote's url for pushing
*
* Existing connections will not be updated.
*
* @param remote the remote
* @param url the url to set or NULL to clear the pushurl
* @return 0 or an error value
*/
GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url);
/**
* Set the remote's fetch refspec * Set the remote's fetch refspec
* *
* @param remote the remote * @param remote the remote
...@@ -190,7 +220,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote); ...@@ -190,7 +220,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote);
* @param remote the remote to update * @param remote the remote to update
* @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value
*/ */
GIT_EXTERN(int) git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)); GIT_EXTERN(int) git_remote_update_tips(git_remote *remote);
/** /**
* Return whether a string is a valid remote URL * Return whether a string is a valid remote URL
...@@ -238,6 +268,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha ...@@ -238,6 +268,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
/**
* Argument to the completion callback which tells it which operation
* finished.
*/
typedef enum git_remote_completion_type {
GIT_REMOTE_COMPLETION_DOWNLOAD,
GIT_REMOTE_COMPLETION_INDEXING,
GIT_REMOTE_COMPLETION_ERROR,
} git_remote_completion_type;
/**
* The callback settings structure
*
* Set the calbacks to be called by the remote.
*/
struct git_remote_callbacks {
int (*progress)(const char *str, void *data);
int (*completion)(git_remote_completion_type type, void *data);
int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
void *data;
};
/**
* Set the callbacks for a remote
*
* Note that the remote keeps its own copy of the data and you need to
* call this function again if you want to change the callbacks.
*
* @param remote the remote to configure
* @param callbacks a pointer to the user's callback settings
*/
GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif
...@@ -96,6 +96,8 @@ typedef enum { ...@@ -96,6 +96,8 @@ typedef enum {
* the top-level directory will be included (with a trailing * the top-level directory will be included (with a trailing
* slash on the entry name). Given this flag, the directory * slash on the entry name). Given this flag, the directory
* itself will not be included, but all the files in it will. * itself will not be included, but all the files in it will.
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given
* path will be treated as a literal path, and not as a pathspec.
*/ */
enum { enum {
...@@ -104,6 +106,7 @@ enum { ...@@ -104,6 +106,7 @@ enum {
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3), GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5),
}; };
/** /**
......
...@@ -179,6 +179,7 @@ typedef struct git_refspec git_refspec; ...@@ -179,6 +179,7 @@ typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote; typedef struct git_remote git_remote;
typedef struct git_remote_head git_remote_head; typedef struct git_remote_head git_remote_head;
typedef struct git_remote_callbacks git_remote_callbacks;
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
......
...@@ -426,17 +426,7 @@ int git_attr_fnmatch__parse( ...@@ -426,17 +426,7 @@ int git_attr_fnmatch__parse(
return -1; return -1;
} else { } else {
/* strip '\' that might have be used for internal whitespace */ /* strip '\' that might have be used for internal whitespace */
char *to = spec->pattern; spec->length = git__unescape(spec->pattern);
for (scan = spec->pattern; *scan; to++, scan++) {
if (*scan == '\\')
scan++; /* skip '\' but include next char */
if (to != scan)
*to = *scan;
}
if (to != scan) {
*to = '\0';
spec->length = (to - spec->pattern);
}
} }
return 0; return 0;
......
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
#include "common.h" #include "common.h"
#include "commit.h" #include "commit.h"
#include "branch.h"
#include "tag.h" #include "tag.h"
#include "config.h"
#include "refspec.h"
#include "git2/branch.h"
static int retrieve_branch_reference( static int retrieve_branch_reference(
git_reference **branch_reference_out, git_reference **branch_reference_out,
...@@ -48,8 +51,8 @@ static int create_error_invalid(const char *msg) ...@@ -48,8 +51,8 @@ static int create_error_invalid(const char *msg)
} }
int git_branch_create( int git_branch_create(
git_oid *oid_out, git_reference **ref_out,
git_repository *repo, git_repository *repository,
const char *branch_name, const char *branch_name,
const git_object *target, const git_object *target,
int force) int force)
...@@ -60,10 +63,8 @@ int git_branch_create( ...@@ -60,10 +63,8 @@ int git_branch_create(
git_buf canonical_branch_name = GIT_BUF_INIT; git_buf canonical_branch_name = GIT_BUF_INIT;
int error = -1; int error = -1;
assert(repo && branch_name && target && oid_out); assert(branch_name && target && ref_out);
assert(git_object_owner(target) == repository);
if (git_object_owner(target) != repo)
return create_error_invalid("The given target does not belong to this repository");
target_type = git_object_type(target); target_type = git_object_type(target);
...@@ -90,17 +91,17 @@ int git_branch_create( ...@@ -90,17 +91,17 @@ int git_branch_create(
if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
goto cleanup; goto cleanup;
if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) if (git_reference_create_oid(&branch, repository,
git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0)
goto cleanup; goto cleanup;
git_oid_cpy(oid_out, git_reference_oid(branch)); *ref_out = branch;
error = 0; error = 0;
cleanup: cleanup:
if (target_type == GIT_OBJ_TAG) if (target_type == GIT_OBJ_TAG)
git_object_free(commit); git_object_free(commit);
git_reference_free(branch);
git_buf_free(&canonical_branch_name); git_buf_free(&canonical_branch_name);
return error; return error;
} }
...@@ -111,6 +112,7 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_ ...@@ -111,6 +112,7 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_
git_reference *head = NULL; git_reference *head = NULL;
int error; int error;
assert(repo && branch_name);
assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE)); assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE));
if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0) if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0)
...@@ -183,28 +185,110 @@ int git_branch_foreach( ...@@ -183,28 +185,110 @@ int git_branch_foreach(
return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter); return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter);
} }
int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force) static int not_a_local_branch(git_reference *ref)
{ {
git_reference *reference = NULL; giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref));
git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; return -1;
int error = 0; }
if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) int git_branch_move(
goto cleanup; git_reference *branch,
const char *new_branch_name,
int force)
{
git_buf new_reference_name = GIT_BUF_INIT;
int error;
/* We need to be able to return GIT_ENOTFOUND */ assert(branch && new_branch_name);
if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0)
goto cleanup; if (!git_reference_is_branch(branch))
return not_a_local_branch(branch);
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
goto cleanup; goto cleanup;
error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force); error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force);
cleanup: cleanup:
git_reference_free(reference);
git_buf_free(&old_reference_name);
git_buf_free(&new_reference_name); git_buf_free(&new_reference_name);
return error; return error;
} }
int git_branch_lookup(
git_reference **ref_out,
git_repository *repo,
const char *branch_name,
git_branch_t branch_type)
{
assert(ref_out && repo && branch_name);
return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
}
static int retrieve_tracking_configuration(
const char **out, git_reference *branch, const char *format)
{
git_config *config;
git_buf buf = GIT_BUF_INIT;
int error;
if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
return -1;
if (git_buf_printf(&buf, format,
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
return -1;
error = git_config_get_string(out, config, git_buf_cstr(&buf));
git_buf_free(&buf);
return error;
}
int git_branch_tracking(
git_reference **tracking_out,
git_reference *branch)
{
const char *remote_name, *merge_name;
git_buf buf = GIT_BUF_INIT;
int error = -1;
git_remote *remote = NULL;
const git_refspec *refspec;
assert(tracking_out && branch);
if (!git_reference_is_branch(branch))
return not_a_local_branch(branch);
if ((error = retrieve_tracking_configuration(&remote_name, branch, "branch.%s.remote")) < 0)
goto cleanup;
if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0)
goto cleanup;
if (strcmp(".", remote_name) != 0) {
if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0)
goto cleanup;
refspec = git_remote_fetchspec(remote);
if (refspec == NULL) {
error = GIT_ENOTFOUND;
goto cleanup;
}
if (git_refspec_transform_r(&buf, refspec, merge_name) < 0)
goto cleanup;
} else
if (git_buf_sets(&buf, merge_name) < 0)
goto cleanup;
error = git_reference_lookup(
tracking_out,
git_reference_owner(branch),
git_buf_cstr(&buf));
cleanup:
git_remote_free(remote);
git_buf_free(&buf);
return error;
}
/*
* 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_branch_h__
#define INCLUDE_branch_h__
#include "git2/branch.h"
struct git_branch {
char *remote; /* TODO: Make this a git_remote */
char *merge;
};
#endif
...@@ -496,3 +496,7 @@ bool git_buf_is_binary(const git_buf *buf) ...@@ -496,3 +496,7 @@ bool git_buf_is_binary(const git_buf *buf)
return ((printable >> 7) < nonprintable); return ((printable >> 7) < nonprintable);
} }
void git_buf_unescape(git_buf *buf)
{
buf->size = git__unescape(buf->ptr);
}
...@@ -151,4 +151,7 @@ int git_buf_common_prefix(git_buf *buf, const git_strarray *strings); ...@@ -151,4 +151,7 @@ int git_buf_common_prefix(git_buf *buf, const git_strarray *strings);
/* Check if buffer looks like it contains binary data */ /* Check if buffer looks like it contains binary data */
bool git_buf_is_binary(const git_buf *buf); bool git_buf_is_binary(const git_buf *buf);
/* Unescape all characters in a buffer */
void git_buf_unescape(git_buf *buf);
#endif #endif
...@@ -49,15 +49,6 @@ ...@@ -49,15 +49,6 @@
#include <regex.h> #include <regex.h>
extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
#define git__throw(error, ...) \
(git___throw(__VA_ARGS__), error)
extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
#define git__rethrow(error, ...) \
(git___rethrow(__VA_ARGS__), error)
#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } #define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
void giterr_set_oom(void); void giterr_set_oom(void);
...@@ -68,5 +59,4 @@ void giterr_set_regex(const regex_t *regex, int error_code); ...@@ -68,5 +59,4 @@ void giterr_set_regex(const regex_t *regex, int error_code);
#include "util.h" #include "util.h"
#endif /* INCLUDE_common_h__ */ #endif /* INCLUDE_common_h__ */
...@@ -983,9 +983,12 @@ static int write_section(git_filebuf *file, const char *key) ...@@ -983,9 +983,12 @@ static int write_section(git_filebuf *file, const char *key)
if (dot == NULL) { if (dot == NULL) {
git_buf_puts(&buf, key); git_buf_puts(&buf, key);
} else { } else {
char *escaped;
git_buf_put(&buf, key, dot - key); git_buf_put(&buf, key, dot - key);
/* TODO: escape */ escaped = escape_value(dot + 1);
git_buf_printf(&buf, " \"%s\"", dot + 1); GITERR_CHECK_ALLOC(escaped);
git_buf_printf(&buf, " \"%s\"", escaped);
git__free(escaped);
} }
git_buf_puts(&buf, "]\n"); git_buf_puts(&buf, "]\n");
......
...@@ -20,14 +20,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec) ...@@ -20,14 +20,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec)
return NULL; return NULL;
/* diff prefix will only be leading non-wildcards */ /* diff prefix will only be leading non-wildcards */
for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan); for (scan = prefix.ptr; *scan; ++scan) {
if (git__iswildcard(*scan) &&
(scan == prefix.ptr || (*(scan - 1) != '\\')))
break;
}
git_buf_truncate(&prefix, scan - prefix.ptr); git_buf_truncate(&prefix, scan - prefix.ptr);
if (prefix.size > 0) if (prefix.size <= 0) {
return git_buf_detach(&prefix);
git_buf_free(&prefix); git_buf_free(&prefix);
return NULL; return NULL;
}
git_buf_unescape(&prefix);
return git_buf_detach(&prefix);
} }
static bool diff_pathspec_is_interesting(const git_strarray *pathspec) static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
...@@ -54,7 +61,11 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) ...@@ -54,7 +61,11 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
return true; return true;
git_vector_foreach(&diff->pathspec, i, match) { git_vector_foreach(&diff->pathspec, i, match) {
int result = p_fnmatch(match->pattern, path, 0); int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) &&
result == FNM_NOMATCH)
result = p_fnmatch(match->pattern, path, 0);
/* if we didn't match, look for exact dirname prefix match */ /* if we didn't match, look for exact dirname prefix match */
if (result == FNM_NOMATCH && if (result == FNM_NOMATCH &&
...@@ -814,9 +825,9 @@ int git_diff_merge( ...@@ -814,9 +825,9 @@ int git_diff_merge(
/* prefix strings also come from old pool, so recreate those.*/ /* prefix strings also come from old pool, so recreate those.*/
onto->opts.old_prefix = onto->opts.old_prefix =
git_pool_strdup(&onto->pool, onto->opts.old_prefix); git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
onto->opts.new_prefix = onto->opts.new_prefix =
git_pool_strdup(&onto->pool, onto->opts.new_prefix); git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
} }
git_vector_foreach(&onto_new, i, delta) git_vector_foreach(&onto_new, i, delta)
...@@ -826,4 +837,3 @@ int git_diff_merge( ...@@ -826,4 +837,3 @@ int git_diff_merge(
return error; return error;
} }
...@@ -10,10 +10,6 @@ ...@@ -10,10 +10,6 @@
#include "mwindow.h" #include "mwindow.h"
typedef struct { typedef struct {
struct {
char last[1024];
} error;
git_error *last_error; git_error *last_error;
git_error error_t; git_error error_t;
......
...@@ -333,3 +333,72 @@ int git_object__resolve_to_type(git_object **obj, git_otype type) ...@@ -333,3 +333,72 @@ int git_object__resolve_to_type(git_object **obj, git_otype type)
*obj = scan; *obj = scan;
return error; return error;
} }
static int dereference_object(git_object **dereferenced, git_object *obj)
{
git_otype type = git_object_type(obj);
switch (type) {
case GIT_OBJ_COMMIT:
return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);
break;
case GIT_OBJ_TAG:
return git_tag_target(dereferenced, (git_tag*)obj);
break;
default:
return GIT_ENOTFOUND;
break;
}
}
static int peel_error(int error, const char* msg)
{
giterr_set(GITERR_INVALID, "The given object cannot be peeled - %s", msg);
return error;
}
int git_object_peel(
git_object **peeled,
git_object *object,
git_otype target_type)
{
git_object *source, *deref = NULL;
assert(object);
if (git_object_type(object) == target_type)
return git_object__dup(peeled, object);
if (target_type == GIT_OBJ_BLOB
|| target_type == GIT_OBJ_ANY)
return peel_error(GIT_EAMBIGUOUS, "Ambiguous target type");
if (git_object_type(object) == GIT_OBJ_BLOB)
return peel_error(GIT_ERROR, "A blob cannot be dereferenced");
source = object;
while (true) {
if (dereference_object(&deref, source) < 0)
goto cleanup;
if (source != object)
git_object_free(source);
if (git_object_type(deref) == target_type) {
*peeled = deref;
return 0;
}
source = deref;
deref = NULL;
}
cleanup:
if (source != object)
git_object_free(source);
git_object_free(deref);
return -1;
}
...@@ -206,6 +206,11 @@ char *git_pool_strdup(git_pool *pool, const char *str) ...@@ -206,6 +206,11 @@ char *git_pool_strdup(git_pool *pool, const char *str)
return git_pool_strndup(pool, str, strlen(str)); return git_pool_strndup(pool, str, strlen(str));
} }
char *git_pool_strdup_safe(git_pool *pool, const char *str)
{
return str ? git_pool_strdup(pool, str) : NULL;
}
char *git_pool_strcat(git_pool *pool, const char *a, const char *b) char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
{ {
void *ptr; void *ptr;
......
...@@ -90,6 +90,13 @@ extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); ...@@ -90,6 +90,13 @@ extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n);
extern char *git_pool_strdup(git_pool *pool, const char *str); extern char *git_pool_strdup(git_pool *pool, const char *str);
/** /**
* Allocate space and duplicate a string into it, NULL is no error.
*
* This is allowed only for pools with item_size == sizeof(char)
*/
extern char *git_pool_strdup_safe(git_pool *pool, const char *str);
/**
* Allocate space for the concatenation of two strings. * Allocate space for the concatenation of two strings.
* *
* This is allowed only for pools with item_size == sizeof(char) * This is allowed only for pools with item_size == sizeof(char)
......
...@@ -28,66 +28,68 @@ static int reflog_init(git_reflog **reflog, git_reference *ref) ...@@ -28,66 +28,68 @@ static int reflog_init(git_reflog **reflog, git_reference *ref)
return -1; return -1;
} }
log->owner = git_reference_owner(ref);
*reflog = log; *reflog = log;
return 0; return 0;
} }
static int reflog_write(const char *log_path, const char *oid_old, static int serialize_reflog_entry(
const char *oid_new, const git_signature *committer, git_buf *buf,
const git_oid *oid_old,
const git_oid *oid_new,
const git_signature *committer,
const char *msg) const char *msg)
{ {
int error; char raw_old[GIT_OID_HEXSZ+1];
git_buf log = GIT_BUF_INIT; char raw_new[GIT_OID_HEXSZ+1];
git_filebuf fbuf = GIT_FILEBUF_INIT;
bool trailing_newline = false;
assert(log_path && oid_old && oid_new && committer); git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
if (msg) { git_buf_clear(buf);
const char *newline = strchr(msg, '\n');
if (newline) {
if (*(newline + 1) == '\0')
trailing_newline = true;
else {
giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
return -1;
}
}
}
git_buf_puts(&log, oid_old); git_buf_puts(buf, raw_old);
git_buf_putc(&log, ' '); git_buf_putc(buf, ' ');
git_buf_puts(buf, raw_new);
git_buf_puts(&log, oid_new); git_signature__writebuf(buf, " ", committer);
git_signature__writebuf(&log, " ", committer); /* drop trailing LF */
git_buf_truncate(&log, log.size - 1); /* drop LF */ git_buf_rtrim(buf);
if (msg) { if (msg) {
git_buf_putc(&log, '\t'); git_buf_putc(buf, '\t');
git_buf_puts(&log, msg); git_buf_puts(buf, msg);
} }
if (!trailing_newline) git_buf_putc(buf, '\n');
git_buf_putc(&log, '\n');
if (git_buf_oom(&log)) { return git_buf_oom(buf);
git_buf_free(&log); }
return -1;
}
error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); static int reflog_entry_new(git_reflog_entry **entry)
if (!error) { {
if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) git_reflog_entry *e;
git_filebuf_cleanup(&fbuf);
else
error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
}
git_buf_free(&log); assert(entry);
return error; e = git__malloc(sizeof(git_reflog_entry));
GITERR_CHECK_ALLOC(e);
memset(e, 0, sizeof(git_reflog_entry));
*entry = e;
return 0;
}
static void reflog_entry_free(git_reflog_entry *entry)
{
git_signature_free(entry->committer);
git__free(entry->msg);
git__free(entry);
} }
static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
...@@ -105,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) ...@@ -105,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
} while (0) } while (0)
while (buf_size > GIT_REFLOG_SIZE_MIN) { while (buf_size > GIT_REFLOG_SIZE_MIN) {
entry = git__malloc(sizeof(git_reflog_entry)); if (reflog_entry_new(&entry) < 0)
GITERR_CHECK_ALLOC(entry); return -1;
entry->committer = git__malloc(sizeof(git_signature)); entry->committer = git__malloc(sizeof(git_signature));
GITERR_CHECK_ALLOC(entry->committer); GITERR_CHECK_ALLOC(entry->committer);
...@@ -153,10 +155,9 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) ...@@ -153,10 +155,9 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
#undef seek_forward #undef seek_forward
fail: fail:
if (entry) { if (entry)
git__free(entry->committer); reflog_entry_free(entry);
git__free(entry);
}
return -1; return -1;
} }
...@@ -168,10 +169,7 @@ void git_reflog_free(git_reflog *reflog) ...@@ -168,10 +169,7 @@ void git_reflog_free(git_reflog *reflog)
for (i=0; i < reflog->entries.length; i++) { for (i=0; i < reflog->entries.length; i++) {
entry = git_vector_get(&reflog->entries, i); entry = git_vector_get(&reflog->entries, i);
git_signature_free(entry->committer); reflog_entry_free(entry);
git__free(entry->msg);
git__free(entry);
} }
git_vector_free(&reflog->entries); git_vector_free(&reflog->entries);
...@@ -179,6 +177,24 @@ void git_reflog_free(git_reflog *reflog) ...@@ -179,6 +177,24 @@ void git_reflog_free(git_reflog *reflog)
git__free(reflog); git__free(reflog);
} }
static int retrieve_reflog_path(git_buf *path, git_reference *ref)
{
return git_buf_join_n(path, '/', 3,
git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name);
}
static int create_new_reflog_file(const char *filepath)
{
int fd;
if ((fd = p_open(filepath,
O_WRONLY | O_CREAT | O_TRUNC,
GIT_REFLOG_FILE_MODE)) < 0)
return -1;
return p_close(fd);
}
int git_reflog_read(git_reflog **reflog, git_reference *ref) int git_reflog_read(git_reflog **reflog, git_reference *ref)
{ {
int error; int error;
...@@ -188,85 +204,138 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) ...@@ -188,85 +204,138 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref)
*reflog = NULL; *reflog = NULL;
assert(reflog && ref);
if (reflog_init(&log, ref) < 0) if (reflog_init(&log, ref) < 0)
return -1; return -1;
error = git_buf_join_n(&log_path, '/', 3, if (retrieve_reflog_path(&log_path, ref) < 0)
ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); goto cleanup;
if (!error) error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
error = git_futils_readbuffer(&log_file, log_path.ptr); if (error < 0 && error != GIT_ENOTFOUND)
goto cleanup;
if (!error) if ((error == GIT_ENOTFOUND) &&
error = reflog_parse(log, log_file.ptr, log_file.size); ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
goto cleanup;
if ((error = reflog_parse(log,
git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
goto cleanup;
if (!error)
*reflog = log; *reflog = log;
else goto success;
cleanup:
git_reflog_free(log); git_reflog_free(log);
success:
git_buf_free(&log_file); git_buf_free(&log_file);
git_buf_free(&log_path); git_buf_free(&log_path);
return error; return error;
} }
int git_reflog_write(git_reference *ref, const git_oid *oid_old, int git_reflog_write(git_reflog *reflog)
const git_signature *committer, const char *msg)
{ {
int error; int error = -1;
char old[GIT_OID_HEXSZ+1]; unsigned int i;
char new[GIT_OID_HEXSZ+1]; git_reflog_entry *entry;
git_buf log_path = GIT_BUF_INIT; git_buf log_path = GIT_BUF_INIT;
git_reference *r; git_buf log = GIT_BUF_INIT;
const git_oid *oid; git_filebuf fbuf = GIT_FILEBUF_INIT;
if ((error = git_reference_resolve(&r, ref)) < 0) assert(reflog);
return error;
oid = git_reference_oid(r);
if (oid == NULL) {
giterr_set(GITERR_REFERENCE,
"Failed to write reflog. Cannot resolve reference `%s`", r->name);
git_reference_free(r);
return -1;
}
git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); if (git_buf_join_n(&log_path, '/', 3,
git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0)
return -1;
git_reference_free(r); if (!git_path_isfile(git_buf_cstr(&log_path))) {
giterr_set(GITERR_INVALID,
"Log file for reference '%s' doesn't exist.", reflog->ref_name);
goto cleanup;
}
error = git_buf_join_n(&log_path, '/', 3, if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0)
ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
if (error < 0)
goto cleanup; goto cleanup;
if (git_path_exists(log_path.ptr) == false) { git_vector_foreach(&reflog->entries, i, entry) {
error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
} else if (git_path_isfile(log_path.ptr) == false) {
giterr_set(GITERR_REFERENCE,
"Failed to write reflog. `%s` is directory", log_path.ptr);
error = -1;
} else if (oid_old == NULL) {
giterr_set(GITERR_REFERENCE,
"Failed to write reflog. Old OID cannot be NULL for existing reference");
error = -1;
}
if (error < 0)
goto cleanup; goto cleanup;
if (oid_old) if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
git_oid_tostr(old, sizeof(old), oid_old); goto cleanup;
else }
p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0);
error = reflog_write(log_path.ptr, old, new, committer, msg); error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
goto success;
cleanup: cleanup:
git_filebuf_cleanup(&fbuf);
success:
git_buf_free(&log);
git_buf_free(&log_path); git_buf_free(&log_path);
return error; return error;
} }
int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
const git_signature *committer, const char *msg)
{
int count;
git_reflog_entry *entry;
const char *newline;
assert(reflog && new_oid && committer);
if (reflog_entry_new(&entry) < 0)
return -1;
if ((entry->committer = git_signature_dup(committer)) == NULL)
goto cleanup;
if (msg != NULL) {
if ((entry->msg = git__strdup(msg)) == NULL)
goto cleanup;
newline = strchr(msg, '\n');
if (newline) {
if (newline[1] != '\0') {
giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
goto cleanup;
}
entry->msg[newline - msg] = '\0';
}
}
count = git_reflog_entrycount(reflog);
if (count == 0)
git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
else {
const git_reflog_entry *previous;
previous = git_reflog_entry_byindex(reflog, count -1);
git_oid_cpy(&entry->oid_old, &previous->oid_cur);
}
git_oid_cpy(&entry->oid_cur, new_oid);
if (git_vector_insert(&reflog->entries, entry) < 0)
goto cleanup;
return 0;
cleanup:
reflog_entry_free(entry);
return -1;
}
int git_reflog_rename(git_reference *ref, const char *new_name) int git_reflog_rename(git_reference *ref, const char *new_name)
{ {
int error = -1, fd; int error = -1, fd;
...@@ -276,7 +345,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name) ...@@ -276,7 +345,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name)
assert(ref && new_name); assert(ref && new_name);
if (git_buf_joinpath(&temp_path, ref->owner->path_repository, GIT_REFLOG_DIR) < 0) if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0)
return -1; return -1;
if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0) if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0)
...@@ -324,8 +393,7 @@ int git_reflog_delete(git_reference *ref) ...@@ -324,8 +393,7 @@ int git_reflog_delete(git_reference *ref)
int error; int error;
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
error = git_buf_join_n( error = retrieve_reflog_path(&path, ref);
&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
if (!error && git_path_exists(path.ptr)) if (!error && git_path_exists(path.ptr))
error = p_unlink(path.ptr); error = p_unlink(path.ptr);
...@@ -370,3 +438,52 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) ...@@ -370,3 +438,52 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry)
assert(entry); assert(entry);
return entry->msg; return entry->msg;
} }
int git_reflog_drop(
git_reflog *reflog,
unsigned int idx,
int rewrite_previous_entry)
{
unsigned int entrycount;
git_reflog_entry *entry, *previous;
assert(reflog);
entrycount = git_reflog_entrycount(reflog);
if (idx >= entrycount)
return GIT_ENOTFOUND;
entry = git_vector_get(&reflog->entries, idx);
reflog_entry_free(entry);
if (git_vector_remove(&reflog->entries, idx) < 0)
return -1;
if (!rewrite_previous_entry)
return 0;
/* No need to rewrite anything when removing the first entry */
if (idx == 0)
return 0;
/* There are no more entries in the log */
if (entrycount == 1)
return 0;
entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
/* If the last entry has just been removed... */
if (idx == entrycount - 1) {
/* ...clear the oid_old member of the "new" last entry */
if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
return -1;
return 0;
}
previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
git_oid_cpy(&entry->oid_old, &previous->oid_cur);
return 0;
}
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
struct git_reflog_entry { struct git_reflog_entry {
git_oid oid_old; git_oid oid_old;
git_oid oid_cur; git_oid oid_cur;
...@@ -28,6 +30,7 @@ struct git_reflog_entry { ...@@ -28,6 +30,7 @@ struct git_reflog_entry {
struct git_reflog { struct git_reflog {
char *ref_name; char *ref_name;
git_repository *owner;
git_vector entries; git_vector entries;
}; };
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "fileops.h" #include "fileops.h"
#include "pack.h" #include "pack.h"
#include "reflog.h" #include "reflog.h"
#include "config.h"
#include <git2/tag.h> #include <git2/tag.h>
#include <git2/object.h> #include <git2/object.h>
...@@ -1816,75 +1815,9 @@ int git_reference_has_log( ...@@ -1816,75 +1815,9 @@ int git_reference_has_log(
return result; return result;
} }
//TODO: How about also taking care of local tracking branches? int git_reference_is_branch(git_reference *ref)
//cf. http://alblue.bandlem.com/2011/07/git-tip-of-week-tracking-branches.html
int git_reference_remote_tracking_from_branch(
git_reference **tracking_ref,
git_reference *branch_ref)
{ {
git_config *config = NULL; assert(ref);
const char *name, *remote, *merge;
git_buf buf = GIT_BUF_INIT;
int error = -1;
assert(tracking_ref && branch_ref);
name = git_reference_name(branch_ref);
if (git__prefixcmp(name, GIT_REFS_HEADS_DIR)) {
giterr_set(
GITERR_INVALID,
"Failed to retrieve tracking reference - '%s' is not a branch.",
name);
return -1;
}
if (git_repository_config(&config, branch_ref->owner) < 0)
return -1;
if (git_buf_printf(
&buf,
"branch.%s.remote",
name + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto cleanup;
if ((error = git_config_get_string(&remote, config, git_buf_cstr(&buf))) < 0)
goto cleanup;
error = -1;
git_buf_clear(&buf);
//TODO: Is it ok to fail when no merge target is found?
if (git_buf_printf(
&buf,
"branch.%s.merge",
name + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto cleanup;
if (git_config_get_string(&merge, config, git_buf_cstr(&buf)) < 0)
goto cleanup;
//TODO: Should we test this?
if (git__prefixcmp(merge, GIT_REFS_HEADS_DIR))
goto cleanup;
git_buf_clear(&buf);
if (git_buf_printf(
&buf,
"refs/remotes/%s/%s",
remote,
merge + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto cleanup;
error = git_reference_lookup(
tracking_ref,
branch_ref->owner,
git_buf_cstr(&buf));
cleanup: return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0;
git_config_free(config);
git_buf_free(&buf);
return error;
} }
...@@ -131,6 +131,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) ...@@ -131,6 +131,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
GITERR_CHECK_ALLOC(remote->url); GITERR_CHECK_ALLOC(remote->url);
git_buf_clear(&buf); git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) {
error = -1;
goto cleanup;
}
error = git_config_get_string(&val, config, git_buf_cstr(&buf));
if (error == GIT_ENOTFOUND)
error = 0;
if (error < 0) {
error = -1;
goto cleanup;
}
if (val) {
remote->pushurl = git__strdup(val);
GITERR_CHECK_ALLOC(remote->pushurl);
}
git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) {
error = -1; error = -1;
goto cleanup; goto cleanup;
...@@ -179,7 +199,7 @@ int git_remote_save(const git_remote *remote) ...@@ -179,7 +199,7 @@ int git_remote_save(const git_remote *remote)
if (git_repository_config__weakptr(&config, remote->repo) < 0) if (git_repository_config__weakptr(&config, remote->repo) < 0)
return -1; return -1;
if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0)
return -1; return -1;
if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) {
...@@ -187,6 +207,26 @@ int git_remote_save(const git_remote *remote) ...@@ -187,6 +207,26 @@ int git_remote_save(const git_remote *remote)
return -1; return -1;
} }
git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0)
return -1;
if (remote->pushurl) {
if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) {
git_buf_free(&buf);
return -1;
}
} else {
int error = git_config_delete(config, git_buf_cstr(&buf));
if (error == GIT_ENOTFOUND) {
error = 0;
}
if (error < 0) {
git_buf_free(&buf);
return -1;
}
}
if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { if (remote->fetch.src != NULL && remote->fetch.dst != NULL) {
git_buf_clear(&buf); git_buf_clear(&buf);
git_buf_clear(&value); git_buf_clear(&value);
...@@ -238,6 +278,38 @@ const char *git_remote_url(git_remote *remote) ...@@ -238,6 +278,38 @@ const char *git_remote_url(git_remote *remote)
return remote->url; return remote->url;
} }
int git_remote_set_url(git_remote *remote, const char* url)
{
assert(remote);
assert(url);
git__free(remote->url);
remote->url = git__strdup(url);
GITERR_CHECK_ALLOC(remote->url);
return 0;
}
const char *git_remote_pushurl(git_remote *remote)
{
assert(remote);
return remote->pushurl;
}
int git_remote_set_pushurl(git_remote *remote, const char* url)
{
assert(remote);
git__free(remote->pushurl);
if (url) {
remote->pushurl = git__strdup(url);
GITERR_CHECK_ALLOC(remote->pushurl);
} else {
remote->pushurl = NULL;
}
return 0;
}
int git_remote_set_fetchspec(git_remote *remote, const char *spec) int git_remote_set_fetchspec(git_remote *remote, const char *spec)
{ {
git_refspec refspec; git_refspec refspec;
...@@ -284,13 +356,33 @@ const git_refspec *git_remote_pushspec(git_remote *remote) ...@@ -284,13 +356,33 @@ const git_refspec *git_remote_pushspec(git_remote *remote)
return &remote->push; return &remote->push;
} }
const char* git_remote__urlfordirection(git_remote *remote, int direction)
{
assert(remote);
if (direction == GIT_DIR_FETCH) {
return remote->url;
}
if (direction == GIT_DIR_PUSH) {
return remote->pushurl ? remote->pushurl : remote->url;
}
return NULL;
}
int git_remote_connect(git_remote *remote, int direction) int git_remote_connect(git_remote *remote, int direction)
{ {
git_transport *t; git_transport *t;
const char *url;
assert(remote); assert(remote);
if (git_transport_new(&t, remote->url) < 0) url = git_remote__urlfordirection(remote, direction);
if (url == NULL )
return -1;
if (git_transport_new(&t, url) < 0)
return -1; return -1;
t->check_cert = remote->check_cert; t->check_cert = remote->check_cert;
...@@ -331,7 +423,7 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats ...@@ -331,7 +423,7 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats
return git_fetch_download_pack(remote, bytes, stats); return git_fetch_download_pack(remote, bytes, stats);
} }
int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) int git_remote_update_tips(git_remote *remote)
{ {
int error = 0; int error = 0;
unsigned int i = 0; unsigned int i = 0;
...@@ -377,12 +469,12 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co ...@@ -377,12 +469,12 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co
continue; continue;
if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)
break; goto on_error;
git_reference_free(ref); git_reference_free(ref);
if (cb != NULL) { if (remote->callbacks.update_tips != NULL) {
if (cb(refname.ptr, &old, &head->oid) < 0) if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.data) < 0)
goto on_error; goto on_error;
} }
} }
...@@ -429,6 +521,7 @@ void git_remote_free(git_remote *remote) ...@@ -429,6 +521,7 @@ void git_remote_free(git_remote *remote)
git__free(remote->push.src); git__free(remote->push.src);
git__free(remote->push.dst); git__free(remote->push.dst);
git__free(remote->url); git__free(remote->url);
git__free(remote->pushurl);
git__free(remote->name); git__free(remote->name);
git__free(remote); git__free(remote);
} }
...@@ -525,3 +618,10 @@ void git_remote_check_cert(git_remote *remote, int check) ...@@ -525,3 +618,10 @@ void git_remote_check_cert(git_remote *remote, int check)
remote->check_cert = check; remote->check_cert = check;
} }
void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks)
{
assert(remote && callbacks);
memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));
}
...@@ -14,13 +14,17 @@ ...@@ -14,13 +14,17 @@
struct git_remote { struct git_remote {
char *name; char *name;
char *url; char *url;
char *pushurl;
git_vector refs; git_vector refs;
struct git_refspec fetch; struct git_refspec fetch;
struct git_refspec push; struct git_refspec push;
git_transport *transport; git_transport *transport;
git_repository *repo; git_repository *repo;
git_remote_callbacks callbacks;
unsigned int need_pack:1, unsigned int need_pack:1,
check_cert; check_cert;
}; };
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
#endif #endif
...@@ -98,6 +98,13 @@ struct git_repository { ...@@ -98,6 +98,13 @@ struct git_repository {
* export */ * export */
void git_object__free(void *object); void git_object__free(void *object);
GIT_INLINE(int) git_object__dup(git_object **dest, git_object *source)
{
git_cached_obj_incref(source);
*dest = source;
return 0;
}
int git_object__resolve_to_type(git_object **obj, git_otype type); int git_object__resolve_to_type(git_object **obj, git_otype type);
int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
......
...@@ -13,48 +13,12 @@ ...@@ -13,48 +13,12 @@
#include "git2.h" #include "git2.h"
typedef enum {
REVPARSE_STATE_INIT,
REVPARSE_STATE_CARET,
REVPARSE_STATE_LINEAR,
REVPARSE_STATE_COLON,
REVPARSE_STATE_DONE,
} revparse_state;
static int revspec_error(const char *revspec) static int revspec_error(const char *revspec)
{ {
giterr_set(GITERR_INVALID, "Failed to parse revision specifier - Invalid pattern '%s'", revspec); giterr_set(GITERR_INVALID, "Failed to parse revision specifier - Invalid pattern '%s'", revspec);
return -1; return -1;
} }
static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec)
{
git_oid resolved;
int error;
if ((error = git_reference_name_to_oid(&resolved, repo, spec)) < 0)
return error;
return git_object_lookup(out, repo, &resolved, GIT_OBJ_ANY);
}
/* Returns non-zero if yes */
static int spec_looks_like_describe_output(const char *spec)
{
regex_t regex;
int regex_error, retcode;
regex_error = regcomp(&regex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED);
if (regex_error != 0) {
giterr_set_regex(&regex, regex_error);
return regex_error;
}
retcode = regexec(&regex, spec, 0, NULL, 0);
regfree(&regex);
return retcode == 0;
}
static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname) static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
{ {
int error, i; int error, i;
...@@ -75,7 +39,7 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const ...@@ -75,7 +39,7 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const
if (*refname) if (*refname)
git_buf_puts(&name, refname); git_buf_puts(&name, refname);
else { else {
git_buf_puts(&name, "HEAD"); git_buf_puts(&name, GIT_HEAD_FILE);
fallbackmode = false; fallbackmode = false;
} }
...@@ -115,21 +79,43 @@ static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char ...@@ -115,21 +79,43 @@ static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char
return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY); return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
} }
static int build_regex(regex_t *regex, const char *pattern)
{
int error;
if (*pattern == '\0') {
giterr_set(GITERR_REGEX, "Empty pattern");
return -1;
}
error = regcomp(regex, pattern, REG_EXTENDED);
if (!error)
return 0;
giterr_set_regex(regex, error);
regfree(regex);
return -1;
}
static int maybe_describe(git_object**out, git_repository *repo, const char *spec) static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
{ {
const char *substr; const char *substr;
int match; int error;
regex_t regex;
/* "git describe" output; snip everything before/including "-g" */
substr = strstr(spec, "-g"); substr = strstr(spec, "-g");
if (substr == NULL) if (substr == NULL)
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
if ((match = spec_looks_like_describe_output(spec)) < 0) if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
return match; return -1;
error = regexec(&regex, spec, 0, NULL, 0);
regfree(&regex);
if (!match) if (error)
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
return maybe_sha_or_abbrev(out, repo, substr+2); return maybe_sha_or_abbrev(out, repo, substr+2);
...@@ -168,373 +154,308 @@ static int revparse_lookup_object(git_object **out, git_repository *repo, const ...@@ -168,373 +154,308 @@ static int revparse_lookup_object(git_object **out, git_repository *repo, const
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
} }
static int all_chars_are_digits(const char *str, size_t len) static int try_parse_numeric(int *n, const char *curly_braces_content)
{ {
size_t i = 0; int content;
const char *end_ptr;
for (i = 0; i < len; i++) if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
if (!git__isdigit(str[i])) return 0; return -1;
if (*end_ptr != '\0')
return -1;
return 1; *n = content;
return 0;
} }
static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec) static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *spec, const char *identifier, unsigned int position)
{ {
git_reference *disambiguated = NULL; git_reference *ref = NULL;
git_reflog *reflog = NULL; git_reflog *reflog = NULL;
int n, retcode = GIT_ERROR; regex_t preg;
int i, refloglen; int numentries, i, cur, error = -1;
const git_reflog_entry *entry; const git_reflog_entry *entry;
const char *msg;
regmatch_t regexmatches[2];
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
size_t refspeclen = strlen(refspec);
size_t reflogspeclen = strlen(reflogspec);
if (git__prefixcmp(reflogspec, "@{") != 0 || cur = position;
git__suffixcmp(reflogspec, "}") != 0)
return revspec_error(reflogspec);
/* "@{-N}" form means walk back N checkouts. That means the HEAD log. */ if (*identifier != '\0' || *base_ref != NULL)
if (!git__prefixcmp(reflogspec, "@{-")) { return revspec_error(spec);
regex_t regex;
int regex_error;
if (refspeclen > 0) if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
return revspec_error(reflogspec); return -1;
if (git__strtol32(&n, reflogspec+3, NULL, 10) < 0 || n < 1) if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
return revspec_error(reflogspec); goto cleanup;
if (!git_reference_lookup(&disambiguated, repo, "HEAD")) { if (git_reflog_read(&reflog, ref) < 0)
if (!git_reflog_read(&reflog, disambiguated)) { goto cleanup;
regex_error = regcomp(&regex, "checkout: moving from (.*) to .*", REG_EXTENDED);
if (regex_error != 0) {
giterr_set_regex(&regex, regex_error);
} else {
regmatch_t regexmatches[2];
retcode = GIT_ENOTFOUND; numentries = git_reflog_entrycount(reflog);
refloglen = git_reflog_entrycount(reflog); for (i = numentries - 1; i >= 0; i--) {
for (i=refloglen-1; i >= 0; i--) {
const char *msg;
entry = git_reflog_entry_byindex(reflog, i); entry = git_reflog_entry_byindex(reflog, i);
msg = git_reflog_entry_msg(entry); msg = git_reflog_entry_msg(entry);
if (!regexec(&regex, msg, 2, regexmatches, 0)) {
n--; if (regexec(&preg, msg, 2, regexmatches, 0))
if (!n) { continue;
cur--;
if (cur > 0)
continue;
git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf));
break;
}
}
}
regfree(&regex);
}
}
}
} else {
int date_error = 0, result;
git_time_t timestamp;
git_buf datebuf = GIT_BUF_INIT;
result = disambiguate_refname(&disambiguated, repo, refspec); if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)
goto cleanup;
if (error < 0 && error != GIT_ENOTFOUND)
goto cleanup;
error = maybe_sha_or_abbrev(out, repo, git_buf_cstr(&buf));
if (result < 0) {
retcode = result;
goto cleanup; goto cleanup;
} }
git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3); error = GIT_ENOTFOUND;
date_error = git__date_parse(&timestamp, git_buf_cstr(&datebuf));
/* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ cleanup:
if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) { git_reference_free(ref);
git_reference *tracking; git_buf_free(&buf);
regfree(&preg);
git_reflog_free(reflog);
return error;
}
if (!(retcode = git_reference_remote_tracking_from_branch(&tracking, disambiguated))) { static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned int identifier)
retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_reference_name(tracking)); {
git_reference_free(tracking); git_reflog *reflog;
} int error = -1;
} unsigned int numentries;
const git_reflog_entry *entry;
bool search_by_pos = (identifier <= 100000000);
/* @{N} -> Nth prior value for the ref (from reflog) */ if (git_reflog_read(&reflog, ref) < 0)
else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) && return -1;
!git__strtol32(&n, reflogspec+2, NULL, 10) &&
n <= 100000000) { /* Allow integer time */ numentries = git_reflog_entrycount(reflog);
git_buf_puts(&buf, git_reference_name(disambiguated)); if (search_by_pos) {
if (numentries < identifier + 1) {
if (n == 0) giterr_set(
retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); GITERR_REFERENCE,
else if (!git_reflog_read(&reflog, disambiguated)) { "Reflog for '%s' has only %d entries, asked for %d",
int numentries = git_reflog_entrycount(reflog); git_reference_name(ref),
if (numentries < n + 1) { numentries,
giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d", identifier);
git_buf_cstr(&buf), numentries, n);
retcode = GIT_ENOTFOUND; error = GIT_ENOTFOUND;
} else { goto cleanup;
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);
}
}
} }
else if (!date_error) { entry = git_reflog_entry_byindex(reflog, identifier);
/* Ref as it was on a certain date */ git_oid_cpy(oid, git_reflog_entry_oidold(entry));
git_reflog *reflog; error = 0;
if (!git_reflog_read(&reflog, disambiguated)) { goto cleanup;
/* Keep walking until we find an entry older than the given date */
int numentries = git_reflog_entrycount(reflog); } else {
int i; int i;
git_time commit_time;
for (i = numentries - 1; i >= 0; i--) { for (i = numentries - 1; i >= 0; i--) {
const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i); entry = git_reflog_entry_byindex(reflog, i);
git_time commit_time = git_reflog_entry_committer(entry)->when; commit_time = git_reflog_entry_committer(entry)->when;
if (commit_time.time - timestamp <= 0) {
retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY);
break;
}
}
if (i == -1) { if (commit_time.time - identifier > 0)
/* Didn't find a match */ continue;
retcode = GIT_ENOTFOUND;
}
git_reflog_free(reflog); git_oid_cpy(oid, git_reflog_entry_oidnew(entry));
} error = 0;
goto cleanup;
} }
git_buf_free(&datebuf); error = GIT_ENOTFOUND;
} }
cleanup: cleanup:
if (reflog)
git_reflog_free(reflog); git_reflog_free(reflog);
git_buf_free(&buf); return error;
git_reference_free(disambiguated);
return retcode;
} }
static git_object* dereference_object(git_object *obj) static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, unsigned int position)
{ {
git_otype type = git_object_type(obj); git_reference *ref;
git_oid oid;
int error = -1;
switch (type) { if (*base_ref == NULL) {
case GIT_OBJ_COMMIT: if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
{ return error;
git_tree *tree = NULL; } else {
if (0 == git_commit_tree(&tree, (git_commit*)obj)) { ref = *base_ref;
return (git_object*)tree; *base_ref = NULL;
}
}
break;
case GIT_OBJ_TAG:
{
git_object *newobj = NULL;
if (0 == git_tag_target(&newobj, (git_tag*)obj)) {
return newobj;
}
} }
break;
default: if (position == 0) {
case GIT_OBJ_TREE: error = git_object_lookup(out, repo, git_reference_oid(ref), GIT_OBJ_ANY);
case GIT_OBJ_BLOB: goto cleanup;
case GIT_OBJ_OFS_DELTA:
case GIT_OBJ_REF_DELTA:
break;
} }
/* Can't dereference some types */ if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
return NULL; goto cleanup;
error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
cleanup:
git_reference_free(ref);
return error;
} }
static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
{ {
int retcode = 1; git_reference *tracking, *ref;
git_object *obj1 = obj, *obj2 = obj; int error = -1;
while (retcode > 0) {
git_otype this_type = git_object_type(obj1);
if (this_type == target_type) { if (*base_ref == NULL) {
*out = obj1; if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
retcode = 0; return error;
} else { } else {
/* Dereference once, if possible. */ ref = *base_ref;
obj2 = dereference_object(obj1); *base_ref = NULL;
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 ((error = git_branch_tracking(&tracking, ref)) < 0)
{ goto cleanup;
if (!strcmp(str, "{commit}")) return GIT_OBJ_COMMIT;
if (!strcmp(str, "{tree}")) return GIT_OBJ_TREE; *base_ref = tracking;
if (!strcmp(str, "{blob}")) return GIT_OBJ_BLOB;
if (!strcmp(str, "{tag}")) return GIT_OBJ_TAG; cleanup:
return GIT_OBJ_BAD; git_reference_free(ref);
return error;
} }
static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement) static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, int identifier_len, git_repository* repo, const char *curly_braces_content)
{ {
git_commit *commit; bool is_numeric;
size_t movementlen = strlen(movement); int parsed = 0, error = -1;
int n; git_buf identifier = GIT_BUF_INIT;
git_time_t timestamp;
if (*movement == '{') {
if (movement[movementlen-1] != '}')
return revspec_error(movement);
/* {} -> 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. */ assert(*out == NULL);
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); if (git_buf_put(&identifier, spec, identifier_len) < 0)
git_revwalk_push(walk, git_object_id(obj)); return -1;
/* Extract the regex from the movement string */ is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
git_buf_put(&buf, movement+2, strlen(movement)-3);
reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED); if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
if (reg_error != 0) { error = revspec_error(spec);
giterr_set_regex(&preg, reg_error); goto cleanup;
} 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); if (is_numeric) {
git_revwalk_free(walk); if (parsed < 0)
} error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, spec, git_buf_cstr(&identifier), -parsed);
return retcode; else
} error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
/* {...} -> Dereference until we reach an object of a certain type. */ goto cleanup;
if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) {
return GIT_ERROR;
}
return 0;
} }
/* Dereference until we reach a commit. */ if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
/* Can't dereference to a commit; fail */
return GIT_ERROR;
}
/* "^" is the same as "^1" */ goto cleanup;
if (movementlen == 0) {
n = 1;
} else {
git__strtol32(&n, movement, NULL, 10);
} }
commit = (git_commit*)obj;
/* "^0" just returns the input */ if (git__date_parse(&timestamp, curly_braces_content) < 0)
goto cleanup;
error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (unsigned int)timestamp);
cleanup:
git_buf_free(&identifier);
return error;
}
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 dereference_to_non_tag(git_object **out, git_object *obj)
{
if (git_object_type(obj) == GIT_OBJ_TAG)
return git_tag_peel(out, (git_tag *)obj);
return git_object__dup(out, obj);
}
static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
{
git_object *temp_commit = NULL;
int error;
if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0)
return -1;
if (n == 0) { if (n == 0) {
*out = obj; *out = temp_commit;
return 0; return 0;
} }
if (git_commit_parent(&commit, commit, n-1) < 0) { error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
return GIT_ENOTFOUND;
}
*out = (git_object*)commit; git_object_free(temp_commit);
return 0; return error;
} }
static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement) static int handle_linear_syntax(git_object **out, git_object *obj, int n)
{ {
int n; git_object *temp_commit = NULL;
int error;
/* Dereference until we reach a commit. */ if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0)
if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { return -1;
/* Can't dereference to a commit; fail */
return GIT_ERROR;
}
/* "~" is the same as "~1" */ error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
if (*movement == '\0') {
n = 1;
} else if (git__strtol32(&n, movement, NULL, 10) < 0) {
return GIT_ERROR;
}
return git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)obj, n); git_object_free(temp_commit);
return error;
} }
static int handle_colon_syntax(git_object **out, static int handle_colon_syntax(
git_repository *repo, git_object **out,
git_object *obj, git_object *obj,
const char *path) const char *path)
{ {
git_object *tree = obj; git_object *tree;
int error = -1; int error = -1;
git_tree_entry *entry = NULL; git_tree_entry *entry = NULL;
/* Dereference until we reach a tree. */ if (git_object_peel(&tree, obj, GIT_OBJ_TREE) < 0)
if (dereference_to_type(&tree, obj, GIT_OBJ_TREE) < 0) return -1;
return GIT_ERROR;
if (*path == '\0') if (*path == '\0') {
return git_object_lookup(out, repo, git_object_id(tree), GIT_OBJ_TREE); *out = tree;
return 0;
}
/* /*
* TODO: Handle the relative path syntax * TODO: Handle the relative path syntax
...@@ -543,188 +464,367 @@ static int handle_colon_syntax(git_object **out, ...@@ -543,188 +464,367 @@ static int handle_colon_syntax(git_object **out,
if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
goto cleanup; goto cleanup;
error = git_tree_entry_to_object(out, repo, entry); error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
cleanup: cleanup:
git_tree_entry_free(entry); git_tree_entry_free(entry);
if (tree != obj)
git_object_free(tree); git_object_free(tree);
return error; return error;
} }
static int revparse_global_grep(git_object **out, git_repository *repo, const char *pattern) static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
{ {
git_revwalk *walk; int error;
int retcode = GIT_ERROR; git_oid oid;
git_object *obj;
if (!pattern[0]) { while (!(error = git_revwalk_next(&oid, walk))) {
giterr_set(GITERR_REGEX, "Empty pattern");
return GIT_ERROR; if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) &&
(error != GIT_ENOTFOUND))
return -1;
if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
*out = obj;
return 0;
}
git_object_free(obj);
} }
if (!git_revwalk_new(&walk, repo)) { if (error < 0 && error == GIT_REVWALKOVER)
error = GIT_ENOTFOUND;
return error;
}
static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
{
regex_t preg; regex_t preg;
int reg_error; git_revwalk *walk = NULL;
git_oid oid; int error = -1;
if (build_regex(&preg, pattern) < 0)
return -1;
if (git_revwalk_new(&walk, repo) < 0)
goto cleanup;
git_revwalk_sorting(walk, GIT_SORT_TIME); git_revwalk_sorting(walk, GIT_SORT_TIME);
git_revwalk_push_glob(walk, "refs/heads/*");
reg_error = regcomp(&preg, pattern, REG_EXTENDED); if (spec_oid == NULL) {
if (reg_error != 0) { // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails
giterr_set_regex(&preg, reg_error); if (git_revwalk_push_glob(walk, "refs/heads/*") < 0)
} else { goto cleanup;
git_object *walkobj = NULL, *resultobj = NULL; } else if (git_revwalk_push(walk, spec_oid) < 0)
while(!git_revwalk_next(&oid, walk)) { goto cleanup;
/* Fetch the commit object, and check for matches in the message */
if (walkobj != resultobj) git_object_free(walkobj); error = walk_and_search(out, walk, &preg);
if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) {
if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { cleanup:
/* Match! */
resultobj = walkobj;
retcode = 0;
break;
}
}
}
if (!resultobj) {
giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern);
retcode = GIT_ENOTFOUND;
git_object_free(walkobj);
} else {
*out = resultobj;
}
regfree(&preg); regfree(&preg);
git_revwalk_free(walk); git_revwalk_free(walk);
return error;
}
static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
{
git_otype expected_type;
if (*curly_braces_content == '\0')
return dereference_to_non_tag(out, obj);
if (*curly_braces_content == '/')
return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
expected_type = parse_obj_type(curly_braces_content);
if (expected_type == GIT_OBJ_BAD)
return -1;
return git_object_peel(out, obj, expected_type);
}
static int extract_curly_braces_content(git_buf *buf, const char *spec, int *pos)
{
git_buf_clear(buf);
assert(spec[*pos] == '^' || spec[*pos] == '@');
(*pos)++;
if (spec[*pos] == '\0' || spec[*pos] != '{')
return revspec_error(spec);
(*pos)++;
while (spec[*pos] != '}') {
if (spec[*pos] == '\0')
return revspec_error(spec);
git_buf_putc(buf, spec[(*pos)++]);
} }
(*pos)++;
return 0;
}
static int extract_path(git_buf *buf, const char *spec, int *pos)
{
git_buf_clear(buf);
assert(spec[*pos] == ':');
(*pos)++;
if (git_buf_puts(buf, spec + *pos) < 0)
return -1;
*pos += git_buf_len(buf);
return 0;
}
static int extract_how_many(int *n, const char *spec, int *pos)
{
const char *end_ptr;
int parsed, accumulated;
char kind = spec[*pos];
assert(spec[*pos] == '^' || spec[*pos] == '~');
accumulated = 0;
do {
do {
(*pos)++;
accumulated++;
} while (spec[(*pos)] == kind && kind == '~');
if (git__isdigit(spec[*pos])) {
if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0)
return revspec_error(spec);
accumulated += (parsed - 1);
*pos = end_ptr - spec;
} }
return retcode; } while (spec[(*pos)] == kind && kind == '~');
*n = accumulated;
return 0;
}
static int object_from_reference(git_object **object, git_reference *reference)
{
git_reference *resolved = NULL;
int error;
if (git_reference_resolve(&resolved, reference) < 0)
return -1;
error = git_object_lookup(object, reference->owner, git_reference_oid(resolved), GIT_OBJ_ANY);
git_reference_free(resolved);
return error;
}
static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, int identifier_len, git_repository *repo, bool allow_empty_identifier)
{
int error;
git_buf identifier = GIT_BUF_INIT;
if (*object != NULL)
return 0;
if (*reference != NULL) {
if ((error = object_from_reference(object, *reference)) < 0)
return error;
git_reference_free(*reference);
*reference = NULL;
return 0;
}
if (!allow_empty_identifier && identifier_len == 0)
return revspec_error(spec);
if (git_buf_put(&identifier, spec, identifier_len) < 0)
return -1;
error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));
git_buf_free(&identifier);
return error;
}
static int ensure_base_rev_is_not_known_yet(git_object *object, const char *spec)
{
if (object == NULL)
return 0;
return revspec_error(spec);
}
static bool any_left_hand_identifier(git_object *object, git_reference *reference, int identifier_len)
{
if (object != NULL)
return true;
if (reference != NULL)
return true;
if (identifier_len > 0)
return true;
return false;
}
static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference, const char *spec)
{
if (!ensure_base_rev_is_not_known_yet(object, spec) && reference == NULL)
return 0;
return revspec_error(spec);
} }
int git_revparse_single(git_object **out, git_repository *repo, const char *spec) 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; int pos = 0, identifier_len = 0;
const char *spec_cur = spec; int error = -1, n;
git_object *cur_obj = NULL, *next_obj = NULL; git_buf buf = GIT_BUF_INIT;
git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT;
int retcode = 0; git_reference *reference = NULL;
git_object *base_rev = NULL;
assert(out && repo && spec); assert(out && repo && spec);
if (spec[0] == ':') { *out = NULL;
if (spec[1] == '/') {
return revparse_global_grep(out, repo, spec+2); while (spec[pos]) {
} switch (spec[pos]) {
/* TODO: support merge-stage path lookup (":2:Makefile"). */ case '^':
giterr_set(GITERR_INVALID, "Unimplemented"); if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
return GIT_ERROR; goto cleanup;
}
if (spec[pos+1] == '{') {
git_object *temp_object = NULL;
while (current_state != REVPARSE_STATE_DONE) { if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
switch (current_state) { goto cleanup;
case REVPARSE_STATE_INIT:
if (!*spec_cur) { if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
/* No operators, just a name. Find it and return. */ goto cleanup;
retcode = revparse_lookup_object(out, repo, spec);
next_state = REVPARSE_STATE_DONE; git_object_free(base_rev);
} else if (*spec_cur == '@') { base_rev = temp_object;
/* '@' 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 { } else {
git_buf_putc(&specbuffer, *spec_cur); git_object *temp_object = NULL;
}
spec_cur++; if ((error = extract_how_many(&n, spec, &pos)) < 0)
goto cleanup;
if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
goto cleanup;
if (current_state != next_state && next_state != REVPARSE_STATE_DONE) { git_object_free(base_rev);
/* Leaving INIT state, find the object specified, in case that state needs it */ base_rev = temp_object;
if ((retcode = revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer))) < 0)
next_state = REVPARSE_STATE_DONE;
} }
break; break;
case '~':
{
git_object *temp_object = NULL;
case REVPARSE_STATE_CARET: if ((error = extract_how_many(&n, spec, &pos)) < 0)
/* Gather characters until NULL, '~', or '^' */ goto cleanup;
if (!*spec_cur) {
retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
next_state = REVPARSE_STATE_DONE; goto cleanup;
} else if (*spec_cur == '~') {
retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
git_buf_clear(&stepbuffer); goto cleanup;
next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE;
} else if (*spec_cur == '^') { git_object_free(base_rev);
retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); base_rev = temp_object;
git_buf_clear(&stepbuffer); break;
if (retcode < 0) {
next_state = REVPARSE_STATE_DONE;
} }
} else if (*spec_cur == ':') {
retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); case ':':
git_buf_clear(&stepbuffer); {
next_state = !retcode ? REVPARSE_STATE_COLON : REVPARSE_STATE_DONE; git_object *temp_object = NULL;
if ((error = extract_path(&buf, spec, &pos)) < 0)
goto cleanup;
if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
goto cleanup;
if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
goto cleanup;
} else {
if (*git_buf_cstr(&buf) == '/') {
if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
goto cleanup;
} else { } else {
git_buf_putc(&stepbuffer, *spec_cur);
}
spec_cur++;
break;
case REVPARSE_STATE_LINEAR: /*
if (!*spec_cur) { * TODO: support merge-stage path lookup (":2:Makefile")
retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer)); * and plain index blob lookup (:i-am/a/blob)
next_state = REVPARSE_STATE_DONE; */
} else if (*spec_cur == '~') { giterr_set(GITERR_INVALID, "Unimplemented");
retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); error = GIT_ERROR;
git_buf_clear(&stepbuffer); goto cleanup;
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: git_object_free(base_rev);
if (*spec_cur) { base_rev = temp_object;
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; break;
}
case '@':
{
git_object *temp_object = NULL;
if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
goto cleanup;
if ((error = ensure_base_rev_is_not_known_yet(base_rev, spec)) < 0)
goto cleanup;
if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
goto cleanup;
case REVPARSE_STATE_DONE: if (temp_object != NULL)
if (cur_obj && *out != cur_obj) git_object_free(cur_obj); base_rev = temp_object;
if (next_obj && *out != next_obj) git_object_free(next_obj);
break; break;
} }
current_state = next_state; default:
if (cur_obj != next_obj) { if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference, spec)) < 0)
if (cur_obj) git_object_free(cur_obj); goto cleanup;
cur_obj = next_obj;
pos++;
identifier_len++;
} }
} }
if (*out != cur_obj) git_object_free(cur_obj); if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj); goto cleanup;
*out = base_rev;
error = 0;
git_buf_free(&specbuffer); cleanup:
git_buf_free(&stepbuffer); if (error)
return retcode; git_object_free(base_rev);
git_reference_free(reference);
git_buf_free(&buf);
return error;
} }
...@@ -99,6 +99,8 @@ int git_status_foreach_ext( ...@@ -99,6 +99,8 @@ int git_status_foreach_ext(
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
/* TODO: support EXCLUDE_SUBMODULES flag */ /* TODO: support EXCLUDE_SUBMODULES flag */
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
...@@ -176,10 +178,12 @@ static int get_one_status(const char *path, unsigned int status, void *data) ...@@ -176,10 +178,12 @@ static int get_one_status(const char *path, unsigned int status, void *data)
sfi->count++; sfi->count++;
sfi->status = status; sfi->status = status;
if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) { if (sfi->count > 1 ||
(strcmp(sfi->expected, path) != 0 &&
p_fnmatch(sfi->expected, path, 0) != 0)) {
giterr_set(GITERR_INVALID, giterr_set(GITERR_INVALID,
"Ambiguous path '%s' given to git_status_file", sfi->expected); "Ambiguous path '%s' given to git_status_file", sfi->expected);
return -1; return GIT_EAMBIGUOUS;
} }
return 0; return 0;
......
...@@ -759,11 +759,12 @@ int git_tree_entry_bypath( ...@@ -759,11 +759,12 @@ int git_tree_entry_bypath(
return error; return error;
} }
static int tree_walk_post( static int tree_walk(
git_tree *tree, git_tree *tree,
git_treewalk_cb callback, git_treewalk_cb callback,
git_buf *path, git_buf *path,
void *payload) void *payload,
bool preorder)
{ {
int error = 0; int error = 0;
unsigned int i; unsigned int i;
...@@ -771,8 +772,8 @@ static int tree_walk_post( ...@@ -771,8 +772,8 @@ static int tree_walk_post(
for (i = 0; i < tree->entries.length; ++i) { for (i = 0; i < tree->entries.length; ++i) {
git_tree_entry *entry = tree->entries.contents[i]; git_tree_entry *entry = tree->entries.contents[i];
if (callback(path->ptr, entry, payload) < 0) if (preorder && callback(path->ptr, entry, payload) < 0)
continue; return -1;
if (git_tree_entry__is_tree(entry)) { if (git_tree_entry__is_tree(entry)) {
git_tree *subtree; git_tree *subtree;
...@@ -789,12 +790,15 @@ static int tree_walk_post( ...@@ -789,12 +790,15 @@ static int tree_walk_post(
if (git_buf_oom(path)) if (git_buf_oom(path))
return -1; return -1;
if (tree_walk_post(subtree, callback, path, payload) < 0) if (tree_walk(subtree, callback, path, payload, preorder) < 0)
return -1; return -1;
git_buf_truncate(path, path_len); git_buf_truncate(path, path_len);
git_tree_free(subtree); git_tree_free(subtree);
} }
if (!preorder && callback(path->ptr, entry, payload) < 0)
return -1;
} }
return 0; return 0;
...@@ -807,12 +811,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl ...@@ -807,12 +811,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl
switch (mode) { switch (mode) {
case GIT_TREEWALK_POST: case GIT_TREEWALK_POST:
error = tree_walk_post(tree, callback, &root_path, payload); error = tree_walk(tree, callback, &root_path, payload, false);
break; break;
case GIT_TREEWALK_PRE: case GIT_TREEWALK_PRE:
tree_error("Preorder tree walking is still not implemented"); error = tree_walk(tree, callback, &root_path, payload, true);
return -1; break;
default: default:
giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
......
...@@ -435,3 +435,21 @@ int git__parse_bool(int *out, const char *value) ...@@ -435,3 +435,21 @@ int git__parse_bool(int *out, const char *value)
return -1; return -1;
} }
size_t git__unescape(char *str)
{
char *scan, *pos = str;
for (scan = str; *scan; pos++, scan++) {
if (*scan == '\\' && *(scan + 1) != '\0')
scan++; /* skip '\' but include next char */
if (pos != scan)
*pos = *scan;
}
if (pos != scan) {
*pos = '\0';
}
return (pos - str);
}
...@@ -238,4 +238,13 @@ extern int git__parse_bool(int *out, const char *value); ...@@ -238,4 +238,13 @@ extern int git__parse_bool(int *out, const char *value);
*/ */
int git__date_parse(git_time_t *out, const char *date); int git__date_parse(git_time_t *out, const char *date);
/*
* Unescapes a string in-place.
*
* Edge cases behavior:
* - "jackie\" -> "jacky\"
* - "chan\\" -> "chan\"
*/
extern size_t git__unescape(char *str);
#endif /* INCLUDE_util_h__ */ #endif /* INCLUDE_util_h__ */
...@@ -59,3 +59,26 @@ void test_config_stress__comments(void) ...@@ -59,3 +59,26 @@ void test_config_stress__comments(void)
git_config_free(config); git_config_free(config);
} }
void test_config_stress__escape_subsection_names(void)
{
struct git_config_file *file;
git_config *config;
const char *str;
cl_assert(git_path_exists("git-test-config"));
cl_git_pass(git_config_file__ondisk(&file, "git-test-config"));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo"));
git_config_free(config);
cl_git_pass(git_config_file__ondisk(&file, "git-test-config"));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other"));
cl_assert(!strcmp("foo", str));
git_config_free(config);
}
...@@ -658,3 +658,23 @@ void test_core_buffer__puts_escaped(void) ...@@ -658,3 +658,23 @@ void test_core_buffer__puts_escaped(void)
git_buf_free(&a); git_buf_free(&a);
} }
static void assert_unescape(char *expected, char *to_unescape) {
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_sets(&buf, to_unescape));
git_buf_unescape(&buf);
cl_assert_equal_s(expected, buf.ptr);
cl_assert_equal_i(strlen(expected), buf.size);
git_buf_free(&buf);
}
void test_core_buffer__unescape(void)
{
assert_unescape("Escaped\\", "Es\\ca\\ped\\");
assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\");
assert_unescape("\\", "\\");
assert_unescape("\\", "\\\\");
assert_unescape("", "");
}
...@@ -208,3 +208,51 @@ void test_diff_tree__bare(void) ...@@ -208,3 +208,51 @@ void test_diff_tree__bare(void)
git_tree_free(a); git_tree_free(a);
git_tree_free(b); git_tree_free(b);
} }
void test_diff_tree__merge(void)
{
/* grabbed a couple of commit oids from the history of the attr repo */
const char *a_commit = "605812a";
const char *b_commit = "370fe9ec22";
const char *c_commit = "f5b0af1fb4f5c";
git_tree *a, *b, *c;
git_diff_list *diff1 = NULL, *diff2 = NULL;
diff_expects exp;
g_repo = cl_git_sandbox_init("attr");
cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL);
cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, a, b, &diff1));
cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, c, b, &diff2));
git_tree_free(a);
git_tree_free(b);
git_tree_free(c);
cl_git_pass(git_diff_merge(diff1, diff2));
git_diff_list_free(diff2);
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff1, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert(exp.files == 6);
cl_assert(exp.file_adds == 2);
cl_assert(exp.file_dels == 1);
cl_assert(exp.file_mods == 3);
cl_assert(exp.hunks == 6);
cl_assert(exp.lines == 59);
cl_assert(exp.line_ctxt == 1);
cl_assert(exp.line_adds == 36);
cl_assert(exp.line_dels == 22);
git_diff_list_free(diff1);
}
...@@ -107,7 +107,7 @@ void test_network_remotelocal__retrieve_advertised_references(void) ...@@ -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_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
cl_assert_equal_i(how_many_refs, 22); cl_assert_equal_i(how_many_refs, 23);
} }
void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void) 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 ...@@ -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_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs));
cl_assert_equal_i(how_many_refs, 22); cl_assert_equal_i(how_many_refs, 23);
git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */
remote = NULL; remote = NULL;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#include "buffer.h" #include "buffer.h"
#include "refspec.h" #include "refspec.h"
#include "transport.h" #include "transport.h"
#include "remote.h"
static git_remote *_remote; static git_remote *_remote;
static git_repository *_repo; static git_repository *_repo;
...@@ -27,8 +28,37 @@ void test_network_remotes__cleanup(void) ...@@ -27,8 +28,37 @@ void test_network_remotes__cleanup(void)
void test_network_remotes__parsing(void) void test_network_remotes__parsing(void)
{ {
git_remote *_remote2 = NULL;
cl_assert_equal_s(git_remote_name(_remote), "test"); cl_assert_equal_s(git_remote_name(_remote), "test");
cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
cl_assert(git_remote_pushurl(_remote) == NULL);
cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_FETCH),
"git://github.com/libgit2/libgit2");
cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_PUSH),
"git://github.com/libgit2/libgit2");
cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl"));
cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl");
cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2");
cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2");
cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_FETCH),
"git://github.com/libgit2/fetchlibgit2");
cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_PUSH),
"git://github.com/libgit2/pushlibgit2");
git_remote_free(_remote2);
}
void test_network_remotes__pushurl(void)
{
cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/notlibgit2"));
cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/notlibgit2");
cl_git_pass(git_remote_set_pushurl(_remote, NULL));
cl_assert(git_remote_pushurl(_remote) == NULL);
} }
void test_network_remotes__parsing_ssh_remote(void) void test_network_remotes__parsing_ssh_remote(void)
...@@ -81,6 +111,7 @@ void test_network_remotes__save(void) ...@@ -81,6 +111,7 @@ void test_network_remotes__save(void)
cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL)); cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL));
cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*"));
cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*"));
cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push"));
cl_git_pass(git_remote_save(_remote)); cl_git_pass(git_remote_save(_remote));
git_remote_free(_remote); git_remote_free(_remote);
_remote = NULL; _remote = NULL;
...@@ -98,6 +129,18 @@ void test_network_remotes__save(void) ...@@ -98,6 +129,18 @@ void test_network_remotes__save(void)
cl_assert(_refspec != NULL); cl_assert(_refspec != NULL);
cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*");
cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*");
cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push");
/* remove the pushurl again and see if we can save that too */
cl_git_pass(git_remote_set_pushurl(_remote, NULL));
cl_git_pass(git_remote_save(_remote));
git_remote_free(_remote);
_remote = NULL;
cl_git_pass(git_remote_load(&_remote, _repo, "upstream"));
cl_assert(git_remote_pushurl(_remote) == NULL);
} }
void test_network_remotes__fnmatch(void) void test_network_remotes__fnmatch(void)
...@@ -143,13 +186,13 @@ void test_network_remotes__list(void) ...@@ -143,13 +186,13 @@ void test_network_remotes__list(void)
git_config *cfg; git_config *cfg;
cl_git_pass(git_remote_list(&list, _repo)); cl_git_pass(git_remote_list(&list, _repo));
cl_assert(list.count == 1); cl_assert(list.count == 2);
git_strarray_free(&list); git_strarray_free(&list);
cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_repository_config(&cfg, _repo));
cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com"));
cl_git_pass(git_remote_list(&list, _repo)); cl_git_pass(git_remote_list(&list, _repo));
cl_assert(list.count == 2); cl_assert(list.count == 3);
git_strarray_free(&list); git_strarray_free(&list);
git_config_free(cfg); git_config_free(cfg);
...@@ -180,4 +223,5 @@ void test_network_remotes__add(void) ...@@ -180,4 +223,5 @@ void test_network_remotes__add(void)
cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*")); cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*"));
cl_assert(git_refspec_force(_refspec) == 1); cl_assert(git_refspec_force(_refspec) == 1);
cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*")); cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*"));
cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2");
} }
#include "clar_libgit2.h"
static git_repository *g_repo;
void test_object_peel__initialize(void)
{
cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
}
void test_object_peel__cleanup(void)
{
git_repository_free(g_repo);
}
static void assert_peel(const char* expected_sha, const char *sha, git_otype requested_type)
{
git_oid oid, expected_oid;
git_object *obj;
git_object *peeled;
cl_git_pass(git_oid_fromstr(&oid, sha));
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
cl_git_pass(git_object_peel(&peeled, obj, requested_type));
cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha));
cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled)));
git_object_free(peeled);
git_object_free(obj);
}
static void assert_peel_error(int error, const char *sha, git_otype requested_type)
{
git_oid oid;
git_object *obj;
git_object *peeled;
cl_git_pass(git_oid_fromstr(&oid, sha));
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type));
git_object_free(obj);
}
void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void)
{
assert_peel("e90810b8df3e80c413d903f631643c716887138d", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG);
assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB);
}
void test_object_peel__can_peel_a_tag(void)
{
assert_peel("e90810b8df3e80c413d903f631643c716887138d", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT);
assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE);
}
void test_object_peel__can_peel_a_commit(void)
{
assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE);
}
void test_object_peel__cannot_peel_a_tree(void)
{
assert_peel_error(GIT_EAMBIGUOUS, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_BLOB);
}
void test_object_peel__cannot_peel_a_blob(void)
{
assert_peel_error(GIT_ERROR, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_COMMIT);
}
void test_object_peel__cannot_target_any_object(void)
{
assert_peel_error(GIT_EAMBIGUOUS, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY);
}
...@@ -29,8 +29,18 @@ static int foreach_cb(git_oid *oid, void *data) ...@@ -29,8 +29,18 @@ static int foreach_cb(git_oid *oid, void *data)
return 0; return 0;
} }
/*
* $ git --git-dir tests-clar/resources/testrepo.git count-objects --verbose
* count: 43
* size: 3
* in-pack: 1640
* packs: 3
* size-pack: 425
* prune-packable: 0
* garbage: 0
*/
void test_odb_foreach__foreach(void) void test_odb_foreach__foreach(void)
{ {
cl_git_pass(git_odb_foreach(_odb, foreach_cb, NULL)); cl_git_pass(git_odb_foreach(_odb, foreach_cb, NULL));
cl_assert(nobj == 1683); cl_assert_equal_i(43 + 1640, nobj); /* count + in-pack */
} }
#include "clar_libgit2.h" #include "clar_libgit2.h"
#include "refs.h" #include "refs.h"
#include "branch.h"
static git_repository *repo; static git_repository *repo;
static git_oid branch_target_oid;
static git_object *target; static git_object *target;
static git_reference *branch;
void test_refs_branches_create__initialize(void) void test_refs_branches_create__initialize(void)
{ {
cl_fixture_sandbox("testrepo.git"); cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git")); cl_git_pass(git_repository_open(&repo, "testrepo.git"));
branch = NULL;
} }
void test_refs_branches_create__cleanup(void) void test_refs_branches_create__cleanup(void)
{ {
git_reference_free(branch);
git_object_free(target); git_object_free(target);
git_repository_free(repo); git_repository_free(repo);
...@@ -39,54 +42,24 @@ void test_refs_branches_create__can_create_a_local_branch(void) ...@@ -39,54 +42,24 @@ void test_refs_branches_create__can_create_a_local_branch(void)
{ {
retrieve_known_commit(&target, repo); retrieve_known_commit(&target, repo);
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target)));
}
void test_refs_branches_create__creating_a_local_branch_triggers_the_creation_of_a_new_direct_reference(void)
{
git_reference *branch;
retrieve_known_commit(&target, repo);
cl_git_fail(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME));
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME));
cl_assert(git_reference_type(branch) == GIT_REF_OID);
git_reference_free(branch);
} }
void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void)
{ {
retrieve_known_commit(&target, repo); retrieve_known_commit(&target, repo);
cl_git_fail(git_branch_create(&branch_target_oid, repo, "br2", target, 0)); cl_git_fail(git_branch_create(&branch, repo, "br2", target, 0));
} }
void test_refs_branches_create__can_force_create_over_an_existing_branch(void) void test_refs_branches_create__can_force_create_over_an_existing_branch(void)
{ {
retrieve_known_commit(&target, repo); retrieve_known_commit(&target, repo);
cl_git_pass(git_branch_create(&branch_target_oid, repo, "br2", target, 1)); cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1));
cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target)));
} cl_assert_equal_s("refs/heads/br2", git_reference_name(branch));
void test_refs_branches_create__can_not_create_a_branch_pointing_at_an_object_unknown_from_the_repository(void)
{
git_repository *repo2;
/* Open another instance of the same repository */
cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git")));
/* Retrieve a commit object from this different repository */
retrieve_known_commit(&target, repo2);
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0));
git_repository_free(repo2);
} }
void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void) void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void)
...@@ -94,8 +67,8 @@ void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_i ...@@ -94,8 +67,8 @@ void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_i
/* b25fa35 is a tag, pointing to another tag which points to a commit */ /* b25fa35 is a tag, pointing to another tag which points to a commit */
retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0));
cl_git_pass(git_oid_streq(&branch_target_oid, "e90810b8df3e80c413d903f631643c716887138d")); cl_git_pass(git_oid_streq(git_reference_oid(branch), "e90810b8df3e80c413d903f631643c716887138d"));
} }
void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void) void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void)
...@@ -103,11 +76,11 @@ void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit ...@@ -103,11 +76,11 @@ void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit
/* 53fc32d is the tree of commit e90810b */ /* 53fc32d is the tree of commit e90810b */
retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016");
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0));
git_object_free(target); git_object_free(target);
/* 521d87c is an annotated tag pointing to a blob */ /* 521d87c is an annotated tag pointing to a blob */
retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91");
cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0));
} }
#include "clar_libgit2.h" #include "clar_libgit2.h"
#include "refs.h" #include "refs.h"
#include "branch.h"
static git_repository *repo; static git_repository *repo;
static git_reference *fake_remote; static git_reference *fake_remote;
......
#include "clar_libgit2.h" #include "clar_libgit2.h"
#include "refs.h" #include "refs.h"
#include "branch.h"
static git_repository *repo; static git_repository *repo;
static git_reference *fake_remote; static git_reference *fake_remote;
...@@ -48,7 +47,7 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count) ...@@ -48,7 +47,7 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count)
void test_refs_branches_foreach__retrieve_all_branches(void) void test_refs_branches_foreach__retrieve_all_branches(void)
{ {
assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 10); assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 11);
} }
void test_refs_branches_foreach__retrieve_remote_branches(void) void test_refs_branches_foreach__retrieve_remote_branches(void)
...@@ -58,7 +57,7 @@ void test_refs_branches_foreach__retrieve_remote_branches(void) ...@@ -58,7 +57,7 @@ void test_refs_branches_foreach__retrieve_remote_branches(void)
void test_refs_branches_foreach__retrieve_local_branches(void) void test_refs_branches_foreach__retrieve_local_branches(void)
{ {
assert_retrieval(GIT_BRANCH_LOCAL, 8); assert_retrieval(GIT_BRANCH_LOCAL, 9);
} }
struct expectations { struct expectations {
......
#include "clar_libgit2.h"
#include "refs.h"
static git_repository *repo;
static git_reference *branch;
void test_refs_branches_lookup__initialize(void)
{
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
branch = NULL;
}
void test_refs_branches_lookup__cleanup(void)
{
git_reference_free(branch);
git_repository_free(repo);
}
void test_refs_branches_lookup__can_retrieve_a_local_branch(void)
{
cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
}
void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch(void)
{
cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE));
}
void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void)
{
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE));
}
#include "clar_libgit2.h" #include "clar_libgit2.h"
#include "branch.h" #include "refs.h"
static git_repository *repo; static git_repository *repo;
static git_reference *ref;
void test_refs_branches_move__initialize(void) void test_refs_branches_move__initialize(void)
{ {
cl_fixture_sandbox("testrepo.git"); repo = cl_git_sandbox_init("testrepo.git");
cl_git_pass(git_repository_open(&repo, "testrepo.git"));
cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/br2"));
} }
void test_refs_branches_move__cleanup(void) void test_refs_branches_move__cleanup(void)
{ {
git_repository_free(repo); git_reference_free(ref);
cl_git_sandbox_cleanup();
cl_fixture_cleanup("testrepo.git");
} }
#define NEW_BRANCH_NAME "new-branch-on-the-block" #define NEW_BRANCH_NAME "new-branch-on-the-block"
void test_refs_branches_move__can_move_a_local_branch(void) void test_refs_branches_move__can_move_a_local_branch(void)
{ {
cl_git_pass(git_branch_move(repo, "br2", NEW_BRANCH_NAME, 0)); cl_git_pass(git_branch_move(ref, NEW_BRANCH_NAME, 0));
cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(ref));
} }
void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void)
{ {
/* Downward */ /* Downward */
cl_git_pass(git_branch_move(repo, "br2", "somewhere/" NEW_BRANCH_NAME, 0)); cl_git_pass(git_branch_move(ref, "somewhere/" NEW_BRANCH_NAME, 0));
/* Upward */ /* Upward */
cl_git_pass(git_branch_move(repo, "somewhere/" NEW_BRANCH_NAME, "br2", 0)); cl_git_pass(git_branch_move(ref, "br2", 0));
} }
void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void)
{ {
/* Downward */ /* Downward */
cl_git_pass(git_branch_move(repo, "br2", "br2/" NEW_BRANCH_NAME, 0)); cl_git_pass(git_branch_move(ref, "br2/" NEW_BRANCH_NAME, 0));
/* Upward */ /* Upward */
cl_git_pass(git_branch_move(repo, "br2/" NEW_BRANCH_NAME, "br2", 0)); cl_git_pass(git_branch_move(ref, "br2", 0));
} }
void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void)
{ {
cl_git_fail(git_branch_move(repo, "br2", "master", 0)); cl_git_fail(git_branch_move(ref, "master", 0));
} }
void test_refs_branches_move__can_not_move_a_non_existing_branch(void) void test_refs_branches_move__can_not_move_a_non_branch(void)
{ {
cl_git_fail(git_branch_move(repo, "i-am-no-branch", NEW_BRANCH_NAME, 0)); git_reference *tag;
}
void test_refs_branches_move__can_force_move_over_an_existing_branch(void) cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b"));
{ cl_git_fail(git_branch_move(tag, NEW_BRANCH_NAME, 0));
cl_git_pass(git_branch_move(repo, "br2", "master", 1));
}
void test_refs_branches_move__can_not_move_a_branch_through_its_canonical_name(void) git_reference_free(tag);
{
cl_git_fail(git_branch_move(repo, "refs/heads/br2", NEW_BRANCH_NAME, 1));
} }
void test_refs_branches_move__moving_a_non_exisiting_branch_returns_ENOTFOUND(void) void test_refs_branches_move__can_force_move_over_an_existing_branch(void)
{ {
int error; cl_git_pass(git_branch_move(ref, "master", 1));
error = git_branch_move(repo, "where/am/I", NEW_BRANCH_NAME, 0);
cl_git_fail(error);
cl_assert_equal_i(GIT_ENOTFOUND, error);
} }
#include "clar_libgit2.h"
#include "refs.h"
static git_repository *repo;
static git_reference *branch;
void test_refs_branches_tracking__initialize(void)
{
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
branch = NULL;
}
void test_refs_branches_tracking__cleanup(void)
{
git_reference_free(branch);
git_repository_free(repo);
}
void test_refs_branches_tracking__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
cl_git_pass(git_branch_tracking(&tracking, branch));
cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking));
git_reference_free(branch);
git_reference_free(tracking);
}
void test_refs_branches_tracking__can_retrieve_the_local_tracking_reference_of_a_local_branch(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local"));
cl_git_pass(git_branch_tracking(&tracking, branch));
cl_assert_equal_s("refs/heads/master", git_reference_name(tracking));
git_reference_free(branch);
git_reference_free(tracking);
}
void test_refs_branches_tracking__cannot_retrieve_a_remote_tracking_reference_from_a_non_branch(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b"));
cl_git_fail(git_branch_tracking(&tracking, branch));
git_reference_free(branch);
}
void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees"));
cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch));
git_reference_free(branch);
}
...@@ -46,7 +46,7 @@ static void assert_retrieval(const char *glob, unsigned int flags, int expected_ ...@@ -46,7 +46,7 @@ static void assert_retrieval(const char *glob, unsigned int flags, int expected_
void test_refs_foreachglob__retrieve_all_refs(void) void test_refs_foreachglob__retrieve_all_refs(void)
{ {
/* 7 heads (including one packed head) + 1 note + 2 remotes + 6 tags */ /* 7 heads (including one packed head) + 1 note + 2 remotes + 6 tags */
assert_retrieval("*", GIT_REF_LISTALL, 17); assert_retrieval("*", GIT_REF_LISTALL, 18);
} }
void test_refs_foreachglob__retrieve_remote_branches(void) void test_refs_foreachglob__retrieve_remote_branches(void)
...@@ -56,7 +56,7 @@ void test_refs_foreachglob__retrieve_remote_branches(void) ...@@ -56,7 +56,7 @@ void test_refs_foreachglob__retrieve_remote_branches(void)
void test_refs_foreachglob__retrieve_local_branches(void) void test_refs_foreachglob__retrieve_local_branches(void)
{ {
assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 8); assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 9);
} }
void test_refs_foreachglob__retrieve_partially_named_references(void) void test_refs_foreachglob__retrieve_partially_named_references(void)
......
...@@ -202,3 +202,19 @@ void test_refs_read__unfound_return_ENOTFOUND(void) ...@@ -202,3 +202,19 @@ void test_refs_read__unfound_return_ENOTFOUND(void)
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/master"));
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master"));
} }
static void assert_is_branch(const char *name, bool expected_branchness)
{
git_reference *reference;
cl_git_pass(git_reference_lookup(&reference, g_repo, name));
cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference));
git_reference_free(reference);
}
void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void)
{
assert_is_branch("refs/heads/master", true);
assert_is_branch("refs/heads/packed", true);
assert_is_branch("refs/remotes/test/master", false);
assert_is_branch("refs/tags/e90810b", false);
}
#include "clar_libgit2.h"
#include "reflog.h"
static git_repository *g_repo;
static git_reflog *g_reflog;
static unsigned int entrycount;
void test_refs_reflog_drop__initialize(void)
{
git_reference *ref;
g_repo = cl_git_sandbox_init("testrepo.git");
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
git_reflog_read(&g_reflog, ref);
entrycount = git_reflog_entrycount(g_reflog);
git_reference_free(ref);
}
void test_refs_reflog_drop__cleanup(void)
{
git_reflog_free(g_reflog);
cl_git_sandbox_cleanup();
}
void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void)
{
cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0));
cl_assert_equal_i(entrycount, git_reflog_entrycount(g_reflog));
}
void test_refs_reflog_drop__can_drop_an_entry(void)
{
cl_assert(entrycount > 4);
cl_git_pass(git_reflog_drop(g_reflog, 2, 0));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
}
void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void)
{
const git_reflog_entry *before_previous, *before_next;
const git_reflog_entry *after_next;
git_oid before_next_old_oid;
cl_assert(entrycount > 4);
before_previous = git_reflog_entry_byindex(g_reflog, 3);
before_next = git_reflog_entry_byindex(g_reflog, 1);
git_oid_cpy(&before_next_old_oid, &before_next->oid_old);
cl_git_pass(git_reflog_drop(g_reflog, 2, 1));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
after_next = git_reflog_entry_byindex(g_reflog, 1);
cl_assert_equal_i(0, git_oid_cmp(&before_next->oid_cur, &after_next->oid_cur));
cl_assert(git_oid_cmp(&before_next_old_oid, &after_next->oid_old) != 0);
cl_assert_equal_i(0, git_oid_cmp(&before_previous->oid_cur, &after_next->oid_old));
}
void test_refs_reflog_drop__can_drop_the_first_entry(void)
{
cl_assert(entrycount > 2);
cl_git_pass(git_reflog_drop(g_reflog, 0, 0));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
}
void test_refs_reflog_drop__can_drop_the_last_entry(void)
{
const git_reflog_entry *entry;
cl_assert(entrycount > 2);
cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
entry = git_reflog_entry_byindex(g_reflog, entrycount - 2);
cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0);
}
void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history(void)
{
const git_reflog_entry *entry;
cl_assert(entrycount > 2);
cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
entry = git_reflog_entry_byindex(g_reflog, entrycount - 2);
cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
}
void test_refs_reflog_drop__can_drop_all_the_entries(void)
{
cl_assert(--entrycount > 0);
do {
cl_git_pass(git_reflog_drop(g_reflog, --entrycount, 1));
} while (entrycount > 0);
cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
cl_assert_equal_i(0, git_reflog_entrycount(g_reflog));
}
void test_refs_reflog_drop__can_persist_deletion_on_disk(void)
{
git_reference *ref;
cl_assert(entrycount > 2);
cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name));
cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
cl_git_pass(git_reflog_write(g_reflog));
git_reflog_free(g_reflog);
git_reflog_read(&g_reflog, ref);
git_reference_free(ref);
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
static const char *new_ref = "refs/heads/test-reflog"; static const char *new_ref = "refs/heads/test-reflog";
static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
static const char *commit_msg = "commit: bla bla"; #define commit_msg "commit: bla bla"
static git_repository *g_repo; static git_repository *g_repo;
...@@ -24,19 +24,17 @@ static void assert_signature(git_signature *expected, git_signature *actual) ...@@ -24,19 +24,17 @@ static void assert_signature(git_signature *expected, git_signature *actual)
// Fixture setup and teardown // Fixture setup and teardown
void test_refs_reflog__initialize(void) void test_refs_reflog_reflog__initialize(void)
{ {
g_repo = cl_git_sandbox_init("testrepo.git"); g_repo = cl_git_sandbox_init("testrepo.git");
} }
void test_refs_reflog__cleanup(void) void test_refs_reflog_reflog__cleanup(void)
{ {
cl_git_sandbox_cleanup(); cl_git_sandbox_cleanup();
} }
void test_refs_reflog_reflog__append_then_read(void)
void test_refs_reflog__write_then_read(void)
{ {
// write a reflog for a given reference and ensure it can be read back // write a reflog for a given reference and ensure it can be read back
git_repository *repo2; git_repository *repo2;
...@@ -44,46 +42,42 @@ void test_refs_reflog__write_then_read(void) ...@@ -44,46 +42,42 @@ void test_refs_reflog__write_then_read(void)
git_oid oid; git_oid oid;
git_signature *committer; git_signature *committer;
git_reflog *reflog; git_reflog *reflog;
git_reflog_entry *entry; const git_reflog_entry *entry;
char oid_str[GIT_OID_HEXSZ+1];
/* Create a new branch pointing at the HEAD */ /* Create a new branch pointing at the HEAD */
git_oid_fromstr(&oid, current_master_tip); git_oid_fromstr(&oid, current_master_tip);
cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0));
git_reference_free(ref);
cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref));
cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); cl_git_pass(git_signature_now(&committer, "foo", "foo@bar"));
cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); cl_git_pass(git_reflog_read(&reflog, ref));
cl_git_fail(git_reflog_write(ref, NULL, committer, "no ancestor NULL for an existing reflog"));
cl_git_fail(git_reflog_write(ref, NULL, committer, "no\nnewline")); cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline"));
cl_git_pass(git_reflog_write(ref, &oid, committer, commit_msg)); cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL));
cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n"));
cl_git_pass(git_reflog_write(reflog));
git_reflog_free(reflog);
/* Reopen a new instance of the repository */ /* Reopen a new instance of the repository */
cl_git_pass(git_repository_open(&repo2, "testrepo.git")); cl_git_pass(git_repository_open(&repo2, "testrepo.git"));
/* Lookup the preivously created branch */ /* Lookup the previously created branch */
cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref));
/* Read and parse the reflog for this branch */ /* Read and parse the reflog for this branch */
cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); cl_git_pass(git_reflog_read(&reflog, lookedup_ref));
cl_assert(reflog->entries.length == 2); cl_assert_equal_i(2, git_reflog_entrycount(reflog));
entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0); entry = git_reflog_entry_byindex(reflog, 0);
assert_signature(committer, entry->committer); assert_signature(committer, entry->committer);
git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
cl_assert_equal_s("0000000000000000000000000000000000000000", oid_str); cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0);
git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur);
cl_assert_equal_s(current_master_tip, oid_str);
cl_assert(entry->msg == NULL); cl_assert(entry->msg == NULL);
entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 1); entry = git_reflog_entry_byindex(reflog, 1);
assert_signature(committer, entry->committer); assert_signature(committer, entry->committer);
git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0);
cl_assert_equal_s(current_master_tip, oid_str); cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0);
git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur);
cl_assert_equal_s(current_master_tip, oid_str);
cl_assert_equal_s(commit_msg, entry->msg); cl_assert_equal_s(commit_msg, entry->msg);
git_signature_free(committer); git_signature_free(committer);
...@@ -94,35 +88,7 @@ void test_refs_reflog__write_then_read(void) ...@@ -94,35 +88,7 @@ void test_refs_reflog__write_then_read(void)
git_reference_free(lookedup_ref); git_reference_free(lookedup_ref);
} }
void test_refs_reflog__dont_write_bad(void) void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void)
{
// avoid writing an obviously wrong reflog
git_reference *ref;
git_oid oid;
git_signature *committer;
/* Create a new branch pointing at the HEAD */
git_oid_fromstr(&oid, current_master_tip);
cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0));
git_reference_free(ref);
cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref));
cl_git_pass(git_signature_now(&committer, "foo", "foo@bar"));
/* Write the reflog for the new branch */
cl_git_pass(git_reflog_write(ref, NULL, committer, NULL));
/* Try to update the reflog with wrong information:
* It's no new reference, so the ancestor OID cannot
* be NULL. */
cl_git_fail(git_reflog_write(ref, NULL, committer, NULL));
git_signature_free(committer);
git_reference_free(ref);
}
void test_refs_reflog__renaming_the_reference_moves_the_reflog(void)
{ {
git_reference *master; git_reference *master;
git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT;
...@@ -145,6 +111,7 @@ void test_refs_reflog__renaming_the_reference_moves_the_reflog(void) ...@@ -145,6 +111,7 @@ void test_refs_reflog__renaming_the_reference_moves_the_reflog(void)
git_buf_free(&moved_log_path); git_buf_free(&moved_log_path);
git_buf_free(&master_log_path); git_buf_free(&master_log_path);
} }
static void assert_has_reflog(bool expected_result, const char *name) static void assert_has_reflog(bool expected_result, const char *name)
{ {
git_reference *ref; git_reference *ref;
...@@ -156,9 +123,50 @@ static void assert_has_reflog(bool expected_result, const char *name) ...@@ -156,9 +123,50 @@ static void assert_has_reflog(bool expected_result, const char *name)
git_reference_free(ref); git_reference_free(ref);
} }
void test_refs_reflog__reference_has_reflog(void) void test_refs_reflog_reflog__reference_has_reflog(void)
{ {
assert_has_reflog(true, "HEAD"); assert_has_reflog(true, "HEAD");
assert_has_reflog(true, "refs/heads/master"); assert_has_reflog(true, "refs/heads/master");
assert_has_reflog(false, "refs/heads/subtrees"); assert_has_reflog(false, "refs/heads/subtrees");
} }
void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void)
{
git_reference *subtrees;
git_reflog *reflog;
git_buf subtrees_log_path = GIT_BUF_INIT;
cl_git_pass(git_reference_lookup(&subtrees, g_repo, "refs/heads/subtrees"));
git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, git_reference_name(subtrees));
cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path)));
cl_git_pass(git_reflog_read(&reflog, subtrees));
cl_assert_equal_i(0, git_reflog_entrycount(reflog));
git_reflog_free(reflog);
git_reference_free(subtrees);
git_buf_free(&subtrees_log_path);
}
void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void)
{
git_reference *master;
git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT;
git_reflog *reflog;
cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master"));
cl_git_pass(git_reflog_read(&reflog, master));
cl_git_pass(git_reflog_write(reflog));
cl_git_pass(git_reference_rename(master, "refs/moved", 0));
cl_git_fail(git_reflog_write(reflog));
git_reflog_free(reflog);
git_reference_free(master);
git_buf_free(&moved_log_path);
git_buf_free(&master_log_path);
}
#include "clar_libgit2.h"
static git_repository *g_repo;
void test_refs_remotetracking__initialize(void)
{
cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
}
void test_refs_remotetracking__cleanup(void)
{
git_repository_free(g_repo);
}
void test_refs_remotetracking__unfound_returns_GIT_ENOTFOUND(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/subtrees"));
cl_assert_equal_i(GIT_ENOTFOUND, git_reference_remote_tracking_from_branch(&tracking, branch));
git_reference_free(branch);
}
void test_refs_remotetracking__retrieving_from_a_non_head_fails(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/tags/e90810b"));
cl_git_fail(git_reference_remote_tracking_from_branch(&tracking, branch));
git_reference_free(branch);
}
void test_refs_remotetracking__can_retrieve_a_remote_tracking_branch_reference(void)
{
git_reference *branch, *tracking;
cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master"));
cl_git_pass(git_reference_remote_tracking_from_branch(&tracking, branch));
cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking));
git_reference_free(branch);
git_reference_free(tracking);
}
...@@ -64,6 +64,8 @@ void test_refs_revparse__invalid_reference_name(void) ...@@ -64,6 +64,8 @@ void test_refs_revparse__invalid_reference_name(void)
cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense^1")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense^1"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense~2")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense~2"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, ""));
} }
void test_refs_revparse__shas(void) void test_refs_revparse__shas(void)
...@@ -75,6 +77,9 @@ void test_refs_revparse__shas(void) ...@@ -75,6 +77,9 @@ void test_refs_revparse__shas(void)
void test_refs_revparse__head(void) void test_refs_revparse__head(void)
{ {
test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
} }
void test_refs_revparse__full_refs(void) void test_refs_revparse__full_refs(void)
...@@ -99,12 +104,18 @@ void test_refs_revparse__describe_output(void) ...@@ -99,12 +104,18 @@ void test_refs_revparse__describe_output(void)
void test_refs_revparse__nth_parent(void) void test_refs_revparse__nth_parent(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^-1"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "^"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^{tree}^"));
test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("be3563a^42", NULL); test_object("be3563a^42", NULL);
} }
...@@ -113,34 +124,56 @@ void test_refs_revparse__not_tag(void) ...@@ -113,34 +124,56 @@ void test_refs_revparse__not_tag(void)
{ {
test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d");
test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d");
test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d");
} }
void test_refs_revparse__to_type(void) void test_refs_revparse__to_type(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{trip}"));
test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}"));
} }
void test_refs_revparse__linear_history(void) void test_refs_revparse__linear_history(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "~"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "foo~bar")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "foo~bar"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~bar")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~bar"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~-1"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~0bar"));
test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a");
} }
void test_refs_revparse__chaining(void) void test_refs_revparse__chaining(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{0}@{0}"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{u}@{-1}"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1}@{-1}"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-3}@{0}"));
test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479"); test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479");
test_object("master^^1^2^1", NULL);
} }
void test_refs_revparse__upstream(void) void test_refs_revparse__upstream(void)
...@@ -159,6 +192,10 @@ void test_refs_revparse__ordinal(void) ...@@ -159,6 +192,10 @@ void test_refs_revparse__ordinal(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{-2}")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{-2}"));
/* TODO: make the test below actually fail
* cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}"));
*/
test_object("nope@{0}", NULL); test_object("nope@{0}", NULL);
test_object("master@{31415}", NULL); test_object("master@{31415}", NULL);
test_object("@{1000}", NULL); test_object("@{1000}", NULL);
...@@ -177,6 +214,7 @@ void test_refs_revparse__previous_head(void) ...@@ -177,6 +214,7 @@ void test_refs_revparse__previous_head(void)
{ {
cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-xyz}")); 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, "@{-0}"));
cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1b}"));
test_object("@{-42}", NULL); test_object("@{-42}", NULL);
...@@ -353,6 +391,7 @@ void test_refs_revparse__colon(void) ...@@ -353,6 +391,7 @@ void test_refs_revparse__colon(void)
test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd");
test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9"); test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9");
test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057");
test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc");
} }
void test_refs_revparse__disambiguation(void) void test_refs_revparse__disambiguation(void)
......
...@@ -7,6 +7,14 @@ ...@@ -7,6 +7,14 @@
url = git://github.com/libgit2/libgit2 url = git://github.com/libgit2/libgit2
fetch = +refs/heads/*:refs/remotes/test/* fetch = +refs/heads/*:refs/remotes/test/*
[remote "test_with_pushurl"]
url = git://github.com/libgit2/fetchlibgit2
pushurl = git://github.com/libgit2/pushlibgit2
fetch = +refs/heads/*:refs/remotes/test_with_pushurl/*
[branch "master"] [branch "master"]
remote = test remote = test
merge = refs/heads/master merge = refs/heads/master
[branch "track-local"]
remote = .
merge = refs/heads/master
0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git 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: be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0900 commit:
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 5b5b025afb0b4c913b4c338a42934a3863bf3644 Ben Straub <bstraub@github.com> 1335806604 -0900 checkout: moving from master to 5b5b025
5b5b025afb0b4c913b4c338a42934a3863bf3644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806605 -0900 checkout: moving from 5b5b025 to master
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0900 checkout: moving from master to br2 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 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 a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806621 -0900 checkout: moving from br2 to master
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806565 -0800 update by push
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806688 -0800 update by push
9fd738e8f7967c078dceed8190330fc8648ee56a
...@@ -517,6 +517,85 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) ...@@ -517,6 +517,85 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void)
cl_git_pass(p_unlink("my-index")); cl_git_pass(p_unlink("my-index"));
} }
void test_status_worktree__bracket_in_filename(void)
{
git_repository *repo;
git_index *index;
status_entry_single result;
unsigned int status_flags;
int error;
#define FILE_WITH_BRACKET "LICENSE[1].md"
#define FILE_WITHOUT_BRACKET "LICENSE1.md"
cl_git_pass(git_repository_init(&repo, "with_bracket", 0));
cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n");
/* file is new to working directory */
memset(&result, 0, sizeof(result));
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
cl_assert_equal_i(1, result.count);
cl_assert(result.status == GIT_STATUS_WT_NEW);
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
cl_assert(status_flags == GIT_STATUS_WT_NEW);
/* ignore the file */
cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n");
memset(&result, 0, sizeof(result));
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
cl_assert_equal_i(2, result.count);
cl_assert(result.status == GIT_STATUS_IGNORED);
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
cl_assert(status_flags == GIT_STATUS_IGNORED);
/* don't ignore the file */
cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n");
memset(&result, 0, sizeof(result));
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
cl_assert_equal_i(2, result.count);
cl_assert(result.status == GIT_STATUS_WT_NEW);
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
cl_assert(status_flags == GIT_STATUS_WT_NEW);
/* add the file to the index */
cl_git_pass(git_repository_index(&index, repo));
cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0));
cl_git_pass(git_index_write(index));
memset(&result, 0, sizeof(result));
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
cl_assert_equal_i(2, result.count);
cl_assert(result.status == GIT_STATUS_INDEX_NEW);
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
/* Create file without bracket */
cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n");
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET));
cl_assert(status_flags == GIT_STATUS_WT_NEW);
cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"));
cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET);
cl_git_fail(error);
cl_assert(error == GIT_EAMBIGUOUS);
git_index_free(index);
git_repository_free(repo);
}
void test_status_worktree__space_in_filename(void) void test_status_worktree__space_in_filename(void)
{ {
...@@ -647,3 +726,49 @@ void test_status_worktree__filemode_changes(void) ...@@ -647,3 +726,49 @@ void test_status_worktree__filemode_changes(void)
git_config_free(cfg); git_config_free(cfg);
} }
static int cb_status__expected_path(const char *p, unsigned int s, void *payload)
{
const char *expected_path = (const char *)payload;
GIT_UNUSED(s);
if (payload == NULL)
cl_fail("Unexpected path");
cl_assert_equal_s(expected_path, p);
return 0;
}
void test_status_worktree__disable_pathspec_match(void)
{
git_repository *repo;
git_status_options opts;
char *file_with_bracket = "LICENSE[1].md",
*imaginary_file_with_bracket = "LICENSE[1-2].md";
cl_git_pass(git_repository_init(&repo, "pathspec", 0));
cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n");
cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n");
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
opts.pathspec.count = 1;
opts.pathspec.strings = &file_with_bracket;
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__expected_path,
file_with_bracket)
);
/* Test passing a pathspec matching files in the workdir. */
/* Must not match because pathspecs are disabled. */
opts.pathspec.strings = &imaginary_file_with_bracket;
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL)
);
git_repository_free(repo);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment