diff options
Diffstat (limited to 'src')
49 files changed, 1239 insertions, 539 deletions
diff --git a/src/attr_file.c b/src/attr_file.c index 0bb761d04..e30ea5e0c 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -395,9 +395,13 @@ bool git_attr_fnmatch__match( if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { bool samename; - /* for attribute checks or root ignore checks, fail match */ + /* + * for attribute checks or checks at the root of this match's + * containing_dir (or root of the repository if no containing_dir), + * do not match. + */ if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || - path->basename == path->path) + path->basename == relpath) return false; flags |= FNM_LEADING_DIR; diff --git a/src/attrcache.c b/src/attrcache.c index 4df14ee2c..54161894b 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -290,14 +290,16 @@ static int attr_cache__lookup_path( const char *cfgval = entry->value; /* expand leading ~/ as needed */ - if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' && - !git_sysdir_find_global_file(&buf, &cfgval[2])) - *out = git_buf_detach(&buf); - else if (cfgval) + if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') { + if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2]))) + *out = git_buf_detach(&buf); + } else if (cfgval) { *out = git__strdup(cfgval); + } } - else if (!git_sysdir_find_xdg_file(&buf, fallback)) + else if (!git_sysdir_find_xdg_file(&buf, fallback)) { *out = git_buf_detach(&buf); + } git_config_entry_free(entry); git_buf_free(&buf); diff --git a/src/branch.c b/src/branch.c index 7d5e9cb7f..fe4955ad6 100644 --- a/src/branch.c +++ b/src/branch.c @@ -127,62 +127,31 @@ int git_branch_create_from_annotated( repository, branch_name, commit->commit, commit->description, force); } -int git_branch_is_checked_out( - const git_reference *branch) +static int branch_equals(git_repository *repo, const char *path, void *payload) { - git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; - git_strarray worktrees; - git_reference *ref = NULL; - git_repository *repo; - const char *worktree; - int found = false; - size_t i; - - assert(branch && git_reference_is_branch(branch)); - - repo = git_reference_owner(branch); + git_reference *branch = (git_reference *) payload; + git_reference *head = NULL; + int equal = 0; - if (git_worktree_list(&worktrees, repo) < 0) - return -1; - - for (i = 0; i < worktrees.count; i++) { - worktree = worktrees.strings[i]; - - if (git_repository_head_for_worktree(&ref, repo, worktree) < 0) - continue; - - if (git__strcmp(ref->name, branch->name) == 0) { - found = true; - git_reference_free(ref); - break; - } - - git_reference_free(ref); - } - git_strarray_free(&worktrees); - - if (found) - return found; + if (git_reference__read_head(&head, repo, path) < 0 || + git_reference_type(head) != GIT_REF_SYMBOLIC) + goto done; - /* Check HEAD of parent */ - if (git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE) < 0) - goto out; - if (git_futils_readbuffer(&buf, path.ptr) < 0) - goto out; - if (git__prefixcmp(buf.ptr, "ref: ") == 0) - git_buf_consume(&buf, buf.ptr + strlen("ref: ")); - git_buf_rtrim(&buf); + equal = !git__strcmp(head->target.symbolic, branch->name); - found = git__strcmp(buf.ptr, branch->name) == 0; +done: + git_reference_free(head); + return equal; +} -out: - git_buf_free(&buf); - git_buf_free(&path); +int git_branch_is_checked_out(const git_reference *branch) +{ + assert(branch && git_reference_is_branch(branch)); - return found; + return git_repository_foreach_head(git_reference_owner(branch), + branch_equals, (void *) branch) == 1; } - int git_branch_delete(git_reference *branch) { int is_head; diff --git a/src/buffer.c b/src/buffer.c index fdb732d9e..6dfcbfbe6 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -18,18 +18,19 @@ char git_buf__initbuf[1]; char git_buf__oom[1]; #define ENSURE_SIZE(b, d) \ - if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\ + if ((d) > (b)->asize && git_buf_grow((b), (d)) < 0)\ return -1; -void git_buf_init(git_buf *buf, size_t initial_size) +int git_buf_init(git_buf *buf, size_t initial_size) { buf->asize = 0; buf->size = 0; buf->ptr = git_buf__initbuf; - if (initial_size) - git_buf_grow(buf, initial_size); + ENSURE_SIZE(buf, initial_size); + + return 0; } int git_buf_try_grow( @@ -577,7 +578,7 @@ char *git_buf_detach(git_buf *buf) return data; } -void git_buf_attach(git_buf *buf, char *ptr, size_t asize) +int git_buf_attach(git_buf *buf, char *ptr, size_t asize) { git_buf_free(buf); @@ -588,9 +589,10 @@ void git_buf_attach(git_buf *buf, char *ptr, size_t asize) buf->asize = (asize < buf->size) ? buf->size + 1 : asize; else /* pass 0 to fall back on strlen + 1 */ buf->asize = buf->size + 1; - } else { - git_buf_grow(buf, asize); } + + ENSURE_SIZE(buf, asize); + return 0; } void git_buf_attach_notowned(git_buf *buf, const char *ptr, size_t size) @@ -724,9 +726,7 @@ int git_buf_join( GITERR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - if (git_buf_grow(buf, alloc_len) < 0) - return -1; - assert(buf->ptr); + ENSURE_SIZE(buf, alloc_len); /* fix up internal pointers */ if (offset_a >= 0) @@ -780,8 +780,7 @@ int git_buf_join3( GITERR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); GITERR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); GITERR_CHECK_ALLOC_ADD(&len_total, len_total, 1); - if (git_buf_grow(buf, len_total) < 0) - return -1; + ENSURE_SIZE(buf, len_total); tgt = buf->ptr; diff --git a/src/buffer.h b/src/buffer.h index a76b2d771..b0aece488 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -34,7 +34,7 @@ GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf) * For the cases where GIT_BUF_INIT cannot be used to do static * initialization. */ -extern void git_buf_init(git_buf *buf, size_t initial_size); +extern int git_buf_init(git_buf *buf, size_t initial_size); /** * Resize the buffer allocation to make more space. @@ -73,7 +73,7 @@ extern void git_buf_sanitize(git_buf *buf); extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b); extern char *git_buf_detach(git_buf *buf); -extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize); +extern int git_buf_attach(git_buf *buf, char *ptr, size_t asize); /* Populates a `git_buf` where the contents are not "owned" by the * buffer, and calls to `git_buf_free` will not free the given buf. diff --git a/src/checkout.c b/src/checkout.c index 9d1eed59f..25018d291 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -370,10 +370,8 @@ static int checkout_action_wd_only( */ const git_index_entry *e = git_index_get_byindex(data->index, pos); - if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) { - notify = GIT_CHECKOUT_NOTIFY_DIRTY; - remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); - } + if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) + return git_iterator_advance_into(wditem, workdir); } } diff --git a/src/config.c b/src/config.c index 0d73ad2cc..169a62880 100644 --- a/src/config.c +++ b/src/config.c @@ -576,22 +576,50 @@ int git_config_foreach_match( * Setters **************/ -static int config_error_nofiles(const char *name) +typedef enum { + BACKEND_USE_SET, + BACKEND_USE_DELETE +} backend_use; + +static const char *uses[] = { + "set", + "delete" +}; + +static int get_backend_for_use(git_config_backend **out, + git_config *cfg, const char *name, backend_use use) { + size_t i; + file_internal *f; + + *out = NULL; + + if (git_vector_length(&cfg->files) == 0) { + giterr_set(GITERR_CONFIG, + "cannot %s value for '%s' when no config files exist", + uses[use], name); + return GIT_ENOTFOUND; + } + + git_vector_foreach(&cfg->files, i, f) { + if (!f->file->readonly) { + *out = f->file; + return 0; + } + } + giterr_set(GITERR_CONFIG, - "cannot set value for '%s' when no config files exist", name); + "cannot %s value for '%s' when all config files are readonly", + uses[use], name); return GIT_ENOTFOUND; } int git_config_delete_entry(git_config *cfg, const char *name) { git_config_backend *file; - file_internal *internal; - internal = git_vector_get(&cfg->files, 0); - if (!internal || !internal->file) - return config_error_nofiles(name); - file = internal->file; + if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; return file->del(file, name); } @@ -617,17 +645,14 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) { int error; git_config_backend *file; - file_internal *internal; if (!value) { giterr_set(GITERR_CONFIG, "the value to set cannot be NULL"); return -1; } - internal = git_vector_get(&cfg->files, 0); - if (!internal || !internal->file) - return config_error_nofiles(name); - file = internal->file; + if (get_backend_for_use(&file, cfg, name, BACKEND_USE_SET) < 0) + return GIT_ENOTFOUND; error = file->set(file, name, value); @@ -1032,12 +1057,9 @@ on_error: int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { git_config_backend *file; - file_internal *internal; - internal = git_vector_get(&cfg->files, 0); - if (!internal || !internal->file) - return config_error_nofiles(name); - file = internal->file; + if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; return file->set_multivar(file, name, regexp, value); } @@ -1045,12 +1067,9 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) { git_config_backend *file; - file_internal *internal; - internal = git_vector_get(&cfg->files, 0); - if (!internal || !internal->file) - return config_error_nofiles(name); - file = internal->file; + if (get_backend_for_use(&file, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; return file->del_multivar(file, name, regexp); } @@ -1339,9 +1358,6 @@ fail_parse: int git_config_parse_path(git_buf *out, const char *value) { - int error = 0; - const git_buf *home; - assert(out && value); git_buf_sanitize(out); @@ -1352,16 +1368,7 @@ int git_config_parse_path(git_buf *out, const char *value) return -1; } - if ((error = git_sysdir_get(&home, GIT_SYSDIR_GLOBAL)) < 0) - return error; - - git_buf_sets(out, home->ptr); - git_buf_puts(out, value + 1); - - if (git_buf_oom(out)) - return -1; - - return 0; + return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL); } return git_buf_sets(out, value); diff --git a/src/config_file.c b/src/config_file.c index 7df43c85f..e15d57bbb 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1256,7 +1256,7 @@ static int included_path(git_buf *out, const char *dir, const char *path) { /* From the user's home */ if (path[0] == '~' && path[1] == '/') - return git_sysdir_find_global_file(out, &path[1]); + return git_sysdir_expand_global_file(out, &path[1]); return git_path_join_unrooted(out, path, dir, NULL); } @@ -1267,7 +1267,7 @@ static const char *escaped = "\n\t\b\"\\"; /* Escape the values to write them to the file */ static char *escape_value(const char *ptr) { - git_buf buf = GIT_BUF_INIT; + git_buf buf; size_t len; const char *esc; @@ -1277,7 +1277,8 @@ static char *escape_value(const char *ptr) if (!len) return git__calloc(1, sizeof(char)); - git_buf_grow(&buf, len); + if (git_buf_init(&buf, len) < 0) + return NULL; while (*ptr != '\0') { if ((esc = strchr(escaped, *ptr)) != NULL) { diff --git a/src/config_file.h b/src/config_file.h index 1c52892c3..654e6cacf 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -7,6 +7,7 @@ #ifndef INCLUDE_config_file_h__ #define INCLUDE_config_file_h__ +#include "git2/sys/config.h" #include "git2/config.h" GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level) diff --git a/src/diff_parse.c b/src/diff_parse.c index 24a8a4af6..5e3a7a177 100644 --- a/src/diff_parse.c +++ b/src/diff_parse.c @@ -45,7 +45,7 @@ static git_diff_parsed *diff_parsed_alloc(void) diff->base.free_fn = diff_parsed_free; if (git_diff_init_options(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { - git__free(&diff); + git__free(diff); return NULL; } diff --git a/src/fileops.c b/src/fileops.c index a0a2795c6..2f3f58d4f 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -770,6 +770,9 @@ static int futils__rmdir_empty_parent(void *opaque, const char *path) if (en == ENOENT || en == ENOTDIR) { /* do nothing */ + } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && + en == EBUSY) { + error = git_path_set_error(errno, path, "rmdir"); } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { error = GIT_ITEROVER; } else { @@ -1155,9 +1158,13 @@ int git_futils_fsync_dir(const char *path) int git_futils_fsync_parent(const char *path) { - char *parent = git_path_dirname(path); - int error = git_futils_fsync_dir(parent); + char *parent; + int error; + + if ((parent = git_path_dirname(path)) == NULL) + return -1; + error = git_futils_fsync_dir(parent); git__free(parent); return error; } diff --git a/src/filter.c b/src/filter.c index e74cc1053..361e08529 100644 --- a/src/filter.c +++ b/src/filter.c @@ -895,7 +895,7 @@ static int stream_list_init( git_array_size(filters->filters) - 1 - i : i; git_filter_entry *fe = git_array_get(filters->filters, filter_idx); git_writestream *filter_stream; - + assert(fe->filter->stream || fe->filter->apply); /* If necessary, create a stream that proxies the traditional @@ -1022,3 +1022,9 @@ int git_filter_list_stream_blob( return git_filter_list_stream_data(filters, &in, target); } + +int git_filter_init(git_filter *filter, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); + return 0; +} diff --git a/src/global.c b/src/global.c index e2ad8fe71..afa57e1d6 100644 --- a/src/global.c +++ b/src/global.c @@ -22,7 +22,7 @@ git_mutex git__mwindow_mutex; -#define MAX_SHUTDOWN_CB 8 +#define MAX_SHUTDOWN_CB 9 static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; static git_atomic git__n_shutdown_callbacks; diff --git a/src/hash/sha1dc/sha1.c b/src/hash/sha1dc/sha1.c index 8d12b832b..facea1bb5 100644 --- a/src/hash/sha1dc/sha1.c +++ b/src/hash/sha1dc/sha1.c @@ -5,31 +5,75 @@ * https://opensource.org/licenses/MIT ***/ +#ifndef SHA1DC_NO_STANDARD_INCLUDES #include <string.h> #include <memory.h> #include <stdio.h> #include <stdlib.h> +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif #include "sha1.h" #include "ubc_check.h" -/* +/* Because Little-Endian architectures are most common, - we only set BIGENDIAN if one of these conditions is met. + we only set SHA1DC_BIGENDIAN if one of these conditions is met. Note that all MSFT platforms are little endian, so none of these will be defined under the MSC compiler. If you are compiling on a big endian platform and your compiler does not define one of these, you will have to add whatever macros your tool chain defines to indicate Big-Endianness. */ -#if (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \ - (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) || \ - defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ - defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) +#ifdef SHA1DC_BIGENDIAN +#undef SHA1DC_BIGENDIAN +#endif + +#if (defined(_BYTE_ORDER) || defined(__BYTE_ORDER) || defined(__BYTE_ORDER__)) + +#if ((defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN)) || \ + (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) ) +#define SHA1DC_BIGENDIAN +#endif + +#else + +#if (defined(_BIG_ENDIAN) || defined(__BIG_ENDIAN) || defined(__BIG_ENDIAN__) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ + defined(__sparc)) +#define SHA1DC_BIGENDIAN +#endif + +#endif + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#if (defined SHA1DC_FORCE_UNALIGNED_ACCESS || \ + defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) + +#define SHA1DC_ALLOW_UNALIGNED_ACCESS -#define BIGENDIAN (1) +#endif /*UNALIGNMENT DETECTION*/ -#endif /*ENDIANNESS SELECTION*/ #define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) #define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) @@ -39,11 +83,11 @@ #define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) -#if defined(BIGENDIAN) +#ifdef SHA1DC_BIGENDIAN #define sha1_load(m, t, temp) { temp = m[t]; } #else #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } -#endif /*define(BIGENDIAN)*/ +#endif #define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x @@ -872,6 +916,11 @@ static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], co ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) /* Complier complains about the checks in the above macro being constant. */ +#endif + #ifdef DOSTORESTATE0 SHA1_RECOMPRESS(0) #endif @@ -1192,6 +1241,10 @@ SHA1_RECOMPRESS(78) SHA1_RECOMPRESS(79) #endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) { switch (step) @@ -1609,7 +1662,7 @@ static void sha1_process(SHA1_CTX* ctx, const uint32_t block[16]) unsigned i, j; uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; uint32_t ihvtmp[5]; - + ctx->ihv1[0] = ctx->ihv[0]; ctx->ihv1[1] = ctx->ihv[1]; ctx->ihv1[2] = ctx->ihv[2]; @@ -1665,7 +1718,7 @@ void SHA1DCInit(SHA1_CTX* ctx) ctx->ihv[3] = 0x10325476; ctx->ihv[4] = 0xC3D2E1F0; ctx->found_collision = 0; - ctx->safe_hash = 1; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; ctx->ubc_check = 1; ctx->detect_coll = 1; ctx->reduced_round_coll = 0; @@ -1713,6 +1766,7 @@ void SHA1DCSetCallback(SHA1_CTX* ctx, collision_block_callback callback) void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len) { unsigned left, fill; + if (len == 0) return; @@ -1731,7 +1785,13 @@ void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len) while (len >= 64) { ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) sha1_process(ctx, (uint32_t*)(buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ buf += 64; len -= 64; } @@ -1790,3 +1850,7 @@ int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) output[19] = (unsigned char)(ctx->ihv[4]); return ctx->found_collision; } + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/src/hash/sha1dc/sha1.h b/src/hash/sha1dc/sha1.h index e867724c0..1e4e94be5 100644 --- a/src/hash/sha1dc/sha1.h +++ b/src/hash/sha1dc/sha1.h @@ -5,42 +5,37 @@ * https://opensource.org/licenses/MIT ***/ +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + #if defined(__cplusplus) extern "C" { #endif +#ifndef SHA1DC_NO_STANDARD_INCLUDES #include <stdint.h> +#endif -/* uses SHA-1 message expansion to expand the first 16 words of W[] to 80 words */ -/* void sha1_message_expansion(uint32_t W[80]); */ - -/* sha-1 compression function; first version takes a message block pre-parsed as 16 32-bit integers, second version takes an already expanded message) */ -/* void sha1_compression(uint32_t ihv[5], const uint32_t m[16]); -void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]); */ - -/* same as sha1_compression_W, but additionally store intermediate states */ +/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ /* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); /* -// function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) -// where 0 <= T < 80 -// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference) -// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block -// the function will return: -// ihvin: the reconstructed input chaining value -// ihvout: the reconstructed output chaining value +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. */ typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); -/* table of sha1_recompression_step_0, ... , sha1_recompression_step_79 */ -/* extern sha1_recompression_type sha1_recompression_step[80];*/ - -/* a callback function type that can be set to be called when a collision block has been found: */ +/* A callback function type that can be set to be called when a collision block has been found: */ /* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); -/* the SHA-1 context */ +/* The SHA-1 context. */ typedef struct { uint64_t total; uint32_t ihv[5]; @@ -59,30 +54,34 @@ typedef struct { uint32_t states[80][5]; } SHA1_CTX; -/* initialize SHA-1 context */ +/* Initialize SHA-1 context. */ void SHA1DCInit(SHA1_CTX*); /* -// function to enable safe SHA-1 hashing: -// collision attacks are thwarted by hashing a detected near-collision block 3 times -// think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: -// the best collision attacks against SHA-1 have complexity about 2^60, -// thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would 2^180 -// an attacker would be better off using a generic birthday search of complexity 2^80 -// -// enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected -// but it will result in a different SHA-1 hash for messages where a collision attack was detected -// this will automatically invalidate SHA-1 based digital signature forgeries -// enabled by default + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. + An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, + but it will result in a different SHA-1 hash for messages where a collision attack was detected. + This will automatically invalidate SHA-1 based digital signature forgeries. + Enabled by default. */ void SHA1DCSetSafeHash(SHA1_CTX*, int); -/* function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up) */ -/* enabled by default */ +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). + Enabled by default + */ void SHA1DCSetUseUBC(SHA1_CTX*, int); -/* function to disable or enable the use of Collision Detection */ -/* enabled by default */ +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ void SHA1DCSetUseDetectColl(SHA1_CTX*, int); /* function to disable or enable the detection of reduced-round SHA-1 collisions */ @@ -98,8 +97,14 @@ void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); /* obtain SHA-1 hash from SHA-1 context */ /* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ -int SHA1DCFinal(unsigned char[20], SHA1_CTX*); +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); #if defined(__cplusplus) } #endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/src/hash/sha1dc/ubc_check.c b/src/hash/sha1dc/ubc_check.c index 27d0976da..b3beff2af 100644 --- a/src/hash/sha1dc/ubc_check.c +++ b/src/hash/sha1dc/ubc_check.c @@ -24,7 +24,12 @@ // ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section */ +#ifndef SHA1DC_NO_STANDARD_INCLUDES #include <stdint.h> +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif #include "ubc_check.h" static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; @@ -361,3 +366,7 @@ if (mask) { dvmask[0]=mask; } + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/src/hash/sha1dc/ubc_check.h b/src/hash/sha1dc/ubc_check.h index b349bed92..d7e17dc73 100644 --- a/src/hash/sha1dc/ubc_check.h +++ b/src/hash/sha1dc/ubc_check.h @@ -20,14 +20,16 @@ // thus one needs to do the recompression check for each DV that has its bit set */ -#ifndef UBC_CHECK_H -#define UBC_CHECK_H +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H #if defined(__cplusplus) extern "C" { #endif +#ifndef SHA1DC_NO_STANDARD_INCLUDES #include <stdint.h> +#endif #define DVMASKSIZE 1 typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; @@ -43,4 +45,8 @@ void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); } #endif -#endif /* UBC_CHECK_H */ +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/src/index.c b/src/index.c index 932a5306a..c29e90fb0 100644 --- a/src/index.c +++ b/src/index.c @@ -54,10 +54,6 @@ static int index_apply_to_wd_diff(git_index *index, int action, const git_strarr unsigned int flags, git_index_matched_path_cb cb, void *payload); -#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) -#define short_entry_size(len) entry_size(struct entry_short, len) -#define long_entry_size(len) entry_size(struct entry_long, len) - #define minimal_entry_size (offsetof(struct entry_short, path)) static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ; @@ -2282,12 +2278,29 @@ out_err: return 0; } +static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags) +{ + if (varint_len) { + if (flags & GIT_IDXENTRY_EXTENDED) + return offsetof(struct entry_long, path) + path_len + 1 + varint_len; + else + return offsetof(struct entry_short, path) + path_len + 1 + varint_len; + } else { +#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) + if (flags & GIT_IDXENTRY_EXTENDED) + return entry_size(struct entry_long, path_len); + else + return entry_size(struct entry_short, path_len); +#undef entry_size + } +} + static size_t read_entry( git_index_entry **out, git_index *index, const void *buffer, size_t buffer_size, - const char **last) + const char *last) { size_t path_length, entry_size; const char *path_ptr; @@ -2344,35 +2357,34 @@ static size_t read_entry( path_length = path_end - path_ptr; } - if (entry.flags & GIT_IDXENTRY_EXTENDED) - entry_size = long_entry_size(path_length); - else - entry_size = short_entry_size(path_length); - - if (INDEX_FOOTER_SIZE + entry_size > buffer_size) - return 0; - + entry_size = index_entry_size(path_length, 0, entry.flags); entry.path = (char *)path_ptr; } else { size_t varint_len; - size_t shared = git_decode_varint((const unsigned char *)path_ptr, - &varint_len); - size_t len = strlen(path_ptr + varint_len); - size_t last_len = strlen(*last); - size_t tmp_path_len; + size_t strip_len = git_decode_varint((const unsigned char *)path_ptr, + &varint_len); + size_t last_len = strlen(last); + size_t prefix_len = last_len - strip_len; + size_t suffix_len = strlen(path_ptr + varint_len); + size_t path_len; if (varint_len == 0) return index_error_invalid("incorrect prefix length"); - GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1); - tmp_path = git__malloc(tmp_path_len); + GITERR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); + GITERR_CHECK_ALLOC_ADD(&path_len, path_len, 1); + tmp_path = git__malloc(path_len); GITERR_CHECK_ALLOC(tmp_path); - memcpy(tmp_path, last, last_len); - memcpy(tmp_path + last_len, path_ptr + varint_len, len); - entry_size = long_entry_size(shared + len); + + memcpy(tmp_path, last, prefix_len); + memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1); + entry_size = index_entry_size(suffix_len, varint_len, entry.flags); entry.path = tmp_path; } + if (INDEX_FOOTER_SIZE + entry_size > buffer_size) + return 0; + if (index_entry_dup(out, index, &entry) < 0) { git__free(tmp_path); return 0; @@ -2445,7 +2457,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) unsigned int i; struct index_header header = { 0 }; git_oid checksum_calculated, checksum_expected; - const char **last = NULL; + const char *last = NULL; const char *empty = ""; #define seek_forward(_increase) { \ @@ -2469,7 +2481,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) index->version = header.version; if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = ∅ + last = empty; seek_forward(INDEX_HEADER_SIZE); @@ -2504,6 +2516,9 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) } error = 0; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + seek_forward(entry_size); } @@ -2574,11 +2589,12 @@ static bool is_index_extended(git_index *index) return (extended > 0); } -static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last) +static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last) { void *mem = NULL; struct entry_short *ondisk; size_t path_len, disk_size; + int varint_len = 0; char *path; const char *path_start = entry->path; size_t same_len = 0; @@ -2586,7 +2602,7 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const cha path_len = ((struct entry_internal *)entry)->pathlen; if (last) { - const char *last_c = *last; + const char *last_c = last; while (*path_start == *last_c) { if (!*path_start || !*last_c) @@ -2596,13 +2612,10 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const cha ++same_len; } path_len -= same_len; - *last = entry->path; + varint_len = git_encode_varint(NULL, 0, same_len); } - if (entry->flags & GIT_IDXENTRY_EXTENDED) - disk_size = long_entry_size(path_len); - else - disk_size = short_entry_size(path_len); + disk_size = index_entry_size(path_len, varint_len, entry->flags); if (git_filebuf_reserve(file, &mem, disk_size) < 0) return -1; @@ -2642,16 +2655,34 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const cha ondisk_ext->flags_extended = htons(entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS); path = ondisk_ext->path; - } - else + disk_size -= offsetof(struct entry_long, path); + } else { path = ondisk->path; + disk_size -= offsetof(struct entry_short, path); + } if (last) { - path += git_encode_varint((unsigned char *) path, - disk_size, - path_len - same_len); + varint_len = git_encode_varint((unsigned char *) path, + disk_size, same_len); + assert(varint_len > 0); + path += varint_len; + disk_size -= varint_len; + + /* + * If using path compression, we are not allowed + * to have additional trailing NULs. + */ + assert(disk_size == path_len + 1); + } else { + /* + * If no path compression is used, we do have + * NULs as padding. As such, simply assert that + * we have enough space left to write the path. + */ + assert(disk_size > path_len); } - memcpy(path, path_start, path_len); + + memcpy(path, path_start, path_len + 1); return 0; } @@ -2662,8 +2693,7 @@ static int write_entries(git_index *index, git_filebuf *file) size_t i; git_vector case_sorted, *entries; git_index_entry *entry; - const char **last = NULL; - const char *empty = ""; + const char *last = NULL; /* If index->entries is sorted case-insensitively, then we need * to re-sort it case-sensitively before writing */ @@ -2676,11 +2706,14 @@ static int write_entries(git_index *index, git_filebuf *file) } if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = ∅ + last = ""; - git_vector_foreach(entries, i, entry) + git_vector_foreach(entries, i, entry) { if ((error = write_disk_entry(file, entry, last)) < 0) break; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + } if (index->ignore_case) git_vector_free(&case_sorted); diff --git a/src/indexer.c b/src/indexer.c index ce67240ce..15f6cc2c4 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -125,7 +125,7 @@ int git_indexer_new( git_hash_ctx_init(&idx->hash_ctx); git_hash_ctx_init(&idx->trailer); - if (git_object__synchronous_writing) + if (git_repository__fsync_gitdir) idx->do_fsync = 1; error = git_buf_joinpath(&path, prefix, suff); @@ -937,7 +937,6 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_buf filename = GIT_BUF_INIT; struct entry *entry; git_oid trailer_hash, file_hash; - git_hash_ctx ctx; git_filebuf index_file = {0}; void *packfile_trailer; @@ -946,9 +945,6 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) return -1; } - if (git_hash_ctx_init(&ctx) < 0) - return -1; - /* Test for this before resolve_deltas(), as it plays with idx->off */ if (idx->off + 20 < idx->pack->mwf.size) { giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack"); @@ -992,6 +988,10 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_vector_sort(&idx->objects); + /* Use the trailer hash as the pack file name to ensure + * files with different contents have different names */ + git_oid_cpy(&idx->hash, &trailer_hash); + git_buf_sets(&filename, idx->pack->pack_name); git_buf_shorten(&filename, strlen("pack")); git_buf_puts(&filename, "idx"); @@ -1018,9 +1018,7 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) /* Write out the object names (SHA-1 hashes) */ git_vector_foreach(&idx->objects, i, entry) { git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid)); - git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ); } - git_hash_final(&idx->hash, &ctx); /* Write out the CRC32 values */ git_vector_foreach(&idx->objects, i, entry) { @@ -1106,14 +1104,12 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) idx->pack_committed = 1; git_buf_free(&filename); - git_hash_ctx_cleanup(&ctx); return 0; on_error: git_mwindow_free_all(&idx->pack->mwf); git_filebuf_cleanup(&index_file); git_buf_free(&filename); - git_hash_ctx_cleanup(&ctx); return -1; } diff --git a/src/object.c b/src/object.c index bd87c9310..2da36a2ee 100644 --- a/src/object.c +++ b/src/object.c @@ -16,7 +16,6 @@ #include "tag.h" bool git_object__strict_input_validation = true; -bool git_object__synchronous_writing = false; typedef struct { const char *str; /* type name string */ diff --git a/src/object.h b/src/object.h index 13117e4c3..dd227d16d 100644 --- a/src/object.h +++ b/src/object.h @@ -10,7 +10,6 @@ #include "repository.h" extern bool git_object__strict_input_validation; -extern bool git_object__synchronous_writing; /** Base git object for inheritance */ struct git_object { @@ -31,6 +31,8 @@ #define GIT_ALTERNATES_MAX_DEPTH 5 +bool git_odb__strict_hash_verification = true; + typedef struct { git_odb_backend *backend; @@ -998,7 +1000,9 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, size_t i; git_rawobj raw; git_odb_object *object; + git_oid hashed; bool found = false; + int error = 0; if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0) found = true; @@ -1011,7 +1015,7 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, continue; if (b->read != NULL) { - int error = b->read(&raw.data, &raw.len, &raw.type, b, id); + error = b->read(&raw.data, &raw.len, &raw.type, b, id); if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) continue; @@ -1025,12 +1029,26 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, if (!found) return GIT_ENOTFOUND; + if (git_odb__strict_hash_verification) { + if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(id, &hashed)) { + error = git_odb__error_mismatch(id, &hashed); + goto out; + } + } + giterr_clear(); if ((object = odb_object__alloc(id, &raw)) == NULL) - return -1; + goto out; *out = git_cache_store_raw(odb_cache(db), object); - return 0; + +out: + if (error) + git__free(raw.data); + return error; } int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) @@ -1081,9 +1099,9 @@ static int read_prefix_1(git_odb_object **out, git_odb *db, const git_oid *key, size_t len, bool only_refreshed) { size_t i; - int error = GIT_ENOTFOUND; + int error = 0; git_oid found_full_oid = {{0}}; - git_rawobj raw; + git_rawobj raw = {0}; void *data = NULL; bool found = false; git_odb_object *object; @@ -1098,18 +1116,29 @@ static int read_prefix_1(git_odb_object **out, git_odb *db, if (b->read_prefix != NULL) { git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) { + error = 0; continue; + } if (error) - return error; + goto out; git__free(data); data = raw.data; if (found && git_oid__cmp(&full_oid, &found_full_oid)) { - git__free(raw.data); - return git_odb__error_ambiguous("multiple matches for prefix"); + git_buf buf = GIT_BUF_INIT; + + git_buf_printf(&buf, "multiple matches for prefix: %s", + git_oid_tostr_s(&full_oid)); + git_buf_printf(&buf, " %s", + git_oid_tostr_s(&found_full_oid)); + + error = git_odb__error_ambiguous(buf.ptr); + git_buf_free(&buf); + goto out; } found_full_oid = full_oid; @@ -1120,11 +1149,28 @@ static int read_prefix_1(git_odb_object **out, git_odb *db, if (!found) return GIT_ENOTFOUND; + if (git_odb__strict_hash_verification) { + git_oid hash; + + if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(&found_full_oid, &hash)) { + error = git_odb__error_mismatch(&found_full_oid, &hash); + goto out; + } + } + if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) - return -1; + goto out; *out = git_cache_store_raw(odb_cache(db), object); - return 0; + +out: + if (error) + git__free(raw.data); + + return error; } int git_odb_read_prefix( @@ -1283,9 +1329,9 @@ static int git_odb_stream__invalid_length( { giterr_set(GITERR_ODB, "cannot %s - " - "Invalid length. %"PRIuZ" was expected. The " - "total size of the received chunks amounts to %"PRIuZ".", - action, stream->declared_size, stream->received_bytes); + "Invalid length. %"PRIdZ" was expected. The " + "total size of the received chunks amounts to %"PRIdZ".", + action, stream->declared_size, stream->received_bytes); return -1; } @@ -1411,6 +1457,19 @@ int git_odb_refresh(struct git_odb *db) return 0; } +int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual) +{ + char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(expected_oid, sizeof(expected_oid), expected); + git_oid_tostr(actual_oid, sizeof(actual_oid), actual); + + giterr_set(GITERR_ODB, "object hash mismatch - expected %s but got %s", + expected_oid, actual_oid); + + return GIT_EMISMATCH; +} + int git_odb__error_notfound( const char *message, const git_oid *oid, size_t oid_len) { @@ -20,6 +20,8 @@ #define GIT_OBJECT_DIR_MODE 0777 #define GIT_OBJECT_FILE_MODE 0444 +extern bool git_odb__strict_hash_verification; + /* DO NOT EXPORT */ typedef struct { void *data; /**< Raw, decompressed object data. */ @@ -96,6 +98,12 @@ int git_odb__hashfd_filtered( */ int git_odb__hashlink(git_oid *out, const char *path); +/** + * Generate a GIT_EMISMATCH error for the ODB. + */ +int git_odb__error_mismatch( + const git_oid *expected, const git_oid *actual); + /* * Generate a GIT_ENOTFOUND error for the ODB. */ diff --git a/src/odb_loose.c b/src/odb_loose.c index e14af4fab..99fdcb44f 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -205,6 +205,11 @@ static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) return inflate(s, 0); } +static void abort_inflate(z_stream *s) +{ + inflateEnd(s); +} + static int finish_inflate(z_stream *s) { int status = Z_OK; @@ -367,6 +372,7 @@ static int inflate_disk_obj(git_rawobj *out, git_buf *obj) (used = get_object_header(&hdr, head)) == 0 || !git_object_typeisloose(hdr.type)) { + abort_inflate(&zs); giterr_set(GITERR_ODB, "failed to inflate disk object"); return -1; } @@ -844,7 +850,7 @@ static int filebuf_flags(loose_backend *backend) int flags = GIT_FILEBUF_TEMPORARY | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT); - if (backend->fsync_object_files || git_object__synchronous_writing) + if (backend->fsync_object_files || git_repository__fsync_gitdir) flags |= GIT_FILEBUF_FSYNC; return flags; diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 841dcce50..759c5015f 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -103,7 +103,7 @@ int git_openssl_stream_global_init(void) ssl_opts |= SSL_OP_NO_COMPRESSION; #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) SSL_load_error_strings(); OpenSSL_add_ssl_algorithms(); #else diff --git a/src/pack.c b/src/pack.c index 60b757e90..f8d0dc9ac 100644 --- a/src/pack.c +++ b/src/pack.c @@ -312,7 +312,7 @@ static int pack_index_open(struct git_pack_file *p) { int error = 0; size_t name_len; - git_buf idx_name = GIT_BUF_INIT; + git_buf idx_name; if (p->index_version > -1) return 0; @@ -320,11 +320,13 @@ static int pack_index_open(struct git_pack_file *p) name_len = strlen(p->pack_name); assert(name_len > strlen(".pack")); /* checked by git_pack_file alloc */ - git_buf_grow(&idx_name, name_len); + if (git_buf_init(&idx_name, name_len) < 0) + return -1; + git_buf_put(&idx_name, p->pack_name, name_len - strlen(".pack")); git_buf_puts(&idx_name, ".idx"); if (git_buf_oom(&idx_name)) { - giterr_set_oom(); + git_buf_free(&idx_name); return -1; } diff --git a/src/path.c b/src/path.c index b7205a646..5fc7a055b 100644 --- a/src/path.c +++ b/src/path.c @@ -1708,6 +1708,7 @@ GIT_INLINE(unsigned int) dotgit_flags( unsigned int flags) { int protectHFS = 0, protectNTFS = 0; + int error = 0; flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; @@ -1720,13 +1721,13 @@ GIT_INLINE(unsigned int) dotgit_flags( #endif if (repo && !protectHFS) - git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS); - if (protectHFS) + error = git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS); + if (!error && protectHFS) flags |= GIT_PATH_REJECT_DOT_GIT_HFS; if (repo && !protectNTFS) - git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS); - if (protectNTFS) + error = git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS); + if (!error && protectNTFS) flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; return flags; diff --git a/src/posix.h b/src/posix.h index bd5a98e26..d26371bca 100644 --- a/src/posix.h +++ b/src/posix.h @@ -24,6 +24,10 @@ #define _S_IFLNK S_IFLNK #endif +#ifndef S_IWUSR +#define S_IWUSR 00200 +#endif + #ifndef S_IXUSR #define S_IXUSR 00100 #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index b325d2763..eb135dc01 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -1140,7 +1140,7 @@ out: static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message) { int error; - git_oid old_id = {{0}}; + git_oid old_id; git_reference *tmp = NULL, *head = NULL, *peeled = NULL; const char *name; @@ -1148,7 +1148,8 @@ static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref return 0; /* if we can't resolve, we use {0}*40 as old id */ - git_reference_name_to_id(&old_id, backend->repo, ref->name); + if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0) + memset(&old_id, 0, sizeof(old_id)); if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0) return error; @@ -2031,7 +2032,7 @@ int git_refdb_backend_fs( backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE; } if ((!git_repository__cvar(&t, backend->repo, GIT_CVAR_FSYNCOBJECTFILES) && t) || - git_object__synchronous_writing) + git_repository__fsync_gitdir) backend->fsync = 1; backend->parent.exists = &refdb_fs_backend__exists; diff --git a/src/refs.c b/src/refs.c index 0837dc4a8..f7120d9ee 100644 --- a/src/refs.c +++ b/src/refs.c @@ -249,6 +249,40 @@ int git_reference_lookup_resolved( return 0; } +int git_reference__read_head( + git_reference **out, + git_repository *repo, + const char *path) +{ + git_buf reference = GIT_BUF_INIT; + char *name = NULL; + int error; + + if ((error = git_futils_readbuffer(&reference, path)) < 0) + goto out; + git_buf_rtrim(&reference); + + if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) == 0) { + git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); + + name = git_path_basename(path); + + if ((*out = git_reference__alloc_symbolic(name, reference.ptr)) == NULL) { + error = -1; + goto out; + } + } else { + if ((error = git_reference_lookup(out, repo, reference.ptr)) < 0) + goto out; + } + +out: + git__free(name); + git_buf_free(&reference); + + return error; +} + int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) { int error = 0, i; @@ -580,20 +614,63 @@ int git_reference_symbolic_set_target( out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message); } +typedef struct { + const char *old_name; + git_refname_t new_name; +} rename_cb_data; + +static int update_wt_heads(git_repository *repo, const char *path, void *payload) +{ + rename_cb_data *data = (rename_cb_data *) payload; + git_reference *head = NULL; + char *gitdir = NULL; + int error; + + if ((error = git_reference__read_head(&head, repo, path)) < 0) { + giterr_set(GITERR_REFERENCE, "could not read HEAD when renaming references"); + goto out; + } + + if ((gitdir = git_path_dirname(path)) == NULL) { + error = -1; + goto out; + } + + if (git_reference_type(head) != GIT_REF_SYMBOLIC || + git__strcmp(head->target.symbolic, data->old_name) != 0) { + error = 0; + goto out; + } + + /* Update HEAD it was pointing to the reference being renamed */ + if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) { + giterr_set(GITERR_REFERENCE, "failed to update HEAD after renaming reference"); + goto out; + } + +out: + git_reference_free(head); + git__free(gitdir); + + return error; +} + static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, const git_signature *signature, const char *message) { + git_repository *repo; git_refname_t normalized; bool should_head_be_updated = false; int error = 0; assert(ref && new_name && signature); + repo = git_reference_owner(ref); + if ((error = reference_normalize_for_repo( - normalized, git_reference_owner(ref), new_name, true)) < 0) + normalized, repo, new_name, true)) < 0) return error; - /* Check if we have to update HEAD. */ if ((error = git_branch_is_head(ref)) < 0) return error; @@ -603,14 +680,18 @@ static int reference__rename(git_reference **out, git_reference *ref, const char if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0) return error; - /* Update HEAD it was pointing to the reference being renamed */ - if (should_head_be_updated && - (error = git_repository_set_head(ref->db->repo, normalized)) < 0) { - giterr_set(GITERR_REFERENCE, "failed to update HEAD after renaming reference"); - return error; + /* Update HEAD if it was pointing to the reference being renamed */ + if (should_head_be_updated) { + error = git_repository_set_head(ref->db->repo, normalized); + } else { + rename_cb_data payload; + payload.old_name = ref->name; + memcpy(&payload.new_name, &normalized, sizeof(normalized)); + + error = git_repository_foreach_head(repo, update_wt_heads, &payload); } - return 0; + return error; } diff --git a/src/refs.h b/src/refs.h index 80e655af7..0c90db3af 100644 --- a/src/refs.h +++ b/src/refs.h @@ -107,6 +107,20 @@ int git_reference_lookup_resolved( const char *name, int max_deref); +/** + * Read reference from a file. + * + * This function will read in the file at `path`. If it is a + * symref, it will return a new unresolved symbolic reference + * with the given name pointing to the reference pointed to by + * the file. If it is not a symbolic reference, it will return + * the resolved reference. + */ +int git_reference__read_head( + git_reference **out, + git_repository *repo, + const char *path); + int git_reference__log_signature(git_signature **out, git_repository *repo); /** Update a reference after a commit. */ diff --git a/src/remote.c b/src/remote.c index d3132f75c..bd8b3cfbc 100644 --- a/src/remote.c +++ b/src/remote.c @@ -192,7 +192,7 @@ static int canonicalize_url(git_buf *out, const char *in) static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) { git_remote *remote; - git_config *config = NULL; + git_config *config_ro = NULL, *config_rw; git_buf canonical_url = GIT_BUF_INIT; git_buf var = GIT_BUF_INIT; int error = -1; @@ -200,7 +200,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n /* name is optional */ assert(out && repo && url); - if ((error = git_repository_config__weakptr(&config, repo)) < 0) + if ((error = git_repository_config_snapshot(&config_ro, repo)) < 0) return error; remote = git__calloc(1, sizeof(git_remote)); @@ -212,7 +212,8 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n (error = canonicalize_url(&canonical_url, url)) < 0) goto on_error; - remote->url = apply_insteadof(repo->_config, canonical_url.ptr, GIT_DIRECTION_FETCH); + remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH); + GITERR_CHECK_ALLOC(remote->url); if (name != NULL) { remote->name = git__strdup(name); @@ -221,7 +222,8 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0) goto on_error; - if ((error = git_config_set_string(config, var.ptr, canonical_url.ptr)) < 0) + if ((error = git_repository_config__weakptr(&config_rw, repo)) < 0 || + (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0) goto on_error; } @@ -233,10 +235,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n if (name && (error = write_add_refspec(repo, name, fetch, true)) < 0) goto on_error; - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - goto on_error; - - if ((error = lookup_remote_prune_config(remote, config, name)) < 0) + if ((error = lookup_remote_prune_config(remote, config_ro, name)) < 0) goto on_error; /* Move the data over to where the matching functions can find them */ @@ -260,7 +259,7 @@ on_error: if (error) git_remote_free(remote); - git_config_free(config); + git_config_free(config_ro); git_buf_free(&canonical_url); git_buf_free(&var); return error; @@ -2412,7 +2411,7 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ proxy = &opts->proxy_opts; } - assert(remote && refspecs); + assert(remote); if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0) return error; diff --git a/src/repository.c b/src/repository.c index 425ef796f..71f77bab9 100644 --- a/src/repository.c +++ b/src/repository.c @@ -36,6 +36,8 @@ # include "win32/w32_util.h" #endif +bool git_repository__fsync_gitdir = false; + static const struct { git_repository_item_t parent; const char *name; @@ -422,10 +424,10 @@ static int read_gitfile(git_buf *path_out, const char *file_path) } static int find_repo( - git_buf *repo_path, - git_buf *parent_path, - git_buf *link_path, - git_buf *common_path, + git_buf *gitdir_path, + git_buf *workdir_path, + git_buf *gitlink_path, + git_buf *commondir_path, const char *start_path, uint32_t flags, const char *ceiling_dirs) @@ -440,7 +442,7 @@ static int find_repo( bool in_dot_git; size_t ceiling_offset = 0; - git_buf_free(repo_path); + git_buf_clear(gitdir_path); error = git_path_prettify(&path, start_path, NULL); if (error < 0) @@ -482,13 +484,13 @@ static int find_repo( if (S_ISDIR(st.st_mode)) { if (valid_repository_path(&path, &common_link)) { git_path_to_dir(&path); - git_buf_set(repo_path, path.ptr, path.size); + git_buf_set(gitdir_path, path.ptr, path.size); - if (link_path) - git_buf_attach(link_path, + if (gitlink_path) + git_buf_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0); - if (common_path) - git_buf_swap(&common_link, common_path); + if (commondir_path) + git_buf_swap(&common_link, commondir_path); break; } @@ -498,12 +500,12 @@ static int find_repo( if (error < 0) break; if (valid_repository_path(&repo_link, &common_link)) { - git_buf_swap(repo_path, &repo_link); + git_buf_swap(gitdir_path, &repo_link); - if (link_path) - error = git_buf_put(link_path, path.ptr, path.size); - if (common_path) - git_buf_swap(&common_link, common_path); + if (gitlink_path) + error = git_buf_put(gitlink_path, path.ptr, path.size); + if (commondir_path) + git_buf_swap(&common_link, commondir_path); } break; } @@ -529,20 +531,20 @@ static int find_repo( break; } - if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_buf_len(repo_path)) - git_buf_clear(parent_path); + if (!error && workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_buf_len(gitdir_path)) + git_buf_clear(workdir_path); else { - git_path_dirname_r(parent_path, path.ptr); - git_path_to_dir(parent_path); + git_path_dirname_r(workdir_path, path.ptr); + git_path_to_dir(workdir_path); } - if (git_buf_oom(parent_path)) + if (git_buf_oom(workdir_path)) return -1; } /* If we didn't find the repository, and we don't have any other error * to report, report that. */ - if (!git_buf_len(repo_path) && !error) { + if (!git_buf_len(gitdir_path) && !error) { giterr_set(GITERR_REPOSITORY, "could not find repository from '%s'", start_path); error = GIT_ENOTFOUND; @@ -758,6 +760,29 @@ success: return error; } +static int repo_is_worktree(unsigned *out, const git_repository *repo) +{ + git_buf gitdir_link = GIT_BUF_INIT; + int error; + + /* Worktrees cannot have the same commondir and gitdir */ + if (repo->commondir && repo->gitdir + && !strcmp(repo->commondir, repo->gitdir)) { + *out = 0; + return 0; + } + + if ((error = git_buf_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) + return -1; + + /* A 'gitdir' file inside a git directory is currently + * only used when the repository is a working tree. */ + *out = !!git_path_exists(gitdir_link.ptr); + + git_buf_free(&gitdir_link); + return error; +} + int git_repository_open_ext( git_repository **repo_ptr, const char *start_path, @@ -765,8 +790,9 @@ int git_repository_open_ext( const char *ceiling_dirs) { int error; - git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT, - link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; + unsigned is_worktree; + git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT, + gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT; git_repository *repo; git_config *config = NULL; @@ -777,7 +803,7 @@ int git_repository_open_ext( *repo_ptr = NULL; error = find_repo( - &path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs); + &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); if (error < 0 || !repo_ptr) return error; @@ -785,24 +811,21 @@ int git_repository_open_ext( repo = repository_alloc(); GITERR_CHECK_ALLOC(repo); - repo->gitdir = git_buf_detach(&path); + repo->gitdir = git_buf_detach(&gitdir); GITERR_CHECK_ALLOC(repo->gitdir); - if (link_path.size) { - repo->gitlink = git_buf_detach(&link_path); + if (gitlink.size) { + repo->gitlink = git_buf_detach(&gitlink); GITERR_CHECK_ALLOC(repo->gitlink); } - if (common_path.size) { - repo->commondir = git_buf_detach(&common_path); + if (commondir.size) { + repo->commondir = git_buf_detach(&commondir); GITERR_CHECK_ALLOC(repo->commondir); } - if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0) + if ((error = repo_is_worktree(&is_worktree, repo)) < 0) goto cleanup; - /* A 'gitdir' file inside a git directory is currently - * only used when the repository is a working tree. */ - if (git_path_exists(path.ptr)) - repo->is_worktree = 1; + repo->is_worktree = is_worktree; /* * We'd like to have the config, but git doesn't particularly @@ -822,13 +845,13 @@ int git_repository_open_ext( if (config && ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &parent)) < 0)) + (error = load_workdir(repo, config, &workdir)) < 0)) goto cleanup; } cleanup: - git_buf_free(&path); - git_buf_free(&parent); + git_buf_free(&gitdir); + git_buf_free(&workdir); git_config_free(config); if (error < 0) @@ -920,13 +943,10 @@ static int load_config( if ((error = git_config_new(&cfg)) < 0) return error; - error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG); - if (error < 0) - goto on_error; + if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0); - if ((error = git_config_add_file_ondisk( - cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 && - error != GIT_ENOTFOUND) + if (error && error != GIT_ENOTFOUND) goto on_error; git_buf_free(&config_path); @@ -1215,7 +1235,7 @@ static int reserved_names_add8dot3(git_repository *repo, const char *path) name_len = strlen(name); - if ((name_len == def_len && memcmp(name, def, def_len) == 0) || + if ((name_len == def_len && memcmp(name, def, def_len) == 0) || (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { git__free(name); return 0; @@ -1764,7 +1784,13 @@ static int repo_init_structure( default_template = true; } - if (tdir) { + /* + * If tdir was the empty string, treat it like tdir was a path to an + * empty directory (so, don't do any copying). This is the behavior + * that git(1) exhibits, although it doesn't seem to be officially + * documented. + */ + if (tdir && git__strcmp(tdir, "") != 0) { uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE | GIT_CPDIR_COPY_DOTFILES; @@ -2063,47 +2089,27 @@ int git_repository_head_detached(git_repository *repo) return exists; } -static int read_worktree_head(git_buf *out, git_repository *repo, const char *name) +static int get_worktree_file_path(git_buf *out, git_repository *repo, const char *worktree, const char *file) { - git_buf path = GIT_BUF_INIT; - int err; - - assert(out && repo && name); - git_buf_clear(out); - - if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0) - goto out; - if (!git_path_exists(path.ptr)) - { - err = -1; - goto out; - } - - if ((err = git_futils_readbuffer(out, path.ptr)) < 0) - goto out; - git_buf_rtrim(out); - -out: - git_buf_free(&path); - - return err; + return git_buf_printf(out, "%s/worktrees/%s/%s", repo->commondir, worktree, file); } int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) { - git_buf buf = GIT_BUF_INIT; - int ret; + git_reference *ref = NULL; + int error; assert(repo && name); - if (read_worktree_head(&buf, repo, name) < 0) - return -1; + if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) + goto out; - ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0; - git_buf_free(&buf); + error = (git_reference_type(ref) != GIT_REF_SYMBOLIC); +out: + git_reference_free(ref); - return ret; + return error; } int git_repository_head(git_reference **head_out, git_repository *repo) @@ -2127,44 +2133,67 @@ int git_repository_head(git_reference **head_out, git_repository *repo) int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) { - git_buf buf = GIT_BUF_INIT; - git_reference *head; - int err; + git_buf path = GIT_BUF_INIT; + git_reference *head = NULL; + int error; assert(out && repo && name); *out = NULL; - if (git_repository_head_detached_for_worktree(repo, name)) - return -1; - if ((err = read_worktree_head(&buf, repo, name)) < 0) + if ((error = get_worktree_file_path(&path, repo, name, GIT_HEAD_FILE)) < 0 || + (error = git_reference__read_head(&head, repo, path.ptr)) < 0) goto out; - /* We can only resolve symbolic references */ - if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) - { - err = -1; - goto out; + if (git_reference_type(head) != GIT_REF_OID) { + git_reference *resolved; + + error = git_reference_lookup_resolved(&resolved, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + head = resolved; } - git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF)); - if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0) + *out = head; + +out: + if (error) + git_reference_free(head); + + git_buf_free(&path); + + return error; +} + +int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload) +{ + git_strarray worktrees = GIT_VECTOR_INIT; + git_buf path = GIT_BUF_INIT; + int error; + size_t i; + + /* Execute callback for HEAD of commondir */ + if ((error = git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE)) < 0 || + (error = cb(repo, path.ptr, payload) != 0)) goto out; - if (git_reference_type(head) == GIT_REF_OID) - { - *out = head; - err = 0; + + if ((error = git_worktree_list(&worktrees, repo)) < 0) { + error = 0; goto out; } - err = git_reference_lookup_resolved( - out, repo, git_reference_symbolic_target(head), -1); - git_reference_free(head); + /* Execute callback for all worktree HEADs */ + for (i = 0; i < worktrees.count; i++) { + if (get_worktree_file_path(&path, repo, worktrees.strings[i], GIT_HEAD_FILE) < 0) + continue; -out: - git_buf_free(&buf); + if ((error = cb(repo, path.ptr, payload)) != 0) + goto out; + } - return err; +out: + git_buf_free(&path); + git_strarray_free(&worktrees); + return error; } int git_repository_head_unborn(git_repository *repo) @@ -2240,13 +2269,13 @@ int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_ parent = git_repository_commondir(repo); break; default: - giterr_set(GITERR_INVALID, "Invalid item directory"); + giterr_set(GITERR_INVALID, "invalid item directory"); return -1; } if (parent == NULL) { - giterr_set(GITERR_INVALID, "Path cannot exist in repository"); - return -1; + giterr_set(GITERR_INVALID, "path cannot exist in repository"); + return GIT_ENOTFOUND; } if (git_buf_sets(out, parent) < 0) @@ -2529,7 +2558,9 @@ static int checkout_message(git_buf *out, git_reference *old, const char *new) git_buf_puts(out, " to "); - if (git_reference__is_branch(new)) + if (git_reference__is_branch(new) || + git_reference__is_tag(new) || + git_reference__is_remote(new)) git_buf_puts(out, git_reference__shorthand(new)); else git_buf_puts(out, new); @@ -2540,6 +2571,41 @@ static int checkout_message(git_buf *out, git_reference *old, const char *new) return 0; } +static int detach(git_repository *repo, const git_oid *id, const char *new) +{ + int error; + git_buf log_message = GIT_BUF_INIT; + git_object *object = NULL, *peeled = NULL; + git_reference *new_head = NULL, *current = NULL; + + assert(repo && id); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0) + goto cleanup; + + if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + + if (new == NULL) + new = git_oid_tostr_s(git_object_id(peeled)); + + if ((error = checkout_message(&log_message, current, new)) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message)); + +cleanup: + git_buf_free(&log_message); + git_object_free(object); + git_object_free(peeled); + git_reference_free(current); + git_reference_free(new_head); + return error; +} + int git_repository_set_head( git_repository* repo, const char* refname) @@ -2562,6 +2628,8 @@ int git_repository_set_head( if (ref && current->type == GIT_REF_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { + giterr_set(GITERR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " + "of a linked repository.", git_reference_name(ref)); error = -1; goto cleanup; } @@ -2571,7 +2639,8 @@ int git_repository_set_head( error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), true, git_buf_cstr(&log_message)); } else { - error = git_repository_set_head_detached(repo, git_reference_target(ref)); + error = detach(repo, git_reference_target(ref), + git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); } } else if (git_reference__is_branch(refname)) { error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, @@ -2586,41 +2655,6 @@ cleanup: return error; } -static int detach(git_repository *repo, const git_oid *id, const char *from) -{ - int error; - git_buf log_message = GIT_BUF_INIT; - git_object *object = NULL, *peeled = NULL; - git_reference *new_head = NULL, *current = NULL; - - assert(repo && id); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0) - goto cleanup; - - if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) - goto cleanup; - - if (from == NULL) - from = git_oid_tostr_s(git_object_id(peeled)); - - if ((error = checkout_message(&log_message, current, from)) < 0) - goto cleanup; - - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message)); - -cleanup: - git_buf_free(&log_message); - git_object_free(object); - git_object_free(peeled); - git_reference_free(current); - git_reference_free(new_head); - return error; -} - int git_repository_set_head_detached( git_repository* repo, const git_oid* commitish) @@ -2734,7 +2768,7 @@ int git_repository__cleanup_files( error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); } - + git_buf_clear(&buf); } diff --git a/src/repository.h b/src/repository.h index 33adfa60a..52f9ec260 100644 --- a/src/repository.h +++ b/src/repository.h @@ -31,6 +31,8 @@ /* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */ #define GIT_DIR_SHORTNAME "GIT~1" +extern bool git_repository__fsync_gitdir; + /** Cvar cache identifiers */ typedef enum { GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */ @@ -160,6 +162,26 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo); int git_repository_create_head(const char *git_dir, const char *ref_name); /* + * Called for each HEAD. + * + * Can return either 0, causing the iteration over HEADs to + * continue, or a non-0 value causing the iteration to abort. The + * return value is passed back to the caller of + * `git_repository_foreach_head` + */ +typedef int (*git_repository_foreach_head_cb)(git_repository *repo, const char *path, void *payload); + +/* + * Iterate over repository and all worktree HEADs. + * + * This function will be called for the repository HEAD and for + * all HEADS of linked worktrees. For each HEAD, the callback is + * executed with the given payload. The return value equals the + * return value of the last executed callback function. + */ +int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload); + +/* * Weak pointers to repository internals. * * The returned pointers do not need to be freed. Do not keep diff --git a/src/revparse.c b/src/revparse.c index d5511b47b..fd6bd1ea6 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -892,6 +892,17 @@ int git_revparse( const char *rstr; revspec->flags = GIT_REVPARSE_RANGE; + /* + * Following git.git, don't allow '..' because it makes command line + * arguments which can be either paths or revisions ambiguous when the + * path is almost certainly intended. The empty range '...' is still + * allowed. + */ + if (!git__strcmp(spec, "..")) { + giterr_set(GITERR_INVALID, "Invalid pattern '..'"); + return GIT_EINVALIDSPEC; + } + lstr = git__substrdup(spec, dotdot - spec); rstr = dotdot + 2; if (dotdot[2] == '.') { @@ -899,9 +910,17 @@ int git_revparse( rstr++; } - error = git_revparse_single(&revspec->from, repo, lstr); - if (!error) - error = git_revparse_single(&revspec->to, repo, rstr); + error = git_revparse_single( + &revspec->from, + repo, + *lstr == '\0' ? "HEAD" : lstr); + + if (!error) { + error = git_revparse_single( + &revspec->to, + repo, + *rstr == '\0' ? "HEAD" : rstr); + } git__free((void*)lstr); } else { diff --git a/src/settings.c b/src/settings.c index 24e549ec1..52b861ba0 100644 --- a/src/settings.c +++ b/src/settings.c @@ -15,6 +15,7 @@ #include "cache.h" #include "global.h" #include "object.h" +#include "odb.h" #include "refs.h" #include "transports/smart.h" @@ -31,7 +32,7 @@ int git_libgit2_features(void) #ifdef GIT_THREADS | GIT_FEATURE_THREADS #endif -#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) +#ifdef GIT_HTTPS | GIT_FEATURE_HTTPS #endif #if defined(GIT_SSH) @@ -227,8 +228,24 @@ int git_libgit2_opts(int key, ...) git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); break; - case GIT_OPT_ENABLE_SYNCHRONOUS_OBJECT_CREATION: - git_object__synchronous_writing = (va_arg(ap, int) != 0); + case GIT_OPT_ENABLE_FSYNC_GITDIR: + git_repository__fsync_gitdir = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; +#endif + break; + + case GIT_OPT_SET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + git_win32__createfile_sharemode = va_arg(ap, unsigned long); +#endif + break; + + case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: + git_odb__strict_hash_verification = (va_arg(ap, int) != 0); break; default: diff --git a/src/signature.c b/src/signature.c index e792a52f8..a56b8a299 100644 --- a/src/signature.c +++ b/src/signature.c @@ -228,8 +228,11 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *time_start = email_end + 2; const char *time_end; - if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) + if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) { + git__free(sig->name); + git__free(sig->email); return signature_error("invalid Unix timestamp"); + } /* do we have a timezone? */ if (time_end + 1 < buffer_end) { diff --git a/src/socket_stream.c b/src/socket_stream.c index fca411717..c0a168448 100644 --- a/src/socket_stream.c +++ b/src/socket_stream.c @@ -106,10 +106,8 @@ int socket_connect(git_stream *stream) for (p = info; p != NULL; p = p->ai_next) { s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); - if (s == INVALID_SOCKET) { - net_set_error("error creating socket"); - break; - } + if (s == INVALID_SOCKET) + continue; if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) break; diff --git a/src/sysdir.c b/src/sysdir.c index ed11221a3..9312a7edb 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -275,3 +275,14 @@ int git_sysdir_find_template_dir(git_buf *path) path, NULL, GIT_SYSDIR_TEMPLATE, "template"); } +int git_sysdir_expand_global_file(git_buf *path, const char *filename) +{ + int error; + + if ((error = git_sysdir_find_global_file(path, NULL)) == 0) { + if (filename) + error = git_buf_joinpath(path, path->ptr, filename); + } + + return error; +} diff --git a/src/sysdir.h b/src/sysdir.h index 11878981c..79f23818a 100644 --- a/src/sysdir.h +++ b/src/sysdir.h @@ -55,6 +55,18 @@ extern int git_sysdir_find_programdata_file(git_buf *path, const char *filename) */ extern int git_sysdir_find_template_dir(git_buf *path); +/** + * Expand the name of a "global" file (i.e. one in a user's home + * directory). Unlike `find_global_file` (above), this makes no + * attempt to check for the existence of the file, and is useful if + * you want the full path regardless of existence. + * + * @param path buffer to write the full path into + * @param filename name of file in the home directory + * @return 0 on success or -1 on error + */ +extern int git_sysdir_expand_global_file(git_buf *path, const char *filename); + typedef enum { GIT_SYSDIR_SYSTEM = 0, GIT_SYSDIR_GLOBAL = 1, diff --git a/src/transports/http.c b/src/transports/http.c index 949e857b0..cb4a6d0d5 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -575,6 +575,9 @@ static int apply_proxy_config(http_subtransport *t) if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0) return error; + opts.credentials = t->owner->proxy.credentials; + opts.certificate_check = t->owner->proxy.certificate_check; + opts.payload = t->owner->proxy.payload; opts.type = GIT_PROXY_SPECIFIED; opts.url = url; error = git_stream_set_proxy(t->io, &opts); diff --git a/src/transports/smart.c b/src/transports/smart.c index e4aa26d86..a96fdf6fb 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -472,6 +472,12 @@ int git_transport_smart_credentials(git_cred **out, git_transport *transport, co return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload); } +int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport) +{ + transport_smart *t = (transport_smart *) transport; + return git_proxy_options_dup(out, &t->proxy); +} + int git_transport_smart(git_transport **out, git_remote *owner, void *param) { transport_smart *t; diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 25e78c65a..8146fa163 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -325,7 +325,8 @@ static int wait_while_ack(gitno_buffer *buf) if (pkt->type == GIT_PKT_ACK && (pkt->status != GIT_ACK_CONTINUE && - pkt->status != GIT_ACK_COMMON)) { + pkt->status != GIT_ACK_COMMON && + pkt->status != GIT_ACK_READY)) { git__free(pkt); return 0; } diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 44d02e522..4c55e3f2a 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -9,6 +9,7 @@ #include <libssh2.h> #endif +#include "global.h" #include "git2.h" #include "buffer.h" #include "netops.h" @@ -893,11 +894,22 @@ int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *p #endif } +#ifdef GIT_SSH +static void shutdown_ssh(void) +{ + libssh2_exit(); +} +#endif + int git_transport_ssh_global_init(void) { #ifdef GIT_SSH + if (libssh2_init(0) < 0) { + giterr_set(GITERR_SSH, "unable to initialize libssh2"); + return -1; + } - libssh2_init(0); + git__on_shutdown(shutdown_ssh); return 0; #else diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index e8e848026..fb504c912 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -429,7 +429,6 @@ static int winhttp_stream_connect(winhttp_stream *s) git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port); if (git_buf_oom(&processed_url)) { - giterr_set_oom(); error = -1; goto on_error; } diff --git a/src/varint.c b/src/varint.c index 2f868607c..beac8c709 100644 --- a/src/varint.c +++ b/src/varint.c @@ -36,7 +36,7 @@ int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) while (value >>= 7) varint[--pos] = 128 | (--value & 127); if (buf) { - if (bufsize < pos) + if (bufsize < (sizeof(varint) - pos)) return -1; memcpy(buf, varint + pos, sizeof(varint) - pos); } diff --git a/src/win32/posix.h b/src/win32/posix.h index 73705fb2b..64769ecd3 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -14,6 +14,9 @@ #include "utf-conv.h" #include "dir.h" +extern unsigned long git_win32__createfile_sharemode; +extern int git_win32__retries; + typedef SOCKET GIT_SOCKET; #define p_lseek(f,n,w) _lseeki64(f, n, w) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 5172627b0..e4fe4142c 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -26,15 +26,6 @@ #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 - * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes. - * The Windows default is 'not inheritable', but the CRT's default (following - * POSIX convention) is 'inheritable'. We have no desire for our handles to be - * inheritable on Windows, so specify the flag to get default behavior back. */ -#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT) - /* Allowable mode bits on Win32. Using mode bits that are not supported on * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it * so we simply remove them. @@ -44,6 +35,164 @@ /* GetFinalPathNameByHandleW signature */ typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); +unsigned long git_win32__createfile_sharemode = + FILE_SHARE_READ | FILE_SHARE_WRITE; +int git_win32__retries = 10; + +GIT_INLINE(void) set_errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + errno = ENOENT; + break; + case ERROR_BAD_ENVIRONMENT: + errno = E2BIG; + break; + case ERROR_BAD_FORMAT: + case ERROR_INVALID_STARTING_CODESEG: + case ERROR_INVALID_STACKSEG: + case ERROR_INVALID_MODULETYPE: + case ERROR_INVALID_EXE_SIGNATURE: + case ERROR_EXE_MARKED_INVALID: + case ERROR_BAD_EXE_FORMAT: + case ERROR_ITERATED_DATA_EXCEEDS_64k: + case ERROR_INVALID_MINALLOCSIZE: + case ERROR_DYNLINK_FROM_INVALID_RING: + case ERROR_IOPL_NOT_ENABLED: + case ERROR_INVALID_SEGDPL: + case ERROR_AUTODATASEG_EXCEEDS_64k: + case ERROR_RING2SEG_MUST_BE_MOVABLE: + case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: + case ERROR_INFLOOP_IN_RELOC_CHAIN: + errno = ENOEXEC; + break; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_DIRECT_ACCESS_HANDLE: + errno = EBADF; + break; + case ERROR_WAIT_NO_CHILDREN: + case ERROR_CHILD_NOT_COMPLETE: + errno = ECHILD; + break; + case ERROR_NO_PROC_SLOTS: + case ERROR_MAX_THRDS_REACHED: + case ERROR_NESTING_NOT_ALLOWED: + errno = EAGAIN; + break; + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_NOT_ENOUGH_QUOTA: + errno = ENOMEM; + break; + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_WRITE_PROTECT: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_BAD_LENGTH: + case ERROR_SEEK: + case ERROR_NOT_DOS_DISK: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_OUT_OF_PAPER: + case ERROR_WRITE_FAULT: + case ERROR_READ_FAULT: + case ERROR_GEN_FAILURE: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_WRONG_DISK: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_DRIVE_LOCKED: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + errno = EACCES; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + case ERROR_NOT_SAME_DEVICE: + errno = EXDEV; + break; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_ACCESS: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_NEGATIVE_SEEK: + errno = EINVAL; + break; + case ERROR_TOO_MANY_OPEN_FILES: + errno = EMFILE; + break; + case ERROR_DISK_FULL: + errno = ENOSPC; + break; + case ERROR_BROKEN_PIPE: + errno = EPIPE; + break; + case ERROR_DIR_NOT_EMPTY: + errno = ENOTEMPTY; + break; + default: + errno = EINVAL; + } +} + +GIT_INLINE(bool) last_error_retryable(void) +{ + int os_error = GetLastError(); + + return (os_error == ERROR_SHARING_VIOLATION || + os_error == ERROR_ACCESS_DENIED); +} + +#define do_with_retries(fn, remediation) \ + do { \ + int __tries, __ret; \ + for (__tries = 0; __tries < git_win32__retries; __tries++) { \ + if (__tries && (__ret = (remediation)) != 0) \ + return __ret; \ + if ((__ret = (fn)) != GIT_RETRY) \ + return __ret; \ + Sleep(5); \ + } \ + return -1; \ + } while (0) \ + +static int ensure_writable(wchar_t *path) +{ + DWORD attrs; + + if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) + goto on_error; + + if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) + return 0; + + if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) + goto on_error; + + return 0; + +on_error: + set_errno(); + return -1; +} + /** * Truncate or extend file. * @@ -89,24 +238,26 @@ int p_link(const char *old, const char *new) return -1; } -int p_unlink(const char *path) +GIT_INLINE(int) unlink_once(const wchar_t *path) { - git_win32_path buf; - int error; + if (DeleteFileW(path)) + return 0; - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; + if (last_error_retryable()) + return GIT_RETRY; - error = _wunlink(buf); + set_errno(); + return -1; +} - /* If the file could not be deleted because it was - * read-only, clear the bit and try again */ - if (error == -1 && errno == EACCES) { - _wchmod(buf, 0666); - error = _wunlink(buf); - } +int p_unlink(const char *path) +{ + git_win32_path wpath; - return error; + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + do_with_retries(unlink_once(wpath), ensure_writable(wpath)); } int p_fsync(int fd) @@ -212,44 +363,6 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, true); } -int p_utimes(const char *filename, const struct p_timeval times[2]) -{ - int fd, error; - - if ((fd = p_open(filename, O_RDWR)) < 0) - return fd; - - error = p_futimes(fd, times); - - close(fd); - return error; -} - -int p_futimes(int fd, const struct p_timeval times[2]) -{ - HANDLE handle; - FILETIME atime = {0}, mtime = {0}; - - if (times == NULL) { - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st, &atime); - SystemTimeToFileTime(&st, &mtime); - } else { - git_win32__timeval_to_filetime(&atime, times[0]); - git_win32__timeval_to_filetime(&mtime, times[1]); - } - - if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) - return -1; - - if (SetFileTime(handle, NULL, &atime, &mtime) == 0) - return -1; - - return 0; -} - int p_readlink(const char *path, char *buf, size_t bufsiz) { git_win32_path path_w, target_w; @@ -282,12 +395,91 @@ int p_symlink(const char *old, const char *new) return git_futils_fake_symlink(old, new); } +struct open_opts { + DWORD access; + DWORD sharing; + SECURITY_ATTRIBUTES security; + DWORD creation_disposition; + DWORD attributes; + int osf_flags; +}; + +GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) +{ + memset(opts, 0, sizeof(struct open_opts)); + + switch (flags & (O_WRONLY | O_RDWR)) { + case O_WRONLY: + opts->access = GENERIC_WRITE; + break; + case O_RDWR: + opts->access = GENERIC_READ | GENERIC_WRITE; + break; + default: + opts->access = GENERIC_READ; + break; + } + + opts->sharing = (DWORD)git_win32__createfile_sharemode; + + switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { + case O_CREAT | O_EXCL: + case O_CREAT | O_TRUNC | O_EXCL: + opts->creation_disposition = CREATE_NEW; + break; + case O_CREAT | O_TRUNC: + opts->creation_disposition = CREATE_ALWAYS; + break; + case O_TRUNC: + opts->creation_disposition = TRUNCATE_EXISTING; + break; + case O_CREAT: + opts->creation_disposition = OPEN_ALWAYS; + break; + default: + opts->creation_disposition = OPEN_EXISTING; + break; + } + + opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? + FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; + opts->osf_flags = flags & (O_RDONLY | O_APPEND); + + opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); + opts->security.lpSecurityDescriptor = NULL; + opts->security.bInheritHandle = 0; +} + +GIT_INLINE(int) open_once( + const wchar_t *path, + struct open_opts *opts) +{ + int fd; + + HANDLE handle = CreateFileW(path, opts->access, opts->sharing, + &opts->security, opts->creation_disposition, opts->attributes, 0); + + if (handle == INVALID_HANDLE_VALUE) { + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; + } + + if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) + CloseHandle(handle); + + return fd; +} + int p_open(const char *path, int flags, ...) { - git_win32_path buf; + git_win32_path wpath; mode_t mode = 0; + struct open_opts opts = {0}; - if (git_win32_path_from_utf8(buf, path) < 0) + if (git_win32_path_from_utf8(wpath, path) < 0) return -1; if (flags & O_CREAT) { @@ -298,19 +490,83 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode & WIN32_MODE_MASK); + open_opts_from_posix(&opts, flags, mode); + + do_with_retries( + open_once(wpath, &opts), + 0); } int p_creat(const char *path, mode_t mode) { - git_win32_path buf; + return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); +} - if (git_win32_path_from_utf8(buf, path) < 0) +int p_utimes(const char *path, const struct p_timeval times[2]) +{ + git_win32_path wpath; + int fd, error; + DWORD attrs_orig, attrs_new = 0; + struct open_opts opts = { 0 }; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + attrs_orig = GetFileAttributesW(wpath); + + if (attrs_orig & FILE_ATTRIBUTE_READONLY) { + attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; + + if (!SetFileAttributesW(wpath, attrs_new)) { + giterr_set(GITERR_OS, "failed to set attributes"); + return -1; + } + } + + open_opts_from_posix(&opts, O_RDWR, 0); + + if ((fd = open_once(wpath, &opts)) < 0) { + error = -1; + goto done; + } + + error = p_futimes(fd, times); + close(fd); + +done: + if (attrs_orig != attrs_new) { + DWORD os_error = GetLastError(); + SetFileAttributesW(wpath, attrs_orig); + SetLastError(os_error); + } + + return error; +} + +int p_futimes(int fd, const struct p_timeval times[2]) +{ + HANDLE handle; + FILETIME atime = { 0 }, mtime = { 0 }; + + if (times == NULL) { + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &atime); + SystemTimeToFileTime(&st, &mtime); + } + else { + git_win32__timeval_to_filetime(&atime, times[0]); + git_win32__timeval_to_filetime(&mtime, times[1]); + } + + if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) + return -1; + + if (SetFileTime(handle, NULL, &atime, &mtime) == 0) return -1; - return _wopen(buf, - _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, - mode & WIN32_MODE_MASK); + return 0; } int p_getcwd(char *buffer_out, size_t size) @@ -583,62 +839,27 @@ int p_access(const char* path, mode_t mode) return _waccess(buf, mode & WIN32_MODE_MASK); } -static int ensure_writable(wchar_t *fpath) +GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) { - 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)) + if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) return 0; - attrs &= ~FILE_ATTRIBUTE_READONLY; - if (!SetFileAttributesW(fpath, attrs)) { - giterr_set(GITERR_OS, "failed to set attributes"); - return -1; - } + if (last_error_retryable()) + return GIT_RETRY; - return 0; + set_errno(); + return -1; } int p_rename(const char *from, const char *to) { - git_win32_path wfrom; - git_win32_path wto; - int rename_tries; - int rename_succeeded; - int error; + git_win32_path wfrom, wto; if (git_win32_path_from_utf8(wfrom, from) < 0 || git_win32_path_from_utf8(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 (ensure_writable(wto) == 0 && - MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) { - rename_succeeded = 1; - break; - } - - error = GetLastError(); - if (error == ERROR_SHARING_VIOLATION || error == ERROR_ACCESS_DENIED) { - Sleep(5); - rename_tries++; - } else - break; - } - - return rename_succeeded ? 0 : -1; + do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); } int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) diff --git a/src/worktree.c b/src/worktree.c index 55fbf5204..ede155b69 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -212,7 +212,7 @@ int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) goto out; out: - free(name); + git__free(name); git_buf_free(&parent); return error; @@ -269,15 +269,32 @@ out: return err; } -int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree) +int git_worktree_add_init_options(git_worktree_add_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT); + return 0; +} + +int git_worktree_add(git_worktree **out, git_repository *repo, + const char *name, const char *worktree, + const git_worktree_add_options *opts) { git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT; git_reference *ref = NULL, *head = NULL; git_commit *commit = NULL; git_repository *wt = NULL; git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT; + git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT; int err; + GITERR_CHECK_VERSION( + opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options"); + + if (opts) + memcpy(&wtopts, opts, sizeof(wtopts)); + assert(out && repo && name && worktree); *out = NULL; @@ -301,6 +318,21 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, if ((err = git_path_prettify_dir(&wddir, worktree, NULL)) < 0) goto out; + if (wtopts.lock) { + int fd; + + if ((err = git_buf_joinpath(&buf, gitdir.ptr, "locked")) < 0) + goto out; + + if ((fd = p_creat(buf.ptr, 0644)) < 0) { + err = fd; + goto out; + } + + p_close(fd); + git_buf_clear(&buf); + } + /* Create worktree .git file */ if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) goto out; @@ -424,11 +456,29 @@ out: return ret; } -int git_worktree_is_prunable(git_worktree *wt, unsigned flags) +int git_worktree_prune_init_options( + git_worktree_prune_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT); + return 0; +} + +int git_worktree_is_prunable(git_worktree *wt, + git_worktree_prune_options *opts) { git_buf reason = GIT_BUF_INIT; + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + + GITERR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); - if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 && + if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 && git_worktree_is_locked(&reason, wt)) { if (!reason.size) @@ -439,7 +489,7 @@ int git_worktree_is_prunable(git_worktree *wt, unsigned flags) return 0; } - if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 && + if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 && git_worktree_validate(wt) == 0) { giterr_set(GITERR_WORKTREE, "Not pruning valid working tree"); @@ -449,13 +499,22 @@ int git_worktree_is_prunable(git_worktree *wt, unsigned flags) return 1; } -int git_worktree_prune(git_worktree *wt, unsigned flags) +int git_worktree_prune(git_worktree *wt, + git_worktree_prune_options *opts) { + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; git_buf path = GIT_BUF_INIT; char *wtpath; int err; - if (!git_worktree_is_prunable(wt, flags)) { + GITERR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); + + if (!git_worktree_is_prunable(wt, &popts)) { err = -1; goto out; } @@ -474,7 +533,7 @@ int git_worktree_prune(git_worktree *wt, unsigned flags) /* Skip deletion of the actual working tree if it does * not exist or deletion was not requested */ - if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || + if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || !git_path_exists(wt->gitlink_path)) { goto out; |
