diff options
| author | Carlos Martín Nieto <cmn@dwim.me> | 2014-08-27 15:09:07 +0200 |
|---|---|---|
| committer | Carlos Martín Nieto <cmn@dwim.me> | 2014-08-27 15:09:07 +0200 |
| commit | 6a0d2b43ee4a32b2894d3c2b7c798c4eb4853534 (patch) | |
| tree | a7fedcd3bffe2d021e31491b4f1f020c1bea8f88 /src | |
| parent | 8733993599d9d9a8fe1b042a1e18d6ecb9b0a0b2 (diff) | |
| parent | cb92467bc284b87c38cdf32f8803a528846d094b (diff) | |
| download | libgit2-6a0d2b43ee4a32b2894d3c2b7c798c4eb4853534.tar.gz | |
Merge remote-tracking branch 'upstream/master' into cmn/ssh-retry
Diffstat (limited to 'src')
70 files changed, 1895 insertions, 756 deletions
diff --git a/src/array.h b/src/array.h index f8a48722a..af9eafa43 100644 --- a/src/array.h +++ b/src/array.h @@ -44,7 +44,7 @@ typedef git_array_t(char) git_array_generic_t; /* use a generic array for growth so this can return the new item */ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) { - git_array_generic_t *a = _a; + volatile git_array_generic_t *a = _a; uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2; char *new_array = git__realloc(a->ptr, new_size * item_size); if (!new_array) { diff --git a/src/attr_file.c b/src/attr_file.c index 3e95a2134..07ffacbaf 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -378,6 +378,18 @@ bool git_attr_fnmatch__match( return (matchval != FNM_NOMATCH); } + /* if path is a directory prefix of a negated pattern, then match */ + if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) { + size_t pathlen = strlen(path->path); + bool prefixed = (pathlen <= match->length) && + ((match->flags & GIT_ATTR_FNMATCH_ICASE) ? + !strncasecmp(match->pattern, path->path, pathlen) : + !strncmp(match->pattern, path->path, pathlen)); + + if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen])) + return true; + } + return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); } @@ -522,7 +534,8 @@ int git_attr_fnmatch__parse( } if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + spec->flags = spec->flags | + GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR; pattern++; } diff --git a/src/buf_text.c b/src/buf_text.c index 631feb3f8..8d2b141b2 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -123,9 +123,13 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src) for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { size_t copylen = next - scan; - /* don't convert existing \r\n to \r\r\n */ - size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2; - size_t needsize = tgt->size + copylen + extralen + 1; + size_t needsize = tgt->size + copylen + 2 + 1; + + /* if we find mixed line endings, bail */ + if (next > start && next[-1] == '\r') { + git_buf_free(tgt); + return GIT_PASSTHROUGH; + } if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0) return -1; @@ -134,8 +138,8 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src) memcpy(tgt->ptr + tgt->size, scan, copylen); tgt->size += copylen; } - if (extralen == 2) - tgt->ptr[tgt->size++] = '\r'; + + tgt->ptr[tgt->size++] = '\r'; tgt->ptr[tgt->size++] = '\n'; } diff --git a/src/buf_text.h b/src/buf_text.h index 3ac9d1443..e753a0244 100644 --- a/src/buf_text.h +++ b/src/buf_text.h @@ -56,9 +56,10 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string) extern void git_buf_text_unescape(git_buf *buf); /** - * Replace all \r\n with \n. Does not modify \r without trailing \n. + * Replace all \r\n with \n. * - * @return 0 on success, -1 on memory error + * @return 0 on success, -1 on memory error, GIT_PASSTHROUGH if the + * source buffer has mixed line endings. */ extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src); diff --git a/src/buffer.c b/src/buffer.c index b8f8660ed..e9c420e16 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -7,6 +7,7 @@ #include "buffer.h" #include "posix.h" #include "git2/buffer.h" +#include "buf_text.h" #include <ctype.h> /* Used as default value for git_buf->ptr so that people can always @@ -141,6 +142,16 @@ int git_buf_set(git_buf *buf, const void *data, size_t len) return 0; } +int git_buf_is_binary(const git_buf *buf) +{ + return git_buf_text_is_binary(buf); +} + +int git_buf_contains_nul(const git_buf *buf) +{ + return git_buf_text_contains_nul(buf); +} + int git_buf_sets(git_buf *buf, const char *string) { return git_buf_set(buf, string, string ? strlen(string) : 0); @@ -178,10 +189,10 @@ int git_buf_puts(git_buf *buf, const char *string) return git_buf_put(buf, string, strlen(string)); } -static const char b64str[] = +static const char base64_encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -int git_buf_put_base64(git_buf *buf, const char *data, size_t len) +int git_buf_encode_base64(git_buf *buf, const char *data, size_t len) { size_t extra = len % 3; uint8_t *write, a, b, c; @@ -196,19 +207,19 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len) b = *read++; c = *read++; - *write++ = b64str[a >> 2]; - *write++ = b64str[(a & 0x03) << 4 | b >> 4]; - *write++ = b64str[(b & 0x0f) << 2 | c >> 6]; - *write++ = b64str[c & 0x3f]; + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; + *write++ = base64_encode[c & 0x3f]; } if (extra > 0) { a = *read++; b = (extra > 1) ? *read++ : 0; - *write++ = b64str[a >> 2]; - *write++ = b64str[(a & 0x03) << 4 | b >> 4]; - *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '='; + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; *write++ = '='; } @@ -218,10 +229,56 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len) return 0; } +/* The inverse of base64_encode, offset by '+' == 43. */ +static const int8_t base64_decode[] = { + 62, + -1, -1, -1, + 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + -1, -1, -1, 0, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +#define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43]) + +int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len) +{ + size_t i; + int8_t a, b, c, d; + size_t orig_size = buf->size; + + assert(len % 4 == 0); + ENSURE_SIZE(buf, buf->size + (len / 4 * 3) + 1); + + for (i = 0; i < len; i += 4) { + if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 || + (b = BASE64_DECODE_VALUE(base64[i+1])) < 0 || + (c = BASE64_DECODE_VALUE(base64[i+2])) < 0 || + (d = BASE64_DECODE_VALUE(base64[i+3])) < 0) { + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + giterr_set(GITERR_INVALID, "Invalid base64 input"); + return -1; + } + + buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); + buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + static const char b85str[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; -int git_buf_put_base85(git_buf *buf, const char *data, size_t len) +int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) { ENSURE_SIZE(buf, buf->size + (5 * ((len / 4) + !!(len % 4))) + 1); diff --git a/src/buffer.h b/src/buffer.h index 70d6d73b3..8ee4b532c 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -156,10 +156,12 @@ void git_buf_rtrim(git_buf *buf); int git_buf_cmp(const git_buf *a, const git_buf *b); /* Write data as base64 encoded in buffer */ -int git_buf_put_base64(git_buf *buf, const char *data, size_t len); +int git_buf_encode_base64(git_buf *buf, const char *data, size_t len); +/* Decode the given bas64 and write the result to the buffer */ +int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len); /* Write data as "base85" encoded in buffer */ -int git_buf_put_base85(git_buf *buf, const char *data, size_t len); +int git_buf_encode_base85(git_buf *buf, const char *data, size_t len); /* * Insert, remove or replace a portion of the buffer. diff --git a/src/cache.c b/src/cache.c index 36ce66570..8dc9cbf9c 100644 --- a/src/cache.c +++ b/src/cache.c @@ -68,8 +68,8 @@ int git_cache_init(git_cache *cache) { memset(cache, 0, sizeof(*cache)); cache->map = git_oidmap_alloc(); - if (git_mutex_init(&cache->lock)) { - giterr_set(GITERR_OS, "Failed to initialize cache mutex"); + if (git_rwlock_init(&cache->lock)) { + giterr_set(GITERR_OS, "Failed to initialize cache rwlock"); return -1; } return 0; @@ -94,19 +94,19 @@ static void clear_cache(git_cache *cache) void git_cache_clear(git_cache *cache) { - if (git_mutex_lock(&cache->lock) < 0) + if (git_rwlock_wrlock(&cache->lock) < 0) return; clear_cache(cache); - git_mutex_unlock(&cache->lock); + git_rwlock_wrunlock(&cache->lock); } void git_cache_free(git_cache *cache) { git_cache_clear(cache); git_oidmap_free(cache->map); - git_mutex_free(&cache->lock); + git_rwlock_free(&cache->lock); git__memzero(cache, sizeof(*cache)); } @@ -152,7 +152,7 @@ static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) khiter_t pos; git_cached_obj *entry = NULL; - if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0) + if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) return NULL; pos = kh_get(oid, cache->map, oid); @@ -166,7 +166,7 @@ static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) } } - git_mutex_unlock(&cache->lock); + git_rwlock_rdunlock(&cache->lock); return entry; } @@ -185,7 +185,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) if (!cache_should_store(entry->type, entry->size)) return entry; - if (git_mutex_lock(&cache->lock) < 0) + if (git_rwlock_wrlock(&cache->lock) < 0) return entry; /* soften the load on the cache */ @@ -227,7 +227,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) } } - git_mutex_unlock(&cache->lock); + git_rwlock_wrunlock(&cache->lock); return entry; } diff --git a/src/cache.h b/src/cache.h index 53fbcf4e9..697123739 100644 --- a/src/cache.h +++ b/src/cache.h @@ -30,7 +30,7 @@ typedef struct { typedef struct { git_oidmap *map; - git_mutex lock; + git_rwlock lock; ssize_t used_memory; } git_cache; diff --git a/src/checkout.c b/src/checkout.c index 20763fd35..f25a6eff0 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -46,6 +46,7 @@ enum { typedef struct { git_repository *repo; + git_iterator *target; git_diff *diff; git_checkout_options opts; bool opts_free_baseline; @@ -54,6 +55,8 @@ typedef struct { git_pool pool; git_vector removes; git_vector conflicts; + git_vector *reuc; + git_vector *names; git_buf path; size_t workdir_len; git_buf tmp; @@ -116,6 +119,7 @@ static int checkout_notify( case GIT_DELTA_ADDED: case GIT_DELTA_IGNORED: case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: target = &delta->new_file; break; case GIT_DELTA_DELETED: @@ -138,6 +142,7 @@ static int checkout_notify( static bool checkout_is_workdir_modified( checkout_data *data, const git_diff_file *baseitem, + const git_diff_file *newitem, const git_index_entry *wditem) { git_oid oid; @@ -169,13 +174,16 @@ static bool checkout_is_workdir_modified( /* Look at the cache to decide if the workdir is modified. If not, * we can simply compare the oid in the cache to the baseitem instead - * of hashing the file. + * of hashing the file. If so, we allow the checkout to proceed if the + * oid is identical (ie, the staged item is what we're trying to check + * out.) */ if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) { if (wditem->mtime.seconds == ie->mtime.seconds && wditem->mtime.nanoseconds == ie->mtime.nanoseconds && wditem->file_size == ie->file_size) - return (git_oid__cmp(&baseitem->id, &ie->id) != 0); + return (git_oid__cmp(&baseitem->id, &ie->id) != 0 && + git_oid_cmp(&newitem->id, &ie->id) != 0); } /* depending on where base is coming from, we may or may not know @@ -401,7 +409,7 @@ static int checkout_action_with_wd( switch (delta->status) { case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ - if (checkout_is_workdir_modified(data, &delta->old_file, wd)) { + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { GITERR_CHECK_ERROR( checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); @@ -414,13 +422,13 @@ static int checkout_action_with_wd( *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); break; case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ - if (checkout_is_workdir_modified(data, &delta->old_file, wd)) + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); else *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); break; case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ - if (checkout_is_workdir_modified(data, &delta->old_file, wd)) + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); else *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); @@ -443,7 +451,7 @@ static int checkout_action_with_wd( } else *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); } - else if (checkout_is_workdir_modified(data, &delta->old_file, wd)) + else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); else *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); @@ -788,11 +796,16 @@ done: static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) { git_index_conflict_iterator *iterator = NULL; + git_index *index; const git_index_entry *ancestor, *ours, *theirs; checkout_conflictdata *conflict; int error = 0; - if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0) + /* Only write conficts from sources that have them: indexes. */ + if ((index = git_iterator_get_index(data->target)) == NULL) + return 0; + + if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0) goto done; data->conflicts._cmp = checkout_conflictdata_cmp; @@ -819,6 +832,10 @@ static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, g git_vector_insert(&data->conflicts, conflict); } + /* Collect the REUC and NAME entries */ + data->reuc = &index->reuc; + data->names = &index->names; + if (error == GIT_ITEROVER) error = 0; @@ -957,16 +974,20 @@ done: static int checkout_conflicts_coalesce_renames( checkout_data *data) { + git_index *index; const git_index_name_entry *name_entry; checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; size_t i, names; int error = 0; + if ((index = git_iterator_get_index(data->target)) == NULL) + return 0; + /* Juggle entries based on renames */ - names = git_index_name_entrycount(data->index); + names = git_index_name_entrycount(index); for (i = 0; i < names; i++) { - name_entry = git_index_name_get_byindex(data->index, i); + name_entry = git_index_name_get_byindex(index, i); if ((error = checkout_conflicts_load_byname_entry( &ancestor_conflict, &our_conflict, &their_conflict, @@ -1010,13 +1031,17 @@ done: static int checkout_conflicts_mark_directoryfile( checkout_data *data) { + git_index *index; checkout_conflictdata *conflict; const git_index_entry *entry; size_t i, j, len; const char *path; int prefixed, error = 0; - len = git_index_entrycount(data->index); + if ((index = git_iterator_get_index(data->target)) == NULL) + return 0; + + len = git_index_entrycount(index); /* Find d/f conflicts */ git_vector_foreach(&data->conflicts, i, conflict) { @@ -1027,7 +1052,7 @@ static int checkout_conflicts_mark_directoryfile( path = conflict->ours ? conflict->ours->path : conflict->theirs->path; - if ((error = git_index_find(&j, data->index, path)) < 0) { + if ((error = git_index_find(&j, index, path)) < 0) { if (error == GIT_ENOTFOUND) giterr_set(GITERR_INDEX, "Index inconsistency, could not find entry for expected conflict '%s'", path); @@ -1036,7 +1061,7 @@ static int checkout_conflicts_mark_directoryfile( } for (; j < len; j++) { - if ((entry = git_index_get_byindex(data->index, j)) == NULL) { + if ((entry = git_index_get_byindex(index, j)) == NULL) { giterr_set(GITERR_INDEX, "Index inconsistency, truncated index while loading expected conflict '%s'", path); error = -1; @@ -1802,6 +1827,24 @@ done: return error; } +static int checkout_conflict_update_index( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if (conflict->ancestor) + error = git_index_add(data->index, conflict->ancestor); + + if (!error && conflict->ours) + error = git_index_add(data->index, conflict->ours); + + if (!error && conflict->theirs) + error = git_index_add(data->index, conflict->theirs); + + return error; +} + static int checkout_create_conflicts(checkout_data *data) { checkout_conflictdata *conflict; @@ -1864,6 +1907,12 @@ static int checkout_create_conflicts(checkout_data *data) else if (!error) error = checkout_write_merge(data, conflict); + /* Update the index extensions (REUC and NAME) if we're checking + * out a different index. (Otherwise just leave them there.) + */ + if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) + error = checkout_conflict_update_index(data, conflict); + if (error) break; @@ -1876,6 +1925,37 @@ static int checkout_create_conflicts(checkout_data *data) return error; } +static int checkout_extensions_update_index(checkout_data *data) +{ + const git_index_reuc_entry *reuc_entry; + const git_index_name_entry *name_entry; + size_t i; + int error = 0; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + + if (data->reuc) { + git_vector_foreach(data->reuc, i, reuc_entry) { + if ((error = git_index_reuc_add(data->index, reuc_entry->path, + reuc_entry->mode[0], &reuc_entry->oid[0], + reuc_entry->mode[1], &reuc_entry->oid[1], + reuc_entry->mode[2], &reuc_entry->oid[2])) < 0) + goto done; + } + } + + if (data->names) { + git_vector_foreach(data->names, i, name_entry) { + if ((error = git_index_name_add(data->index, name_entry->ancestor, + name_entry->ours, name_entry->theirs)) < 0) + goto done; + } + } + +done: + return error; +} static void checkout_data_clear(checkout_data *data) { @@ -1919,6 +1999,7 @@ static int checkout_data_init( return error; data->repo = repo; + data->target = target; GITERR_CHECK_VERSION( proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); @@ -1943,15 +2024,15 @@ static int checkout_data_init( (error = git_config_refresh(cfg)) < 0) goto cleanup; - /* if we are checking out the index, don't reload, - * otherwise get index and force reload + /* Get the repository index and reload it (unless we're checking + * out the index; then it has the changes we're trying to check + * out and those should not be overwritten.) */ - if ((data->index = git_iterator_get_index(target)) != NULL) { - GIT_REFCOUNT_INC(data->index); - } else { - /* otherwise, grab and reload the index */ - if ((error = git_repository_index(&data->index, data->repo)) < 0 || - (error = git_index_read(data->index, true)) < 0) + if ((error = git_repository_index(&data->index, data->repo)) < 0) + goto cleanup; + + if (data->index != git_iterator_get_index(target)) { + if ((error = git_index_read(data->index, true)) < 0) goto cleanup; /* cannot checkout if unresolved conflicts exist */ @@ -1963,7 +2044,7 @@ static int checkout_data_init( goto cleanup; } - /* clean conflict data when doing a tree or commit checkout */ + /* clean conflict data in the current index */ git_index_name_clear(data->index); git_index_reuc_clear(data->index); } @@ -2063,6 +2144,7 @@ int git_checkout_iterator( diff_opts.flags = GIT_DIFF_INCLUDE_UNMODIFIED | + GIT_DIFF_INCLUDE_UNREADABLE | GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ GIT_DIFF_INCLUDE_IGNORED | @@ -2132,6 +2214,10 @@ int git_checkout_iterator( (error = checkout_create_conflicts(&data)) < 0) goto cleanup; + if (data.index != git_iterator_get_index(target) && + (error = checkout_extensions_update_index(&data)) < 0) + goto cleanup; + assert(data.completed_steps == data.total_steps); cleanup: @@ -2154,7 +2240,7 @@ int git_checkout_index( git_index *index, const git_checkout_options *opts) { - int error; + int error, owned = 0; git_iterator *index_i; if (!index && !repo) { @@ -2162,10 +2248,16 @@ int git_checkout_index( "Must provide either repository or index to checkout"); return -1; } - if (index && repo && git_index_owner(index) != repo) { + + if (index && repo && + git_index_owner(index) && + git_index_owner(index) != repo) { giterr_set(GITERR_CHECKOUT, "Index to checkout does not match repository"); return -1; + } else if(index && repo && !git_index_owner(index)) { + GIT_REFCOUNT_OWN(index, repo); + owned = 1; } if (!repo) @@ -2178,6 +2270,9 @@ int git_checkout_index( if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL))) error = git_checkout_iterator(index_i, opts); + if (owned) + GIT_REFCOUNT_OWN(index, NULL); + git_iterator_free(index_i); git_index_free(index); diff --git a/src/cherrypick.c b/src/cherrypick.c index e02348a03..e58d0ab4c 100644 --- a/src/cherrypick.c +++ b/src/cherrypick.c @@ -17,9 +17,9 @@ #include "git2/commit.h" #include "git2/sys/commit.h" -#define GIT_CHERRY_PICK_FILE_MODE 0666 +#define GIT_CHERRYPICK_FILE_MODE 0666 -static int write_cherry_pick_head( +static int write_cherrypick_head( git_repository *repo, const char *commit_oidstr) { @@ -27,8 +27,8 @@ static int write_cherry_pick_head( git_buf file_path = GIT_BUF_INIT; int error = 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRY_PICK_HEAD_FILE)) >= 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) >= 0 && + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 && (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) error = git_filebuf_commit(&file); @@ -49,7 +49,7 @@ static int write_merge_msg( int error = 0; if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 || (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) goto cleanup; @@ -64,10 +64,10 @@ cleanup: return error; } -static int cherry_pick_normalize_opts( +static int cherrypick_normalize_opts( git_repository *repo, - git_cherry_pick_options *opts, - const git_cherry_pick_options *given, + git_cherrypick_options *opts, + const git_cherrypick_options *given, const char *their_label) { int error = 0; @@ -77,10 +77,10 @@ static int cherry_pick_normalize_opts( GIT_UNUSED(repo); if (given != NULL) - memcpy(opts, given, sizeof(git_cherry_pick_options)); + memcpy(opts, given, sizeof(git_cherrypick_options)); else { - git_cherry_pick_options default_opts = GIT_CHERRY_PICK_OPTIONS_INIT; - memcpy(opts, &default_opts, sizeof(git_cherry_pick_options)); + git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; + memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); } if (!opts->checkout_opts.checkout_strategy) @@ -95,14 +95,14 @@ static int cherry_pick_normalize_opts( return error; } -static int cherry_pick_state_cleanup(git_repository *repo) +static int cherrypick_state_cleanup(git_repository *repo) { - const char *state_files[] = { GIT_CHERRY_PICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; + const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); } -static int cherry_pick_seterr(git_commit *commit, const char *fmt) +static int cherrypick_seterr(git_commit *commit, const char *fmt) { char commit_oidstr[GIT_OID_HEXSZ + 1]; @@ -112,71 +112,71 @@ static int cherry_pick_seterr(git_commit *commit, const char *fmt) return -1; } -int git_cherry_pick_commit( +int git_cherrypick_commit( git_index **out, git_repository *repo, - git_commit *cherry_pick_commit, + git_commit *cherrypick_commit, git_commit *our_commit, unsigned int mainline, const git_merge_options *merge_opts) { git_commit *parent_commit = NULL; - git_tree *parent_tree = NULL, *our_tree = NULL, *cherry_pick_tree = NULL; + git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; int parent = 0, error = 0; - assert(out && repo && cherry_pick_commit && our_commit); + assert(out && repo && cherrypick_commit && our_commit); - if (git_commit_parentcount(cherry_pick_commit) > 1) { + if (git_commit_parentcount(cherrypick_commit) > 1) { if (!mainline) - return cherry_pick_seterr(cherry_pick_commit, + return cherrypick_seterr(cherrypick_commit, "Mainline branch is not specified but %s is a merge commit"); parent = mainline; } else { if (mainline) - return cherry_pick_seterr(cherry_pick_commit, + return cherrypick_seterr(cherrypick_commit, "Mainline branch specified but %s is not a merge commit"); - parent = git_commit_parentcount(cherry_pick_commit); + parent = git_commit_parentcount(cherrypick_commit); } if (parent && - ((error = git_commit_parent(&parent_commit, cherry_pick_commit, (parent - 1))) < 0 || + ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) goto done; - if ((error = git_commit_tree(&cherry_pick_tree, cherry_pick_commit)) < 0 || + if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || (error = git_commit_tree(&our_tree, our_commit)) < 0) goto done; - error = git_merge_trees(out, repo, parent_tree, our_tree, cherry_pick_tree, merge_opts); + error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); done: git_tree_free(parent_tree); git_tree_free(our_tree); - git_tree_free(cherry_pick_tree); + git_tree_free(cherrypick_tree); git_commit_free(parent_commit); return error; } -int git_cherry_pick( +int git_cherrypick( git_repository *repo, git_commit *commit, - const git_cherry_pick_options *given_opts) + const git_cherrypick_options *given_opts) { - git_cherry_pick_options opts; + git_cherrypick_options opts; git_reference *our_ref = NULL; git_commit *our_commit = NULL; char commit_oidstr[GIT_OID_HEXSZ + 1]; const char *commit_msg, *commit_summary; git_buf their_label = GIT_BUF_INIT; - git_index *index_new = NULL, *index_repo = NULL; + git_index *index_new = NULL; int error = 0; assert(repo && commit); - GITERR_CHECK_VERSION(given_opts, GIT_CHERRY_PICK_OPTIONS_VERSION, "git_cherry_pick_options"); + GITERR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) return error; @@ -191,25 +191,22 @@ int git_cherry_pick( if ((error = write_merge_msg(repo, commit_msg)) < 0 || (error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || - (error = cherry_pick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 || - (error = write_cherry_pick_head(repo, commit_oidstr)) < 0 || + (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 || + (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || (error = git_repository_head(&our_ref, repo)) < 0 || (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 || - (error = git_cherry_pick_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__indexes(repo, index_new)) < 0 || - (error = git_repository_index(&index_repo, repo)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 || - (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0) + (error = git_cherrypick_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || + (error = git_merge__check_result(repo, index_new)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 || + (error = git_checkout_index(repo, index_new, &opts.checkout_opts)) < 0) goto on_error; - goto done; on_error: - cherry_pick_state_cleanup(repo); + cherrypick_state_cleanup(repo); done: git_index_free(index_new); - git_index_free(index_repo); git_commit_free(our_commit); git_reference_free(our_ref); git_buf_free(&their_label); @@ -217,10 +214,10 @@ done: return error; } -int git_cherry_pick_init_options( - git_cherry_pick_options *opts, unsigned int version) +int git_cherrypick_init_options( + git_cherrypick_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_cherry_pick_options, GIT_CHERRY_PICK_OPTIONS_INIT); + opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT); return 0; } diff --git a/src/clone.c b/src/clone.c index 6c4fb6727..7835be1b1 100644 --- a/src/clone.c +++ b/src/clone.c @@ -24,6 +24,8 @@ #include "repository.h" #include "odb.h" +static int clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature); + static int create_branch( git_reference **branch, git_repository *repo, @@ -229,6 +231,29 @@ cleanup: return retcode; } +static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) +{ + GIT_UNUSED(payload); + + return git_repository_init(out, path, bare); +} + +static int default_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + int error; + git_remote_callbacks *callbacks = payload; + + if ((error = git_remote_create(out, repo, name, url)) < 0) + return error; + + return git_remote_set_callbacks(*out, callbacks); +} + /* * submodules? */ @@ -241,8 +266,9 @@ static int create_and_configure_origin( { int error; git_remote *origin = NULL; - const char *name; char buf[GIT_PATH_MAX]; + git_remote_create_cb remote_create = options->remote_cb; + void *payload = options->remote_cb_payload; /* If the path exists and is a dir, the url should be the absolute path */ if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) { @@ -252,14 +278,12 @@ static int create_and_configure_origin( url = buf; } - name = options->remote_name ? options->remote_name : "origin"; - if ((error = git_remote_create(&origin, repo, name, url)) < 0) - goto on_error; - - if (options->ignore_cert_errors) - git_remote_check_cert(origin, 0); + if (!remote_create) { + remote_create = default_remote_create; + payload = (void *)&options->remote_callbacks; + } - if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0) + if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) goto on_error; if ((error = git_remote_save(origin)) < 0) @@ -307,7 +331,7 @@ static int checkout_branch(git_repository *repo, git_remote *remote, const git_c return error; } -int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +static int clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { int error; git_buf reflog_message = GIT_BUF_INIT; @@ -347,27 +371,30 @@ cleanup: return error; } -int git_clone__should_clone_local(const char *url, git_clone_local_t local) +int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) { - const char *path; - int is_url; + git_buf fromurl = GIT_BUF_INIT; + const char *path = url_or_path; + bool is_url, is_local; if (local == GIT_CLONE_NO_LOCAL) - return false; - - is_url = !git__prefixcmp(url, "file://"); + return 0; - if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) - return false; + if ((is_url = git_path_is_local_file_url(url_or_path)) != 0) { + if (git_path_fromurl(&fromurl, url_or_path) < 0) { + is_local = -1; + goto done; + } - path = url; - if (is_url) - path = url + strlen("file://"); + path = fromurl.ptr; + } - if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) - return true; + is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && + git_path_isdir(path); - return false; +done: + git_buf_free(&fromurl); + return is_local; } int git_clone( @@ -381,6 +408,7 @@ int git_clone( git_remote *origin; git_clone_options options = GIT_CLONE_OPTIONS_INIT; uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; + git_repository_create_cb repository_cb; assert(out && url && local_path); @@ -400,20 +428,28 @@ int git_clone( if (git_path_exists(local_path)) rmdir_flags |= GIT_RMDIR_SKIP_ROOT; - if ((error = git_repository_init(&repo, local_path, options.bare)) < 0) + if (options.repository_cb) + repository_cb = options.repository_cb; + else + repository_cb = default_repository_create; + + if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - if (git_clone__should_clone_local(url, options.local)) { - int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; - error = git_clone_local_into( + int clone_local = git_clone__should_clone_local(url, options.local); + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; + + if (clone_local == 1) + error = clone_local_into( repo, origin, &options.checkout_opts, options.checkout_branch, link, options.signature); - } else { - error = git_clone_into( + else if (clone_local == 0) + error = clone_into( repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); - } + else + error = -1; git_remote_free(origin); } @@ -452,6 +488,9 @@ static const char *repository_base(git_repository *repo) static bool can_link(const char *src, const char *dst, int link) { #ifdef GIT_WIN32 + GIT_UNUSED(src); + GIT_UNUSED(dst); + GIT_UNUSED(link); return false; #else @@ -470,7 +509,7 @@ static bool can_link(const char *src, const char *dst, int link) #endif } -int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) +static int clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) { int error, flags; git_repository *src; diff --git a/src/config_file.c b/src/config_file.c index 56271144b..7106f18db 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1522,6 +1522,9 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size); + if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n') + git_filebuf_write(&file, "\n", 1); + /* And now if we just need to add a variable */ if (!section_matches && write_section(&file, section) < 0) goto rewrite_fail; @@ -1536,9 +1539,6 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p } /* If we are here, there is at least a section line */ - if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n') - git_filebuf_write(&file, "\n", 1); - q = quotes_for_value(value); git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q); } @@ -1649,7 +1649,7 @@ static int is_multiline_var(const char *str) } /* An odd number means last backslash wasn't escaped, so it's multiline */ - return (end > str) && (count & 1); + return count & 1; } static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes) diff --git a/src/crlf.c b/src/crlf.c index 821e04eb2..93448760d 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -286,7 +286,8 @@ static int crlf_check( if (error < 0) return error; - if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE) + if (ca.crlf_action == GIT_CRLF_GUESS && + ca.auto_crlf == GIT_AUTO_CRLF_FALSE) return GIT_PASSTHROUGH; if (ca.auto_crlf == GIT_AUTO_CRLF_INPUT && diff --git a/src/diff.c b/src/diff.c index 325c599e0..2691d7ca0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -92,6 +92,10 @@ static int diff_delta__from_one( if (status == GIT_DELTA_UNTRACKED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) return 0; + + if (status == GIT_DELTA_UNREADABLE && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) + return 0; if (!git_pathspec__match( &diff->pathspec, entry->path, @@ -196,6 +200,7 @@ static git_diff_delta *diff_delta__last_for_item( if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) return delta; break; + case GIT_DELTA_UNREADABLE: case GIT_DELTA_UNTRACKED: if (diff->strcomp(delta->new_file.path, item->path) == 0 && git_oid__cmp(&delta->new_file.id, &item->id) == 0) @@ -293,6 +298,10 @@ bool git_diff_delta__should_skip( (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) return true; + if (delta->status == GIT_DELTA_UNREADABLE && + (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) + return true; + return false; } @@ -734,6 +743,11 @@ static int maybe_modified( else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) status = GIT_DELTA_TYPECHANGE; + else if (nmode == GIT_FILEMODE_UNREADABLE) { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, nitem); + return error; + } else { if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); @@ -954,6 +968,13 @@ static int handle_unmatched_new_item( } } + else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) + delta_type = GIT_DELTA_UNTRACKED; + else + delta_type = GIT_DELTA_UNREADABLE; + } + /* Actually create the record for this item if necessary */ if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0) return error; diff --git a/src/diff_file.c b/src/diff_file.c index f2a1d5099..96be0942b 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -112,6 +112,7 @@ int git_diff_file_content__init_from_diff( has_data = !use_old && (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; break; + case GIT_DELTA_UNREADABLE: case GIT_DELTA_MODIFIED: case GIT_DELTA_COPIED: case GIT_DELTA_RENAMED: diff --git a/src/diff_print.c b/src/diff_print.c index bb925ef98..43a90b3d8 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -82,14 +82,15 @@ char git_diff_status_char(git_delta_t status) char code; switch (status) { - case GIT_DELTA_ADDED: code = 'A'; break; - case GIT_DELTA_DELETED: code = 'D'; break; - case GIT_DELTA_MODIFIED: code = 'M'; break; - case GIT_DELTA_RENAMED: code = 'R'; break; - case GIT_DELTA_COPIED: code = 'C'; break; - case GIT_DELTA_IGNORED: code = 'I'; break; - case GIT_DELTA_UNTRACKED: code = '?'; break; - default: code = ' '; break; + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + case GIT_DELTA_UNREADABLE: code = 'X'; break; + default: code = ' '; break; } return code; @@ -351,7 +352,7 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) else git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - git_buf_put_base85(pi->buf, scan, chunk_len); + git_buf_encode_base85(pi->buf, scan, chunk_len); git_buf_putc(pi->buf, '\n'); if (git_buf_oom(pi->buf)) { @@ -441,6 +442,7 @@ static int diff_print_patch_file( if (S_ISDIR(delta->new_file.mode) || delta->status == GIT_DELTA_UNMODIFIED || delta->status == GIT_DELTA_IGNORED || + delta->status == GIT_DELTA_UNREADABLE || (delta->status == GIT_DELTA_UNTRACKED && (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; diff --git a/src/diff_tform.c b/src/diff_tform.c index a2dab0ae2..423a0ca33 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -114,7 +114,7 @@ static git_diff_delta *diff_delta__merge_like_cgit_reversed( if ((dup = diff_delta__dup(a, pool)) == NULL) return NULL; - if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED) + if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED || b->status == GIT_DELTA_UNREADABLE) return dup; if (dup->status == GIT_DELTA_DELETED) { @@ -732,6 +732,7 @@ static bool is_rename_source( switch (delta->status) { case GIT_DELTA_ADDED: case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: case GIT_DELTA_IGNORED: return false; @@ -786,6 +787,7 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) { return (delta->status == GIT_DELTA_ADDED || delta->status == GIT_DELTA_UNTRACKED || + delta->status == GIT_DELTA_UNREADABLE || delta->status == GIT_DELTA_IGNORED); } diff --git a/src/errors.c b/src/errors.c index 393a7875f..7a2600586 100644 --- a/src/errors.c +++ b/src/errors.c @@ -45,15 +45,19 @@ void giterr_set(int error_class, const char *string, ...) #endif int error_code = (error_class == GITERR_OS) ? errno : 0; - va_start(arglist, string); - git_buf_vprintf(&buf, string, arglist); - va_end(arglist); + if (string) { + va_start(arglist, string); + git_buf_vprintf(&buf, string, arglist); + va_end(arglist); + + if (error_class == GITERR_OS) + git_buf_PUTS(&buf, ": "); + } if (error_class == GITERR_OS) { #ifdef GIT_WIN32 char * win32_error = git_win32_get_error_message(win32_error_code); if (win32_error) { - git_buf_PUTS(&buf, ": "); git_buf_puts(&buf, win32_error); git__free(win32_error); @@ -61,10 +65,8 @@ void giterr_set(int error_class, const char *string, ...) } else #endif - if (error_code) { - git_buf_PUTS(&buf, ": "); + if (error_code) git_buf_puts(&buf, strerror(error_code)); - } if (error_code) errno = 0; diff --git a/src/filebuf.c b/src/filebuf.c index d23bcc11c..25f6e52ef 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -334,8 +334,6 @@ int git_filebuf_commit(git_filebuf *file) file->fd = -1; - p_unlink(file->path_original); - if (p_rename(file->path_lock, file->path_original) < 0) { giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original); goto on_error; diff --git a/src/fileops.c b/src/fileops.c index bebbae4f9..34659ad3b 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -355,8 +355,9 @@ int git_futils_mkdir( if (p_mkdir(make_path.ptr, mode) < 0) { int tmp_errno = giterr_system_last(); - /* ignore error if directory already exists */ - if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + /* ignore error if not at end or if directory already exists */ + if (lastch == '\0' && + (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) { giterr_system_set(tmp_errno); giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); goto done; @@ -374,7 +375,8 @@ int git_futils_mkdir( if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && st.st_mode != mode && - (error = p_chmod(make_path.ptr, mode)) < 0) { + (error = p_chmod(make_path.ptr, mode)) < 0 && + lastch == '\0') { giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr); goto done; } diff --git a/src/global.c b/src/global.c index 1c4a7a1a9..f89c73256 100644 --- a/src/global.c +++ b/src/global.c @@ -16,6 +16,14 @@ git_mutex git__mwindow_mutex; #define MAX_SHUTDOWN_CB 8 +#ifdef GIT_SSL +# include <openssl/ssl.h> +SSL_CTX *git__ssl_ctx; +# ifdef GIT_THREADS +static git_mutex *openssl_locks; +# endif +#endif + static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; static git_atomic git__n_shutdown_callbacks; static git_atomic git__n_inits; @@ -39,6 +47,62 @@ static void git__shutdown(void) } +#if defined(GIT_THREADS) && defined(GIT_SSL) +void openssl_locking_function(int mode, int n, const char *file, int line) +{ + int lock; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + lock = mode & CRYPTO_LOCK; + + if (lock) { + git_mutex_lock(&openssl_locks[n]); + } else { + git_mutex_unlock(&openssl_locks[n]); + } +} +#endif + + +static void init_ssl(void) +{ +#ifdef GIT_SSL + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + git__ssl_ctx = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + +# ifdef GIT_THREADS + { + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + if (openssl_locks == NULL) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + + for (i = 0; i < num_locks; i++) { + if (git_mutex_init(&openssl_locks[i]) != 0) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + } + + CRYPTO_set_locking_callback(openssl_locking_function); + } +# endif +#endif +} + /** * Handle the global state with TLS * @@ -168,10 +232,14 @@ static void init_once(void) return; pthread_key_create(&_tls_key, &cb__free_status); + /* Initialize any other subsystems that have global state */ if ((init_error = git_hash_global_init()) >= 0) init_error = git_sysdir_global_init(); + /* OpenSSL needs to be initialized from the main thread */ + init_ssl(); + GIT_MEMORY_BARRIER; } @@ -225,6 +293,13 @@ static git_global_st __state; int git_threads_init(void) { + static int ssl_inited = 0; + + if (!ssl_inited) { + init_ssl(); + ssl_inited = 1; + } + git_atomic_inc(&git__n_inits); return 0; } diff --git a/src/global.h b/src/global.h index 778250376..106504628 100644 --- a/src/global.h +++ b/src/global.h @@ -13,8 +13,14 @@ typedef struct { git_error *last_error; git_error error_t; + char oid_fmt[41]; } git_global_st; +#ifdef GIT_SSL +# include <openssl/ssl.h> +extern SSL_CTX *git__ssl_ctx; +#endif + git_global_st *git__global_state(void); extern git_mutex git__mwindow_mutex; diff --git a/src/indexer.c b/src/indexer.c index 25c3d0537..010de329b 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -18,6 +18,8 @@ #include "oidmap.h" #include "zstream.h" +extern git_mutex git__mwindow_mutex; + #define UINT31_MAX (0x7FFFFFFF) struct entry { @@ -433,6 +435,8 @@ static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t git_map map; int error; + assert(data && size); + /* the offset needs to be at the beginning of the a page boundary */ page_start = (offset / page_size) * page_size; page_offset = offset - page_start; @@ -451,9 +455,12 @@ static int append_to_pack(git_indexer *idx, const void *data, size_t size) { git_off_t current_size = idx->pack->mwf.size; + if (!size) + return 0; + /* add the extra space we need at the end */ if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) { - giterr_system_set(errno); + giterr_set(GITERR_OS, "Failed to increase size of pack file '%s'", idx->pack->pack_name); return -1; } @@ -1044,6 +1051,11 @@ void git_indexer_free(git_indexer *idx) } git_vector_free_deep(&idx->deltas); - git_packfile_free(idx->pack); + + if (!git_mutex_lock(&git__mwindow_mutex)) { + git_packfile_free(idx->pack); + git_mutex_unlock(&git__mwindow_mutex); + } + git__free(idx); } diff --git a/src/merge.c b/src/merge.c index a279d31d4..add7eab64 100644 --- a/src/merge.c +++ b/src/merge.c @@ -228,8 +228,11 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l return -1; git_vector_foreach(twos, i, two) { - git_commit_list_parse(walk, two); + if (git_commit_list_parse(walk, two) < 0) + return -1; + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) return -1; } @@ -2193,8 +2196,7 @@ static int merge_normalize_checkout_opts( memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options)); else { git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; + default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options)); } @@ -2226,64 +2228,6 @@ static int merge_normalize_checkout_opts( return error; } -static int merge_affected_paths(git_vector *paths, git_repository *repo, git_index *index_new) -{ - git_tree *head_tree = NULL; - git_iterator *iter_head = NULL, *iter_new = NULL; - git_diff *merged_list = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_delta *delta; - size_t i; - const git_index_entry *e; - char *path; - int error = 0; - - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) - goto done; - - git_vector_foreach(&merged_list->deltas, i, delta) { - path = git__strdup(delta->new_file.path); - GITERR_CHECK_ALLOC(path); - - if ((error = git_vector_insert(paths, path)) < 0) - goto on_error; - } - - for (i = 0; i < git_index_entrycount(index_new); i++) { - e = git_index_get_byindex(index_new, i); - - if (git_index_entry_stage(e) != 0 && - (git_vector_last(paths) == NULL || - strcmp(git_vector_last(paths), e->path) != 0)) { - - path = git__strdup(e->path); - GITERR_CHECK_ALLOC(path); - - if ((error = git_vector_insert(paths, path)) < 0) - goto on_error; - } - } - - goto done; - -on_error: - git_vector_foreach(paths, i, path) - git__free(path); - - git_vector_clear(paths); - -done: - git_tree_free(head_tree); - git_iterator_free(iter_head); - git_iterator_free(iter_new); - git_diff_free(merged_list); - - return error; -} - static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) { git_tree *head_tree = NULL; @@ -2372,99 +2316,58 @@ done: return error; } -int git_merge__indexes(git_repository *repo, git_index *index_new) +int git_merge__check_result(git_repository *repo, git_index *index_new) { - git_index *index_repo = NULL; - int index_repo_caps = 0; + git_tree *head_tree = NULL; + git_iterator *iter_head = NULL, *iter_new = NULL; + git_diff *merged_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_delta *delta; git_vector paths = GIT_VECTOR_INIT; - size_t index_conflicts = 0, wd_conflicts = 0, conflicts, i; - char *path; + size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; const git_index_entry *e; - const git_index_name_entry *name; - const git_index_reuc_entry *reuc; int error = 0; - if ((error = git_repository_index(&index_repo, repo)) < 0) - goto done; - - /* Set the index to case sensitive to handle the merge */ - index_repo_caps = git_index_caps(index_repo); - - if ((error = git_index_set_caps(index_repo, (index_repo_caps & ~GIT_INDEXCAP_IGNORE_CASE))) < 0) - goto done; - - /* Make sure the index and workdir state do not prevent merging */ - if ((error = merge_affected_paths(&paths, repo, index_new)) < 0 || - (error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || - (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) - goto done; - - if ((conflicts = index_conflicts + wd_conflicts) > 0) { - giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge", - conflicts, (conflicts != 1) ? "s" : ""); - error = GIT_EMERGECONFLICT; - + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) goto done; - } - - /* Remove removed items from the index */ - git_vector_foreach(&paths, i, path) { - if (git_index_get_bypath(index_new, path, 0) == NULL) { - if ((error = git_index_remove(index_repo, path, 0)) < 0 && - error != GIT_ENOTFOUND) - goto done; - } - } - /* Add updated items to the index */ - git_vector_foreach(&paths, i, path) { - if ((e = git_index_get_bypath(index_new, path, 0)) != NULL) { - if ((error = git_index_add(index_repo, e)) < 0) - goto done; - } + git_vector_foreach(&merged_list->deltas, i, delta) { + if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) + goto done; } - /* Add conflicts */ - git_index_conflict_cleanup(index_repo); - for (i = 0; i < git_index_entrycount(index_new); i++) { e = git_index_get_byindex(index_new, i); if (git_index_entry_stage(e) != 0 && - (error = git_index_add(index_repo, e)) < 0) - goto done; - } - - /* Add name entries */ - git_index_name_clear(index_repo); - - for (i = 0; i < git_index_name_entrycount(index_new); i++) { - name = git_index_name_get_byindex(index_new, i); + (git_vector_last(&paths) == NULL || + strcmp(git_vector_last(&paths), e->path) != 0)) { - if ((error = git_index_name_add(index_repo, - name->ancestor, name->ours, name->theirs)) < 0) - goto done; + if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) + goto done; + } } - /* Add the reuc */ - git_index_reuc_clear(index_repo); - - for (i = 0; i < git_index_reuc_entrycount(index_new); i++) { - reuc = (git_index_reuc_entry *)git_index_reuc_get_byindex(index_new, i); + /* Make sure the index and workdir state do not prevent merging */ + if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || + (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) + goto done; - if ((error = git_index_reuc_add(index_repo, reuc->path, - reuc->mode[0], &reuc->oid[0], - reuc->mode[1], &reuc->oid[1], - reuc->mode[2], &reuc->oid[2])) < 0) - goto done; + if ((conflicts = index_conflicts + wd_conflicts) > 0) { + giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge", + conflicts, (conflicts != 1) ? "s" : ""); + error = GIT_EMERGECONFLICT; } done: - if (index_repo != NULL) - git_index_set_caps(index_repo, index_repo_caps); - - git_index_free(index_repo); - git_vector_free_deep(&paths); + git_vector_free(&paths); + git_tree_free(head_tree); + git_iterator_free(iter_head); + git_iterator_free(iter_new); + git_diff_free(merged_list); return error; } @@ -2479,12 +2382,14 @@ int git_merge__append_conflicts_to_merge_msg( size_t i; int error; + if (!git_index_has_conflicts(index)) + return 0; + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; - if (git_index_has_conflicts(index)) - git_filebuf_printf(&file, "\nConflicts:\n"); + git_filebuf_printf(&file, "\nConflicts:\n"); for (i = 0; i < git_index_entrycount(index); i++) { const git_index_entry *e = git_index_get_byindex(index, i); @@ -2509,7 +2414,6 @@ cleanup: return error; } - static int merge_state_cleanup(git_repository *repo) { const char *state_files[] = { @@ -2657,7 +2561,7 @@ int git_merge( git_checkout_options checkout_opts; git_merge_head *ancestor_head = NULL, *our_head = NULL; git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL; - git_index *index_new = NULL, *index_repo = NULL; + git_index *index_new = NULL; size_t i; int error = 0; @@ -2697,10 +2601,9 @@ int git_merge( /* TODO: recursive, octopus, etc... */ if ((error = git_merge_trees(&index_new, repo, ancestor_tree, our_tree, their_trees[0], merge_opts)) < 0 || - (error = git_merge__indexes(repo, index_new)) < 0 || - (error = git_repository_index(&index_repo, repo)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 || - (error = git_checkout_index(repo, index_repo, &checkout_opts)) < 0) + (error = git_merge__check_result(repo, index_new)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 || + (error = git_checkout_index(repo, index_new, &checkout_opts)) < 0) goto on_error; goto done; @@ -2710,7 +2613,6 @@ on_error: done: git_index_free(index_new); - git_index_free(index_repo); git_tree_free(ancestor_tree); git_tree_free(our_tree); diff --git a/src/merge.h b/src/merge.h index 00f6197bf..cc17389ab 100644 --- a/src/merge.h +++ b/src/merge.h @@ -149,7 +149,7 @@ int git_merge__setup( const git_merge_head *heads[], size_t heads_len); -int git_merge__indexes(git_repository *repo, git_index *index_new); +int git_merge__check_result(git_repository *repo, git_index *index_new); int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); diff --git a/src/mwindow.c b/src/mwindow.c index 7e5fcdfbc..1d64d26a4 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -11,6 +11,10 @@ #include "fileops.h" #include "map.h" #include "global.h" +#include "strmap.h" +#include "pack.h" + +GIT__USE_STRMAP; #define DEFAULT_WINDOW_SIZE \ (sizeof(void*) >= 8 \ @@ -26,20 +30,127 @@ size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; /* Whenever you want to read or modify this, grab git__mwindow_mutex */ static git_mwindow_ctl mem_ctl; -/* - * Free all the windows in a sequence, typically because we're done - * with the file +/* Global list of mwindow files, to open packs once across repos */ +git_strmap *git__pack_cache = NULL; + +/** + * Run under mwindow lock */ -void git_mwindow_free_all(git_mwindow_file *mwf) +int git_mwindow_files_init(void) { - git_mwindow_ctl *ctl = &mem_ctl; - size_t i; + if (git__pack_cache) + return 0; + + return git_strmap_alloc(&git__pack_cache); +} + +void git_mwindow_files_free(void) +{ + git_strmap *tmp = git__pack_cache; + + git__pack_cache = NULL; + git_strmap_free(tmp); +} + +int git_mwindow_get_pack(struct git_pack_file **out, const char *path) +{ + int error; + char *packname; + git_strmap_iter pos; + struct git_pack_file *pack; + + if ((error = git_packfile__name(&packname, path)) < 0) + return error; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) { + giterr_set(GITERR_OS, "failed to lock mwindow mutex"); + return -1; + } + + if (git_mwindow_files_init() < 0) { + git_mutex_unlock(&git__mwindow_mutex); + git__free(packname); + return -1; + } + + pos = git_strmap_lookup_index(git__pack_cache, packname); + git__free(packname); + + if (git_strmap_valid_index(git__pack_cache, pos)) { + pack = git_strmap_value_at(git__pack_cache, pos); + git_atomic_inc(&pack->refcount); + + git_mutex_unlock(&git__mwindow_mutex); + *out = pack; + return 0; + } + + /* If we didn't find it, we need to create it */ + if ((error = git_packfile_alloc(&pack, path)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return error; + } + + git_atomic_inc(&pack->refcount); + + git_strmap_insert(git__pack_cache, pack->pack_name, pack, error); + git_mutex_unlock(&git__mwindow_mutex); + + if (error < 0) { + git_packfile_free(pack); + return -1; + } + + *out = pack; + return 0; +} + +void git_mwindow_put_pack(struct git_pack_file *pack) +{ + int count; + git_strmap_iter pos; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) + return; + + /* put before get would be a corrupted state */ + assert(git__pack_cache); + + pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name); + /* if we cannot find it, the state is corrupted */ + assert(git_strmap_valid_index(git__pack_cache, pos)); + + count = git_atomic_dec(&pack->refcount); + if (count == 0) { + git_strmap_delete_at(git__pack_cache, pos); + git_packfile_free(pack); + } + + git_mutex_unlock(&git__mwindow_mutex); + return; +} +void git_mwindow_free_all(git_mwindow_file *mwf) +{ if (git_mutex_lock(&git__mwindow_mutex)) { giterr_set(GITERR_THREAD, "unable to lock mwindow mutex"); return; } + git_mwindow_free_all_locked(mwf); + + git_mutex_unlock(&git__mwindow_mutex); +} + +/* + * Free all the windows in a sequence, typically because we're done + * with the file + */ +void git_mwindow_free_all_locked(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &mem_ctl; + size_t i; + /* * Remove these windows from the global list */ @@ -67,8 +178,6 @@ void git_mwindow_free_all(git_mwindow_file *mwf) mwf->windows = w->next; git__free(w); } - - git_mutex_unlock(&git__mwindow_mutex); } /* diff --git a/src/mwindow.h b/src/mwindow.h index 0018ebbf0..63418e458 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -36,10 +36,18 @@ typedef struct git_mwindow_ctl { } git_mwindow_ctl; int git_mwindow_contains(git_mwindow *win, git_off_t offset); -void git_mwindow_free_all(git_mwindow_file *mwf); +void git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ +void git_mwindow_free_all_locked(git_mwindow_file *mwf); /* run under lock */ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); int git_mwindow_file_register(git_mwindow_file *mwf); void git_mwindow_file_deregister(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); +int git_mwindow_files_init(void); +void git_mwindow_files_free(void); + +struct git_pack_file; /* just declaration to avoid cyclical includes */ +int git_mwindow_get_pack(struct git_pack_file **out, const char *path); +void git_mwindow_put_pack(struct git_pack_file *pack); + #endif diff --git a/src/netops.c b/src/netops.c index a0193a022..fceb4fb74 100644 --- a/src/netops.c +++ b/src/netops.c @@ -13,6 +13,7 @@ # include <netinet/in.h> # include <arpa/inet.h> #else +# include <winsock2.h> # include <ws2tcpip.h> # ifdef _MSC_VER # pragma comment(lib, "ws2_32") @@ -33,6 +34,7 @@ #include "posix.h" #include "buffer.h" #include "http_parser.h" +#include "global.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -157,7 +159,7 @@ void gitno_buffer_setup_callback( void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) { #ifdef GIT_SSL - if (socket->ssl.ctx) { + if (socket->ssl.ssl) { gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); return; } @@ -202,7 +204,6 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) ret = 0; SSL_free(ssl->ssl); - SSL_CTX_free(ssl->ctx); return ret; } @@ -390,18 +391,12 @@ static int ssl_setup(gitno_socket *socket, const char *host, int flags) { int ret; - SSL_library_init(); - SSL_load_error_strings(); - socket->ssl.ctx = SSL_CTX_new(SSLv23_method()); - if (socket->ssl.ctx == NULL) - return ssl_set_error(&socket->ssl, 0); - - SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx)) - return ssl_set_error(&socket->ssl, 0); + if (git__ssl_ctx == NULL) { + giterr_set(GITERR_NET, "OpenSSL initialization failed"); + return -1; + } - socket->ssl.ssl = SSL_new(socket->ssl.ctx); + socket->ssl.ssl = SSL_new(git__ssl_ctx); if (socket->ssl.ssl == NULL) return ssl_set_error(&socket->ssl, 0); @@ -538,7 +533,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) size_t off = 0; #ifdef GIT_SSL - if (socket->ssl.ctx) + if (socket->ssl.ssl) return gitno_send_ssl(&socket->ssl, msg, len, flags); #endif @@ -559,7 +554,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) int gitno_close(gitno_socket *s) { #ifdef GIT_SSL - if (s->ssl.ctx && + if (s->ssl.ssl && gitno_ssl_teardown(&s->ssl) < 0) return -1; #endif @@ -723,6 +718,9 @@ int gitno_extract_url_parts( if (u.field_set & (1 << UF_PATH)) { *path = git__substrdup(_path, u.field_data[UF_PATH].len); GITERR_CHECK_ALLOC(*path); + } else { + giterr_set(GITERR_NET, "invalid url, missing path"); + return GIT_EINVALIDSPEC; } if (u.field_set & (1 << UF_USERINFO)) { diff --git a/src/netops.h b/src/netops.h index 8e3a2524f..dfb4ab7b4 100644 --- a/src/netops.h +++ b/src/netops.h @@ -16,7 +16,6 @@ struct gitno_ssl { #ifdef GIT_SSL - SSL_CTX *ctx; SSL *ssl; #else size_t dummy; diff --git a/src/odb_pack.c b/src/odb_pack.c index 3750da37f..1757cf920 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -210,7 +210,7 @@ static int packfile_load__cb(void *data, git_buf *path) return 0; } - error = git_packfile_alloc(&pack, path->ptr); + error = git_mwindow_get_pack(&pack, path->ptr); /* ignore missing .pack file as git does */ if (error == GIT_ENOTFOUND) { @@ -605,7 +605,7 @@ static void pack_backend__free(git_odb_backend *_backend) for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p = git_vector_get(&backend->packs, i); - git_packfile_free(p); + git_mwindow_put_pack(p); } git_vector_free(&backend->packs); @@ -647,7 +647,7 @@ int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) if (pack_backend__alloc(&backend, 1) < 0) return -1; - if (git_packfile_alloc(&packfile, idx) < 0 || + if (git_mwindow_get_pack(&packfile, idx) < 0 || git_vector_insert(&backend->packs, packfile) < 0) { pack_backend__free((git_odb_backend *)backend); @@ -664,6 +664,9 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) struct pack_backend *backend = NULL; git_buf path = GIT_BUF_INIT; + if (git_mwindow_files_init() < 0) + return -1; + if (pack_backend__alloc(&backend, 8) < 0) return -1; @@ -8,6 +8,7 @@ #include "common.h" #include "git2/oid.h" #include "repository.h" +#include "global.h" #include <string.h> #include <limits.h> @@ -99,6 +100,13 @@ void git_oid_pathfmt(char *str, const git_oid *oid) str = fmt_one(str, oid->id[i]); } +char *git_oid_tostr_s(const git_oid *oid) +{ + char *str = GIT_GLOBAL->oid_fmt; + git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); + return str; +} + char *git_oid_allocfmt(const git_oid *oid) { char *str = git__malloc(GIT_OID_HEXSZ + 1); @@ -9,6 +9,17 @@ #include "git2/oid.h" +/** + * Format a git_oid into a newly allocated c-string. + * + * The c-string is owned by the caller and needs to be manually freed. + * + * @param id the oid structure to format + * @return the c-string; NULL if memory is exhausted. Caller must + * deallocate the string with git__free(). + */ +char *git_oid_allocfmt(const git_oid *id); + GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) { int i; diff --git a/src/pack.c b/src/pack.c index ace7abb58..b05aa91f1 100644 --- a/src/pack.c +++ b/src/pack.c @@ -744,7 +744,7 @@ cleanup: git__free(obj->data); if (elem) - *obj_offset = elem->offset; + *obj_offset = curpos; git_array_clear(chain); return error; @@ -968,10 +968,10 @@ void git_packfile_free(struct git_pack_file *p) cache_free(&p->bases); - git_mwindow_free_all(&p->mwf); - - if (p->mwf.fd >= 0) + if (p->mwf.fd >= 0) { + git_mwindow_free_all_locked(&p->mwf); p_close(p->mwf.fd); + } pack_index_free(p); @@ -1063,6 +1063,23 @@ cleanup: return -1; } +int git_packfile__name(char **out, const char *path) +{ + size_t path_len; + git_buf buf = GIT_BUF_INIT; + + path_len = strlen(path); + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL); + + if (git_buf_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) + return -1; + + *out = git_buf_detach(&buf); + return 0; +} + int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) { struct stat st; diff --git a/src/pack.h b/src/pack.h index 610e70c18..34d37d907 100644 --- a/src/pack.h +++ b/src/pack.h @@ -90,6 +90,7 @@ struct git_pack_file { git_mwindow_file mwf; git_map index_map; git_mutex lock; /* protect updates to mwf and index_map */ + git_atomic refcount; uint32_t num_objects; uint32_t num_bad_objects; @@ -123,6 +124,8 @@ typedef struct git_packfile_stream { size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type); +int git_packfile__name(char **out, const char *path); + int git_packfile_unpack_header( size_t *size_p, git_otype *type_p, diff --git a/src/path.c b/src/path.c index 5beab97ed..77f8d8858 100644 --- a/src/path.c +++ b/src/path.c @@ -377,26 +377,33 @@ static int error_invalid_local_file_uri(const char *uri) return -1; } -int git_path_fromurl(git_buf *local_path_out, const char *file_url) +static int local_file_url_prefixlen(const char *file_url) { - int offset = 0, len; + int len = -1; - assert(local_path_out && file_url); + if (git__prefixcmp(file_url, "file://") == 0) { + if (file_url[7] == '/') + len = 8; + else if (git__prefixcmp(file_url + 7, "localhost/") == 0) + len = 17; + } - if (git__prefixcmp(file_url, "file://") != 0) - return error_invalid_local_file_uri(file_url); + return len; +} + +bool git_path_is_local_file_url(const char *file_url) +{ + return (local_file_url_prefixlen(file_url) > 0); +} - offset += 7; - len = (int)strlen(file_url); +int git_path_fromurl(git_buf *local_path_out, const char *file_url) +{ + int offset; - if (offset < len && file_url[offset] == '/') - offset++; - else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0) - offset += 10; - else - return error_invalid_local_file_uri(file_url); + assert(local_path_out && file_url); - if (offset >= len || file_url[offset] == '/') + if ((offset = local_file_url_prefixlen(file_url)) < 0 || + file_url[offset] == '\0' || file_url[offset] == '/') return error_invalid_local_file_uri(file_url); #ifndef GIT_WIN32 @@ -404,7 +411,6 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url) #endif git_buf_clear(local_path_out); - return git__percent_decode(local_path_out, file_url + offset); } @@ -1104,20 +1110,29 @@ int git_path_dirload_with_stat( if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || (error = git_path_lstat(full.ptr, &ps->st)) < 0) { + if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = 0; + /* file was removed between readdir and lstat */ git_vector_remove(contents, i--); - continue; + } else { + /* Treat the file as unreadable if we get any other error */ + memset(&ps->st, 0, sizeof(ps->st)); + ps->st.st_mode = GIT_FILEMODE_UNREADABLE; } - break; + giterr_clear(); + error = 0; + continue; } if (S_ISDIR(ps->st.st_mode)) { ps->path[ps->path_len++] = '/'; ps->path[ps->path_len] = '\0'; } + else if (!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) { + /* skip everything but dirs, plain files, and symlinks */ + git_vector_remove(contents, i--); + } } /* sort now that directory suffix is added */ @@ -1130,18 +1145,8 @@ int git_path_dirload_with_stat( int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) { - int error; - - /* If url_or_path begins with file:// treat it as a URL */ - if (!git__prefixcmp(url_or_path, "file://")) { - if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { - return error; - } - } else { /* We assume url_or_path is already a path */ - if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { - return error; - } - } - - return 0; + if (git_path_is_local_file_url(url_or_path)) + return git_path_fromurl(local_path_out, url_or_path); + else + return git_buf_sets(local_path_out, url_or_path); } diff --git a/src/path.h b/src/path.h index 3e6efe3de..46d6efe93 100644 --- a/src/path.h +++ b/src/path.h @@ -8,6 +8,7 @@ #define INCLUDE_path_h__ #include "common.h" +#include "posix.h" #include "buffer.h" #include "vector.h" @@ -127,6 +128,14 @@ GIT_INLINE(int) git_path_is_relative(const char *p) return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); } +/** + * Check if string is at end of path segment (i.e. looking at '/' or '\0') + */ +GIT_INLINE(int) git_path_at_end_of_segment(const char *p) +{ + return !*p || *p == '/'; +} + extern int git__percent_decode(git_buf *decoded_out, const char *input); /** @@ -439,6 +448,7 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); extern bool git_path_does_fs_decompose_unicode(const char *root); /* Used for paths to repositories on the filesystem */ +extern bool git_path_is_local_file_url(const char *file_url); extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path); #endif diff --git a/src/pool.c b/src/pool.c index 146f118b4..a516ff9eb 100644 --- a/src/pool.c +++ b/src/pool.c @@ -146,7 +146,7 @@ GIT_INLINE(void) pool_remove_page( void *git_pool_malloc(git_pool *pool, uint32_t items) { git_pool_page *scan = pool->open, *prev; - uint32_t size = items * pool->item_size; + uint32_t size = ((items * pool->item_size) + 7) & ~7; void *ptr = NULL; pool->has_string_alloc = 0; diff --git a/src/posix.h b/src/posix.h index 965cd98d5..9ef348739 100644 --- a/src/posix.h +++ b/src/posix.h @@ -12,23 +12,61 @@ #include <time.h> #include "fnmatch.h" +/* stat: file mode type testing macros */ #ifndef S_IFGITLINK #define S_IFGITLINK 0160000 #define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) #endif +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#undef _S_IFLNK +#define _S_IFLNK S_IFLNK +#endif + +#ifndef S_IXUSR +#define S_IXUSR 00100 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#ifndef S_ISFIFO +#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +#endif + /* if S_ISGID is not defined, then don't try to set it */ #ifndef S_ISGID #define S_ISGID 0 #endif -#if !defined(O_BINARY) +#ifndef O_BINARY #define O_BINARY 0 #endif -#if !defined(O_CLOEXEC) +#ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif +/* access() mode parameter #defines */ +#ifndef F_OK +#define F_OK 0 /* existence check */ +#endif +#ifndef W_OK +#define W_OK 2 /* write mode check */ +#endif +#ifndef R_OK +#define R_OK 4 /* read mode check */ +#endif + /* Determine whether an errno value indicates that a read or write failed * because the descriptor is blocked. */ @@ -38,6 +76,12 @@ #define GIT_ISBLOCKED(e) ((e) == EAGAIN) #endif +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT (INT_MAX-1) +#endif + typedef int git_file; /** @@ -56,8 +100,6 @@ typedef int git_file; extern int p_read(git_file fd, void *buf, size_t cnt); extern int p_write(git_file fd, const void *buf, size_t cnt); -#define p_fstat(f,b) fstat(f, b) -#define p_lseek(f,n,w) lseek(f, n, w) #define p_close(fd) close(fd) #define p_umask(m) umask(m) @@ -66,30 +108,6 @@ extern int p_creat(const char *path, mode_t mode); extern int p_getcwd(char *buffer_out, size_t size); extern int p_rename(const char *from, const char *to); -#ifndef GIT_WIN32 - -#define p_stat(p,b) stat(p, b) -#define p_chdir(p) chdir(p) -#define p_rmdir(p) rmdir(p) -#define p_chmod(p,m) chmod(p, m) -#define p_access(p,m) access(p,m) -#define p_ftruncate(fd, sz) ftruncate(fd, sz) -#define p_recv(s,b,l,f) recv(s,b,l,f) -#define p_send(s,b,l,f) send(s,b,l,f) -typedef int GIT_SOCKET; -#define INVALID_SOCKET -1 - -#define p_localtime_r localtime_r -#define p_gmtime_r gmtime_r - -#else - -typedef SOCKET GIT_SOCKET; -extern struct tm * p_localtime_r (const time_t *timer, struct tm *result); -extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result); - -#endif - /** * Platform-dependent methods */ diff --git a/src/refs.h b/src/refs.h index f75a4bf7e..2e79bdfca 100644 --- a/src/refs.h +++ b/src/refs.h @@ -35,7 +35,7 @@ #define GIT_FETCH_HEAD_FILE "FETCH_HEAD" #define GIT_MERGE_HEAD_FILE "MERGE_HEAD" #define GIT_REVERT_HEAD_FILE "REVERT_HEAD" -#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD" +#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD" #define GIT_BISECT_LOG_FILE "BISECT_LOG" #define GIT_REBASE_MERGE_DIR "rebase-merge/" #define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" @@ -63,7 +63,7 @@ struct git_reference { } target; git_oid peel; - char name[0]; + char name[GIT_FLEX_ARRAY]; }; git_reference *git_reference__set_name(git_reference *ref, const char *name); diff --git a/src/refspec.c b/src/refspec.c index fa60aa7aa..9f0df35a7 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -181,39 +181,75 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) static int refspec_transform( git_buf *out, const char *from, const char *to, const char *name) { - size_t to_len = to ? strlen(to) : 0; - size_t from_len = from ? strlen(from) : 0; - size_t name_len = name ? strlen(name) : 0; + const char *from_star, *to_star; + const char *name_slash, *from_slash; + size_t replacement_len, star_offset; git_buf_sanitize(out); + git_buf_clear(out); - if (git_buf_set(out, to, to_len) < 0) - return -1; + /* + * There are two parts to each side of a refspec, the bit + * before the star and the bit after it. The star can be in + * the middle of the pattern, so we need to look at each bit + * individually. + */ + from_star = strchr(from, '*'); + to_star = strchr(to, '*'); - if (to_len > 0) { - /* No '*' at the end of 'to' means that refspec is mapped to one - * specific branch, so no actual transformation is needed. - */ - if (out->ptr[to_len - 1] != '*') - return 0; - git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */ - } + assert(from_star && to_star); + + /* star offset, both in 'from' and in 'name' */ + star_offset = from_star - from; - if (from_len > 0) /* ignore trailing '*' from 'from' */ - from_len--; - if (from_len > name_len) - from_len = name_len; + /* the first half is copied over */ + git_buf_put(out, to, to_star - to); - return git_buf_put(out, name + from_len, name_len - from_len); + /* then we copy over the replacement, from the star's offset to the next slash in 'name' */ + name_slash = strchr(name + star_offset, '/'); + if (!name_slash) + name_slash = strrchr(name, '\0'); + + /* if there is no slash after the star in 'from', we want to copy everything over */ + from_slash = strchr(from + star_offset, '/'); + if (!from_slash) + name_slash = strrchr(name, '\0'); + + replacement_len = (name_slash - name) - star_offset; + git_buf_put(out, name + star_offset, replacement_len); + + return git_buf_puts(out, to_star + 1); } int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) { + assert(out && spec && name); + git_buf_sanitize(out); + + if (!git_refspec_src_matches(spec, name)) { + giterr_set(GITERR_INVALID, "ref '%s' doesn't match the source", name); + return -1; + } + + if (!spec->pattern) + return git_buf_puts(out, spec->dst); + return refspec_transform(out, spec->src, spec->dst, name); } int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) { + assert(out && spec && name); + git_buf_sanitize(out); + + if (!git_refspec_dst_matches(spec, name)) { + giterr_set(GITERR_INVALID, "ref '%s' doesn't match the destination", name); + return -1; + } + + if (!spec->pattern) + return git_buf_puts(out, spec->src); + return refspec_transform(out, spec->dst, spec->src, name); } diff --git a/src/remote.c b/src/remote.c index 47b61b1b1..433015f60 100644 --- a/src/remote.c +++ b/src/remote.c @@ -267,9 +267,11 @@ int git_remote_dup(git_remote **dest, git_remote *source) if (source->pushurl != NULL) { remote->pushurl = git__strdup(source->pushurl); - GITERR_CHECK_ALLOC(remote->pushurl); + GITERR_CHECK_ALLOC(remote->pushurl); } + remote->transport_cb = source->transport_cb; + remote->transport_cb_payload = source->transport_cb_payload; remote->repo = source->repo; remote->download_tags = source->download_tags; remote->check_cert = source->check_cert; @@ -659,8 +661,14 @@ int git_remote_connect(git_remote *remote, git_direction direction) return -1; } - /* A transport could have been supplied in advance with - * git_remote_set_transport */ + /* If we don't have a transport object yet, and the caller specified a + * custom transport factory, use that */ + if (!t && remote->transport_cb && + (error = remote->transport_cb(&t, remote, remote->transport_cb_payload)) < 0) + return error; + + /* If we still don't have a transport, then use the global + * transport registrations which map URI schemes to transport factories */ if (!t && (error = git_transport_new(&t, remote, url)) < 0) return error; @@ -691,6 +699,11 @@ int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote { assert(remote); + if (!remote->transport) { + giterr_set(GITERR_NET, "No transport bound to this remote"); + return -1; + } + return remote->transport->ls(out, size, remote->transport); } @@ -1262,18 +1275,20 @@ const git_remote_callbacks *git_remote_get_callbacks(git_remote *remote) return &remote->callbacks; } -int git_remote_set_transport(git_remote *remote, git_transport *transport) +int git_remote_set_transport( + git_remote *remote, + git_transport_cb transport_cb, + void *payload) { - assert(remote && transport); - - GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); + assert(remote); if (remote->transport) { giterr_set(GITERR_NET, "A transport is already bound to this remote"); return -1; } - remote->transport = transport; + remote->transport_cb = transport_cb; + remote->transport_cb_payload = payload; return 0; } @@ -1932,6 +1947,8 @@ int git_remote_default_branch(git_buf *out, git_remote *remote) size_t heads_len, i; int error; + assert(out); + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) return error; diff --git a/src/remote.h b/src/remote.h index 4164a14b3..c471756b8 100644 --- a/src/remote.h +++ b/src/remote.h @@ -9,6 +9,7 @@ #include "git2/remote.h" #include "git2/transport.h" +#include "git2/sys/transport.h" #include "refspec.h" #include "vector.h" @@ -22,6 +23,8 @@ struct git_remote { git_vector refs; git_vector refspecs; git_vector active_refspecs; + git_transport_cb transport_cb; + void *transport_cb_payload; git_transport *transport; git_repository *repo; git_remote_callbacks callbacks; diff --git a/src/repository.c b/src/repository.c index e8d50aed3..4f7a5feab 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1915,8 +1915,8 @@ int git_repository_state(git_repository *repo) state = GIT_REPOSITORY_STATE_MERGE; else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) state = GIT_REPOSITORY_STATE_REVERT; - else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE)) - state = GIT_REPOSITORY_STATE_CHERRY_PICK; + else if(git_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_CHERRYPICK; else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) state = GIT_REPOSITORY_STATE_BISECT; @@ -1958,7 +1958,7 @@ static const char *state_files[] = { GIT_MERGE_MODE_FILE, GIT_MERGE_MSG_FILE, GIT_REVERT_HEAD_FILE, - GIT_CHERRY_PICK_HEAD_FILE, + GIT_CHERRYPICK_HEAD_FILE, GIT_BISECT_LOG_FILE, GIT_REBASE_MERGE_DIR, GIT_REBASE_APPLY_DIR, diff --git a/src/revert.c b/src/revert.c index 9c587724b..36560a77c 100644 --- a/src/revert.c +++ b/src/revert.c @@ -174,7 +174,7 @@ int git_revert( char commit_oidstr[GIT_OID_HEXSZ + 1]; const char *commit_msg; git_buf their_label = GIT_BUF_INIT; - git_index *index_new = NULL, *index_repo = NULL; + git_index *index_new = NULL; int error; assert(repo && commit); @@ -199,10 +199,9 @@ int git_revert( (error = git_repository_head(&our_ref, repo)) < 0 || (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 || (error = git_revert_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__indexes(repo, index_new)) < 0 || - (error = git_repository_index(&index_repo, repo)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 || - (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0) + (error = git_merge__check_result(repo, index_new)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 || + (error = git_checkout_index(repo, index_new, &opts.checkout_opts)) < 0) goto on_error; goto done; @@ -212,7 +211,6 @@ on_error: done: git_index_free(index_new); - git_index_free(index_repo); git_commit_free(our_commit); git_reference_free(our_ref); git_buf_free(&their_label); diff --git a/src/revwalk.c b/src/revwalk.c index 530c9705e..bd07d02cb 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -48,9 +48,6 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit) assert(commit); - git_array_init_to_size(pending, 2); - GITERR_CHECK_ARRAY(pending); - do { commit->uninteresting = 1; diff --git a/src/status.c b/src/status.c index 8d7612f72..cb2490042 100644 --- a/src/status.c +++ b/src/status.c @@ -62,6 +62,9 @@ static unsigned int workdir_delta2status( case GIT_DELTA_UNTRACKED: st = GIT_STATUS_WT_NEW; break; + case GIT_DELTA_UNREADABLE: + st = GIT_STATUS_WT_UNREADABLE; + break; case GIT_DELTA_DELETED: st = GIT_STATUS_WT_DELETED; break; @@ -310,6 +313,10 @@ int git_status_list_new( diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) findopt.flags = findopt.flags | @@ -329,8 +336,9 @@ int git_status_list_new( if (show != GIT_STATUS_SHOW_INDEX_ONLY) { if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, index, &diffopt)) < 0) + &status->idx2wd, repo, index, &diffopt)) < 0) { goto done; + } if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) @@ -407,8 +415,9 @@ int git_status_foreach_ext( size_t i; int error = 0; - if ((error = git_status_list_new(&status, repo, opts)) < 0) + if ((error = git_status_list_new(&status, repo, opts)) < 0) { return error; + } git_vector_foreach(&status->paired, i, status_entry) { const char *path = status_entry->head_to_index ? diff --git a/src/strnlen.h b/src/strnlen.h index fdd7fe39c..eecfe3c02 100644 --- a/src/strnlen.h +++ b/src/strnlen.h @@ -7,7 +7,8 @@ #ifndef INCLUDE_strlen_h__ #define INCLUDE_strlen_h__ -#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) +#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ + (defined(_MSC_VER) && _MSC_VER < 1500) # define NO_STRNLEN #endif diff --git a/src/thread-utils.h b/src/thread-utils.h index daec14eeb..5511a5117 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -53,12 +53,6 @@ typedef struct { #endif -#if defined(GIT_WIN32) -#define git_thread_yield() Sleep(0) -#else -#define git_thread_yield() sched_yield() -#endif - /* Pthreads Mutex */ #define git_mutex pthread_mutex_t #define git_mutex_init(a) pthread_mutex_init(a, NULL) @@ -186,7 +180,6 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #define git_thread unsigned int #define git_thread_create(thread, attr, start_routine, arg) 0 #define git_thread_join(id, status) (void)0 -#define git_thread_yield() (void)0 /* Pthreads Mutex */ #define git_mutex unsigned int diff --git a/src/trace.h b/src/trace.h index 4d4e3bf53..486084d01 100644 --- a/src/trace.h +++ b/src/trace.h @@ -46,8 +46,16 @@ GIT_INLINE(void) git_trace__write_fmt( #else +GIT_INLINE(void) git_trace__null( + git_trace_level_t level, + const char *fmt, ...) +{ + GIT_UNUSED(level); + GIT_UNUSED(fmt); +} + #define git_trace_level() ((void)0) -#define git_trace(lvl, ...) ((void)0) +#define git_trace git_trace__null #endif diff --git a/src/transport.c b/src/transport.c index 2194b1864..c0311fa90 100644 --- a/src/transport.c +++ b/src/transport.c @@ -9,11 +9,11 @@ #include "git2/remote.h" #include "git2/net.h" #include "git2/transport.h" +#include "git2/sys/transport.h" #include "path.h" typedef struct transport_definition { char *prefix; - unsigned priority; git_transport_cb fn; void *param; } transport_definition; @@ -24,51 +24,54 @@ static git_smart_subtransport_definition git_subtransport_definition = { git_sma static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 }; #endif -static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL }; +static transport_definition local_transport_definition = { "file://", git_transport_local, NULL }; #ifdef GIT_SSH -static transport_definition ssh_transport_definition = { "ssh://", 1, git_transport_smart, &ssh_subtransport_definition }; +static transport_definition ssh_transport_definition = { "ssh://", git_transport_smart, &ssh_subtransport_definition }; #else -static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL }; +static transport_definition dummy_transport_definition = { NULL, git_transport_dummy, NULL }; #endif static transport_definition transports[] = { - {"git://", 1, git_transport_smart, &git_subtransport_definition}, - {"http://", 1, git_transport_smart, &http_subtransport_definition}, - {"https://", 1, git_transport_smart, &http_subtransport_definition}, - {"file://", 1, git_transport_local, NULL}, + { "git://", git_transport_smart, &git_subtransport_definition }, + { "http://", git_transport_smart, &http_subtransport_definition }, + { "https://", git_transport_smart, &http_subtransport_definition }, + { "file://", git_transport_local, NULL }, #ifdef GIT_SSH - {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition}, + { "ssh://", git_transport_smart, &ssh_subtransport_definition }, #endif - {NULL, 0, 0} + { NULL, 0, 0 } }; -static git_vector additional_transports = GIT_VECTOR_INIT; +static git_vector custom_transports = GIT_VECTOR_INIT; #define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 -static int transport_find_fn(const char *url, git_transport_cb *callback, void **param) +static int transport_find_fn( + git_transport_cb *out, + const char *url, + void **param) { size_t i = 0; - unsigned priority = 0; - transport_definition *definition = NULL, *definition_iter; + transport_definition *d, *definition = NULL; - // First, check to see if it's an obvious URL, which a URL scheme - for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { - definition_iter = &transports[i]; - - if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix))) - continue; - - if (definition_iter->priority > priority) - definition = definition_iter; + /* Find a user transport who wants to deal with this URI */ + git_vector_foreach(&custom_transports, i, d) { + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + definition = d; + break; + } } - git_vector_foreach(&additional_transports, i, definition_iter) { - if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix))) - continue; + /* Find a system transport for this URI */ + if (!definition) { + for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { + d = &transports[i]; - if (definition_iter->priority > priority) - definition = definition_iter; + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + definition = d; + break; + } + } } #ifdef GIT_WIN32 @@ -90,7 +93,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * #ifdef GIT_SSH definition = &ssh_transport_definition; #else - definition = &dummy_transport_definition; + definition = &dummy_transport_definition; #endif #ifndef GIT_WIN32 @@ -100,9 +103,9 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * #endif if (!definition) - return -1; + return GIT_ENOTFOUND; - *callback = definition->fn; + *out = definition->fn; *param = definition->param; return 0; @@ -128,74 +131,96 @@ int git_transport_new(git_transport **out, git_remote *owner, const char *url) void *param; int error; - if (transport_find_fn(url, &fn, ¶m) < 0) { + if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) { giterr_set(GITERR_NET, "Unsupported URL protocol"); return -1; - } + } else if (error < 0) + return error; - error = fn(&transport, owner, param); - if (error < 0) + if ((error = fn(&transport, owner, param)) < 0) return error; + GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); + *out = transport; return 0; } int git_transport_register( - const char *prefix, - unsigned priority, + const char *scheme, git_transport_cb cb, void *param) { - transport_definition *d; + git_buf prefix = GIT_BUF_INIT; + transport_definition *d, *definition = NULL; + size_t i; + int error = 0; - d = git__calloc(sizeof(transport_definition), 1); - GITERR_CHECK_ALLOC(d); + assert(scheme); + assert(cb); - d->prefix = git__strdup(prefix); - - if (!d->prefix) + if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0) goto on_error; - d->priority = priority; - d->fn = cb; - d->param = param; + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + error = GIT_EEXISTS; + goto on_error; + } + } + + definition = git__calloc(1, sizeof(transport_definition)); + GITERR_CHECK_ALLOC(definition); + + definition->prefix = git_buf_detach(&prefix); + definition->fn = cb; + definition->param = param; - if (git_vector_insert(&additional_transports, d) < 0) + if (git_vector_insert(&custom_transports, definition) < 0) goto on_error; return 0; on_error: - git__free(d->prefix); - git__free(d); - return -1; + git_buf_free(&prefix); + git__free(definition); + return error; } -int git_transport_unregister( - const char *prefix, - unsigned priority) +int git_transport_unregister(const char *scheme) { + git_buf prefix = GIT_BUF_INIT; transport_definition *d; - unsigned i; + size_t i; + int error = 0; + + assert(scheme); - git_vector_foreach(&additional_transports, i, d) { - if (d->priority == priority && !strcasecmp(d->prefix, prefix)) { - if (git_vector_remove(&additional_transports, i) < 0) - return -1; + if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0) + goto done; + + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + if ((error = git_vector_remove(&custom_transports, i)) < 0) + goto done; git__free(d->prefix); git__free(d); - if (!additional_transports.length) - git_vector_free(&additional_transports); + if (!custom_transports.length) + git_vector_free(&custom_transports); - return 0; + error = 0; + goto done; } } - return GIT_ENOTFOUND; + error = GIT_ENOTFOUND; + +done: + git_buf_free(&prefix); + return error; } /* from remote.h */ @@ -204,7 +229,7 @@ int git_remote_valid_url(const char *url) git_transport_cb fn; void *param; - return !transport_find_fn(url, &fn, ¶m); + return !transport_find_fn(&fn, url, ¶m); } int git_remote_supported_url(const char* url) @@ -212,7 +237,7 @@ int git_remote_supported_url(const char* url) git_transport_cb fn; void *param; - if (transport_find_fn(url, &fn, ¶m) < 0) + if (transport_find_fn(&fn, url, ¶m) < 0) return 0; return fn != &git_transport_dummy; diff --git a/src/transports/auth.c b/src/transports/auth.c new file mode 100644 index 000000000..c1154db34 --- /dev/null +++ b/src/transports/auth.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" +#include "buffer.h" +#include "auth.h" + +static int basic_next_token( + git_buf *out, git_http_auth_context *ctx, git_cred *c) +{ + git_cred_userpass_plaintext *cred; + git_buf raw = GIT_BUF_INIT; + int error = -1; + + GIT_UNUSED(ctx); + + if (c->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) { + giterr_set(GITERR_INVALID, "invalid credential type for basic auth"); + goto on_error; + } + + cred = (git_cred_userpass_plaintext *)c; + + git_buf_printf(&raw, "%s:%s", cred->username, cred->password); + + if (git_buf_oom(&raw) || + git_buf_puts(out, "Authorization: Basic ") < 0 || + git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 || + git_buf_puts(out, "\r\n") < 0) + goto on_error; + + error = 0; + +on_error: + if (raw.size) + git__memzero(raw.ptr, raw.size); + + git_buf_free(&raw); + return error; +} + +static git_http_auth_context basic_context = { + GIT_AUTHTYPE_BASIC, + GIT_CREDTYPE_USERPASS_PLAINTEXT, + NULL, + basic_next_token, + NULL +}; + +int git_http_auth_basic( + git_http_auth_context **out, const gitno_connection_data *connection_data) +{ + GIT_UNUSED(connection_data); + + *out = &basic_context; + return 0; +} + +int git_http_auth_dummy( + git_http_auth_context **out, const gitno_connection_data *connection_data) +{ + GIT_UNUSED(connection_data); + + *out = NULL; + return 0; +} + diff --git a/src/transports/auth.h b/src/transports/auth.h new file mode 100644 index 000000000..52138cf8f --- /dev/null +++ b/src/transports/auth.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_http_auth_h__ +#define INCLUDE_http_auth_h__ + +#include "git2.h" +#include "netops.h" + +typedef enum { + GIT_AUTHTYPE_BASIC = 1, + GIT_AUTHTYPE_NEGOTIATE = 2, +} git_http_authtype_t; + +typedef struct git_http_auth_context git_http_auth_context; + +struct git_http_auth_context { + /** Type of scheme */ + git_http_authtype_t type; + + /** Supported credentials */ + git_credtype_t credtypes; + + /** Sets the challenge on the authentication context */ + int (*set_challenge)(git_http_auth_context *ctx, const char *challenge); + + /** Gets the next authentication token from the context */ + int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred); + + /** Frees the authentication context */ + void (*free)(git_http_auth_context *ctx); +}; + +typedef struct { + /** Type of scheme */ + git_http_authtype_t type; + + /** Name of the scheme (as used in the Authorization header) */ + const char *name; + + /** Credential types this scheme supports */ + git_credtype_t credtypes; + + /** Function to initialize an authentication context */ + int (*init_context)( + git_http_auth_context **out, + const gitno_connection_data *connection_data); +} git_http_auth_scheme; + +int git_http_auth_dummy( + git_http_auth_context **out, + const gitno_connection_data *connection_data); + +int git_http_auth_basic( + git_http_auth_context **out, + const gitno_connection_data *connection_data); + +#endif + diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c new file mode 100644 index 000000000..8b99fc735 --- /dev/null +++ b/src/transports/auth_negotiate.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifdef GIT_GSSAPI + +#include "git2.h" +#include "common.h" +#include "buffer.h" +#include "auth.h" + +#include <gssapi.h> +#include <krb5.h> + +static gss_OID_desc negotiate_oid_spnego = + { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; +static gss_OID_desc negotiate_oid_krb5 = + { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; + +static gss_OID negotiate_oids[] = + { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; + +typedef struct { + git_http_auth_context parent; + unsigned configured : 1, + complete : 1; + git_buf target; + char *challenge; + gss_ctx_id_t gss_context; + gss_OID oid; +} http_auth_negotiate_context; + +static void negotiate_err_set( + OM_uint32 status_major, + OM_uint32 status_minor, + const char *message) +{ + gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + OM_uint32 status_display, context = 0; + + if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE, + GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) { + giterr_set(GITERR_NET, "%s: %.*s (%d.%d)", + message, (int)buffer.length, (const char *)buffer.value, + status_major, status_minor); + gss_release_buffer(&status_minor, &buffer); + } else { + giterr_set(GITERR_NET, "%s: unknown negotiate error (%d.%d)", + message, status_major, status_minor); + } +} + +static int negotiate_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + assert(ctx && ctx->configured && challenge); + + git__free(ctx->challenge); + + ctx->challenge = git__strdup(challenge); + GITERR_CHECK_ALLOC(ctx->challenge); + + return 0; +} + +static int negotiate_next_token( + git_buf *buf, + git_http_auth_context *c, + git_cred *cred) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + OM_uint32 status_major, status_minor; + gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, + input_token = GSS_C_EMPTY_BUFFER, + output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER; + git_buf input_buf = GIT_BUF_INIT; + gss_name_t server = NULL; + gss_OID mech; + size_t challenge_len; + int error = 0; + + assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDTYPE_DEFAULT); + + if (ctx->complete) + return 0; + + target_buffer.value = (void *)ctx->target.ptr; + target_buffer.length = ctx->target.size; + + status_major = gss_import_name(&status_minor, &target_buffer, + GSS_C_NT_HOSTBASED_SERVICE, &server); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, + "Could not parse principal"); + error = -1; + goto done; + } + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + if (challenge_len < 9) { + giterr_set(GITERR_NET, "No negotiate challenge sent from server"); + error = -1; + goto done; + } else if (challenge_len > 9) { + if (git_buf_decode_base64(&input_buf, + ctx->challenge + 10, challenge_len - 10) < 0) { + giterr_set(GITERR_NET, "Invalid negotiate challenge from server"); + error = -1; + goto done; + } + + input_token.value = input_buf.ptr; + input_token.length = input_buf.size; + input_token_ptr = &input_token; + } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { + giterr_set(GITERR_NET, "Could not restart authentication"); + error = -1; + goto done; + } + + mech = &negotiate_oid_spnego; + + if (GSS_ERROR(status_major = gss_init_sec_context( + &status_minor, + GSS_C_NO_CREDENTIAL, + &ctx->gss_context, + server, + mech, + GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + input_token_ptr, + NULL, + &output_token, + NULL, + NULL))) { + negotiate_err_set(status_major, status_minor, "Negotiate failure"); + error = -1; + goto done; + } + + /* This message merely told us auth was complete; we do not respond. */ + if (status_major == GSS_S_COMPLETE) { + ctx->complete = 1; + goto done; + } + + git_buf_puts(buf, "Authorization: Negotiate "); + git_buf_encode_base64(buf, output_token.value, output_token.length); + git_buf_puts(buf, "\r\n"); + + if (git_buf_oom(buf)) + error = -1; + +done: + gss_release_name(&status_minor, &server); + gss_release_buffer(&status_minor, (gss_buffer_t) &output_token); + git_buf_free(&input_buf); + return error; +} + +static void negotiate_context_free(git_http_auth_context *c) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + OM_uint32 status_minor; + + if (ctx->gss_context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context( + &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER); + ctx->gss_context = GSS_C_NO_CONTEXT; + } + + git_buf_free(&ctx->target); + + git__free(ctx->challenge); + + ctx->configured = 0; + ctx->complete = 0; + ctx->oid = NULL; + + git__free(ctx); +} + +static int negotiate_init_context( + http_auth_negotiate_context *ctx, + const gitno_connection_data *connection_data) +{ + OM_uint32 status_major, status_minor; + gss_OID item, *oid; + gss_OID_set mechanism_list; + size_t i; + + /* Query supported mechanisms looking for SPNEGO) */ + if (GSS_ERROR(status_major = + gss_indicate_mechs(&status_minor, &mechanism_list))) { + negotiate_err_set(status_major, status_minor, + "could not query mechanisms"); + return -1; + } + + if (mechanism_list) { + for (oid = negotiate_oids; *oid; oid++) { + for (i = 0; i < mechanism_list->count; i++) { + item = &mechanism_list->elements[i]; + + if (item->length == (*oid)->length && + memcmp(item->elements, (*oid)->elements, item->length) == 0) { + ctx->oid = *oid; + break; + } + + } + + if (ctx->oid) + break; + } + } + + gss_release_oid_set(&status_minor, &mechanism_list); + + if (!ctx->oid) { + giterr_set(GITERR_NET, "Negotiate authentication is not supported"); + return -1; + } + + git_buf_puts(&ctx->target, "HTTP@"); + git_buf_puts(&ctx->target, connection_data->host); + + if (git_buf_oom(&ctx->target)) + return -1; + + ctx->gss_context = GSS_C_NO_CONTEXT; + ctx->configured = 1; + + return 0; +} + +int git_http_auth_negotiate( + git_http_auth_context **out, + const gitno_connection_data *connection_data) +{ + http_auth_negotiate_context *ctx; + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); + GITERR_CHECK_ALLOC(ctx); + + if (negotiate_init_context(ctx, connection_data) < 0) { + git__free(ctx); + return -1; + } + + ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE; + ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT; + ctx->parent.set_challenge = negotiate_set_challenge; + ctx->parent.next_token = negotiate_next_token; + ctx->parent.free = negotiate_context_free; + + *out = (git_http_auth_context *)ctx; + + return 0; +} + +#endif /* GIT_GSSAPI */ + diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h new file mode 100644 index 000000000..d7270b7ab --- /dev/null +++ b/src/transports/auth_negotiate.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_auth_negotiate_h__ +#define INCLUDE_auth_negotiate_h__ + +#include "git2.h" +#include "auth.h" + +#ifdef GIT_GSSAPI + +extern int git_http_auth_negotiate( + git_http_auth_context **out, + const gitno_connection_data *connection_data); + +#else + +#define git_http_auth_negotiate git_http_auth_dummy + +#endif /* GIT_GSSAPI */ + +#endif + diff --git a/src/transports/git.c b/src/transports/git.c index 21507c1c7..e2690fe36 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -8,6 +8,7 @@ #include "git2.h" #include "buffer.h" #include "netops.h" +#include "git2/sys/transport.h" #define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) diff --git a/src/transports/http.c b/src/transports/http.c index a7eff7365..f9df53b71 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -11,6 +11,13 @@ #include "buffer.h" #include "netops.h" #include "smart.h" +#include "auth.h" +#include "auth_negotiate.h" + +git_http_auth_scheme auth_schemes[] = { + { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, + { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic }, +}; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; @@ -20,7 +27,6 @@ static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive static const char *receive_pack_service_url = "/git-receive-pack"; static const char *get_verb = "GET"; static const char *post_verb = "POST"; -static const char *basic_authtype = "Basic"; #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) @@ -35,10 +41,6 @@ enum last_cb { VALUE }; -typedef enum { - GIT_HTTP_AUTH_BASIC = 1, -} http_authmechanism_t; - typedef struct { git_smart_subtransport_stream parent; const char *service; @@ -58,9 +60,6 @@ typedef struct { transport_smart *owner; gitno_socket socket; gitno_connection_data connection_data; - git_cred *cred; - git_cred *url_cred; - http_authmechanism_t auth_mechanism; bool connected; /* Parser structures */ @@ -76,6 +75,11 @@ typedef struct { enum last_cb last_cb; int parse_error; unsigned parse_finished : 1; + + /* Authentication */ + git_cred *cred; + git_cred *url_cred; + git_vector auth_contexts; } http_subtransport; typedef struct { @@ -88,28 +92,91 @@ typedef struct { size_t *bytes_read; } parser_context; -static int apply_basic_credential(git_buf *buf, git_cred *cred) +static bool credtype_match(git_http_auth_scheme *scheme, void *data) { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - git_buf raw = GIT_BUF_INIT; - int error = -1; + unsigned int credtype = *(unsigned int *)data; - git_buf_printf(&raw, "%s:%s", c->username, c->password); + return !!(scheme->credtypes & credtype); +} - if (git_buf_oom(&raw) || - git_buf_puts(buf, "Authorization: Basic ") < 0 || - git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 || - git_buf_puts(buf, "\r\n") < 0) - goto on_error; +static bool challenge_match(git_http_auth_scheme *scheme, void *data) +{ + const char *scheme_name = scheme->name; + const char *challenge = (const char *)data; + size_t scheme_len; - error = 0; + scheme_len = strlen(scheme_name); + return (strncmp(challenge, scheme_name, scheme_len) == 0 && + (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')); +} -on_error: - if (raw.size) - memset(raw.ptr, 0x0, raw.size); +static int auth_context_match( + git_http_auth_context **out, + http_subtransport *t, + bool (*scheme_match)(git_http_auth_scheme *scheme, void *data), + void *data) +{ + git_http_auth_scheme *scheme = NULL; + git_http_auth_context *context = NULL, *c; + size_t i; + + *out = NULL; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + if (scheme_match(&auth_schemes[i], data)) { + scheme = &auth_schemes[i]; + break; + } + } + + if (!scheme) + return 0; + + /* See if authentication has already started for this scheme */ + git_vector_foreach(&t->auth_contexts, i, c) { + if (c->type == scheme->type) { + context = c; + break; + } + } + + if (!context) { + if (scheme->init_context(&context, &t->connection_data) < 0) + return -1; + else if (!context) + return 0; + else if (git_vector_insert(&t->auth_contexts, context) < 0) + return -1; + } + + *out = context; + + return 0; +} + +static int apply_credentials(git_buf *buf, http_subtransport *t) +{ + git_cred *cred = t->cred; + git_http_auth_context *context; + + /* Apply the credentials given to us in the URL */ + if (!cred && t->connection_data.user && t->connection_data.pass) { + if (!t->url_cred && + git_cred_userpass_plaintext_new(&t->url_cred, + t->connection_data.user, t->connection_data.pass) < 0) + return -1; + + cred = t->url_cred; + } + + if (!cred) + return 0; - git_buf_free(&raw); - return error; + /* Get or create a context for the best scheme for this cred type */ + if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0) + return -1; + + return context->next_token(buf, context, cred); } static int gen_request( @@ -137,19 +204,9 @@ static int gen_request( git_buf_puts(buf, "Accept: */*\r\n"); /* Apply credentials to the request */ - if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && - t->auth_mechanism == GIT_HTTP_AUTH_BASIC && - apply_basic_credential(buf, t->cred) < 0) + if (apply_credentials(buf, t) < 0) return -1; - /* Use url-parsed basic auth if username and password are both provided */ - if (!t->cred && t->connection_data.user && t->connection_data.pass) { - if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred, - t->connection_data.user, t->connection_data.pass) < 0) - return -1; - if (apply_basic_credential(buf, t->url_cred) < 0) return -1; - } - git_buf_puts(buf, "\r\n"); if (git_buf_oom(buf)) @@ -158,20 +215,26 @@ static int gen_request( return 0; } -static int parse_unauthorized_response( +static int parse_authenticate_response( git_vector *www_authenticate, - int *allowed_types, - http_authmechanism_t *auth_mechanism) + http_subtransport *t, + int *allowed_types) { - unsigned i; - char *entry; - - git_vector_foreach(www_authenticate, i, entry) { - if (!strncmp(entry, basic_authtype, 5) && - (entry[5] == '\0' || entry[5] == ' ')) { - *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; - *auth_mechanism = GIT_HTTP_AUTH_BASIC; - } + git_http_auth_context *context; + char *challenge; + size_t i; + + git_vector_foreach(www_authenticate, i, challenge) { + if (auth_context_match(&context, t, challenge_match, challenge) < 0) + return -1; + else if (!context) + continue; + + if (context->set_challenge && + context->set_challenge(context, challenge) < 0) + return -1; + + *allowed_types |= context->credtypes; } return 0; @@ -248,7 +311,7 @@ static int on_headers_complete(http_parser *parser) http_subtransport *t = ctx->t; http_stream *s = ctx->s; git_buf buf = GIT_BUF_INIT; - int error = 0, no_callback = 0; + int error = 0, no_callback = 0, allowed_auth_types = 0; /* Both parse_header_name and parse_header_value are populated * and ready for consumption. */ @@ -256,26 +319,26 @@ static int on_headers_complete(http_parser *parser) if (on_header_ready(t) < 0) return t->parse_error = PARSE_ERROR_GENERIC; - /* Check for an authentication failure. */ + /* Capture authentication headers which may be a 401 (authentication + * is not complete) or a 200 (simply informing us that auth *is* + * complete.) + */ + if (parse_authenticate_response(&t->www_authenticate, t, + &allowed_auth_types) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; - if (parser->status_code == 401 && - get_verb == s->verb) { - if (!t->owner->cred_acquire_payload) { + /* Check for an authentication failure. */ + if (parser->status_code == 401 && get_verb == s->verb) { + if (!t->owner->cred_acquire_cb) { no_callback = 1; } else { - int allowed_types = 0; - - if (parse_unauthorized_response(&t->www_authenticate, - &allowed_types, &t->auth_mechanism) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - if (allowed_types && - (!t->cred || 0 == (t->cred->credtype & allowed_types))) { + if (allowed_auth_types && + (!t->cred || 0 == (t->cred->credtype & allowed_auth_types))) { error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->connection_data.user, - allowed_types, + allowed_auth_types, t->owner->cred_acquire_payload); if (error == GIT_PASSTHROUGH) { @@ -286,7 +349,8 @@ static int on_headers_complete(http_parser *parser) assert(t->cred); /* Successfully acquired a credential. */ - return t->parse_error = PARSE_ERROR_REPLAY; + t->parse_error = PARSE_ERROR_REPLAY; + return 0; } } } @@ -324,7 +388,8 @@ static int on_headers_complete(http_parser *parser) t->connected = 0; s->redirect_count++; - return t->parse_error = PARSE_ERROR_REPLAY; + t->parse_error = PARSE_ERROR_REPLAY; + return 0; } /* Check for a 200 HTTP status code. */ @@ -382,6 +447,13 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) parser_context *ctx = (parser_context *) parser->data; http_subtransport *t = ctx->t; + /* If our goal is to replay the request (either an auth failure or + * a redirect) then don't bother buffering since we're ignoring the + * content anyway. + */ + if (t->parse_error == PARSE_ERROR_REPLAY) + return 0; + if (ctx->buf_size < len) { giterr_set(GITERR_NET, "Can't fit data in the buffer"); return t->parse_error = PARSE_ERROR_GENERIC; @@ -456,7 +528,7 @@ static int http_connect(http_subtransport *t) if (t->connected && http_should_keep_alive(&t->parser) && - http_body_is_final(&t->parser)) + t->parse_finished) return 0; if (t->socket.socket) @@ -502,10 +574,8 @@ replay: clear_parser_state(t); - if (gen_request(&request, s, 0) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); + if (gen_request(&request, s, 0) < 0) return -1; - } if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { git_buf_free(&request); @@ -537,7 +607,23 @@ replay: } while (!*bytes_read && !t->parse_finished) { - t->parse_buffer.offset = 0; + size_t data_offset; + + /* + * Make the parse_buffer think it's as full of data as + * the buffer, so it won't try to recv more data than + * we can put into it. + * + * data_offset is the actual data offset from which we + * should tell the parser to start reading. + */ + if (buf_size >= t->parse_buffer.len) { + t->parse_buffer.offset = 0; + } else { + t->parse_buffer.offset = t->parse_buffer.len - buf_size; + } + + data_offset = t->parse_buffer.offset; if (gitno_recv(&t->parse_buffer) < 0) return -1; @@ -558,8 +644,8 @@ replay: bytes_parsed = http_parser_execute(&t->parser, &t->settings, - t->parse_buffer.data, - t->parse_buffer.offset); + t->parse_buffer.data + data_offset, + t->parse_buffer.offset - data_offset); t->parser.data = NULL; @@ -577,7 +663,7 @@ replay: if (t->parse_error < 0) return -1; - if (bytes_parsed != t->parse_buffer.offset) { + if (bytes_parsed != t->parse_buffer.offset - data_offset) { giterr_set(GITERR_NET, "HTTP parser error: %s", http_errno_description((enum http_errno)t->parser.http_errno)); @@ -604,10 +690,8 @@ static int http_stream_write_chunked( clear_parser_state(t); - if (gen_request(&request, s, 0) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); + if (gen_request(&request, s, 0) < 0) return -1; - } if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { git_buf_free(&request); @@ -679,10 +763,8 @@ static int http_stream_write_single( clear_parser_state(t); - if (gen_request(&request, s, len) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); + if (gen_request(&request, s, len) < 0) return -1; - } if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) goto on_error; @@ -849,6 +931,8 @@ static int http_action( static int http_close(git_smart_subtransport *subtransport) { http_subtransport *t = (http_subtransport *) subtransport; + git_http_auth_context *context; + size_t i; clear_parser_state(t); @@ -867,6 +951,13 @@ static int http_close(git_smart_subtransport *subtransport) t->url_cred = NULL; } + git_vector_foreach(&t->auth_contexts, i, context) { + if (context->free) + context->free(context); + } + + git_vector_clear(&t->auth_contexts); + gitno_connection_data_free_ptrs(&t->connection_data); return 0; @@ -878,6 +969,7 @@ static void http_free(git_smart_subtransport *subtransport) http_close(subtransport); + git_vector_free(&t->auth_contexts); git__free(t); } diff --git a/src/transports/smart.h b/src/transports/smart.h index f1fc29520..b29faae44 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -9,6 +9,7 @@ #include "netops.h" #include "buffer.h" #include "push.h" +#include "git2/sys/transport.h" #define GIT_SIDE_BAND_DATA 1 #define GIT_SIDE_BAND_PROGRESS 2 diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index a52aacc60..82891165f 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -592,7 +592,9 @@ int git_smart__download_pack( } } else if (pkt->type == GIT_PKT_DATA) { git_pkt_data *p = (git_pkt_data *) pkt; - error = writepack->append(writepack, p->data, p->len, stats); + + if (p->len) + error = writepack->append(writepack, p->data, p->len, stats); } else if (pkt->type == GIT_PKT_FLUSH) { /* A flush indicates the end of the packfile */ git__free(pkt); diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 6a7f67e99..fff81661a 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -5,6 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#ifdef GIT_SSH +#include <libssh2.h> +#endif + #include "git2.h" #include "buffer.h" #include "netops.h" @@ -13,8 +17,6 @@ #ifdef GIT_SSH -#include <libssh2.h> - #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) static const char prefix_ssh[] = "ssh://"; @@ -35,6 +37,9 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; ssh_stream *current_stream; + git_cred *cred; + char *cmd_uploadpack; + char *cmd_receivepack; } ssh_subtransport; static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); @@ -134,11 +139,22 @@ static int ssh_stream_write( size_t len) { ssh_stream *s = (ssh_stream *)stream; + size_t off = 0; + ssize_t ret = 0; if (!s->sent_command && send_command(s) < 0) return -1; - if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) { + do { + ret = libssh2_channel_write(s->channel, buffer + off, len - off); + if (ret < 0) + break; + + off += ret; + + } while (off < len); + + if (ret < 0) { ssh_error(s->session, "SSH could not write data"); return -1; } @@ -552,7 +568,9 @@ static int ssh_uploadpack_ls( const char *url, git_smart_subtransport_stream **stream) { - return _git_ssh_setup_conn(t, url, cmd_uploadpack, stream); + const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; + + return _git_ssh_setup_conn(t, url, cmd, stream); } static int ssh_uploadpack( @@ -576,7 +594,9 @@ static int ssh_receivepack_ls( const char *url, git_smart_subtransport_stream **stream) { - if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0) + const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; + + if (_git_ssh_setup_conn(t, url, cmd, stream) < 0) return -1; return 0; @@ -641,6 +661,8 @@ static void _ssh_free(git_smart_subtransport *subtransport) assert(!t->current_stream); + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); git__free(t); } @@ -720,3 +742,46 @@ int git_smart_subtransport_ssh( return -1; #endif } + +int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) +{ +#ifdef GIT_SSH + git_strarray *paths = (git_strarray *) payload; + git_transport *transport; + transport_smart *smart; + ssh_subtransport *t; + int error; + git_smart_subtransport_definition ssh_definition = { + git_smart_subtransport_ssh, + 0, /* no RPC */ + }; + + if (paths->count != 2) { + giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings"); + return GIT_EINVALIDSPEC; + } + + if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) + return error; + + smart = (transport_smart *) transport; + t = (ssh_subtransport *) smart->wrapped; + + t->cmd_uploadpack = git__strdup(paths->strings[0]); + GITERR_CHECK_ALLOC(t->cmd_uploadpack); + t->cmd_receivepack = git__strdup(paths->strings[1]); + GITERR_CHECK_ALLOC(t->cmd_receivepack); + + *out = transport; + return 0; +#else + GIT_UNUSED(owner); + GIT_UNUSED(payload); + + assert(out); + *out = NULL; + + giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support"); + return -1; +#endif +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index bd9509cd4..1e46dfaee 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -36,6 +36,10 @@ #define CACHED_POST_BODY_BUF_SIZE 4096 #define UUID_LENGTH_CCH 32 +#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH +#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 +#endif + static const char *prefix_http = "http://"; static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; @@ -97,7 +101,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred) if (git_buf_oom(&raw) || git_buf_puts(&buf, "Authorization: Basic ") < 0 || - git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0) + git_buf_encode_base64(&buf, git_buf_cstr(&raw), raw.size) < 0) goto on_error; if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) { @@ -745,9 +749,9 @@ replay: /* Verify that we got the correct content-type back */ if (post_verb == s->verb) - snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); else - snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters"); diff --git a/src/tree.c b/src/tree.c index b64efe460..28190d6da 100644 --- a/src/tree.c +++ b/src/tree.c @@ -17,6 +17,8 @@ #define DEFAULT_TREE_SIZE 16 #define MAX_FILEMODE_BYTES 6 +GIT__USE_STRMAP; + static bool valid_filemode(const int filemode) { return (filemode == GIT_FILEMODE_TREE @@ -365,7 +367,8 @@ size_t git_tree_entrycount(const git_tree *tree) unsigned int git_treebuilder_entrycount(git_treebuilder *bld) { assert(bld); - return (unsigned int)bld->entrycount; + + return git_strmap_num_entries(bld->map); } static int tree_error(const char *str, const char *path) @@ -450,6 +453,7 @@ static int append_entry( git_filemode_t filemode) { git_tree_entry *entry; + int error = 0; if (!valid_entry_name(filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); @@ -460,12 +464,13 @@ static int append_entry( git_oid_cpy(&entry->oid, id); entry->attr = (uint16_t)filemode; - if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { - git__free(entry); + git_strmap_insert(bld->map, entry->filename, entry, error); + if (error < 0) { + git_tree_entry_free(entry); + giterr_set(GITERR_TREE, "failed to append entry %s to the tree builder", filename); return -1; } - bld->entrycount++; return 0; } @@ -610,18 +615,17 @@ int git_tree__write_index( int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld; - size_t i, source_entries = DEFAULT_TREE_SIZE; + size_t i; assert(builder_p); bld = git__calloc(1, sizeof(git_treebuilder)); GITERR_CHECK_ALLOC(bld); - if (source != NULL) - source_entries = source->entries.length; - - if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0) - goto on_error; + if (git_strmap_alloc(&bld->map) < 0) { + git__free(bld); + return -1; + } if (source != NULL) { git_tree_entry *entry_src; @@ -651,7 +655,8 @@ int git_treebuilder_insert( git_filemode_t filemode) { git_tree_entry *entry; - size_t pos; + int error; + git_strmap_iter pos; assert(bld && id && filename); @@ -661,22 +666,20 @@ int git_treebuilder_insert( if (!valid_entry_name(filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); - if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) { - entry = git_vector_get(&bld->entries, pos); - if (entry->removed) { - entry->removed = 0; - bld->entrycount++; - } + pos = git_strmap_lookup_index(bld->map, filename); + if (git_strmap_valid_index(bld->map, pos)) { + entry = git_strmap_value_at(bld->map, pos); } else { entry = alloc_entry(filename); GITERR_CHECK_ALLOC(entry); - if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { - git__free(entry); + git_strmap_insert(bld->map, entry->filename, entry, error); + + if (error < 0) { + git_tree_entry_free(entry); + giterr_set(GITERR_TREE, "failed to insert %s", filename); return -1; } - - bld->entrycount++; } git_oid_cpy(&entry->oid, id); @@ -690,17 +693,14 @@ int git_treebuilder_insert( static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { - size_t idx; - git_tree_entry *entry; + git_tree_entry *entry = NULL; + git_strmap_iter pos; assert(bld && filename); - if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0) - return NULL; - - entry = git_vector_get(&bld->entries, idx); - if (entry->removed) - return NULL; + pos = git_strmap_lookup_index(bld->map, filename); + if (git_strmap_valid_index(bld->map, pos)) + entry = git_strmap_value_at(bld->map, pos); return entry; } @@ -712,35 +712,44 @@ const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *file int git_treebuilder_remove(git_treebuilder *bld, const char *filename) { - git_tree_entry *remove_ptr = treebuilder_get(bld, filename); + git_tree_entry *entry = treebuilder_get(bld, filename); - if (remove_ptr == NULL || remove_ptr->removed) + if (entry == NULL) return tree_error("Failed to remove entry. File isn't in the tree", filename); - remove_ptr->removed = 1; - bld->entrycount--; + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { int error = 0; - size_t i; + size_t i, entrycount; git_buf tree = GIT_BUF_INIT; git_odb *odb; + git_tree_entry *entry; + git_vector entries; assert(bld); - git_vector_sort(&bld->entries); + entrycount = git_strmap_num_entries(bld->map); + if (git_vector_init(&entries, entrycount, entry_sort_cmp) < 0) + return -1; - /* Grow the buffer beforehand to an estimated size */ - error = git_buf_grow(&tree, bld->entries.length * 72); + git_strmap_foreach_value(bld->map, entry, { + if (git_vector_insert(&entries, entry) < 0) + return -1; + }); - for (i = 0; i < bld->entries.length && !error; ++i) { - git_tree_entry *entry = git_vector_get(&bld->entries, i); + git_vector_sort(&entries); + + /* Grow the buffer beforehand to an estimated size */ + error = git_buf_grow(&tree, entrycount * 72); - if (entry->removed) - continue; + for (i = 0; i < entries.length && !error; ++i) { + git_tree_entry *entry = git_vector_get(&entries, i); git_buf_printf(&tree, "%o ", entry->attr); git_buf_put(&tree, entry->filename, entry->filename_len + 1); @@ -750,6 +759,8 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b error = -1; } + git_vector_free(&entries); + if (!error && !(error = git_repository_odb__weakptr(&odb, repo))) error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE); @@ -763,31 +774,27 @@ void git_treebuilder_filter( git_treebuilder_filter_cb filter, void *payload) { - size_t i; + const char *filename; git_tree_entry *entry; assert(bld && filter); - git_vector_foreach(&bld->entries, i, entry) { - if (!entry->removed && filter(entry, payload)) { - entry->removed = 1; - bld->entrycount--; - } - } + git_strmap_foreach(bld->map, filename, entry, { + if (filter(entry, payload)) { + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + } + }); } void git_treebuilder_clear(git_treebuilder *bld) { - size_t i; git_tree_entry *e; assert(bld); - git_vector_foreach(&bld->entries, i, e) - git_tree_entry_free(e); - - git_vector_clear(&bld->entries); - bld->entrycount = 0; + git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); + git_strmap_clear(bld->map); } void git_treebuilder_free(git_treebuilder *bld) @@ -796,7 +803,7 @@ void git_treebuilder_free(git_treebuilder *bld) return; git_treebuilder_clear(bld); - git_vector_free(&bld->entries); + git_strmap_free(bld->map); git__free(bld); } diff --git a/src/tree.h b/src/tree.h index f07039a07..5d27eb7c9 100644 --- a/src/tree.h +++ b/src/tree.h @@ -11,9 +11,9 @@ #include "repository.h" #include "odb.h" #include "vector.h" +#include "strmap.h" struct git_tree_entry { - uint16_t removed; uint16_t attr; git_oid oid; size_t filename_len; @@ -26,8 +26,7 @@ struct git_tree { }; struct git_treebuilder { - git_vector entries; - size_t entrycount; /* vector may contain "removed" entries */ + git_strmap *map; }; GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) diff --git a/src/unix/posix.h b/src/unix/posix.h index 1e41bcf18..e4f3ac67a 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -4,33 +4,47 @@ * 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_posix__w32_h__ -#define INCLUDE_posix__w32_h__ +#ifndef INCLUDE_posix__unix_h__ +#define INCLUDE_posix__unix_h__ #include <stdio.h> #include <sys/param.h> +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_fstat(f,b) fstat(f, b) #define p_lstat(p,b) lstat(p,b) +#define p_stat(p,b) stat(p, b) + #define p_readlink(a, b, c) readlink(a, b, c) #define p_symlink(o,n) symlink(o, n) #define p_link(o,n) link(o, n) #define p_unlink(p) unlink(p) #define p_mkdir(p,m) mkdir(p, m) #define p_fsync(fd) fsync(fd) +extern char *p_realpath(const char *, char *); -/* The OpenBSD realpath function behaves differently */ -#if !defined(__OpenBSD__) -# define p_realpath(p, po) realpath(p, po) -#else -char *p_realpath(const char *, char *); -#endif +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +#define p_inet_pton(a, b, c) inet_pton(a, b, c) +#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) +#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) #define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) #define p_mkstemp(p) mkstemp(p) -#define p_inet_pton(a, b, c) inet_pton(a, b, c) +#define p_chdir(p) chdir(p) +#define p_chmod(p,m) chmod(p, m) +#define p_rmdir(p) rmdir(p) +#define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) /* see win32/posix.h for explanation about why this exists */ #define p_lstat_posixly(p,b) lstat(p,b) +#define p_localtime_r(c, r) localtime_r(c, r) +#define p_gmtime_r(c, r) gmtime_r(c, r) + #endif diff --git a/src/unix/realpath.c b/src/unix/realpath.c index 15601bd22..2e49150c2 100644 --- a/src/unix/realpath.c +++ b/src/unix/realpath.c @@ -6,7 +6,7 @@ */ #include <git2/common.h> -#ifdef __OpenBSD__ +#ifndef GIT_WIN32 #include <limits.h> #include <stdlib.h> @@ -16,15 +16,16 @@ char *p_realpath(const char *pathname, char *resolved) { char *ret; - if ((ret = realpath(pathname, resolved)) == NULL) return NULL; - /* Figure out if the file exists */ - if (!access(ret, F_OK)) - return ret; - - return NULL; +#ifdef __OpenBSD__ + /* The OpenBSD realpath function behaves differently, + * figure out if the file exists */ + if (access(ret, F_OK) < 0) + ret = NULL; +#endif + return ret; } #endif diff --git a/src/util.c b/src/util.c index f9d37e4f4..5c305950f 100644 --- a/src/util.c +++ b/src/util.c @@ -613,7 +613,8 @@ void git__qsort_r( defined(__OpenBSD__) || defined(__NetBSD__) || \ defined(__gnu_hurd__) || defined(__ANDROID_API__) || \ defined(__sun) || defined(__CYGWIN__) || \ - (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) + (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) || \ + (defined(_MSC_VER) && _MSC_VER < 1500) git__insertsort_r(els, nel, elsize, NULL, cmp, payload); #elif defined(GIT_WIN32) git__qsort_r_glue glue = { cmp, payload }; diff --git a/src/util.h b/src/util.h index ca676c039..6e57ad8c3 100644 --- a/src/util.h +++ b/src/util.h @@ -419,7 +419,7 @@ GIT_INLINE(double) git__timer(void) scaling_factor = (double)info.numer / (double)info.denom; } - return (double)time * scaling_factor / 1.0E-9; + return (double)time * scaling_factor / 1.0E9; } #else @@ -431,13 +431,13 @@ GIT_INLINE(double) git__timer(void) struct timespec tp; if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { - return (double) tp.tv_sec + (double) tp.tv_nsec / 1E-9; + return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; } else { /* Fall back to using gettimeofday */ struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); - return (double)tv.tv_sec + (double)tv.tv_usec / 1E-6; + return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; } } diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 059e39cbc..83ee28765 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -9,18 +9,11 @@ #if defined(__MINGW32__) -/* use a 64-bit file offset type */ -# undef lseek -# define lseek _lseeki64 -# undef stat -# define stat _stati64 -# undef fstat -# define fstat _fstati64 - -/* stat: file mode type testing macros */ -# define _S_IFLNK 0120000 -# define S_IFLNK _S_IFLNK -# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#if _WIN32_WINNT >= 0x0601 +#define stat __stat64 +#else +#define stat _stati64 +#endif #endif diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h index 50865ed17..4789d63df 100644 --- a/src/win32/msvc-compat.h +++ b/src/win32/msvc-compat.h @@ -9,41 +9,12 @@ #if defined(_MSC_VER) -/* access() mode parameter #defines */ -# define F_OK 0 /* existence check */ -# define W_OK 2 /* write mode check */ -# define R_OK 4 /* read mode check */ +/* 64-bit stat information, regardless of USE_32BIT_TIME_T define */ +#define stat __stat64 -# define lseek _lseeki64 -# define stat _stat64 -# define fstat _fstat64 - -/* stat: file mode type testing macros */ -# define _S_IFLNK 0120000 -# define S_IFLNK _S_IFLNK -# define S_IXUSR 00100 - -# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) -# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) - -# define mode_t unsigned short - -/* case-insensitive string comparison */ -# define strcasecmp _stricmp -# define strncasecmp _strnicmp - -/* MSVC doesn't define ssize_t at all */ +typedef unsigned short mode_t; typedef SSIZE_T ssize_t; -/* define snprintf using variadic macro support if available */ -#if _MSC_VER >= 1400 -# define snprintf(BUF, SZ, FMT, ...) _snprintf_s(BUF, SZ, _TRUNCATE, FMT, __VA_ARGS__) -#else -# define snprintf _snprintf -#endif - #endif #define GIT_STDLIB_CALL __cdecl diff --git a/src/win32/posix.h b/src/win32/posix.h index 2cbea1807..e055a77d0 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -12,49 +12,35 @@ #include "utf-conv.h" #include "dir.h" -/* define some standard errnos that the runtime may be missing. for example, - * mingw lacks EAFNOSUPPORT. */ +typedef SOCKET GIT_SOCKET; -#ifndef EAFNOSUPPORT -# define EAFNOSUPPORT (INT_MAX-1) -#endif - -#ifdef _MSC_VER -# define p_ftruncate(fd, sz) _chsize_s(fd, sz) -#else /* MinGW */ -# define p_ftruncate(fd, sz) _chsize(fd, sz) -#endif - -GIT_INLINE(int) p_link(const char *old, const char *new) -{ - GIT_UNUSED(old); - GIT_UNUSED(new); - errno = ENOSYS; - return -1; -} - -extern int p_mkdir(const char *path, mode_t mode); -extern int p_unlink(const char *path); +#define p_lseek(f,n,w) _lseeki64(f, n, w) +#define p_fstat(f,b) _fstat64(f, b) extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_stat(const char* path, struct stat* buf); + extern int p_readlink(const char *path, char *buf, size_t bufsiz); extern int p_symlink(const char *old, const char *new); +extern int p_link(const char *old, const char *new); +extern int p_unlink(const char *path); +extern int p_mkdir(const char *path, mode_t mode); +extern int p_fsync(int fd); extern char *p_realpath(const char *orig_path, char *buffer); + +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); +extern int p_inet_pton(int af, const char* src, void* dst); + +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); extern int p_mkstemp(char *tmp_path); -extern int p_stat(const char* path, struct stat* buf); extern int p_chdir(const char* path); extern int p_chmod(const char* path, mode_t mode); extern int p_rmdir(const char* path); extern int p_access(const char* path, mode_t mode); -extern int p_fsync(int fd); -extern int p_open(const char *path, int flags, ...); -extern int p_creat(const char *path, mode_t mode); -extern int p_getcwd(char *buffer_out, size_t size); -extern int p_rename(const char *from, const char *to); -extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); -extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); -extern int p_inet_pton(int af, const char* src, void* dst); +extern int p_ftruncate(int fd, long size); /* p_lstat is almost but not quite POSIX correct. Specifically, the use of * ENOTDIR is wrong, in that it does not mean precisely that a non-directory @@ -64,4 +50,7 @@ extern int p_inet_pton(int af, const char* src, void* dst); */ extern int p_lstat_posixly(const char *filename, struct stat *buf); +extern struct tm * p_localtime_r (const time_t *timer, struct tm *result); +extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result); + #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 34938431a..0023f95ff 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -19,6 +19,10 @@ # define FILE_NAME_NORMALIZED 0 #endif +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif + /* Options which we always provide to _wopen. * * _O_BINARY - Raw access; no translation of CR or LF characters @@ -47,6 +51,15 @@ static int utf8_to_16_with_errno(git_win32_path dest, const char *src) return len; } +int p_ftruncate(int fd, long size) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1500 + return _chsize_s(fd, size); +#else + return _chsize(fd, size); +#endif +} + int p_mkdir(const char *path, mode_t mode) { git_win32_path buf; @@ -59,6 +72,14 @@ int p_mkdir(const char *path, mode_t mode) return _wmkdir(buf); } +int p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + int p_unlink(const char *path) { git_win32_path buf; @@ -543,11 +564,19 @@ char *p_realpath(const char *orig_path, char *buffer) int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) { -#ifdef _MSC_VER +#if defined(_MSC_VER) int len; - if (count == 0 || - (len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0) + if (count == 0) + return _vscprintf(format, argptr); + + #if _MSC_VER >= 1500 + len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); + #else + len = _vsnprintf(buffer, count, format, argptr); + #endif + + if (len < 0) return _vscprintf(format, argptr); return len; @@ -570,7 +599,7 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...) int p_mkstemp(char *tmp_path) { -#if defined(_MSC_VER) +#if defined(_MSC_VER) && _MSC_VER >= 1500 if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0) return -1; #else @@ -591,6 +620,31 @@ int p_access(const char* path, mode_t mode) return _waccess(buf, mode); } +static int ensure_writable(wchar_t *fpath) +{ + DWORD attrs; + + attrs = GetFileAttributesW(fpath); + if (attrs == INVALID_FILE_ATTRIBUTES) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return 0; + + giterr_set(GITERR_OS, "failed to get attributes"); + return -1; + } + + if (!(attrs & FILE_ATTRIBUTE_READONLY)) + return 0; + + attrs &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(fpath, attrs)) { + giterr_set(GITERR_OS, "failed to set attributes"); + return -1; + } + + return 0; +} + int p_rename(const char *from, const char *to) { git_win32_path wfrom; @@ -602,12 +656,13 @@ int p_rename(const char *from, const char *to) if (utf8_to_16_with_errno(wfrom, from) < 0 || utf8_to_16_with_errno(wto, to) < 0) return -1; - + /* wait up to 50ms if file is locked by another thread or process */ rename_tries = 0; rename_succeeded = 0; while (rename_tries < 10) { - if (MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) { + if (ensure_writable(wto) == 0 && + MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) { rename_succeeded = 1; break; } |
