summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/array.h2
-rw-r--r--src/attr.c12
-rw-r--r--src/attr_file.c40
-rw-r--r--src/attr_file.h6
-rw-r--r--src/attrcache.c13
-rw-r--r--src/blame.c14
-rw-r--r--src/blob.c9
-rw-r--r--src/branch.c15
-rw-r--r--src/buf_text.c14
-rw-r--r--src/buf_text.h5
-rw-r--r--src/buffer.c114
-rw-r--r--src/buffer.h6
-rw-r--r--src/cache.c18
-rw-r--r--src/cache.h2
-rw-r--r--src/cc-compat.h8
-rw-r--r--src/checkout.c161
-rw-r--r--src/cherrypick.c93
-rw-r--r--src/clone.c386
-rw-r--r--src/clone.h12
-rw-r--r--src/commit.c99
-rw-r--r--src/common.h6
-rw-r--r--src/config.c70
-rw-r--r--src/config.h6
-rw-r--r--src/config_cache.c50
-rw-r--r--src/config_file.c621
-rw-r--r--src/crlf.c30
-rw-r--r--src/diff.c291
-rw-r--r--src/diff.h6
-rw-r--r--src/diff_driver.c19
-rw-r--r--src/diff_file.c4
-rw-r--r--src/diff_patch.c1
-rw-r--r--src/diff_print.c111
-rw-r--r--src/diff_stats.c2
-rw-r--r--src/diff_tform.c12
-rw-r--r--src/errors.c16
-rw-r--r--src/filebuf.c2
-rw-r--r--src/fileops.c22
-rw-r--r--src/fileops.h2
-rw-r--r--src/filter.c22
-rw-r--r--src/filter.h4
-rw-r--r--src/fnmatch.c36
-rw-r--r--src/fnmatch.h27
-rw-r--r--src/global.c94
-rw-r--r--src/global.h7
-rw-r--r--src/graph.c4
-rw-r--r--src/ignore.c73
-rw-r--r--src/ignore.h9
-rw-r--r--src/index.c73
-rw-r--r--src/indexer.c195
-rw-r--r--src/iterator.c74
-rw-r--r--src/iterator.h3
-rw-r--r--src/map.h1
-rw-r--r--src/merge.c357
-rw-r--r--src/merge.h2
-rw-r--r--src/message.c4
-rw-r--r--src/mwindow.c125
-rw-r--r--src/mwindow.h10
-rw-r--r--src/netops.c68
-rw-r--r--src/netops.h18
-rw-r--r--src/object.c2
-rw-r--r--src/odb.c14
-rw-r--r--src/odb_loose.c6
-rw-r--r--src/odb_pack.c9
-rw-r--r--src/oid.c8
-rw-r--r--src/oid.h11
-rw-r--r--src/oidarray.c21
-rw-r--r--src/oidarray.h18
-rw-r--r--src/pack-objects.c9
-rw-r--r--src/pack.c312
-rw-r--r--src/pack.h13
-rw-r--r--src/path.c206
-rw-r--r--src/path.h28
-rw-r--r--src/pool.c4
-rw-r--r--src/posix.c9
-rw-r--r--src/posix.h73
-rw-r--r--src/push.c13
-rw-r--r--src/refdb.c13
-rw-r--r--src/refdb_fs.c40
-rw-r--r--src/refs.c48
-rw-r--r--src/refs.h7
-rw-r--r--src/refspec.c72
-rw-r--r--src/remote.c469
-rw-r--r--src/remote.h4
-rw-r--r--src/repository.c202
-rw-r--r--src/repository.h4
-rw-r--r--src/revert.c23
-rw-r--r--src/revparse.c33
-rw-r--r--src/revwalk.c5
-rw-r--r--src/signature.c6
-rw-r--r--src/stash.c11
-rw-r--r--src/status.c83
-rw-r--r--src/strnlen.h3
-rw-r--r--src/submodule.c110
-rw-r--r--src/sysdir.c18
-rw-r--r--src/tag.c14
-rw-r--r--src/thread-utils.h23
-rw-r--r--src/trace.h10
-rw-r--r--src/transport.c196
-rw-r--r--src/transports/auth.c71
-rw-r--r--src/transports/auth.h63
-rw-r--r--src/transports/auth_negotiate.c275
-rw-r--r--src/transports/auth_negotiate.h27
-rw-r--r--src/transports/cred.c58
-rw-r--r--src/transports/cred.h14
-rw-r--r--src/transports/cred_helpers.c3
-rw-r--r--src/transports/git.c1
-rw-r--r--src/transports/http.c313
-rw-r--r--src/transports/local.c63
-rw-r--r--src/transports/smart.c59
-rw-r--r--src/transports/smart.h7
-rw-r--r--src/transports/smart_pkt.c1
-rw-r--r--src/transports/smart_protocol.c77
-rw-r--r--src/transports/ssh.c286
-rw-r--r--src/transports/winhttp.c174
-rw-r--r--src/tree.c117
-rw-r--r--src/tree.h5
-rw-r--r--src/unix/map.c6
-rw-r--r--src/unix/posix.h32
-rw-r--r--src/unix/realpath.c15
-rw-r--r--src/util.c5
-rw-r--r--src/util.h13
-rw-r--r--src/win32/map.c5
-rw-r--r--src/win32/mingw-compat.h16
-rw-r--r--src/win32/msvc-compat.h35
-rw-r--r--src/win32/posix.h45
-rw-r--r--src/win32/posix_w32.c82
-rw-r--r--src/win32/pthread.c109
-rw-r--r--src/win32/pthread.h33
128 files changed, 5324 insertions, 2342 deletions
diff --git a/src/array.h b/src/array.h
index f8a48722a..af9eafa43 100644
--- a/src/array.h
+++ b/src/array.h
@@ -44,7 +44,7 @@ typedef git_array_t(char) git_array_generic_t;
/* use a generic array for growth so this can return the new item */
GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
{
- git_array_generic_t *a = _a;
+ volatile git_array_generic_t *a = _a;
uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2;
char *new_array = git__realloc(a->ptr, new_size * item_size);
if (!new_array) {
diff --git a/src/attr.c b/src/attr.c
index 05b0c1b3c..a02172689 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -377,7 +377,7 @@ static int push_attr_file(
return error;
}
-static int push_one_attr(void *ref, git_buf *path)
+static int push_one_attr(void *ref, const char *path)
{
int error = 0, n_src, i;
attr_walk_up_info *info = (attr_walk_up_info *)ref;
@@ -388,7 +388,7 @@ static int push_one_attr(void *ref, git_buf *path)
for (i = 0; !error && i < n_src; ++i)
error = push_attr_file(
- info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
+ info->repo, info->files, src[i], path, GIT_ATTR_FILE);
return error;
}
@@ -411,7 +411,7 @@ static int collect_attr_files(
const char *path,
git_vector *files)
{
- int error;
+ int error = 0;
git_buf dir = GIT_BUF_INIT;
const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL };
@@ -447,7 +447,11 @@ static int collect_attr_files(
giterr_clear(); /* no error even if there is no index */
info.files = files;
- error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
+ if (!strcmp(dir.ptr, "."))
+ error = push_one_attr(&info, "");
+ else
+ error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
+
if (error < 0)
goto cleanup;
diff --git a/src/attr_file.c b/src/attr_file.c
index 156a23d91..07ffacbaf 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,40 @@ 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);
+ }
+
+ /* if path is a directory prefix of a negated pattern, then match */
+ if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
+ size_t pathlen = strlen(path->path);
+ bool prefixed = (pathlen <= match->length) &&
+ ((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
+ !strncasecmp(match->pattern, path->path, pathlen) :
+ !strncmp(match->pattern, path->path, pathlen));
+
+ if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
+ return true;
+ }
+
return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
}
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);
@@ -509,7 +534,8 @@ int git_attr_fnmatch__parse(
}
if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
- spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
+ spec->flags = spec->flags |
+ GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
pattern++;
}
diff --git a/src/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/blame.c b/src/blame.c
index e45c0ee1c..2cc5e552b 100644
--- a/src/blame.c
+++ b/src/blame.c
@@ -316,7 +316,6 @@ static int blame_internal(git_blame *blame)
ent->suspect = o;
blame->ent = ent;
- blame->path = blame->path;
git_blame__like_git(blame, blame->options.flags);
@@ -480,14 +479,9 @@ int git_blame_buffer(
return 0;
}
-int git_blame_init_options(git_blame_options* opts, int version)
+int git_blame_init_options(git_blame_options *opts, unsigned int version)
{
- if (version != GIT_BLAME_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_blame_options", version);
- return -1;
- } else {
- git_blame_options o = GIT_BLAME_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/blob.c b/src/blob.c
index 0aa2516db..30d5b705b 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -198,7 +198,8 @@ int git_blob__create_from_paths(
if (try_load_filters)
/* Load the filters for writing this file to the ODB */
error = git_filter_list_load(
- &fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB);
+ &fl, repo, NULL, hint_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_DEFAULT);
if (error < 0)
/* well, that didn't work */;
@@ -333,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);
@@ -356,7 +358,8 @@ int git_blob_filtered_content(
return 0;
if (!(error = git_filter_list_load(
- &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) {
+ &fl, git_blob_owner(blob), blob, path,
+ GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT))) {
error = git_filter_list_apply_to_blob(out, fl, blob);
diff --git a/src/branch.c b/src/branch.c
index 63c6ec110..52760853b 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -306,17 +306,13 @@ int git_branch_name(
static int retrieve_upstream_configuration(
const char **out,
- git_repository *repo,
+ const git_config *config,
const char *canonical_branch_name,
const char *format)
{
- git_config *config;
git_buf buf = GIT_BUF_INIT;
int error;
- if (git_repository_config__weakptr(&config, repo) < 0)
- return -1;
-
if (git_buf_printf(&buf, format,
canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
return -1;
@@ -336,6 +332,7 @@ int git_branch_upstream_name(
int error = -1;
git_remote *remote = NULL;
const git_refspec *refspec;
+ git_config *config;
assert(out && refname);
@@ -344,12 +341,15 @@ int git_branch_upstream_name(
if (!git_reference__is_branch(refname))
return not_a_local_branch(refname);
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
+
if ((error = retrieve_upstream_configuration(
- &remote_name, repo, refname, "branch.%s.remote")) < 0)
+ &remote_name, config, refname, "branch.%s.remote")) < 0)
goto cleanup;
if ((error = retrieve_upstream_configuration(
- &merge_name, repo, refname, "branch.%s.merge")) < 0)
+ &merge_name, config, refname, "branch.%s.merge")) < 0)
goto cleanup;
if (!*remote_name || !*merge_name) {
@@ -378,6 +378,7 @@ int git_branch_upstream_name(
error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
cleanup:
+ git_config_free(config);
git_remote_free(remote);
git_buf_free(&buf);
return error;
diff --git a/src/buf_text.c b/src/buf_text.c
index 631feb3f8..8d2b141b2 100644
--- a/src/buf_text.c
+++ b/src/buf_text.c
@@ -123,9 +123,13 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
size_t copylen = next - scan;
- /* don't convert existing \r\n to \r\r\n */
- size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2;
- size_t needsize = tgt->size + copylen + extralen + 1;
+ size_t needsize = tgt->size + copylen + 2 + 1;
+
+ /* if we find mixed line endings, bail */
+ if (next > start && next[-1] == '\r') {
+ git_buf_free(tgt);
+ return GIT_PASSTHROUGH;
+ }
if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)
return -1;
@@ -134,8 +138,8 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
memcpy(tgt->ptr + tgt->size, scan, copylen);
tgt->size += copylen;
}
- if (extralen == 2)
- tgt->ptr[tgt->size++] = '\r';
+
+ tgt->ptr[tgt->size++] = '\r';
tgt->ptr[tgt->size++] = '\n';
}
diff --git a/src/buf_text.h b/src/buf_text.h
index 3ac9d1443..e753a0244 100644
--- a/src/buf_text.h
+++ b/src/buf_text.h
@@ -56,9 +56,10 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
extern void git_buf_text_unescape(git_buf *buf);
/**
- * Replace all \r\n with \n. Does not modify \r without trailing \n.
+ * Replace all \r\n with \n.
*
- * @return 0 on success, -1 on memory error
+ * @return 0 on success, -1 on memory error, GIT_PASSTHROUGH if the
+ * source buffer has mixed line endings.
*/
extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
diff --git a/src/buffer.c b/src/buffer.c
index 5169c3e09..e9c420e16 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -7,6 +7,7 @@
#include "buffer.h"
#include "posix.h"
#include "git2/buffer.h"
+#include "buf_text.h"
#include <ctype.h>
/* Used as default value for git_buf->ptr so that people can always
@@ -104,17 +105,20 @@ void git_buf_free(git_buf *buf)
void git_buf_sanitize(git_buf *buf)
{
if (buf->ptr == NULL) {
- assert (buf->size == 0 && buf->asize == 0);
+ assert(buf->size == 0 && buf->asize == 0);
buf->ptr = git_buf__initbuf;
- }
+ } else if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
}
void git_buf_clear(git_buf *buf)
{
buf->size = 0;
- if (!buf->ptr)
+ if (!buf->ptr) {
buf->ptr = git_buf__initbuf;
+ buf->asize = 0;
+ }
if (buf->asize > 0)
buf->ptr[0] = '\0';
@@ -129,12 +133,25 @@ int git_buf_set(git_buf *buf, const void *data, size_t len)
ENSURE_SIZE(buf, len + 1);
memmove(buf->ptr, data, len);
}
+
buf->size = len;
- buf->ptr[buf->size] = '\0';
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+
}
return 0;
}
+int git_buf_is_binary(const git_buf *buf)
+{
+ return git_buf_text_is_binary(buf);
+}
+
+int git_buf_contains_nul(const git_buf *buf)
+{
+ return git_buf_text_contains_nul(buf);
+}
+
int git_buf_sets(git_buf *buf, const char *string)
{
return git_buf_set(buf, string, string ? strlen(string) : 0);
@@ -172,10 +189,10 @@ int git_buf_puts(git_buf *buf, const char *string)
return git_buf_put(buf, string, strlen(string));
}
-static const char b64str[] =
+static const char base64_encode[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
+int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
{
size_t extra = len % 3;
uint8_t *write, a, b, c;
@@ -190,19 +207,19 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
b = *read++;
c = *read++;
- *write++ = b64str[a >> 2];
- *write++ = b64str[(a & 0x03) << 4 | b >> 4];
- *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
- *write++ = b64str[c & 0x3f];
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6];
+ *write++ = base64_encode[c & 0x3f];
}
if (extra > 0) {
a = *read++;
b = (extra > 1) ? *read++ : 0;
- *write++ = b64str[a >> 2];
- *write++ = b64str[(a & 0x03) << 4 | b >> 4];
- *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
+ *write++ = base64_encode[a >> 2];
+ *write++ = base64_encode[(a & 0x03) << 4 | b >> 4];
+ *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '=';
*write++ = '=';
}
@@ -212,10 +229,56 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
return 0;
}
+/* The inverse of base64_encode, offset by '+' == 43. */
+static const int8_t base64_decode[] = {
+ 62,
+ -1, -1, -1,
+ 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ -1, -1, -1, 0, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ -1, -1, -1, -1, -1, -1,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+};
+
+#define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43])
+
+int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
+{
+ size_t i;
+ int8_t a, b, c, d;
+ size_t orig_size = buf->size;
+
+ assert(len % 4 == 0);
+ ENSURE_SIZE(buf, buf->size + (len / 4 * 3) + 1);
+
+ for (i = 0; i < len; i += 4) {
+ if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 ||
+ (b = BASE64_DECODE_VALUE(base64[i+1])) < 0 ||
+ (c = BASE64_DECODE_VALUE(base64[i+2])) < 0 ||
+ (d = BASE64_DECODE_VALUE(base64[i+3])) < 0) {
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ giterr_set(GITERR_INVALID, "Invalid base64 input");
+ return -1;
+ }
+
+ buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4);
+ buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f);
+ }
+
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
static const char b85str[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
-int git_buf_put_base85(git_buf *buf, const char *data, size_t len)
+int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
{
ENSURE_SIZE(buf, buf->size + (5 * ((len / 4) + !!(len % 4))) + 1);
@@ -326,19 +389,20 @@ void git_buf_consume(git_buf *buf, const char *end)
void git_buf_truncate(git_buf *buf, size_t len)
{
- if (len < buf->size) {
- buf->size = len;
+ if (len >= buf->size)
+ return;
+
+ buf->size = len;
+ if (buf->size < buf->asize)
buf->ptr[buf->size] = '\0';
- }
}
void git_buf_shorten(git_buf *buf, size_t amount)
{
- if (amount > buf->size)
- amount = buf->size;
-
- buf->size = buf->size - amount;
- buf->ptr[buf->size] = '\0';
+ if (buf->size > amount)
+ git_buf_truncate(buf, buf->size - amount);
+ else
+ git_buf_clear(buf);
}
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
@@ -574,7 +638,8 @@ void git_buf_rtrim(git_buf *buf)
buf->size--;
}
- buf->ptr[buf->size] = '\0';
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
}
int git_buf_cmp(const git_buf *a, const git_buf *b)
@@ -598,8 +663,7 @@ int git_buf_splice(
/* Ported from git.git
* https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
*/
- if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
- return -1;
+ ENSURE_SIZE(buf, buf->size + nb_to_insert - nb_to_insert + 1);
memmove(buf->ptr + where + nb_to_insert,
buf->ptr + where + nb_to_remove,
diff --git a/src/buffer.h b/src/buffer.h
index 70d6d73b3..8ee4b532c 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -156,10 +156,12 @@ void git_buf_rtrim(git_buf *buf);
int git_buf_cmp(const git_buf *a, const git_buf *b);
/* Write data as base64 encoded in buffer */
-int git_buf_put_base64(git_buf *buf, const char *data, size_t len);
+int git_buf_encode_base64(git_buf *buf, const char *data, size_t len);
+/* Decode the given bas64 and write the result to the buffer */
+int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len);
/* Write data as "base85" encoded in buffer */
-int git_buf_put_base85(git_buf *buf, const char *data, size_t len);
+int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
/*
* Insert, remove or replace a portion of the buffer.
diff --git a/src/cache.c b/src/cache.c
index 36ce66570..8dc9cbf9c 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -68,8 +68,8 @@ int git_cache_init(git_cache *cache)
{
memset(cache, 0, sizeof(*cache));
cache->map = git_oidmap_alloc();
- if (git_mutex_init(&cache->lock)) {
- giterr_set(GITERR_OS, "Failed to initialize cache mutex");
+ if (git_rwlock_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize cache rwlock");
return -1;
}
return 0;
@@ -94,19 +94,19 @@ static void clear_cache(git_cache *cache)
void git_cache_clear(git_cache *cache)
{
- if (git_mutex_lock(&cache->lock) < 0)
+ if (git_rwlock_wrlock(&cache->lock) < 0)
return;
clear_cache(cache);
- git_mutex_unlock(&cache->lock);
+ git_rwlock_wrunlock(&cache->lock);
}
void git_cache_free(git_cache *cache)
{
git_cache_clear(cache);
git_oidmap_free(cache->map);
- git_mutex_free(&cache->lock);
+ git_rwlock_free(&cache->lock);
git__memzero(cache, sizeof(*cache));
}
@@ -152,7 +152,7 @@ static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
khiter_t pos;
git_cached_obj *entry = NULL;
- if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
+ if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
return NULL;
pos = kh_get(oid, cache->map, oid);
@@ -166,7 +166,7 @@ static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
}
}
- git_mutex_unlock(&cache->lock);
+ git_rwlock_rdunlock(&cache->lock);
return entry;
}
@@ -185,7 +185,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry)
if (!cache_should_store(entry->type, entry->size))
return entry;
- if (git_mutex_lock(&cache->lock) < 0)
+ if (git_rwlock_wrlock(&cache->lock) < 0)
return entry;
/* soften the load on the cache */
@@ -227,7 +227,7 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry)
}
}
- git_mutex_unlock(&cache->lock);
+ git_rwlock_wrunlock(&cache->lock);
return entry;
}
diff --git a/src/cache.h b/src/cache.h
index 53fbcf4e9..697123739 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -30,7 +30,7 @@ typedef struct {
typedef struct {
git_oidmap *map;
- git_mutex lock;
+ git_rwlock lock;
ssize_t used_memory;
} git_cache;
diff --git a/src/cc-compat.h b/src/cc-compat.h
index e73cb6de8..0b66d8ba1 100644
--- a/src/cc-compat.h
+++ b/src/cc-compat.h
@@ -35,6 +35,14 @@
# define GIT_TYPEOF(x)
#endif
+#if defined(__GNUC__)
+# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size)))
+#elif defined(_MSC_VER)
+# define GIT_ALIGN(x,size) __declspec(align(size)) x
+#else
+# define GIT_ALIGN(x,size) x
+#endif
+
#define GIT_UNUSED(x) ((void)(x))
/* Define the printf format specifer to use for size_t output */
diff --git a/src/checkout.c b/src/checkout.c
index bc976b854..f25a6eff0 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -46,6 +46,7 @@ enum {
typedef struct {
git_repository *repo;
+ git_iterator *target;
git_diff *diff;
git_checkout_options opts;
bool opts_free_baseline;
@@ -54,6 +55,8 @@ typedef struct {
git_pool pool;
git_vector removes;
git_vector conflicts;
+ git_vector *reuc;
+ git_vector *names;
git_buf path;
size_t workdir_len;
git_buf tmp;
@@ -116,6 +119,7 @@ static int checkout_notify(
case GIT_DELTA_ADDED:
case GIT_DELTA_IGNORED:
case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_UNREADABLE:
target = &delta->new_file;
break;
case GIT_DELTA_DELETED:
@@ -138,6 +142,7 @@ static int checkout_notify(
static bool checkout_is_workdir_modified(
checkout_data *data,
const git_diff_file *baseitem,
+ const git_diff_file *newitem,
const git_index_entry *wditem)
{
git_oid oid;
@@ -169,13 +174,16 @@ static bool checkout_is_workdir_modified(
/* Look at the cache to decide if the workdir is modified. If not,
* we can simply compare the oid in the cache to the baseitem instead
- * of hashing the file.
+ * of hashing the file. If so, we allow the checkout to proceed if the
+ * oid is identical (ie, the staged item is what we're trying to check
+ * out.)
*/
if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) {
if (wditem->mtime.seconds == ie->mtime.seconds &&
wditem->mtime.nanoseconds == ie->mtime.nanoseconds &&
wditem->file_size == ie->file_size)
- return (git_oid__cmp(&baseitem->id, &ie->id) != 0);
+ return (git_oid__cmp(&baseitem->id, &ie->id) != 0 &&
+ git_oid_cmp(&newitem->id, &ie->id) != 0);
}
/* depending on where base is coming from, we may or may not know
@@ -184,9 +192,7 @@ static bool checkout_is_workdir_modified(
if (baseitem->size && wditem->file_size != baseitem->size)
return true;
- if (git_diff__oid_for_file(
- data->repo, wditem->path, wditem->mode,
- wditem->file_size, &oid) < 0)
+ if (git_diff__oid_for_entry(&oid, data->diff, wditem, NULL) < 0)
return false;
return (git_oid__cmp(&baseitem->id, &oid) != 0);
@@ -403,7 +409,7 @@ static int checkout_action_with_wd(
switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
- if (checkout_is_workdir_modified(data, &delta->old_file, wd)) {
+ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) {
GITERR_CHECK_ERROR(
checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) );
*action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
@@ -416,13 +422,13 @@ static int checkout_action_with_wd(
*action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
break;
case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
- if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
break;
case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
- if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
@@ -445,7 +451,7 @@ static int checkout_action_with_wd(
} else
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
}
- else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd))
*action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
else
*action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
@@ -790,11 +796,16 @@ done:
static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec)
{
git_index_conflict_iterator *iterator = NULL;
+ git_index *index;
const git_index_entry *ancestor, *ours, *theirs;
checkout_conflictdata *conflict;
int error = 0;
- if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0)
+ /* Only write conficts from sources that have them: indexes. */
+ if ((index = git_iterator_get_index(data->target)) == NULL)
+ return 0;
+
+ if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0)
goto done;
data->conflicts._cmp = checkout_conflictdata_cmp;
@@ -821,6 +832,10 @@ static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, g
git_vector_insert(&data->conflicts, conflict);
}
+ /* Collect the REUC and NAME entries */
+ data->reuc = &index->reuc;
+ data->names = &index->names;
+
if (error == GIT_ITEROVER)
error = 0;
@@ -959,16 +974,20 @@ done:
static int checkout_conflicts_coalesce_renames(
checkout_data *data)
{
+ git_index *index;
const git_index_name_entry *name_entry;
checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict;
size_t i, names;
int error = 0;
+ if ((index = git_iterator_get_index(data->target)) == NULL)
+ return 0;
+
/* Juggle entries based on renames */
- names = git_index_name_entrycount(data->index);
+ names = git_index_name_entrycount(index);
for (i = 0; i < names; i++) {
- name_entry = git_index_name_get_byindex(data->index, i);
+ name_entry = git_index_name_get_byindex(index, i);
if ((error = checkout_conflicts_load_byname_entry(
&ancestor_conflict, &our_conflict, &their_conflict,
@@ -1012,13 +1031,17 @@ done:
static int checkout_conflicts_mark_directoryfile(
checkout_data *data)
{
+ git_index *index;
checkout_conflictdata *conflict;
const git_index_entry *entry;
size_t i, j, len;
const char *path;
int prefixed, error = 0;
- len = git_index_entrycount(data->index);
+ if ((index = git_iterator_get_index(data->target)) == NULL)
+ return 0;
+
+ len = git_index_entrycount(index);
/* Find d/f conflicts */
git_vector_foreach(&data->conflicts, i, conflict) {
@@ -1029,7 +1052,7 @@ static int checkout_conflicts_mark_directoryfile(
path = conflict->ours ?
conflict->ours->path : conflict->theirs->path;
- if ((error = git_index_find(&j, data->index, path)) < 0) {
+ if ((error = git_index_find(&j, index, path)) < 0) {
if (error == GIT_ENOTFOUND)
giterr_set(GITERR_INDEX,
"Index inconsistency, could not find entry for expected conflict '%s'", path);
@@ -1038,7 +1061,7 @@ static int checkout_conflicts_mark_directoryfile(
}
for (; j < len; j++) {
- if ((entry = git_index_get_byindex(data->index, j)) == NULL) {
+ if ((entry = git_index_get_byindex(index, j)) == NULL) {
giterr_set(GITERR_INDEX,
"Index inconsistency, truncated index while loading expected conflict '%s'", path);
error = -1;
@@ -1214,7 +1237,8 @@ static int blob_content_to_file(
if (!opts->disable_filters)
error = git_filter_list_load(
- &fl, git_blob_owner(blob), blob, hint_path, GIT_FILTER_TO_WORKTREE);
+ &fl, git_blob_owner(blob), blob, hint_path,
+ GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT);
if (!error)
error = git_filter_list_apply_to_blob(&out, fl, blob);
@@ -1803,6 +1827,24 @@ done:
return error;
}
+static int checkout_conflict_update_index(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ int error = 0;
+
+ if (conflict->ancestor)
+ error = git_index_add(data->index, conflict->ancestor);
+
+ if (!error && conflict->ours)
+ error = git_index_add(data->index, conflict->ours);
+
+ if (!error && conflict->theirs)
+ error = git_index_add(data->index, conflict->theirs);
+
+ return error;
+}
+
static int checkout_create_conflicts(checkout_data *data)
{
checkout_conflictdata *conflict;
@@ -1865,6 +1907,12 @@ static int checkout_create_conflicts(checkout_data *data)
else if (!error)
error = checkout_write_merge(data, conflict);
+ /* Update the index extensions (REUC and NAME) if we're checking
+ * out a different index. (Otherwise just leave them there.)
+ */
+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = checkout_conflict_update_index(data, conflict);
+
if (error)
break;
@@ -1877,6 +1925,37 @@ static int checkout_create_conflicts(checkout_data *data)
return error;
}
+static int checkout_extensions_update_index(checkout_data *data)
+{
+ const git_index_reuc_entry *reuc_entry;
+ const git_index_name_entry *name_entry;
+ size_t i;
+ int error = 0;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
+ if (data->reuc) {
+ git_vector_foreach(data->reuc, i, reuc_entry) {
+ if ((error = git_index_reuc_add(data->index, reuc_entry->path,
+ reuc_entry->mode[0], &reuc_entry->oid[0],
+ reuc_entry->mode[1], &reuc_entry->oid[1],
+ reuc_entry->mode[2], &reuc_entry->oid[2])) < 0)
+ goto done;
+ }
+ }
+
+ if (data->names) {
+ git_vector_foreach(data->names, i, name_entry) {
+ if ((error = git_index_name_add(data->index, name_entry->ancestor,
+ name_entry->ours, name_entry->theirs)) < 0)
+ goto done;
+ }
+ }
+
+done:
+ return error;
+}
static void checkout_data_clear(checkout_data *data)
{
@@ -1920,6 +1999,7 @@ static int checkout_data_init(
return error;
data->repo = repo;
+ data->target = target;
GITERR_CHECK_VERSION(
proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options");
@@ -1944,15 +2024,15 @@ static int checkout_data_init(
(error = git_config_refresh(cfg)) < 0)
goto cleanup;
- /* if we are checking out the index, don't reload,
- * otherwise get index and force reload
+ /* Get the repository index and reload it (unless we're checking
+ * out the index; then it has the changes we're trying to check
+ * out and those should not be overwritten.)
*/
- if ((data->index = git_iterator_get_index(target)) != NULL) {
- GIT_REFCOUNT_INC(data->index);
- } else {
- /* otherwise, grab and reload the index */
- if ((error = git_repository_index(&data->index, data->repo)) < 0 ||
- (error = git_index_read(data->index, true)) < 0)
+ if ((error = git_repository_index(&data->index, data->repo)) < 0)
+ goto cleanup;
+
+ if (data->index != git_iterator_get_index(target)) {
+ if ((error = git_index_read(data->index, true)) < 0)
goto cleanup;
/* cannot checkout if unresolved conflicts exist */
@@ -1964,7 +2044,7 @@ static int checkout_data_init(
goto cleanup;
}
- /* clean conflict data when doing a tree or commit checkout */
+ /* clean conflict data in the current index */
git_index_name_clear(data->index);
git_index_reuc_clear(data->index);
}
@@ -2064,6 +2144,7 @@ int git_checkout_iterator(
diff_opts.flags =
GIT_DIFF_INCLUDE_UNMODIFIED |
+ GIT_DIFF_INCLUDE_UNREADABLE |
GIT_DIFF_INCLUDE_UNTRACKED |
GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
GIT_DIFF_INCLUDE_IGNORED |
@@ -2133,6 +2214,10 @@ int git_checkout_iterator(
(error = checkout_create_conflicts(&data)) < 0)
goto cleanup;
+ if (data.index != git_iterator_get_index(target) &&
+ (error = checkout_extensions_update_index(&data)) < 0)
+ goto cleanup;
+
assert(data.completed_steps == data.total_steps);
cleanup:
@@ -2155,7 +2240,7 @@ int git_checkout_index(
git_index *index,
const git_checkout_options *opts)
{
- int error;
+ int error, owned = 0;
git_iterator *index_i;
if (!index && !repo) {
@@ -2163,10 +2248,16 @@ int git_checkout_index(
"Must provide either repository or index to checkout");
return -1;
}
- if (index && repo && git_index_owner(index) != repo) {
+
+ if (index && repo &&
+ git_index_owner(index) &&
+ git_index_owner(index) != repo) {
giterr_set(GITERR_CHECKOUT,
"Index to checkout does not match repository");
return -1;
+ } else if(index && repo && !git_index_owner(index)) {
+ GIT_REFCOUNT_OWN(index, repo);
+ owned = 1;
}
if (!repo)
@@ -2179,6 +2270,9 @@ int git_checkout_index(
if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL)))
error = git_checkout_iterator(index_i, opts);
+ if (owned)
+ GIT_REFCOUNT_OWN(index, NULL);
+
git_iterator_free(index_i);
git_index_free(index);
@@ -2242,14 +2336,9 @@ int git_checkout_head(
return git_checkout_tree(repo, NULL, opts);
}
-int git_checkout_init_opts(git_checkout_options* opts, int version)
+int git_checkout_init_options(git_checkout_options *opts, unsigned int version)
{
- if (version != GIT_CHECKOUT_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_checkout_options", version);
- return -1;
- } else {
- git_checkout_options o = GIT_CHECKOUT_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/cherrypick.c b/src/cherrypick.c
index 6a5ca834c..e58d0ab4c 100644
--- a/src/cherrypick.c
+++ b/src/cherrypick.c
@@ -17,9 +17,9 @@
#include "git2/commit.h"
#include "git2/sys/commit.h"
-#define GIT_CHERRY_PICK_FILE_MODE 0666
+#define GIT_CHERRYPICK_FILE_MODE 0666
-static int write_cherry_pick_head(
+static int write_cherrypick_head(
git_repository *repo,
const char *commit_oidstr)
{
@@ -27,8 +27,8 @@ static int write_cherry_pick_head(
git_buf file_path = GIT_BUF_INIT;
int error = 0;
- if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRY_PICK_HEAD_FILE)) >= 0 &&
- (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) >= 0 &&
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file);
@@ -49,7 +49,7 @@ static int write_merge_msg(
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
- (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
goto cleanup;
@@ -64,10 +64,10 @@ cleanup:
return error;
}
-static int cherry_pick_normalize_opts(
+static int cherrypick_normalize_opts(
git_repository *repo,
- git_cherry_pick_options *opts,
- const git_cherry_pick_options *given,
+ git_cherrypick_options *opts,
+ const git_cherrypick_options *given,
const char *their_label)
{
int error = 0;
@@ -77,10 +77,10 @@ static int cherry_pick_normalize_opts(
GIT_UNUSED(repo);
if (given != NULL)
- memcpy(opts, given, sizeof(git_cherry_pick_options));
+ memcpy(opts, given, sizeof(git_cherrypick_options));
else {
- git_cherry_pick_options default_opts = GIT_CHERRY_PICK_OPTIONS_INIT;
- memcpy(opts, &default_opts, sizeof(git_cherry_pick_options));
+ git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT;
+ memcpy(opts, &default_opts, sizeof(git_cherrypick_options));
}
if (!opts->checkout_opts.checkout_strategy)
@@ -95,14 +95,14 @@ static int cherry_pick_normalize_opts(
return error;
}
-static int cherry_pick_state_cleanup(git_repository *repo)
+static int cherrypick_state_cleanup(git_repository *repo)
{
- const char *state_files[] = { GIT_CHERRY_PICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
+ const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
}
-static int cherry_pick_seterr(git_commit *commit, const char *fmt)
+static int cherrypick_seterr(git_commit *commit, const char *fmt)
{
char commit_oidstr[GIT_OID_HEXSZ + 1];
@@ -112,71 +112,71 @@ static int cherry_pick_seterr(git_commit *commit, const char *fmt)
return -1;
}
-int git_cherry_pick_commit(
+int git_cherrypick_commit(
git_index **out,
git_repository *repo,
- git_commit *cherry_pick_commit,
+ git_commit *cherrypick_commit,
git_commit *our_commit,
unsigned int mainline,
const git_merge_options *merge_opts)
{
git_commit *parent_commit = NULL;
- git_tree *parent_tree = NULL, *our_tree = NULL, *cherry_pick_tree = NULL;
+ git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL;
int parent = 0, error = 0;
- assert(out && repo && cherry_pick_commit && our_commit);
+ assert(out && repo && cherrypick_commit && our_commit);
- if (git_commit_parentcount(cherry_pick_commit) > 1) {
+ if (git_commit_parentcount(cherrypick_commit) > 1) {
if (!mainline)
- return cherry_pick_seterr(cherry_pick_commit,
+ return cherrypick_seterr(cherrypick_commit,
"Mainline branch is not specified but %s is a merge commit");
parent = mainline;
} else {
if (mainline)
- return cherry_pick_seterr(cherry_pick_commit,
+ return cherrypick_seterr(cherrypick_commit,
"Mainline branch specified but %s is not a merge commit");
- parent = git_commit_parentcount(cherry_pick_commit);
+ parent = git_commit_parentcount(cherrypick_commit);
}
if (parent &&
- ((error = git_commit_parent(&parent_commit, cherry_pick_commit, (parent - 1))) < 0 ||
+ ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 ||
(error = git_commit_tree(&parent_tree, parent_commit)) < 0))
goto done;
- if ((error = git_commit_tree(&cherry_pick_tree, cherry_pick_commit)) < 0 ||
+ if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 ||
(error = git_commit_tree(&our_tree, our_commit)) < 0)
goto done;
- error = git_merge_trees(out, repo, parent_tree, our_tree, cherry_pick_tree, merge_opts);
+ error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts);
done:
git_tree_free(parent_tree);
git_tree_free(our_tree);
- git_tree_free(cherry_pick_tree);
+ git_tree_free(cherrypick_tree);
git_commit_free(parent_commit);
return error;
}
-int git_cherry_pick(
+int git_cherrypick(
git_repository *repo,
git_commit *commit,
- const git_cherry_pick_options *given_opts)
+ const git_cherrypick_options *given_opts)
{
- git_cherry_pick_options opts;
+ git_cherrypick_options opts;
git_reference *our_ref = NULL;
git_commit *our_commit = NULL;
char commit_oidstr[GIT_OID_HEXSZ + 1];
const char *commit_msg, *commit_summary;
git_buf their_label = GIT_BUF_INIT;
- git_index *index_new = NULL, *index_repo = NULL;
+ git_index *index_new = NULL;
int error = 0;
assert(repo && commit);
- GITERR_CHECK_VERSION(given_opts, GIT_CHERRY_PICK_OPTIONS_VERSION, "git_cherry_pick_options");
+ GITERR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options");
if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0)
return error;
@@ -191,25 +191,22 @@ int git_cherry_pick(
if ((error = write_merge_msg(repo, commit_msg)) < 0 ||
(error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 ||
- (error = cherry_pick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
- (error = write_cherry_pick_head(repo, commit_oidstr)) < 0 ||
+ (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
+ (error = write_cherrypick_head(repo, commit_oidstr)) < 0 ||
(error = git_repository_head(&our_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
- (error = git_cherry_pick_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
- (error = git_merge__indexes(repo, index_new)) < 0 ||
- (error = git_repository_index(&index_repo, repo)) < 0 ||
- (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
- (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0)
+ (error = git_cherrypick_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
+ (error = git_merge__check_result(repo, index_new)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 ||
+ (error = git_checkout_index(repo, index_new, &opts.checkout_opts)) < 0)
goto on_error;
-
goto done;
on_error:
- cherry_pick_state_cleanup(repo);
+ cherrypick_state_cleanup(repo);
done:
git_index_free(index_new);
- git_index_free(index_repo);
git_commit_free(our_commit);
git_reference_free(our_ref);
git_buf_free(&their_label);
@@ -217,14 +214,10 @@ done:
return error;
}
-int git_cherry_pick_init_opts(git_cherry_pick_options* opts, int version)
+int git_cherrypick_init_options(
+ git_cherrypick_options *opts, unsigned int version)
{
- if (version != GIT_CHERRY_PICK_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_cherry_pick_options", version);
- return -1;
- } else {
- git_cherry_pick_options o = GIT_CHERRY_PICK_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/clone.c b/src/clone.c
index 62f103561..43b839003 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -22,6 +22,9 @@
#include "refs.h"
#include "path.h"
#include "repository.h"
+#include "odb.h"
+
+static int clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature);
static int create_branch(
git_reference **branch,
@@ -105,54 +108,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 +116,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 +131,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;
}
@@ -182,76 +146,58 @@ static int update_head_to_remote(
{
int error = 0;
size_t refs_len;
- git_refspec dummy_spec;
+ git_refspec *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;
- /* Did we just clone an empty repository? */
- if (refs_len == 0)
+ /* We cloned an empty repository or one with an unborn HEAD */
+ if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE))
return setup_tracking_config(
repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
- /* Get the remote's HEAD. This is always the first ref in the list. */
+ /* We know we have HEAD, let's see where it points */
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;
- if (head_info.refspec == NULL) {
- memset(&dummy_spec, 0, sizeof(git_refspec));
- head_info.refspec = &dummy_spec;
+ error = git_remote_default_branch(&branch, remote);
+ if (error == GIT_ENOTFOUND) {
+ error = git_repository_set_head_detached(
+ repo, remote_head_id, signature, reflog_message);
+ goto cleanup;
}
- /* 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)
- 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;
+ refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch));
- 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);
+ if (refspec == NULL) {
+ giterr_set(GITERR_NET, "the remote's default branch does not fit the refspec configuration");
+ error = GIT_EINVALIDSPEC;
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)
+ /* Determine the remote tracking reference name from the local master */
+ if ((error = git_refspec_transform(
+ &remote_master_name,
+ refspec,
+ git_buf_cstr(&branch))) < 0)
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);
- } else {
- error = git_repository_set_head_detached(
- repo, &head_info.remote_head_oid, signature, reflog_message);
- }
+ error = update_head_to_new_branch(
+ repo,
+ remote_head_id,
+ git_buf_cstr(&branch),
+ signature, reflog_message);
cleanup:
git_buf_free(&remote_master_name);
- git_buf_free(&head_info.branchname);
+ git_buf_free(&branch);
+
return error;
}
@@ -284,6 +230,29 @@ cleanup:
return retcode;
}
+static int default_repository_create(git_repository **out, const char *path, int bare, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ return git_repository_init(out, path, bare);
+}
+
+static int default_remote_create(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url,
+ void *payload)
+{
+ int error;
+ git_remote_callbacks *callbacks = payload;
+
+ if ((error = git_remote_create(out, repo, name, url)) < 0)
+ return error;
+
+ return git_remote_set_callbacks(*out, callbacks);
+}
+
/*
* submodules?
*/
@@ -296,16 +265,24 @@ static int create_and_configure_origin(
{
int error;
git_remote *origin = NULL;
- const char *name;
+ char buf[GIT_PATH_MAX];
+ git_remote_create_cb remote_create = options->remote_cb;
+ void *payload = options->remote_cb_payload;
- name = options->remote_name ? options->remote_name : "origin";
- if ((error = git_remote_create(&origin, repo, name, url)) < 0)
- goto on_error;
+ /* 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;
+ }
- if (options->ignore_cert_errors)
- git_remote_check_cert(origin, 0);
+ if (!remote_create) {
+ remote_create = default_remote_create;
+ payload = (void *)&options->remote_callbacks;
+ }
- if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0)
+ if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0)
goto on_error;
if ((error = git_remote_save(origin)) < 0)
@@ -336,57 +313,87 @@ 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;
+}
+
+static int clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature)
{
- int error = 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);
+ git_remote_free(remote);
+ git_buf_free(&reflog_message);
+
+ return error;
+}
+
+int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local)
+{
+ git_buf fromurl = GIT_BUF_INIT;
+ const char *path = url_or_path;
+ bool is_url, is_local;
+
+ if (local == GIT_CLONE_NO_LOCAL)
+ return 0;
- /* Go back to the original refspecs */
- {
- int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs);
- if (!error)
- error = error_alt;
+ if ((is_url = git_path_is_local_file_url(url_or_path)) != 0) {
+ if (git_path_fromurl(&fromurl, url_or_path) < 0) {
+ is_local = -1;
+ goto done;
+ }
+
+ path = fromurl.ptr;
}
- git_strarray_free(&refspecs);
- git_buf_free(&reflog_message);
+ is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) &&
+ git_path_isdir(path);
- return error;
+done:
+ git_buf_free(&fromurl);
+ return is_local;
}
int git_clone(
@@ -400,6 +407,7 @@ int git_clone(
git_remote *origin;
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
+ git_repository_create_cb repository_cb;
assert(out && url && local_path);
@@ -419,12 +427,28 @@ int git_clone(
if (git_path_exists(local_path))
rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
- if ((error = git_repository_init(&repo, local_path, options.bare)) < 0)
+ if (options.repository_cb)
+ repository_cb = options.repository_cb;
+ else
+ repository_cb = default_repository_create;
+
+ if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0)
return error;
if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
- error = git_clone_into(
- repo, origin, &options.checkout_opts, options.checkout_branch, options.signature);
+ int clone_local = git_clone__should_clone_local(url, options.local);
+ int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
+
+ if (clone_local == 1)
+ error = clone_local_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, link, options.signature);
+ else if (clone_local == 0)
+ error = clone_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, options.signature);
+ else
+ error = -1;
git_remote_free(origin);
}
@@ -445,14 +469,100 @@ int git_clone(
return error;
}
-int git_clone_init_options(git_clone_options* opts, int version)
+int git_clone_init_options(git_clone_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ 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)
{
- if (version != GIT_CLONE_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_clone_options", version);
+#ifdef GIT_WIN32
+ GIT_UNUSED(src);
+ GIT_UNUSED(dst);
+ GIT_UNUSED(link);
+ 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
+}
+
+static int clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature)
+{
+ int error, flags;
+ git_repository *src;
+ 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;
- } else {
- git_clone_options o = GIT_CLONE_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
}
+
+ /*
+ * 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/commit.c b/src/commit.c
index 255debe82..227d5c4a5 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -34,6 +34,35 @@ void git_commit__free(void *_commit)
git__free(commit);
}
+static int update_ref_for_commit(git_repository *repo, git_reference *ref, const char *update_ref, const git_oid *id, const git_signature *committer)
+{
+ git_reference *ref2 = NULL;
+ int error;
+ git_commit *c;
+ const char *shortmsg;
+ git_buf reflog_msg = GIT_BUF_INIT;
+
+ if ((error = git_commit_lookup(&c, repo, id)) < 0) {
+ return error;
+ }
+
+ shortmsg = git_commit_summary(c);
+ git_buf_printf(&reflog_msg, "commit%s: %s",
+ git_commit_parentcount(c) == 0 ? " (initial)" : "",
+ shortmsg);
+ git_commit_free(c);
+
+ if (ref) {
+ error = git_reference_set_target(&ref2, ref, id, committer, git_buf_cstr(&reflog_msg));
+ git_reference_free(ref2);
+ } else {
+ error = git_reference__update_terminal(repo, update_ref, id, committer, git_buf_cstr(&reflog_msg));
+ }
+
+ git_buf_free(&reflog_msg);
+ return error;
+}
+
int git_commit_create_from_callback(
git_oid *id,
git_repository *repo,
@@ -46,6 +75,9 @@ int git_commit_create_from_callback(
git_commit_parent_callback parent_cb,
void *parent_payload)
{
+ git_reference *ref = NULL;
+ int error = 0, matched_parent = 0;
+ const git_oid *current_id = NULL;
git_buf commit = GIT_BUF_INIT;
size_t i = 0;
git_odb *odb;
@@ -53,10 +85,31 @@ int git_commit_create_from_callback(
assert(id && repo && tree && parent_cb);
+ if (update_ref) {
+ error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ }
+ giterr_clear();
+
+ if (ref)
+ current_id = git_reference_target(ref);
+
git_oid__writebuf(&commit, "tree ", tree);
- while ((parent = parent_cb(i++, parent_payload)) != NULL)
+ while ((parent = parent_cb(i, parent_payload)) != NULL) {
git_oid__writebuf(&commit, "parent ", parent);
+ if (i == 0 && current_id && git_oid_equal(current_id, parent))
+ matched_parent = 1;
+ i++;
+ }
+
+ if (ref && !matched_parent) {
+ git_reference_free(ref);
+ git_buf_free(&commit);
+ giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent");
+ return GIT_EMODIFIED;
+ }
git_signature__writebuf(&commit, "author ", author);
git_signature__writebuf(&commit, "committer ", committer);
@@ -78,24 +131,8 @@ int git_commit_create_from_callback(
git_buf_free(&commit);
if (update_ref != NULL) {
- int error;
- git_commit *c;
- const char *shortmsg;
- git_buf reflog_msg = GIT_BUF_INIT;
-
- if (git_commit_lookup(&c, repo, id) < 0)
- goto on_error;
-
- shortmsg = git_commit_summary(c);
- git_buf_printf(&reflog_msg, "commit%s: %s",
- git_commit_parentcount(c) == 0 ? " (initial)" : "",
- shortmsg);
- git_commit_free(c);
-
- error = git_reference__update_terminal(repo, update_ref, id,
- committer, git_buf_cstr(&reflog_msg));
-
- git_buf_free(&reflog_msg);
+ error = update_ref_for_commit(repo, ref, update_ref, id, committer);
+ git_reference_free(ref);
return error;
}
@@ -242,6 +279,8 @@ int git_commit_amend(
{
git_repository *repo;
git_oid tree_id;
+ git_reference *ref;
+ int error;
assert(id && commit_to_amend);
@@ -266,9 +305,27 @@ int git_commit_amend(
git_oid_cpy(&tree_id, git_tree_id(tree));
}
- return git_commit_create_from_callback(
- id, repo, update_ref, author, committer, message_encoding, message,
+ if (update_ref) {
+ if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
+ return error;
+
+ if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
+ git_reference_free(ref);
+ giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch");
+ return -1;
+ }
+ }
+
+ error = git_commit_create_from_callback(
+ id, repo, NULL, author, committer, message_encoding, message,
&tree_id, commit_parent_for_amend, (void *)commit_to_amend);
+
+ if (!error && update_ref) {
+ error = update_ref_for_commit(repo, ref, NULL, id, committer);
+ git_reference_free(ref);
+ }
+
+ return error;
}
int git_commit__parse(void *_commit, git_odb_object *odb_obj)
diff --git a/src/common.h b/src/common.h
index 9c8bdc18a..807e5fa39 100644
--- a/src/common.h
+++ b/src/common.h
@@ -44,6 +44,7 @@
#else
# include <unistd.h>
+# include <strings.h>
# ifdef GIT_THREADS
# include <pthread.h>
# include <sched.h>
@@ -169,6 +170,11 @@ GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int v
}
#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)
+#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \
+ TYPE _tmpl = TPL; \
+ GITERR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \
+ memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0)
+
/* NOTE: other giterr functions are in the public errors.h header file */
#include "util.h"
diff --git a/src/config.c b/src/config.c
index b3168f735..8a0fb653c 100644
--- a/src/config.c
+++ b/src/config.c
@@ -137,6 +137,38 @@ int git_config_open_ondisk(git_config **out, const char *path)
return error;
}
+int git_config_snapshot(git_config **out, git_config *in)
+{
+ int error = 0;
+ size_t i;
+ file_internal *internal;
+ git_config *config;
+
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
+ return -1;
+
+ git_vector_foreach(&in->files, i, internal) {
+ git_config_backend *b;
+
+ if ((error = internal->file->snapshot(&b, internal->file)) < 0)
+ break;
+
+ if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) {
+ b->free(b);
+ break;
+ }
+ }
+
+ if (error < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
+}
+
static int find_internal_file_by_level(
file_internal **internal_out,
const git_config *cfg,
@@ -967,16 +999,19 @@ void git_config_iterator_free(git_config_iterator *iter)
int git_config_find_global(git_buf *path)
{
+ git_buf_sanitize(path);
return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL);
}
int git_config_find_xdg(git_buf *path)
{
+ git_buf_sanitize(path);
return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);
}
int git_config_find_system(git_buf *path)
{
+ git_buf_sanitize(path);
return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
}
@@ -984,24 +1019,22 @@ int git_config__global_location(git_buf *buf)
{
const git_buf *paths;
const char *sep, *start;
- size_t len;
if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0)
return -1;
/* no paths, so give up */
- if (git_buf_len(paths) == 0)
+ if (!paths || !git_buf_len(paths))
return -1;
- start = git_buf_cstr(paths);
- sep = strchr(start, GIT_PATH_LIST_SEPARATOR);
-
- if (sep)
- len = sep - start;
- else
- len = paths->size;
+ /* find unescaped separator or end of string */
+ for (sep = start = git_buf_cstr(paths); *sep; ++sep) {
+ if (*sep == GIT_PATH_LIST_SEPARATOR &&
+ (sep <= start || sep[-1] != '\\'))
+ break;
+ }
- if (git_buf_set(buf, start, len) < 0)
+ if (git_buf_set(buf, start, (size_t)(sep - start)) < 0)
return -1;
return git_buf_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL);
@@ -1144,7 +1177,7 @@ int git_config_parse_int64(int64_t *out, const char *value)
}
fail_parse:
- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value ? value : "(null)");
return -1;
}
@@ -1164,7 +1197,7 @@ int git_config_parse_int32(int32_t *out, const char *value)
return 0;
fail_parse:
- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value ? value : "(null)");
return -1;
}
@@ -1276,14 +1309,9 @@ cleanup:
return error;
}
-int git_config_init_backend(git_config_backend* backend, int version)
+int git_config_init_backend(git_config_backend *backend, unsigned int version)
{
- if (version != GIT_CONFIG_BACKEND_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_config_backend", version);
- return -1;
- } else {
- git_config_backend b = GIT_CONFIG_BACKEND_INIT;
- memcpy(backend, &b, sizeof(b));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT);
+ return 0;
}
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 bb26aa8a3..7106f18db 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -26,7 +26,7 @@ GIT__USE_STRMAP;
typedef struct cvar_t {
struct cvar_t *next;
git_config_entry *entry;
- int included; /* whether this is part of [include] */
+ bool included; /* whether this is part of [include] */
} cvar_t;
typedef struct git_config_file_iter {
@@ -87,28 +87,54 @@ struct reader {
};
typedef struct {
+ git_atomic refcount;
+ git_strmap *values;
+} refcounted_strmap;
+
+typedef struct {
git_config_backend parent;
+ /* mutex to coordinate accessing the values */
+ git_mutex values_mutex;
+ refcounted_strmap *values;
+ int readonly;
+} diskfile_header;
- git_strmap *values;
+typedef struct {
+ diskfile_header header;
+
+ git_config_level_t level;
git_array_t(struct reader) readers;
char *file_path;
-
- git_config_level_t level;
} diskfile_backend;
-static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
+typedef struct {
+ diskfile_header header;
+
+ diskfile_backend *snapshot_from;
+} diskfile_readonly_backend;
+
+static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
static int parse_variable(struct reader *reader, char **var_name, char **var_value);
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
static char *escape_value(const char *ptr);
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
+static int config_snapshot(git_config_backend **out, git_config_backend *in);
+
static void set_parse_error(struct reader *reader, int col, const char *error_str)
{
giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
error_str, reader->file_path, reader->line_number, col);
}
+static int config_error_readonly(void)
+{
+ giterr_set(GITERR_CONFIG, "this backend is read-only");
+ return -1;
+}
+
static void cvar_free(cvar_t *var)
{
if (var == NULL)
@@ -120,18 +146,6 @@ static void cvar_free(cvar_t *var)
git__free(var);
}
-static int cvar_length(cvar_t *var)
-{
- int length = 0;
-
- while (var) {
- length++;
- var = var->next;
- }
-
- return length;
-}
-
int git_config_file_normalize_section(char *start, char *end)
{
char *scan;
@@ -155,6 +169,30 @@ int git_config_file_normalize_section(char *start, char *end)
return 0;
}
+/* Add or append the new config option */
+static int append_entry(git_strmap *values, cvar_t *var)
+{
+ git_strmap_iter pos;
+ cvar_t *existing;
+ int error = 0;
+
+ pos = git_strmap_lookup_index(values, var->entry->name);
+ if (!git_strmap_valid_index(values, pos)) {
+ git_strmap_insert(values, var->entry->name, var, error);
+ } else {
+ existing = git_strmap_value_at(values, pos);
+ while (existing->next != NULL) {
+ existing = existing->next;
+ }
+ existing->next = var;
+ }
+
+ if (error > 0)
+ error = 0;
+
+ return error;
+}
+
static void free_vars(git_strmap *values)
{
cvar_t *var = NULL;
@@ -172,6 +210,55 @@ static void free_vars(git_strmap *values)
git_strmap_free(values);
}
+static void refcounted_strmap_free(refcounted_strmap *map)
+{
+ if (!map)
+ return;
+
+ if (git_atomic_dec(&map->refcount) != 0)
+ return;
+
+ free_vars(map->values);
+ git__free(map);
+}
+
+/**
+ * Take the current values map from the backend and increase its
+ * refcount. This is its own function to make sure we use the mutex to
+ * avoid the map pointer from changing under us.
+ */
+static refcounted_strmap *refcounted_strmap_take(diskfile_header *h)
+{
+ refcounted_strmap *map;
+
+ git_mutex_lock(&h->values_mutex);
+
+ map = h->values;
+ git_atomic_inc(&map->refcount);
+
+ git_mutex_unlock(&h->values_mutex);
+
+ return map;
+}
+
+static int refcounted_strmap_alloc(refcounted_strmap **out)
+{
+ refcounted_strmap *map;
+ int error;
+
+ map = git__calloc(1, sizeof(refcounted_strmap));
+ GITERR_CHECK_ALLOC(map);
+
+ git_atomic_set(&map->refcount, 1);
+
+ if ((error = git_strmap_alloc(&map->values)) < 0)
+ git__free(map);
+ else
+ *out = map;
+
+ return error;
+}
+
static int config_open(git_config_backend *cfg, git_config_level_t level)
{
int res;
@@ -180,13 +267,13 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
b->level = level;
- if ((res = git_strmap_alloc(&b->values)) < 0)
+ if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
return res;
git_array_init(b->readers);
reader = git_array_alloc(b->readers);
if (!reader) {
- git_strmap_free(b->values);
+ refcounted_strmap_free(b->header.values);
return -1;
}
memset(reader, 0, sizeof(struct reader));
@@ -202,9 +289,9 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
if (res == GIT_ENOTFOUND)
return 0;
- if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) {
- free_vars(b->values);
- b->values = NULL;
+ if (res < 0 || (res = config_parse(b->header.values->values, b, reader, level, 0)) < 0) {
+ refcounted_strmap_free(b->header.values);
+ b->header.values = NULL;
}
reader = git_array_get(b->readers, 0);
@@ -213,44 +300,62 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
return res;
}
+/* The meat of the refresh, as we want to use it in different places */
+static int config__refresh(git_config_backend *cfg)
+{
+ refcounted_strmap *values = NULL, *tmp;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ struct reader *reader = NULL;
+ int error = 0;
+
+ if ((error = refcounted_strmap_alloc(&values)) < 0)
+ 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;
+
+ git_mutex_lock(&b->header.values_mutex);
+
+ tmp = b->header.values;
+ b->header.values = values;
+ values = tmp;
+
+ git_mutex_unlock(&b->header.values_mutex);
+
+out:
+ refcounted_strmap_free(values);
+ if (reader)
+ git_buf_free(&reader->buffer);
+ return error;
+}
+
static int config_refresh(git_config_backend *cfg)
{
- int res = 0, updated = 0, any_updated = 0;
+ int error = 0, updated = 0, any_updated = 0;
diskfile_backend *b = (diskfile_backend *)cfg;
- git_strmap *old_values;
struct reader *reader = NULL;
uint32_t i;
for (i = 0; i < git_array_size(b->readers); i++) {
reader = git_array_get(b->readers, i);
-
- res = git_futils_readbuffer_updated(
+ error = git_futils_readbuffer_updated(
&reader->buffer, reader->file_path,
&reader->file_mtime, &reader->file_size, &updated);
- if (res < 0)
- return (res == GIT_ENOTFOUND) ? 0 : res;
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
if (updated)
any_updated = 1;
}
if (!any_updated)
- return (res == GIT_ENOTFOUND) ? 0 : res;
-
- /* need to reload - store old values and prep for reload */
- old_values = b->values;
- if ((res = git_strmap_alloc(&b->values)) < 0) {
- b->values = old_values;
- } else if ((res = config_parse(b, reader, b->level, 0)) < 0) {
- free_vars(b->values);
- b->values = old_values;
- } else {
- free_vars(old_values);
- }
+ return (error == GIT_ENOTFOUND) ? 0 : error;
- git_buf_free(&reader->buffer);
- return res;
+ return config__refresh(cfg);
}
static void backend_free(git_config_backend *_backend)
@@ -268,13 +373,15 @@ static void backend_free(git_config_backend *_backend)
git_array_clear(backend->readers);
git__free(backend->file_path);
- free_vars(backend->values);
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
git__free(backend);
}
static void config_iterator_free(
git_config_iterator* iter)
{
+ iter->backend->free(iter->backend);
git__free(iter);
}
@@ -283,12 +390,13 @@ static int config_iterator_next(
git_config_iterator *iter)
{
git_config_file_iter *it = (git_config_file_iter *) iter;
- diskfile_backend *b = (diskfile_backend *) it->parent.backend;
+ diskfile_header *h = (diskfile_header *) it->parent.backend;
+ git_strmap *values = h->values->values;
int err = 0;
cvar_t * var;
if (it->next_var == NULL) {
- err = git_strmap_next((void**) &var, &(it->iter), b->values);
+ err = git_strmap_next((void**) &var, &(it->iter), values);
} else {
var = it->next_var;
}
@@ -308,15 +416,28 @@ static int config_iterator_new(
git_config_iterator **iter,
struct git_config_backend* backend)
{
- diskfile_backend *b = (diskfile_backend *)backend;
- git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter));
+ diskfile_header *h;
+ git_config_file_iter *it;
+ git_config_backend *snapshot;
+ diskfile_backend *b = (diskfile_backend *) backend;
+ int error;
+
+ if ((error = config_snapshot(&snapshot, backend)) < 0)
+ return error;
- GIT_UNUSED(b);
+ if ((error = snapshot->open(snapshot, b->level)) < 0)
+ return error;
+ it = git__calloc(1, sizeof(git_config_file_iter));
GITERR_CHECK_ALLOC(it);
- it->parent.backend = backend;
- it->iter = git_strmap_begin(b->values);
+ h = (diskfile_header *)snapshot;
+
+ /* strmap_begin() is currently a macro returning 0 */
+ GIT_UNUSED(h);
+
+ it->parent.backend = snapshot;
+ it->iter = git_strmap_begin(h->values);
it->next_var = NULL;
it->parent.next = config_iterator_next;
@@ -328,8 +449,9 @@ static int config_iterator_new(
static int config_set(git_config_backend *cfg, const char *name, const char *value)
{
- cvar_t *var = NULL, *old_var = NULL;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
char *key, *esc_value = NULL;
khiter_t pos;
int rval, ret;
@@ -337,93 +459,82 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val
if ((rval = git_config__normalize_name(name, &key)) < 0)
return rval;
+ map = refcounted_strmap_take(&b->header);
+ values = map->values;
+
/*
* Try to find it in the existing values and update it if it
* only has one value.
*/
- pos = git_strmap_lookup_index(b->values, key);
- if (git_strmap_valid_index(b->values, pos)) {
- cvar_t *existing = git_strmap_value_at(b->values, pos);
- char *tmp = NULL;
-
- git__free(key);
+ pos = git_strmap_lookup_index(values, key);
+ if (git_strmap_valid_index(values, pos)) {
+ cvar_t *existing = git_strmap_value_at(values, pos);
if (existing->next != NULL) {
giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
- return -1;
+ ret = -1;
+ goto out;
}
/* don't update if old and new values already match */
if ((!existing->entry->value && !value) ||
- (existing->entry->value && value && !strcmp(existing->entry->value, value)))
- return 0;
-
- if (value) {
- tmp = git__strdup(value);
- GITERR_CHECK_ALLOC(tmp);
- esc_value = escape_value(value);
- GITERR_CHECK_ALLOC(esc_value);
+ (existing->entry->value && value &&
+ !strcmp(existing->entry->value, value))) {
+ ret = 0;
+ goto out;
}
-
- git__free((void *)existing->entry->value);
- existing->entry->value = tmp;
-
- ret = config_write(b, existing->entry->name, NULL, esc_value);
-
- git__free(esc_value);
- return ret;
}
- var = git__malloc(sizeof(cvar_t));
- GITERR_CHECK_ALLOC(var);
- memset(var, 0x0, sizeof(cvar_t));
- var->entry = git__malloc(sizeof(git_config_entry));
- GITERR_CHECK_ALLOC(var->entry);
- memset(var->entry, 0x0, sizeof(git_config_entry));
-
- var->entry->name = key;
- var->entry->value = NULL;
+ /* No early returns due to sanity checks, let's write it out and refresh */
if (value) {
- var->entry->value = git__strdup(value);
- GITERR_CHECK_ALLOC(var->entry->value);
esc_value = escape_value(value);
GITERR_CHECK_ALLOC(esc_value);
}
- if ((ret = config_write(b, key, NULL, esc_value)) < 0) {
- git__free(esc_value);
- cvar_free(var);
- return ret;
- }
+ if ((ret = config_write(b, key, NULL, esc_value)) < 0)
+ goto out;
- git__free(esc_value);
- git_strmap_insert2(b->values, key, var, old_var, rval);
- if (rval < 0)
- return -1;
- if (old_var != NULL)
- cvar_free(old_var);
+ ret = config_refresh(cfg);
- return 0;
+out:
+ refcounted_strmap_free(map);
+ git__free(esc_value);
+ git__free(key);
+ return ret;
}
/*
* Internal function that actually gets the value in string form
*/
-static int config_get(const git_config_backend *cfg, const char *key, const git_config_entry **out)
+static int config_get(git_config_backend *cfg, const char *key, const git_config_entry **out)
{
- diskfile_backend *b = (diskfile_backend *)cfg;
- khiter_t pos = git_strmap_lookup_index(b->values, key);
+ diskfile_header *h = (diskfile_header *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ khiter_t pos;
cvar_t *var;
+ int error;
+
+ if (!h->readonly && ((error = config_refresh(cfg)) < 0))
+ return error;
+
+ map = refcounted_strmap_take(h);
+ values = map->values;
+
+ pos = git_strmap_lookup_index(values, key);
/* no error message; the config system will write one */
- if (!git_strmap_valid_index(b->values, pos))
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
return GIT_ENOTFOUND;
+ }
- var = git_strmap_value_at(b->values, pos);
+ var = git_strmap_value_at(values, pos);
while (var->next)
var = var->next;
+ refcounted_strmap_free(map);
*out = var->entry;
return 0;
}
@@ -431,9 +542,9 @@ static int config_get(const git_config_backend *cfg, const char *key, const git_
static int config_set_multivar(
git_config_backend *cfg, const char *name, const char *regexp, const char *value)
{
- int replaced = 0;
- cvar_t *var, *newvar;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
char *key;
regex_t preg;
int result;
@@ -444,62 +555,33 @@ static int config_set_multivar(
if ((result = git_config__normalize_name(name, &key)) < 0)
return result;
- pos = git_strmap_lookup_index(b->values, key);
- if (!git_strmap_valid_index(b->values, pos)) {
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+ if (!git_strmap_valid_index(values, pos)) {
/* If we don't have it, behave like a normal set */
result = config_set(cfg, name, value);
+ refcounted_strmap_free(map);
git__free(key);
return result;
}
- var = git_strmap_value_at(b->values, pos);
-
result = regcomp(&preg, regexp, REG_EXTENDED);
if (result < 0) {
- git__free(key);
giterr_set_regex(&preg, result);
- regfree(&preg);
- return -1;
- }
-
- for (;;) {
- if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
- char *tmp = git__strdup(value);
- GITERR_CHECK_ALLOC(tmp);
-
- git__free((void *)var->entry->value);
- var->entry->value = tmp;
- replaced = 1;
- }
-
- if (var->next == NULL)
- break;
-
- var = var->next;
+ result = -1;
+ goto out;
}
- /* If we've reached the end of the variables and we haven't found it yet, we need to append it */
- if (!replaced) {
- newvar = git__malloc(sizeof(cvar_t));
- GITERR_CHECK_ALLOC(newvar);
- memset(newvar, 0x0, sizeof(cvar_t));
- newvar->entry = git__malloc(sizeof(git_config_entry));
- GITERR_CHECK_ALLOC(newvar->entry);
- memset(newvar->entry, 0x0, sizeof(git_config_entry));
+ /* If we do have it, set call config_write() and reload */
+ if ((result = config_write(b, key, &preg, value)) < 0)
+ goto out;
- newvar->entry->name = git__strdup(var->entry->name);
- GITERR_CHECK_ALLOC(newvar->entry->name);
-
- newvar->entry->value = git__strdup(value);
- GITERR_CHECK_ALLOC(newvar->entry->value);
-
- newvar->entry->level = var->entry->level;
-
- var->next = newvar;
- }
-
- result = config_write(b, key, &preg, value);
+ result = config_refresh(cfg);
+out:
+ refcounted_strmap_free(map);
git__free(key);
regfree(&preg);
@@ -510,6 +592,7 @@ static int config_delete(git_config_backend *cfg, const char *name)
{
cvar_t *var;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map; git_strmap *values;
char *key;
int result;
khiter_t pos;
@@ -517,35 +600,37 @@ static int config_delete(git_config_backend *cfg, const char *name)
if ((result = git_config__normalize_name(name, &key)) < 0)
return result;
- pos = git_strmap_lookup_index(b->values, key);
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
git__free(key);
- if (!git_strmap_valid_index(b->values, pos)) {
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
return GIT_ENOTFOUND;
}
- var = git_strmap_value_at(b->values, pos);
+ var = git_strmap_value_at(values, pos);
+ refcounted_strmap_free(map);
if (var->next != NULL) {
giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
return -1;
}
- git_strmap_delete_at(b->values, pos);
-
- result = config_write(b, var->entry->name, NULL, NULL);
+ if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0)
+ return result;
- cvar_free(var);
- return result;
+ return config_refresh(cfg);
}
static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
- cvar_t *var, *prev = NULL, *new_head = NULL;
- cvar_t **to_delete;
- int to_delete_idx;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
char *key;
regex_t preg;
int result;
@@ -554,66 +639,45 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con
if ((result = git_config__normalize_name(name, &key)) < 0)
return result;
- pos = git_strmap_lookup_index(b->values, key);
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
- if (!git_strmap_valid_index(b->values, pos)) {
- giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ pos = git_strmap_lookup_index(values, key);
+
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
git__free(key);
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
return GIT_ENOTFOUND;
}
- var = git_strmap_value_at(b->values, pos);
+ refcounted_strmap_free(map);
result = regcomp(&preg, regexp, REG_EXTENDED);
if (result < 0) {
- git__free(key);
giterr_set_regex(&preg, result);
- regfree(&preg);
- return -1;
- }
-
- to_delete = git__calloc(cvar_length(var), sizeof(cvar_t *));
- GITERR_CHECK_ALLOC(to_delete);
- to_delete_idx = 0;
-
- while (var != NULL) {
- cvar_t *next = var->next;
-
- if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
- // If we are past the head, reattach previous node to next one,
- // otherwise set the new head for the strmap.
- if (prev != NULL) {
- prev->next = next;
- } else {
- new_head = next;
- }
-
- to_delete[to_delete_idx++] = var;
- } else {
- prev = var;
- }
-
- var = next;
- }
-
- if (new_head != NULL) {
- git_strmap_set_value_at(b->values, pos, new_head);
- } else {
- git_strmap_delete_at(b->values, pos);
+ result = -1;
+ goto out;
}
- if (to_delete_idx > 0)
- result = config_write(b, key, &preg, NULL);
+ if ((result = config_write(b, key, &preg, NULL)) < 0)
+ goto out;
- while (to_delete_idx-- > 0)
- cvar_free(to_delete[to_delete_idx]);
+ result = config_refresh(cfg);
+out:
git__free(key);
- git__free(to_delete);
regfree(&preg);
return result;
}
+static int config_snapshot(git_config_backend **out, git_config_backend *in)
+{
+ diskfile_backend *b = (diskfile_backend *) in;
+
+ return git_config_file__snapshot(out, b);
+}
+
int git_config_file__ondisk(git_config_backend **out, const char *path)
{
diskfile_backend *backend;
@@ -621,20 +685,122 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
backend = git__calloc(1, sizeof(diskfile_backend));
GITERR_CHECK_ALLOC(backend);
- backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
+ 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);
- backend->parent.open = config_open;
- backend->parent.get = config_get;
- backend->parent.set = config_set;
- backend->parent.set_multivar = config_set_multivar;
- backend->parent.del = config_delete;
- backend->parent.del_multivar = config_delete_multivar;
- backend->parent.iterator = config_iterator_new;
- backend->parent.refresh = config_refresh;
- backend->parent.free = backend_free;
+ backend->header.parent.open = config_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set;
+ backend->header.parent.set_multivar = config_set_multivar;
+ backend->header.parent.del = config_delete;
+ backend->header.parent.del_multivar = config_delete_multivar;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.refresh = config_refresh;
+ backend->header.parent.snapshot = config_snapshot;
+ backend->header.parent.free = backend_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
+
+static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
+
+static int config_set_multivar_readonly(
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
+
+static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+
+ return config_error_readonly();
+}
+
+static int config_delete_readonly(git_config_backend *cfg, const char *name)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+
+ return config_error_readonly();
+}
+
+static int config_refresh_readonly(git_config_backend *cfg)
+{
+ GIT_UNUSED(cfg);
+
+ return config_error_readonly();
+}
+
+static void backend_readonly_free(git_config_backend *_backend)
+{
+ diskfile_backend *backend = (diskfile_backend *)_backend;
+
+ if (backend == NULL)
+ return;
+
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
+ git__free(backend);
+}
+
+static int config_readonly_open(git_config_backend *cfg, git_config_level_t level)
+{
+ diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
+ diskfile_backend *src = b->snapshot_from;
+ refcounted_strmap *src_map;
+
+ /* We're just copying data, don't care about the level */
+ GIT_UNUSED(level);
+
+ src_map = refcounted_strmap_take(&src->header);
+ b->header.values = src_map;
+
+ return 0;
+}
+
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
+{
+ diskfile_readonly_backend *backend;
+
+ backend = git__calloc(1, sizeof(diskfile_readonly_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ git_mutex_init(&backend->header.values_mutex);
+
+ backend->snapshot_from = in;
+
+ backend->header.readonly = 1;
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ backend->header.parent.open = config_readonly_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set_readonly;
+ backend->header.parent.set_multivar = config_set_multivar_readonly;
+ backend->header.parent.del = config_delete_readonly;
+ backend->header.parent.del_multivar = config_delete_multivar_readonly;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.refresh = config_refresh_readonly;
+ backend->header.parent.free = backend_readonly_free;
*out = (git_config_backend *)backend;
@@ -1014,16 +1180,15 @@ static int included_path(git_buf *out, const char *dir, const char *path)
return git_path_join_unrooted(out, path, dir, NULL);
}
-static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
+static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
{
int c;
char *current_section = NULL;
char *var_name;
char *var_value;
- cvar_t *var, *existing;
+ cvar_t *var;
git_buf buf = GIT_BUF_INIT;
int result = 0;
- khiter_t pos;
uint32_t reader_idx;
if (depth >= MAX_INCLUDE_DEPTH) {
@@ -1088,21 +1253,13 @@ static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_c
var->entry->level = level;
var->included = !!depth;
- /* Add or append the new config option */
- pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
- if (!git_strmap_valid_index(cfg_file->values, pos)) {
- git_strmap_insert(cfg_file->values, var->entry->name, var, result);
- if (result < 0)
- break;
+
+ if ((result = append_entry(values, var)) < 0)
+ break;
+ else
result = 0;
- } else {
- existing = git_strmap_value_at(cfg_file->values, pos);
- while (existing->next != NULL) {
- existing = existing->next;
- }
- existing->next = var;
- }
+ /* Add or append the new config option */
if (!git__strcmp(var->entry->name, "include.path")) {
struct reader *r;
git_buf path = GIT_BUF_INIT;
@@ -1131,7 +1288,7 @@ static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_c
&r->file_size, NULL)) < 0)
break;
- result = config_parse(cfg_file, r, level, depth+1);
+ result = config_parse(values, cfg_file, r, level, depth+1);
r = git_array_get(cfg_file->readers, index);
git_buf_free(&r->buffer);
@@ -1365,6 +1522,9 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
+ if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
+ git_filebuf_write(&file, "\n", 1);
+
/* And now if we just need to add a variable */
if (!section_matches && write_section(&file, section) < 0)
goto rewrite_fail;
@@ -1374,14 +1534,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
* this, but instead we'll handle it gracefully with an error. */
if (value == NULL) {
giterr_set(GITERR_CONFIG,
- "Race condition when writing a config file (a cvar has been removed)");
+ "race condition when writing a config file (a cvar has been removed)");
goto rewrite_fail;
}
/* If we are here, there is at least a section line */
- if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
- git_filebuf_write(&file, "\n", 1);
-
q = quotes_for_value(value);
git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
}
@@ -1492,7 +1649,7 @@ static int is_multiline_var(const char *str)
}
/* An odd number means last backslash wasn't escaped, so it's multiline */
- return (end > str) && (count & 1);
+ return count & 1;
}
static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
diff --git a/src/crlf.c b/src/crlf.c
index 8be1b9a05..93448760d 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -138,11 +138,24 @@ 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 (ca->safe_crlf && (stats.cr != stats.crlf || stats.lf != stats.crlf)) {
- giterr_set(GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
- git_filter_source_path(src));
- return -1;
+ if (stats.cr != stats.crlf || stats.lf != stats.crlf) {
+ switch (ca->safe_crlf) {
+ case GIT_SAFE_CRLF_FAIL:
+ giterr_set(
+ GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
+ git_filter_source_path(src));
+ return -1;
+ case GIT_SAFE_CRLF_WARN:
+ /* TODO: issue warning when warning API is available */;
+ break;
+ default:
+ break;
+ }
}
/*
@@ -267,12 +280,14 @@ static int crlf_check(
if (ca.crlf_action == GIT_CRLF_GUESS ||
(ca.crlf_action == GIT_CRLF_AUTO &&
git_filter_source_mode(src) == GIT_FILTER_SMUDGE)) {
+
error = git_repository__cvar(
&ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
if (error < 0)
return error;
- if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
+ if (ca.crlf_action == GIT_CRLF_GUESS &&
+ ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
return GIT_PASSTHROUGH;
if (ca.auto_crlf == GIT_AUTO_CRLF_INPUT &&
@@ -285,6 +300,11 @@ static int crlf_check(
&ca.safe_crlf, git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF);
if (error < 0)
return error;
+
+ /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
+ if ((git_filter_source_options(src) & GIT_FILTER_OPT_ALLOW_UNSAFE) &&
+ ca.safe_crlf == GIT_SAFE_CRLF_FAIL)
+ ca.safe_crlf = GIT_SAFE_CRLF_WARN;
}
*payload = git__malloc(sizeof(ca));
diff --git a/src/diff.c b/src/diff.c
index 4b6fbe25a..2691d7ca0 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -92,6 +92,10 @@ static int diff_delta__from_one(
if (status == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
return 0;
+
+ if (status == GIT_DELTA_UNREADABLE &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
+ return 0;
if (!git_pathspec__match(
&diff->pathspec, entry->path,
@@ -196,6 +200,7 @@ static git_diff_delta *diff_delta__last_for_item(
if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
+ case GIT_DELTA_UNREADABLE:
case GIT_DELTA_UNTRACKED:
if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
git_oid__cmp(&delta->new_file.id, &item->id) == 0)
@@ -293,6 +298,10 @@ bool git_diff_delta__should_skip(
(flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
+ if (delta->status == GIT_DELTA_UNREADABLE &&
+ (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
+ return true;
+
return false;
}
@@ -381,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;
@@ -406,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 */
@@ -442,6 +451,14 @@ static int diff_list_apply_options(
diff->new_src = tmp_src;
}
+ /* Unset UPDATE_INDEX unless diffing workdir and index */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
+ (!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR ||
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
+ !(diff->old_src == GIT_ITERATOR_TYPE_INDEX ||
+ diff->new_src == GIT_ITERATOR_TYPE_INDEX)))
+ diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
+
/* if ignore_submodules not explicitly set, check diff config */
if (diff->opts.ignore_submodules <= 0) {
const git_config_entry *entry;
@@ -473,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;
@@ -482,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)
@@ -510,76 +528,106 @@ void git_diff_addref(git_diff *diff)
}
int git_diff__oid_for_file(
- git_repository *repo,
+ git_oid *out,
+ git_diff *diff,
const char *path,
uint16_t mode,
- git_off_t size,
- git_oid *oid)
+ git_off_t size)
+{
+ git_index_entry entry;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.mode = mode;
+ entry.file_size = size;
+ entry.path = (char *)path;
+
+ return git_diff__oid_for_entry(out, diff, &entry, NULL);
+}
+
+int git_diff__oid_for_entry(
+ git_oid *out,
+ git_diff *diff,
+ const git_index_entry *src,
+ const git_oid *update_match)
{
- int result = 0;
+ int error = 0;
git_buf full_path = GIT_BUF_INIT;
+ git_index_entry entry = *src;
+ git_filter_list *fl = NULL;
+
+ memset(out, 0, sizeof(*out));
if (git_buf_joinpath(
- &full_path, git_repository_workdir(repo), path) < 0)
+ &full_path, git_repository_workdir(diff->repo), entry.path) < 0)
return -1;
- if (!mode) {
+ if (!entry.mode) {
struct stat st;
- if (p_stat(path, &st) < 0) {
- giterr_set(GITERR_OS, "Could not stat '%s'", path);
- result = -1;
- goto cleanup;
+ diff->perf.stat_calls++;
+
+ if (p_stat(full_path.ptr, &st) < 0) {
+ error = git_path_set_error(errno, entry.path, "stat");
+ git_buf_free(&full_path);
+ return error;
}
- mode = st.st_mode;
- size = st.st_size;
+ git_index_entry__init_from_stat(
+ &entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
}
/* calculate OID for file if possible */
- if (S_ISGITLINK(mode)) {
+ if (S_ISGITLINK(entry.mode)) {
git_submodule *sm;
- memset(oid, 0, sizeof(*oid));
-
- if (!git_submodule_lookup(&sm, repo, path)) {
+ if (!git_submodule_lookup(&sm, diff->repo, entry.path)) {
const git_oid *sm_oid = git_submodule_wd_id(sm);
if (sm_oid)
- git_oid_cpy(oid, sm_oid);
+ git_oid_cpy(out, sm_oid);
git_submodule_free(sm);
} else {
/* if submodule lookup failed probably just in an intermediate
* state where some init hasn't happened, so ignore the error
*/
giterr_clear();
- memset(oid, 0, sizeof(*oid));
}
- } else if (S_ISLNK(mode)) {
- result = git_odb__hashlink(oid, full_path.ptr);
- } else if (!git__is_sizet(size)) {
- giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path);
- result = -1;
- } else {
- git_filter_list *fl = NULL;
-
- result = git_filter_list_load(&fl, repo, NULL, path, GIT_FILTER_TO_ODB);
- if (!result) {
- int fd = git_futils_open_ro(full_path.ptr);
- if (fd < 0)
- result = fd;
- else {
- result = git_odb__hashfd_filtered(
- oid, fd, (size_t)size, GIT_OBJ_BLOB, fl);
- p_close(fd);
- }
-
- git_filter_list_free(fl);
+ } else if (S_ISLNK(entry.mode)) {
+ error = git_odb__hashlink(out, full_path.ptr);
+ diff->perf.oid_calculations++;
+ } else if (!git__is_sizet(entry.file_size)) {
+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
+ entry.path);
+ error = -1;
+ } else if (!(error = git_filter_list_load(
+ &fl, diff->repo, NULL, entry.path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE)))
+ {
+ int fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0)
+ error = fd;
+ else {
+ error = git_odb__hashfd_filtered(
+ out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
+ p_close(fd);
+ diff->perf.oid_calculations++;
}
+
+ git_filter_list_free(fl);
}
-cleanup:
+ /* update index for entry if requested */
+ if (!error && update_match && git_oid_equal(out, update_match)) {
+ git_index *idx;
+
+ if (!(error = git_repository_index(&idx, diff->repo))) {
+ memcpy(&entry.id, out, sizeof(entry.id));
+ error = git_index_add(idx, &entry);
+ git_index_free(idx);
+ }
+ }
+
git_buf_free(&full_path);
- return result;
+ return error;
}
static bool diff_time_eq(
@@ -595,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
@@ -660,6 +707,7 @@ static int maybe_modified(
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
+ bool modified_uncertain = false;
const char *matched_pathspec;
int error = 0;
@@ -695,6 +743,11 @@ static int maybe_modified(
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
status = GIT_DELTA_TYPECHANGE;
+ else if (nmode == GIT_FILEMODE_UNREADABLE) {
+ if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
+ error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, nitem);
+ return error;
+ }
else {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem);
@@ -727,15 +780,21 @@ static int maybe_modified(
/* if the stat data looks different, then mark modified - this just
* means that the OID will be recalculated below to confirm change
*/
- else if (omode != nmode ||
- oitem->file_size != nitem->file_size ||
- !diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
+ else if (omode != nmode || oitem->file_size != nitem->file_size) {
+ status = GIT_DELTA_MODIFIED;
+ modified_uncertain =
+ (oitem->file_size <= 0 && nitem->file_size > 0);
+ }
+ else if (!diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
(use_ctime &&
!diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
oitem->ino != nitem->ino ||
oitem->uid != nitem->uid ||
oitem->gid != nitem->gid)
+ {
status = GIT_DELTA_MODIFIED;
+ modified_uncertain = true;
+ }
}
/* if mode is GITLINK and submodules are ignored, then skip */
@@ -746,10 +805,14 @@ static int maybe_modified(
/* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now
*/
- if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->id)) {
+ if (modified_uncertain && git_oid_iszero(&nitem->id)) {
if (git_oid_iszero(&noid)) {
- if ((error = git_diff__oid_for_file(diff->repo,
- nitem->path, nitem->mode, nitem->file_size, &noid)) < 0)
+ const git_oid *update_check =
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) ?
+ &oitem->id : NULL;
+
+ if ((error = git_diff__oid_for_entry(
+ &noid, diff, nitem, update_check)) < 0)
return error;
}
@@ -795,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 &&
@@ -889,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;
@@ -931,6 +968,13 @@ static int handle_unmatched_new_item(
}
}
+ else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
+ delta_type = GIT_DELTA_UNTRACKED;
+ else
+ delta_type = GIT_DELTA_UNREADABLE;
+ }
+
/* Actually create the record for this item if necessary */
if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
return error;
@@ -1019,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)) {
@@ -1066,14 +1109,14 @@ int git_diff__from_iterators(
error = 0;
}
+ diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;
+
cleanup:
if (!error)
*diff_ptr = diff;
else
git_diff_free(diff);
- git_buf_free(&info.ignore_prefix);
-
return error;
}
@@ -1174,6 +1217,9 @@ int git_diff_index_to_workdir(
&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
);
+ if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX))
+ error = git_index_write(index);
+
return error;
}
@@ -1226,20 +1272,6 @@ int git_diff_tree_to_workdir_with_index(
return error;
}
-int git_diff_options_init(git_diff_options *options, unsigned int version)
-{
- git_diff_options template = GIT_DIFF_OPTIONS_INIT;
-
- if (version != template.version) {
- giterr_set(GITERR_INVALID,
- "Invalid version %d for git_diff_options", (int)version);
- return -1;
- }
-
- memcpy(options, &template, sizeof(*options));
- return 0;
-}
-
size_t git_diff_num_deltas(const git_diff *diff)
{
assert(diff);
@@ -1271,6 +1303,15 @@ int git_diff_is_sorted_icase(const git_diff *diff)
return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
}
+int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
+ out->stat_calls = diff->perf.stat_calls;
+ out->oid_calculations = diff->perf.oid_calculations;
+ return 0;
+}
+
int git_diff__paired_foreach(
git_diff *head2idx,
git_diff *idx2wd,
@@ -1573,38 +1614,26 @@ int git_diff_commit_as_email(
return error;
}
-int git_diff_init_options(git_diff_options* opts, int version)
+int git_diff_init_options(git_diff_options *opts, unsigned int version)
{
- if (version != GIT_DIFF_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_diff_options", version);
- return -1;
- } else {
- git_diff_options o = GIT_DIFF_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
+ return 0;
}
-int git_diff_find_init_options(git_diff_find_options* opts, int version)
+int git_diff_find_init_options(
+ git_diff_find_options *opts, unsigned int version)
{
- if (version != GIT_DIFF_FIND_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_diff_find_options", version);
- return -1;
- } else {
- git_diff_find_options o = GIT_DIFF_FIND_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
+ return 0;
}
-int git_diff_format_email_init_options(git_diff_format_email_options* opts, int version)
+int git_diff_format_email_init_options(
+ git_diff_format_email_options *opts, unsigned int version)
{
- if (version != GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_diff_format_email_options", version);
- return -1;
- } else {
- git_diff_format_email_options o = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_format_email_options,
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/diff.h b/src/diff.h
index aae8fbff1..3305238d0 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -8,6 +8,7 @@
#define INCLUDE_diff_h__
#include "git2/diff.h"
+#include "git2/sys/diff.h"
#include "git2/oid.h"
#include <stdio.h>
@@ -62,6 +63,7 @@ struct git_diff {
git_iterator_type_t old_src;
git_iterator_type_t new_src;
uint32_t diffcaps;
+ git_diff_perfdata perf;
int (*strcomp)(const char *, const char *);
int (*strncomp)(const char *, const char *, size_t);
@@ -90,7 +92,9 @@ extern int git_diff_delta__format_file_header(
int oid_strlen);
extern int git_diff__oid_for_file(
- git_repository *, const char *, uint16_t, git_off_t, git_oid *);
+ git_oid *out, git_diff *, const char *, uint16_t, git_off_t);
+extern int git_diff__oid_for_entry(
+ git_oid *out, git_diff *, const git_index_entry *, const git_oid *update);
extern int git_diff__from_iterators(
git_diff **diff_ptr,
diff --git a/src/diff_driver.c b/src/diff_driver.c
index 8136e0dd9..c3c5f365b 100644
--- a/src/diff_driver.c
+++ b/src/diff_driver.c
@@ -233,17 +233,17 @@ static int git_diff_driver_load(
return 0;
}
- /* if you can't read config for repo, just use default driver */
- if (git_repository_config__weakptr(&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 you can't read config for repo, just use default driver */
+ if (git_repository_config_snapshot(&cfg, repo) < 0) {
+ giterr_clear();
+ goto done;
+ }
+
if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
goto done;
@@ -321,6 +321,7 @@ static int git_diff_driver_load(
done:
git_buf_free(&name);
+ git_config_free(cfg);
if (!*out) {
int error2 = git_diff_driver_builtin(out, reg, driver_name);
@@ -396,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_file.c b/src/diff_file.c
index b9f92df3f..96be0942b 100644
--- a/src/diff_file.c
+++ b/src/diff_file.c
@@ -112,6 +112,7 @@ int git_diff_file_content__init_from_diff(
has_data = !use_old &&
(diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0;
break;
+ case GIT_DELTA_UNREADABLE:
case GIT_DELTA_MODIFIED:
case GIT_DELTA_COPIED:
case GIT_DELTA_RENAMED:
@@ -300,7 +301,8 @@ static int diff_file_content_load_workdir_file(
goto cleanup;
if ((error = git_filter_list_load(
- &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0)
+ &fl, fc->repo, NULL, fc->file->path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE)) < 0)
goto cleanup;
/* if there are no filters, try to mmap the file */
diff --git a/src/diff_patch.c b/src/diff_patch.c
index 38d5f4257..4e0672aa1 100644
--- a/src/diff_patch.c
+++ b/src/diff_patch.c
@@ -274,6 +274,7 @@ int git_diff_foreach(
return error;
memset(&xo, 0, sizeof(xo));
+ memset(&patch, 0, sizeof(patch));
diff_output_init(
&xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts);
diff --git a/src/diff_print.c b/src/diff_print.c
index 07c1f8577..43a90b3d8 100644
--- a/src/diff_print.c
+++ b/src/diff_print.c
@@ -82,14 +82,15 @@ char git_diff_status_char(git_delta_t status)
char code;
switch (status) {
- case GIT_DELTA_ADDED: code = 'A'; break;
- case GIT_DELTA_DELETED: code = 'D'; break;
- case GIT_DELTA_MODIFIED: code = 'M'; break;
- case GIT_DELTA_RENAMED: code = 'R'; break;
- case GIT_DELTA_COPIED: code = 'C'; break;
- case GIT_DELTA_IGNORED: code = 'I'; break;
- case GIT_DELTA_UNTRACKED: code = '?'; break;
- default: code = ' '; break;
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ case GIT_DELTA_UNREADABLE: code = 'X'; break;
+ default: code = ' '; break;
}
return code;
@@ -286,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;
@@ -325,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_encode_base85(pi->buf, scan, chunk_len);
git_buf_putc(pi->buf, '\n');
if (git_buf_oom(pi->buf)) {
@@ -344,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++;
}
@@ -364,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++;
@@ -392,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(
@@ -414,6 +442,7 @@ static int diff_print_patch_file(
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
+ delta->status == GIT_DELTA_UNREADABLE ||
(delta->status == GIT_DELTA_UNTRACKED &&
(pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
return 0;
@@ -597,9 +626,9 @@ int git_diff_print_callback__to_file_handle(
}
/* print a git_patch to a git_buf */
-int git_patch_to_buf(
- git_buf *out,
- git_patch *patch)
+int git_patch_to_buf(git_buf *out, git_patch *patch)
{
+ assert(out && patch);
+ git_buf_sanitize(out);
return git_patch_print(patch, git_diff_print_callback__to_buf, out);
}
diff --git a/src/diff_stats.c b/src/diff_stats.c
index 6ad670c42..42ccbfb87 100644
--- a/src/diff_stats.c
+++ b/src/diff_stats.c
@@ -284,6 +284,8 @@ int git_diff_stats_to_buf(
if (width < STATS_FULL_MIN_SCALE)
width = STATS_FULL_MIN_SCALE;
}
+ if (width > stats->max_filestat)
+ width = 0;
for (i = 0; i < stats->files_changed; ++i) {
if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 97fbc2883..423a0ca33 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -114,7 +114,7 @@ static git_diff_delta *diff_delta__merge_like_cgit_reversed(
if ((dup = diff_delta__dup(a, pool)) == NULL)
return NULL;
- if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED)
+ if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED || b->status == GIT_DELTA_UNREADABLE)
return dup;
if (dup->status == GIT_DELTA_DELETED) {
@@ -574,14 +574,14 @@ static int similarity_measure(
if (exact_match) {
if (git_oid_iszero(&a_file->id) &&
diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
- !git_diff__oid_for_file(diff->repo, a_file->path,
- a_file->mode, a_file->size, &a_file->id))
+ !git_diff__oid_for_file(&a_file->id,
+ diff, a_file->path, a_file->mode, a_file->size))
a_file->flags |= GIT_DIFF_FLAG_VALID_ID;
if (git_oid_iszero(&b_file->id) &&
diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
- !git_diff__oid_for_file(diff->repo, b_file->path,
- b_file->mode, b_file->size, &b_file->id))
+ !git_diff__oid_for_file(&b_file->id,
+ diff, b_file->path, b_file->mode, b_file->size))
b_file->flags |= GIT_DIFF_FLAG_VALID_ID;
}
@@ -732,6 +732,7 @@ static bool is_rename_source(
switch (delta->status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_UNREADABLE:
case GIT_DELTA_IGNORED:
return false;
@@ -786,6 +787,7 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
{
return (delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_UNTRACKED ||
+ delta->status == GIT_DELTA_UNREADABLE ||
delta->status == GIT_DELTA_IGNORED);
}
diff --git a/src/errors.c b/src/errors.c
index 393a7875f..7a2600586 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -45,15 +45,19 @@ void giterr_set(int error_class, const char *string, ...)
#endif
int error_code = (error_class == GITERR_OS) ? errno : 0;
- va_start(arglist, string);
- git_buf_vprintf(&buf, string, arglist);
- va_end(arglist);
+ if (string) {
+ va_start(arglist, string);
+ git_buf_vprintf(&buf, string, arglist);
+ va_end(arglist);
+
+ if (error_class == GITERR_OS)
+ git_buf_PUTS(&buf, ": ");
+ }
if (error_class == GITERR_OS) {
#ifdef GIT_WIN32
char * win32_error = git_win32_get_error_message(win32_error_code);
if (win32_error) {
- git_buf_PUTS(&buf, ": ");
git_buf_puts(&buf, win32_error);
git__free(win32_error);
@@ -61,10 +65,8 @@ void giterr_set(int error_class, const char *string, ...)
}
else
#endif
- if (error_code) {
- git_buf_PUTS(&buf, ": ");
+ if (error_code)
git_buf_puts(&buf, strerror(error_code));
- }
if (error_code)
errno = 0;
diff --git a/src/filebuf.c b/src/filebuf.c
index d23bcc11c..25f6e52ef 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -334,8 +334,6 @@ int git_filebuf_commit(git_filebuf *file)
file->fd = -1;
- p_unlink(file->path_original);
-
if (p_rename(file->path_lock, file->path_original) < 0) {
giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original);
goto on_error;
diff --git a/src/fileops.c b/src/fileops.c
index 13b8f6a39..bd9d27c7a 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -355,8 +355,9 @@ int git_futils_mkdir(
if (p_mkdir(make_path.ptr, mode) < 0) {
int tmp_errno = giterr_system_last();
- /* ignore error if directory already exists */
- if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ /* ignore error if not at end or if directory already exists */
+ if (lastch == '\0' &&
+ (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
giterr_system_set(tmp_errno);
giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
goto done;
@@ -374,7 +375,8 @@ int git_futils_mkdir(
if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
(lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
st.st_mode != mode &&
- (error = p_chmod(make_path.ptr, mode)) < 0) {
+ (error = p_chmod(make_path.ptr, mode)) < 0 &&
+ lastch == '\0') {
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
goto done;
}
@@ -503,15 +505,15 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
return error;
}
-static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
+static int futils__rmdir_empty_parent(void *opaque, const char *path)
{
futils__rmdir_data *data = opaque;
int error = 0;
- if (git_buf_len(path) <= data->baselen)
+ if (strlen(path) <= data->baselen)
error = GIT_ITEROVER;
- else if (p_rmdir(git_buf_cstr(path)) < 0) {
+ else if (p_rmdir(path) < 0) {
int en = errno;
if (en == ENOENT || en == ENOTDIR) {
@@ -519,7 +521,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
error = GIT_ITEROVER;
} else {
- error = git_path_set_error(errno, git_buf_cstr(path), "rmdir");
+ error = git_path_set_error(errno, path, "rmdir");
}
}
@@ -740,9 +742,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.c b/src/filter.c
index b2f57964a..b9e4f9ec8 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -23,6 +23,7 @@ struct git_filter_source {
git_oid oid; /* zero if unknown (which is likely) */
uint16_t filemode; /* zero if unknown */
git_filter_mode_t mode;
+ uint32_t options;
};
typedef struct {
@@ -358,6 +359,11 @@ git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
return src->mode;
}
+uint32_t git_filter_source_options(const git_filter_source *src)
+{
+ return src->options;
+}
+
static int filter_list_new(
git_filter_list **out, const git_filter_source *src)
{
@@ -372,6 +378,7 @@ static int filter_list_new(
fl->source.repo = src->repo;
fl->source.path = fl->path;
fl->source.mode = src->mode;
+ fl->source.options = src->options;
*out = fl;
return 0;
@@ -419,12 +426,16 @@ static int filter_list_check_attributes(
}
int git_filter_list_new(
- git_filter_list **out, git_repository *repo, git_filter_mode_t mode)
+ git_filter_list **out,
+ git_repository *repo,
+ git_filter_mode_t mode,
+ uint32_t options)
{
git_filter_source src = { 0 };
src.repo = repo;
src.path = NULL;
src.mode = mode;
+ src.options = options;
return filter_list_new(out, &src);
}
@@ -433,7 +444,8 @@ int git_filter_list_load(
git_repository *repo,
git_blob *blob, /* can be NULL */
const char *path,
- git_filter_mode_t mode)
+ git_filter_mode_t mode,
+ uint32_t options)
{
int error = 0;
git_filter_list *fl = NULL;
@@ -448,6 +460,7 @@ int git_filter_list_load(
src.repo = repo;
src.path = path;
src.mode = mode;
+ src.options = options;
if (blob)
git_oid_cpy(&src.oid, git_blob_id(blob));
@@ -578,6 +591,9 @@ int git_filter_list_apply_to_data(
git_buf *dbuffer[2], local = GIT_BUF_INIT;
unsigned int si = 0;
+ git_buf_sanitize(tgt);
+ git_buf_sanitize(src);
+
if (!fl)
return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size);
@@ -613,7 +629,7 @@ int git_filter_list_apply_to_data(
/* PASSTHROUGH means filter decided not to process the buffer */
error = 0;
} else if (!error) {
- git_buf_shorten(dbuffer[di], 0); /* force NUL termination */
+ git_buf_sanitize(dbuffer[di]); /* force NUL termination */
si = di; /* swap buffers */
} else {
tgt->size = 0;
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 3846bab3c..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.
*/
@@ -62,6 +96,8 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
flags &= ~FNM_PATHNAME;
while (c == '*')
c = *++pattern;
+ if (c == '/')
+ c = *++pattern;
}
if (*string == '.' && (flags & FNM_PERIOD) &&
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 15baf1eb8..4a0b680ef 100644
--- a/src/global.c
+++ b/src/global.c
@@ -16,6 +16,14 @@ git_mutex git__mwindow_mutex;
#define MAX_SHUTDOWN_CB 8
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+SSL_CTX *git__ssl_ctx;
+# ifdef GIT_THREADS
+static git_mutex *openssl_locks;
+# endif
+#endif
+
static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
static git_atomic git__n_shutdown_callbacks;
static git_atomic git__n_inits;
@@ -23,7 +31,7 @@ static git_atomic git__n_inits;
void git__on_shutdown(git_global_shutdown_fn callback)
{
int count = git_atomic_inc(&git__n_shutdown_callbacks);
- assert(count <= MAX_SHUTDOWN_CB);
+ assert(count <= MAX_SHUTDOWN_CB && count > 0);
git__shutdown_callbacks[count - 1] = callback;
}
@@ -31,10 +39,68 @@ static void git__shutdown(void)
{
int pos;
- while ((pos = git_atomic_dec(&git__n_shutdown_callbacks)) >= 0) {
- if (git__shutdown_callbacks[pos])
- git__shutdown_callbacks[pos]();
+ for (pos = git_atomic_get(&git__n_shutdown_callbacks); pos > 0; pos = git_atomic_dec(&git__n_shutdown_callbacks)) {
+ git_global_shutdown_fn cb = git__swap(git__shutdown_callbacks[pos - 1], NULL);
+ if (cb != NULL)
+ cb();
+ }
+
+}
+
+#if defined(GIT_THREADS) && defined(GIT_SSL)
+void openssl_locking_function(int mode, int n, const char *file, int line)
+{
+ int lock;
+
+ GIT_UNUSED(file);
+ GIT_UNUSED(line);
+
+ lock = mode & CRYPTO_LOCK;
+
+ if (lock) {
+ git_mutex_lock(&openssl_locks[n]);
+ } else {
+ git_mutex_unlock(&openssl_locks[n]);
+ }
+}
+#endif
+
+
+static void init_ssl(void)
+{
+#ifdef GIT_SSL
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+ git__ssl_ctx = SSL_CTX_new(SSLv23_method());
+ SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ }
+
+# ifdef GIT_THREADS
+ {
+ int num_locks, i;
+
+ num_locks = CRYPTO_num_locks();
+ openssl_locks = git__calloc(num_locks, sizeof(git_mutex));
+ if (openssl_locks == NULL) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ }
+
+ for (i = 0; i < num_locks; i++) {
+ if (git_mutex_init(&openssl_locks[i]) != 0) {
+ SSL_CTX_free(git__ssl_ctx);
+ git__ssl_ctx = NULL;
+ }
+ }
+
+ CRYPTO_set_locking_callback(openssl_locking_function);
}
+# endif
+#endif
}
/**
@@ -74,9 +140,9 @@ 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()
+static int synchronized_threads_init(void)
{
int error;
@@ -110,7 +176,7 @@ int git_threads_init(void)
return error;
}
-static void synchronized_threads_shutdown()
+static void synchronized_threads_shutdown(void)
{
/* Shut down any subsystems that have global state */
git__shutdown();
@@ -157,6 +223,9 @@ int init_error = 0;
static void cb__free_status(void *st)
{
+ git_global_st *state = (git_global_st *) st;
+ git__free(state->error_t.message);
+
git__free(st);
}
@@ -166,10 +235,14 @@ static void init_once(void)
return;
pthread_key_create(&_tls_key, &cb__free_status);
+
/* Initialize any other subsystems that have global state */
if ((init_error = git_hash_global_init()) >= 0)
init_error = git_sysdir_global_init();
+ /* OpenSSL needs to be initialized from the main thread */
+ init_ssl();
+
GIT_MEMORY_BARRIER;
}
@@ -223,6 +296,13 @@ static git_global_st __state;
int git_threads_init(void)
{
+ static int ssl_inited = 0;
+
+ if (!ssl_inited) {
+ init_ssl();
+ ssl_inited = 1;
+ }
+
git_atomic_inc(&git__n_inits);
return 0;
}
diff --git a/src/global.h b/src/global.h
index 778250376..a89a8d6ab 100644
--- a/src/global.h
+++ b/src/global.h
@@ -7,14 +7,21 @@
#ifndef INCLUDE_global_h__
#define INCLUDE_global_h__
+#include "common.h"
#include "mwindow.h"
#include "hash.h"
typedef struct {
git_error *last_error;
git_error error_t;
+ char oid_fmt[GIT_OID_HEXSZ+1];
} git_global_st;
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+extern SSL_CTX *git__ssl_ctx;
+#endif
+
git_global_st *git__global_state(void);
extern git_mutex git__mwindow_mutex;
diff --git a/src/graph.c b/src/graph.c
index 1c264d997..8accd808c 100644
--- a/src/graph.c
+++ b/src/graph.c
@@ -124,9 +124,9 @@ static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
(commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))
continue;
else if (commit->flags & PARENT1)
- (*behind)++;
- else if (commit->flags & PARENT2)
(*ahead)++;
+ else if (commit->flags & PARENT2)
+ (*behind)++;
for (i = 0; i < commit->out_degree; i++) {
git_commit_list_node *p = commit->parents[i];
diff --git a/src/ignore.c b/src/ignore.c
index f373c9482..520bcfe41 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -91,11 +91,11 @@ static int push_ignore_file(
return error;
}
-static int push_one_ignore(void *payload, git_buf *path)
+static int push_one_ignore(void *payload, const char *path)
{
git_ignores *ign = payload;
ign->depth++;
- return push_ignore_file(ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+ return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE);
}
static int get_internal_ignores(git_attr_file **out, git_repository *repo)
@@ -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 c044af402..8b757f269 100644
--- a/src/index.c
+++ b/src/index.c
@@ -842,7 +842,7 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src)
{
- char *tgt_path = tgt->path;
+ const char *tgt_path = tgt->path;
memcpy(tgt, src, sizeof(*tgt));
tgt->path = tgt_path; /* reset to existing path data */
}
@@ -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;
@@ -1753,35 +1767,42 @@ static size_t read_entry(
git_index_entry **out, const void *buffer, size_t buffer_size)
{
size_t path_length, entry_size;
- uint16_t flags_raw;
const char *path_ptr;
- const struct entry_short *source = buffer;
+ struct entry_short source;
git_index_entry entry = {{0}};
if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)
return 0;
- entry.ctime.seconds = (git_time_t)ntohl(source->ctime.seconds);
- entry.ctime.nanoseconds = ntohl(source->ctime.nanoseconds);
- entry.mtime.seconds = (git_time_t)ntohl(source->mtime.seconds);
- entry.mtime.nanoseconds = ntohl(source->mtime.nanoseconds);
- entry.dev = ntohl(source->dev);
- entry.ino = ntohl(source->ino);
- entry.mode = ntohl(source->mode);
- entry.uid = ntohl(source->uid);
- entry.gid = ntohl(source->gid);
- entry.file_size = ntohl(source->file_size);
- git_oid_cpy(&entry.id, &source->oid);
- entry.flags = ntohs(source->flags);
+ /* buffer is not guaranteed to be aligned */
+ memcpy(&source, buffer, sizeof(struct entry_short));
+
+ entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds);
+ entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds);
+ entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds);
+ entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds);
+ entry.dev = ntohl(source.dev);
+ entry.ino = ntohl(source.ino);
+ entry.mode = ntohl(source.mode);
+ entry.uid = ntohl(source.uid);
+ entry.gid = ntohl(source.gid);
+ entry.file_size = ntohl(source.file_size);
+ git_oid_cpy(&entry.id, &source.oid);
+ entry.flags = ntohs(source.flags);
if (entry.flags & GIT_IDXENTRY_EXTENDED) {
- const struct entry_long *source_l = (const struct entry_long *)source;
- path_ptr = source_l->path;
+ uint16_t flags_raw;
+ size_t flags_offset;
- flags_raw = ntohs(source_l->flags_extended);
- memcpy(&entry.flags_extended, &flags_raw, 2);
+ flags_offset = offsetof(struct entry_long, flags_extended);
+ memcpy(&flags_raw, (const char *) buffer + flags_offset,
+ sizeof(flags_raw));
+ flags_raw = ntohs(flags_raw);
+
+ memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw));
+ path_ptr = (const char *) buffer + offsetof(struct entry_long, path);
} else
- path_ptr = source->path;
+ path_ptr = (const char *) buffer + offsetof(struct entry_short, path);
path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;
@@ -1832,14 +1853,12 @@ static int read_header(struct index_header *dest, const void *buffer)
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size)
{
- const struct index_extension *source;
struct index_extension dest;
size_t total_size;
- source = (const struct index_extension *)(buffer);
-
- memcpy(dest.signature, source->signature, 4);
- dest.extension_size = ntohl(source->extension_size);
+ /* buffer is not guaranteed to be aligned */
+ memcpy(&dest, buffer, sizeof(struct index_extension));
+ dest.extension_size = ntohl(dest.extension_size);
total_size = dest.extension_size + sizeof(struct index_extension);
@@ -2282,9 +2301,7 @@ static int read_tree_cb(
entry->mode == old_entry->mode &&
git_oid_equal(&entry->id, &old_entry->id))
{
- char *oldpath = entry->path;
- memcpy(entry, old_entry, sizeof(*entry));
- entry->path = oldpath;
+ index_entry_cpy(entry, old_entry);
entry->flags_extended = 0;
}
diff --git a/src/indexer.c b/src/indexer.c
index adf5ceaa7..010de329b 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -18,6 +18,8 @@
#include "oidmap.h"
#include "zstream.h"
+extern git_mutex git__mwindow_mutex;
+
#define UINT31_MAX (0x7FFFFFFF)
struct entry {
@@ -34,7 +36,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 +68,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 +110,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 +126,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 +426,47 @@ 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;
+
+ assert(data && size);
+
+ /* the offset needs to be at the beginning of the a page boundary */
+ page_start = (offset / page_size) * page_size;
+ page_offset = offset - page_start;
+
+ 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;
+
+ if (!size)
+ return 0;
+
+ /* add the extra space we need at the end */
+ if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) {
+ giterr_set(GITERR_OS, "Failed to increase size of pack file '%s'", idx->pack->pack_name);
+ 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 +478,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 +645,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 +664,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 +680,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 +690,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 +731,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;
@@ -717,6 +745,9 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats)
/* Loop until we find the first REF delta */
git_vector_foreach(&idx->deltas, i, delta) {
+ if (!delta)
+ continue;
+
curpos = delta->delta_off;
error = git_packfile_unpack_header(&size, &type, &idx->pack->mwf, &w, &curpos);
git_mwindow_close(&w);
@@ -756,13 +787,18 @@ static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
{
unsigned int i;
struct delta_info *delta;
- int progressed = 0, progress_cb_result;
+ int progressed = 0, non_null = 0, progress_cb_result;
while (idx->deltas.length > 0) {
progressed = 0;
+ non_null = 0;
git_vector_foreach(&idx->deltas, i, delta) {
git_rawobj obj;
+ if (!delta)
+ continue;
+
+ non_null = 1;
idx->off = delta->delta_off;
if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0)
continue;
@@ -777,16 +813,15 @@ static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
if ((progress_cb_result = do_progress_callback(idx, stats)) < 0)
return progress_cb_result;
- /*
- * Remove this delta from the list and
- * decrease i so we don't skip over the next
- * delta.
- */
- git_vector_remove(&idx->deltas, i);
+ /* remove from the list */
+ git_vector_set(NULL, &idx->deltas, i, NULL);
git__free(delta);
- i--;
}
+ /* if none were actually set, we're done */
+ if (!non_null)
+ break;
+
if (!progressed && (fix_thin_pack(idx, stats) < 0)) {
giterr_set(GITERR_INDEXER, "missing delta bases");
return -1;
@@ -810,19 +845,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");
- 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");
+ if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0)
return -1;
- }
/*
* We now use the same technique as before to determine the
@@ -830,6 +858,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);
@@ -899,13 +928,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);
@@ -988,14 +1011,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;
@@ -1015,7 +1042,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); });
@@ -1024,7 +1051,11 @@ void git_indexer_free(git_indexer *idx)
}
git_vector_free_deep(&idx->deltas);
- git_packfile_free(idx->pack);
- git_filebuf_cleanup(&idx->pack_file);
+
+ if (!git_mutex_lock(&git__mwindow_mutex)) {
+ git_packfile_free(idx->pack);
+ git_mutex_unlock(&git__mwindow_mutex);
+ }
+
git__free(idx);
}
diff --git a/src/iterator.c b/src/iterator.c
index ef27fa71f..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;
@@ -1016,6 +1017,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
fs_iterator__free_frame(ff);
return GIT_ENOTFOUND;
}
+ fi->base.stat_calls += ff->entries.length;
fs_iterator__seek_frame_start(fi, ff);
@@ -1289,24 +1291,37 @@ 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]);
}
/* convert submodules to GITLINK and remove trailing slashes */
git_vector_foreach(&ff->entries, pos, entry) {
- if (S_ISDIR(entry->st.st_mode) &&
- git_submodule__is_submodule(fi->base.repo, entry->path))
- {
+ if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path))
+ continue;
+
+ if (git_submodule__is_submodule(fi->base.repo, entry->path)) {
entry->st.st_mode = GIT_FILEMODE_COMMIT;
entry->path_len--;
entry->path[entry->path_len] = '\0';
@@ -1340,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;
}
@@ -1485,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;
@@ -1492,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)
@@ -1547,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);
}
@@ -1562,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);
@@ -1578,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 ba9c1e486..d88ad5191 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -52,6 +52,7 @@ struct git_iterator {
char *start;
char *end;
int (*prefixcomp)(const char *str, const char *prefix);
+ size_t stat_calls;
unsigned int flags;
};
@@ -244,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);
diff --git a/src/map.h b/src/map.h
index da3d1e19a..722eb7a30 100644
--- a/src/map.h
+++ b/src/map.h
@@ -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 69c42bc0c..ddeea8752 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -27,6 +27,7 @@
#include "index.h"
#include "filebuf.h"
#include "config.h"
+#include "oidarray.h"
#include "git2/types.h"
#include "git2/repository.h"
@@ -39,6 +40,7 @@
#include "git2/signature.h"
#include "git2/config.h"
#include "git2/tree.h"
+#include "git2/oidarray.h"
#include "git2/sys/index.h"
#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
@@ -139,7 +141,7 @@ int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, co
return 0;
}
-int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two)
{
git_revwalk *walk;
git_vector list;
@@ -173,13 +175,63 @@ int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const
return GIT_ENOTFOUND;
}
+ *out = result;
+ *walk_out = walk;
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+
+}
+
+int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ int error;
+ git_revwalk *walk;
+ git_commit_list *result;
+
+ if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+ return error;
+
git_oid_cpy(out, &result->item->oid);
git_commit_list_free(&result);
git_revwalk_free(walk);
return 0;
+}
+
+int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ int error;
+ git_revwalk *walk;
+ git_commit_list *result, *list;
+ git_array_oid_t array;
+
+ git_array_init(array);
+
+ if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+ return error;
+
+ list = result;
+ while (list) {
+ git_oid *id = git_array_alloc(array);
+ if (id == NULL)
+ goto on_error;
+
+ git_oid_cpy(id, &list->item->oid);
+ list = list->next;
+ }
+
+ git_oidarray__from_array(out, &array);
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
on_error:
+ git_commit_list_free(&result);
git_revwalk_free(walk);
return -1;
}
@@ -228,8 +280,11 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l
return -1;
git_vector_foreach(twos, i, two) {
- git_commit_list_parse(walk, two);
+ if (git_commit_list_parse(walk, two) < 0)
+ return -1;
+
two->flags |= PARENT2;
+
if (git_pqueue_insert(&list, two) < 0)
return -1;
}
@@ -2193,8 +2248,7 @@ static int merge_normalize_checkout_opts(
memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options));
else {
git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
- default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE |
- GIT_CHECKOUT_ALLOW_CONFLICTS;
+ default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options));
}
@@ -2226,64 +2280,6 @@ static int merge_normalize_checkout_opts(
return error;
}
-static int merge_affected_paths(git_vector *paths, git_repository *repo, git_index *index_new)
-{
- git_tree *head_tree = NULL;
- git_iterator *iter_head = NULL, *iter_new = NULL;
- git_diff *merged_list = NULL;
- git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
- git_diff_delta *delta;
- size_t i;
- const git_index_entry *e;
- char *path;
- int error = 0;
-
- if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
- (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
- (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
- (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0)
- goto done;
-
- git_vector_foreach(&merged_list->deltas, i, delta) {
- path = git__strdup(delta->new_file.path);
- GITERR_CHECK_ALLOC(path);
-
- if ((error = git_vector_insert(paths, path)) < 0)
- goto on_error;
- }
-
- for (i = 0; i < git_index_entrycount(index_new); i++) {
- e = git_index_get_byindex(index_new, i);
-
- if (git_index_entry_stage(e) != 0 &&
- (git_vector_last(paths) == NULL ||
- strcmp(git_vector_last(paths), e->path) != 0)) {
-
- path = git__strdup(e->path);
- GITERR_CHECK_ALLOC(path);
-
- if ((error = git_vector_insert(paths, path)) < 0)
- goto on_error;
- }
- }
-
- goto done;
-
-on_error:
- git_vector_foreach(paths, i, path)
- git__free(path);
-
- git_vector_clear(paths);
-
-done:
- git_tree_free(head_tree);
- git_iterator_free(iter_head);
- git_iterator_free(iter_new);
- git_diff_free(merged_list);
-
- return error;
-}
-
static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
{
git_tree *head_tree = NULL;
@@ -2342,7 +2338,6 @@ done:
static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
{
- git_index *index_repo = NULL;
git_diff *wd_diff_list = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
int error = 0;
@@ -2351,6 +2346,16 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde
*conflicts = 0;
+ /* We need to have merged at least 1 file for the possibility to exist to
+ * have conflicts with the workdir. Passing 0 as the pathspec count paramter
+ * will consider all files in the working directory, that is, we may detect
+ * a conflict if there were untracked files in the workdir prior to starting
+ * the merge. This typically happens when cherry-picking a commmit whose
+ * changes have already been applied.
+ */
+ if (merged_paths->length == 0)
+ return 0;
+
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
/* Workdir changes may exist iff they do not conflict with changes that
@@ -2360,111 +2365,69 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde
opts.pathspec.count = merged_paths->length;
opts.pathspec.strings = (char **)merged_paths->contents;
- if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, index_repo, &opts)) < 0)
+ if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0)
goto done;
*conflicts = wd_diff_list->deltas.length;
done:
- git_index_free(index_repo);
git_diff_free(wd_diff_list);
return error;
}
-int git_merge__indexes(git_repository *repo, git_index *index_new)
+int git_merge__check_result(git_repository *repo, git_index *index_new)
{
- git_index *index_repo = NULL;
- int index_repo_caps = 0;
+ git_tree *head_tree = NULL;
+ git_iterator *iter_head = NULL, *iter_new = NULL;
+ git_diff *merged_list = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_delta *delta;
git_vector paths = GIT_VECTOR_INIT;
- size_t index_conflicts = 0, wd_conflicts = 0, conflicts, i;
- char *path;
+ size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts;
const git_index_entry *e;
- const git_index_name_entry *name;
- const git_index_reuc_entry *reuc;
int error = 0;
- if ((error = git_repository_index(&index_repo, repo)) < 0)
- goto done;
-
- /* Set the index to case sensitive to handle the merge */
- index_repo_caps = git_index_caps(index_repo);
-
- if ((error = git_index_set_caps(index_repo, (index_repo_caps & ~GIT_INDEXCAP_IGNORE_CASE))) < 0)
- goto done;
-
- /* Make sure the index and workdir state do not prevent merging */
- if ((error = merge_affected_paths(&paths, repo, index_new)) < 0 ||
- (error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 ||
- (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0)
- goto done;
-
- if ((conflicts = index_conflicts + wd_conflicts) > 0) {
- giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge",
- conflicts, (conflicts != 1) ? "s" : "");
- error = GIT_EMERGECONFLICT;
-
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0)
goto done;
- }
-
- /* Remove removed items from the index */
- git_vector_foreach(&paths, i, path) {
- if (git_index_get_bypath(index_new, path, 0) == NULL) {
- if ((error = git_index_remove(index_repo, path, 0)) < 0 &&
- error != GIT_ENOTFOUND)
- goto done;
- }
- }
- /* Add updated items to the index */
- git_vector_foreach(&paths, i, path) {
- if ((e = git_index_get_bypath(index_new, path, 0)) != NULL) {
- if ((error = git_index_add(index_repo, e)) < 0)
- goto done;
- }
+ git_vector_foreach(&merged_list->deltas, i, delta) {
+ if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0)
+ goto done;
}
- /* Add conflicts */
- git_index_conflict_cleanup(index_repo);
-
for (i = 0; i < git_index_entrycount(index_new); i++) {
e = git_index_get_byindex(index_new, i);
if (git_index_entry_stage(e) != 0 &&
- (error = git_index_add(index_repo, e)) < 0)
- goto done;
- }
+ (git_vector_last(&paths) == NULL ||
+ strcmp(git_vector_last(&paths), e->path) != 0)) {
- /* Add name entries */
- git_index_name_clear(index_repo);
-
- for (i = 0; i < git_index_name_entrycount(index_new); i++) {
- name = git_index_name_get_byindex(index_new, i);
-
- if ((error = git_index_name_add(index_repo,
- name->ancestor, name->ours, name->theirs)) < 0)
- goto done;
+ if ((error = git_vector_insert(&paths, (char *)e->path)) < 0)
+ goto done;
+ }
}
- /* Add the reuc */
- git_index_reuc_clear(index_repo);
-
- for (i = 0; i < git_index_reuc_entrycount(index_new); i++) {
- reuc = (git_index_reuc_entry *)git_index_reuc_get_byindex(index_new, i);
+ /* Make sure the index and workdir state do not prevent merging */
+ if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 ||
+ (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0)
+ goto done;
- if ((error = git_index_reuc_add(index_repo, reuc->path,
- reuc->mode[0], &reuc->oid[0],
- reuc->mode[1], &reuc->oid[1],
- reuc->mode[2], &reuc->oid[2])) < 0)
- goto done;
+ if ((conflicts = index_conflicts + wd_conflicts) > 0) {
+ giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge",
+ conflicts, (conflicts != 1) ? "s" : "");
+ error = GIT_EMERGECONFLICT;
}
done:
- if (index_repo != NULL)
- git_index_set_caps(index_repo, index_repo_caps);
-
- git_index_free(index_repo);
- git_vector_free_deep(&paths);
+ git_vector_free(&paths);
+ git_tree_free(head_tree);
+ git_iterator_free(iter_head);
+ git_iterator_free(iter_new);
+ git_diff_free(merged_list);
return error;
}
@@ -2479,12 +2442,14 @@ int git_merge__append_conflicts_to_merge_msg(
size_t i;
int error;
+ if (!git_index_has_conflicts(index))
+ return 0;
+
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup;
- if (git_index_has_conflicts(index))
- git_filebuf_printf(&file, "\nConflicts:\n");
+ git_filebuf_printf(&file, "\nConflicts:\n");
for (i = 0; i < git_index_entrycount(index); i++) {
const git_index_entry *e = git_index_get_byindex(index, i);
@@ -2509,7 +2474,6 @@ cleanup:
return error;
}
-
static int merge_state_cleanup(git_repository *repo)
{
const char *state_files[] = {
@@ -2564,8 +2528,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 +2571,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 +2579,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);
@@ -2620,7 +2621,7 @@ int git_merge(
git_checkout_options checkout_opts;
git_merge_head *ancestor_head = NULL, *our_head = NULL;
git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL;
- git_index *index_new = NULL, *index_repo = NULL;
+ git_index *index_new = NULL;
size_t i;
int error = 0;
@@ -2660,10 +2661,9 @@ int git_merge(
/* TODO: recursive, octopus, etc... */
if ((error = git_merge_trees(&index_new, repo, ancestor_tree, our_tree, their_trees[0], merge_opts)) < 0 ||
- (error = git_merge__indexes(repo, index_new)) < 0 ||
- (error = git_repository_index(&index_repo, repo)) < 0 ||
- (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
- (error = git_checkout_index(repo, index_repo, &checkout_opts)) < 0)
+ (error = git_merge__check_result(repo, index_new)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 ||
+ (error = git_checkout_index(repo, index_new, &checkout_opts)) < 0)
goto on_error;
goto done;
@@ -2673,7 +2673,6 @@ on_error:
done:
git_index_free(index_new);
- git_index_free(index_repo);
git_tree_free(ancestor_tree);
git_tree_free(our_tree);
@@ -2803,38 +2802,24 @@ void git_merge_head_free(git_merge_head *head)
git__free(head);
}
-int git_merge_init_options(git_merge_options *opts, int version)
+int git_merge_init_options(git_merge_options *opts, unsigned int version)
{
- if (version != GIT_MERGE_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_merge_options", version);
- return -1;
- } else {
- git_merge_options default_opts = GIT_MERGE_OPTIONS_INIT;
- memcpy(opts, &default_opts, sizeof(git_merge_options));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT);
+ return 0;
}
-int git_merge_file_init_input(git_merge_file_input *input, int version)
+int git_merge_file_init_input(git_merge_file_input *input, unsigned int version)
{
- if (version != GIT_MERGE_FILE_INPUT_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_merge_file_input", version);
- return -1;
- } else {
- git_merge_file_input i = GIT_MERGE_FILE_INPUT_INIT;
- memcpy(input, &i, sizeof(i));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT);
+ return 0;
}
-int git_merge_file_init_options(git_merge_file_options *opts, int version)
+int git_merge_file_init_options(
+ git_merge_file_options *opts, unsigned int version)
{
- if (version != GIT_MERGE_FILE_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_merge_file_options", version);
- return -1;
- } else {
- git_merge_file_options o = GIT_MERGE_FILE_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/merge.h b/src/merge.h
index 00f6197bf..cc17389ab 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -149,7 +149,7 @@ int git_merge__setup(
const git_merge_head *heads[],
size_t heads_len);
-int git_merge__indexes(git_repository *repo, git_index *index_new);
+int git_merge__check_result(git_repository *repo, git_index *index_new);
int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index);
diff --git a/src/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/mwindow.c b/src/mwindow.c
index 7e5fcdfbc..1d64d26a4 100644
--- a/src/mwindow.c
+++ b/src/mwindow.c
@@ -11,6 +11,10 @@
#include "fileops.h"
#include "map.h"
#include "global.h"
+#include "strmap.h"
+#include "pack.h"
+
+GIT__USE_STRMAP;
#define DEFAULT_WINDOW_SIZE \
(sizeof(void*) >= 8 \
@@ -26,20 +30,127 @@ size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
/* Whenever you want to read or modify this, grab git__mwindow_mutex */
static git_mwindow_ctl mem_ctl;
-/*
- * Free all the windows in a sequence, typically because we're done
- * with the file
+/* Global list of mwindow files, to open packs once across repos */
+git_strmap *git__pack_cache = NULL;
+
+/**
+ * Run under mwindow lock
*/
-void git_mwindow_free_all(git_mwindow_file *mwf)
+int git_mwindow_files_init(void)
{
- git_mwindow_ctl *ctl = &mem_ctl;
- size_t i;
+ if (git__pack_cache)
+ return 0;
+
+ return git_strmap_alloc(&git__pack_cache);
+}
+
+void git_mwindow_files_free(void)
+{
+ git_strmap *tmp = git__pack_cache;
+
+ git__pack_cache = NULL;
+ git_strmap_free(tmp);
+}
+
+int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
+{
+ int error;
+ char *packname;
+ git_strmap_iter pos;
+ struct git_pack_file *pack;
+
+ if ((error = git_packfile__name(&packname, path)) < 0)
+ return error;
+
+ if (git_mutex_lock(&git__mwindow_mutex) < 0) {
+ giterr_set(GITERR_OS, "failed to lock mwindow mutex");
+ return -1;
+ }
+
+ if (git_mwindow_files_init() < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ git__free(packname);
+ return -1;
+ }
+
+ pos = git_strmap_lookup_index(git__pack_cache, packname);
+ git__free(packname);
+
+ if (git_strmap_valid_index(git__pack_cache, pos)) {
+ pack = git_strmap_value_at(git__pack_cache, pos);
+ git_atomic_inc(&pack->refcount);
+
+ git_mutex_unlock(&git__mwindow_mutex);
+ *out = pack;
+ return 0;
+ }
+
+ /* If we didn't find it, we need to create it */
+ if ((error = git_packfile_alloc(&pack, path)) < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ return error;
+ }
+
+ git_atomic_inc(&pack->refcount);
+
+ git_strmap_insert(git__pack_cache, pack->pack_name, pack, error);
+ git_mutex_unlock(&git__mwindow_mutex);
+
+ if (error < 0) {
+ git_packfile_free(pack);
+ return -1;
+ }
+
+ *out = pack;
+ return 0;
+}
+
+void git_mwindow_put_pack(struct git_pack_file *pack)
+{
+ int count;
+ git_strmap_iter pos;
+
+ if (git_mutex_lock(&git__mwindow_mutex) < 0)
+ return;
+
+ /* put before get would be a corrupted state */
+ assert(git__pack_cache);
+
+ pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name);
+ /* if we cannot find it, the state is corrupted */
+ assert(git_strmap_valid_index(git__pack_cache, pos));
+
+ count = git_atomic_dec(&pack->refcount);
+ if (count == 0) {
+ git_strmap_delete_at(git__pack_cache, pos);
+ git_packfile_free(pack);
+ }
+
+ git_mutex_unlock(&git__mwindow_mutex);
+ return;
+}
+void git_mwindow_free_all(git_mwindow_file *mwf)
+{
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
return;
}
+ git_mwindow_free_all_locked(mwf);
+
+ git_mutex_unlock(&git__mwindow_mutex);
+}
+
+/*
+ * Free all the windows in a sequence, typically because we're done
+ * with the file
+ */
+void git_mwindow_free_all_locked(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+
/*
* Remove these windows from the global list
*/
@@ -67,8 +178,6 @@ void git_mwindow_free_all(git_mwindow_file *mwf)
mwf->windows = w->next;
git__free(w);
}
-
- git_mutex_unlock(&git__mwindow_mutex);
}
/*
diff --git a/src/mwindow.h b/src/mwindow.h
index 0018ebbf0..63418e458 100644
--- a/src/mwindow.h
+++ b/src/mwindow.h
@@ -36,10 +36,18 @@ typedef struct git_mwindow_ctl {
} git_mwindow_ctl;
int git_mwindow_contains(git_mwindow *win, git_off_t offset);
-void git_mwindow_free_all(git_mwindow_file *mwf);
+void git_mwindow_free_all(git_mwindow_file *mwf); /* locks */
+void git_mwindow_free_all_locked(git_mwindow_file *mwf); /* run under lock */
unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left);
int git_mwindow_file_register(git_mwindow_file *mwf);
void git_mwindow_file_deregister(git_mwindow_file *mwf);
void git_mwindow_close(git_mwindow **w_cursor);
+int git_mwindow_files_init(void);
+void git_mwindow_files_free(void);
+
+struct git_pack_file; /* just declaration to avoid cyclical includes */
+int git_mwindow_get_pack(struct git_pack_file **out, const char *path);
+void git_mwindow_put_pack(struct git_pack_file *pack);
+
#endif
diff --git a/src/netops.c b/src/netops.c
index ad27d84cf..adbae61c4 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -13,6 +13,7 @@
# include <netinet/in.h>
# include <arpa/inet.h>
#else
+# include <winsock2.h>
# include <ws2tcpip.h>
# ifdef _MSC_VER
# pragma comment(lib, "ws2_32")
@@ -33,6 +34,7 @@
#include "posix.h"
#include "buffer.h"
#include "http_parser.h"
+#include "global.h"
#ifdef GIT_WIN32
static void net_set_error(const char *str)
@@ -157,7 +159,7 @@ void gitno_buffer_setup_callback(
void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
{
#ifdef GIT_SSL
- if (socket->ssl.ctx) {
+ if (socket->ssl.ssl) {
gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
return;
}
@@ -202,12 +204,13 @@ static int gitno_ssl_teardown(gitno_ssl *ssl)
ret = 0;
SSL_free(ssl->ssl);
- SSL_CTX_free(ssl->ctx);
return ret;
}
+#endif
+
/* Match host names according to RFC 2818 rules */
-static int match_host(const char *pattern, const char *host)
+int gitno__match_host(const char *pattern, const char *host)
{
for (;;) {
char c = tolower(*pattern++);
@@ -230,9 +233,9 @@ static int match_host(const char *pattern, const char *host)
while(*host) {
char h = tolower(*host);
if (c == h)
- return match_host(pattern, host++);
+ return gitno__match_host(pattern, host++);
if (h == '.')
- return match_host(pattern, host);
+ return gitno__match_host(pattern, host);
host++;
}
return -1;
@@ -250,12 +253,14 @@ static int check_host_name(const char *name, const char *host)
if (!strcasecmp(name, host))
return 0;
- if (match_host(name, host) < 0)
+ if (gitno__match_host(name, host) < 0)
return -1;
return 0;
}
+#ifdef GIT_SSL
+
static int verify_server_cert(gitno_ssl *ssl, const char *host)
{
X509 *cert;
@@ -287,6 +292,10 @@ static int verify_server_cert(gitno_ssl *ssl, const char *host)
cert = SSL_get_peer_certificate(ssl->ssl);
+ if (!cert) {
+ giterr_set(GITERR_SSL, "the server did not provide a certificate");
+ return -1;
+ }
/* Check the alternative names */
alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
@@ -321,7 +330,7 @@ static int verify_server_cert(gitno_ssl *ssl, const char *host)
GENERAL_NAMES_free(alts);
if (matched == 0)
- goto cert_fail;
+ goto cert_fail_name;
if (matched == 1)
return 0;
@@ -358,11 +367,11 @@ static int verify_server_cert(gitno_ssl *ssl, const char *host)
int size = ASN1_STRING_to_UTF8(&peer_cn, str);
GITERR_CHECK_ALLOC(peer_cn);
if (memchr(peer_cn, '\0', size))
- goto cert_fail;
+ goto cert_fail_name;
}
if (check_host_name((char *)peer_cn, host) < 0)
- goto cert_fail;
+ goto cert_fail_name;
OPENSSL_free(peer_cn);
@@ -372,28 +381,22 @@ on_error:
OPENSSL_free(peer_cn);
return ssl_set_error(ssl, 0);
-cert_fail:
+cert_fail_name:
OPENSSL_free(peer_cn);
- giterr_set(GITERR_SSL, "Certificate host name check failed");
- return -1;
+ giterr_set(GITERR_SSL, "hostname does not match certificate");
+ return GIT_ECERTIFICATE;
}
-static int ssl_setup(gitno_socket *socket, const char *host, int flags)
+static int ssl_setup(gitno_socket *socket, const char *host)
{
int ret;
- SSL_library_init();
- SSL_load_error_strings();
- socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
- if (socket->ssl.ctx == NULL)
- return ssl_set_error(&socket->ssl, 0);
-
- SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
- SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
- if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
- return ssl_set_error(&socket->ssl, 0);
+ if (git__ssl_ctx == NULL) {
+ giterr_set(GITERR_NET, "OpenSSL initialization failed");
+ return -1;
+ }
- socket->ssl.ssl = SSL_new(socket->ssl.ctx);
+ socket->ssl.ssl = SSL_new(git__ssl_ctx);
if (socket->ssl.ssl == NULL)
return ssl_set_error(&socket->ssl, 0);
@@ -403,9 +406,6 @@ static int ssl_setup(gitno_socket *socket, const char *host, int flags)
if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
return ssl_set_error(&socket->ssl, ret);
- if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
- return 0;
-
return verify_server_cert(&socket->ssl, host);
}
#endif
@@ -458,7 +458,7 @@ int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int f
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
- if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
+ if ((ret = p_getaddrinfo(host, port, &hints, &info)) != 0) {
giterr_set(GITERR_NET,
"Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
return -1;
@@ -491,8 +491,9 @@ int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int f
p_freeaddrinfo(info);
#ifdef GIT_SSL
- if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
- return -1;
+ if ((flags & GITNO_CONNECT_SSL) &&
+ (ret = ssl_setup(s_out, host)) < 0)
+ return ret;
#else
/* SSL is not supported */
if (flags & GITNO_CONNECT_SSL) {
@@ -530,7 +531,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
size_t off = 0;
#ifdef GIT_SSL
- if (socket->ssl.ctx)
+ if (socket->ssl.ssl)
return gitno_send_ssl(&socket->ssl, msg, len, flags);
#endif
@@ -551,7 +552,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
int gitno_close(gitno_socket *s)
{
#ifdef GIT_SSL
- if (s->ssl.ctx &&
+ if (s->ssl.ssl &&
gitno_ssl_teardown(&s->ssl) < 0)
return -1;
#endif
@@ -715,6 +716,9 @@ int gitno_extract_url_parts(
if (u.field_set & (1 << UF_PATH)) {
*path = git__substrdup(_path, u.field_data[UF_PATH].len);
GITERR_CHECK_ALLOC(*path);
+ } else {
+ giterr_set(GITERR_NET, "invalid url, missing path");
+ return GIT_EINVALIDSPEC;
}
if (u.field_set & (1 << UF_USERINFO)) {
diff --git a/src/netops.h b/src/netops.h
index 666d66b12..beb0e0760 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -16,7 +16,6 @@
struct gitno_ssl {
#ifdef GIT_SSL
- SSL_CTX *ctx;
SSL *ssl;
#else
size_t dummy;
@@ -48,12 +47,21 @@ typedef struct gitno_buffer gitno_buffer;
enum {
/* Attempt to create an SSL connection. */
GITNO_CONNECT_SSL = 1,
-
- /* Valid only when GITNO_CONNECT_SSL is also specified.
- * Indicates that the server certificate should not be validated. */
- GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
};
+/**
+ * Check if the name in a cert matches the wanted hostname
+ *
+ * Check if a pattern from a certificate matches the hostname we
+ * wanted to connect to according to RFC2818 rules (which specifies
+ * HTTP over TLS). Mainly, an asterisk matches anything, but is
+ * limited to a single url component.
+ *
+ * Note that this does not set an error message. It expects the user
+ * to provide the message for the user.
+ */
+int gitno__match_host(const char *pattern, const char *host);
+
void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);
void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
int gitno_recv(gitno_buffer *buf);
diff --git a/src/object.c b/src/object.c
index 3847a0739..93068b85f 100644
--- a/src/object.c
+++ b/src/object.c
@@ -375,7 +375,7 @@ int git_object_lookup_bypath(
assert(out && treeish && path);
- if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE) < 0) ||
+ if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0 ||
(error = git_tree_entry_bypath(&entry, tree, path)) < 0)
{
goto cleanup;
diff --git a/src/odb.c b/src/odb.c
index 00740d2e2..a4fc02686 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -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;
@@ -1123,14 +1124,9 @@ int git_odb__error_ambiguous(const char *message)
return GIT_EAMBIGUOUS;
}
-int git_odb_init_backend(git_odb_backend* backend, int version)
+int git_odb_init_backend(git_odb_backend *backend, unsigned int version)
{
- if (version != GIT_ODB_BACKEND_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_odb_backend", version);
- return -1;
- } else {
- git_odb_backend b = GIT_ODB_BACKEND_INIT;
- memcpy(backend, &b, sizeof(b));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT);
+ return 0;
}
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 7b46a6652..ef6de41a9 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -714,7 +714,7 @@ struct foreach_state {
GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
{
int v, i = 0;
- if (strlen(ptr) != 41)
+ if (strlen(ptr) != GIT_OID_HEXSZ+1)
return -1;
if (ptr[2] != '/') {
@@ -755,6 +755,10 @@ static int foreach_cb(void *_state, git_buf *path)
{
struct foreach_state *state = (struct foreach_state *) _state;
+ /* non-dir is some stray file, ignore it */
+ if (!git_path_isdir(git_buf_cstr(path)))
+ return 0;
+
return git_path_direach(path, 0, foreach_object_dir_cb, state);
}
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 3750da37f..1757cf920 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -210,7 +210,7 @@ static int packfile_load__cb(void *data, git_buf *path)
return 0;
}
- error = git_packfile_alloc(&pack, path->ptr);
+ error = git_mwindow_get_pack(&pack, path->ptr);
/* ignore missing .pack file as git does */
if (error == GIT_ENOTFOUND) {
@@ -605,7 +605,7 @@ static void pack_backend__free(git_odb_backend *_backend)
for (i = 0; i < backend->packs.length; ++i) {
struct git_pack_file *p = git_vector_get(&backend->packs, i);
- git_packfile_free(p);
+ git_mwindow_put_pack(p);
}
git_vector_free(&backend->packs);
@@ -647,7 +647,7 @@ int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
if (pack_backend__alloc(&backend, 1) < 0)
return -1;
- if (git_packfile_alloc(&packfile, idx) < 0 ||
+ if (git_mwindow_get_pack(&packfile, idx) < 0 ||
git_vector_insert(&backend->packs, packfile) < 0)
{
pack_backend__free((git_odb_backend *)backend);
@@ -664,6 +664,9 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
struct pack_backend *backend = NULL;
git_buf path = GIT_BUF_INIT;
+ if (git_mwindow_files_init() < 0)
+ return -1;
+
if (pack_backend__alloc(&backend, 8) < 0)
return -1;
diff --git a/src/oid.c b/src/oid.c
index b640cadd1..969931d04 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -8,6 +8,7 @@
#include "common.h"
#include "git2/oid.h"
#include "repository.h"
+#include "global.h"
#include <string.h>
#include <limits.h>
@@ -99,6 +100,13 @@ void git_oid_pathfmt(char *str, const git_oid *oid)
str = fmt_one(str, oid->id[i]);
}
+char *git_oid_tostr_s(const git_oid *oid)
+{
+ char *str = GIT_GLOBAL->oid_fmt;
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
+ return str;
+}
+
char *git_oid_allocfmt(const git_oid *oid)
{
char *str = git__malloc(GIT_OID_HEXSZ + 1);
diff --git a/src/oid.h b/src/oid.h
index cfe7ca1b2..aa1f0bfdc 100644
--- a/src/oid.h
+++ b/src/oid.h
@@ -9,6 +9,17 @@
#include "git2/oid.h"
+/**
+ * Format a git_oid into a newly allocated c-string.
+ *
+ * The c-string is owned by the caller and needs to be manually freed.
+ *
+ * @param id the oid structure to format
+ * @return the c-string; NULL if memory is exhausted. Caller must
+ * deallocate the string with git__free().
+ */
+char *git_oid_allocfmt(const git_oid *id);
+
GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2)
{
int i;
diff --git a/src/oidarray.c b/src/oidarray.c
new file mode 100644
index 000000000..1d51a2958
--- /dev/null
+++ b/src/oidarray.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/oidarray.h"
+#include "oidarray.h"
+#include "array.h"
+
+void git_oidarray_free(git_oidarray *arr)
+{
+ git__free(arr->ids);
+}
+
+void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array)
+{
+ arr->count = array->size;
+ arr->ids = array->ptr;
+}
diff --git a/src/oidarray.h b/src/oidarray.h
new file mode 100644
index 000000000..a7215ae6c
--- /dev/null
+++ b/src/oidarray.h
@@ -0,0 +1,18 @@
+/*
+ * 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_oidarray_h__
+#define INCLUDE_oidarray_h__
+
+#include "common.h"
+#include "git2/oidarray.h"
+#include "array.h"
+
+typedef git_array_t(git_oid) git_array_oid_t;
+
+extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array);
+
+#endif
diff --git a/src/pack-objects.c b/src/pack-objects.c
index ace8afd17..0040a826b 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -90,8 +90,8 @@ static int packbuilder_config(git_packbuilder *pb)
int ret;
int64_t val;
- if (git_repository_config__weakptr(&config, pb->repo) < 0)
- return -1;
+ if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0)
+ return ret;
#define config_get(KEY,DST,DFLT) do { \
ret = git_config_get_int64(&val, config, KEY); \
@@ -109,6 +109,8 @@ static int packbuilder_config(git_packbuilder *pb)
#undef config_get
+ git_config_free(config);
+
return 0;
}
@@ -1207,7 +1209,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list,
git_mutex_unlock(&target->mutex);
if (!sub_size) {
- git_thread_join(target->thread, NULL);
+ git_thread_join(&target->thread, NULL);
git_cond_free(&target->cond);
git_mutex_free(&target->mutex);
active_threads--;
@@ -1276,6 +1278,7 @@ int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t siz
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
{
PREPARE_PACK;
+ git_buf_sanitize(buf);
return write_pack(pb, &write_pack_buf, buf);
}
diff --git a/src/pack.c b/src/pack.c
index de038a45c..516e0f3ca 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,135 @@ 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 = 0, 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;
-
- 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;
+ 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 (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;
+ }
+
+ /* 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--;
}
- *obj_offset = curpos;
+cleanup:
+ if (error < 0)
+ git__free(obj->data);
+
+ if (elem)
+ *obj_offset = curpos;
+
+ git_array_clear(chain);
return error;
}
@@ -660,7 +773,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 +804,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 +849,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 +876,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;
}
@@ -852,16 +965,17 @@ void git_packfile_free(struct git_pack_file *p)
cache_free(&p->bases);
- git_mwindow_free_all(&p->mwf);
-
- if (p->mwf.fd >= 0)
+ if (p->mwf.fd >= 0) {
+ git_mwindow_free_all_locked(&p->mwf);
p_close(p->mwf.fd);
+ }
pack_index_free(p);
git__free(p->bad_object_sha1);
git_mutex_free(&p->lock);
+ git_mutex_free(&p->bases.lock);
git__free(p);
}
@@ -946,6 +1060,23 @@ cleanup:
return -1;
}
+int git_packfile__name(char **out, const char *path)
+{
+ size_t path_len;
+ git_buf buf = GIT_BUF_INIT;
+
+ path_len = strlen(path);
+
+ if (path_len < strlen(".idx"))
+ return git_odb__error_notfound("invalid packfile path", NULL);
+
+ if (git_buf_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0)
+ return -1;
+
+ *out = git_buf_detach(&buf);
+ return 0;
+}
+
int git_packfile_alloc(struct git_pack_file **pack_out, const char *path)
{
struct stat st;
@@ -997,6 +1128,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..34d37d907 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;
@@ -80,6 +90,7 @@ struct git_pack_file {
git_mwindow_file mwf;
git_map index_map;
git_mutex lock; /* protect updates to mwf and index_map */
+ git_atomic refcount;
uint32_t num_objects;
uint32_t num_bad_objects;
@@ -113,6 +124,8 @@ typedef struct git_packfile_stream {
size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type);
+int git_packfile__name(char **out, const char *path);
+
int git_packfile_unpack_header(
size_t *size_p,
git_otype *type_p,
diff --git a/src/path.c b/src/path.c
index 2690cd8e8..67133f97e 100644
--- a/src/path.c
+++ b/src/path.c
@@ -377,26 +377,33 @@ static int error_invalid_local_file_uri(const char *uri)
return -1;
}
-int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+static int local_file_url_prefixlen(const char *file_url)
{
- int offset = 0, len;
+ int len = -1;
- assert(local_path_out && file_url);
+ if (git__prefixcmp(file_url, "file://") == 0) {
+ if (file_url[7] == '/')
+ len = 8;
+ else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
+ len = 17;
+ }
- if (git__prefixcmp(file_url, "file://") != 0)
- return error_invalid_local_file_uri(file_url);
+ return len;
+}
- offset += 7;
- len = (int)strlen(file_url);
+bool git_path_is_local_file_url(const char *file_url)
+{
+ return (local_file_url_prefixlen(file_url) > 0);
+}
- if (offset < len && file_url[offset] == '/')
- offset++;
- else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0)
- offset += 10;
- else
- return error_invalid_local_file_uri(file_url);
+int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+{
+ int offset;
+
+ assert(local_path_out && file_url);
- if (offset >= len || file_url[offset] == '/')
+ if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
+ file_url[offset] == '\0' || file_url[offset] == '/')
return error_invalid_local_file_uri(file_url);
#ifndef GIT_WIN32
@@ -404,14 +411,13 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url)
#endif
git_buf_clear(local_path_out);
-
return git__percent_decode(local_path_out, file_url + offset);
}
int git_path_walk_up(
git_buf *path,
const char *ceiling,
- int (*cb)(void *data, git_buf *),
+ int (*cb)(void *data, const char *),
void *data)
{
int error = 0;
@@ -429,12 +435,20 @@ int git_path_walk_up(
}
scan = git_buf_len(path);
+ /* empty path: yield only once */
+ if (!scan) {
+ error = cb(data, "");
+ if (error)
+ giterr_set_after_callback(error);
+ return error;
+ }
+
iter.ptr = path->ptr;
iter.size = git_buf_len(path);
iter.asize = path->asize;
while (scan >= stop) {
- error = cb(data, &iter);
+ error = cb(data, iter.ptr);
iter.ptr[scan] = oldc;
if (error) {
@@ -454,6 +468,13 @@ int git_path_walk_up(
if (scan >= 0)
iter.ptr[scan] = oldc;
+ /* relative path: yield for the last component */
+ if (!error && stop == 0 && iter.ptr[0] != '/') {
+ error = cb(data, "");
+ if (error)
+ giterr_set_after_callback(error);
+ }
+
return error;
}
@@ -744,6 +765,61 @@ int git_path_cmp(
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
+int git_path_make_relative(git_buf *path, const char *parent)
+{
+ const char *p, *q, *p_dirsep, *q_dirsep;
+ size_t plen = path->size, newlen, depth = 1, i;
+
+ for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/') {
+ p_dirsep = p;
+ q_dirsep = q;
+ }
+ else if (*p != *q)
+ break;
+ }
+
+ /* need at least 1 common path segment */
+ if ((p_dirsep == path->ptr || q_dirsep == parent) &&
+ (*p_dirsep != '/' || *q_dirsep != '/')) {
+ giterr_set(GITERR_INVALID,
+ "%s is not a parent of %s", parent, path->ptr);
+ return GIT_ENOTFOUND;
+ }
+
+ if (*p == '/' && !*q)
+ p++;
+ else if (!*p && *q == '/')
+ q++;
+ else if (!*p && !*q)
+ return git_buf_clear(path), 0;
+ else {
+ p = p_dirsep + 1;
+ q = q_dirsep + 1;
+ }
+
+ plen -= (p - path->ptr);
+
+ if (!*q)
+ return git_buf_set(path, p, plen);
+
+ for (; (q = strchr(q, '/')) && *(q + 1); q++)
+ depth++;
+
+ newlen = (depth * 3) + plen;
+
+ if (git_buf_try_grow(path, newlen + 1, 1, 0) < 0)
+ return -1;
+
+ memmove(path->ptr + (depth * 3), p, plen + 1);
+
+ for (i = 0; i < depth; i++)
+ memcpy(path->ptr + (i * 3), "../", 3);
+
+ path->size = newlen;
+ return 0;
+}
+
bool git_path_has_non_ascii(const char *path, size_t pathlen)
{
const uint8_t *scan = (const uint8_t *)path, *end;
@@ -799,8 +875,11 @@ int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen)
if (rv != (size_t)-1)
break;
+ /* if we cannot convert the data (probably because iconv thinks
+ * it is not valid UTF-8 source data), then use original data
+ */
if (errno != E2BIG)
- goto fail;
+ return 0;
/* make space for 2x the remaining data to be converted
* (with per retry overhead to avoid infinite loops)
@@ -823,6 +902,64 @@ fail:
return -1;
}
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
+
+/* Check if the platform is decomposing unicode data for us. We will
+ * emulate core Git and prefer to use precomposed unicode data internally
+ * on these platforms, composing the decomposed unicode on the fly.
+ *
+ * This mainly happens on the Mac where HDFS stores filenames as
+ * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
+ * return decomposed unicode from readdir() even when the actual
+ * filesystem is storing precomposed unicode.
+ */
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ bool found_decomposed = false;
+ char tmp[6];
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0 ||
+ (fd = p_mkstemp(path.ptr)) < 0)
+ goto done;
+ p_close(fd);
+
+ /* record trailing digits generated by mkstemp */
+ memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
+
+ /* try to look up as NFD path */
+ if (git_buf_joinpath(&path, root, nfd_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ found_decomposed = git_path_exists(path.ptr);
+
+ /* remove temporary file (using original precomposed path) */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ (void)p_unlink(path.ptr);
+
+done:
+ git_buf_free(&path);
+ return found_decomposed;
+}
+
+#else
+
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ GIT_UNUSED(root);
+ return false;
+}
+
#endif
#if defined(__sun) || defined(__GNU__)
@@ -843,7 +980,7 @@ int git_path_direach(
path_dirent_data de_data;
struct dirent *de, *de_buf = (struct dirent *)&de_data;
- (void)flags;
+ GIT_UNUSED(flags);
#ifdef GIT_USE_ICONV
git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
@@ -914,7 +1051,7 @@ int git_path_dirload(
path_dirent_data de_data;
struct dirent *de, *de_buf = (struct dirent *)&de_data;
- (void)flags;
+ GIT_UNUSED(flags);
#ifdef GIT_USE_ICONV
git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
@@ -966,8 +1103,10 @@ int git_path_dirload(
entry_path[path_len] = '/';
memcpy(&entry_path[path_len + need_slash], de_path, de_len);
- if ((error = git_vector_insert(contents, entry_path)) < 0)
+ if ((error = git_vector_insert(contents, entry_path)) < 0) {
+ git__free(entry_path);
break;
+ }
}
closedir(dir);
@@ -1043,20 +1182,29 @@ int git_path_dirload_with_stat(
if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
(error = git_path_lstat(full.ptr, &ps->st)) < 0) {
+
if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = 0;
+ /* file was removed between readdir and lstat */
git_vector_remove(contents, i--);
- continue;
+ } else {
+ /* Treat the file as unreadable if we get any other error */
+ memset(&ps->st, 0, sizeof(ps->st));
+ ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
}
- break;
+ giterr_clear();
+ error = 0;
+ continue;
}
if (S_ISDIR(ps->st.st_mode)) {
ps->path[ps->path_len++] = '/';
ps->path[ps->path_len] = '\0';
}
+ else if (!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
+ /* skip everything but dirs, plain files, and symlinks */
+ git_vector_remove(contents, i--);
+ }
}
/* sort now that directory suffix is added */
@@ -1066,3 +1214,11 @@ 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)
+{
+ if (git_path_is_local_file_url(url_or_path))
+ return git_path_fromurl(local_path_out, url_or_path);
+ else
+ return git_buf_sets(local_path_out, url_or_path);
+}
diff --git a/src/path.h b/src/path.h
index 2367d707b..11bb6d173 100644
--- a/src/path.h
+++ b/src/path.h
@@ -8,6 +8,7 @@
#define INCLUDE_path_h__
#include "common.h"
+#include "posix.h"
#include "buffer.h"
#include "vector.h"
@@ -127,6 +128,14 @@ GIT_INLINE(int) git_path_is_relative(const char *p)
return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')));
}
+/**
+ * Check if string is at end of path segment (i.e. looking at '/' or '\0')
+ */
+GIT_INLINE(int) git_path_at_end_of_segment(const char *p)
+{
+ return !*p || *p == '/';
+}
+
extern int git__percent_decode(git_buf *decoded_out, const char *input);
/**
@@ -188,6 +197,17 @@ extern bool git_path_contains(git_buf *dir, const char *item);
extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
/**
+ * Make the path relative to the given parent path.
+ *
+ * @param path The path to make relative
+ * @param parent The parent path to make path relative to
+ * @return 0 if path was made relative, GIT_ENOTFOUND
+ * if there was not common root between the paths,
+ * or <0.
+ */
+extern int git_path_make_relative(git_buf *path, const char *parent);
+
+/**
* Check if the given path contains the given file.
*
* @param dir Directory path that might contain file
@@ -303,7 +323,7 @@ extern int git_path_cmp(
extern int git_path_walk_up(
git_buf *pathbuf,
const char *ceiling,
- int (*callback)(void *payload, git_buf *path),
+ int (*callback)(void *payload, const char *path),
void *payload);
/**
@@ -436,4 +456,10 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen);
#endif /* GIT_USE_ICONV */
+extern bool git_path_does_fs_decompose_unicode(const char *root);
+
+/* Used for paths to repositories on the filesystem */
+extern bool git_path_is_local_file_url(const char *file_url);
+extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
+
#endif
diff --git a/src/pool.c b/src/pool.c
index 146f118b4..30555273e 100644
--- a/src/pool.c
+++ b/src/pool.c
@@ -7,7 +7,7 @@ struct git_pool_page {
git_pool_page *next;
uint32_t size;
uint32_t avail;
- char data[GIT_FLEX_ARRAY];
+ GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8);
};
struct pool_freelist {
@@ -146,7 +146,7 @@ GIT_INLINE(void) pool_remove_page(
void *git_pool_malloc(git_pool *pool, uint32_t items)
{
git_pool_page *scan = pool->open, *prev;
- uint32_t size = items * pool->item_size;
+ uint32_t size = ((items * pool->item_size) + 7) & ~7;
void *ptr = NULL;
pool->has_string_alloc = 0;
diff --git a/src/posix.c b/src/posix.c
index 7b2962feb..7aeb0e6c1 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -99,7 +99,7 @@ const char *p_gai_strerror(int ret)
#endif /* NO_ADDRINFO */
-int p_open(const char *path, int flags, ...)
+int p_open(const char *path, volatile int flags, ...)
{
mode_t mode = 0;
@@ -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..9ef348739 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -12,23 +12,61 @@
#include <time.h>
#include "fnmatch.h"
+/* stat: file mode type testing macros */
#ifndef S_IFGITLINK
#define S_IFGITLINK 0160000
#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
#endif
+#ifndef S_IFLNK
+#define S_IFLNK 0120000
+#undef _S_IFLNK
+#define _S_IFLNK S_IFLNK
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 00100
+#endif
+
+#ifndef S_ISLNK
+#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+#endif
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+#endif
+
+#ifndef S_ISFIFO
+#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
+#endif
+
/* if S_ISGID is not defined, then don't try to set it */
#ifndef S_ISGID
#define S_ISGID 0
#endif
-#if !defined(O_BINARY)
+#ifndef O_BINARY
#define O_BINARY 0
#endif
-#if !defined(O_CLOEXEC)
+#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
+/* access() mode parameter #defines */
+#ifndef F_OK
+#define F_OK 0 /* existence check */
+#endif
+#ifndef W_OK
+#define W_OK 2 /* write mode check */
+#endif
+#ifndef R_OK
+#define R_OK 4 /* read mode check */
+#endif
+
/* Determine whether an errno value indicates that a read or write failed
* because the descriptor is blocked.
*/
@@ -38,6 +76,12 @@
#define GIT_ISBLOCKED(e) ((e) == EAGAIN)
#endif
+/* define some standard errnos that the runtime may be missing. for example,
+ * mingw lacks EAFNOSUPPORT. */
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT (INT_MAX-1)
+#endif
+
typedef int git_file;
/**
@@ -56,8 +100,6 @@ typedef int git_file;
extern int p_read(git_file fd, void *buf, size_t cnt);
extern int p_write(git_file fd, const void *buf, size_t cnt);
-#define p_fstat(f,b) fstat(f, b)
-#define p_lseek(f,n,w) lseek(f, n, w)
#define p_close(fd) close(fd)
#define p_umask(m) umask(m)
@@ -66,29 +108,6 @@ extern int p_creat(const char *path, mode_t mode);
extern int p_getcwd(char *buffer_out, size_t size);
extern int p_rename(const char *from, const char *to);
-#ifndef GIT_WIN32
-
-#define p_stat(p,b) stat(p, b)
-#define p_chdir(p) chdir(p)
-#define p_rmdir(p) rmdir(p)
-#define p_chmod(p,m) chmod(p, m)
-#define p_access(p,m) access(p,m)
-#define p_recv(s,b,l,f) recv(s,b,l,f)
-#define p_send(s,b,l,f) send(s,b,l,f)
-typedef int GIT_SOCKET;
-#define INVALID_SOCKET -1
-
-#define p_localtime_r localtime_r
-#define p_gmtime_r gmtime_r
-
-#else
-
-typedef SOCKET GIT_SOCKET;
-extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
-extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
-
-#endif
-
/**
* Platform-dependent methods
*/
diff --git a/src/push.c b/src/push.c
index 9943f215c..be5ec1c0e 100644
--- a/src/push.c
+++ b/src/push.c
@@ -716,14 +716,9 @@ void git_push_free(git_push *push)
git__free(push);
}
-int git_push_init_options(git_push_options* opts, int version)
+int git_push_init_options(git_push_options *opts, unsigned int version)
{
- if (version != GIT_PUSH_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_push_options", version);
- return -1;
- } else {
- git_push_options o = GIT_PUSH_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/refdb.c b/src/refdb.c
index 3e7a592f8..69bf74734 100644
--- a/src/refdb.c
+++ b/src/refdb.c
@@ -236,14 +236,9 @@ int git_refdb_ensure_log(git_refdb *db, const char *refname)
return db->backend->ensure_log(db->backend, refname);
}
-int git_refdb_init_backend(git_refdb_backend* backend, int version)
+int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
{
- if (version != GIT_REFDB_BACKEND_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_refdb_backend", version);
- return -1;
- } else {
- git_refdb_backend b = GIT_REFDB_BACKEND_INIT;
- memcpy(backend, &b, sizeof(b));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
+ return 0;
}
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..2e79bdfca 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -35,7 +35,7 @@
#define GIT_FETCH_HEAD_FILE "FETCH_HEAD"
#define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"
-#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD"
+#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD"
#define GIT_BISECT_LOG_FILE "BISECT_LOG"
#define GIT_REBASE_MERGE_DIR "rebase-merge/"
#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive"
@@ -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;
@@ -61,12 +63,11 @@ struct git_reference {
} target;
git_oid peel;
- char name[0];
+ char name[GIT_FLEX_ARRAY];
};
git_reference *git_reference__set_name(git_reference *ref, const char *name);
-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/refspec.c b/src/refspec.c
index fa60aa7aa..9f0df35a7 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -181,39 +181,75 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
static int refspec_transform(
git_buf *out, const char *from, const char *to, const char *name)
{
- size_t to_len = to ? strlen(to) : 0;
- size_t from_len = from ? strlen(from) : 0;
- size_t name_len = name ? strlen(name) : 0;
+ const char *from_star, *to_star;
+ const char *name_slash, *from_slash;
+ size_t replacement_len, star_offset;
git_buf_sanitize(out);
+ git_buf_clear(out);
- if (git_buf_set(out, to, to_len) < 0)
- return -1;
+ /*
+ * There are two parts to each side of a refspec, the bit
+ * before the star and the bit after it. The star can be in
+ * the middle of the pattern, so we need to look at each bit
+ * individually.
+ */
+ from_star = strchr(from, '*');
+ to_star = strchr(to, '*');
- if (to_len > 0) {
- /* No '*' at the end of 'to' means that refspec is mapped to one
- * specific branch, so no actual transformation is needed.
- */
- if (out->ptr[to_len - 1] != '*')
- return 0;
- git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */
- }
+ assert(from_star && to_star);
+
+ /* star offset, both in 'from' and in 'name' */
+ star_offset = from_star - from;
- if (from_len > 0) /* ignore trailing '*' from 'from' */
- from_len--;
- if (from_len > name_len)
- from_len = name_len;
+ /* the first half is copied over */
+ git_buf_put(out, to, to_star - to);
- return git_buf_put(out, name + from_len, name_len - from_len);
+ /* then we copy over the replacement, from the star's offset to the next slash in 'name' */
+ name_slash = strchr(name + star_offset, '/');
+ if (!name_slash)
+ name_slash = strrchr(name, '\0');
+
+ /* if there is no slash after the star in 'from', we want to copy everything over */
+ from_slash = strchr(from + star_offset, '/');
+ if (!from_slash)
+ name_slash = strrchr(name, '\0');
+
+ replacement_len = (name_slash - name) - star_offset;
+ git_buf_put(out, name + star_offset, replacement_len);
+
+ return git_buf_puts(out, to_star + 1);
}
int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name)
{
+ assert(out && spec && name);
+ git_buf_sanitize(out);
+
+ if (!git_refspec_src_matches(spec, name)) {
+ giterr_set(GITERR_INVALID, "ref '%s' doesn't match the source", name);
+ return -1;
+ }
+
+ if (!spec->pattern)
+ return git_buf_puts(out, spec->dst);
+
return refspec_transform(out, spec->src, spec->dst, name);
}
int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name)
{
+ assert(out && spec && name);
+ git_buf_sanitize(out);
+
+ if (!git_refspec_dst_matches(spec, name)) {
+ giterr_set(GITERR_INVALID, "ref '%s' doesn't match the destination", name);
+ return -1;
+ }
+
+ if (!spec->pattern)
+ return git_buf_puts(out, spec->src);
+
return refspec_transform(out, spec->dst, spec->src, name);
}
diff --git a/src/remote.c b/src/remote.c
index ea638e373..dfad946d5 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -73,13 +73,15 @@ static int ensure_remote_name_is_valid(const char *name)
if (!git_remote_is_valid_name(name)) {
giterr_set(
GITERR_CONFIG,
- "'%s' is not a valid remote name.", name);
+ "'%s' is not a valid remote name.", name ? name : "(null)");
error = GIT_EINVALIDSPEC;
}
return error;
}
+#if 0
+/* We could export this as a helper */
static int get_check_cert(int *out, git_repository *repo)
{
git_config *cfg;
@@ -105,6 +107,7 @@ static int get_check_cert(int *out, git_repository *repo)
*out = git_config__get_bool_force(cfg, "http.sslverify", 1);
return 0;
}
+#endif
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
@@ -121,9 +124,6 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
remote->repo = repo;
remote->update_fetchhead = 1;
- if (get_check_cert(&remote->check_cert, repo) < 0)
- goto on_error;
-
if (git_vector_init(&remote->refs, 32, NULL) < 0)
goto on_error;
@@ -267,12 +267,13 @@ int git_remote_dup(git_remote **dest, git_remote *source)
if (source->pushurl != NULL) {
remote->pushurl = git__strdup(source->pushurl);
- GITERR_CHECK_ALLOC(remote->pushurl);
+ GITERR_CHECK_ALLOC(remote->pushurl);
}
+ remote->transport_cb = source->transport_cb;
+ remote->transport_cb_payload = source->transport_cb_payload;
remote->repo = source->repo;
remote->download_tags = source->download_tags;
- remote->check_cert = source->check_cert;
remote->update_fetchhead = source->update_fetchhead;
if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
@@ -356,8 +357,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
if ((error = ensure_remote_name_is_valid(name)) < 0)
return error;
- if (git_repository_config__weakptr(&config, repo) < 0)
- return -1;
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
remote = git__malloc(sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);
@@ -367,9 +368,6 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
- if ((error = get_check_cert(&remote->check_cert, repo)) < 0)
- goto cleanup;
-
if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
@@ -403,6 +401,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;
}
@@ -437,6 +436,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
*out = remote;
cleanup:
+ git_config_free(config);
git_buf_free(&buf);
if (error < 0)
@@ -657,18 +657,21 @@ int git_remote_connect(git_remote *remote, git_direction direction)
return -1;
}
- /* A transport could have been supplied in advance with
- * git_remote_set_transport */
+ /* If we don't have a transport object yet, and the caller specified a
+ * custom transport factory, use that */
+ if (!t && remote->transport_cb &&
+ (error = remote->transport_cb(&t, remote, remote->transport_cb_payload)) < 0)
+ return error;
+
+ /* If we still don't have a transport, then use the global
+ * transport registrations which map URI schemes to transport factories */
if (!t && (error = git_transport_new(&t, remote, url)) < 0)
return error;
if (t->set_callbacks &&
- (error = t->set_callbacks(t, remote->callbacks.sideband_progress, NULL, remote->callbacks.payload)) < 0)
+ (error = t->set_callbacks(t, remote->callbacks.sideband_progress, NULL, remote->callbacks.certificate_check, remote->callbacks.payload)) < 0)
goto on_error;
- if (!remote->check_cert)
- flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
-
if ((error = t->connect(t, url, remote->callbacks.credentials, remote->callbacks.payload, direction, flags)) != 0)
goto on_error;
@@ -689,6 +692,11 @@ int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote
{
assert(remote);
+ if (!remote->transport) {
+ giterr_set(GITERR_NET, "No transport bound to this remote");
+ return -1;
+ }
+
return remote->transport->ls(out, size, remote->transport);
}
@@ -1050,16 +1058,20 @@ static int update_tips_for_spec(
if (autotag && !git_odb_exists(odb, &head->oid))
continue;
- if (git_vector_insert(&update_heads, head) < 0)
+ if (!autotag && git_vector_insert(&update_heads, head) < 0)
goto on_error;
error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
memset(&old, 0, GIT_OID_RAWSZ);
+ if (autotag && git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+ }
+
if (!git_oid__cmp(&old, &head->oid))
continue;
@@ -1229,13 +1241,6 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
return 0;
}
-void git_remote_check_cert(git_remote *remote, int check)
-{
- assert(remote);
-
- remote->check_cert = check;
-}
-
int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks)
{
assert(remote && callbacks);
@@ -1248,6 +1253,7 @@ int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *cal
return remote->transport->set_callbacks(remote->transport,
remote->callbacks.sideband_progress,
NULL,
+ remote->callbacks.certificate_check,
remote->callbacks.payload);
return 0;
@@ -1260,18 +1266,20 @@ const git_remote_callbacks *git_remote_get_callbacks(git_remote *remote)
return &remote->callbacks;
}
-int git_remote_set_transport(git_remote *remote, git_transport *transport)
+int git_remote_set_transport(
+ git_remote *remote,
+ git_transport_cb transport_cb,
+ void *payload)
{
- assert(remote && transport);
-
- GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+ assert(remote);
if (remote->transport) {
giterr_set(GITERR_NET, "A transport is already bound to this remote");
return -1;
}
- remote->transport = transport;
+ remote->transport_cb = transport_cb;
+ remote->transport_cb_payload = payload;
return 0;
}
@@ -1303,13 +1311,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);
@@ -1356,19 +1365,24 @@ static int update_branch_remote_config_entry(
}
static int rename_one_remote_reference(
- git_reference *reference,
+ git_reference *reference_in,
const char *old_remote_name,
const char *new_remote_name)
{
int error;
+ git_reference *ref = NULL, *dummy = NULL;
+ git_buf namespace = GIT_BUF_INIT, old_namespace = GIT_BUF_INIT;
git_buf new_name = GIT_BUF_INIT;
git_buf log_message = GIT_BUF_INIT;
+ size_t pfx_len;
+ const char *target;
- if ((error = git_buf_printf(
- &new_name,
- GIT_REFS_REMOTES_DIR "%s%s",
- new_remote_name,
- reference->name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name))) < 0)
+ if ((error = git_buf_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0)
+ return error;
+
+ pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1;
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0)
goto cleanup;
if ((error = git_buf_printf(&log_message,
@@ -1376,12 +1390,36 @@ static int rename_one_remote_reference(
old_remote_name, new_remote_name)) < 0)
goto cleanup;
- error = git_reference_rename(
- NULL, reference, git_buf_cstr(&new_name), 0,
- NULL, git_buf_cstr(&log_message));
- git_reference_free(reference);
+ if ((error = git_reference_rename(&ref, reference_in, git_buf_cstr(&new_name), 1,
+ NULL, git_buf_cstr(&log_message))) < 0)
+ goto cleanup;
+
+ if (git_reference_type(ref) != GIT_REF_SYMBOLIC)
+ goto cleanup;
+
+ /* Handle refs like origin/HEAD -> origin/master */
+ target = git_reference_symbolic_target(ref);
+ if ((error = git_buf_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0)
+ goto cleanup;
+
+ if (git__prefixcmp(target, old_namespace.ptr))
+ goto cleanup;
+
+ git_buf_clear(&new_name);
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, target + pfx_len)) < 0)
+ goto cleanup;
+
+ error = git_reference_symbolic_set_target(&dummy, ref, git_buf_cstr(&new_name),
+ NULL, git_buf_cstr(&log_message));
+
+ git_reference_free(dummy);
cleanup:
+ git_reference_free(reference_in);
+ git_reference_free(ref);
+ git_buf_free(&namespace);
+ git_buf_free(&old_namespace);
git_buf_free(&new_name);
git_buf_free(&log_message);
return error;
@@ -1393,18 +1431,20 @@ static int rename_remote_references(
const char *new_name)
{
int error;
+ git_buf buf = GIT_BUF_INIT;
git_reference *ref;
git_reference_iterator *iter;
- if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ if ((error = git_buf_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0)
return error;
- while ((error = git_reference_next(&ref, iter)) == 0) {
- if (git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
- git_reference_free(ref);
- continue;
- }
+ error = git_reference_iterator_glob_new(&iter, repo, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+ while ((error = git_reference_next(&ref, iter)) == 0) {
if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0)
break;
}
@@ -1414,11 +1454,7 @@ static int rename_remote_references(
return (error == GIT_ITEROVER) ? 0 : error;
}
-static int rename_fetch_refspecs(
- git_remote *remote,
- const char *new_name,
- int (*callback)(const char *problematic_refspec, void *payload),
- void *payload)
+static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name)
{
git_config *config;
git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT;
@@ -1429,6 +1465,9 @@ static int rename_fetch_refspecs(
if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0)
return error;
+ if ((error = git_vector_init(problems, 1, NULL)) < 0)
+ return error;
+
if ((error = git_buf_printf(
&base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0)
return error;
@@ -1437,15 +1476,15 @@ static int rename_fetch_refspecs(
if (spec->push)
continue;
- /* Every refspec is a problem refspec for an anonymous remote, OR */
/* Does the dst part of the refspec follow the expected format? */
- if (!remote->name ||
- strcmp(git_buf_cstr(&base), spec->string)) {
+ if (strcmp(git_buf_cstr(&base), spec->string)) {
+ char *dup;
- if ((error = callback(spec->string, payload)) != 0) {
- giterr_set_after_callback(error);
+ dup = git__strdup(spec->string);
+ GITERR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(problems, dup)) < 0)
break;
- }
continue;
}
@@ -1471,18 +1510,25 @@ static int rename_fetch_refspecs(
git_buf_free(&base);
git_buf_free(&var);
git_buf_free(&val);
+
+ if (error < 0) {
+ char *str;
+ git_vector_foreach(problems, i, str)
+ git__free(str);
+
+ git_vector_free(problems);
+ }
+
return error;
}
-int git_remote_rename(
- git_remote *remote,
- const char *new_name,
- git_remote_rename_problem_cb callback,
- void *payload)
+int git_remote_rename(git_strarray *out, git_remote *remote, const char *new_name)
{
int error;
+ git_vector problem_refspecs;
+ char *tmp, *dup;
- assert(remote && new_name);
+ assert(out && remote && new_name);
if (!remote->name) {
giterr_set(GITERR_INVALID, "Can't rename an anonymous remote.");
@@ -1492,54 +1538,30 @@ int git_remote_rename(
if ((error = ensure_remote_name_is_valid(new_name)) < 0)
return error;
- if (remote->repo) {
- if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
- return error;
+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
+ return error;
- if (!remote->name) {
- if ((error = rename_fetch_refspecs(
- remote,
- new_name,
- callback,
- payload)) < 0)
- return error;
+ if ((error = rename_remote_config_section(remote->repo, remote->name, new_name)) < 0)
+ return error;
- remote->name = git__strdup(new_name);
- GITERR_CHECK_ALLOC(remote->name);
+ if ((error = update_branch_remote_config_entry(remote->repo, remote->name, new_name)) < 0)
+ return error;
- return git_remote_save(remote);
- }
+ if ((error = rename_remote_references(remote->repo, remote->name, new_name)) < 0)
+ return error;
- if ((error = rename_remote_config_section(
- remote->repo,
- remote->name,
- new_name)) < 0)
- return error;
-
- if ((error = update_branch_remote_config_entry(
- remote->repo,
- remote->name,
- new_name)) < 0)
- return error;
-
- if ((error = rename_remote_references(
- remote->repo,
- remote->name,
- new_name)) < 0)
- return error;
-
- if ((error = rename_fetch_refspecs(
- remote,
- new_name,
- callback,
- payload)) < 0)
- return error;
- }
+ if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0)
+ return error;
- git__free(remote->name);
+ out->count = problem_refspecs.length;
+ out->strings = (char **) problem_refspecs.contents;
- remote->name = git__strdup(new_name);
- GITERR_CHECK_ALLOC(remote->name);
+ dup = git__strdup(new_name);
+ GITERR_CHECK_ALLOC(dup);
+
+ tmp = remote->name;
+ remote->name = dup;
+ git__free(tmp);
return 0;
}
@@ -1736,14 +1758,229 @@ const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n)
return git_vector_get(&remote->refspecs, n);
}
-int git_remote_init_callbacks(git_remote_callbacks* opts, int version)
+int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ 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)
{
- if (version != GIT_REMOTE_CALLBACKS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_remote_callbacks", version);
+ int error;
+ git_repository *repo;
+
+ assert(remote);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't delete an anonymous remote.");
return -1;
- } else {
- git_remote_callbacks o = GIT_REMOTE_CALLBACKS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
}
+
+ 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;
+
+ 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;
+
+ assert(out);
+
+ if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0)
+ return error;
+
+ if (heads_len == 0)
+ return GIT_ENOTFOUND;
+
+ if (strcmp(heads[0]->name, GIT_HEAD_FILE))
+ 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 (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR))
+ 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/remote.h b/src/remote.h
index 4164a14b3..f88601e9b 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -9,6 +9,7 @@
#include "git2/remote.h"
#include "git2/transport.h"
+#include "git2/sys/transport.h"
#include "refspec.h"
#include "vector.h"
@@ -22,13 +23,14 @@ struct git_remote {
git_vector refs;
git_vector refspecs;
git_vector active_refspecs;
+ git_transport_cb transport_cb;
+ void *transport_cb_payload;
git_transport *transport;
git_repository *repo;
git_remote_callbacks callbacks;
git_transfer_progress stats;
unsigned int need_pack;
git_remote_autotag_option_t download_tags;
- int check_cert;
int update_fetchhead;
};
diff --git a/src/repository.c b/src/repository.c
index 8daa04d5d..51d39eb6d 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -169,13 +169,9 @@ int git_repository_new(git_repository **out)
return 0;
}
-static int load_config_data(git_repository *repo)
+static int load_config_data(git_repository *repo, const git_config *config)
{
int is_bare;
- git_config *config;
-
- if (git_repository_config__weakptr(&config, repo) < 0)
- return -1;
/* Try to figure out if it's bare, default to non-bare if it's not set */
if (git_config_get_bool(&is_bare, config, "core.bare") < 0)
@@ -186,19 +182,15 @@ static int load_config_data(git_repository *repo)
return 0;
}
-static int load_workdir(git_repository *repo, git_buf *parent_path)
+static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
{
int error;
- git_config *config;
const git_config_entry *ce;
git_buf worktree = GIT_BUF_INIT;
if (repo->is_bare)
return 0;
- if ((error = git_repository_config__weakptr(&config, repo)) < 0)
- return error;
-
if ((error = git_config__lookup_entry(
&ce, config, "core.worktree", false)) < 0)
return error;
@@ -467,16 +459,22 @@ int git_repository_open_ext(
if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
repo->is_bare = 1;
- else if ((error = load_config_data(repo)) < 0 ||
- (error = load_workdir(repo, &parent)) < 0)
- {
- git_repository_free(repo);
- return error;
+ 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);
}
+ 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)
@@ -627,6 +625,17 @@ int git_repository_config(git_config **out, git_repository *repo)
return 0;
}
+int git_repository_config_snapshot(git_config **out, git_repository *repo)
+{
+ int error;
+ git_config *weak;
+
+ if ((error = git_repository_config__weakptr(&weak, repo)) < 0)
+ return error;
+
+ return git_config_snapshot(out, weak);
+}
+
void git_repository_set_config(git_repository *repo, git_config *config)
{
assert(repo && config);
@@ -880,60 +889,6 @@ static bool are_symlinks_supported(const char *wd_path)
return symlinks_supported;
}
-#ifdef GIT_USE_ICONV
-
-static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
-static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
-
-/* Check if the platform is decomposing unicode data for us. We will
- * emulate core Git and prefer to use precomposed unicode data internally
- * on these platforms, composing the decomposed unicode on the fly.
- *
- * This mainly happens on the Mac where HDFS stores filenames as
- * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
- * return decomposed unicode from readdir() even when the actual
- * filesystem is storing precomposed unicode.
- */
-static bool does_fs_decompose_unicode_paths(const char *wd_path)
-{
- git_buf path = GIT_BUF_INIT;
- int fd;
- bool found_decomposed = false;
- char tmp[6];
-
- /* Create a file using a precomposed path and then try to find it
- * using the decomposed name. If the lookup fails, then we will mark
- * that we should precompose unicode for this repository.
- */
- if (git_buf_joinpath(&path, wd_path, nfc_file) < 0 ||
- (fd = p_mkstemp(path.ptr)) < 0)
- goto done;
- p_close(fd);
-
- /* record trailing digits generated by mkstemp */
- memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
-
- /* try to look up as NFD path */
- if (git_buf_joinpath(&path, wd_path, nfd_file) < 0)
- goto done;
- memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
-
- found_decomposed = git_path_exists(path.ptr);
-
- /* remove temporary file (using original precomposed path) */
- if (git_buf_joinpath(&path, wd_path, nfc_file) < 0)
- goto done;
- memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
-
- (void)p_unlink(path.ptr);
-
-done:
- git_buf_free(&path);
- return found_decomposed;
-}
-
-#endif
-
static int create_empty_file(const char *path, mode_t mode)
{
int fd;
@@ -1024,8 +979,9 @@ static int repo_init_fs_configs(
#ifdef GIT_USE_ICONV
if ((error = git_config_set_bool(
cfg, "core.precomposeunicode",
- does_fs_decompose_unicode_paths(work_dir))) < 0)
+ git_path_does_fs_decompose_unicode(work_dir))) < 0)
return error;
+ /* on non-iconv platforms, don't even set core.precomposeunicode */
#endif
return 0;
@@ -1038,7 +994,7 @@ static int repo_init_config(
uint32_t mode)
{
int error = 0;
- git_buf cfg_path = GIT_BUF_INIT;
+ git_buf cfg_path = GIT_BUF_INIT, worktree_path = GIT_BUF_INIT;
git_config *config = NULL;
bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0);
bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0);
@@ -1063,9 +1019,16 @@ static int repo_init_config(
if (!is_bare) {
SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
- if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD))
- SET_REPO_CONFIG(string, "core.worktree", work_dir);
- else if (is_reinit) {
+ if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
+ if ((error = git_buf_sets(&worktree_path, work_dir)) < 0)
+ goto cleanup;
+
+ if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK))
+ if ((error = git_path_make_relative(&worktree_path, repo_dir)) < 0)
+ goto cleanup;
+
+ SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr);
+ } else if (is_reinit) {
if (git_config_delete_entry(config, "core.worktree") < 0)
giterr_clear();
}
@@ -1082,6 +1045,7 @@ static int repo_init_config(
cleanup:
git_buf_free(&cfg_path);
+ git_buf_free(&worktree_path);
git_config_free(config);
return error;
@@ -1170,10 +1134,11 @@ static int repo_write_template(
}
static int repo_write_gitlink(
- const char *in_dir, const char *to_repo)
+ const char *in_dir, const char *to_repo, bool use_relative_path)
{
int error;
git_buf buf = GIT_BUF_INIT;
+ git_buf path_to_repo = GIT_BUF_INIT;
struct stat st;
git_path_dirname_r(&buf, to_repo);
@@ -1201,13 +1166,20 @@ static int repo_write_gitlink(
git_buf_clear(&buf);
- error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo);
+ error = git_buf_sets(&path_to_repo, to_repo);
+
+ if (!error && use_relative_path)
+ error = git_path_make_relative(&path_to_repo, in_dir);
+
+ if (!error)
+ error = git_buf_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr);
if (!error)
error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr);
cleanup:
git_buf_free(&buf);
+ git_buf_free(&path_to_repo);
return error;
}
@@ -1234,6 +1206,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
@@ -1250,7 +1223,7 @@ static int repo_init_structure(
if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 &&
(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0)
{
- if (repo_write_gitlink(work_dir, repo_dir) < 0)
+ if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0)
return -1;
}
@@ -1274,10 +1247,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);
@@ -1298,9 +1273,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;
@@ -1671,7 +1651,7 @@ int git_repository_set_workdir(
if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
- error = repo_write_gitlink(path.ptr, git_repository_path(repo));
+ error = repo_write_gitlink(path.ptr, git_repository_path(repo), false);
/* passthrough error means gitlink is unnecessary */
if (error == GIT_PASSTHROUGH)
@@ -1701,6 +1681,32 @@ int git_repository_is_bare(git_repository *repo)
return repo->is_bare;
}
+int git_repository_set_bare(git_repository *repo)
+{
+ int error;
+ git_config *config;
+
+ assert(repo);
+
+ if (repo->is_bare)
+ return 0;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ if ((error = git_config_set_bool(config, "core.bare", false)) < 0)
+ return error;
+
+ if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0)
+ return error;
+
+ git__free(repo->workdir);
+ repo->workdir = NULL;
+ repo->is_bare = 1;
+
+ return 0;
+}
+
int git_repository_head_tree(git_tree **tree, git_repository *repo)
{
git_reference *head;
@@ -1789,7 +1795,8 @@ int git_repository_hashfile(
/* passing empty string for "as_path" indicated --no-filters */
if (strlen(as_path) > 0) {
error = git_filter_list_load(
- &fl, repo, NULL, as_path, GIT_FILTER_TO_ODB);
+ &fl, repo, NULL, as_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_DEFAULT);
if (error < 0)
return error;
} else {
@@ -1950,8 +1957,8 @@ int git_repository_state(git_repository *repo)
state = GIT_REPOSITORY_STATE_MERGE;
else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))
state = GIT_REPOSITORY_STATE_REVERT;
- else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))
- state = GIT_REPOSITORY_STATE_CHERRY_PICK;
+ else if(git_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_CHERRYPICK;
else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))
state = GIT_REPOSITORY_STATE_BISECT;
@@ -1993,7 +2000,7 @@ static const char *state_files[] = {
GIT_MERGE_MODE_FILE,
GIT_MERGE_MSG_FILE,
GIT_REVERT_HEAD_FILE,
- GIT_CHERRY_PICK_HEAD_FILE,
+ GIT_CHERRYPICK_HEAD_FILE,
GIT_BISECT_LOG_FILE,
GIT_REBASE_MERGE_DIR,
GIT_REBASE_APPLY_DIR,
@@ -2026,14 +2033,11 @@ int git_repository_is_shallow(git_repository *repo)
return st.st_size == 0 ? 0 : 1;
}
-int git_repository_init_init_options(git_repository_init_options* opts, int version)
+int git_repository_init_init_options(
+ git_repository_init_options *opts, unsigned int version)
{
- if (version != GIT_REPOSITORY_INIT_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_repository_init_options", version);
- return -1;
- } else {
- git_repository_init_options o = GIT_REPOSITORY_INIT_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_repository_init_options,
+ GIT_REPOSITORY_INIT_OPTIONS_INIT);
+ return 0;
}
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 */
diff --git a/src/revert.c b/src/revert.c
index 29e124f6c..36560a77c 100644
--- a/src/revert.c
+++ b/src/revert.c
@@ -174,7 +174,7 @@ int git_revert(
char commit_oidstr[GIT_OID_HEXSZ + 1];
const char *commit_msg;
git_buf their_label = GIT_BUF_INIT;
- git_index *index_new = NULL, *index_repo = NULL;
+ git_index *index_new = NULL;
int error;
assert(repo && commit);
@@ -199,10 +199,9 @@ int git_revert(
(error = git_repository_head(&our_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
(error = git_revert_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
- (error = git_merge__indexes(repo, index_new)) < 0 ||
- (error = git_repository_index(&index_repo, repo)) < 0 ||
- (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
- (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0)
+ (error = git_merge__check_result(repo, index_new)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_new)) < 0 ||
+ (error = git_checkout_index(repo, index_new, &opts.checkout_opts)) < 0)
goto on_error;
goto done;
@@ -212,7 +211,6 @@ on_error:
done:
git_index_free(index_new);
- git_index_free(index_repo);
git_commit_free(our_commit);
git_reference_free(our_ref);
git_buf_free(&their_label);
@@ -220,14 +218,9 @@ done:
return error;
}
-int git_revert_init_opts(git_revert_options* opts, int version)
+int git_revert_init_options(git_revert_options *opts, unsigned int version)
{
- if (version != GIT_REVERT_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_revert_options", version);
- return -1;
- } else {
- git_revert_options o = GIT_REVERT_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT);
+ return 0;
}
diff --git a/src/revparse.c b/src/revparse.c
index 60872e187..e0ec3941d 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -205,7 +205,6 @@ cleanup:
static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
{
git_reflog *reflog;
- int error = -1;
size_t numentries;
const git_reflog_entry *entry;
bool search_by_pos = (identifier <= 100000000);
@@ -216,21 +215,11 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide
numentries = git_reflog_entrycount(reflog);
if (search_by_pos) {
- if (numentries < identifier + 1) {
- giterr_set(
- GITERR_REFERENCE,
- "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
- git_reference_name(ref), numentries, identifier);
-
- error = GIT_ENOTFOUND;
- goto cleanup;
- }
+ if (numentries < identifier + 1)
+ goto notfound;
entry = git_reflog_entry_byindex(reflog, identifier);
git_oid_cpy(oid, git_reflog_entry_id_new(entry));
- error = 0;
- goto cleanup;
-
} else {
size_t i;
git_time commit_time;
@@ -243,16 +232,24 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide
continue;
git_oid_cpy(oid, git_reflog_entry_id_new(entry));
- error = 0;
- goto cleanup;
+ break;
}
- error = GIT_ENOTFOUND;
+ if (i == numentries)
+ goto notfound;
}
-cleanup:
git_reflog_free(reflog);
- return error;
+ return 0;
+
+notfound:
+ giterr_set(
+ GITERR_REFERENCE,
+ "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
+ git_reference_name(ref), numentries, identifier);
+
+ git_reflog_free(reflog);
+ return GIT_ENOTFOUND;
}
static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
diff --git a/src/revwalk.c b/src/revwalk.c
index 7aedd1f44..bd07d02cb 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -48,9 +48,6 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
assert(commit);
- git_array_alloc(pending);
- GITERR_CHECK_ARRAY(pending);
-
do {
commit->uninteresting = 1;
@@ -67,7 +64,7 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
tmp = git_array_pop(pending);
commit = tmp ? *tmp : NULL;
- } while (git_array_size(pending) > 0);
+ } while (commit != NULL);
git_array_clear(pending);
diff --git a/src/signature.c b/src/signature.c
index f501cd8b6..2a16b484a 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -70,9 +70,9 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema
if (p->name == NULL || p->email == NULL)
return -1; /* oom */
- if (p->name[0] == '\0') {
+ if (p->name[0] == '\0' || p->email[0] == '\0') {
git_signature_free(p);
- return signature_error("Signature cannot have an empty name");
+ return signature_error("Signature cannot have an empty name or email");
}
p->when.time = time;
@@ -144,7 +144,7 @@ int git_signature_default(git_signature **out, git_repository *repo)
git_config *cfg;
const char *user_name, *user_email;
- if ((error = git_repository_config(&cfg, repo)) < 0)
+ if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
return error;
if (!(error = git_config_get_string(&user_name, cfg, "user.name")) &&
diff --git a/src/stash.c b/src/stash.c
index 86e0a627c..caffd0cea 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -232,7 +232,8 @@ static int build_untracked_tree(
}
if (flags & GIT_STASH_INCLUDE_IGNORED) {
- opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_RECURSE_IGNORED_DIRS;
data.include_ignored = true;
}
@@ -447,10 +448,11 @@ static int ensure_there_are_changes_to_stash(
if (include_untracked_files)
opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
- GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
if (include_ignored_files)
- opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);
@@ -634,7 +636,8 @@ int git_stash_drop(
entry = git_reflog_entry_byindex(reflog, 0);
git_reference_free(stash);
- if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1, NULL, NULL) < 0))
+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1, NULL, NULL);
+ if (error < 0)
goto cleanup;
/* We need to undo the writing that we just did */
diff --git a/src/status.c b/src/status.c
index c4b990a84..cb2490042 100644
--- a/src/status.c
+++ b/src/status.c
@@ -62,6 +62,9 @@ static unsigned int workdir_delta2status(
case GIT_DELTA_UNTRACKED:
st = GIT_STATUS_WT_NEW;
break;
+ case GIT_DELTA_UNREADABLE:
+ st = GIT_STATUS_WT_UNREADABLE;
+ break;
case GIT_DELTA_DELETED:
st = GIT_STATUS_WT_DELETED;
break;
@@ -81,15 +84,15 @@ static unsigned int workdir_delta2status(
if (git_oid_iszero(&idx2wd->old_file.id) &&
diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
!git_diff__oid_for_file(
- diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode,
- idx2wd->old_file.size, &idx2wd->old_file.id))
+ &idx2wd->old_file.id, diff, idx2wd->old_file.path,
+ idx2wd->old_file.mode, idx2wd->old_file.size))
idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
if (git_oid_iszero(&idx2wd->new_file.id) &&
diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
!git_diff__oid_for_file(
- diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode,
- idx2wd->new_file.size, &idx2wd->new_file.id))
+ &idx2wd->new_file.id, diff, idx2wd->new_file.path,
+ idx2wd->new_file.mode, idx2wd->new_file.size))
idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id))
@@ -225,6 +228,28 @@ static git_status_list *git_status_list_alloc(git_index *index)
return status;
}
+static int status_validate_options(const git_status_options *opts)
+{
+ if (!opts)
+ return 0;
+
+ GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
+
+ if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ giterr_set(GITERR_INVALID, "Unknown status 'show' option");
+ return -1;
+ }
+
+ if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 &&
+ (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) {
+ giterr_set(GITERR_INVALID, "Updating index from status "
+ "is not allowed when index refresh is disabled");
+ return -1;
+ }
+
+ return 0;
+}
+
int git_status_list_new(
git_status_list **out,
git_repository *repo,
@@ -240,11 +265,10 @@ int git_status_list_new(
int error = 0;
unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
- assert(show <= GIT_STATUS_SHOW_WORKDIR_ONLY);
-
*out = NULL;
- GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
+ if (status_validate_options(opts) < 0)
+ return -1;
if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
(error = git_repository_index(&index, repo)) < 0)
@@ -287,6 +311,12 @@ int git_status_list_new(
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+ if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED;
if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
findopt.flags = findopt.flags |
@@ -306,8 +336,9 @@ int git_status_list_new(
if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
if ((error = git_diff_index_to_workdir(
- &status->idx2wd, repo, index, &diffopt)) < 0)
+ &status->idx2wd, repo, index, &diffopt)) < 0) {
goto done;
+ }
if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
(error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
@@ -384,8 +415,9 @@ int git_status_foreach_ext(
size_t i;
int error = 0;
- if ((error = git_status_list_new(&status, repo, opts)) < 0)
+ if ((error = git_status_list_new(&status, repo, opts)) < 0) {
return error;
+ }
git_vector_foreach(&status->paired, i, status_entry) {
const char *path = status_entry->head_to_index ?
@@ -495,14 +527,31 @@ int git_status_should_ignore(
return git_ignore_path_is_ignored(ignored, repo, path);
}
-int git_status_init_options(git_status_options* opts, int version)
+int git_status_init_options(git_status_options *opts, unsigned int version)
{
- if (version != GIT_STATUS_OPTIONS_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_status_options", version);
- return -1;
- } else {
- git_status_options o = GIT_STATUS_OPTIONS_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT);
+ return 0;
+}
+
+int git_status_list_get_perfdata(
+ git_diff_perfdata *out, const git_status_list *status)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
+
+ out->stat_calls = 0;
+ out->oid_calculations = 0;
+
+ if (status->head2idx) {
+ out->stat_calls += status->head2idx->perf.stat_calls;
+ out->oid_calculations += status->head2idx->perf.oid_calculations;
}
+ if (status->idx2wd) {
+ out->stat_calls += status->idx2wd->perf.stat_calls;
+ out->oid_calculations += status->idx2wd->perf.oid_calculations;
+ }
+
+ return 0;
}
+
diff --git a/src/strnlen.h b/src/strnlen.h
index fdd7fe39c..eecfe3c02 100644
--- a/src/strnlen.h
+++ b/src/strnlen.h
@@ -7,7 +7,8 @@
#ifndef INCLUDE_strlen_h__
#define INCLUDE_strlen_h__
-#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__)
+#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\
+ (defined(_MSC_VER) && _MSC_VER < 1500)
# define NO_STRNLEN
#endif
diff --git a/src/submodule.c b/src/submodule.c
index 5ddbfe828..ccc8ad117 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -306,6 +306,56 @@ void git_submodule_cache_free(git_repository *repo)
submodule_cache_free(cache);
}
+static int submodule_repo_init(
+ git_repository **out,
+ git_repository *parent_repo,
+ const char *path,
+ const char *url,
+ bool use_gitlink)
+{
+ int error = 0;
+ git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ git_repository *subrepo = NULL;
+
+ error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
+ initopt.origin_url = url;
+
+ /* init submodule repository and add origin remote as needed */
+
+ /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
+ * gitlink in the sub-repo workdir directory to that repository
+ *
+ * Old style: sub-repo goes directly into repo/<name>/.git/
+ */
+ if (use_gitlink) {
+ error = git_buf_join3(
+ &repodir, '/', git_repository_path(parent_repo), "modules", path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.workdir_path = workdir.ptr;
+ initopt.flags |=
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
+ GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
+
+ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+ } else
+ error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt);
+
+cleanup:
+ git_buf_free(&workdir);
+ git_buf_free(&repodir);
+
+ *out = subrepo;
+
+ return error;
+}
+
int git_submodule_add_setup(
git_submodule **out,
git_repository *repo,
@@ -317,7 +367,6 @@ int git_submodule_add_setup(
git_config_backend *mods = NULL;
git_submodule *sm = NULL;
git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
- git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
git_repository *subrepo = NULL;
assert(repo && url && path);
@@ -371,41 +420,14 @@ int git_submodule_add_setup(
if (error < 0)
goto cleanup;
- /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
- * gitlink in the sub-repo workdir directory to that repository
- *
- * Old style: sub-repo goes directly into repo/<name>/.git/
+ /* if the repo does not already exist, then init a new repo and add it.
+ * Otherwise, just add the existing repo.
*/
-
- initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
- GIT_REPOSITORY_INIT_NO_REINIT;
- initopt.origin_url = real_url.ptr;
-
- if (git_path_exists(name.ptr) &&
- git_path_contains(&name, DOT_GIT))
- {
- /* repo appears to already exist - reinit? */
- }
- else if (use_gitlink) {
- git_buf repodir = GIT_BUF_INIT;
-
- error = git_buf_join3(
- &repodir, '/', git_repository_path(repo), "modules", path);
- if (error < 0)
+ if (!(git_path_exists(name.ptr) &&
+ git_path_contains(&name, DOT_GIT))) {
+ if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0)
goto cleanup;
-
- initopt.workdir_path = name.ptr;
- initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
-
- error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
-
- git_buf_free(&repodir);
}
- else {
- error = git_repository_init_ext(&subrepo, name.ptr, &initopt);
- }
- if (error < 0)
- goto cleanup;
/* add submodule to hash and "reload" it */
@@ -437,6 +459,23 @@ cleanup:
return error;
}
+int git_submodule_repo_init(
+ git_repository **out,
+ const git_submodule *sm,
+ int use_gitlink)
+{
+ int error;
+ git_repository *sub_repo = NULL;
+
+ assert(out && sm);
+
+ error = submodule_repo_init(&sub_repo, sm->repo, sm->path, sm->url, use_gitlink);
+
+ *out = sub_repo;
+
+ return error;
+}
+
int git_submodule_add_finalize(git_submodule *sm)
{
int error;
@@ -641,7 +680,9 @@ int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *ur
{
int error = 0;
- assert(url);
+ assert(out && repo && url);
+
+ git_buf_sanitize(out);
if (git_path_is_relative(url)) {
if (!(error = get_url_base(out, repo)))
@@ -1895,6 +1936,7 @@ static void submodule_get_index_status(unsigned int *status, git_submodule *sm)
*status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
}
+
static void submodule_get_wd_status(
unsigned int *status,
git_submodule *sm,
diff --git a/src/sysdir.c b/src/sysdir.c
index aebf23135..cd94a8b57 100644
--- a/src/sysdir.c
+++ b/src/sysdir.c
@@ -90,6 +90,8 @@ void git_sysdir_global_shutdown(void)
int i;
for (i = 0; i < GIT_SYSDIR__MAX; ++i)
git_buf_free(&git_sysdir__dirs[i]);
+
+ git_sysdir__dirs_shutdown_set = 0;
}
static int git_sysdir_check_selector(git_sysdir_t which)
@@ -194,14 +196,19 @@ static int git_sysdir_find_in_dirlist(
const git_buf *syspath;
GITERR_CHECK_ERROR(git_sysdir_get(&syspath, which));
+ if (!syspath || !git_buf_len(syspath))
+ goto done;
for (scan = git_buf_cstr(syspath); scan; scan = next) {
- for (next = strchr(scan, GIT_PATH_LIST_SEPARATOR);
- next && next > scan && next[-1] == '\\';
- next = strchr(next + 1, GIT_PATH_LIST_SEPARATOR))
- /* find unescaped separator or end of string */;
+ /* find unescaped separator or end of string */
+ for (next = scan; *next; ++next) {
+ if (*next == GIT_PATH_LIST_SEPARATOR &&
+ (next <= scan || next[-1] != '\\'))
+ break;
+ }
- len = next ? (size_t)(next++ - scan) : strlen(scan);
+ len = (size_t)(next - scan);
+ next = (*next ? next + 1 : NULL);
if (!len)
continue;
@@ -213,6 +220,7 @@ static int git_sysdir_find_in_dirlist(
return 0;
}
+done:
git_buf_free(path);
giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);
return GIT_ENOTFOUND;
diff --git a/src/tag.c b/src/tag.c
index 1a4ee1e1c..d7b531d34 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -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/thread-utils.h b/src/thread-utils.h
index 50d8610a3..5511a5117 100644
--- a/src/thread-utils.h
+++ b/src/thread-utils.h
@@ -40,17 +40,17 @@ typedef git_atomic git_atomic_ssize;
#ifdef GIT_THREADS
-#define git_thread pthread_t
-#define git_thread_create(thread, attr, start_routine, arg) \
- pthread_create(thread, attr, start_routine, arg)
-#define git_thread_kill(thread) pthread_cancel(thread)
-#define git_thread_exit(status) pthread_exit(status)
-#define git_thread_join(id, status) pthread_join(id, status)
+#if !defined(GIT_WIN32)
+
+typedef struct {
+ pthread_t thread;
+} git_thread;
+
+#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \
+ pthread_create(&(git_thread_ptr)->thread, attr, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ pthread_join((git_thread_ptr)->thread, status)
-#if defined(GIT_WIN32)
-#define git_thread_yield() Sleep(0)
-#else
-#define git_thread_yield() sched_yield()
#endif
/* Pthreads Mutex */
@@ -179,10 +179,7 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
#define git_thread unsigned int
#define git_thread_create(thread, attr, start_routine, arg) 0
-#define git_thread_kill(thread) (void)0
-#define git_thread_exit(status) (void)0
#define git_thread_join(id, status) (void)0
-#define git_thread_yield() (void)0
/* Pthreads Mutex */
#define git_mutex unsigned int
diff --git a/src/trace.h b/src/trace.h
index 4d4e3bf53..486084d01 100644
--- a/src/trace.h
+++ b/src/trace.h
@@ -46,8 +46,16 @@ GIT_INLINE(void) git_trace__write_fmt(
#else
+GIT_INLINE(void) git_trace__null(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ GIT_UNUSED(level);
+ GIT_UNUSED(fmt);
+}
+
#define git_trace_level() ((void)0)
-#define git_trace(lvl, ...) ((void)0)
+#define git_trace git_trace__null
#endif
diff --git a/src/transport.c b/src/transport.c
index dc074a503..d42c92684 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -9,11 +9,11 @@
#include "git2/remote.h"
#include "git2/net.h"
#include "git2/transport.h"
+#include "git2/sys/transport.h"
#include "path.h"
typedef struct transport_definition {
char *prefix;
- unsigned priority;
git_transport_cb fn;
void *param;
} transport_definition;
@@ -24,52 +24,55 @@ static git_smart_subtransport_definition git_subtransport_definition = { git_sma
static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
#endif
-static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
-#ifdef GIT_SSH
-static transport_definition ssh_transport_definition = { "ssh://", 1, git_transport_smart, &ssh_subtransport_definition };
-#else
-static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
-#endif
+static transport_definition local_transport_definition = { "file://", git_transport_local, NULL };
static transport_definition transports[] = {
- {"git://", 1, git_transport_smart, &git_subtransport_definition},
- {"http://", 1, git_transport_smart, &http_subtransport_definition},
- {"https://", 1, git_transport_smart, &http_subtransport_definition},
- {"file://", 1, git_transport_local, NULL},
+ { "git://", git_transport_smart, &git_subtransport_definition },
+ { "http://", git_transport_smart, &http_subtransport_definition },
+#if defined(GIT_SSL) || defined(GIT_WINHTTP)
+ { "https://", git_transport_smart, &http_subtransport_definition },
+#endif
+ { "file://", git_transport_local, NULL },
#ifdef GIT_SSH
- {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition},
+ { "ssh://", git_transport_smart, &ssh_subtransport_definition },
#endif
- {NULL, 0, 0}
+ { NULL, 0, 0 }
};
-static git_vector additional_transports = GIT_VECTOR_INIT;
+static git_vector custom_transports = GIT_VECTOR_INIT;
#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
-static int transport_find_fn(const char *url, git_transport_cb *callback, void **param)
+static transport_definition * transport_find_by_url(const char *url)
{
size_t i = 0;
- unsigned priority = 0;
- transport_definition *definition = NULL, *definition_iter;
+ transport_definition *d;
- // First, check to see if it's an obvious URL, which a URL scheme
- for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
- definition_iter = &transports[i];
+ /* Find a user transport who wants to deal with this URI */
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) {
+ return d;
+ }
+ }
- if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
- continue;
+ /* Find a system transport for this URI */
+ for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
+ d = &transports[i];
- if (definition_iter->priority > priority)
- definition = definition_iter;
+ if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) {
+ return d;
+ }
}
- git_vector_foreach(&additional_transports, i, definition_iter) {
- if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
- continue;
+ return NULL;
+}
- if (definition_iter->priority > priority)
- definition = definition_iter;
- }
+static int transport_find_fn(
+ git_transport_cb *out,
+ const char *url,
+ void **param)
+{
+ transport_definition *definition = transport_find_by_url(url);
#ifdef GIT_WIN32
/* On Windows, it might not be possible to discern between absolute local
@@ -86,12 +89,10 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
/* It could be a SSH remote path. Check to see if there's a :
* SSH is an unsupported transport mechanism in this version of libgit2 */
- if (!definition && strrchr(url, ':'))
-#ifdef GIT_SSH
- definition = &ssh_transport_definition;
-#else
- definition = &dummy_transport_definition;
-#endif
+ if (!definition && strrchr(url, ':')) {
+ // re-search transports again with ssh:// as url so that we can find a third party ssh transport
+ definition = transport_find_by_url("ssh://");
+ }
#ifndef GIT_WIN32
/* Check to see if the path points to a file on the local file system */
@@ -100,9 +101,9 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
#endif
if (!definition)
- return -1;
+ return GIT_ENOTFOUND;
- *callback = definition->fn;
+ *out = definition->fn;
*param = definition->param;
return 0;
@@ -112,15 +113,6 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
* Public API *
**************/
-int git_transport_dummy(git_transport **transport, git_remote *owner, void *param)
-{
- GIT_UNUSED(transport);
- GIT_UNUSED(owner);
- GIT_UNUSED(param);
- giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
- return -1;
-}
-
int git_transport_new(git_transport **out, git_remote *owner, const char *url)
{
git_transport_cb fn;
@@ -128,83 +120,96 @@ int git_transport_new(git_transport **out, git_remote *owner, const char *url)
void *param;
int error;
- if (transport_find_fn(url, &fn, &param) < 0) {
+ if ((error = transport_find_fn(&fn, url, &param)) == GIT_ENOTFOUND) {
giterr_set(GITERR_NET, "Unsupported URL protocol");
return -1;
- }
+ } else if (error < 0)
+ return error;
- error = fn(&transport, owner, param);
- if (error < 0)
+ if ((error = fn(&transport, owner, param)) < 0)
return error;
+ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+
*out = transport;
return 0;
}
int git_transport_register(
- const char *prefix,
- unsigned priority,
+ const char *scheme,
git_transport_cb cb,
void *param)
{
- transport_definition *d;
+ git_buf prefix = GIT_BUF_INIT;
+ transport_definition *d, *definition = NULL;
+ size_t i;
+ int error = 0;
- d = git__calloc(sizeof(transport_definition), 1);
- GITERR_CHECK_ALLOC(d);
+ assert(scheme);
+ assert(cb);
- d->prefix = git__strdup(prefix);
-
- if (!d->prefix)
+ if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0)
goto on_error;
- d->priority = priority;
- d->fn = cb;
- d->param = param;
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strcasecmp(d->prefix, prefix.ptr) == 0) {
+ error = GIT_EEXISTS;
+ goto on_error;
+ }
+ }
+
+ definition = git__calloc(1, sizeof(transport_definition));
+ GITERR_CHECK_ALLOC(definition);
+
+ definition->prefix = git_buf_detach(&prefix);
+ definition->fn = cb;
+ definition->param = param;
- if (git_vector_insert(&additional_transports, d) < 0)
+ if (git_vector_insert(&custom_transports, definition) < 0)
goto on_error;
return 0;
on_error:
- git__free(d->prefix);
- git__free(d);
- return -1;
+ git_buf_free(&prefix);
+ git__free(definition);
+ return error;
}
-int git_transport_unregister(
- const char *prefix,
- unsigned priority)
+int git_transport_unregister(const char *scheme)
{
+ git_buf prefix = GIT_BUF_INIT;
transport_definition *d;
- unsigned i;
+ size_t i;
+ int error = 0;
+
+ assert(scheme);
+
+ if ((error = git_buf_printf(&prefix, "%s://", scheme)) < 0)
+ goto done;
- git_vector_foreach(&additional_transports, i, d) {
- if (d->priority == priority && !strcasecmp(d->prefix, prefix)) {
- if (git_vector_remove(&additional_transports, i) < 0)
- return -1;
+ git_vector_foreach(&custom_transports, i, d) {
+ if (strcasecmp(d->prefix, prefix.ptr) == 0) {
+ if ((error = git_vector_remove(&custom_transports, i)) < 0)
+ goto done;
git__free(d->prefix);
git__free(d);
- if (!additional_transports.length)
- git_vector_free(&additional_transports);
+ if (!custom_transports.length)
+ git_vector_free(&custom_transports);
- return 0;
+ error = 0;
+ goto done;
}
}
- return GIT_ENOTFOUND;
-}
-
-/* from remote.h */
-int git_remote_valid_url(const char *url)
-{
- git_transport_cb fn;
- void *param;
+ error = GIT_ENOTFOUND;
- return !transport_find_fn(url, &fn, &param);
+done:
+ git_buf_free(&prefix);
+ return error;
}
int git_remote_supported_url(const char* url)
@@ -212,20 +217,13 @@ int git_remote_supported_url(const char* url)
git_transport_cb fn;
void *param;
- if (transport_find_fn(url, &fn, &param) < 0)
- return 0;
-
- return fn != &git_transport_dummy;
+ /* The only error we expect is ENOTFOUND */
+ return !transport_find_fn(&fn, url, &param);
}
-int git_transport_init(git_transport* opts, int version)
+int git_transport_init(git_transport *opts, unsigned int version)
{
- if (version != GIT_TRANSPORT_VERSION) {
- giterr_set(GITERR_INVALID, "Invalid version %d for git_transport", version);
- return -1;
- } else {
- git_transport o = GIT_TRANSPORT_INIT;
- memcpy(opts, &o, sizeof(o));
- return 0;
- }
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_transport, GIT_TRANSPORT_INIT);
+ return 0;
}
diff --git a/src/transports/auth.c b/src/transports/auth.c
new file mode 100644
index 000000000..c1154db34
--- /dev/null
+++ b/src/transports/auth.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "buffer.h"
+#include "auth.h"
+
+static int basic_next_token(
+ git_buf *out, git_http_auth_context *ctx, git_cred *c)
+{
+ git_cred_userpass_plaintext *cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
+
+ GIT_UNUSED(ctx);
+
+ if (c->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) {
+ giterr_set(GITERR_INVALID, "invalid credential type for basic auth");
+ goto on_error;
+ }
+
+ cred = (git_cred_userpass_plaintext *)c;
+
+ git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(out, "Authorization: Basic ") < 0 ||
+ git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 ||
+ git_buf_puts(out, "\r\n") < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (raw.size)
+ git__memzero(raw.ptr, raw.size);
+
+ git_buf_free(&raw);
+ return error;
+}
+
+static git_http_auth_context basic_context = {
+ GIT_AUTHTYPE_BASIC,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT,
+ NULL,
+ basic_next_token,
+ NULL
+};
+
+int git_http_auth_basic(
+ git_http_auth_context **out, const gitno_connection_data *connection_data)
+{
+ GIT_UNUSED(connection_data);
+
+ *out = &basic_context;
+ return 0;
+}
+
+int git_http_auth_dummy(
+ git_http_auth_context **out, const gitno_connection_data *connection_data)
+{
+ GIT_UNUSED(connection_data);
+
+ *out = NULL;
+ return 0;
+}
+
diff --git a/src/transports/auth.h b/src/transports/auth.h
new file mode 100644
index 000000000..52138cf8f
--- /dev/null
+++ b/src/transports/auth.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_http_auth_h__
+#define INCLUDE_http_auth_h__
+
+#include "git2.h"
+#include "netops.h"
+
+typedef enum {
+ GIT_AUTHTYPE_BASIC = 1,
+ GIT_AUTHTYPE_NEGOTIATE = 2,
+} git_http_authtype_t;
+
+typedef struct git_http_auth_context git_http_auth_context;
+
+struct git_http_auth_context {
+ /** Type of scheme */
+ git_http_authtype_t type;
+
+ /** Supported credentials */
+ git_credtype_t credtypes;
+
+ /** Sets the challenge on the authentication context */
+ int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
+
+ /** Gets the next authentication token from the context */
+ int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred);
+
+ /** Frees the authentication context */
+ void (*free)(git_http_auth_context *ctx);
+};
+
+typedef struct {
+ /** Type of scheme */
+ git_http_authtype_t type;
+
+ /** Name of the scheme (as used in the Authorization header) */
+ const char *name;
+
+ /** Credential types this scheme supports */
+ git_credtype_t credtypes;
+
+ /** Function to initialize an authentication context */
+ int (*init_context)(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+} git_http_auth_scheme;
+
+int git_http_auth_dummy(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+int git_http_auth_basic(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+#endif
+
diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c
new file mode 100644
index 000000000..8b99fc735
--- /dev/null
+++ b/src/transports/auth_negotiate.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_GSSAPI
+
+#include "git2.h"
+#include "common.h"
+#include "buffer.h"
+#include "auth.h"
+
+#include <gssapi.h>
+#include <krb5.h>
+
+static gss_OID_desc negotiate_oid_spnego =
+ { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
+static gss_OID_desc negotiate_oid_krb5 =
+ { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static gss_OID negotiate_oids[] =
+ { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
+
+typedef struct {
+ git_http_auth_context parent;
+ unsigned configured : 1,
+ complete : 1;
+ git_buf target;
+ char *challenge;
+ gss_ctx_id_t gss_context;
+ gss_OID oid;
+} http_auth_negotiate_context;
+
+static void negotiate_err_set(
+ OM_uint32 status_major,
+ OM_uint32 status_minor,
+ const char *message)
+{
+ gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
+ OM_uint32 status_display, context = 0;
+
+ if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
+ giterr_set(GITERR_NET, "%s: %.*s (%d.%d)",
+ message, (int)buffer.length, (const char *)buffer.value,
+ status_major, status_minor);
+ gss_release_buffer(&status_minor, &buffer);
+ } else {
+ giterr_set(GITERR_NET, "%s: unknown negotiate error (%d.%d)",
+ message, status_major, status_minor);
+ }
+}
+
+static int negotiate_set_challenge(
+ git_http_auth_context *c,
+ const char *challenge)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+
+ assert(ctx && ctx->configured && challenge);
+
+ git__free(ctx->challenge);
+
+ ctx->challenge = git__strdup(challenge);
+ GITERR_CHECK_ALLOC(ctx->challenge);
+
+ return 0;
+}
+
+static int negotiate_next_token(
+ git_buf *buf,
+ git_http_auth_context *c,
+ git_cred *cred)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ OM_uint32 status_major, status_minor;
+ gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
+ input_token = GSS_C_EMPTY_BUFFER,
+ output_token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
+ git_buf input_buf = GIT_BUF_INIT;
+ gss_name_t server = NULL;
+ gss_OID mech;
+ size_t challenge_len;
+ int error = 0;
+
+ assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDTYPE_DEFAULT);
+
+ if (ctx->complete)
+ return 0;
+
+ target_buffer.value = (void *)ctx->target.ptr;
+ target_buffer.length = ctx->target.size;
+
+ status_major = gss_import_name(&status_minor, &target_buffer,
+ GSS_C_NT_HOSTBASED_SERVICE, &server);
+
+ if (GSS_ERROR(status_major)) {
+ negotiate_err_set(status_major, status_minor,
+ "Could not parse principal");
+ error = -1;
+ goto done;
+ }
+
+ challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
+
+ if (challenge_len < 9) {
+ giterr_set(GITERR_NET, "No negotiate challenge sent from server");
+ error = -1;
+ goto done;
+ } else if (challenge_len > 9) {
+ if (git_buf_decode_base64(&input_buf,
+ ctx->challenge + 10, challenge_len - 10) < 0) {
+ giterr_set(GITERR_NET, "Invalid negotiate challenge from server");
+ error = -1;
+ goto done;
+ }
+
+ input_token.value = input_buf.ptr;
+ input_token.length = input_buf.size;
+ input_token_ptr = &input_token;
+ } else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
+ giterr_set(GITERR_NET, "Could not restart authentication");
+ error = -1;
+ goto done;
+ }
+
+ mech = &negotiate_oid_spnego;
+
+ if (GSS_ERROR(status_major = gss_init_sec_context(
+ &status_minor,
+ GSS_C_NO_CREDENTIAL,
+ &ctx->gss_context,
+ server,
+ mech,
+ GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ input_token_ptr,
+ NULL,
+ &output_token,
+ NULL,
+ NULL))) {
+ negotiate_err_set(status_major, status_minor, "Negotiate failure");
+ error = -1;
+ goto done;
+ }
+
+ /* This message merely told us auth was complete; we do not respond. */
+ if (status_major == GSS_S_COMPLETE) {
+ ctx->complete = 1;
+ goto done;
+ }
+
+ git_buf_puts(buf, "Authorization: Negotiate ");
+ git_buf_encode_base64(buf, output_token.value, output_token.length);
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ error = -1;
+
+done:
+ gss_release_name(&status_minor, &server);
+ gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
+ git_buf_free(&input_buf);
+ return error;
+}
+
+static void negotiate_context_free(git_http_auth_context *c)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ OM_uint32 status_minor;
+
+ if (ctx->gss_context != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(
+ &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
+ ctx->gss_context = GSS_C_NO_CONTEXT;
+ }
+
+ git_buf_free(&ctx->target);
+
+ git__free(ctx->challenge);
+
+ ctx->configured = 0;
+ ctx->complete = 0;
+ ctx->oid = NULL;
+
+ git__free(ctx);
+}
+
+static int negotiate_init_context(
+ http_auth_negotiate_context *ctx,
+ const gitno_connection_data *connection_data)
+{
+ OM_uint32 status_major, status_minor;
+ gss_OID item, *oid;
+ gss_OID_set mechanism_list;
+ size_t i;
+
+ /* Query supported mechanisms looking for SPNEGO) */
+ if (GSS_ERROR(status_major =
+ gss_indicate_mechs(&status_minor, &mechanism_list))) {
+ negotiate_err_set(status_major, status_minor,
+ "could not query mechanisms");
+ return -1;
+ }
+
+ if (mechanism_list) {
+ for (oid = negotiate_oids; *oid; oid++) {
+ for (i = 0; i < mechanism_list->count; i++) {
+ item = &mechanism_list->elements[i];
+
+ if (item->length == (*oid)->length &&
+ memcmp(item->elements, (*oid)->elements, item->length) == 0) {
+ ctx->oid = *oid;
+ break;
+ }
+
+ }
+
+ if (ctx->oid)
+ break;
+ }
+ }
+
+ gss_release_oid_set(&status_minor, &mechanism_list);
+
+ if (!ctx->oid) {
+ giterr_set(GITERR_NET, "Negotiate authentication is not supported");
+ return -1;
+ }
+
+ git_buf_puts(&ctx->target, "HTTP@");
+ git_buf_puts(&ctx->target, connection_data->host);
+
+ if (git_buf_oom(&ctx->target))
+ return -1;
+
+ ctx->gss_context = GSS_C_NO_CONTEXT;
+ ctx->configured = 1;
+
+ return 0;
+}
+
+int git_http_auth_negotiate(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data)
+{
+ http_auth_negotiate_context *ctx;
+
+ *out = NULL;
+
+ ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
+ GITERR_CHECK_ALLOC(ctx);
+
+ if (negotiate_init_context(ctx, connection_data) < 0) {
+ git__free(ctx);
+ return -1;
+ }
+
+ ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE;
+ ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT;
+ ctx->parent.set_challenge = negotiate_set_challenge;
+ ctx->parent.next_token = negotiate_next_token;
+ ctx->parent.free = negotiate_context_free;
+
+ *out = (git_http_auth_context *)ctx;
+
+ return 0;
+}
+
+#endif /* GIT_GSSAPI */
+
diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h
new file mode 100644
index 000000000..d7270b7ab
--- /dev/null
+++ b/src/transports/auth_negotiate.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_auth_negotiate_h__
+#define INCLUDE_auth_negotiate_h__
+
+#include "git2.h"
+#include "auth.h"
+
+#ifdef GIT_GSSAPI
+
+extern int git_http_auth_negotiate(
+ git_http_auth_context **out,
+ const gitno_connection_data *connection_data);
+
+#else
+
+#define git_http_auth_negotiate git_http_auth_dummy
+
+#endif /* GIT_GSSAPI */
+
+#endif
+
diff --git a/src/transports/cred.c b/src/transports/cred.c
index 913ec36cc..1b4d29c0a 100644
--- a/src/transports/cred.c
+++ b/src/transports/cred.c
@@ -17,6 +17,40 @@ int git_cred_has_username(git_cred *cred)
return 1;
}
+const char *git_cred__username(git_cred *cred)
+{
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERNAME:
+ {
+ git_cred_username *c = (git_cred_username *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT:
+ {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_KEY:
+ {
+ git_cred_ssh_key *c = (git_cred_ssh_key *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_CUSTOM:
+ {
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *) cred;
+ return c->username;
+ }
+ case GIT_CREDTYPE_SSH_INTERACTIVE:
+ {
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *) cred;
+ return c->username;
+ }
+
+ default:
+ return NULL;
+ }
+}
+
static void plaintext_free(struct git_cred *cred)
{
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
@@ -129,6 +163,11 @@ static void default_free(struct git_cred *cred)
git__free(c);
}
+static void username_free(struct git_cred *cred)
+{
+ git__free(cred);
+}
+
int git_cred_ssh_key_new(
git_cred **cred,
const char *username,
@@ -263,3 +302,22 @@ int git_cred_default_new(git_cred **cred)
*cred = c;
return 0;
}
+
+int git_cred_username_new(git_cred **cred, const char *username)
+{
+ git_cred_username *c;
+ size_t len;
+
+ assert(cred);
+
+ len = strlen(username);
+ c = git__malloc(sizeof(git_cred_username) + len + 1);
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERNAME;
+ c->parent.free = username_free;
+ memcpy(c->username, username, len + 1);
+
+ *cred = (git_cred *) c;
+ return 0;
+}
diff --git a/src/transports/cred.h b/src/transports/cred.h
new file mode 100644
index 000000000..2de8deee8
--- /dev/null
+++ b/src/transports/cred.h
@@ -0,0 +1,14 @@
+/*
+ * 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_git_cred_h__
+#define INCLUDE_git_cred_h__
+
+#include "git2/transport.h"
+
+const char *git_cred__username(git_cred *cred);
+
+#endif
diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c
index d420e3e3c..5cc9b0869 100644
--- a/src/transports/cred_helpers.c
+++ b/src/transports/cred_helpers.c
@@ -41,6 +41,9 @@ int git_cred_userpass(
else
return -1;
+ if (GIT_CREDTYPE_USERNAME & allowed_types)
+ return git_cred_username_new(cred, effective_username);
+
if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
return -1;
diff --git a/src/transports/git.c b/src/transports/git.c
index 21507c1c7..e2690fe36 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -8,6 +8,7 @@
#include "git2.h"
#include "buffer.h"
#include "netops.h"
+#include "git2/sys/transport.h"
#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
diff --git a/src/transports/http.c b/src/transports/http.c
index a7eff7365..7ef0b519c 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -11,6 +11,17 @@
#include "buffer.h"
#include "netops.h"
#include "smart.h"
+#include "auth.h"
+#include "auth_negotiate.h"
+
+git_http_auth_scheme auth_schemes[] = {
+ { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
+ { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
+};
+
+#ifdef GIT_SSL
+# include <openssl/x509v3.h>
+#endif
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
@@ -20,7 +31,6 @@ static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive
static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
-static const char *basic_authtype = "Basic";
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
@@ -35,10 +45,6 @@ enum last_cb {
VALUE
};
-typedef enum {
- GIT_HTTP_AUTH_BASIC = 1,
-} http_authmechanism_t;
-
typedef struct {
git_smart_subtransport_stream parent;
const char *service;
@@ -58,9 +64,6 @@ typedef struct {
transport_smart *owner;
gitno_socket socket;
gitno_connection_data connection_data;
- git_cred *cred;
- git_cred *url_cred;
- http_authmechanism_t auth_mechanism;
bool connected;
/* Parser structures */
@@ -76,6 +79,11 @@ typedef struct {
enum last_cb last_cb;
int parse_error;
unsigned parse_finished : 1;
+
+ /* Authentication */
+ git_cred *cred;
+ git_cred *url_cred;
+ git_vector auth_contexts;
} http_subtransport;
typedef struct {
@@ -88,28 +96,91 @@ typedef struct {
size_t *bytes_read;
} parser_context;
-static int apply_basic_credential(git_buf *buf, git_cred *cred)
+static bool credtype_match(git_http_auth_scheme *scheme, void *data)
{
- git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
- git_buf raw = GIT_BUF_INIT;
- int error = -1;
+ unsigned int credtype = *(unsigned int *)data;
- git_buf_printf(&raw, "%s:%s", c->username, c->password);
+ return !!(scheme->credtypes & credtype);
+}
- if (git_buf_oom(&raw) ||
- git_buf_puts(buf, "Authorization: Basic ") < 0 ||
- git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||
- git_buf_puts(buf, "\r\n") < 0)
- goto on_error;
+static bool challenge_match(git_http_auth_scheme *scheme, void *data)
+{
+ const char *scheme_name = scheme->name;
+ const char *challenge = (const char *)data;
+ size_t scheme_len;
- error = 0;
+ scheme_len = strlen(scheme_name);
+ return (strncmp(challenge, scheme_name, scheme_len) == 0 &&
+ (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
+}
-on_error:
- if (raw.size)
- memset(raw.ptr, 0x0, raw.size);
+static int auth_context_match(
+ git_http_auth_context **out,
+ http_subtransport *t,
+ bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
+ void *data)
+{
+ git_http_auth_scheme *scheme = NULL;
+ git_http_auth_context *context = NULL, *c;
+ size_t i;
+
+ *out = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
+ if (scheme_match(&auth_schemes[i], data)) {
+ scheme = &auth_schemes[i];
+ break;
+ }
+ }
+
+ if (!scheme)
+ return 0;
- git_buf_free(&raw);
- return error;
+ /* See if authentication has already started for this scheme */
+ git_vector_foreach(&t->auth_contexts, i, c) {
+ if (c->type == scheme->type) {
+ context = c;
+ break;
+ }
+ }
+
+ if (!context) {
+ if (scheme->init_context(&context, &t->connection_data) < 0)
+ return -1;
+ else if (!context)
+ return 0;
+ else if (git_vector_insert(&t->auth_contexts, context) < 0)
+ return -1;
+ }
+
+ *out = context;
+
+ return 0;
+}
+
+static int apply_credentials(git_buf *buf, http_subtransport *t)
+{
+ git_cred *cred = t->cred;
+ git_http_auth_context *context;
+
+ /* Apply the credentials given to us in the URL */
+ if (!cred && t->connection_data.user && t->connection_data.pass) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred,
+ t->connection_data.user, t->connection_data.pass) < 0)
+ return -1;
+
+ cred = t->url_cred;
+ }
+
+ if (!cred)
+ return 0;
+
+ /* Get or create a context for the best scheme for this cred type */
+ if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
+ return -1;
+
+ return context->next_token(buf, context, cred);
}
static int gen_request(
@@ -137,19 +208,9 @@ static int gen_request(
git_buf_puts(buf, "Accept: */*\r\n");
/* Apply credentials to the request */
- if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
- apply_basic_credential(buf, t->cred) < 0)
+ if (apply_credentials(buf, t) < 0)
return -1;
- /* Use url-parsed basic auth if username and password are both provided */
- if (!t->cred && t->connection_data.user && t->connection_data.pass) {
- if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred,
- t->connection_data.user, t->connection_data.pass) < 0)
- return -1;
- if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
- }
-
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
@@ -158,20 +219,26 @@ static int gen_request(
return 0;
}
-static int parse_unauthorized_response(
+static int parse_authenticate_response(
git_vector *www_authenticate,
- int *allowed_types,
- http_authmechanism_t *auth_mechanism)
+ http_subtransport *t,
+ int *allowed_types)
{
- unsigned i;
- char *entry;
-
- git_vector_foreach(www_authenticate, i, entry) {
- if (!strncmp(entry, basic_authtype, 5) &&
- (entry[5] == '\0' || entry[5] == ' ')) {
- *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
- *auth_mechanism = GIT_HTTP_AUTH_BASIC;
- }
+ git_http_auth_context *context;
+ char *challenge;
+ size_t i;
+
+ git_vector_foreach(www_authenticate, i, challenge) {
+ if (auth_context_match(&context, t, challenge_match, challenge) < 0)
+ return -1;
+ else if (!context)
+ continue;
+
+ if (context->set_challenge &&
+ context->set_challenge(context, challenge) < 0)
+ return -1;
+
+ *allowed_types |= context->credtypes;
}
return 0;
@@ -248,7 +315,7 @@ static int on_headers_complete(http_parser *parser)
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- int error = 0, no_callback = 0;
+ int error = 0, no_callback = 0, allowed_auth_types = 0;
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
@@ -256,26 +323,26 @@ static int on_headers_complete(http_parser *parser)
if (on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
- /* Check for an authentication failure. */
+ /* Capture authentication headers which may be a 401 (authentication
+ * is not complete) or a 200 (simply informing us that auth *is*
+ * complete.)
+ */
+ if (parse_authenticate_response(&t->www_authenticate, t,
+ &allowed_auth_types) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- if (parser->status_code == 401 &&
- get_verb == s->verb) {
- if (!t->owner->cred_acquire_payload) {
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 && get_verb == s->verb) {
+ if (!t->owner->cred_acquire_cb) {
no_callback = 1;
} else {
- int allowed_types = 0;
-
- if (parse_unauthorized_response(&t->www_authenticate,
- &allowed_types, &t->auth_mechanism) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- if (allowed_types &&
- (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+ if (allowed_auth_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_auth_types))) {
error = t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
t->connection_data.user,
- allowed_types,
+ allowed_auth_types,
t->owner->cred_acquire_payload);
if (error == GIT_PASSTHROUGH) {
@@ -286,7 +353,8 @@ static int on_headers_complete(http_parser *parser)
assert(t->cred);
/* Successfully acquired a credential. */
- return t->parse_error = PARSE_ERROR_REPLAY;
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
}
}
}
@@ -324,7 +392,8 @@ static int on_headers_complete(http_parser *parser)
t->connected = 0;
s->redirect_count++;
- return t->parse_error = PARSE_ERROR_REPLAY;
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
}
/* Check for a 200 HTTP status code. */
@@ -382,6 +451,13 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
+ /* If our goal is to replay the request (either an auth failure or
+ * a redirect) then don't bother buffering since we're ignoring the
+ * content anyway.
+ */
+ if (t->parse_error == PARSE_ERROR_REPLAY)
+ return 0;
+
if (ctx->buf_size < len) {
giterr_set(GITERR_NET, "Can't fit data in the buffer");
return t->parse_error = PARSE_ERROR_GENERIC;
@@ -452,11 +528,11 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
static int http_connect(http_subtransport *t)
{
- int flags = 0;
+ int flags = 0, error;
if (t->connected &&
http_should_keep_alive(&t->parser) &&
- http_body_is_final(&t->parser))
+ t->parse_finished)
return 0;
if (t->socket.socket)
@@ -469,13 +545,55 @@ static int http_connect(http_subtransport *t)
return -1;
flags |= GITNO_CONNECT_SSL;
-
- if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)
- flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
}
- if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0)
- return -1;
+ error = gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags);
+
+#ifdef GIT_SSL
+ if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL) {
+ X509 *cert = SSL_get_peer_certificate(t->socket.ssl.ssl);
+ git_cert_x509 cert_info;
+ int len, is_valid;
+ unsigned char *guard, *encoded_cert;
+
+ /* Retrieve the length of the certificate first */
+ len = i2d_X509(cert, NULL);
+ if (len < 0) {
+ giterr_set(GITERR_NET, "failed to retrieve certificate information");
+ return -1;
+ }
+
+
+ encoded_cert = git__malloc(len);
+ GITERR_CHECK_ALLOC(encoded_cert);
+ /* i2d_X509 makes 'copy' point to just after the data */
+ guard = encoded_cert;
+
+ len = i2d_X509(cert, &guard);
+ if (len < 0) {
+ git__free(encoded_cert);
+ giterr_set(GITERR_NET, "failed to retrieve certificate information");
+ return -1;
+ }
+
+ giterr_clear();
+ is_valid = error != GIT_ECERTIFICATE;
+ cert_info.cert_type = GIT_CERT_X509;
+ cert_info.data = encoded_cert;
+ cert_info.len = len;
+ error = t->owner->certificate_check_cb((git_cert *) &cert_info, is_valid, t->owner->message_cb_payload);
+ git__free(encoded_cert);
+
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "user cancelled certificate check");
+
+ return error;
+ }
+ }
+#endif
+ if (error < 0)
+ return error;
t->connected = 1;
return 0;
@@ -502,10 +620,8 @@ replay:
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -537,7 +653,24 @@ replay:
}
while (!*bytes_read && !t->parse_finished) {
- t->parse_buffer.offset = 0;
+ size_t data_offset;
+ int error;
+
+ /*
+ * Make the parse_buffer think it's as full of data as
+ * the buffer, so it won't try to recv more data than
+ * we can put into it.
+ *
+ * data_offset is the actual data offset from which we
+ * should tell the parser to start reading.
+ */
+ if (buf_size >= t->parse_buffer.len) {
+ t->parse_buffer.offset = 0;
+ } else {
+ t->parse_buffer.offset = t->parse_buffer.len - buf_size;
+ }
+
+ data_offset = t->parse_buffer.offset;
if (gitno_recv(&t->parse_buffer) < 0)
return -1;
@@ -558,8 +691,8 @@ replay:
bytes_parsed = http_parser_execute(&t->parser,
&t->settings,
- t->parse_buffer.data,
- t->parse_buffer.offset);
+ t->parse_buffer.data + data_offset,
+ t->parse_buffer.offset - data_offset);
t->parser.data = NULL;
@@ -568,8 +701,8 @@ replay:
if (PARSE_ERROR_REPLAY == t->parse_error) {
s->sent_request = 0;
- if (http_connect(t) < 0)
- return -1;
+ if ((error = http_connect(t)) < 0)
+ return error;
goto replay;
}
@@ -577,7 +710,7 @@ replay:
if (t->parse_error < 0)
return -1;
- if (bytes_parsed != t->parse_buffer.offset) {
+ if (bytes_parsed != t->parse_buffer.offset - data_offset) {
giterr_set(GITERR_NET,
"HTTP parser error: %s",
http_errno_description((enum http_errno)t->parser.http_errno));
@@ -604,10 +737,8 @@ static int http_stream_write_chunked(
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -679,10 +810,8 @@ static int http_stream_write_single(
clear_parser_state(t);
- if (gen_request(&request, s, len) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, len) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
goto on_error;
@@ -825,8 +954,8 @@ static int http_action(
(ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
return ret;
- if (http_connect(t) < 0)
- return -1;
+ if ((ret = http_connect(t)) < 0)
+ return ret;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
@@ -849,6 +978,8 @@ static int http_action(
static int http_close(git_smart_subtransport *subtransport)
{
http_subtransport *t = (http_subtransport *) subtransport;
+ git_http_auth_context *context;
+ size_t i;
clear_parser_state(t);
@@ -867,6 +998,13 @@ static int http_close(git_smart_subtransport *subtransport)
t->url_cred = NULL;
}
+ git_vector_foreach(&t->auth_contexts, i, context) {
+ if (context->free)
+ context->free(context);
+ }
+
+ git_vector_clear(&t->auth_contexts);
+
gitno_connection_data_free_ptrs(&t->connection_data);
return 0;
@@ -878,6 +1016,7 @@ static void http_free(git_smart_subtransport *subtransport)
http_close(subtransport);
+ git_vector_free(&t->auth_contexts);
git__free(t);
}
diff --git a/src/transports/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..d0f9c90e8 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)
{
@@ -52,18 +53,20 @@ static int git_smart__set_callbacks(
git_transport *transport,
git_transport_message_cb progress_cb,
git_transport_message_cb error_cb,
+ git_transport_certificate_check_cb certificate_check_cb,
void *message_cb_payload)
{
transport_smart *t = (transport_smart *)transport;
t->progress_cb = progress_cb;
t->error_cb = error_cb;
+ t->certificate_check_cb = certificate_check_cb;
t->message_cb_payload = message_cb_payload;
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 +77,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 +103,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 +129,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 +183,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 +198,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 +313,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..44e241adc 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -9,6 +9,7 @@
#include "netops.h"
#include "buffer.h"
#include "push.h"
+#include "git2/sys/transport.h"
#define GIT_SIDE_BAND_DATA 1
#define GIT_SIDE_BAND_PROGRESS 2
@@ -23,6 +24,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,
@@ -135,6 +137,7 @@ typedef struct {
int flags;
git_transport_message_cb progress_cb;
git_transport_message_cb error_cb;
+ git_transport_certificate_check_cb certificate_check_cb;
void *message_cb_payload;
git_smart_subtransport *wrapped;
git_smart_subtransport_stream *current_stream;
@@ -154,7 +157,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 +177,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..7c20382dc 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, ' ');
}
@@ -261,7 +314,7 @@ static int wait_while_ack(gitno_buffer *buf)
break;
if (pkt->type == GIT_PKT_ACK &&
- (pkt->status != GIT_ACK_CONTINUE ||
+ (pkt->status != GIT_ACK_CONTINUE &&
pkt->status != GIT_ACK_COMMON)) {
git__free(pkt);
return 0;
@@ -539,7 +592,9 @@ int git_smart__download_pack(
}
} else if (pkt->type == GIT_PKT_DATA) {
git_pkt_data *p = (git_pkt_data *) pkt;
- error = writepack->append(writepack, p->data, p->len, stats);
+
+ if (p->len)
+ error = writepack->append(writepack, p->data, p->len, stats);
} else if (pkt->type == GIT_PKT_FLUSH) {
/* A flush indicates the end of the packfile */
git__free(pkt);
@@ -585,9 +640,9 @@ static int gen_pktline(git_buf *buf, git_push *push)
{
push_spec *spec;
size_t i, len;
- char old_id[41], new_id[41];
+ char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1];
- old_id[40] = '\0'; new_id[40] = '\0';
+ old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0';
git_vector_foreach(&push->specs, i, spec) {
len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->rref);
@@ -908,7 +963,7 @@ int git_smart__push(git_transport *transport, git_push *push)
#ifdef PUSH_DEBUG
{
git_remote_head *head;
- char hex[41]; hex[40] = '\0';
+ char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0';
git_vector_foreach(&push->remote->refs, i, head) {
git_oid_fmt(hex, &head->oid);
@@ -969,7 +1024,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/transports/ssh.c b/src/transports/ssh.c
index b403727c9..15a45ca86 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -5,15 +5,18 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
+#ifdef GIT_SSH
+#include <libssh2.h>
+#endif
+
#include "git2.h"
#include "buffer.h"
#include "netops.h"
#include "smart.h"
+#include "cred.h"
#ifdef GIT_SSH
-#include <libssh2.h>
-
#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
static const char prefix_ssh[] = "ssh://";
@@ -35,8 +38,12 @@ typedef struct {
transport_smart *owner;
ssh_stream *current_stream;
git_cred *cred;
+ char *cmd_uploadpack;
+ char *cmd_receivepack;
} ssh_subtransport;
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
+
static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
{
char *ssherr;
@@ -132,11 +139,22 @@ static int ssh_stream_write(
size_t len)
{
ssh_stream *s = (ssh_stream *)stream;
+ size_t off = 0;
+ ssize_t ret = 0;
if (!s->sent_command && send_command(s) < 0)
return -1;
- if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) {
+ do {
+ ret = libssh2_channel_write(s->channel, buffer + off, len - off);
+ if (ret < 0)
+ break;
+
+ off += ret;
+
+ } while (off < len);
+
+ if (ret < 0) {
ssh_error(s->session, "SSH could not write data");
return -1;
}
@@ -274,6 +292,10 @@ static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) {
}
shutdown:
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ ssh_error(session, "error authenticating");
+
libssh2_agent_disconnect(agent);
libssh2_agent_free(agent);
@@ -287,6 +309,7 @@ static int _git_ssh_authenticate_session(
int rc;
do {
+ giterr_clear();
switch (cred->credtype) {
case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
@@ -339,11 +362,52 @@ static int _git_ssh_authenticate_session(
}
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+ if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
+ return GIT_EAUTH;
+
if (rc != LIBSSH2_ERROR_NONE) {
- ssh_error(session, "Failed to authenticate SSH session");
+ if (!giterr_last())
+ ssh_error(session, "Failed to authenticate SSH session");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods)
+{
+ int error, no_callback = 0;
+ git_cred *cred = NULL;
+
+ if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH)
+ no_callback = 1;
+ else if (error < 0)
+ return error;
+ else if (!cred) {
+ giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
+ return -1;
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_SSH, "authentication required but no callback set");
+ return -1;
+ }
+
+ if (!(cred->credtype & auth_methods)) {
+ cred->free(cred);
+ giterr_set(GITERR_SSH, "callback returned unsupported credentials type");
return -1;
}
+ *out = cred;
+
return 0;
}
@@ -387,8 +451,9 @@ static int _git_ssh_setup_conn(
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
const char *default_port="22";
- int no_callback = 0;
+ int auth_methods, error = 0;
ssh_stream *s;
+ git_cred *cred = NULL;
LIBSSH2_SESSION* session=NULL;
LIBSSH2_CHANNEL* channel=NULL;
@@ -399,56 +464,102 @@ static int _git_ssh_setup_conn(
s = (ssh_stream *)*stream;
if (!git__prefixcmp(url, prefix_ssh)) {
- if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0)
+ if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
goto on_error;
} else {
- if (git_ssh_extract_url_parts(&host, &user, url) < 0)
+ if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
goto on_error;
port = git__strdup(default_port);
GITERR_CHECK_ALLOC(port);
}
- if (gitno_connect(&s->socket, host, port, 0) < 0)
+ if ((error = gitno_connect(&s->socket, host, port, 0)) < 0)
+ goto on_error;
+
+ if ((error = _git_ssh_session_create(&session, s->socket)) < 0)
goto on_error;
- if (user && pass) {
- if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
+ if (t->owner->certificate_check_cb != NULL) {
+ git_cert_hostkey cert = { 0 };
+ const char *key;
+
+ cert.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_SHA1;
+ memcpy(&cert.hash_sha1, key, 20);
+ }
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_MD5;
+ memcpy(&cert.hash_md5, key, 16);
+ }
+
+ if (cert.type == 0) {
+ giterr_set(GITERR_SSH, "unable to get the host key");
+ return -1;
+ }
+
+ /* We don't currently trust any hostkeys */
+ giterr_clear();
+ error = t->owner->certificate_check_cb((git_cert *) &cert, 0, t->owner->message_cb_payload);
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "user cancelled hostkey check");
+
goto on_error;
- } else if (!t->owner->cred_acquire_cb) {
- no_callback = 1;
- } else {
- int error;
- error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
- GIT_CREDTYPE_USERPASS_PLAINTEXT |
- GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
- GIT_CREDTYPE_SSH_INTERACTIVE,
- t->owner->cred_acquire_payload);
+ }
+ }
- if (error == GIT_PASSTHROUGH)
- no_callback = 1;
- else if (error < 0)
+ /* we need the username to ask for auth methods */
+ if (!user) {
+ if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
goto on_error;
- else if (!t->cred) {
- giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
+
+ user = git__strdup(((git_cred_username *) cred)->username);
+ cred->free(cred);
+ cred = NULL;
+ if (!user)
+ goto on_error;
+ } else if (user && pass) {
+ if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0)
goto on_error;
- }
}
- if (no_callback) {
- giterr_set(GITERR_SSH, "authentication required but no callback set");
+ if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
goto on_error;
- }
- assert(t->cred);
+ error = GIT_EAUTH;
+ /* if we already have something to try */
+ if (cred && auth_methods & cred->credtype)
+ error = _git_ssh_authenticate_session(session, cred);
- if (_git_ssh_session_create(&session, s->socket) < 0)
- goto on_error;
+ while (error == GIT_EAUTH) {
+ if (cred) {
+ cred->free(cred);
+ cred = NULL;
+ }
+
+ if ((error = request_creds(&cred, t, user, auth_methods)) < 0)
+ goto on_error;
+
+ if (strcmp(user, git_cred__username(cred))) {
+ giterr_set(GITERR_SSH, "username does not match previous request");
+ error = -1;
+ goto on_error;
+ }
+
+ error = _git_ssh_authenticate_session(session, cred);
+ }
- if (_git_ssh_authenticate_session(session, t->cred) < 0)
+ if (error < 0)
goto on_error;
channel = libssh2_channel_open_session(session);
if (!channel) {
+ error = -1;
ssh_error(session, "Failed to open SSH channel");
goto on_error;
}
@@ -459,6 +570,9 @@ static int _git_ssh_setup_conn(
s->channel = channel;
t->current_stream = s;
+ if (cred)
+ cred->free(cred);
+
git__free(host);
git__free(port);
git__free(path);
@@ -475,6 +589,9 @@ on_error:
if (*stream)
ssh_stream_free(*stream);
+ if (cred)
+ cred->free(cred);
+
git__free(host);
git__free(port);
git__free(user);
@@ -483,7 +600,7 @@ on_error:
if (session)
libssh2_session_free(session);
- return -1;
+ return error;
}
static int ssh_uploadpack_ls(
@@ -491,10 +608,9 @@ static int ssh_uploadpack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
- return -1;
+ const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
- return 0;
+ return _git_ssh_setup_conn(t, url, cmd, stream);
}
static int ssh_uploadpack(
@@ -518,10 +634,10 @@ static int ssh_receivepack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
- return -1;
+ const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
- return 0;
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
}
static int ssh_receivepack(
@@ -583,8 +699,57 @@ static void _ssh_free(git_smart_subtransport *subtransport)
assert(!t->current_stream);
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
git__free(t);
}
+
+#define SSH_AUTH_PUBLICKEY "publickey"
+#define SSH_AUTH_PASSWORD "password"
+#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
+{
+ const char *list, *ptr;
+
+ *out = 0;
+
+ list = libssh2_userauth_list(session, username, strlen(username));
+
+ /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
+ if (list == NULL && !libssh2_userauth_authenticated(session))
+ return -1;
+
+ ptr = list;
+ while (ptr) {
+ if (*ptr == ',')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
+ *out |= GIT_CREDTYPE_SSH_KEY;
+ *out |= GIT_CREDTYPE_SSH_CUSTOM;
+ ptr += strlen(SSH_AUTH_PUBLICKEY);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
+ *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ ptr += strlen(SSH_AUTH_PASSWORD);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
+ *out |= GIT_CREDTYPE_SSH_INTERACTIVE;
+ ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
+ continue;
+ }
+
+ /* Skipt it if we don't know it */
+ ptr = strchr(ptr, ',');
+ }
+
+ return 0;
+}
#endif
int git_smart_subtransport_ssh(
@@ -615,3 +780,46 @@ int git_smart_subtransport_ssh(
return -1;
#endif
}
+
+int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
+{
+#ifdef GIT_SSH
+ git_strarray *paths = (git_strarray *) payload;
+ git_transport *transport;
+ transport_smart *smart;
+ ssh_subtransport *t;
+ int error;
+ git_smart_subtransport_definition ssh_definition = {
+ git_smart_subtransport_ssh,
+ 0, /* no RPC */
+ };
+
+ if (paths->count != 2) {
+ giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
+ return error;
+
+ smart = (transport_smart *) transport;
+ t = (ssh_subtransport *) smart->wrapped;
+
+ t->cmd_uploadpack = git__strdup(paths->strings[0]);
+ GITERR_CHECK_ALLOC(t->cmd_uploadpack);
+ t->cmd_receivepack = git__strdup(paths->strings[1]);
+ GITERR_CHECK_ALLOC(t->cmd_receivepack);
+
+ *out = transport;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+ GIT_UNUSED(payload);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index bd9509cd4..8aef63193 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -16,6 +16,8 @@
#include "remote.h"
#include "repository.h"
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32")
#include <winhttp.h>
#pragma comment(lib, "winhttp")
@@ -35,6 +37,11 @@
#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
#define CACHED_POST_BODY_BUF_SIZE 4096
#define UUID_LENGTH_CCH 32
+#define TIMEOUT_INFINITE -1
+#define DEFAULT_CONNECT_TIMEOUT 60000
+#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH
+#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0
+#endif
static const char *prefix_http = "http://";
static const char *prefix_https = "https://";
@@ -97,7 +104,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred)
if (git_buf_oom(&raw) ||
git_buf_puts(&buf, "Authorization: Basic ") < 0 ||
- git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
+ git_buf_encode_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
goto on_error;
if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) {
@@ -198,6 +205,39 @@ static int fallback_cred_acquire_cb(
return error;
}
+static int certificate_check(winhttp_stream *s, int valid)
+{
+ int error;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ PCERT_CONTEXT cert_ctx;
+ DWORD cert_ctx_size = sizeof(cert_ctx);
+ git_cert_x509 cert;
+
+ /* If there is no override, we should fail if WinHTTP doesn't think it's fine */
+ if (t->owner->certificate_check_cb == NULL && !valid)
+ return GIT_ECERTIFICATE;
+
+ if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
+ return 0;
+
+ if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
+ giterr_set(GITERR_OS, "failed to get server certificate");
+ return -1;
+ }
+
+ giterr_clear();
+ cert.cert_type = GIT_CERT_X509;
+ cert.data = cert_ctx->pbCertEncoded;
+ cert.len = cert_ctx->cbCertEncoded;
+ error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->owner->cred_acquire_payload);
+ CertFreeCertificateContext(cert_ctx);
+
+ if (error < 0 && !giterr_last())
+ giterr_set(GITERR_NET, "user cancelled certificate check");
+
+ return error;
+}
+
static int winhttp_stream_connect(winhttp_stream *s)
{
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
@@ -208,6 +248,8 @@ static int winhttp_stream_connect(winhttp_stream *s)
BOOL peerdist = FALSE;
int error = -1;
unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
+ int default_timeout = TIMEOUT_INFINITE;
+ int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
/* Prepare URL */
git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
@@ -236,6 +278,11 @@ static int winhttp_stream_connect(winhttp_stream *s)
goto on_error;
}
+ if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
+ giterr_set(GITERR_OS, "Failed to set timeouts for WinHTTP");
+ goto on_error;
+ }
+
/* Set proxy if necessary */
if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
goto on_error;
@@ -341,13 +388,6 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
goto on_error;
-
- if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&
- !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
- (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
- giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
- goto on_error;
- }
}
/* If we have a credential on the subtransport, apply it to the request */
@@ -463,6 +503,8 @@ static int winhttp_connect(
int32_t port;
const char *default_port = "80";
int error = -1;
+ int default_timeout = TIMEOUT_INFINITE;
+ int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
/* Prepare port */
if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0)
@@ -487,6 +529,12 @@ static int winhttp_connect(
goto on_error;
}
+ if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
+ giterr_set(GITERR_OS, "Failed to set timeouts for WinHTTP");
+ goto on_error;
+ }
+
+
/* Establish connection */
t->connection = WinHttpConnect(
t->session,
@@ -507,6 +555,74 @@ on_error:
return error;
}
+static int do_send_request(winhttp_stream *s, size_t len, int ignore_length)
+{
+ int request_failed = 0, cert_valid = 1, error = 0;
+
+ if (ignore_length) {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
+ return -1;
+ }
+ } else {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ len, 0)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int send_request(winhttp_stream *s, size_t len, int ignore_length)
+{
+ int request_failed = 0, cert_valid = 1, error = 0;
+ DWORD ignore_flags;
+
+ if ((error = do_send_request(s, len, ignore_length)) < 0)
+ request_failed = 1;
+
+ if (request_failed) {
+ if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) {
+ giterr_set(GITERR_OS, "failed to send request");
+ return -1;
+ } else {
+ cert_valid = 0;
+ }
+ }
+
+ giterr_clear();
+ if ((error = certificate_check(s, cert_valid)) < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_OS, "user cancelled certificate check");
+
+ return error;
+ }
+
+ /* if neither the request nor the certificate check returned errors, we're done */
+ if (!request_failed)
+ return 0;
+
+ ignore_flags =
+ SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+ if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
+ giterr_set(GITERR_OS, "failed to set security options");
+ return -1;
+ }
+
+ if ((error = do_send_request(s, len, ignore_length)) < 0)
+ giterr_set(GITERR_OS, "failed to send request");
+
+ return error;
+}
+
static int winhttp_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
@@ -517,6 +633,7 @@ static int winhttp_stream_read(
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD dw_bytes_read;
char replay_count = 0;
+ int error;
replay:
/* Enforce a reasonable cap on the number of replays */
@@ -533,15 +650,12 @@ replay:
DWORD status_code, status_code_length, content_type_length, bytes_written;
char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
+ int request_failed = 0, cert_valid = 1;
if (!s->sent_request) {
- if (!WinHttpSendRequest(s->request,
- WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0,
- s->post_body_len, 0)) {
- giterr_set(GITERR_OS, "Failed to send request");
- return -1;
- }
+
+ if ((error = send_request(s, s->post_body_len, 0)) < 0)
+ return error;
s->sent_request = 1;
}
@@ -745,9 +859,9 @@ replay:
/* Verify that we got the correct content-type back */
if (post_verb == s->verb)
- snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
+ p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
else
- snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
+ p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters");
@@ -795,6 +909,7 @@ static int winhttp_stream_write_single(
winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD bytes_written;
+ int error;
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
@@ -805,13 +920,8 @@ static int winhttp_stream_write_single(
return -1;
}
- if (!WinHttpSendRequest(s->request,
- WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0,
- (DWORD)len, 0)) {
- giterr_set(GITERR_OS, "Failed to send request");
- return -1;
- }
+ if ((error = send_request(s, len, 0)) < 0)
+ return error;
s->sent_request = 1;
@@ -934,6 +1044,7 @@ static int winhttp_stream_write_chunked(
{
winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int error;
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
@@ -947,13 +1058,8 @@ static int winhttp_stream_write_chunked(
return -1;
}
- if (!WinHttpSendRequest(s->request,
- WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0,
- WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
- giterr_set(GITERR_OS, "Failed to send request");
- return -1;
- }
+ if ((error = send_request(s, 0, 1)) < 0)
+ return error;
s->sent_request = 1;
}
@@ -1112,9 +1218,9 @@ static int winhttp_action(
int ret = -1;
if (!t->connection)
- if (gitno_connection_data_from_url(&t->connection_data, url, NULL) < 0 ||
- winhttp_connect(t, url) < 0)
- return -1;
+ if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 ||
+ (ret = winhttp_connect(t, url)) < 0)
+ return ret;
if (winhttp_stream_alloc(t, &s) < 0)
return -1;
diff --git a/src/tree.c b/src/tree.c
index 94f779eca..28190d6da 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -17,6 +17,8 @@
#define DEFAULT_TREE_SIZE 16
#define MAX_FILEMODE_BYTES 6
+GIT__USE_STRMAP;
+
static bool valid_filemode(const int filemode)
{
return (filemode == GIT_FILEMODE_TREE
@@ -365,7 +367,8 @@ size_t git_tree_entrycount(const git_tree *tree)
unsigned int git_treebuilder_entrycount(git_treebuilder *bld)
{
assert(bld);
- return (unsigned int)bld->entrycount;
+
+ return git_strmap_num_entries(bld->map);
}
static int tree_error(const char *str, const char *path)
@@ -450,6 +453,7 @@ static int append_entry(
git_filemode_t filemode)
{
git_tree_entry *entry;
+ int error = 0;
if (!valid_entry_name(filename))
return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
@@ -460,12 +464,13 @@ static int append_entry(
git_oid_cpy(&entry->oid, id);
entry->attr = (uint16_t)filemode;
- if (git_vector_insert(&bld->entries, entry) < 0) {
- git__free(entry);
+ git_strmap_insert(bld->map, entry->filename, entry, error);
+ if (error < 0) {
+ git_tree_entry_free(entry);
+ giterr_set(GITERR_TREE, "failed to append entry %s to the tree builder", filename);
return -1;
}
- bld->entrycount++;
return 0;
}
@@ -610,18 +615,17 @@ int git_tree__write_index(
int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
{
git_treebuilder *bld;
- size_t i, source_entries = DEFAULT_TREE_SIZE;
+ size_t i;
assert(builder_p);
bld = git__calloc(1, sizeof(git_treebuilder));
GITERR_CHECK_ALLOC(bld);
- if (source != NULL)
- source_entries = source->entries.length;
-
- if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0)
- goto on_error;
+ if (git_strmap_alloc(&bld->map) < 0) {
+ git__free(bld);
+ return -1;
+ }
if (source != NULL) {
git_tree_entry *entry_src;
@@ -651,7 +655,8 @@ int git_treebuilder_insert(
git_filemode_t filemode)
{
git_tree_entry *entry;
- size_t pos;
+ int error;
+ git_strmap_iter pos;
assert(bld && id && filename);
@@ -661,22 +666,20 @@ int git_treebuilder_insert(
if (!valid_entry_name(filename))
return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
- if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) {
- entry = git_vector_get(&bld->entries, pos);
- if (entry->removed) {
- entry->removed = 0;
- bld->entrycount++;
- }
+ pos = git_strmap_lookup_index(bld->map, filename);
+ if (git_strmap_valid_index(bld->map, pos)) {
+ entry = git_strmap_value_at(bld->map, pos);
} else {
entry = alloc_entry(filename);
GITERR_CHECK_ALLOC(entry);
- if (git_vector_insert(&bld->entries, entry) < 0) {
- git__free(entry);
+ git_strmap_insert(bld->map, entry->filename, entry, error);
+
+ if (error < 0) {
+ git_tree_entry_free(entry);
+ giterr_set(GITERR_TREE, "failed to insert %s", filename);
return -1;
}
-
- bld->entrycount++;
}
git_oid_cpy(&entry->oid, id);
@@ -690,17 +693,14 @@ int git_treebuilder_insert(
static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
{
- size_t idx;
- git_tree_entry *entry;
+ git_tree_entry *entry = NULL;
+ git_strmap_iter pos;
assert(bld && filename);
- if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0)
- return NULL;
-
- entry = git_vector_get(&bld->entries, idx);
- if (entry->removed)
- return NULL;
+ pos = git_strmap_lookup_index(bld->map, filename);
+ if (git_strmap_valid_index(bld->map, pos))
+ entry = git_strmap_value_at(bld->map, pos);
return entry;
}
@@ -712,35 +712,44 @@ const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *file
int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
{
- git_tree_entry *remove_ptr = treebuilder_get(bld, filename);
+ git_tree_entry *entry = treebuilder_get(bld, filename);
- if (remove_ptr == NULL || remove_ptr->removed)
+ if (entry == NULL)
return tree_error("Failed to remove entry. File isn't in the tree", filename);
- remove_ptr->removed = 1;
- bld->entrycount--;
+ git_strmap_delete(bld->map, filename);
+ git_tree_entry_free(entry);
+
return 0;
}
int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
{
int error = 0;
- size_t i;
+ size_t i, entrycount;
git_buf tree = GIT_BUF_INIT;
git_odb *odb;
+ git_tree_entry *entry;
+ git_vector entries;
assert(bld);
- git_vector_sort(&bld->entries);
+ entrycount = git_strmap_num_entries(bld->map);
+ if (git_vector_init(&entries, entrycount, entry_sort_cmp) < 0)
+ return -1;
- /* Grow the buffer beforehand to an estimated size */
- error = git_buf_grow(&tree, bld->entries.length * 72);
+ git_strmap_foreach_value(bld->map, entry, {
+ if (git_vector_insert(&entries, entry) < 0)
+ return -1;
+ });
- for (i = 0; i < bld->entries.length && !error; ++i) {
- git_tree_entry *entry = git_vector_get(&bld->entries, i);
+ git_vector_sort(&entries);
+
+ /* Grow the buffer beforehand to an estimated size */
+ error = git_buf_grow(&tree, entrycount * 72);
- if (entry->removed)
- continue;
+ for (i = 0; i < entries.length && !error; ++i) {
+ git_tree_entry *entry = git_vector_get(&entries, i);
git_buf_printf(&tree, "%o ", entry->attr);
git_buf_put(&tree, entry->filename, entry->filename_len + 1);
@@ -750,6 +759,8 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b
error = -1;
}
+ git_vector_free(&entries);
+
if (!error &&
!(error = git_repository_odb__weakptr(&odb, repo)))
error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
@@ -763,31 +774,27 @@ void git_treebuilder_filter(
git_treebuilder_filter_cb filter,
void *payload)
{
- size_t i;
+ const char *filename;
git_tree_entry *entry;
assert(bld && filter);
- git_vector_foreach(&bld->entries, i, entry) {
- if (!entry->removed && filter(entry, payload)) {
- entry->removed = 1;
- bld->entrycount--;
- }
- }
+ git_strmap_foreach(bld->map, filename, entry, {
+ if (filter(entry, payload)) {
+ git_strmap_delete(bld->map, filename);
+ git_tree_entry_free(entry);
+ }
+ });
}
void git_treebuilder_clear(git_treebuilder *bld)
{
- size_t i;
git_tree_entry *e;
assert(bld);
- git_vector_foreach(&bld->entries, i, e)
- git_tree_entry_free(e);
-
- git_vector_clear(&bld->entries);
- bld->entrycount = 0;
+ git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e));
+ git_strmap_clear(bld->map);
}
void git_treebuilder_free(git_treebuilder *bld)
@@ -796,7 +803,7 @@ void git_treebuilder_free(git_treebuilder *bld)
return;
git_treebuilder_clear(bld);
- git_vector_free(&bld->entries);
+ git_strmap_free(bld->map);
git__free(bld);
}
diff --git a/src/tree.h b/src/tree.h
index f07039a07..5d27eb7c9 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -11,9 +11,9 @@
#include "repository.h"
#include "odb.h"
#include "vector.h"
+#include "strmap.h"
struct git_tree_entry {
- uint16_t removed;
uint16_t attr;
git_oid oid;
size_t filename_len;
@@ -26,8 +26,7 @@ struct git_tree {
};
struct git_treebuilder {
- git_vector entries;
- size_t entrycount; /* vector may contain "removed" entries */
+ git_strmap *map;
};
GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e)
diff --git a/src/unix/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/unix/posix.h b/src/unix/posix.h
index 1e41bcf18..e4f3ac67a 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -4,33 +4,47 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
-#ifndef INCLUDE_posix__w32_h__
-#define INCLUDE_posix__w32_h__
+#ifndef INCLUDE_posix__unix_h__
+#define INCLUDE_posix__unix_h__
#include <stdio.h>
#include <sys/param.h>
+typedef int GIT_SOCKET;
+#define INVALID_SOCKET -1
+
+#define p_lseek(f,n,w) lseek(f, n, w)
+#define p_fstat(f,b) fstat(f, b)
#define p_lstat(p,b) lstat(p,b)
+#define p_stat(p,b) stat(p, b)
+
#define p_readlink(a, b, c) readlink(a, b, c)
#define p_symlink(o,n) symlink(o, n)
#define p_link(o,n) link(o, n)
#define p_unlink(p) unlink(p)
#define p_mkdir(p,m) mkdir(p, m)
#define p_fsync(fd) fsync(fd)
+extern char *p_realpath(const char *, char *);
-/* The OpenBSD realpath function behaves differently */
-#if !defined(__OpenBSD__)
-# define p_realpath(p, po) realpath(p, po)
-#else
-char *p_realpath(const char *, char *);
-#endif
+#define p_recv(s,b,l,f) recv(s,b,l,f)
+#define p_send(s,b,l,f) send(s,b,l,f)
+#define p_inet_pton(a, b, c) inet_pton(a, b, c)
+#define p_strcasecmp(s1, s2) strcasecmp(s1, s2)
+#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c)
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
#define p_mkstemp(p) mkstemp(p)
-#define p_inet_pton(a, b, c) inet_pton(a, b, c)
+#define p_chdir(p) chdir(p)
+#define p_chmod(p,m) chmod(p, m)
+#define p_rmdir(p) rmdir(p)
+#define p_access(p,m) access(p,m)
+#define p_ftruncate(fd, sz) ftruncate(fd, sz)
/* see win32/posix.h for explanation about why this exists */
#define p_lstat_posixly(p,b) lstat(p,b)
+#define p_localtime_r(c, r) localtime_r(c, r)
+#define p_gmtime_r(c, r) gmtime_r(c, r)
+
#endif
diff --git a/src/unix/realpath.c b/src/unix/realpath.c
index 15601bd22..2e49150c2 100644
--- a/src/unix/realpath.c
+++ b/src/unix/realpath.c
@@ -6,7 +6,7 @@
*/
#include <git2/common.h>
-#ifdef __OpenBSD__
+#ifndef GIT_WIN32
#include <limits.h>
#include <stdlib.h>
@@ -16,15 +16,16 @@
char *p_realpath(const char *pathname, char *resolved)
{
char *ret;
-
if ((ret = realpath(pathname, resolved)) == NULL)
return NULL;
- /* Figure out if the file exists */
- if (!access(ret, F_OK))
- return ret;
-
- return NULL;
+#ifdef __OpenBSD__
+ /* The OpenBSD realpath function behaves differently,
+ * figure out if the file exists */
+ if (access(ret, F_OK) < 0)
+ ret = NULL;
+#endif
+ return ret;
}
#endif
diff --git a/src/util.c b/src/util.c
index 39858254f..5c305950f 100644
--- a/src/util.c
+++ b/src/util.c
@@ -612,8 +612,9 @@ void git__qsort_r(
#if defined(__MINGW32__) || defined(AMIGA) || \
defined(__OpenBSD__) || defined(__NetBSD__) || \
defined(__gnu_hurd__) || defined(__ANDROID_API__) || \
- defined(__sun) || \
- (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8)
+ defined(__sun) || defined(__CYGWIN__) || \
+ (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) || \
+ (defined(_MSC_VER) && _MSC_VER < 1500)
git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
#elif defined(GIT_WIN32)
git__qsort_r_glue glue = { cmp, payload };
diff --git a/src/util.h b/src/util.h
index 6fb2dc0f4..6e57ad8c3 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)
@@ -412,7 +419,7 @@ GIT_INLINE(double) git__timer(void)
scaling_factor = (double)info.numer / (double)info.denom;
}
- return (double)time * scaling_factor / 1.0E-9;
+ return (double)time * scaling_factor / 1.0E9;
}
#else
@@ -424,13 +431,13 @@ GIT_INLINE(double) git__timer(void)
struct timespec tp;
if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
- return (double) tp.tv_sec + (double) tp.tv_nsec / 1E-9;
+ return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9;
} else {
/* Fall back to using gettimeofday */
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
- return (double)tv.tv_sec + (double)tv.tv_usec / 1E-6;
+ return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6;
}
}
diff --git a/src/win32/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..83ee28765 100644
--- a/src/win32/mingw-compat.h
+++ b/src/win32/mingw-compat.h
@@ -9,17 +9,11 @@
#if defined(__MINGW32__)
-/* use a 64-bit file offset type */
-# define lseek _lseeki64
-# undef stat
-# define stat _stati64
-# undef fstat
-# define fstat _fstati64
-
-/* stat: file mode type testing macros */
-# define _S_IFLNK 0120000
-# define S_IFLNK _S_IFLNK
-# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+#if _WIN32_WINNT >= 0x0601
+#define stat __stat64
+#else
+#define stat _stati64
+#endif
#endif
diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h
index 50865ed17..4789d63df 100644
--- a/src/win32/msvc-compat.h
+++ b/src/win32/msvc-compat.h
@@ -9,41 +9,12 @@
#if defined(_MSC_VER)
-/* access() mode parameter #defines */
-# define F_OK 0 /* existence check */
-# define W_OK 2 /* write mode check */
-# define R_OK 4 /* read mode check */
+/* 64-bit stat information, regardless of USE_32BIT_TIME_T define */
+#define stat __stat64
-# define lseek _lseeki64
-# define stat _stat64
-# define fstat _fstat64
-
-/* stat: file mode type testing macros */
-# define _S_IFLNK 0120000
-# define S_IFLNK _S_IFLNK
-# define S_IXUSR 00100
-
-# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
-# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
-# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
-# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
-
-# define mode_t unsigned short
-
-/* case-insensitive string comparison */
-# define strcasecmp _stricmp
-# define strncasecmp _strnicmp
-
-/* MSVC doesn't define ssize_t at all */
+typedef unsigned short mode_t;
typedef SSIZE_T ssize_t;
-/* define snprintf using variadic macro support if available */
-#if _MSC_VER >= 1400
-# define snprintf(BUF, SZ, FMT, ...) _snprintf_s(BUF, SZ, _TRUNCATE, FMT, __VA_ARGS__)
-#else
-# define snprintf _snprintf
-#endif
-
#endif
#define GIT_STDLIB_CALL __cdecl
diff --git a/src/win32/posix.h b/src/win32/posix.h
index 7f9d57cc3..e055a77d0 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -12,43 +12,35 @@
#include "utf-conv.h"
#include "dir.h"
-/* define some standard errnos that the runtime may be missing. for example,
- * mingw lacks EAFNOSUPPORT. */
+typedef SOCKET GIT_SOCKET;
-#ifndef EAFNOSUPPORT
-# define EAFNOSUPPORT (INT_MAX-1)
-#endif
-
-GIT_INLINE(int) p_link(const char *old, const char *new)
-{
- GIT_UNUSED(old);
- GIT_UNUSED(new);
- errno = ENOSYS;
- return -1;
-}
-
-extern int p_mkdir(const char *path, mode_t mode);
-extern int p_unlink(const char *path);
+#define p_lseek(f,n,w) _lseeki64(f, n, w)
+#define p_fstat(f,b) _fstat64(f, b)
extern int p_lstat(const char *file_name, struct stat *buf);
+extern int p_stat(const char* path, struct stat* buf);
+
extern int p_readlink(const char *path, char *buf, size_t bufsiz);
extern int p_symlink(const char *old, const char *new);
+extern int p_link(const char *old, const char *new);
+extern int p_unlink(const char *path);
+extern int p_mkdir(const char *path, mode_t mode);
+extern int p_fsync(int fd);
extern char *p_realpath(const char *orig_path, char *buffer);
+
+extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
+extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
+extern int p_inet_pton(int af, const char* src, void* dst);
+
+#define strcasecmp(s1, s2) _stricmp(s1, s2)
+#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c)
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4);
extern int p_mkstemp(char *tmp_path);
-extern int p_stat(const char* path, struct stat* buf);
extern int p_chdir(const char* path);
extern int p_chmod(const char* path, mode_t mode);
extern int p_rmdir(const char* path);
extern int p_access(const char* path, mode_t mode);
-extern int p_fsync(int fd);
-extern int p_open(const char *path, int flags, ...);
-extern int p_creat(const char *path, mode_t mode);
-extern int p_getcwd(char *buffer_out, size_t size);
-extern int p_rename(const char *from, const char *to);
-extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
-extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
-extern int p_inet_pton(int af, const char* src, void* dst);
+extern int p_ftruncate(int fd, long size);
/* p_lstat is almost but not quite POSIX correct. Specifically, the use of
* ENOTDIR is wrong, in that it does not mean precisely that a non-directory
@@ -58,4 +50,7 @@ extern int p_inet_pton(int af, const char* src, void* dst);
*/
extern int p_lstat_posixly(const char *filename, struct stat *buf);
+extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
+
#endif
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 0d070f6b5..0023f95ff 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -19,6 +19,19 @@
# define FILE_NAME_NORMALIZED 0
#endif
+#ifndef IO_REPARSE_TAG_SYMLINK
+#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
+#endif
+
+/* Options which we always provide to _wopen.
+ *
+ * _O_BINARY - Raw access; no translation of CR or LF characters
+ * _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);
@@ -38,6 +51,15 @@ static int utf8_to_16_with_errno(git_win32_path dest, const char *src)
return len;
}
+int p_ftruncate(int fd, long size)
+{
+#if defined(_MSC_VER) && _MSC_VER >= 1500
+ return _chsize_s(fd, size);
+#else
+ return _chsize(fd, size);
+#endif
+}
+
int p_mkdir(const char *path, mode_t mode)
{
git_win32_path buf;
@@ -50,6 +72,14 @@ int p_mkdir(const char *path, mode_t mode)
return _wmkdir(buf);
}
+int p_link(const char *old, const char *new)
+{
+ GIT_UNUSED(old);
+ GIT_UNUSED(new);
+ errno = ENOSYS;
+ return -1;
+}
+
int p_unlink(const char *path)
{
git_win32_path buf;
@@ -317,7 +347,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 +357,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)
@@ -534,11 +564,19 @@ char *p_realpath(const char *orig_path, char *buffer)
int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
{
-#ifdef _MSC_VER
+#if defined(_MSC_VER)
int len;
- if (count == 0 ||
- (len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0)
+ if (count == 0)
+ return _vscprintf(format, argptr);
+
+ #if _MSC_VER >= 1500
+ len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
+ #else
+ len = _vsnprintf(buffer, count, format, argptr);
+ #endif
+
+ if (len < 0)
return _vscprintf(format, argptr);
return len;
@@ -561,7 +599,7 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...)
int p_mkstemp(char *tmp_path)
{
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && _MSC_VER >= 1500
if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
return -1;
#else
@@ -569,7 +607,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)
@@ -582,6 +620,31 @@ int p_access(const char* path, mode_t mode)
return _waccess(buf, mode);
}
+static int ensure_writable(wchar_t *fpath)
+{
+ DWORD attrs;
+
+ attrs = GetFileAttributesW(fpath);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND)
+ return 0;
+
+ giterr_set(GITERR_OS, "failed to get attributes");
+ return -1;
+ }
+
+ if (!(attrs & FILE_ATTRIBUTE_READONLY))
+ return 0;
+
+ attrs &= ~FILE_ATTRIBUTE_READONLY;
+ if (!SetFileAttributesW(fpath, attrs)) {
+ giterr_set(GITERR_OS, "failed to set attributes");
+ return -1;
+ }
+
+ return 0;
+}
+
int p_rename(const char *from, const char *to)
{
git_win32_path wfrom;
@@ -593,12 +656,13 @@ int p_rename(const char *from, const char *to)
if (utf8_to_16_with_errno(wfrom, from) < 0 ||
utf8_to_16_with_errno(wto, to) < 0)
return -1;
-
+
/* wait up to 50ms if file is locked by another thread or process */
rename_tries = 0;
rename_succeeded = 0;
while (rename_tries < 10) {
- if (MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
+ if (ensure_writable(wto) == 0 &&
+ MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
rename_succeeded = 1;
break;
}
diff --git a/src/win32/pthread.c b/src/win32/pthread.c
index db8927471..ec45ecbe5 100644
--- a/src/win32/pthread.c
+++ b/src/win32/pthread.c
@@ -8,32 +8,64 @@
#include "pthread.h"
#include "../global.h"
-int pthread_create(
- pthread_t *GIT_RESTRICT thread,
+#define CLEAN_THREAD_EXIT 0x6F012842
+
+/* The thread procedure stub used to invoke the caller's procedure
+ * and capture the return value for later collection. Windows will
+ * only hold a DWORD, but we need to be able to store an entire
+ * void pointer. This requires the indirection. */
+static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
+{
+ git_win32_thread *thread = lpParameter;
+
+ thread->result = thread->proc(thread->param);
+
+ return CLEAN_THREAD_EXIT;
+}
+
+int git_win32__thread_create(
+ git_win32_thread *GIT_RESTRICT thread,
const pthread_attr_t *GIT_RESTRICT attr,
void *(*start_routine)(void*),
void *GIT_RESTRICT arg)
{
GIT_UNUSED(attr);
- *thread = CreateThread(
- NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL);
- return *thread ? 0 : -1;
+
+ thread->result = NULL;
+ thread->param = arg;
+ thread->proc = start_routine;
+ thread->thread = CreateThread(
+ NULL, 0, git_win32__threadproc, thread, 0, NULL);
+
+ return thread->thread ? 0 : -1;
}
-int pthread_join(pthread_t thread, void **value_ptr)
+int git_win32__thread_join(
+ git_win32_thread *thread,
+ void **value_ptr)
{
- DWORD ret = WaitForSingleObject(thread, INFINITE);
+ DWORD exit;
+
+ if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0)
+ return -1;
+
+ if (!GetExitCodeThread(thread->thread, &exit)) {
+ CloseHandle(thread->thread);
+ return -1;
+ }
- if (ret == WAIT_OBJECT_0) {
- if (value_ptr != NULL) {
- *value_ptr = NULL;
- GetExitCodeThread(thread, (void *)value_ptr);
- }
- CloseHandle(thread);
- return 0;
+ /* Check for the thread having exited uncleanly. If exit was unclean,
+ * then we don't have a return value to give back to the caller. */
+ if (exit != CLEAN_THREAD_EXIT) {
+ assert(false);
+ thread->result = NULL;
}
- return -1;
+ if (value_ptr)
+ *value_ptr = thread->result;
+
+ CloseHandle(thread->thread);
+ return 0;
}
int pthread_mutex_init(
@@ -144,9 +176,6 @@ int pthread_num_processors_np(void)
return n ? n : 1;
}
-
-static HINSTANCE win32_kernel32_dll;
-
typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *);
static win32_srwlock_fn win32_srwlock_initialize;
@@ -159,7 +188,7 @@ int pthread_rwlock_init(
pthread_rwlock_t *GIT_RESTRICT lock,
const pthread_rwlockattr_t *GIT_RESTRICT attr)
{
- (void)attr;
+ GIT_UNUSED(attr);
if (win32_srwlock_initialize)
win32_srwlock_initialize(&lock->native.srwl);
@@ -217,38 +246,22 @@ int pthread_rwlock_destroy(pthread_rwlock_t *lock)
return 0;
}
-
-static void win32_pthread_shutdown(void)
-{
- if (win32_kernel32_dll) {
- FreeLibrary(win32_kernel32_dll);
- win32_kernel32_dll = NULL;
- }
-}
-
int win32_pthread_initialize(void)
{
- if (win32_kernel32_dll)
- return 0;
-
- win32_kernel32_dll = LoadLibrary("Kernel32.dll");
- if (!win32_kernel32_dll) {
- giterr_set(GITERR_OS, "Could not load Kernel32.dll!");
- return -1;
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule) {
+ win32_srwlock_initialize = (win32_srwlock_fn)
+ GetProcAddress(hModule, "InitializeSRWLock");
+ win32_srwlock_acquire_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockShared");
+ win32_srwlock_release_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockShared");
+ win32_srwlock_acquire_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockExclusive");
+ win32_srwlock_release_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockExclusive");
}
- win32_srwlock_initialize = (win32_srwlock_fn)
- GetProcAddress(win32_kernel32_dll, "InitializeSRWLock");
- win32_srwlock_acquire_shared = (win32_srwlock_fn)
- GetProcAddress(win32_kernel32_dll, "AcquireSRWLockShared");
- win32_srwlock_release_shared = (win32_srwlock_fn)
- GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockShared");
- win32_srwlock_acquire_exclusive = (win32_srwlock_fn)
- GetProcAddress(win32_kernel32_dll, "AcquireSRWLockExclusive");
- win32_srwlock_release_exclusive = (win32_srwlock_fn)
- GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive");
-
- git__on_shutdown(win32_pthread_shutdown);
-
return 0;
}
diff --git a/src/win32/pthread.h b/src/win32/pthread.h
index af5b121f0..e4826ca7f 100644
--- a/src/win32/pthread.h
+++ b/src/win32/pthread.h
@@ -16,13 +16,19 @@
# define GIT_RESTRICT __restrict__
#endif
+typedef struct {
+ HANDLE thread;
+ void *(*proc)(void *);
+ void *param;
+ void *result;
+} git_win32_thread;
+
typedef int pthread_mutexattr_t;
typedef int pthread_condattr_t;
typedef int pthread_attr_t;
typedef int pthread_rwlockattr_t;
typedef CRITICAL_SECTION pthread_mutex_t;
-typedef HANDLE pthread_t;
typedef HANDLE pthread_cond_t;
typedef struct { void *Ptr; } GIT_SRWLOCK;
@@ -36,13 +42,26 @@ typedef struct {
#define PTHREAD_MUTEX_INITIALIZER {(void*)-1}
-int pthread_create(
- pthread_t *GIT_RESTRICT thread,
- const pthread_attr_t *GIT_RESTRICT attr,
- void *(*start_routine)(void*),
- void *GIT_RESTRICT arg);
+int git_win32__thread_create(
+ git_win32_thread *GIT_RESTRICT,
+ const pthread_attr_t *GIT_RESTRICT,
+ void *(*) (void *),
+ void *GIT_RESTRICT);
+
+int git_win32__thread_join(
+ git_win32_thread *,
+ void **);
+
+#ifdef GIT_THREADS
-int pthread_join(pthread_t, void **);
+typedef git_win32_thread git_thread;
+
+#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \
+ git_win32__thread_create(git_thread_ptr, attr, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ git_win32__thread_join(git_thread_ptr, status)
+
+#endif
int pthread_mutex_init(
pthread_mutex_t *GIT_RESTRICT mutex,