diff options
| author | Ben Straub <bstraub@github.com> | 2012-07-27 11:17:21 -0700 |
|---|---|---|
| committer | Ben Straub <bstraub@github.com> | 2012-07-27 11:17:21 -0700 |
| commit | a4827a5b5c2a9d2d8a35a5a76adcc9881a11b07b (patch) | |
| tree | 137edda22ef9ccdf75e847cb504a11efa2de0e18 /src | |
| parent | 15445f9ef7fea43a550a78c7425ae91c53a1c108 (diff) | |
| parent | 31637cd56a5ed339fdebc9cd51af8f50a7794f24 (diff) | |
| download | libgit2-a4827a5b5c2a9d2d8a35a5a76adcc9881a11b07b.tar.gz | |
Merge remote-tracking branch 'upstream/development' into test-merge
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr_file.c | 12 | ||||
| -rw-r--r-- | src/branch.c | 128 | ||||
| -rw-r--r-- | src/branch.h | 17 | ||||
| -rw-r--r-- | src/buffer.c | 4 | ||||
| -rw-r--r-- | src/buffer.h | 3 | ||||
| -rw-r--r-- | src/common.h | 10 | ||||
| -rw-r--r-- | src/config_file.c | 7 | ||||
| -rw-r--r-- | src/diff.c | 28 | ||||
| -rw-r--r-- | src/global.h | 4 | ||||
| -rw-r--r-- | src/object.c | 69 | ||||
| -rw-r--r-- | src/pool.c | 5 | ||||
| -rw-r--r-- | src/pool.h | 7 | ||||
| -rw-r--r-- | src/reflog.c | 321 | ||||
| -rw-r--r-- | src/reflog.h | 3 | ||||
| -rw-r--r-- | src/refs.c | 73 | ||||
| -rw-r--r-- | src/remote.c | 112 | ||||
| -rw-r--r-- | src/remote.h | 4 | ||||
| -rw-r--r-- | src/repository.h | 7 | ||||
| -rw-r--r-- | src/revparse.c | 1056 | ||||
| -rw-r--r-- | src/status.c | 8 | ||||
| -rw-r--r-- | src/tree.c | 20 | ||||
| -rw-r--r-- | src/util.c | 18 | ||||
| -rw-r--r-- | src/util.h | 9 |
23 files changed, 1184 insertions, 741 deletions
diff --git a/src/attr_file.c b/src/attr_file.c index 0dad09727..837c42d8e 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -426,17 +426,7 @@ int git_attr_fnmatch__parse( return -1; } else { /* strip '\' that might have be used for internal whitespace */ - char *to = 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); - } + spec->length = git__unescape(spec->pattern); } return 0; diff --git a/src/branch.c b/src/branch.c index 671e42051..52fed67ad 100644 --- a/src/branch.c +++ b/src/branch.c @@ -7,8 +7,11 @@ #include "common.h" #include "commit.h" -#include "branch.h" #include "tag.h" +#include "config.h" +#include "refspec.h" + +#include "git2/branch.h" static int retrieve_branch_reference( git_reference **branch_reference_out, @@ -48,8 +51,8 @@ static int create_error_invalid(const char *msg) } int git_branch_create( - git_oid *oid_out, - git_repository *repo, + git_reference **ref_out, + git_repository *repository, const char *branch_name, const git_object *target, int force) @@ -60,10 +63,8 @@ int git_branch_create( git_buf canonical_branch_name = GIT_BUF_INIT; int error = -1; - assert(repo && branch_name && target && oid_out); - - if (git_object_owner(target) != repo) - return create_error_invalid("The given target does not belong to this repository"); + assert(branch_name && target && ref_out); + assert(git_object_owner(target) == repository); target_type = git_object_type(target); @@ -90,17 +91,17 @@ int git_branch_create( if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) 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; - git_oid_cpy(oid_out, git_reference_oid(branch)); + *ref_out = branch; error = 0; cleanup: if (target_type == GIT_OBJ_TAG) git_object_free(commit); - git_reference_free(branch); git_buf_free(&canonical_branch_name); return error; } @@ -111,6 +112,7 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_ git_reference *head = NULL; int error; + assert(repo && branch_name); 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) @@ -183,28 +185,110 @@ int git_branch_foreach( 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) +{ + giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref)); + return -1; +} + +int git_branch_move( + git_reference *branch, + const char *new_branch_name, + int force) { - git_reference *reference = NULL; - git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; - int error = 0; + git_buf new_reference_name = GIT_BUF_INIT; + int error; - if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) - goto cleanup; + assert(branch && new_branch_name); - /* We need to be able to return GIT_ENOTFOUND */ - 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) 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: - git_reference_free(reference); - git_buf_free(&old_reference_name); git_buf_free(&new_reference_name); 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; +} diff --git a/src/branch.h b/src/branch.h deleted file mode 100644 index d0e5abc8b..000000000 --- a/src/branch.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 diff --git a/src/buffer.c b/src/buffer.c index 5d54ee1a5..b57998e1b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -496,3 +496,7 @@ bool git_buf_is_binary(const git_buf *buf) return ((printable >> 7) < nonprintable); } +void git_buf_unescape(git_buf *buf) +{ + buf->size = git__unescape(buf->ptr); +} diff --git a/src/buffer.h b/src/buffer.h index 75f3b0e4f..17922e408 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -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 */ bool git_buf_is_binary(const git_buf *buf); +/* Unescape all characters in a buffer */ +void git_buf_unescape(git_buf *buf); + #endif diff --git a/src/common.h b/src/common.h index 419a8d06b..1db308fc7 100644 --- a/src/common.h +++ b/src/common.h @@ -49,15 +49,6 @@ #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; } void giterr_set_oom(void); @@ -68,5 +59,4 @@ void giterr_set_regex(const regex_t *regex, int error_code); #include "util.h" - #endif /* INCLUDE_common_h__ */ diff --git a/src/config_file.c b/src/config_file.c index 1f3ebfca9..7ced1e5ba 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -983,9 +983,12 @@ static int write_section(git_filebuf *file, const char *key) if (dot == NULL) { git_buf_puts(&buf, key); } else { + char *escaped; git_buf_put(&buf, key, dot - key); - /* TODO: escape */ - git_buf_printf(&buf, " \"%s\"", dot + 1); + escaped = escape_value(dot + 1); + GITERR_CHECK_ALLOC(escaped); + git_buf_printf(&buf, " \"%s\"", escaped); + git__free(escaped); } git_buf_puts(&buf, "]\n"); diff --git a/src/diff.c b/src/diff.c index 4fea894f8..2b1529d63 100644 --- a/src/diff.c +++ b/src/diff.c @@ -20,14 +20,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec) return NULL; /* 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); - if (prefix.size > 0) - return git_buf_detach(&prefix); + if (prefix.size <= 0) { + git_buf_free(&prefix); + return NULL; + } - git_buf_free(&prefix); - return NULL; + git_buf_unescape(&prefix); + + return git_buf_detach(&prefix); } 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) return true; 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 (result == FNM_NOMATCH && @@ -814,9 +825,9 @@ int git_diff_merge( /* prefix strings also come from old pool, so recreate those.*/ 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 = - 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) @@ -826,4 +837,3 @@ int git_diff_merge( return error; } - diff --git a/src/global.h b/src/global.h index 2b525ce07..6e7373fa3 100644 --- a/src/global.h +++ b/src/global.h @@ -10,10 +10,6 @@ #include "mwindow.h" typedef struct { - struct { - char last[1024]; - } error; - git_error *last_error; git_error error_t; diff --git a/src/object.c b/src/object.c index 14d64befe..3ff894212 100644 --- a/src/object.c +++ b/src/object.c @@ -333,3 +333,72 @@ int git_object__resolve_to_type(git_object **obj, git_otype type) *obj = scan; 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; +} diff --git a/src/pool.c b/src/pool.c index 63bf09cee..64b5c6b00 100644 --- a/src/pool.c +++ b/src/pool.c @@ -206,6 +206,11 @@ char *git_pool_strdup(git_pool *pool, const char *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) { void *ptr; diff --git a/src/pool.h b/src/pool.h index 54a2861ed..05d339244 100644 --- a/src/pool.h +++ b/src/pool.h @@ -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); /** + * 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. * * This is allowed only for pools with item_size == sizeof(char) diff --git a/src/reflog.c b/src/reflog.c index 004ba936d..53cc25fa1 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -28,66 +28,68 @@ static int reflog_init(git_reflog **reflog, git_reference *ref) return -1; } + log->owner = git_reference_owner(ref); *reflog = log; return 0; } -static int reflog_write(const char *log_path, const char *oid_old, - const char *oid_new, const git_signature *committer, - const char *msg) +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) { - int error; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; - bool trailing_newline = false; + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; - 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) { - 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_clear(buf); - git_buf_puts(&log, oid_old); - git_buf_putc(&log, ' '); + git_buf_puts(buf, raw_old); + 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); - git_buf_truncate(&log, log.size - 1); /* drop LF */ + /* drop trailing LF */ + git_buf_rtrim(buf); if (msg) { - git_buf_putc(&log, '\t'); - git_buf_puts(&log, msg); + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); } - if (!trailing_newline) - git_buf_putc(&log, '\n'); + git_buf_putc(buf, '\n'); - if (git_buf_oom(&log)) { - git_buf_free(&log); - return -1; - } + return git_buf_oom(buf); +} - error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); - if (!error) { - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - git_filebuf_cleanup(&fbuf); - else - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - } +static int reflog_entry_new(git_reflog_entry **entry) +{ + git_reflog_entry *e; - 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) @@ -105,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) } while (0) while (buf_size > GIT_REFLOG_SIZE_MIN) { - entry = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(entry); + if (reflog_entry_new(&entry) < 0) + return -1; entry->committer = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(entry->committer); @@ -153,10 +155,9 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) #undef seek_forward fail: - if (entry) { - git__free(entry->committer); - git__free(entry); - } + if (entry) + reflog_entry_free(entry); + return -1; } @@ -168,10 +169,7 @@ void git_reflog_free(git_reflog *reflog) for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); + reflog_entry_free(entry); } git_vector_free(&reflog->entries); @@ -179,6 +177,24 @@ void git_reflog_free(git_reflog *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 error; @@ -188,83 +204,136 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) *reflog = NULL; + assert(reflog && ref); + if (reflog_init(&log, ref) < 0) return -1; - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + if (retrieve_reflog_path(&log_path, ref) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; - if (!error) - error = git_futils_readbuffer(&log_file, log_path.ptr); + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; - if (!error) - error = reflog_parse(log, log_file.ptr, log_file.size); + *reflog = log; + goto success; - if (!error) - *reflog = log; - else - git_reflog_free(log); +cleanup: + git_reflog_free(log); +success: git_buf_free(&log_file); git_buf_free(&log_path); return error; } -int git_reflog_write(git_reference *ref, const git_oid *oid_old, - const git_signature *committer, const char *msg) +int git_reflog_write(git_reflog *reflog) { - int error; - char old[GIT_OID_HEXSZ+1]; - char new[GIT_OID_HEXSZ+1]; + int error = -1; + unsigned int i; + git_reflog_entry *entry; git_buf log_path = GIT_BUF_INIT; - git_reference *r; - const git_oid *oid; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(reflog); - if ((error = git_reference_resolve(&r, ref)) < 0) - 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); + if (git_buf_join_n(&log_path, '/', 3, + git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) return -1; + + 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; + } + + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; } + + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} - git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); +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; - git_reference_free(r); + assert(reflog && new_oid && committer); - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); - if (error < 0) + if (reflog_entry_new(&entry) < 0) + return -1; + + if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; - if (git_path_exists(log_path.ptr) == false) { - error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); - } 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 (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'; + } } - if (error < 0) - goto cleanup; - if (oid_old) - git_oid_tostr(old, sizeof(old), oid_old); - else - p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 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; - error = reflog_write(log_path.ptr, old, new, committer, msg); + 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: - git_buf_free(&log_path); - return error; + reflog_entry_free(entry); + return -1; } 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); - 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; 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) int error; git_buf path = GIT_BUF_INIT; - error = git_buf_join_n( - &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = retrieve_reflog_path(&path, ref); if (!error && git_path_exists(path.ptr)) error = p_unlink(path.ptr); @@ -370,3 +438,52 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) assert(entry); 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; +} diff --git a/src/reflog.h b/src/reflog.h index 33cf0776c..3bbdf6e10 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -17,6 +17,8 @@ #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) +#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" + struct git_reflog_entry { git_oid oid_old; git_oid oid_cur; @@ -28,6 +30,7 @@ struct git_reflog_entry { struct git_reflog { char *ref_name; + git_repository *owner; git_vector entries; }; diff --git a/src/refs.c b/src/refs.c index b3c140bec..32f54fc31 100644 --- a/src/refs.c +++ b/src/refs.c @@ -11,7 +11,6 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" -#include "config.h" #include <git2/tag.h> #include <git2/object.h> @@ -1816,75 +1815,9 @@ int git_reference_has_log( return result; } -//TODO: How about also taking care of local tracking branches? -//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) +int git_reference_is_branch(git_reference *ref) { - git_config *config = NULL; - 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)); + assert(ref); -cleanup: - git_config_free(config); - git_buf_free(&buf); - return error; + return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0; } diff --git a/src/remote.c b/src/remote.c index 00e108a0a..c2bfb09bb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -131,6 +131,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) GITERR_CHECK_ALLOC(remote->url); 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) { error = -1; goto cleanup; @@ -179,7 +199,7 @@ int git_remote_save(const git_remote *remote) if (git_repository_config__weakptr(&config, remote->repo) < 0) 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; 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) 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) { git_buf_clear(&buf); git_buf_clear(&value); @@ -238,6 +278,38 @@ const char *git_remote_url(git_remote *remote) 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) { git_refspec refspec; @@ -284,13 +356,33 @@ const git_refspec *git_remote_pushspec(git_remote *remote) 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) { git_transport *t; + const char *url; 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; t->check_cert = remote->check_cert; @@ -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); } -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; unsigned int i = 0; @@ -377,12 +469,12 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co continue; if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) - break; + goto on_error; git_reference_free(ref); - if (cb != NULL) { - if (cb(refname.ptr, &old, &head->oid) < 0) + if (remote->callbacks.update_tips != NULL) { + if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.data) < 0) goto on_error; } } @@ -429,6 +521,7 @@ void git_remote_free(git_remote *remote) git__free(remote->push.src); git__free(remote->push.dst); git__free(remote->url); + git__free(remote->pushurl); git__free(remote->name); git__free(remote); } @@ -525,3 +618,10 @@ void git_remote_check_cert(git_remote *remote, int 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)); +} diff --git a/src/remote.h b/src/remote.h index 0949ad434..5083b9912 100644 --- a/src/remote.h +++ b/src/remote.h @@ -14,13 +14,17 @@ struct git_remote { char *name; char *url; + char *pushurl; git_vector refs; struct git_refspec fetch; struct git_refspec push; git_transport *transport; git_repository *repo; + git_remote_callbacks callbacks; unsigned int need_pack:1, check_cert; }; +const char* git_remote__urlfordirection(struct git_remote *remote, int direction); + #endif diff --git a/src/repository.h b/src/repository.h index 91c69a655..4e03e632b 100644 --- a/src/repository.h +++ b/src/repository.h @@ -98,6 +98,13 @@ struct git_repository { * export */ 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_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); diff --git a/src/revparse.c b/src/revparse.c index 777dee685..e2c0de612 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -13,48 +13,12 @@ #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) { giterr_set(GITERR_INVALID, "Failed to parse revision specifier - Invalid pattern '%s'", revspec); 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(®ex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - return regex_error; - } - - retcode = regexec(®ex, spec, 0, NULL, 0); - regfree(®ex); - return retcode == 0; -} - static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname) { int error, i; @@ -75,7 +39,7 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const if (*refname) git_buf_puts(&name, refname); else { - git_buf_puts(&name, "HEAD"); + git_buf_puts(&name, GIT_HEAD_FILE); fallbackmode = false; } @@ -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); } +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) { const char *substr; - int match; + int error; + regex_t regex; - /* "git describe" output; snip everything before/including "-g" */ substr = strstr(spec, "-g"); if (substr == NULL) return GIT_ENOTFOUND; - if ((match = spec_looks_like_describe_output(spec)) < 0) - return match; + if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) + return -1; + + error = regexec(®ex, spec, 0, NULL, 0); + regfree(®ex); - if (!match) + if (error) return GIT_ENOTFOUND; 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 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__isdigit(str[i])) return 0; + if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0) + return -1; - return 1; + if (*end_ptr != '\0') + 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; - int n, retcode = GIT_ERROR; - int i, refloglen; + regex_t preg; + int numentries, i, cur, error = -1; const git_reflog_entry *entry; + const char *msg; + regmatch_t regexmatches[2]; git_buf buf = GIT_BUF_INIT; - size_t refspeclen = strlen(refspec); - size_t reflogspeclen = strlen(reflogspec); - if (git__prefixcmp(reflogspec, "@{") != 0 || - git__suffixcmp(reflogspec, "}") != 0) - return revspec_error(reflogspec); + cur = position; - /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */ - if (!git__prefixcmp(reflogspec, "@{-")) { - regex_t regex; - int regex_error; + if (*identifier != '\0' || *base_ref != NULL) + return revspec_error(spec); - if (refspeclen > 0) - return revspec_error(reflogspec); + if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) + return -1; - if (git__strtol32(&n, reflogspec+3, NULL, 10) < 0 || n < 1) - return revspec_error(reflogspec); - - if (!git_reference_lookup(&disambiguated, repo, "HEAD")) { - if (!git_reflog_read(&reflog, disambiguated)) { - regex_error = regcomp(®ex, "checkout: moving from (.*) to .*", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - } else { - regmatch_t regexmatches[2]; - - retcode = GIT_ENOTFOUND; - - refloglen = git_reflog_entrycount(reflog); - for (i=refloglen-1; i >= 0; i--) { - const char *msg; - entry = git_reflog_entry_byindex(reflog, i); - - msg = git_reflog_entry_msg(entry); - if (!regexec(®ex, msg, 2, regexmatches, 0)) { - n--; - if (!n) { - git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); - retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf)); - break; - } - } - } - regfree(®ex); - } - } - } - } else { - int date_error = 0, result; - git_time_t timestamp; - git_buf datebuf = GIT_BUF_INIT; + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + goto cleanup; - result = disambiguate_refname(&disambiguated, repo, refspec); + if (git_reflog_read(&reflog, ref) < 0) + goto cleanup; - if (result < 0) { - retcode = result; - goto cleanup; - } + numentries = git_reflog_entrycount(reflog); - git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3); - date_error = git__date_parse(×tamp, git_buf_cstr(&datebuf)); + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + msg = git_reflog_entry_msg(entry); + + if (regexec(&preg, msg, 2, regexmatches, 0)) + continue; - /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ - if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) { - git_reference *tracking; - - if (!(retcode = git_reference_remote_tracking_from_branch(&tracking, disambiguated))) { - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_reference_name(tracking)); - git_reference_free(tracking); - } - } + cur--; - /* @{N} -> Nth prior value for the ref (from reflog) */ - else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) && - !git__strtol32(&n, reflogspec+2, NULL, 10) && - n <= 100000000) { /* Allow integer time */ - - git_buf_puts(&buf, git_reference_name(disambiguated)); - - if (n == 0) - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); - else if (!git_reflog_read(&reflog, disambiguated)) { - int numentries = git_reflog_entrycount(reflog); - if (numentries < n + 1) { - giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d", - git_buf_cstr(&buf), numentries, n); - retcode = GIT_ENOTFOUND; - } else { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n); - const git_oid *oid = git_reflog_entry_oidold(entry); - retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY); - } - } - } + if (cur > 0) + continue; + + git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); - else if (!date_error) { - /* Ref as it was on a certain date */ - git_reflog *reflog; - if (!git_reflog_read(&reflog, disambiguated)) { - /* Keep walking until we find an entry older than the given date */ - int numentries = git_reflog_entrycount(reflog); - int i; - - for (i = numentries - 1; i >= 0; i--) { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i); - git_time commit_time = git_reflog_entry_committer(entry)->when; - if (commit_time.time - timestamp <= 0) { - retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); - break; - } - } + if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0) + goto cleanup; - if (i == -1) { - /* Didn't find a match */ - retcode = GIT_ENOTFOUND; - } + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; - git_reflog_free(reflog); - } - } + error = maybe_sha_or_abbrev(out, repo, git_buf_cstr(&buf)); - git_buf_free(&datebuf); + goto cleanup; } + + error = GIT_ENOTFOUND; cleanup: - if (reflog) - git_reflog_free(reflog); + git_reference_free(ref); git_buf_free(&buf); - git_reference_free(disambiguated); - return retcode; + regfree(&preg); + git_reflog_free(reflog); + return error; } -static git_object* dereference_object(git_object *obj) +static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned int identifier) { - git_otype type = git_object_type(obj); + git_reflog *reflog; + int error = -1; + unsigned int numentries; + const git_reflog_entry *entry; + bool search_by_pos = (identifier <= 100000000); - switch (type) { - case GIT_OBJ_COMMIT: - { - git_tree *tree = NULL; - if (0 == git_commit_tree(&tree, (git_commit*)obj)) { - return (git_object*)tree; - } + if (git_reflog_read(&reflog, ref) < 0) + return -1; + + numentries = git_reflog_entrycount(reflog); + + if (search_by_pos) { + if (numentries < identifier + 1) { + giterr_set( + GITERR_REFERENCE, + "Reflog for '%s' has only %d entries, asked for %d", + git_reference_name(ref), + numentries, + identifier); + + error = GIT_ENOTFOUND; + goto cleanup; } - break; - case GIT_OBJ_TAG: - { - git_object *newobj = NULL; - if (0 == git_tag_target(&newobj, (git_tag*)obj)) { - return newobj; - } + + entry = git_reflog_entry_byindex(reflog, identifier); + git_oid_cpy(oid, git_reflog_entry_oidold(entry)); + error = 0; + goto cleanup; + + } else { + int i; + git_time commit_time; + + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + commit_time = git_reflog_entry_committer(entry)->when; + + if (commit_time.time - identifier > 0) + continue; + + git_oid_cpy(oid, git_reflog_entry_oidnew(entry)); + error = 0; + goto cleanup; } - break; - - default: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - break; + + error = GIT_ENOTFOUND; } - /* Can't dereference some types */ - return NULL; +cleanup: + git_reflog_free(reflog); + return error; } -static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) +static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, unsigned int position) { - int retcode = 1; - git_object *obj1 = obj, *obj2 = obj; - - while (retcode > 0) { - git_otype this_type = git_object_type(obj1); - - if (this_type == target_type) { - *out = obj1; - retcode = 0; - } else { - /* Dereference once, if possible. */ - obj2 = dereference_object(obj1); - if (!obj2) { - giterr_set(GITERR_REFERENCE, "Can't dereference to type"); - retcode = GIT_ERROR; - } - } - if (obj1 != obj && obj1 != obj2) { - git_object_free(obj1); - } - obj1 = obj2; + git_reference *ref; + git_oid oid; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (position == 0) { + error = git_object_lookup(out, repo, git_reference_oid(ref), GIT_OBJ_ANY); + goto cleanup; } - return retcode; + + if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) + goto cleanup; + + error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY); + +cleanup: + git_reference_free(ref); + return error; } -static git_otype parse_obj_type(const char *str) +static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) { - 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; + git_reference *tracking, *ref; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if ((error = git_branch_tracking(&tracking, ref)) < 0) + goto cleanup; + + *base_ref = tracking; + +cleanup: + 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; - size_t movementlen = strlen(movement); - int n; - - 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; - } + bool is_numeric; + int parsed = 0, error = -1; + git_buf identifier = GIT_BUF_INIT; + git_time_t timestamp; - /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */ - if (movement[1] == '/') { - int retcode = GIT_ERROR; - git_revwalk *walk; - if (!git_revwalk_new(&walk, repo)) { - git_oid oid; - regex_t preg; - int reg_error; - git_buf buf = GIT_BUF_INIT; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push(walk, git_object_id(obj)); - - /* Extract the regex from the movement string */ - git_buf_put(&buf, movement+2, strlen(movement)-3); - - reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - while(!git_revwalk_next(&oid, walk)) { - git_object *walkobj; - - /* Fetch the commit object, and check for matches in the message */ - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Found it! */ - retcode = 0; - *out = walkobj; - if (obj == walkobj) { - /* Avoid leaking an object */ - git_object_free(walkobj); - } - break; - } - git_object_free(walkobj); - } - } - if (retcode < 0) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", movement); - } - regfree(&preg); - } + assert(*out == NULL); - git_buf_free(&buf); - git_revwalk_free(walk); - } - return retcode; - } + if (git_buf_put(&identifier, spec, identifier_len) < 0) + return -1; - /* {...} -> Dereference until we reach an object of a certain type. */ - if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) { - return GIT_ERROR; - } - return 0; + is_numeric = !try_parse_numeric(&parsed, curly_braces_content); + + if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { + error = revspec_error(spec); + goto cleanup; } - /* Dereference until we reach a commit. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; + if (is_numeric) { + if (parsed < 0) + error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, spec, git_buf_cstr(&identifier), -parsed); + else + error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed); + + goto cleanup; } - /* "^" is the same as "^1" */ - if (movementlen == 0) { - n = 1; - } else { - git__strtol32(&n, movement, NULL, 10); + if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { + error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo); + + goto cleanup; } - commit = (git_commit*)obj; - /* "^0" just returns the input */ + if (git__date_parse(×tamp, 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) { - *out = obj; + *out = temp_commit; return 0; } - if (git_commit_parent(&commit, commit, n-1) < 0) { - return GIT_ENOTFOUND; - } + error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); - *out = (git_object*)commit; - return 0; + git_object_free(temp_commit); + 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 (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; - } + if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + return -1; - /* "~" is the same as "~1" */ - if (*movement == '\0') { - n = 1; - } else if (git__strtol32(&n, movement, NULL, 10) < 0) { - return GIT_ERROR; - } + error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); - 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, - git_repository *repo, +static int handle_colon_syntax( + git_object **out, git_object *obj, const char *path) { - git_object *tree = obj; + git_object *tree; int error = -1; git_tree_entry *entry = NULL; - /* Dereference until we reach a tree. */ - if (dereference_to_type(&tree, obj, GIT_OBJ_TREE) < 0) - return GIT_ERROR; + if (git_object_peel(&tree, obj, GIT_OBJ_TREE) < 0) + return -1; - if (*path == '\0') - return git_object_lookup(out, repo, git_object_id(tree), GIT_OBJ_TREE); + if (*path == '\0') { + *out = tree; + return 0; + } /* * TODO: Handle the relative path syntax @@ -543,188 +464,367 @@ static int handle_colon_syntax(git_object **out, if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) goto cleanup; - error = git_tree_entry_to_object(out, repo, entry); + error = git_tree_entry_to_object(out, git_object_owner(tree), entry); cleanup: git_tree_entry_free(entry); - if (tree != obj) - git_object_free(tree); + git_object_free(tree); 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 retcode = GIT_ERROR; + int error; + git_oid oid; + git_object *obj; + + while (!(error = git_revwalk_next(&oid, walk))) { - if (!pattern[0]) { - 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)) { - regex_t preg; - int reg_error; - git_oid oid; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push_glob(walk, "refs/heads/*"); - - reg_error = regcomp(&preg, pattern, REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - git_object *walkobj = NULL, *resultobj = NULL; - while(!git_revwalk_next(&oid, walk)) { - /* Fetch the commit object, and check for matches in the message */ - if (walkobj != resultobj) git_object_free(walkobj); - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Match! */ - resultobj = walkobj; - retcode = 0; - break; - } - } - } - if (!resultobj) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern); - retcode = GIT_ENOTFOUND; - git_object_free(walkobj); - } else { - *out = resultobj; - } - regfree(&preg); - git_revwalk_free(walk); + 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; + git_revwalk *walk = NULL; + 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); + + if (spec_oid == NULL) { + // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails + if (git_revwalk_push_glob(walk, "refs/heads/*") < 0) + goto cleanup; + } else if (git_revwalk_push(walk, spec_oid) < 0) + goto cleanup; + + error = walk_and_search(out, walk, &preg); + +cleanup: + regfree(&preg); + 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; } + + } 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; } - return retcode; + 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) { - revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT; - const char *spec_cur = spec; - git_object *cur_obj = NULL, *next_obj = NULL; - git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT; - int retcode = 0; + int pos = 0, identifier_len = 0; + int error = -1, n; + git_buf buf = GIT_BUF_INIT; + + git_reference *reference = NULL; + git_object *base_rev = NULL; assert(out && repo && spec); - if (spec[0] == ':') { - if (spec[1] == '/') { - return revparse_global_grep(out, repo, spec+2); - } - /* TODO: support merge-stage path lookup (":2:Makefile"). */ - giterr_set(GITERR_INVALID, "Unimplemented"); - return GIT_ERROR; - } + *out = NULL; - while (current_state != REVPARSE_STATE_DONE) { - switch (current_state) { - case REVPARSE_STATE_INIT: - if (!*spec_cur) { - /* No operators, just a name. Find it and return. */ - retcode = revparse_lookup_object(out, repo, spec); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '@') { - /* '@' syntax doesn't allow chaining */ - git_buf_puts(&stepbuffer, spec_cur); - retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - next_state = REVPARSE_STATE_CARET; - } else if (*spec_cur == '~') { - next_state = REVPARSE_STATE_LINEAR; - } else if (*spec_cur == ':') { - next_state = REVPARSE_STATE_COLON; - } else { - git_buf_putc(&specbuffer, *spec_cur); - } - spec_cur++; + while (spec[pos]) { + switch (spec[pos]) { + case '^': + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; - if (current_state != next_state && next_state != REVPARSE_STATE_DONE) { - /* Leaving INIT state, find the object specified, in case that state needs it */ - if ((retcode = revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer))) < 0) - next_state = REVPARSE_STATE_DONE; - } - break; + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; - case REVPARSE_STATE_CARET: - /* Gather characters until NULL, '~', or '^' */ - if (!*spec_cur) { - retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; - } - } else if (*spec_cur == ':') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_COLON : REVPARSE_STATE_DONE; + if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; } else { - git_buf_putc(&stepbuffer, *spec_cur); + git_object *temp_object = NULL; + + 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; + + git_object_free(base_rev); + base_rev = temp_object; } - spec_cur++; break; - case REVPARSE_STATE_LINEAR: - if (!*spec_cur) { - retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; - } - } else if (*spec_cur == '^') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_CARET : REVPARSE_STATE_DONE; - } else { - git_buf_putc(&stepbuffer, *spec_cur); - } - spec_cur++; + case '~': + { + git_object *temp_object = NULL; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; break; + } + + case ':': + { + 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; - case REVPARSE_STATE_COLON: - if (*spec_cur) { - git_buf_putc(&stepbuffer, *spec_cur); + if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; } else { - retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; + if (*git_buf_cstr(&buf) == '/') { + if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0) + goto cleanup; + } else { + + /* + * TODO: support merge-stage path lookup (":2:Makefile") + * and plain index blob lookup (:i-am/a/blob) + */ + giterr_set(GITERR_INVALID, "Unimplemented"); + error = GIT_ERROR; + goto cleanup; + } } - spec_cur++; + + git_object_free(base_rev); + base_rev = temp_object; break; + } + + case '@': + { + git_object *temp_object = NULL; - case REVPARSE_STATE_DONE: - if (cur_obj && *out != cur_obj) git_object_free(cur_obj); - if (next_obj && *out != next_obj) git_object_free(next_obj); + 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; + + if (temp_object != NULL) + base_rev = temp_object; break; } - current_state = next_state; - if (cur_obj != next_obj) { - if (cur_obj) git_object_free(cur_obj); - cur_obj = next_obj; + default: + if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference, spec)) < 0) + goto cleanup; + + pos++; + identifier_len++; } } - if (*out != cur_obj) git_object_free(cur_obj); - if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj); + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; - git_buf_free(&specbuffer); - git_buf_free(&stepbuffer); - return retcode; + *out = base_rev; + error = 0; + +cleanup: + if (error) + git_object_free(base_rev); + git_reference_free(reference); + git_buf_free(&buf); + return error; } diff --git a/src/status.c b/src/status.c index e9ad3cfe4..d78237689 100644 --- a/src/status.c +++ b/src/status.c @@ -99,6 +99,8 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) 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 */ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && @@ -176,10 +178,12 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; 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, "Ambiguous path '%s' given to git_status_file", sfi->expected); - return -1; + return GIT_EAMBIGUOUS; } return 0; diff --git a/src/tree.c b/src/tree.c index 31a581cdb..9d793cbb8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -759,11 +759,12 @@ int git_tree_entry_bypath( return error; } -static int tree_walk_post( +static int tree_walk( git_tree *tree, git_treewalk_cb callback, git_buf *path, - void *payload) + void *payload, + bool preorder) { int error = 0; unsigned int i; @@ -771,8 +772,8 @@ static int tree_walk_post( for (i = 0; i < tree->entries.length; ++i) { git_tree_entry *entry = tree->entries.contents[i]; - if (callback(path->ptr, entry, payload) < 0) - continue; + if (preorder && callback(path->ptr, entry, payload) < 0) + return -1; if (git_tree_entry__is_tree(entry)) { git_tree *subtree; @@ -789,12 +790,15 @@ static int tree_walk_post( if (git_buf_oom(path)) return -1; - if (tree_walk_post(subtree, callback, path, payload) < 0) + if (tree_walk(subtree, callback, path, payload, preorder) < 0) return -1; git_buf_truncate(path, path_len); git_tree_free(subtree); } + + if (!preorder && callback(path->ptr, entry, payload) < 0) + return -1; } return 0; @@ -807,12 +811,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl switch (mode) { case GIT_TREEWALK_POST: - error = tree_walk_post(tree, callback, &root_path, payload); + error = tree_walk(tree, callback, &root_path, payload, false); break; case GIT_TREEWALK_PRE: - tree_error("Preorder tree walking is still not implemented"); - return -1; + error = tree_walk(tree, callback, &root_path, payload, true); + break; default: giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); diff --git a/src/util.c b/src/util.c index 3093cd767..90bb3d02a 100644 --- a/src/util.c +++ b/src/util.c @@ -435,3 +435,21 @@ int git__parse_bool(int *out, const char *value) 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); +} diff --git a/src/util.h b/src/util.h index a84dcab1e..905fc927f 100644 --- a/src/util.h +++ b/src/util.h @@ -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); +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + #endif /* INCLUDE_util_h__ */ |
