diff options
| author | Alan Rogers <alan@github.com> | 2014-06-04 15:36:28 -0700 |
|---|---|---|
| committer | Alan Rogers <alan@github.com> | 2014-06-04 15:36:28 -0700 |
| commit | dc49e1b5b368b4b7d5b8ae8a0a12027f86395cad (patch) | |
| tree | 6335b980e9ff7688d758e411088838b6adb75baa /src | |
| parent | 54c02d212d70b439f402c74c6b1f6c835daa43fc (diff) | |
| parent | 90befde4a1938641dfdb9a7bdb9f361d1de5c26f (diff) | |
| download | libgit2-dc49e1b5b368b4b7d5b8ae8a0a12027f86395cad.tar.gz | |
Merge remote-tracking branch 'origin/development' into fix-git-status-list-new-unreadable-folder
Conflicts:
include/git2/diff.h
Diffstat (limited to 'src')
55 files changed, 1413 insertions, 605 deletions
diff --git a/src/attr_file.c b/src/attr_file.c index 156a23d91..3e95a2134 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -281,7 +281,7 @@ uint32_t git_attr_file__name_hash(const char *name) int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value) { @@ -342,14 +342,11 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path) bool git_attr_fnmatch__match( git_attr_fnmatch *match, - const git_attr_path *path) + git_attr_path *path) { const char *filename; int flags = 0; - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) - return false; - if (match->flags & GIT_ATTR_FNMATCH_ICASE) flags |= FNM_CASEFOLD; if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR) @@ -365,12 +362,28 @@ bool git_attr_fnmatch__match( flags |= FNM_LEADING_DIR; } + if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { + int matchval; + + /* for attribute checks or root ignore checks, fail match */ + if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || + path->basename == path->path) + return false; + + /* for ignore checks, use container of current item for check */ + path->basename[-1] = '\0'; + flags |= FNM_LEADING_DIR; + matchval = p_fnmatch(match->pattern, path->path, flags); + path->basename[-1] = '/'; + return (matchval != FNM_NOMATCH); + } + return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); } bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path) + git_attr_path *path) { bool matched = git_attr_fnmatch__match(&rule->match, path); diff --git a/src/attr_file.h b/src/attr_file.h index e50aec07c..87cde7e35 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -138,7 +138,7 @@ int git_attr_file__clear_rules( int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value); @@ -162,13 +162,13 @@ extern int git_attr_fnmatch__parse( extern bool git_attr_fnmatch__match( git_attr_fnmatch *rule, - const git_attr_path *path); + git_attr_path *path); extern void git_attr_rule__free(git_attr_rule *rule); extern bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path); + git_attr_path *path); extern git_attr_assignment *git_attr_rule__lookup_assignment( git_attr_rule *rule, const char *name); diff --git a/src/attrcache.c b/src/attrcache.c index f1bc70467..b4579bfc0 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -53,7 +53,7 @@ int git_attr_cache__alloc_file_entry( cachesize++; } - ce = git_pool_mallocz(pool, cachesize); + ce = git_pool_mallocz(pool, (uint32_t)cachesize); GITERR_CHECK_ALLOC(ce); if (baselen) { @@ -349,14 +349,11 @@ int git_attr_cache__do_init(git_repository *repo) { int ret = 0; git_attr_cache *cache = git_repository_attr_cache(repo); - git_config *cfg; + git_config *cfg = NULL; if (cache) return 0; - if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0) - return ret; - cache = git__calloc(1, sizeof(git_attr_cache)); GITERR_CHECK_ALLOC(cache); @@ -367,6 +364,9 @@ int git_attr_cache__do_init(git_repository *repo) return -1; } + if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) + goto cancel; + /* cache config settings for attributes and ignores */ ret = attr_cache__lookup_path( &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); @@ -390,11 +390,14 @@ int git_attr_cache__do_init(git_repository *repo) if (cache) goto cancel; /* raced with another thread, free this but no error */ + git_config_free(cfg); + /* insert default macros */ return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); cancel: attr_cache__free(cache); + git_config_free(cfg); return ret; } diff --git a/src/blob.c b/src/blob.c index ab7dec67f..30d5b705b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -334,7 +334,8 @@ int git_blob_is_binary(const git_blob *blob) assert(blob); content.ptr = blob->odb_object->buffer; - content.size = min(blob->odb_object->cached.size, 4000); + content.size = + min(blob->odb_object->cached.size, GIT_FILTER_BYTES_TO_CHECK_NUL); content.asize = 0; return git_buf_text_is_binary(&content); diff --git a/src/clone.c b/src/clone.c index c6be00f0e..6c4fb6727 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,6 +22,7 @@ #include "refs.h" #include "path.h" #include "repository.h" +#include "odb.h" static int create_branch( git_reference **branch, @@ -105,54 +106,6 @@ static int create_tracking_branch( git_reference_name(*branch)); } -struct head_info { - git_repository *repo; - git_oid remote_head_oid; - git_buf branchname; - const git_refspec *refspec; - bool found; -}; - -static int reference_matches_remote_head( - const char *reference_name, - void *payload) -{ - struct head_info *head_info = (struct head_info *)payload; - git_oid oid; - int error; - - /* TODO: Should we guard against references - * which name doesn't start with refs/heads/ ? - */ - - error = git_reference_name_to_id(&oid, head_info->repo, reference_name); - if (error == GIT_ENOTFOUND) { - /* If the reference doesn't exists, it obviously cannot match the - * expected oid. */ - giterr_clear(); - return 0; - } - - if (!error && !git_oid__cmp(&head_info->remote_head_oid, &oid)) { - /* Determine the local reference name from the remote tracking one */ - error = git_refspec_rtransform( - &head_info->branchname, head_info->refspec, reference_name); - - if (!error && - git_buf_len(&head_info->branchname) > 0 && - !(error = git_buf_sets( - &head_info->branchname, - git_buf_cstr(&head_info->branchname) + - strlen(GIT_REFS_HEADS_DIR)))) - { - head_info->found = true; - error = GIT_ITEROVER; - } - } - - return error; -} - static int update_head_to_new_branch( git_repository *repo, const git_oid *target, @@ -161,7 +114,12 @@ static int update_head_to_new_branch( const char *reflog_message) { git_reference *tracking_branch = NULL; - int error = create_tracking_branch(&tracking_branch, repo, target, name, + int error; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + + error = create_tracking_branch(&tracking_branch, repo, target, name, signature, reflog_message); if (!error) @@ -171,6 +129,10 @@ static int update_head_to_new_branch( git_reference_free(tracking_branch); + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + return error; } @@ -180,12 +142,13 @@ static int update_head_to_remote( const git_signature *signature, const char *reflog_message) { - int error = 0; + int error = 0, found_branch = 0; size_t refs_len; - git_refspec dummy_spec; + git_refspec dummy_spec, *refspec; const git_remote_head *remote_head, **refs; - struct head_info head_info; + const git_oid *remote_head_id; git_buf remote_master_name = GIT_BUF_INIT; + git_buf branch = GIT_BUF_INIT; if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) return error; @@ -195,63 +158,45 @@ static int update_head_to_remote( return setup_tracking_config( repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE); + error = git_remote_default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE); + } else { + found_branch = 1; + } + /* Get the remote's HEAD. This is always the first ref in the list. */ remote_head = refs[0]; assert(remote_head); - memset(&head_info, 0, sizeof(head_info)); - git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); - head_info.repo = repo; - head_info.refspec = - git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE); + remote_head_id = &remote_head->oid; + refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch)); - if (head_info.refspec == NULL) { + if (refspec == NULL) { memset(&dummy_spec, 0, sizeof(git_refspec)); - head_info.refspec = &dummy_spec; + refspec = &dummy_spec; } /* Determine the remote tracking reference name from the local master */ if ((error = git_refspec_transform( &remote_master_name, - head_info.refspec, - GIT_REFS_HEADS_MASTER_FILE)) < 0) + refspec, + git_buf_cstr(&branch))) < 0) return error; - /* Check to see if the remote HEAD points to the remote master */ - error = reference_matches_remote_head( - git_buf_cstr(&remote_master_name), &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { - error = update_head_to_new_branch( - repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), - signature, reflog_message); - goto cleanup; - } - - /* Not master. Check all the other refs. */ - error = git_reference_foreach_name( - repo, reference_matches_remote_head, &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { + if (found_branch) { error = update_head_to_new_branch( repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), + remote_head_id, + git_buf_cstr(&branch), signature, reflog_message); } else { error = git_repository_set_head_detached( - repo, &head_info.remote_head_oid, signature, reflog_message); + repo, remote_head_id, signature, reflog_message); } -cleanup: git_buf_free(&remote_master_name); - git_buf_free(&head_info.branchname); + git_buf_free(&branch); return error; } @@ -297,6 +242,15 @@ static int create_and_configure_origin( int error; git_remote *origin = NULL; const char *name; + char buf[GIT_PATH_MAX]; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } name = options->remote_name ? options->remote_name : "origin"; if ((error = git_remote_create(&origin, repo, name, url)) < 0) @@ -336,59 +290,86 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature, const char *reflog_message) +{ + int error; + + if (branch) + error = update_head_to_branch(repo, git_remote_name(remote), branch, + signature, reflog_message); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote, signature, reflog_message); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + + return error; +} + +int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { - int error = 0, old_fetchhead; - git_strarray refspecs; + int error; git_buf reflog_message = GIT_BUF_INIT; + git_remote *remote; + const git_remote_callbacks *callbacks; - assert(repo && remote); + assert(repo && _remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); return -1; } - - if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0) + if ((error = git_remote_dup(&remote, _remote)) < 0) return error; + callbacks = git_remote_get_callbacks(_remote); + if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && + (error = git_remote_set_callbacks(remote, callbacks)) < 0) + goto cleanup; + if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) - return error; + goto cleanup; - old_fetchhead = git_remote_update_fetchhead(remote); git_remote_set_update_fetchhead(remote, 0); git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) goto cleanup; - if (branch) - error = update_head_to_branch(repo, git_remote_name(remote), branch, - signature, git_buf_cstr(&reflog_message)); - /* Point HEAD to the same ref as the remote's head */ - else - error = update_head_to_remote(repo, remote, signature, git_buf_cstr(&reflog_message)); - - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); cleanup: - git_remote_set_update_fetchhead(remote, old_fetchhead); - - /* Go back to the original refspecs */ - { - int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs); - if (!error) - error = error_alt; - } - - git_strarray_free(&refspecs); + git_remote_free(remote); git_buf_free(&reflog_message); return error; } +int git_clone__should_clone_local(const char *url, git_clone_local_t local) +{ + const char *path; + int is_url; + + if (local == GIT_CLONE_NO_LOCAL) + return false; + + is_url = !git__prefixcmp(url, "file://"); + + if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) + return false; + + path = url; + if (is_url) + path = url + strlen("file://"); + + if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) + return true; + + return false; +} + int git_clone( git_repository **out, const char *url, @@ -423,8 +404,16 @@ int git_clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - error = git_clone_into( - repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); + if (git_clone__should_clone_local(url, options.local)) { + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; + error = git_clone_local_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, link, options.signature); + } else { + error = git_clone_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, options.signature); + } git_remote_free(origin); } @@ -451,3 +440,91 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version) opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); return 0; } + +static const char *repository_base(git_repository *repo) +{ + if (git_repository_is_bare(repo)) + return git_repository_path(repo); + + return git_repository_workdir(repo); +} + +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) +{ + int error, flags; + git_repository *src; + git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; + git_buf reflog_message = GIT_BUF_INIT; + + assert(repo && remote); + + if (!git_repository_is_empty(repo)) { + giterr_set(GITERR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) { + git_buf_free(&src_path); + return error; + } + + git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR); + git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR); + if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) { + error = -1; + goto cleanup; + } + + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + + if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE)) < 0) + goto cleanup; + + git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); + +cleanup: + git_buf_free(&reflog_message); + git_buf_free(&src_path); + git_buf_free(&src_odb); + git_buf_free(&dst_odb); + git_repository_free(src); + return error; +} diff --git a/src/clone.h b/src/clone.h new file mode 100644 index 000000000..14ca5d44c --- /dev/null +++ b/src/clone.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_clone_h__ +#define INCLUDE_clone_h__ + +extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); + +#endif diff --git a/src/config.c b/src/config.c index 757ff0387..8a0fb653c 100644 --- a/src/config.c +++ b/src/config.c @@ -139,7 +139,7 @@ int git_config_open_ondisk(git_config **out, const char *path) int git_config_snapshot(git_config **out, git_config *in) { - int error; + int error = 0; size_t i; file_internal *internal; git_config *config; @@ -153,19 +153,19 @@ int git_config_snapshot(git_config **out, git_config *in) git_config_backend *b; if ((error = internal->file->snapshot(&b, internal->file)) < 0) - goto on_error; + break; if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) { b->free(b); - goto on_error; + break; } } - *out = config; - return error; + if (error < 0) + git_config_free(config); + else + *out = config; -on_error: - git_config_free(config); return error; } diff --git a/src/config.h b/src/config.h index 00b6063e7..b0dcb49ac 100644 --- a/src/config.h +++ b/src/config.h @@ -76,4 +76,10 @@ extern int git_config__get_bool_force( extern int git_config__get_int_force( const git_config *cfg, const char *key, int fallback_value); +/* API for repository cvar-style lookups from config - not cached, but + * uses cvar value maps and fallbacks + */ +extern int git_config__cvar( + int *out, git_config *config, git_cvar_cached cvar); + #endif diff --git a/src/config_cache.c b/src/config_cache.c index 4bcbf02bf..45c39ce17 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -7,11 +7,11 @@ #include "common.h" #include "fileops.h" +#include "repository.h" #include "config.h" #include "git2/config.h" #include "vector.h" #include "filter.h" -#include "repository.h" struct map_data { const char *cvar_name; @@ -51,6 +51,12 @@ static git_cvar_map _cvar_map_autocrlf[] = { {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} }; +static git_cvar_map _cvar_map_safecrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, + {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN} +}; + /* * Generic map for integer values */ @@ -68,33 +74,39 @@ static struct map_data _cvar_maps[] = { {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, - {"core.safecrlf", NULL, 0, GIT_SAFE_CRLF_DEFAULT}, + {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, + {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, }; +int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) +{ + int error = 0; + struct map_data *data = &_cvar_maps[(int)cvar]; + const git_config_entry *entry; + + git_config__lookup_entry(&entry, config, data->cvar_name, false); + + if (!entry) + *out = data->default_value; + else if (data->maps) + error = git_config_lookup_map_value( + out, data->maps, data->map_count, entry->value); + else + error = git_config_parse_bool(out, entry->value); + + return error; +} + int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) { *out = repo->cvar_cache[(int)cvar]; if (*out == GIT_CVAR_NOT_CACHED) { - struct map_data *data = &_cvar_maps[(int)cvar]; - git_config *config; int error; - const git_config_entry *entry; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - git_config__lookup_entry(&entry, config, data->cvar_name, false); - - if (!entry) - *out = data->default_value; - else if (data->maps) - error = git_config_lookup_map_value( - out, data->maps, data->map_count, entry->value); - else - error = git_config_parse_bool(out, entry->value); + git_config *config; - if (error < 0) + if ((error = git_repository_config__weakptr(&config, repo)) < 0 || + (error = git_config__cvar(out, config, cvar)) < 0) return error; repo->cvar_cache[(int)cvar] = *out; diff --git a/src/config_file.c b/src/config_file.c index b00d0827d..56271144b 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -270,7 +270,6 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) if ((res = refcounted_strmap_alloc(&b->header.values)) < 0) return res; - git_mutex_init(&b->header.values_mutex); git_array_init(b->readers); reader = git_array_alloc(b->readers); if (!reader) { @@ -313,6 +312,7 @@ static int config__refresh(git_config_backend *cfg) goto out; reader = git_array_get(b->readers, git_array_size(b->readers) - 1); + GITERR_CHECK_ALLOC(reader); if ((error = config_parse(values->values, b, reader, b->level, 0)) < 0) goto out; @@ -327,7 +327,8 @@ static int config__refresh(git_config_backend *cfg) out: refcounted_strmap_free(values); - git_buf_free(&reader->buffer); + if (reader) + git_buf_free(&reader->buffer); return error; } @@ -344,8 +345,8 @@ static int config_refresh(git_config_backend *cfg) &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated); - if (error < 0) - return (error == GIT_ENOTFOUND) ? 0 : error; + if (error < 0 && error != GIT_ENOTFOUND) + return error; if (updated) any_updated = 1; @@ -373,6 +374,7 @@ static void backend_free(git_config_backend *_backend) git__free(backend->file_path); refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -684,6 +686,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->file_path = git__strdup(path); GITERR_CHECK_ALLOC(backend->file_path); @@ -756,6 +759,7 @@ static void backend_readonly_free(git_config_backend *_backend) return; refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -782,6 +786,7 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->snapshot_from = in; diff --git a/src/crlf.c b/src/crlf.c index dad3ecebc..821e04eb2 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -138,6 +138,10 @@ static int crlf_apply_to_odb( if (git_buf_text_gather_stats(&stats, from, false)) return GIT_PASSTHROUGH; + /* If there are no CR characters to filter out, then just pass */ + if (!stats.cr) + return GIT_PASSTHROUGH; + /* If safecrlf is enabled, sanity-check the result. */ if (stats.cr != stats.crlf || stats.lf != stats.crlf) { switch (ca->safe_crlf) { diff --git a/src/diff.c b/src/diff.c index b55d123c1..2691d7ca0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -390,7 +390,7 @@ static int diff_list_apply_options( git_diff *diff, const git_diff_options *opts) { - git_config *cfg; + git_config *cfg = NULL; git_repository *repo = diff->repo; git_pool *pool = &diff->pool; int val; @@ -415,20 +415,20 @@ static int diff_list_apply_options( diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* load config values that affect diff behavior */ - if ((val = git_repository_config__weakptr(&cfg, repo)) < 0) + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) return val; - if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val) + !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_TRUSTCTIME) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ @@ -490,8 +490,6 @@ static int diff_list_apply_options( /* strdup prefix from pool so we're not dependent on external data */ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix); diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix); - if (!diff->opts.old_prefix || !diff->opts.new_prefix) - return -1; if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { const char *tmp_prefix = diff->opts.old_prefix; @@ -499,7 +497,10 @@ static int diff_list_apply_options( diff->opts.new_prefix = tmp_prefix; } - return 0; + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0; } static void diff_list_free(git_diff *diff) @@ -642,7 +643,6 @@ typedef struct { git_iterator *new_iter; const git_index_entry *oitem; const git_index_entry *nitem; - git_buf ignore_prefix; } diff_in_progress; #define MODE_BITS_MASK 0000777 @@ -858,24 +858,13 @@ static int handle_unmatched_new_item( /* check if this is a prefix of the other side */ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - /* check if this is contained in an ignored parent directory */ - if (git_buf_len(&info->ignore_prefix)) { - if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0) - delta_type = GIT_DELTA_IGNORED; - else - git_buf_clear(&info->ignore_prefix); - } + /* update delta_type if this item is ignored */ + if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; if (nitem->mode == GIT_FILEMODE_TREE) { bool recurse_into_dir = contains_oitem; - /* if not already inside an ignored dir, check if this is ignored */ - if (delta_type != GIT_DELTA_IGNORED && - git_iterator_current_is_ignored(info->new_iter)) { - delta_type = GIT_DELTA_IGNORED; - git_buf_sets(&info->ignore_prefix, nitem->path); - } - /* check if user requests recursion into this type of dir */ recurse_into_dir = contains_oitem || (delta_type == GIT_DELTA_UNTRACKED && @@ -952,27 +941,12 @@ static int handle_unmatched_new_item( } } - /* In core git, the next two checks are effectively reversed -- - * i.e. when an file contained in an ignored directory is explicitly - * ignored, it shows up as an ignored file in the diff list, even though - * other untracked files in the same directory are skipped completely. - * - * To me, this seems odd. If the directory is ignored and the file is - * untracked, we should skip it consistently, regardless of whether it - * happens to match a pattern in the ignore file. - * - * To match the core git behavior, reverse the following two if checks - * so that individual file ignores are checked before container - * directory exclusions are used to skip the file. - */ else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) /* item contained in ignored directory, so skip over it */ return git_iterator_advance(&info->nitem, info->new_iter); - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; @@ -1089,7 +1063,6 @@ int git_diff__from_iterators( info.repo = repo; info.old_iter = old_iter; info.new_iter = new_iter; - git_buf_init(&info.ignore_prefix, 0); /* make iterators have matching icase behavior */ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { @@ -1144,8 +1117,6 @@ cleanup: else git_diff_free(diff); - git_buf_free(&info.ignore_prefix); - return error; } diff --git a/src/diff_driver.c b/src/diff_driver.c index fc1354f36..c3c5f365b 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -233,17 +233,17 @@ static int git_diff_driver_load( return 0; } + drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); + GITERR_CHECK_ALLOC(drv); + drv->type = DIFF_DRIVER_AUTO; + memcpy(drv->name, driver_name, namelen); + /* if you can't read config for repo, just use default driver */ if (git_repository_config_snapshot(&cfg, repo) < 0) { giterr_clear(); goto done; } - drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); - GITERR_CHECK_ALLOC(drv); - drv->type = DIFF_DRIVER_AUTO; - memcpy(drv->name, driver_name, namelen); - if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0) goto done; @@ -397,7 +397,11 @@ void git_diff_driver_update_options( int git_diff_driver_content_is_binary( git_diff_driver *driver, const char *content, size_t content_len) { - const git_buf search = { (char *)content, 0, min(content_len, 4000) }; + git_buf search; + + search.ptr = (char *)content; + search.size = min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL); + search.asize = 0; GIT_UNUSED(driver); diff --git a/src/diff_print.c b/src/diff_print.c index 964c49540..32b109bb2 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -287,33 +287,46 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) { git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL; const void *old_data, *new_data; - size_t old_data_len, new_data_len, delta_data_len, inflated_len, remain; + git_off_t old_data_len, new_data_len; + unsigned long delta_data_len, inflated_len; const char *out_type = "literal"; - char *ptr; + char *scan, *end; int error; old_data = old ? git_blob_rawcontent(old) : NULL; new_data = new ? git_blob_rawcontent(new) : NULL; - old_data_len = old ? (size_t)git_blob_rawsize(old) : 0; - new_data_len = new ? (size_t)git_blob_rawsize(new) : 0; + old_data_len = old ? git_blob_rawsize(old) : 0; + new_data_len = new ? git_blob_rawsize(new) : 0; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len)) + return GIT_EBUFS; out = &deflate; - inflated_len = new_data_len; + inflated_len = (unsigned long)new_data_len; if ((error = git_zstream_deflatebuf( - &deflate, new_data, new_data_len)) < 0) + out, new_data, (size_t)new_data_len)) < 0) goto done; - if (old && new) { - void *delta_data; + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong((git_off_t)deflate.size)) { + error = GIT_EBUFS; + goto done; + } - delta_data = git_delta(old_data, old_data_len, new_data, - new_data_len, &delta_data_len, deflate.size); + if (old && new) { + void *delta_data = git_delta( + old_data, (unsigned long)old_data_len, + new_data, (unsigned long)new_data_len, + &delta_data_len, (unsigned long)deflate.size); if (delta_data) { - error = git_zstream_deflatebuf(&delta, delta_data, delta_data_len); - free(delta_data); + error = git_zstream_deflatebuf( + &delta, delta_data, (size_t)delta_data_len); + + git__free(delta_data); if (error < 0) goto done; @@ -326,18 +339,20 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) } } - git_buf_printf(pi->buf, "%s %" PRIuZ "\n", out_type, inflated_len); + git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len); pi->line.num_lines++; - for (ptr = out->ptr, remain = out->size; remain > 0; ) { - size_t chunk_len = (52 < remain) ? 52 : remain; + for (scan = out->ptr, end = out->ptr + out->size; scan < end; ) { + size_t chunk_len = end - scan; + if (chunk_len > 52) + chunk_len = 52; if (chunk_len <= 26) - git_buf_putc(pi->buf, chunk_len + 'A' - 1); + git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1); else - git_buf_putc(pi->buf, chunk_len - 26 + 'a' - 1); + git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - git_buf_put_base85(pi->buf, ptr, chunk_len); + git_buf_put_base85(pi->buf, scan, chunk_len); git_buf_putc(pi->buf, '\n'); if (git_buf_oom(pi->buf)) { @@ -345,8 +360,7 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) goto done; } - ptr += chunk_len; - remain -= chunk_len; + scan += chunk_len; pi->line.num_lines++; } @@ -365,26 +379,33 @@ static int diff_print_patch_file_binary( git_blob *old = NULL, *new = NULL; const git_oid *old_id, *new_id; int error; + size_t pre_binary_size; - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) { - pi->line.num_lines = 1; - return diff_delta_format_with_paths( - pi->buf, delta, oldpfx, newpfx, - "Binary files %s%s and %s%s differ\n"); - } + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) + goto noshow; + pre_binary_size = pi->buf->size; git_buf_printf(pi->buf, "GIT binary patch\n"); pi->line.num_lines++; old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL; new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL; - if ((old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) || - (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) || - (error = print_binary_hunk(pi, old, new)) < 0 || + if (old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) + goto done; + if (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) + goto done; + + if ((error = print_binary_hunk(pi, old, new)) < 0 || (error = git_buf_putc(pi->buf, '\n')) < 0 || (error = print_binary_hunk(pi, new, old)) < 0) - goto done; + { + if (error == GIT_EBUFS) { + giterr_clear(); + git_buf_truncate(pi->buf, pre_binary_size); + goto noshow; + } + } pi->line.num_lines++; @@ -393,6 +414,12 @@ done: git_blob_free(new); return error; + +noshow: + pi->line.num_lines = 1; + return diff_delta_format_with_paths( + pi->buf, delta, oldpfx, newpfx, + "Binary files %s%s and %s%s differ\n"); } static int diff_print_patch_file( diff --git a/src/fileops.c b/src/fileops.c index 13b8f6a39..bebbae4f9 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -740,9 +740,11 @@ static int _cp_r_callback(void *ref, git_buf *from) return error; /* make symlink or regular file */ - if (S_ISLNK(from_st.st_mode)) + if (info->flags & GIT_CPDIR_LINK_FILES) { + error = p_link(from->ptr, info->to.ptr); + } else if (S_ISLNK(from_st.st_mode)) { error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - else { + } else { mode_t usemode = from_st.st_mode; if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) diff --git a/src/fileops.h b/src/fileops.h index 62227abae..4f5700a99 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -173,6 +173,7 @@ extern int git_futils_cp( * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the * source file to the target; with this flag, always use 0666 (or 0777 if * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files */ typedef enum { GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), @@ -181,6 +182,7 @@ typedef enum { GIT_CPDIR_OVERWRITE = (1u << 3), GIT_CPDIR_CHMOD_DIRS = (1u << 4), GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6), } git_futils_cpdir_flags; /** diff --git a/src/filter.h b/src/filter.h index d0ace0f9a..5a366108b 100644 --- a/src/filter.h +++ b/src/filter.h @@ -10,6 +10,10 @@ #include "common.h" #include "git2/filter.h" +/* Amount of file to examine for NUL byte when checking binary-ness */ +#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 + +/* Possible CRLF values */ typedef enum { GIT_CRLF_GUESS = -1, GIT_CRLF_BINARY = 0, diff --git a/src/fnmatch.c b/src/fnmatch.c index d8a83a8ed..d7899e3e6 100644 --- a/src/fnmatch.c +++ b/src/fnmatch.c @@ -6,6 +6,40 @@ */ /* + * This file contains code originally derrived from OpenBSD fnmatch.c + * + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. * Compares a filename or pathname to a pattern. */ diff --git a/src/fnmatch.h b/src/fnmatch.h index 920e7de4d..88af45939 100644 --- a/src/fnmatch.h +++ b/src/fnmatch.h @@ -1,8 +1,29 @@ /* - * Copyright (C) the libgit2 contributors. All rights reserved. + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ #ifndef INCLUDE_fnmatch__compat_h__ #define INCLUDE_fnmatch__compat_h__ diff --git a/src/global.c b/src/global.c index 4dfdcf438..7da31853e 100644 --- a/src/global.c +++ b/src/global.c @@ -76,7 +76,7 @@ static void git__shutdown(void) #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; -static DWORD _mutex = 0; +static volatile LONG _mutex = 0; static int synchronized_threads_init() { diff --git a/src/ignore.c b/src/ignore.c index f373c9482..78f01ac44 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -248,14 +248,15 @@ void git_ignore__free(git_ignores *ignores) } static bool ignore_lookup_in_rules( - git_attr_file *file, git_attr_path *path, int *ignored) + int *ignored, git_attr_file *file, git_attr_path *path) { size_t j; git_attr_fnmatch *match; git_vector_rforeach(&file->rules, j, match) { if (git_attr_fnmatch__match(match, path)) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? + GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; return true; } } @@ -264,34 +265,34 @@ static bool ignore_lookup_in_rules( } int git_ignore__lookup( - git_ignores *ignores, const char *pathname, int *ignored) + int *out, git_ignores *ignores, const char *pathname) { unsigned int i; git_attr_file *file; git_attr_path path; + *out = GIT_IGNORE_NOTFOUND; + if (git_attr_path__init( &path, pathname, git_repository_workdir(ignores->repo)) < 0) return -1; /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores->ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores->ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores->ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } - *ignored = 0; - cleanup: git_attr_path__free(&path); return 0; @@ -335,8 +336,6 @@ int git_ignore_path_is_ignored( int error; const char *workdir; git_attr_path path; - char *tail, *end; - bool full_is_dir; git_ignores ignores; unsigned int i; git_attr_file *file; @@ -345,55 +344,42 @@ int git_ignore_path_is_ignored( workdir = repo ? git_repository_workdir(repo) : NULL; - if ((error = git_attr_path__init(&path, pathname, workdir)) < 0) - return error; + memset(&path, 0, sizeof(path)); + memset(&ignores, 0, sizeof(ignores)); - tail = path.path; - end = &path.full.ptr[path.full.size]; - full_is_dir = path.is_dir; + if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 || + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) + goto cleanup; while (1) { - /* advance to next component of path */ - path.basename = tail; - - while (tail < end && *tail != '/') tail++; - *tail = '\0'; - - path.full.size = (tail - path.full.ptr); - path.is_dir = (tail == end) ? full_is_dir : true; - - /* initialize ignores the first time through */ - if (path.basename == path.path && - (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) - break; - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores.ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores.ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores.ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } - /* if we found no rules before reaching the end, we're done */ - if (tail == end) + /* move up one directory */ + if (path.basename == path.path) break; - - /* now add this directory to list of ignores */ - if ((error = git_ignore__push_dir(&ignores, path.path)) < 0) + path.basename[-1] = '\0'; + while (path.basename > path.path && *path.basename != '/') + path.basename--; + if (path.basename > path.path) + path.basename++; + path.is_dir = 1; + + if ((error = git_ignore__pop_dir(&ignores)) < 0) break; - - /* reinstate divider in path */ - *tail = '/'; - while (*tail == '/') tail++; } *ignored = 0; @@ -404,7 +390,6 @@ cleanup: return error; } - int git_ignore__check_pathspec_for_exact_ignores( git_repository *repo, git_vector *vspec, diff --git a/src/ignore.h b/src/ignore.h index ff9369000..77668c661 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -42,7 +42,14 @@ extern int git_ignore__pop_dir(git_ignores *ign); extern void git_ignore__free(git_ignores *ign); -extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); +enum { + GIT_IGNORE_UNCHECKED = -2, + GIT_IGNORE_NOTFOUND = -1, + GIT_IGNORE_FALSE = 0, + GIT_IGNORE_TRUE = 1, +}; + +extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path); /* command line Git sometimes generates an error message if given a * pathspec that contains an exact match to an ignored file (provided diff --git a/src/index.c b/src/index.c index 8a7f29279..b63a0bec6 100644 --- a/src/index.c +++ b/src/index.c @@ -1104,6 +1104,15 @@ int git_index_remove_bypath(git_index *index, const char *path) return 0; } +static bool valid_filemode(const int filemode) +{ + return (filemode == GIT_FILEMODE_BLOB || + filemode == GIT_FILEMODE_BLOB_EXECUTABLE || + filemode == GIT_FILEMODE_LINK || + filemode == GIT_FILEMODE_COMMIT); +} + + int git_index_add(git_index *index, const git_index_entry *source_entry) { git_index_entry *entry = NULL; @@ -1111,6 +1120,11 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) assert(index && source_entry && source_entry->path); + if (!valid_filemode(source_entry->mode)) { + giterr_set(GITERR_INDEX, "invalid filemode"); + return -1; + } + if ((ret = index_entry_dup(&entry, source_entry)) < 0 || (ret = index_insert(index, &entry, 1)) < 0) return ret; diff --git a/src/indexer.c b/src/indexer.c index 3c8415c7c..25c3d0537 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -34,7 +34,6 @@ struct git_indexer { have_delta :1; struct git_pack_header hdr; struct git_pack_file *pack; - git_filebuf pack_file; unsigned int mode; git_off_t off; git_off_t entry_start; @@ -67,33 +66,18 @@ const git_oid *git_indexer_hash(const git_indexer *idx) return &idx->hash; } -static int open_pack(struct git_pack_file **out, const char *filename) -{ - struct git_pack_file *pack; - - if (git_packfile_alloc(&pack, filename) < 0) - return -1; - - if ((pack->mwf.fd = p_open(pack->pack_name, O_RDONLY)) < 0) { - giterr_set(GITERR_OS, "Failed to open packfile."); - git_packfile_free(pack); - return -1; - } - - *out = pack; - return 0; -} - static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) { int error; + git_map map; - /* Verify we recognize this pack file format. */ - if ((error = p_read(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { - giterr_set(GITERR_OS, "Failed to read in pack header"); + if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) return error; - } + memcpy(hdr, map.data, sizeof(*hdr)); + p_munmap(&map); + + /* Verify we recognize this pack file format. */ if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { giterr_set(GITERR_INDEXER, "Wrong pack signature"); return -1; @@ -124,9 +108,9 @@ int git_indexer_new( void *progress_payload) { git_indexer *idx; - git_buf path = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT; static const char suff[] = "/pack"; - int error; + int error, fd = -1; idx = git__calloc(1, sizeof(git_indexer)); GITERR_CHECK_ALLOC(idx); @@ -140,19 +124,30 @@ int git_indexer_new( if (error < 0) goto cleanup; - error = git_filebuf_open(&idx->pack_file, path.ptr, - GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER, - idx->mode); + fd = git_futils_mktmp(&tmp_path, git_buf_cstr(&path), idx->mode); git_buf_free(&path); + if (fd < 0) + goto cleanup; + + error = git_packfile_alloc(&idx->pack, git_buf_cstr(&tmp_path)); + git_buf_free(&tmp_path); + if (error < 0) goto cleanup; + idx->pack->mwf.fd = fd; + if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) + goto cleanup; + *out = idx; return 0; cleanup: + if (fd != -1) + p_close(fd); + git_buf_free(&path); - git_filebuf_cleanup(&idx->pack_file); + git_buf_free(&tmp_path); git__free(idx); return -1; } @@ -429,6 +424,42 @@ static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) idx->inbuf_len += size - to_expell; } +static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size) +{ + git_file fd = idx->pack->mwf.fd; + long page_size = git__page_size(); + git_off_t page_start, page_offset; + unsigned char *map_data; + git_map map; + int error; + + /* the offset needs to be at the beginning of the a page boundary */ + page_start = (offset / page_size) * page_size; + page_offset = offset - page_start; + + if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) + return error; + + map_data = (unsigned char *)map.data; + memcpy(map_data + page_offset, data, size); + p_munmap(&map); + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + git_off_t current_size = idx->pack->mwf.size; + + /* add the extra space we need at the end */ + if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) { + giterr_system_set(errno); + return -1; + } + + return write_at(idx, data, idx->pack->mwf.size, size); +} + int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats) { int error = -1; @@ -440,22 +471,13 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_tran processed = stats->indexed_objects; - if ((error = git_filebuf_write(&idx->pack_file, data, size)) < 0) + if ((error = append_to_pack(idx, data, size)) < 0) return error; hash_partially(idx, data, (int)size); /* Make sure we set the new size of the pack */ - if (idx->opened_pack) { - idx->pack->mwf.size += size; - } else { - if ((error = open_pack(&idx->pack, idx->pack_file.path_lock)) < 0) - return error; - idx->opened_pack = 1; - mwf = &idx->pack->mwf; - if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) - return error; - } + idx->pack->mwf.size += size; if (!idx->parsed_header) { unsigned int total_objects; @@ -616,17 +638,10 @@ static int index_path(git_buf *path, git_indexer *idx, const char *suffix) * Rewind the packfile by the trailer, as we might need to fix the * packfile by injecting objects at the tail and must overwrite it. */ -static git_off_t seek_back_trailer(git_indexer *idx) +static void seek_back_trailer(git_indexer *idx) { - git_off_t off; - - if ((off = p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_CUR)) < 0) - return -1; - idx->pack->mwf.size -= GIT_OID_RAWSZ; git_mwindow_free_all(&idx->pack->mwf); - - return off; } static int inject_object(git_indexer *idx, git_oid *id) @@ -642,7 +657,8 @@ static int inject_object(git_indexer *idx, git_oid *id) size_t len, hdr_len; int error; - entry_start = seek_back_trailer(idx); + seek_back_trailer(idx); + entry_start = idx->pack->mwf.size; if (git_odb_read(&obj, idx->odb, id) < 0) return -1; @@ -657,7 +673,9 @@ static int inject_object(git_indexer *idx, git_oid *id) /* Write out the object header */ hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj)); - git_filebuf_write(&idx->pack_file, hdr, hdr_len); + if ((error = append_to_pack(idx, hdr, hdr_len)) < 0) + goto cleanup; + idx->pack->mwf.size += hdr_len; entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); @@ -665,13 +683,16 @@ static int inject_object(git_indexer *idx, git_oid *id) goto cleanup; /* And then the compressed object */ - git_filebuf_write(&idx->pack_file, buf.ptr, buf.size); + if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) + goto cleanup; + idx->pack->mwf.size += buf.size; entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); git_buf_free(&buf); /* Write a fake trailer so the pack functions play ball */ - if ((error = git_filebuf_write(&idx->pack_file, &foo, GIT_OID_RAWSZ)) < 0) + + if ((error = append_to_pack(idx, &foo, GIT_OID_RAWSZ)) < 0) goto cleanup; idx->pack->mwf.size += GIT_OID_RAWSZ; @@ -703,7 +724,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) size_t size; git_otype type; git_mwindow *w = NULL; - git_off_t curpos; + git_off_t curpos = 0; unsigned char *base_info; unsigned int left = 0; git_oid base; @@ -817,19 +838,12 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta ctx = &idx->trailer; git_hash_ctx_init(ctx); - git_mwindow_free_all(mwf); + /* Update the header to include the numer of local objects we injected */ idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); - if (p_lseek(idx->pack_file.fd, 0, SEEK_SET) < 0) { - giterr_set(GITERR_OS, "failed to seek to the beginning of the pack"); + if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) return -1; - } - - if (p_write(idx->pack_file.fd, &idx->hdr, sizeof(struct git_pack_header)) < 0) { - giterr_set(GITERR_OS, "failed to update the pack header"); - return -1; - } /* * We now use the same technique as before to determine the @@ -837,6 +851,7 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta * hash_partially() keep the existing trailer out of the * calculation. */ + git_mwindow_free_all(mwf); idx->inbuf_len = 0; while (hashed < mwf->size) { ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); @@ -906,13 +921,7 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) return -1; git_hash_final(&trailer_hash, &idx->trailer); - if (p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_END) < 0) - return -1; - - if (p_write(idx->pack_file.fd, &trailer_hash, GIT_OID_RAWSZ) < 0) { - giterr_set(GITERR_OS, "failed to update pack trailer"); - return -1; - } + write_at(idx, &trailer_hash, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ); } git_vector_sort(&idx->objects); @@ -995,14 +1004,18 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_mwindow_free_all(&idx->pack->mwf); /* We need to close the descriptor here so Windows doesn't choke on commit_at */ - p_close(idx->pack->mwf.fd); + if (p_close(idx->pack->mwf.fd) < 0) { + giterr_set(GITERR_OS, "failed to close packfile"); + goto on_error; + } + idx->pack->mwf.fd = -1; if (index_path(&filename, idx, ".pack") < 0) goto on_error; + /* And don't forget to rename the packfile to its new place. */ - if (git_filebuf_commit_at(&idx->pack_file, filename.ptr) < 0) - return -1; + p_rename(idx->pack->pack_name, git_buf_cstr(&filename)); git_buf_free(&filename); return 0; @@ -1022,7 +1035,7 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->objects); - if (idx->pack) { + if (idx->pack && idx->pack->idx_cache) { struct git_pack_entry *pentry; kh_foreach_value( idx->pack->idx_cache, pentry, { git__free(pentry); }); @@ -1032,6 +1045,5 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->deltas); git_packfile_free(idx->pack); - git_filebuf_cleanup(&idx->pack_file); git__free(idx); } diff --git a/src/iterator.c b/src/iterator.c index 4f8087c8d..c664f17cd 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -897,6 +897,7 @@ struct fs_iterator_frame { fs_iterator_frame *next; git_vector entries; size_t index; + int is_ignored; }; typedef struct fs_iterator fs_iterator; @@ -1290,16 +1291,28 @@ GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path) static int workdir_iterator__enter_dir(fs_iterator *fi) { + workdir_iterator *wi = (workdir_iterator *)fi; fs_iterator_frame *ff = fi->stack; size_t pos; git_path_with_stat *entry; bool found_submodules = false; - /* only push new ignores if this is not top level directory */ + /* check if this directory is ignored */ + if (git_ignore__lookup( + &ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) { + giterr_clear(); + ff->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ if (ff->next != NULL) { - workdir_iterator *wi = (workdir_iterator *)fi; ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/'); + /* inherit ignored from parent if no rule specified */ + if (ff->is_ignored <= GIT_IGNORE_NOTFOUND) + ff->is_ignored = ff->next->is_ignored; + + /* push new ignores for files in this directory */ (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]); } @@ -1342,7 +1355,7 @@ static int workdir_iterator__update_entry(fs_iterator *fi) return GIT_ENOTFOUND; /* reset is_ignored since we haven't checked yet */ - wi->is_ignored = -1; + wi->is_ignored = GIT_IGNORE_UNCHECKED; return 0; } @@ -1487,6 +1500,19 @@ int git_iterator_current_parent_tree( return 0; } +static void workdir_iterator_update_is_ignored(workdir_iterator *wi) +{ + if (git_ignore__lookup( + &wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) { + giterr_clear(); + wi->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (wi->is_ignored <= GIT_IGNORE_NOTFOUND) + wi->is_ignored = wi->fi.stack->is_ignored; +} + bool git_iterator_current_is_ignored(git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; @@ -1494,14 +1520,22 @@ bool git_iterator_current_is_ignored(git_iterator *iter) if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) return false; - if (wi->is_ignored != -1) - return (bool)(wi->is_ignored != 0); + if (wi->is_ignored != GIT_IGNORE_UNCHECKED) + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); + + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_tree_is_ignored(git_iterator *iter) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) + return false; - return (bool)wi->is_ignored; + return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE); } int git_iterator_cmp(git_iterator *iter, const char *path_prefix) @@ -1549,10 +1583,8 @@ int git_iterator_advance_over_with_status( return error; if (!S_ISDIR(entry->mode)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; - if (wi->is_ignored) + workdir_iterator_update_is_ignored(wi); + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; return git_iterator_advance(entryptr, iter); } @@ -1564,14 +1596,12 @@ int git_iterator_advance_over_with_status( /* scan inside directory looking for a non-ignored item */ while (entry && !iter->prefixcomp(entry->path, base)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); /* if we found an explicitly ignored item, then update from * EMPTY to IGNORED */ - if (wi->is_ignored) + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; else if (S_ISDIR(entry->mode)) { error = git_iterator_advance_into(&entry, iter); @@ -1580,7 +1610,7 @@ int git_iterator_advance_over_with_status( continue; else if (error == GIT_ENOTFOUND) { error = 0; - wi->is_ignored = true; /* mark empty directories as ignored */ + wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ } else break; /* real error, stop here */ } else { diff --git a/src/iterator.h b/src/iterator.h index f67830212..d88ad5191 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -245,6 +245,8 @@ extern int git_iterator_current_parent_tree( extern bool git_iterator_current_is_ignored(git_iterator *iter); +extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); + extern int git_iterator_cmp( git_iterator *iter, const char *path_prefix); @@ -42,5 +42,6 @@ typedef struct { /* memory mapped buffer */ extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); extern int p_munmap(git_map *map); +extern long git__page_size(void); #endif /* INCLUDE_map_h__ */ diff --git a/src/merge.c b/src/merge.c index 6a8e5874f..a279d31d4 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,8 +2564,42 @@ done: return error; } +static int merge_preference(git_merge_preference_t *out, git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + *out = GIT_MERGE_PREFERENCE_NONE; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + if (!bool_value) + *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; + } else { + if (strcasecmp(value, "only") == 0) + *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; + } + +done: + git_config_free(config); + return error; +} + int git_merge_analysis( - git_merge_analysis_t *out, + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len) @@ -2573,14 +2607,7 @@ int git_merge_analysis( git_merge_head *ancestor_head = NULL, *our_head = NULL; int error = 0; - assert(out && repo && their_heads); - - *out = GIT_MERGE_ANALYSIS_NONE; - - if (git_repository_head_unborn(repo)) { - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; - goto done; - } + assert(analysis_out && preference_out && repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "Can only merge a single branch"); @@ -2588,20 +2615,30 @@ int git_merge_analysis( goto done; } + *analysis_out = GIT_MERGE_ANALYSIS_NONE; + + if ((error = merge_preference(preference_out, repo)) < 0) + goto done; + + if (git_repository_head_unborn(repo)) { + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + goto done; + } + if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0) goto done; /* We're up-to-date if we're trying to merge our own common ancestor. */ if (ancestor_head && git_oid_equal(&ancestor_head->oid, &their_heads[0]->oid)) - *out = GIT_MERGE_ANALYSIS_UP_TO_DATE; + *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; /* We're fastforwardable if we're our own common ancestor. */ else if (ancestor_head && git_oid_equal(&ancestor_head->oid, &our_head->oid)) - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; /* Otherwise, just a normal merge is possible. */ else - *out = GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; done: git_merge_head_free(ancestor_head); diff --git a/src/message.c b/src/message.c index 07b2569ad..6c5a2379f 100644 --- a/src/message.c +++ b/src/message.c @@ -21,7 +21,7 @@ static size_t line_length_without_trailing_spaces(const char *line, size_t len) /* Greatly inspired from git.git "stripspace" */ /* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ -int git_message_prettify(git_buf *message_out, const char *message, int strip_comments) +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments, char comment_char) { const size_t message_len = strlen(message); @@ -40,7 +40,7 @@ int git_message_prettify(git_buf *message_out, const char *message, int strip_co line_length = message_len - i; } - if (strip_comments && line_length && message[i] == '#') + if (strip_comments && line_length && message[i] == comment_char) continue; rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); diff --git a/src/netops.c b/src/netops.c index 24092c17f..a0193a022 100644 --- a/src/netops.c +++ b/src/netops.c @@ -206,6 +206,8 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) return ret; } +#endif + /* Match host names according to RFC 2818 rules */ int gitno__match_host(const char *pattern, const char *host) { @@ -256,6 +258,8 @@ static int check_host_name(const char *name, const char *host) return 0; } +#ifdef GIT_SSL + static int verify_server_cert(gitno_ssl *ssl, const char *host) { X509 *cert; @@ -783,6 +783,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) return error; } + giterr_clear(); if ((object = odb_object__alloc(id, &raw)) == NULL) return -1; diff --git a/src/pack.c b/src/pack.c index de038a45c..ace7abb58 100644 --- a/src/pack.c +++ b/src/pack.c @@ -83,16 +83,12 @@ static void cache_free(git_pack_cache *cache) } git_offmap_free(cache->entries); - git_mutex_free(&cache->lock); + cache->entries = NULL; } - - memset(cache, 0, sizeof(*cache)); } static int cache_init(git_pack_cache *cache) { - memset(cache, 0, sizeof(*cache)); - cache->entries = git_offmap_alloc(); GITERR_CHECK_ALLOC(cache->entries); @@ -514,72 +510,102 @@ int git_packfile_resolve_header( return error; } -static int packfile_unpack_delta( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - git_off_t *curpos, - size_t delta_size, - git_otype delta_type, - git_off_t obj_offset) +#define SMALL_STACK_SIZE 64 + +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. + */ +static int pack_dependency_chain(git_dependency_chain *chain_out, + git_pack_cache_entry **cached_out, git_off_t *cached_off, + struct pack_chain_elem *small_stack, size_t *stack_sz, + struct git_pack_file *p, git_off_t obj_offset) { - git_off_t base_offset, base_key; - git_rawobj base, delta; - git_pack_cache_entry *cached = NULL; - int error, found_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + git_off_t curpos = obj_offset, base_offset; + int error = 0, use_heap = 0; + size_t size, elem_pos; + git_otype type; - base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); - git_mwindow_close(w_curs); - if (base_offset == 0) - return packfile_error("delta offset is zero"); - if (base_offset < 0) /* must actually be an error code */ - return (int)base_offset; + elem_pos = 0; + while (true) { + struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; - if (!p->bases.entries && (cache_init(&p->bases) < 0)) - return -1; + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + *cached_out = cached; + *cached_off = obj_offset; + break; + } - base_key = base_offset; /* git_packfile_unpack modifies base_offset */ - if ((cached = cache_get(&p->bases, base_offset)) != NULL) { - memcpy(&base, &cached->raw, sizeof(git_rawobj)); - found_base = 1; - } + /* if we run out of space on the small stack, use the array */ + if (elem_pos == SMALL_STACK_SIZE) { + git_array_init_to_size(chain, elem_pos); + GITERR_CHECK_ARRAY(chain); + memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); + chain.size = elem_pos; + use_heap = 1; + } + + curpos = obj_offset; + if (!use_heap) { + elem = &small_stack[elem_pos]; + } else { + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } + } - if (!cached) { /* have to inflate it */ - error = git_packfile_unpack(&base, p, &base_offset); + elem->base_key = obj_offset; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); - /* - * TODO: git.git tries to load the base from other packfiles - * or loose objects. - * - * We'll need to do this in order to support thin packs. - */ if (error < 0) - return error; - } + goto on_error; - error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); - git_mwindow_close(w_curs); + elem->offset = curpos; + elem->size = size; + elem->type = type; + elem->base_key = obj_offset; - if (error < 0) { - if (!found_base) - git__free(base.data); - return error; - } + if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA) + break; - obj->type = base.type; - error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); - if (error < 0) - goto on_error; + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); - if (found_base) - git_atomic_dec(&cached->refcount); - else if (cache_add(&p->bases, &base, base_key) < 0) - git__free(base.data); + if (base_offset == 0) { + error = packfile_error("delta offset is zero"); + goto on_error; + } + if (base_offset < 0) { /* must actually be an error code */ + error = (int)base_offset; + goto on_error; + } -on_error: - git__free(delta.data); + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + elem_pos++; + } + + + *stack_sz = elem_pos + 1; + *chain_out = chain; + return error; - return error; /* error set by git__delta_apply */ +on_error: + git_array_clear(chain); + return error; } int git_packfile_unpack( @@ -589,48 +615,138 @@ int git_packfile_unpack( { git_mwindow *w_curs = NULL; git_off_t curpos = *obj_offset; - int error; - - size_t size = 0; - git_otype type; + int error, free_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + struct pack_chain_elem *elem = NULL, *stack; + git_pack_cache_entry *cached = NULL; + struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; + size_t stack_size, elem_pos; + git_otype base_type; /* * TODO: optionally check the CRC on the packfile */ + error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); + if (error < 0) + return error; + obj->data = NULL; obj->len = 0; obj->type = GIT_OBJ_BAD; - error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); - git_mwindow_close(&w_curs); + /* let's point to the right stack */ + stack = chain.ptr ? chain.ptr : small_stack; - if (error < 0) - return error; + elem_pos = stack_size; + if (cached) { + memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + elem_pos--; /* stack_size includes the base, which isn't actually there */ + } else { + elem = &stack[--elem_pos]; + base_type = elem->type; + } - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - error = packfile_unpack_delta( - obj, p, &w_curs, &curpos, - size, type, *obj_offset); - break; + if (error < 0) + goto cleanup; + switch (base_type) { case GIT_OBJ_COMMIT: case GIT_OBJ_TREE: case GIT_OBJ_BLOB: case GIT_OBJ_TAG: - error = packfile_unpack_compressed( - obj, p, &w_curs, &curpos, - size, type); + if (!cached) { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + base_type = elem->type; + } + if (error < 0) + goto cleanup; break; - + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + error = packfile_error("dependency chain ends in a delta"); + goto cleanup; default: - error = packfile_error("invalid packfile type in header");; - break; + error = packfile_error("invalid packfile type in header"); + goto cleanup; + } + + /* + * Finding the object we want a cached base element is + * problematic, as we need to make sure we don't accidentally + * give the caller the cached object, which it would then feel + * free to free, so we need to copy the data. + */ + if (cached && stack_size == 1) { + void *data = obj->data; + obj->data = git__malloc(obj->len + 1); + GITERR_CHECK_ALLOC(obj->data); + memcpy(obj->data, data, obj->len + 1); + git_atomic_dec(&cached->refcount); + goto cleanup; } - *obj_offset = curpos; + /* we now apply each consecutive delta until we run out */ + while (elem_pos > 0 && !error) { + git_rawobj base, delta; + + /* + * We can now try to add the base to the cache, as + * long as it's not already the cached one. + */ + if (!cached) + free_base = !!cache_add(&p->bases, obj, elem->base_key); + + elem = &stack[elem_pos - 1]; + curpos = elem->offset; + error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + + if (error < 0) + break; + + /* the current object becomes the new base, on which we apply the delta */ + base = *obj; + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJ_BAD; + + error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + obj->type = base_type; + /* + * We usually don't want to free the base at this + * point, as we put it into the cache in the previous + * iteration. free_base lets us know that we got the + * base object directly from the packfile, so we can free it. + */ + git__free(delta.data); + if (free_base) { + free_base = 0; + git__free(base.data); + } + + if (cached) { + git_atomic_dec(&cached->refcount); + cached = NULL; + } + + if (error < 0) + break; + + elem_pos--; + } + +cleanup: + if (error < 0) + git__free(obj->data); + + if (elem) + *obj_offset = elem->offset; + + git_array_clear(chain); return error; } @@ -660,7 +776,7 @@ int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, st = inflateInit(&obj->zstream); if (st != Z_OK) { git__free(obj); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init packfile stream"); return -1; } @@ -691,7 +807,7 @@ ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t written = len - obj->zstream.avail_out; if (st != Z_OK && st != Z_STREAM_END) { - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error reading from the zlib stream"); return -1; } @@ -736,7 +852,7 @@ int packfile_unpack_compressed( st = inflateInit(&stream); if (st != Z_OK) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init zlib stream on unpack"); return -1; } @@ -763,7 +879,7 @@ int packfile_unpack_compressed( if ((st != Z_STREAM_END) || stream.total_out != size) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error inflating zlib stream"); return -1; } @@ -862,6 +978,7 @@ void git_packfile_free(struct git_pack_file *p) git__free(p->bad_object_sha1); git_mutex_free(&p->lock); + git_mutex_free(&p->bases.lock); git__free(p); } @@ -997,6 +1114,11 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) return -1; } + if (cache_init(&p->bases) < 0) { + git__free(p); + return -1; + } + *pack_out = p; return 0; diff --git a/src/pack.h b/src/pack.h index 58f81e2f0..610e70c18 100644 --- a/src/pack.h +++ b/src/pack.h @@ -17,6 +17,7 @@ #include "mwindow.h" #include "odb.h" #include "oidmap.h" +#include "array.h" #define GIT_PACK_FILE_MODE 0444 @@ -60,6 +61,15 @@ typedef struct git_pack_cache_entry { git_rawobj raw; } git_pack_cache_entry; +struct pack_chain_elem { + git_off_t base_key; + git_off_t offset; + size_t size; + git_otype type; +}; + +typedef git_array_t(struct pack_chain_elem) git_dependency_chain; + #include "offmap.h" GIT__USE_OFFMAP; diff --git a/src/path.c b/src/path.c index 0c1013fa0..377f396d3 100644 --- a/src/path.c +++ b/src/path.c @@ -1135,3 +1135,21 @@ int git_path_dirload_with_stat( return error; } + +int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) +{ + int error; + + /* If url_or_path begins with file:// treat it as a URL */ + if (!git__prefixcmp(url_or_path, "file://")) { + if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { + return error; + } + } else { /* We assume url_or_path is already a path */ + if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { + return error; + } + } + + return 0; +} diff --git a/src/path.h b/src/path.h index 3213c5104..3e6efe3de 100644 --- a/src/path.h +++ b/src/path.h @@ -438,4 +438,7 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); extern bool git_path_does_fs_decompose_unicode(const char *root); +/* Used for paths to repositories on the filesystem */ +extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path); + #endif diff --git a/src/posix.c b/src/posix.c index 7484ac0d8..7aeb0e6c1 100644 --- a/src/posix.c +++ b/src/posix.c @@ -207,6 +207,13 @@ int p_write(git_file fd, const void *buf, size_t cnt) #include "map.h" +long git__page_size(void) +{ + /* dummy; here we don't need any alignment anyway */ + return 4096; +} + + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { GIT_MMAP_VALIDATE(out, len, prot, flags); diff --git a/src/posix.h b/src/posix.h index f85b1aebd..965cd98d5 100644 --- a/src/posix.h +++ b/src/posix.h @@ -73,6 +73,7 @@ extern int p_rename(const char *from, const char *to); #define p_rmdir(p) rmdir(p) #define p_chmod(p,m) chmod(p, m) #define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) #define p_recv(s,b,l,f) recv(s,b,l,f) #define p_send(s,b,l,f) send(s,b,l,f) typedef int GIT_SOCKET; diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f9bd4eab5..0e36ca8ac 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -458,6 +458,7 @@ typedef struct { git_pool pool; git_vector loose; + git_sortedcache *cache; size_t loose_pos; size_t packed_pos; } refdb_fs_iter; @@ -468,6 +469,7 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) git_vector_free(&iter->loose); git_pool_clear(&iter->pool); + git_sortedcache_free(iter->cache); git__free(iter); } @@ -539,10 +541,14 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -556,7 +562,6 @@ static int refdb_fs_backend__iterator_next( break; } - git_sortedcache_runlock(backend->refcache); return error; } @@ -579,10 +584,14 @@ static int refdb_fs_backend__iterator_next_name( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -596,7 +605,6 @@ static int refdb_fs_backend__iterator_next_name( break; } - git_sortedcache_runlock(backend->refcache); return error; } @@ -927,19 +935,15 @@ static int has_reflog(git_repository *repo, const char *name); /* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */ static int should_write_reflog(int *write, git_repository *repo, const char *name) { - git_config *config; - int error, logall, is_bare; - - /* Defaults to the opposite of the repo being bare */ - is_bare = git_repository_is_bare(repo); - logall = !is_bare; + int error, logall; - if ((error = git_repository_config__weakptr(&config, repo)) < 0) + error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES); + if (error < 0) return error; - error = git_config_get_bool(&logall, config, "core.logallrefupdates"); - if (error < 0 && error != GIT_ENOTFOUND) - return error; + /* Defaults to the opposite of the repo being bare */ + if (logall == GIT_LOGALLREFUPDATES_UNSET) + logall = !git_repository_is_bare(repo); if (!logall) { *write = 0; diff --git a/src/refs.c b/src/refs.c index 9428f617d..1603876da 100644 --- a/src/refs.c +++ b/src/refs.c @@ -159,8 +159,7 @@ int git_reference_name_to_id( } static int reference_normalize_for_repo( - char *out, - size_t out_size, + git_refname_t out, git_repository *repo, const char *name) { @@ -171,7 +170,7 @@ static int reference_normalize_for_repo( precompose) flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE; - return git_reference_normalize_name(out, out_size, name, flags); + return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); } int git_reference_lookup_resolved( @@ -180,7 +179,7 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - char scan_name[GIT_REFNAME_MAX]; + git_refname_t scan_name; git_ref_t scan_type; int error = 0, nesting; git_reference *ref = NULL; @@ -197,8 +196,7 @@ int git_reference_lookup_resolved( scan_type = GIT_REF_SYMBOLIC; - if ((error = reference_normalize_for_repo( - scan_name, sizeof(scan_name), repo, name)) < 0) + if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0) return error; if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) @@ -354,7 +352,7 @@ static int reference__create( const git_oid *old_id, const char *old_target) { - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; git_refdb *refdb; git_reference *ref = NULL; int error = 0; @@ -365,7 +363,7 @@ static int reference__create( if (ref_out) *ref_out = NULL; - error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name); + error = reference_normalize_for_repo(normalized, repo, name); if (error < 0) return error; @@ -388,15 +386,14 @@ static int reference__create( return -1; } - ref = git_reference__alloc(name, oid, NULL); + ref = git_reference__alloc(normalized, oid, NULL); } else { - char normalized_target[GIT_REFNAME_MAX]; + git_refname_t normalized_target; - if ((error = git_reference__normalize_name_lax( - normalized_target, sizeof(normalized_target), symbolic)) < 0) + if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0) return error; - ref = git_reference__alloc_symbolic(name, normalized_target); + ref = git_reference__alloc_symbolic(normalized, normalized_target); } GITERR_CHECK_ALLOC(ref); @@ -569,18 +566,14 @@ int git_reference_symbolic_set_target( static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, const git_signature *signature, const char *message) { - unsigned int normalization_flags; - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; bool should_head_be_updated = false; int error = 0; assert(ref && new_name && signature); - normalization_flags = ref->type == GIT_REF_SYMBOLIC ? - GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; - - if ((error = git_reference_normalize_name( - normalized, sizeof(normalized), new_name, normalization_flags)) < 0) + if ((error = reference_normalize_for_repo( + normalized, git_reference_owner(ref), new_name)) < 0) return error; @@ -590,12 +583,12 @@ static int reference__rename(git_reference **out, git_reference *ref, const char should_head_be_updated = (error > 0); - if ((error = git_refdb_rename(out, ref->db, ref->name, new_name, force, signature, message)) < 0) + 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, new_name, signature, message)) < 0) { + (error = git_repository_set_head(ref->db->repo, normalized, signature, message)) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); return error; } @@ -1018,17 +1011,6 @@ cleanup: return error; } -int git_reference__normalize_name_lax( - char *buffer_out, - size_t out_size, - const char *name) -{ - return git_reference_normalize_name( - buffer_out, - out_size, - name, - GIT_REF_FORMAT_ALLOW_ONELEVEL); -} #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) int git_reference_cmp( diff --git a/src/refs.h b/src/refs.h index d57d67026..f75a4bf7e 100644 --- a/src/refs.h +++ b/src/refs.h @@ -51,6 +51,8 @@ #define GIT_REFNAME_MAX 1024 +typedef char git_refname_t[GIT_REFNAME_MAX]; + struct git_reference { git_refdb *db; git_ref_t type; @@ -66,7 +68,6 @@ struct git_reference { git_reference *git_reference__set_name(git_reference *ref, const char *name); -int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *signature, const char *log_message); int git_reference__is_valid_name(const char *refname, unsigned int flags); diff --git a/src/remote.c b/src/remote.c index 31ee97795..0c82433d1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -403,6 +403,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if (!optional_setting_found) { error = GIT_ENOTFOUND; + giterr_set(GITERR_CONFIG, "Remote '%s' does not exist.", name); goto cleanup; } @@ -1304,13 +1305,14 @@ static int rename_remote_config_section( if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) goto cleanup; - if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) - goto cleanup; + if (new_name && + (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)) + goto cleanup; error = git_config_rename_section( repo, git_buf_cstr(&old_section_name), - git_buf_cstr(&new_section_name)); + new_name ? git_buf_cstr(&new_section_name) : NULL); cleanup: git_buf_free(&old_section_name); @@ -1743,3 +1745,217 @@ int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); return 0; } + +/* asserts a branch.<foo>.remote format */ +static const char *name_offset(size_t *len_out, const char *name) +{ + size_t prefix_len; + const char *dot; + + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); + + assert(dot); + + *len_out = dot - name - prefix_len; + return name + prefix_len; +} + +static int remove_branch_config_related_entries( + git_repository *repo, + const char *remote_name) +{ + int error; + git_config *config; + git_config_entry *entry; + git_config_iterator *iter; + git_buf buf = GIT_BUF_INIT; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) + return error; + + /* find any branches with us as upstream and remove that config */ + while ((error = git_config_next(&entry, iter)) == 0) { + const char *branch; + size_t branch_len; + + if (strcmp(remote_name, entry->value)) + continue; + + branch = name_offset(&branch_len, entry->name); + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; + } + + if (error == GIT_ITEROVER) + error = 0; + + git_buf_free(&buf); + git_config_iterator_free(iter); + return error; +} + +static int remove_refs(git_repository *repo, const git_refspec *spec) +{ + git_reference_iterator *iter = NULL; + git_vector refs; + const char *name; + char *dup; + int error; + size_t i; + + if ((error = git_vector_init(&refs, 8, NULL)) < 0) + return error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + goto cleanup; + + while ((error = git_reference_next_name(&name, iter)) == 0) { + if (!git_refspec_dst_matches(spec, name)) + continue; + + dup = git__strdup(name); + if (!dup) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&refs, dup)) < 0) + goto cleanup; + } + if (error == GIT_ITEROVER) + error = 0; + if (error < 0) + goto cleanup; + + git_vector_foreach(&refs, i, name) { + if ((error = git_reference_remove(repo, name)) < 0) + break; + } + +cleanup: + git_reference_iterator_free(iter); + git_vector_foreach(&refs, i, dup) { + git__free(dup); + } + git_vector_free(&refs); + return error; +} + +static int remove_remote_tracking(git_repository *repo, const char *remote_name) +{ + git_remote *remote; + int error; + size_t i, count; + + /* we want to use what's on the config, regardless of changes to the instance in memory */ + if ((error = git_remote_load(&remote, repo, remote_name)) < 0) + return error; + + count = git_remote_refspec_count(remote); + for (i = 0; i < count; i++) { + const git_refspec *refspec = git_remote_get_refspec(remote, i); + + /* shouldn't ever actually happen */ + if (refspec == NULL) + continue; + + if ((error = remove_refs(repo, refspec)) < 0) + break; + } + + git_remote_free(remote); + return error; +} + +int git_remote_delete(git_remote *remote) +{ + int error; + git_repository *repo; + + assert(remote); + + if (!remote->name) { + giterr_set(GITERR_INVALID, "Can't delete an anonymous remote."); + return -1; + } + + repo = git_remote_owner(remote); + + if ((error = remove_branch_config_related_entries(repo, + git_remote_name(remote))) < 0) + return error; + + if ((error = remove_remote_tracking(repo, git_remote_name(remote))) < 0) + return error; + + if ((error = rename_remote_config_section( + repo, git_remote_name(remote), NULL)) < 0) + return error; + + git_remote_free(remote); + + return 0; +} + +int git_remote_default_branch(git_buf *out, git_remote *remote) +{ + const git_remote_head **heads; + const git_remote_head *guess = NULL; + const git_oid *head_id; + size_t heads_len, i; + int error; + + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) + return error; + + if (heads_len == 0) + return GIT_ENOTFOUND; + + git_buf_sanitize(out); + /* the first one must be HEAD so if that has the symref info, we're done */ + if (heads[0]->symref_target) + return git_buf_puts(out, heads[0]->symref_target); + + /* + * If there's no symref information, we have to look over them + * and guess. We return the first match unless the master + * branch is a candidate. Then we return the master branch. + */ + head_id = &heads[0]->oid; + + for (i = 1; i < heads_len; i++) { + if (git_oid_cmp(head_id, &heads[i]->oid)) + continue; + + if (!guess) { + guess = heads[i]; + continue; + } + + if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) { + guess = heads[i]; + break; + } + } + + if (!guess) + return GIT_ENOTFOUND; + + return git_buf_puts(out, guess->name); +} diff --git a/src/repository.c b/src/repository.c index 7d055e28e..e8d50aed3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -443,7 +443,6 @@ int git_repository_open_ext( int error; git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; git_repository *repo; - git_config *config; if (repo_ptr) *repo_ptr = NULL; @@ -458,23 +457,24 @@ int git_repository_open_ext( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) repo->is_bare = 1; - else if ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &parent)) < 0) - { + else { + git_config *config = NULL; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0 || + (error = load_config_data(repo, config)) < 0 || + (error = load_workdir(repo, config, &parent)) < 0) + git_repository_free(repo); + git_config_free(config); - git_repository_free(repo); - return error; } - git_config_free(config); + if (!error) + *repo_ptr = repo; git_buf_free(&parent); - *repo_ptr = repo; - return 0; + + return error; } int git_repository_open(git_repository **repo_out, const char *path) @@ -1190,6 +1190,7 @@ static int repo_init_structure( bool external_tpl = ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); mode_t dmode = pick_dir_mode(opts); + bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; /* Hide the ".git" directory */ #ifdef GIT_WIN32 @@ -1230,10 +1231,12 @@ static int repo_init_structure( default_template = true; } - if (tdir) - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); + if (tdir) { + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE; + if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) + cpflags |= GIT_CPDIR_CHMOD_DIRS; + error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); + } git_buf_free(&template_buf); git_config_free(cfg); @@ -1254,9 +1257,14 @@ static int repo_init_structure( * - only create files if no external template was specified */ for (tpl = repo_template; !error && tpl->path; ++tpl) { - if (!tpl->content) + if (!tpl->content) { + uint32_t mkdir_flags = GIT_MKDIR_PATH; + if (chmod) + mkdir_flags |= GIT_MKDIR_CHMOD; + error = git_futils_mkdir( - tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); + tpl->path, repo_dir, dmode, mkdir_flags); + } else if (!external_tpl) { const char *content = tpl->content; diff --git a/src/repository.h b/src/repository.h index 27eec9dd8..aba16a016 100644 --- a/src/repository.h +++ b/src/repository.h @@ -39,6 +39,7 @@ typedef enum { GIT_CVAR_ABBREV, /* core.abbrev */ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ + GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -92,6 +93,9 @@ typedef enum { GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, /* core.safecrlf */ GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE, + /* core.logallrefupdates */ + GIT_LOGALLREFUPDATES_UNSET = 2, + GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, } git_cvar_value; /* internal repository init flags */ @@ -363,20 +363,22 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu } /* write the buffer */ - if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) - return -1; + if ((error = git_odb_open_wstream( + &stream, odb, strlen(buffer), GIT_OBJ_TAG)) < 0) + return error; - git_odb_stream_write(stream, buffer, strlen(buffer)); + if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) + error = git_odb_stream_finalize_write(oid, stream); - error = git_odb_stream_finalize_write(oid, stream); git_odb_stream_free(stream); if (error < 0) { git_buf_free(&ref_name); - return -1; + return error; } - error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); + error = git_reference_create( + &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); git_reference_free(new_ref); git_buf_free(&ref_name); diff --git a/src/transports/local.c b/src/transports/local.c index 2c17e6271..f859f0b70 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -40,17 +40,29 @@ typedef struct { have_refs : 1; } transport_local; +static void free_head(git_remote_head *head) +{ + git__free(head->name); + git__free(head->symref_target); + git__free(head); +} + static int add_ref(transport_local *t, const char *name) { const char peeled[] = "^{}"; - git_oid head_oid; + git_reference *ref, *resolved; git_remote_head *head; + git_oid obj_id; git_object *obj = NULL, *target = NULL; git_buf buf = GIT_BUF_INIT; int error; - error = git_reference_name_to_id(&head_oid, t->repo, name); + if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) + return error; + + error = git_reference_resolve(&resolved, ref); if (error < 0) { + git_reference_free(ref); if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { /* This is actually okay. Empty repos often have a HEAD that * points to a nonexistent "refs/heads/master". */ @@ -60,17 +72,25 @@ static int add_ref(transport_local *t, const char *name) return error; } + git_oid_cpy(&obj_id, git_reference_target(resolved)); + git_reference_free(resolved); + head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); - git_oid_cpy(&head->oid, &head_oid); + git_oid_cpy(&head->oid, &obj_id); + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + head->symref_target = git__strdup(git_reference_symbolic_target(ref)); + GITERR_CHECK_ALLOC(head->symref_target); + } + git_reference_free(ref); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); return error; } @@ -103,8 +123,7 @@ static int add_ref(transport_local *t, const char *name) git_oid_cpy(&head->oid, git_object_id(target)); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); } } @@ -156,27 +175,9 @@ on_error: return -1; } -static int path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) -{ - int error; - - /* If url_or_path begins with file:// treat it as a URL */ - if (!git__prefixcmp(url_or_path, "file://")) { - if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { - return error; - } - } else { /* We assume url_or_path is already a path */ - if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { - return error; - } - } - - return 0; -} - /* * Try to open the url as a git directory. The direction doesn't - * matter in this case because we're calulating the heads ourselves. + * matter in this case because we're calculating the heads ourselves. */ static int local_connect( git_transport *transport, @@ -203,7 +204,7 @@ static int local_connect( t->flags = flags; /* 'url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, url)) < 0) { git_buf_free(&buf); return error; } @@ -367,7 +368,7 @@ static int local_push( size_t j; /* 'push->remote->url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, push->remote->url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) { git_buf_free(&buf); return error; } @@ -626,10 +627,8 @@ static void local_free(git_transport *transport) size_t i; git_remote_head *head; - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } + git_vector_foreach(&t->refs, i, head) + free_head(head); git_vector_free(&t->refs); diff --git a/src/transports/smart.c b/src/transports/smart.c index 69eaf9b78..a5c3e82dc 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -7,6 +7,7 @@ #include "git2.h" #include "smart.h" #include "refs.h" +#include "refspec.h" static int git_smart__recv_cb(gitno_buffer *buf) { @@ -63,7 +64,7 @@ static int git_smart__set_callbacks( return 0; } -int git_smart__update_heads(transport_smart *t) +int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; git_pkt *pkt; @@ -74,6 +75,25 @@ int git_smart__update_heads(transport_smart *t) if (pkt->type != GIT_PKT_REF) continue; + if (symrefs) { + git_refspec *spec; + git_buf buf = GIT_BUF_INIT; + size_t j; + int error = 0; + + git_vector_foreach(symrefs, j, spec) { + git_buf_clear(&buf); + if (git_refspec_src_matches(spec, ref->head.name) && + !(error = git_refspec_transform(&buf, spec, ref->head.name))) + ref->head.symref_target = git_buf_detach(&buf); + } + + git_buf_free(&buf); + + if (error < 0) + return error; + } + if (git_vector_insert(&t->heads, &ref->head) < 0) return -1; } @@ -81,6 +101,19 @@ int git_smart__update_heads(transport_smart *t) return 0; } +static void free_symrefs(git_vector *symrefs) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(symrefs, i, spec) { + git_refspec__free(spec); + git__free(spec); + } + + git_vector_free(symrefs); +} + static int git_smart__connect( git_transport *transport, const char *url, @@ -94,6 +127,7 @@ static int git_smart__connect( int error; git_pkt *pkt; git_pkt_ref *first; + git_vector symrefs; git_smart_service_t service; if (git_smart__reset_stream(t, true) < 0) @@ -147,8 +181,11 @@ static int git_smart__connect( first = (git_pkt_ref *)git_vector_get(&t->refs, 0); + if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) + return error; + /* Detect capabilities */ - if (git_smart__detect_caps(first, &t->caps) < 0) + if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0) return -1; /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ @@ -159,7 +196,9 @@ static int git_smart__connect( } /* Keep a list of heads for _ls */ - git_smart__update_heads(t); + git_smart__update_heads(t, &symrefs); + + free_symrefs(&symrefs); if (t->rpc && git_smart__reset_stream(t, false) < 0) return -1; @@ -272,6 +311,18 @@ static int git_smart__close(git_transport *transport) unsigned int i; git_pkt *p; int ret; + git_smart_subtransport_stream *stream; + const char flush[] = "0000"; + + /* + * If we're still connected at this point and not using RPC, + * we should say goodbye by sending a flush, or git-daemon + * will complain that we disconnected unexpectedly. + */ + if (t->connected && !t->rpc && + !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + t->current_stream->write(t->current_stream, flush, 4); + } ret = git_smart__reset_stream(t, true); diff --git a/src/transports/smart.h b/src/transports/smart.h index a2b6b2a71..f1fc29520 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -23,6 +23,7 @@ #define GIT_CAP_DELETE_REFS "delete-refs" #define GIT_CAP_REPORT_STATUS "report-status" #define GIT_CAP_THIN_PACK "thin-pack" +#define GIT_CAP_SYMREF "symref" enum git_pkt_type { GIT_PKT_CMD, @@ -154,7 +155,7 @@ typedef struct { /* smart_protocol.c */ int git_smart__store_refs(transport_smart *t, int flushes); -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); int git_smart__push(git_transport *transport, git_push *push); int git_smart__negotiate_fetch( @@ -174,7 +175,7 @@ int git_smart__download_pack( int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); -int git_smart__update_heads(transport_smart *t); +int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index e9376ae6f..b5f9d6dbe 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -433,6 +433,7 @@ void git_pkt_free(git_pkt *pkt) if (pkt->type == GIT_PKT_REF) { git_pkt_ref *p = (git_pkt_ref *) pkt; git__free(p->head.name); + git__free(p->head.symref_target); } if (pkt->type == GIT_PKT_OK) { diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 5dd6bab24..a52aacc60 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -26,17 +26,16 @@ int git_smart__store_refs(transport_smart *t, int flushes) int error, flush = 0, recvd; const char *line_end = NULL; git_pkt *pkt = NULL; - git_pkt_ref *ref; size_t i; /* Clear existing refs in case git_remote_connect() is called again * after git_remote_disconnect(). */ - git_vector_foreach(refs, i, ref) { - git__free(ref->head.name); - git__free(ref); + git_vector_foreach(refs, i, pkt) { + git_pkt_free(pkt); } git_vector_clear(refs); + pkt = NULL; do { if (buf->offset > 0) @@ -78,7 +77,52 @@ int git_smart__store_refs(transport_smart *t, int flushes) return flush; } -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) +static int append_symref(const char **out, git_vector *symrefs, const char *ptr) +{ + int error; + const char *end; + git_buf buf = GIT_BUF_INIT; + git_refspec *mapping; + + ptr += strlen(GIT_CAP_SYMREF); + if (*ptr != '=') + goto on_invalid; + + ptr++; + if (!(end = strchr(ptr, ' ')) && + !(end = strchr(ptr, '\0'))) + goto on_invalid; + + if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0) + return error; + + /* symref mapping has refspec format */ + mapping = git__malloc(sizeof(git_refspec)); + GITERR_CHECK_ALLOC(mapping); + + error = git_refspec__parse(mapping, git_buf_cstr(&buf), true); + git_buf_free(&buf); + + /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ + if (error < 0) { + if (giterr_last()->klass != GITERR_NOMEMORY) + goto on_invalid; + + return error; + } + + if ((error = git_vector_insert(symrefs, mapping)) < 0) + return error; + + *out = end; + return 0; + +on_invalid: + giterr_set(GITERR_NET, "remote sent invalid symref"); + return -1; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) { const char *ptr; @@ -141,6 +185,15 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { + int error; + + if ((error = append_symref(&ptr, symrefs, ptr)) < 0) + return error; + + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -969,7 +1022,7 @@ int git_smart__push(git_transport *transport, git_push *push) if (error < 0) goto done; - error = git_smart__update_heads(t); + error = git_smart__update_heads(t, NULL); } done: diff --git a/src/unix/map.c b/src/unix/map.c index e62ab3e76..3d0cbbaf8 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -10,8 +10,14 @@ #include "map.h" #include <sys/mman.h> +#include <unistd.h> #include <errno.h> +long git__page_size(void) +{ + return sysconf(_SC_PAGE_SIZE); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { int mprot = 0; diff --git a/src/util.h b/src/util.h index 6fb2dc0f4..ca676c039 100644 --- a/src/util.h +++ b/src/util.h @@ -133,6 +133,13 @@ GIT_INLINE(int) git__is_uint32(size_t p) return p == (size_t)r; } +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(git_off_t p) +{ + unsigned long r = (unsigned long)p; + return p == (git_off_t)r; +} + /* 32-bit cross-platform rotl */ #ifdef _MSC_VER /* use built-in method in MSVC */ # define git__rotl(v, s) (uint32_t)_rotl(v, s) diff --git a/src/win32/map.c b/src/win32/map.c index 902ea3994..ef83f882e 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -23,6 +23,11 @@ static DWORD get_page_size(void) return page_size; } +long git__page_size(void) +{ + return (long)get_page_size(); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 8f51d6f5a..059e39cbc 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -10,6 +10,7 @@ #if defined(__MINGW32__) /* use a 64-bit file offset type */ +# undef lseek # define lseek _lseeki64 # undef stat # define stat _stati64 diff --git a/src/win32/posix.h b/src/win32/posix.h index 7f9d57cc3..2cbea1807 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -19,6 +19,12 @@ # define EAFNOSUPPORT (INT_MAX-1) #endif +#ifdef _MSC_VER +# define p_ftruncate(fd, sz) _chsize_s(fd, sz) +#else /* MinGW */ +# define p_ftruncate(fd, sz) _chsize(fd, sz) +#endif + GIT_INLINE(int) p_link(const char *old, const char *new) { GIT_UNUSED(old); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 0d070f6b5..34938431a 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -19,6 +19,15 @@ # define FILE_NAME_NORMALIZED 0 #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) + /* GetFinalPathNameByHandleW signature */ typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); @@ -317,7 +326,7 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - return _wopen(buf, flags | _O_BINARY, mode); + return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode); } int p_creat(const char *path, mode_t mode) @@ -327,7 +336,7 @@ int p_creat(const char *path, mode_t mode) if (utf8_to_16_with_errno(buf, path) < 0) return -1; - return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); + return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode); } int p_getcwd(char *buffer_out, size_t size) @@ -569,7 +578,7 @@ int p_mkstemp(char *tmp_path) return -1; #endif - return p_creat(tmp_path, 0744); //-V536 + return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536 } int p_access(const char* path, mode_t mode) |
