summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attr.c43
-rw-r--r--src/attr.h20
-rw-r--r--src/attr_file.c1
-rw-r--r--src/attr_file.h1
-rw-r--r--src/attrcache.h24
-rw-r--r--src/blob.c15
-rw-r--r--src/branch.c64
-rw-r--r--src/buf_text.c77
-rw-r--r--src/buf_text.h14
-rw-r--r--src/buffer.h11
-rw-r--r--src/checkout.c64
-rw-r--r--src/commit.c4
-rw-r--r--src/common.h9
-rw-r--r--src/config.c99
-rw-r--r--src/config.h7
-rw-r--r--src/crlf.c118
-rw-r--r--src/diff.c177
-rw-r--r--src/diff_output.c63
-rw-r--r--src/diff_tform.c33
-rw-r--r--src/errors.c1
-rw-r--r--src/fetchhead.c1
-rw-r--r--src/fileops.c211
-rw-r--r--src/fileops.h63
-rw-r--r--src/filter.c3
-rw-r--r--src/filter.h1
-rw-r--r--src/global.c9
-rw-r--r--src/graph.c16
-rw-r--r--src/hashsig.c21
-rw-r--r--src/ignore.c3
-rw-r--r--src/ignore.h5
-rw-r--r--src/index.c57
-rw-r--r--src/index.h3
-rw-r--r--src/indexer.c12
-rw-r--r--src/iterator.c1158
-rw-r--r--src/iterator.h153
-rw-r--r--src/mwindow.c6
-rw-r--r--src/notes.c99
-rw-r--r--src/notes.h1
-rw-r--r--src/odb.c24
-rw-r--r--src/odb_pack.c28
-rw-r--r--src/oid.c7
-rw-r--r--src/path.c44
-rw-r--r--src/path.h8
-rw-r--r--src/pool.c33
-rw-r--r--src/pool.h7
-rw-r--r--src/posix.h3
-rw-r--r--src/push.c31
-rw-r--r--src/push.h7
-rw-r--r--src/refdb.c177
-rw-r--r--src/refdb.h46
-rw-r--r--src/refdb_fs.c1023
-rw-r--r--src/refdb_fs.h15
-rw-r--r--src/reflog.c2
-rw-r--r--src/refs.c1541
-rw-r--r--src/refs.h19
-rw-r--r--src/remote.c8
-rw-r--r--src/repository.c51
-rw-r--r--src/repository.h6
-rw-r--r--src/reset.c43
-rw-r--r--src/revparse.c9
-rw-r--r--src/stash.c6
-rw-r--r--src/status.c67
-rw-r--r--src/submodule.c16
-rw-r--r--src/tag.c12
-rw-r--r--src/trace.c39
-rw-r--r--src/trace.h56
-rw-r--r--src/transports/http.c131
-rw-r--r--src/transports/local.c238
-rw-r--r--src/transports/smart.c2
-rw-r--r--src/transports/smart.h1
-rw-r--r--src/transports/smart_pkt.c22
-rw-r--r--src/transports/smart_protocol.c135
-rw-r--r--src/transports/winhttp.c160
-rw-r--r--src/tree.c21
-rw-r--r--src/tsort.c8
-rw-r--r--src/unix/posix.h1
-rw-r--r--src/util.c106
-rw-r--r--src/util.h10
-rw-r--r--src/win32/error.c72
-rw-r--r--src/win32/findfile.c196
-rw-r--r--src/win32/findfile.h15
-rw-r--r--src/win32/version.h20
82 files changed, 4332 insertions, 2801 deletions
diff --git a/src/attr.c b/src/attr.c
index 9c88771e3..979fecc14 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -1,6 +1,8 @@
#include "repository.h"
#include "fileops.h"
#include "config.h"
+#include "attr.h"
+#include "ignore.h"
#include "git2/oid.h"
#include <ctype.h>
@@ -593,17 +595,28 @@ static int collect_attr_files(
return error;
}
-static char *try_global_default(const char *relpath)
+static int attr_cache__lookup_path(
+ const char **out, git_config *cfg, const char *key, const char *fallback)
{
- git_buf dflt = GIT_BUF_INIT;
- char *rval = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
- if (!git_futils_find_global_file(&dflt, relpath))
- rval = git_buf_detach(&dflt);
+ if (!(error = git_config_get_string(out, cfg, key)))
+ return 0;
- git_buf_free(&dflt);
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
- return rval;
+ if (!git_futils_find_xdg_file(&buf, fallback))
+ *out = git_buf_detach(&buf);
+ else
+ *out = NULL;
+
+ git_buf_free(&buf);
+ }
+
+ return error;
}
int git_attr_cache__init(git_repository *repo)
@@ -619,19 +632,15 @@ int git_attr_cache__init(git_repository *repo)
if (git_repository_config__weakptr(&cfg, repo) < 0)
return -1;
- ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG);
- if (ret < 0 && ret != GIT_ENOTFOUND)
+ ret = attr_cache__lookup_path(
+ &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
+ if (ret < 0)
return ret;
- if (ret == GIT_ENOTFOUND)
- cache->cfg_attr_file = try_global_default(GIT_ATTR_CONFIG_DEFAULT);
- ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG);
- if (ret < 0 && ret != GIT_ENOTFOUND)
+ ret = attr_cache__lookup_path(
+ &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
+ if (ret < 0)
return ret;
- if (ret == GIT_ENOTFOUND)
- cache->cfg_excl_file = try_global_default(GIT_IGNORE_CONFIG_DEFAULT);
-
- giterr_clear();
/* allocate hashtable for attribute and ignore file contents */
if (cache->files == NULL) {
diff --git a/src/attr.h b/src/attr.h
index 0fc33089b..19c979bcd 100644
--- a/src/attr.h
+++ b/src/attr.h
@@ -8,27 +8,13 @@
#define INCLUDE_attr_h__
#include "attr_file.h"
-#include "strmap.h"
-
-#define GIT_ATTR_CONFIG "core.attributesfile"
-#define GIT_ATTR_CONFIG_DEFAULT ".config/git/attributes"
-#define GIT_IGNORE_CONFIG "core.excludesfile"
-#define GIT_IGNORE_CONFIG_DEFAULT ".config/git/ignore"
-
-typedef struct {
- int initialized;
- git_pool pool;
- git_strmap *files; /* hash path to git_attr_file of rules */
- git_strmap *macros; /* hash name to vector<git_attr_assignment> */
- const char *cfg_attr_file; /* cached value of core.attributesfile */
- const char *cfg_excl_file; /* cached value of core.excludesfile */
-} git_attr_cache;
+
+#define GIT_ATTR_CONFIG "core.attributesfile"
+#define GIT_IGNORE_CONFIG "core.excludesfile"
typedef int (*git_attr_file_parser)(
git_repository *, void *, const char *, git_attr_file *);
-extern int git_attr_cache__init(git_repository *repo);
-
extern int git_attr_cache__insert_macro(
git_repository *repo, git_attr_rule *macro);
diff --git a/src/attr_file.c b/src/attr_file.c
index 628cb1544..74bd2133f 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -1,6 +1,7 @@
#include "common.h"
#include "repository.h"
#include "filebuf.h"
+#include "attr.h"
#include "git2/blob.h"
#include "git2/tree.h"
#include <ctype.h>
diff --git a/src/attr_file.h b/src/attr_file.h
index 8dc8303f7..2cc8546a2 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -17,6 +17,7 @@
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_INREPO "info/attributes"
#define GIT_ATTR_FILE_SYSTEM "gitattributes"
+#define GIT_ATTR_FILE_XDG "attributes"
#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
diff --git a/src/attrcache.h b/src/attrcache.h
new file mode 100644
index 000000000..12cec4bfb
--- /dev/null
+++ b/src/attrcache.h
@@ -0,0 +1,24 @@
+/*
+ * 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_attrcache_h__
+#define INCLUDE_attrcache_h__
+
+#include "pool.h"
+#include "strmap.h"
+
+typedef struct {
+ int initialized;
+ git_pool pool;
+ git_strmap *files; /* hash path to git_attr_file of rules */
+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */
+ const char *cfg_attr_file; /* cached value of core.attributesfile */
+ const char *cfg_excl_file; /* cached value of core.excludesfile */
+} git_attr_cache;
+
+extern int git_attr_cache__init(git_repository *repo);
+
+#endif
diff --git a/src/blob.c b/src/blob.c
index bcb6ac96b..c0514fc13 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -12,6 +12,7 @@
#include "common.h"
#include "blob.h"
#include "filter.h"
+#include "buf_text.h"
const void *git_blob_rawcontent(const git_blob *blob)
{
@@ -221,7 +222,9 @@ int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *
return -1;
}
- error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true);
+ error = blob_create_internal(
+ oid, repo, git_buf_cstr(&full_path),
+ git_buf_cstr(&full_path) + strlen(workdir), true);
git_buf_free(&full_path);
return error;
@@ -231,13 +234,21 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat
{
int error;
git_buf full_path = GIT_BUF_INIT;
+ const char *workdir, *hintpath;
if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
git_buf_free(&full_path);
return error;
}
- error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true);
+ hintpath = git_buf_cstr(&full_path);
+ workdir = git_repository_workdir(repo);
+
+ if (workdir && !git__prefixcmp(hintpath, workdir))
+ hintpath += strlen(workdir);
+
+ error = blob_create_internal(
+ oid, repo, git_buf_cstr(&full_path), hintpath, true);
git_buf_free(&full_path);
return error;
diff --git a/src/branch.c b/src/branch.c
index a50387541..45ecca751 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -54,11 +54,11 @@ static int not_a_local_branch(const char *reference_name)
}
int git_branch_create(
- git_reference **ref_out,
- git_repository *repository,
- const char *branch_name,
- const git_commit *commit,
- int force)
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ int force)
{
git_reference *branch = NULL;
git_buf canonical_branch_name = GIT_BUF_INIT;
@@ -124,10 +124,7 @@ on_error:
}
typedef struct {
- int (*branch_cb)(
- const char *branch_name,
- git_branch_t branch_type,
- void *payload);
+ git_branch_foreach_cb branch_cb;
void *callback_payload;
unsigned int branch_type;
} branch_foreach_filter;
@@ -148,14 +145,10 @@ static int branch_foreach_cb(const char *branch_name, void *payload)
}
int git_branch_foreach(
- git_repository *repo,
- unsigned int list_flags,
- int (*branch_cb)(
- const char *branch_name,
- git_branch_t branch_type,
- void *payload),
- void *payload
-)
+ git_repository *repo,
+ unsigned int list_flags,
+ git_branch_foreach_cb branch_cb,
+ void *payload)
{
branch_foreach_filter filter;
@@ -167,6 +160,7 @@ int git_branch_foreach(
}
int git_branch_move(
+ git_reference **out,
git_reference *branch,
const char *new_branch_name,
int force)
@@ -181,28 +175,20 @@ int git_branch_move(
if (!git_reference_is_branch(branch))
return not_a_local_branch(git_reference_name(branch));
- if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
- goto cleanup;
-
- if (git_buf_printf(
- &old_config_section,
- "branch.%s",
- git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
- goto cleanup;
-
- if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0)
- goto cleanup;
+ if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 ||
+ (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 ||
+ (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0)
+ goto done;
- if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0)
- goto cleanup;
-
- if ((error = git_config_rename_section(
- git_reference_owner(branch),
+ if ((error = git_config_rename_section(git_reference_owner(branch),
git_buf_cstr(&old_config_section),
git_buf_cstr(&new_config_section))) < 0)
- goto cleanup;
+ goto done;
+
+ if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)
+ goto done;
-cleanup:
+done:
git_buf_free(&new_reference_name);
git_buf_free(&old_config_section);
git_buf_free(&new_config_section);
@@ -211,10 +197,10 @@ cleanup:
}
int git_branch_lookup(
- git_reference **ref_out,
- git_repository *repo,
- const char *branch_name,
- git_branch_t branch_type)
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type)
{
assert(ref_out && repo && branch_name);
diff --git a/src/buf_text.c b/src/buf_text.c
index 3a8f442b4..443454b5f 100644
--- a/src/buf_text.c
+++ b/src/buf_text.c
@@ -60,6 +60,83 @@ void git_buf_text_unescape(git_buf *buf)
buf->size = git__unescape(buf->ptr);
}
+int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
+{
+ const char *scan = src->ptr;
+ const char *scan_end = src->ptr + src->size;
+ const char *next = memchr(scan, '\r', src->size);
+ char *out;
+
+ assert(tgt != src);
+
+ if (!next)
+ return GIT_ENOTFOUND;
+
+ /* reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size) < 0)
+ return -1;
+ out = tgt->ptr;
+ tgt->size = 0;
+
+ /* Find the next \r and copy whole chunk up to there to tgt */
+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+ if (next > scan) {
+ size_t copylen = next - scan;
+ memcpy(out, scan, copylen);
+ out += copylen;
+ }
+
+ /* Do not drop \r unless it is followed by \n */
+ if (next[1] != '\n')
+ *out++ = '\r';
+ }
+
+ /* Copy remaining input into dest */
+ memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */
+ out += (scan_end - scan);
+ tgt->size = out - tgt->ptr;
+
+ return 0;
+}
+
+int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
+{
+ const char *start = src->ptr;
+ const char *end = start + src->size;
+ const char *scan = start;
+ const char *next = memchr(scan, '\n', src->size);
+
+ assert(tgt != src);
+
+ if (!next)
+ return GIT_ENOTFOUND;
+
+ /* attempt to reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)
+ return -1;
+ tgt->size = 0;
+
+ 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;
+
+ if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)
+ return -1;
+
+ if (next > scan) {
+ memcpy(tgt->ptr + tgt->size, scan, copylen);
+ tgt->size += copylen;
+ }
+ if (extralen == 2)
+ tgt->ptr[tgt->size++] = '\r';
+ tgt->ptr[tgt->size++] = '\n';
+ }
+
+ return git_buf_put(tgt, scan, end - scan);
+}
+
int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
{
size_t i;
diff --git a/src/buf_text.h b/src/buf_text.h
index 458ee33c9..58e4e26a7 100644
--- a/src/buf_text.h
+++ b/src/buf_text.h
@@ -56,6 +56,20 @@ 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 (or do nothing if no \r\n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error
+ */
+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Replace all \n with \r\n (or do nothing if no \n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error
+ */
+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
+
+/**
* Fill buffer with the common prefix of a array of strings
*
* Buffer will be set to empty if there is no common prefix
diff --git a/src/buffer.h b/src/buffer.h
index 6e73895b4..5402f3827 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -119,7 +119,7 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf);
#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1)
-GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)
+GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch)
{
ssize_t idx = (ssize_t)buf->size - 1;
while (idx >= 0 && buf->ptr[idx] == ch) idx--;
@@ -127,18 +127,17 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)
return idx;
}
-GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch)
+GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)
{
ssize_t idx = (ssize_t)buf->size - 1;
while (idx >= 0 && buf->ptr[idx] != ch) idx--;
return idx;
}
-GIT_INLINE(ssize_t) git_buf_find(git_buf *buf, char ch)
+GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)
{
- size_t idx = 0;
- while (idx < buf->size && buf->ptr[idx] != ch) idx++;
- return (idx == buf->size) ? -1 : (ssize_t)idx;
+ void *found = memchr(buf->ptr, ch, buf->size);
+ return found ? (ssize_t)((const char *)found - buf->ptr) : -1;
}
/* Remove whitespace from the end of the buffer */
diff --git a/src/checkout.c b/src/checkout.c
index 19ac913d3..24fa21024 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -23,6 +23,7 @@
#include "blob.h"
#include "diff.h"
#include "pathspec.h"
+#include "buf_text.h"
/* See docs/checkout-internals.md for more information */
@@ -234,10 +235,13 @@ static int checkout_action_wd_only(
/* check if item is tracked in the index but not in the checkout diff */
if (data->index != NULL) {
if (wd->mode != GIT_FILEMODE_TREE) {
- if (git_index_get_bypath(data->index, wd->path, 0) != NULL) {
+ int error;
+
+ if ((error = git_index_find(NULL, data->index, wd->path)) == 0) {
notify = GIT_CHECKOUT_NOTIFY_DIRTY;
remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
- }
+ } else if (error != GIT_ENOTFOUND)
+ return error;
} else {
/* for tree entries, we have to see if there are any index
* entries that are contained inside that tree
@@ -456,7 +460,7 @@ static int checkout_action(
while (1) {
if (!wd)
return checkout_action_no_wd(data, delta);
-
+
cmp = strcomp(wd->path, delta->old_file.path);
/* 1. wd before delta ("a/a" before "a/b")
@@ -473,9 +477,9 @@ static int checkout_action(
if (cmp == 0) {
if (wd->mode == GIT_FILEMODE_TREE) {
/* case 2 - entry prefixed by workdir tree */
- if (git_iterator_advance_into_directory(workdir, &wd) < 0)
+ if (git_iterator_advance_into(&wd, workdir) < 0)
goto fail;
-
+
*wditem_ptr = wd;
continue;
}
@@ -484,14 +488,14 @@ static int checkout_action(
if (delta->old_file.path[strlen(wd->path)] == '/') {
act = checkout_action_with_wd_blocker(data, delta, wd);
*wditem_ptr =
- git_iterator_advance(workdir, &wd) ? NULL : wd;
+ git_iterator_advance(&wd, workdir) ? NULL : wd;
return act;
}
}
/* case 1 - handle wd item (if it matches pathspec) */
if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||
- git_iterator_advance(workdir, &wd) < 0)
+ git_iterator_advance(&wd, workdir) < 0)
goto fail;
*wditem_ptr = wd;
@@ -501,7 +505,7 @@ static int checkout_action(
if (cmp == 0) {
/* case 4 */
act = checkout_action_with_wd(data, delta, wd);
- *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
+ *wditem_ptr = git_iterator_advance(&wd, workdir) ? NULL : wd;
return act;
}
@@ -514,7 +518,7 @@ static int checkout_action(
if (delta->status == GIT_DELTA_TYPECHANGE) {
if (delta->old_file.mode == GIT_FILEMODE_TREE) {
act = checkout_action_with_wd(data, delta, wd);
- if (git_iterator_advance_into_directory(workdir, &wd) < 0)
+ if (git_iterator_advance_into(&wd, workdir) < 0)
wd = NULL;
*wditem_ptr = wd;
return act;
@@ -525,7 +529,7 @@ static int checkout_action(
delta->old_file.mode == GIT_FILEMODE_COMMIT)
{
act = checkout_action_with_wd(data, delta, wd);
- if (git_iterator_advance(workdir, &wd) < 0)
+ if (git_iterator_advance(&wd, workdir) < 0)
wd = NULL;
*wditem_ptr = wd;
return act;
@@ -554,7 +558,7 @@ static int checkout_remaining_wd_items(
while (wd && !error) {
if (!(error = checkout_action_wd_only(data, workdir, wd, spec)))
- error = git_iterator_advance(workdir, &wd);
+ error = git_iterator_advance(&wd, workdir);
}
return error;
@@ -578,7 +582,7 @@ static int checkout_get_actions(
git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)
return -1;
- if ((error = git_iterator_current(workdir, &wditem)) < 0)
+ if ((error = git_iterator_current(&wditem, workdir)) < 0)
goto fail;
deltas = &data->diff->deltas;
@@ -610,7 +614,7 @@ static int checkout_get_actions(
if (act & CHECKOUT_ACTION__CONFLICT)
counts[CHECKOUT_ACTION__CONFLICT]++;
}
-
+
error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec);
if (error < 0)
goto fail;
@@ -665,11 +669,11 @@ static int buffer_to_file(
giterr_set(GITERR_OS, "Could not write to '%s'", path);
(void)p_close(fd);
} else {
- if ((error = p_fstat(fd, st)) < 0)
- giterr_set(GITERR_OS, "Error while statting '%s'", path);
-
if ((error = p_close(fd)) < 0)
giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ if ((error = p_stat(path, st)) < 0)
+ giterr_set(GITERR_OS, "Error while statting '%s'", path);
}
if (!error &&
@@ -1134,16 +1138,17 @@ static int checkout_data_init(
if ((error = git_config_refresh(cfg)) < 0)
goto cleanup;
- if (git_iterator_inner_type(target) == GIT_ITERATOR_TYPE_INDEX) {
- /* if we are iterating over the index, don't reload */
- data->index = git_iterator_index_get_index(target);
+ /* if we are checking out the index, don't reload,
+ * otherwise get index and force reload
+ */
+ 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)) < 0)
goto cleanup;
-
+
/* clear the REUC when doing a tree or commit checkout */
git_index_reuc_clear(data->index);
}
@@ -1241,16 +1246,15 @@ int git_checkout_iterator(
GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
- (error = git_iterator_for_workdir_range(
- &workdir, data.repo, iterflags, data.pfx, data.pfx)) < 0 ||
- (error = git_iterator_for_tree_range(
+ (error = git_iterator_for_workdir(
+ &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
+ data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_tree(
&baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0)
goto cleanup;
- /* Handle case insensitivity for baseline if necessary */
- if (git_iterator_ignore_case(workdir) != git_iterator_ignore_case(baseline))
- if ((error = git_iterator_spoolandsort_push(baseline, true)) < 0)
- goto cleanup;
+ /* Should not have case insensitivity mismatch */
+ assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));
/* Generate baseline-to-target diff which will include an entry for
* every possible update that might need to be made.
@@ -1321,7 +1325,7 @@ int git_checkout_index(
return error;
GIT_REFCOUNT_INC(index);
- if (!(error = git_iterator_for_index(&index_i, index)))
+ if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL)))
error = git_checkout_iterator(index_i, opts);
git_iterator_free(index_i);
@@ -1348,7 +1352,7 @@ int git_checkout_tree(
return -1;
}
- if (!(error = git_iterator_for_tree(&tree_i, tree)))
+ if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL)))
error = git_checkout_iterator(tree_i, opts);
git_iterator_free(tree_i);
@@ -1369,7 +1373,7 @@ int git_checkout_head(
return error;
if (!(error = checkout_lookup_head_tree(&head, repo)) &&
- !(error = git_iterator_for_tree(&head_i, head)))
+ !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL)))
error = git_checkout_iterator(head_i, opts);
git_iterator_free(head_i);
diff --git a/src/commit.c b/src/commit.c
index 29ce39107..e2d3d346b 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -20,7 +20,7 @@
static void clear_parents(git_commit *commit)
{
- unsigned int i;
+ size_t i;
for (i = 0; i < commit->parent_ids.length; ++i) {
git_oid *parent = git_vector_get(&commit->parent_ids, i);
@@ -121,7 +121,7 @@ int git_commit_create(
git_buf_free(&commit);
if (update_ref != NULL)
- return git_reference__update(repo, oid, update_ref);
+ return git_reference__update_terminal(repo, update_ref, oid);
return 0;
diff --git a/src/common.h b/src/common.h
index e3a9e1984..02d9ce9b6 100644
--- a/src/common.h
+++ b/src/common.h
@@ -29,9 +29,10 @@
# include "win32/msvc-compat.h"
# include "win32/mingw-compat.h"
# include "win32/error.h"
+# include "win32/version.h"
# ifdef GIT_THREADS
# include "win32/pthread.h"
-#endif
+# endif
#else
@@ -56,6 +57,12 @@
#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
/**
+ * Check a return value and propogate result if non-zero.
+ */
+#define GITERR_CHECK_ERROR(code) \
+ do { int _err = (code); if (_err < 0) return _err; } while (0)
+
+/**
* Set the error message for this thread, formatting as needed.
*/
void giterr_set(int error_class, const char *string, ...);
diff --git a/src/config.c b/src/config.c
index d6aa3078c..5379b0ec5 100644
--- a/src/config.c
+++ b/src/config.c
@@ -36,7 +36,7 @@ static void file_internal_free(file_internal *internal)
static void config_free(git_config *cfg)
{
- unsigned int i;
+ size_t i;
file_internal *internal;
for(i = 0; i < cfg->files.length; ++i){
@@ -284,7 +284,7 @@ int git_config_add_backend(
int git_config_refresh(git_config *cfg)
{
int error = 0;
- unsigned int i;
+ size_t i;
for (i = 0; i < cfg->files.length && !error; ++i) {
file_internal *internal = git_vector_get(&cfg->files, i);
@@ -312,7 +312,7 @@ int git_config_foreach_match(
void *payload)
{
int ret = 0;
- unsigned int i;
+ size_t i;
file_internal *internal;
git_config_backend *file;
@@ -362,6 +362,11 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
git_config_backend *file;
file_internal *internal;
+ if (!value) {
+ giterr_set(GITERR_CONFIG, "The value to set cannot be NULL");
+ return -1;
+ }
+
internal = git_vector_get(&cfg->files, 0);
file = internal->file;
@@ -510,62 +515,48 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex
return file->set_multivar(file, name, regexp, value);
}
-int git_config_find_global_r(git_buf *path)
+static int git_config__find_file_to_path(
+ char *out, size_t outlen, int (*find)(git_buf *buf))
{
- int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if ((error = find(&path)) < 0)
+ goto done;
+ if (path.size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ error = GIT_EBUFS;
+ goto done;
+ }
+
+ git_buf_copy_cstr(out, outlen, &path);
+
+done:
+ git_buf_free(&path);
return error;
}
-int git_config_find_xdg_r(git_buf *path)
+int git_config_find_global_r(git_buf *path)
{
- int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME_ALT);
-
- return error;
+ return git_futils_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL);
}
int git_config_find_global(char *global_config_path, size_t length)
{
- git_buf path = GIT_BUF_INIT;
- int ret = git_config_find_global_r(&path);
-
- if (ret < 0) {
- git_buf_free(&path);
- return ret;
- }
-
- if (path.size >= length) {
- git_buf_free(&path);
- giterr_set(GITERR_NOMEMORY,
- "Path is to long to fit on the given buffer");
- return -1;
- }
+ return git_config__find_file_to_path(
+ global_config_path, length, git_config_find_global_r);
+}
- git_buf_copy_cstr(global_config_path, length, &path);
- git_buf_free(&path);
- return 0;
+int git_config_find_xdg_r(git_buf *path)
+{
+ return git_futils_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);
}
int git_config_find_xdg(char *xdg_config_path, size_t length)
{
- git_buf path = GIT_BUF_INIT;
- int ret = git_config_find_xdg_r(&path);
-
- if (ret < 0) {
- git_buf_free(&path);
- return ret;
- }
-
- if (path.size >= length) {
- git_buf_free(&path);
- giterr_set(GITERR_NOMEMORY,
- "Path is to long to fit on the given buffer");
- return -1;
- }
-
- git_buf_copy_cstr(xdg_config_path, length, &path);
- git_buf_free(&path);
- return 0;
+ return git_config__find_file_to_path(
+ xdg_config_path, length, git_config_find_xdg_r);
}
int git_config_find_system_r(git_buf *path)
@@ -575,24 +566,8 @@ int git_config_find_system_r(git_buf *path)
int git_config_find_system(char *system_config_path, size_t length)
{
- git_buf path = GIT_BUF_INIT;
- int ret = git_config_find_system_r(&path);
-
- if (ret < 0) {
- git_buf_free(&path);
- return ret;
- }
-
- if (path.size >= length) {
- git_buf_free(&path);
- giterr_set(GITERR_NOMEMORY,
- "Path is to long to fit on the given buffer");
- return -1;
- }
-
- git_buf_copy_cstr(system_config_path, length, &path);
- git_buf_free(&path);
- return 0;
+ return git_config__find_file_to_path(
+ system_config_path, length, git_config_find_system_r);
}
int git_config_open_default(git_config **out)
diff --git a/src/config.h b/src/config.h
index db5ebb3b7..c43e47e82 100644
--- a/src/config.h
+++ b/src/config.h
@@ -12,10 +12,11 @@
#include "vector.h"
#include "repository.h"
-#define GIT_CONFIG_FILENAME ".gitconfig"
-#define GIT_CONFIG_FILENAME_ALT ".config/git/config"
-#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
+#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"
+#define GIT_CONFIG_FILENAME_XDG "config"
+
+#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
diff --git a/src/crlf.c b/src/crlf.c
index 060d39d37..81268da83 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -9,9 +9,10 @@
#include "fileops.h"
#include "hash.h"
#include "filter.h"
+#include "buf_text.h"
#include "repository.h"
-
#include "git2/attr.h"
+#include "git2/blob.h"
struct crlf_attrs {
int crlf_action;
@@ -21,6 +22,8 @@ struct crlf_attrs {
struct crlf_filter {
git_filter f;
struct crlf_attrs attrs;
+ git_repository *repo;
+ char path[GIT_FLEX_ARRAY];
};
static int check_crlf(const char *value)
@@ -103,36 +106,46 @@ static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, con
return -1;
}
-static int drop_crlf(git_buf *dest, const git_buf *source)
+static int has_cr_in_index(git_filter *self)
{
- const char *scan = source->ptr, *next;
- const char *scan_end = git_buf_cstr(source) + git_buf_len(source);
+ struct crlf_filter *filter = (struct crlf_filter *)self;
+ git_index *index;
+ const git_index_entry *entry;
+ git_blob *blob;
+ const void *blobcontent;
+ git_off_t blobsize;
+ bool found_cr;
+
+ if (git_repository_index__weakptr(&index, filter->repo) < 0) {
+ giterr_clear();
+ return false;
+ }
- /* Main scan loop. Find the next carriage return and copy the
- * whole chunk up to that point to the destination buffer.
- */
- while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) {
- /* copy input up to \r */
- if (next > scan)
- git_buf_put(dest, scan, next - scan);
+ if (!(entry = git_index_get_bypath(index, filter->path, 0)) &&
+ !(entry = git_index_get_bypath(index, filter->path, 1)))
+ return false;
- /* Do not drop \r unless it is followed by \n */
- if (*(next + 1) != '\n')
- git_buf_putc(dest, '\r');
+ if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
+ return true;
- scan = next + 1;
- }
+ if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0)
+ return false;
- /* If there was no \r, then tell the library to skip this filter */
- if (scan == source->ptr)
- return -1;
+ blobcontent = git_blob_rawcontent(blob);
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize))
+ blobsize = (size_t)-1;
+
+ found_cr = (blobcontent != NULL &&
+ blobsize > 0 &&
+ memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
- /* Copy remaining input into dest */
- git_buf_put(dest, scan, scan_end - scan);
- return 0;
+ git_blob_free(blob);
+ return found_cr;
}
-static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source)
+static int crlf_apply_to_odb(
+ git_filter *self, git_buf *dest, const git_buf *source)
{
struct crlf_filter *filter = (struct crlf_filter *)self;
@@ -162,40 +175,21 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou
if (stats.cr != stats.crlf)
return -1;
-#if 0
- if (crlf_action == CRLF_GUESS) {
+ if (filter->attrs.crlf_action == GIT_CRLF_GUESS) {
/*
* If the file in the index has any CR in it, do not convert.
* This is the new safer autocrlf handling.
*/
- if (has_cr_in_index(path))
- return 0;
+ if (has_cr_in_index(self))
+ return -1;
}
-#endif
if (!stats.cr)
return -1;
}
/* Actually drop the carriage returns */
- return drop_crlf(dest, source);
-}
-
-static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending)
-{
- const char *scan = git_buf_cstr(source),
- *next,
- *scan_end = git_buf_cstr(source) + git_buf_len(source);
-
- while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) {
- if (next > scan)
- git_buf_put(dest, scan, next-scan);
- git_buf_puts(dest, ending);
- scan = next + 1;
- }
-
- git_buf_put(dest, scan, scan_end - scan);
- return 0;
+ return git_buf_text_crlf_to_lf(dest, source);
}
static const char *line_ending(struct crlf_filter *filter)
@@ -238,26 +232,28 @@ line_ending_error:
return NULL;
}
-static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source)
+static int crlf_apply_to_workdir(
+ git_filter *self, git_buf *dest, const git_buf *source)
{
struct crlf_filter *filter = (struct crlf_filter *)self;
const char *workdir_ending = NULL;
- assert (self && dest && source);
+ assert(self && dest && source);
/* Empty file? Nothing to do. */
if (git_buf_len(source) == 0)
- return 0;
+ return -1;
/* Determine proper line ending */
workdir_ending = line_ending(filter);
- if (!workdir_ending) return -1;
-
- /* If the line ending is '\n', just copy the input */
- if (!strcmp(workdir_ending, "\n"))
- return git_buf_puts(dest, git_buf_cstr(source));
+ if (!workdir_ending)
+ return -1;
+ if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */
+ return -1;
- return convert_line_endings(dest, source, workdir_ending);
+ /* for now, only lf->crlf conversion is supported here */
+ assert(!strcmp("\r\n", workdir_ending));
+ return git_buf_text_lf_to_crlf(dest, source);
}
static int find_and_add_filter(
@@ -266,6 +262,7 @@ static int find_and_add_filter(
{
struct crlf_attrs ca;
struct crlf_filter *filter;
+ size_t pathlen;
int error;
/* Load gitattributes for the path */
@@ -293,22 +290,27 @@ static int find_and_add_filter(
/* If we're good, we create a new filter object and push it
* into the filters array */
- filter = git__malloc(sizeof(struct crlf_filter));
+ pathlen = strlen(path);
+ filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1);
GITERR_CHECK_ALLOC(filter);
filter->f.apply = apply;
filter->f.do_free = NULL;
memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs));
+ filter->repo = repo;
+ memcpy(filter->path, path, pathlen + 1);
return git_vector_insert(filters, filter);
}
-int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
+int git_filter_add__crlf_to_odb(
+ git_vector *filters, git_repository *repo, const char *path)
{
return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);
}
-int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path)
+int git_filter_add__crlf_to_workdir(
+ git_vector *filters, git_repository *repo, const char *path)
{
return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
}
diff --git a/src/diff.c b/src/diff.c
index 0861b13eb..7152683e7 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -12,6 +12,9 @@
#include "filter.h"
#include "pathspec.h"
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
+
static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
git_delta_t status,
@@ -29,7 +32,7 @@ static git_diff_delta *diff_delta__alloc(
delta->new_file.path = delta->old_file.path;
- if (diff->opts.flags & GIT_DIFF_REVERSE) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
switch (status) {
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
@@ -63,17 +66,22 @@ static int diff_delta__from_one(
int notify_res;
if (status == GIT_DELTA_IGNORED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
return 0;
if (status == GIT_DELTA_UNTRACKED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
+ return 0;
+
+ if (entry->mode == GIT_FILEMODE_COMMIT &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
return 0;
if (!git_pathspec_match_path(
&diff->pathspec, entry->path,
- (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
- (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec))
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
+ &matched_pathspec))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
@@ -124,10 +132,15 @@ static int diff_delta__from_two(
int notify_res;
if (status == GIT_DELTA_UNMODIFIED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 0;
- if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
+ if (old_entry->mode == GIT_FILEMODE_COMMIT &&
+ new_entry->mode == GIT_FILEMODE_COMMIT &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
+ return 0;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry;
old_entry = new_entry;
@@ -149,7 +162,7 @@ static int diff_delta__from_two(
delta->new_file.mode = new_mode;
if (new_oid) {
- if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
git_oid_cpy(&delta->old_file.oid, new_oid);
else
git_oid_cpy(&delta->new_file.oid, new_oid);
@@ -291,8 +304,11 @@ static git_diff_list *git_diff_list_alloc(
* - diff.noprefix
*/
- if (opts == NULL)
+ if (opts == NULL) {
+ /* Make sure we default to 3 lines */
+ diff->opts.context_lines = 3;
return diff;
+ }
memcpy(&diff->opts, opts, sizeof(git_diff_options));
@@ -313,14 +329,14 @@ static git_diff_list *git_diff_list_alloc(
if (!diff->opts.old_prefix || !diff->opts.new_prefix)
goto fail;
- if (diff->opts.flags & GIT_DIFF_REVERSE) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
const char *swap = diff->opts.old_prefix;
diff->opts.old_prefix = diff->opts.new_prefix;
diff->opts.new_prefix = swap;
}
/* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
- if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
return diff;
@@ -449,8 +465,9 @@ static int maybe_modified(
if (!git_pathspec_match_path(
&diff->pathspec, oitem->path,
- (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
- (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec))
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
+ &matched_pathspec))
return 0;
/* on platforms with no symlinks, preserve mode of existing symlinks */
@@ -475,7 +492,7 @@ static int maybe_modified(
/* if basic type of file changed, then split into delete and add */
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
- if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE) != 0)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
status = GIT_DELTA_TYPECHANGE;
else {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
@@ -509,13 +526,17 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED;
else if (S_ISGITLINK(nmode)) {
+ int err;
git_submodule *sub;
- if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
status = GIT_DELTA_UNMODIFIED;
- else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
- return -1;
- else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
+ else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {
+ if (err == GIT_EEXISTS)
+ status = GIT_DELTA_UNMODIFIED;
+ else
+ return err;
+ } else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
status = GIT_DELTA_UNMODIFIED;
else {
unsigned int sm_status = 0;
@@ -536,6 +557,11 @@ static int maybe_modified(
}
}
+ /* if mode is GITLINK and submodules are ignored, then skip */
+ else if (S_ISGITLINK(nmode) &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
+ status = GIT_DELTA_UNMODIFIED;
+
/* 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
*/
@@ -546,7 +572,13 @@ static int maybe_modified(
return -1;
use_noid = &noid;
}
- if (omode == nmode && git_oid_equal(&oitem->oid, use_noid))
+
+ /* if oid matches, then mark unmodified (except submodules, where
+ * the filesystem content may be modified even if the oid still
+ * matches between the index and the workdir HEAD)
+ */
+ if (omode == nmode && !S_ISGITLINK(omode) &&
+ git_oid_equal(&oitem->oid, use_noid))
status = GIT_DELTA_UNMODIFIED;
}
@@ -619,19 +651,14 @@ int git_diff__from_iterators(
if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
goto fail;
- if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
- /* If either iterator does not have ignore_case set, then we will
- * spool its data, sort it icase, and use that for the merge join
- * with the other iterator which was icase sorted. This call is
- * a no-op on an iterator that already matches "ignore_case".
- */
- if (git_iterator_spoolandsort_push(old_iter, true) < 0 ||
- git_iterator_spoolandsort_push(new_iter, true) < 0)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
+ if (git_iterator_set_ignore_case(old_iter, true) < 0 ||
+ git_iterator_set_ignore_case(new_iter, true) < 0)
goto fail;
}
- if (git_iterator_current(old_iter, &oitem) < 0 ||
- git_iterator_current(new_iter, &nitem) < 0)
+ if (git_iterator_current(&oitem, old_iter) < 0 ||
+ git_iterator_current(&nitem, new_iter) < 0)
goto fail;
/* run iterators building diffs */
@@ -646,7 +673,7 @@ int git_diff__from_iterators(
/* if we are generating TYPECHANGE records then check for that
* instead of just generating a DELETE record
*/
- if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
entry_is_prefixed(diff, nitem, oitem))
{
/* this entry has become a tree! convert to TYPECHANGE */
@@ -661,14 +688,14 @@ int git_diff__from_iterators(
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
*/
if (S_ISDIR(nitem->mode) &&
- !(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS))
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
{
- if (git_iterator_advance(new_iter, &nitem) < 0)
+ if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
}
}
- if (git_iterator_advance(old_iter, &oitem) < 0)
+ if (git_iterator_advance(&oitem, old_iter) < 0)
goto fail;
}
@@ -689,30 +716,48 @@ int git_diff__from_iterators(
* it or if the user requested the contents of untracked
* directories and it is not under an ignored directory.
*/
- bool recurse_untracked =
+ bool recurse_into_dir =
(delta_type == GIT_DELTA_UNTRACKED &&
- (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
+ (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
/* do not advance into directories that contain a .git file */
- if (!contains_oitem && recurse_untracked) {
+ if (!contains_oitem && recurse_into_dir) {
git_buf *full = NULL;
- if (git_iterator_current_workdir_path(new_iter, &full) < 0)
+ if (git_iterator_current_workdir_path(&full, new_iter) < 0)
goto fail;
if (git_path_contains_dir(full, DOT_GIT))
- recurse_untracked = false;
+ recurse_into_dir = false;
}
- if (contains_oitem || recurse_untracked) {
- /* if this directory is ignored, remember it as the
- * "ignore_prefix" for processing contained items
- */
- if (delta_type == GIT_DELTA_UNTRACKED &&
- git_iterator_current_is_ignored(new_iter))
- git_buf_sets(&ignore_prefix, nitem->path);
+ /* if directory is ignored, remember ignore_prefix */
+ if ((contains_oitem || recurse_into_dir) &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ git_iterator_current_is_ignored(new_iter))
+ {
+ git_buf_sets(&ignore_prefix, nitem->path);
+ delta_type = GIT_DELTA_IGNORED;
- if (git_iterator_advance_into_directory(new_iter, &nitem) < 0)
- goto fail;
+ /* skip recursion if we've just learned this is ignored */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
+ recurse_into_dir = false;
+ }
+
+ if (contains_oitem || recurse_into_dir) {
+ /* advance into directory */
+ error = git_iterator_advance_into(&nitem, new_iter);
+
+ /* if directory is empty, can't advance into it, so skip */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = git_iterator_advance(&nitem, new_iter);
+ git_buf_clear(&ignore_prefix);
+ }
+
+ if (error < 0)
+ goto fail;
continue;
}
}
@@ -732,8 +777,9 @@ int git_diff__from_iterators(
* checked before container directory exclusions are used to
* skip the file.
*/
- else if (delta_type == GIT_DELTA_IGNORED) {
- if (git_iterator_advance(new_iter, &nitem) < 0)
+ else if (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {
+ if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
continue; /* ignored parent directory, so skip completely */
}
@@ -751,7 +797,7 @@ int git_diff__from_iterators(
* instead of just generating an ADDED/UNTRACKED record
*/
if (delta_type != GIT_DELTA_IGNORED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
contains_oitem)
{
/* this entry was prefixed with a tree - make TYPECHANGE */
@@ -762,7 +808,7 @@ int git_diff__from_iterators(
}
}
- if (git_iterator_advance(new_iter, &nitem) < 0)
+ if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
}
@@ -772,11 +818,10 @@ int git_diff__from_iterators(
else {
assert(oitem && nitem && cmp == 0);
- if (maybe_modified(
- old_iter, oitem, new_iter, nitem, diff) < 0 ||
- git_iterator_advance(old_iter, &oitem) < 0 ||
- git_iterator_advance(new_iter, &nitem) < 0)
- goto fail;
+ if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
+ git_iterator_advance(&oitem, old_iter) < 0 ||
+ git_iterator_advance(&nitem, new_iter) < 0)
+ goto fail;
}
}
@@ -797,7 +842,7 @@ fail:
git_iterator *a = NULL, *b = NULL; \
char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
- if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
error = git_diff__from_iterators(diff, repo, a, b, opts); \
git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
} while (0)
@@ -814,8 +859,8 @@ int git_diff_tree_to_tree(
assert(diff && repo);
DIFF_FROM_ITERATORS(
- git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx),
- git_iterator_for_tree_range(&b, new_tree, 0, pfx, pfx)
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_tree(&b, new_tree, 0, pfx, pfx)
);
return error;
@@ -836,8 +881,8 @@ int git_diff_tree_to_index(
return error;
DIFF_FROM_ITERATORS(
- git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx),
- git_iterator_for_index_range(&b, index, 0, pfx, pfx)
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_index(&b, index, 0, pfx, pfx)
);
return error;
@@ -857,8 +902,9 @@ int git_diff_index_to_workdir(
return error;
DIFF_FROM_ITERATORS(
- git_iterator_for_index_range(&a, index, 0, pfx, pfx),
- git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx)
+ git_iterator_for_index(&a, index, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
);
return error;
@@ -876,8 +922,9 @@ int git_diff_tree_to_workdir(
assert(diff && repo);
DIFF_FROM_ITERATORS(
- git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx),
- git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx)
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
);
return error;
diff --git a/src/diff_output.c b/src/diff_output.c
index 209a6e017..e8dd5b317 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -12,6 +12,7 @@
#include <ctype.h>
#include "fileops.h"
#include "filter.h"
+#include "buf_text.h"
static int read_next_int(const char **str, int *value)
{
@@ -202,7 +203,7 @@ static void setup_xdiff_options(
memset(param, 0, sizeof(xpparam_t));
cfg->ctxlen =
- (!opts || !opts->context_lines) ? 3 : opts->context_lines;
+ (!opts) ? 3 : opts->context_lines;
cfg->interhunkctxlen =
(!opts) ? 0 : opts->interhunk_lines;
@@ -299,7 +300,12 @@ static int get_workdir_sm_content(
if ((error = git_submodule_lookup(&sm, ctxt->repo, file->path)) < 0 ||
(error = git_submodule_status(&sm_status, sm)) < 0)
+ {
+ /* GIT_EEXISTS means a "submodule" that has not been git added */
+ if (error == GIT_EEXISTS)
+ error = 0;
return error;
+ }
/* update OID if we didn't have it previously */
if ((file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
@@ -330,6 +336,33 @@ static int get_workdir_sm_content(
return 0;
}
+static int get_filtered(
+ git_map *map, git_file fd, git_diff_file *file, git_vector *filters)
+{
+ int error;
+ git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
+
+ if ((error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) < 0)
+ return error;
+
+ if (!filters->length)
+ git_buf_swap(&filtered, &raw);
+ else
+ error = git_filters_apply(&filtered, &raw, filters);
+
+ if (!error) {
+ map->len = git_buf_len(&filtered);
+ map->data = git_buf_detach(&filtered);
+
+ file->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ }
+
+ git_buf_free(&raw);
+ git_buf_free(&filtered);
+
+ return error;
+}
+
static int get_workdir_content(
diff_context *ctxt,
git_diff_delta *delta,
@@ -363,7 +396,7 @@ static int get_workdir_content(
map->data = git__malloc(alloc_len);
GITERR_CHECK_ALLOC(map->data);
- read_len = p_readlink(path.ptr, map->data, (int)alloc_len);
+ read_len = p_readlink(path.ptr, map->data, alloc_len);
if (read_len < 0) {
giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path);
error = -1;
@@ -381,8 +414,8 @@ static int get_workdir_content(
goto cleanup;
}
- if (!file->size)
- file->size = git_futils_filesize(fd);
+ if (!file->size && !(file->size = git_futils_filesize(fd)))
+ goto close_and_cleanup;
if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
(delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
@@ -394,26 +427,12 @@ static int get_workdir_content(
goto close_and_cleanup;
if (error == 0) { /* note: git_filters_load returns filter count */
- if (!file->size)
- goto close_and_cleanup;
-
error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size);
- file->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
- } else {
- git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
-
- if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) &&
- !(error = git_filters_apply(&filtered, &raw, &filters)))
- {
- map->len = git_buf_len(&filtered);
- map->data = git_buf_detach(&filtered);
-
- file->flags |= GIT_DIFF_FLAG__FREE_DATA;
- }
-
- git_buf_free(&raw);
- git_buf_free(&filtered);
+ if (!error)
+ file->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
}
+ if (error != 0)
+ error = get_filtered(map, fd, file, &filters);
close_and_cleanup:
git_filters_free(&filters);
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 958d2bfec..efcb19d95 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -174,16 +174,34 @@ static int find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p)
{
git_hashsig_option_t opt = (git_hashsig_option_t)p;
+ int error = 0;
+
GIT_UNUSED(f);
- return git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
+ error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
}
static int find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p)
{
git_hashsig_option_t opt = (git_hashsig_option_t)p;
+ int error = 0;
+
GIT_UNUSED(f);
- return git_hashsig_create((git_hashsig **)out, buf, len, opt);
+ error = git_hashsig_create((git_hashsig **)out, buf, len, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
}
static void find_similar__hashsig_free(void *sig, void *payload)
@@ -376,15 +394,20 @@ static int similarity_calc(
git_buf_free(&path);
} else { /* compute hashsig from blob buffer */
git_blob *blob = NULL;
+ git_off_t blobsize;
/* TODO: add max size threshold a la diff? */
if ((error = git_blob_lookup(&blob, diff->repo, &file->oid)) < 0)
return error;
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize)) /* ? what to do ? */
+ blobsize = (size_t)-1;
+
error = opts->metric->buffer_signature(
&cache[file_idx], file, git_blob_rawcontent(blob),
- git_blob_rawsize(blob), opts->metric->payload);
+ (size_t)blobsize, opts->metric->payload);
git_blob_free(blob);
}
@@ -414,6 +437,10 @@ static int similarity_measure(
return -1;
if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0)
return -1;
+
+ /* some metrics may not wish to process this file (too big / too small) */
+ if (!cache[a_idx] || !cache[b_idx])
+ return 0;
/* compare signatures */
if (opts->metric->similarity(
diff --git a/src/errors.c b/src/errors.c
index c5f0b3b59..e2629f69e 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -103,6 +103,7 @@ int giterr_set_regex(const regex_t *regex, int error_code)
void giterr_clear(void)
{
+ set_error(0, NULL);
GIT_GLOBAL->last_error = NULL;
errno = 0;
diff --git a/src/fetchhead.c b/src/fetchhead.c
index 6e8fb9fac..4dcebb857 100644
--- a/src/fetchhead.c
+++ b/src/fetchhead.c
@@ -16,7 +16,6 @@
#include "refs.h"
#include "repository.h"
-
int git_fetchhead_ref_cmp(const void *a, const void *b)
{
const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
diff --git a/src/fileops.c b/src/fileops.c
index c1824e812..d6244711f 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -529,7 +529,7 @@ int git_futils_cleanupdir_r(const char *path)
git_buf fullpath = GIT_BUF_INIT;
futils__rmdir_data data;
- if ((error = git_buf_put(&fullpath, path, strlen(path)) < 0))
+ if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0)
goto clean_up;
data.base = "";
@@ -558,75 +558,186 @@ clean_up:
return error;
}
-int git_futils_find_system_file(git_buf *path, const char *filename)
+
+static int git_futils_guess_system_dirs(git_buf *out)
{
#ifdef GIT_WIN32
- // try to find git.exe/git.cmd on path
- if (!win32_find_system_file_using_path(path, filename))
- return 0;
+ return git_win32__find_system_dirs(out);
+#else
+ return git_buf_sets(out, "/etc");
+#endif
+}
- // try to find msysgit installation path using registry
- if (!win32_find_system_file_using_registry(path, filename))
- return 0;
+static int git_futils_guess_global_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_global_dirs(out);
#else
- if (git_buf_joinpath(path, "/etc", filename) < 0)
- return -1;
+ return git_buf_sets(out, getenv("HOME"));
+#endif
+}
- if (git_path_exists(path->ptr) == true)
- return 0;
+static int git_futils_guess_xdg_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_xdg_dirs(out);
+#else
+ const char *env = NULL;
+
+ if ((env = getenv("XDG_CONFIG_HOME")) != NULL)
+ return git_buf_joinpath(out, env, "git");
+ else if ((env = getenv("HOME")) != NULL)
+ return git_buf_joinpath(out, env, ".config/git");
+
+ git_buf_clear(out);
+ return 0;
#endif
+}
- git_buf_clear(path);
- giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
- return GIT_ENOTFOUND;
+typedef int (*git_futils_dirs_guess_cb)(git_buf *out);
+
+static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] =
+ { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };
+
+static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {
+ git_futils_guess_system_dirs,
+ git_futils_guess_global_dirs,
+ git_futils_guess_xdg_dirs,
+};
+
+static int git_futils_check_selector(git_futils_dir_t which)
+{
+ if (which < GIT_FUTILS_DIR__MAX)
+ return 0;
+ giterr_set(GITERR_INVALID, "config directory selector out of range");
+ return -1;
}
-int git_futils_find_global_file(git_buf *path, const char *filename)
+int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which)
{
-#ifdef GIT_WIN32
- struct win32_path root;
- static const wchar_t *tmpls[4] = {
- L"%HOME%\\",
- L"%HOMEDRIVE%%HOMEPATH%\\",
- L"%USERPROFILE%\\",
- NULL,
- };
- const wchar_t **tmpl;
-
- for (tmpl = tmpls; *tmpl != NULL; tmpl++) {
- /* try to expand environment variable, skipping if not set */
- if (win32_expand_path(&root, *tmpl) != 0 || root.path[0] == L'%')
+ assert(out);
+
+ *out = NULL;
+
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
+
+ if (!git_buf_len(&git_futils__dirs[which]))
+ GITERR_CHECK_ERROR(
+ git_futils__dir_guess[which](&git_futils__dirs[which]));
+
+ *out = &git_futils__dirs[which];
+ return 0;
+}
+
+int git_futils_dirs_get_str(char *out, size_t outlen, git_futils_dir_t which)
+{
+ const git_buf *path = NULL;
+
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
+ GITERR_CHECK_ERROR(git_futils_dirs_get(&path, which));
+
+ if (!out || path->size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ return GIT_EBUFS;
+ }
+
+ git_buf_copy_cstr(out, outlen, path);
+ return 0;
+}
+
+#define PATH_MAGIC "$PATH"
+
+int git_futils_dirs_set(git_futils_dir_t which, const char *search_path)
+{
+ const char *expand_path = NULL;
+ git_buf merge = GIT_BUF_INIT;
+
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
+
+ if (search_path != NULL)
+ expand_path = strstr(search_path, PATH_MAGIC);
+
+ /* init with default if not yet done and needed (ignoring error) */
+ if ((!search_path || expand_path) &&
+ !git_buf_len(&git_futils__dirs[which]))
+ git_futils__dir_guess[which](&git_futils__dirs[which]);
+
+ /* if $PATH is not referenced, then just set the path */
+ if (!expand_path)
+ return git_buf_sets(&git_futils__dirs[which], search_path);
+
+ /* otherwise set to join(before $PATH, old value, after $PATH) */
+ if (expand_path > search_path)
+ git_buf_set(&merge, search_path, expand_path - search_path);
+
+ if (git_buf_len(&git_futils__dirs[which]))
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR,
+ merge.ptr, git_futils__dirs[which].ptr);
+
+ expand_path += strlen(PATH_MAGIC);
+ if (*expand_path)
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path);
+
+ git_buf_swap(&git_futils__dirs[which], &merge);
+ git_buf_free(&merge);
+
+ return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0;
+}
+
+void git_futils_dirs_free(void)
+{
+ int i;
+ for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i)
+ git_buf_free(&git_futils__dirs[i]);
+}
+
+static int git_futils_find_in_dirlist(
+ git_buf *path, const char *name, git_futils_dir_t which, const char *label)
+{
+ size_t len;
+ const char *scan, *next = NULL;
+ const git_buf *syspath;
+
+ GITERR_CHECK_ERROR(git_futils_dirs_get(&syspath, which));
+
+ 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 */;
+
+ len = next ? (size_t)(next++ - scan) : strlen(scan);
+ if (!len)
continue;
- /* try to look up file under path */
- if (!win32_find_file(path, &root, filename))
+ GITERR_CHECK_ERROR(git_buf_set(path, scan, len));
+ GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
+
+ if (git_path_exists(path->ptr))
return 0;
}
- giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename);
git_buf_clear(path);
-
+ giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);
return GIT_ENOTFOUND;
-#else
- const char *home = getenv("HOME");
-
- if (home == NULL) {
- giterr_set(GITERR_OS, "Global file lookup failed. "
- "Cannot locate the user's home directory");
- return GIT_ENOTFOUND;
- }
+}
- if (git_buf_joinpath(path, home, filename) < 0)
- return -1;
+int git_futils_find_system_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_SYSTEM, "system");
+}
- if (git_path_exists(path->ptr) == false) {
- giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename);
- git_buf_clear(path);
- return GIT_ENOTFOUND;
- }
+int git_futils_find_global_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_GLOBAL, "global");
+}
- return 0;
-#endif
+int git_futils_find_xdg_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_XDG, "global/xdg");
}
int git_futils_fake_symlink(const char *old, const char *new)
diff --git a/src/fileops.h b/src/fileops.h
index 7ba99d3d9..627a6923d 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -276,25 +276,72 @@ extern void git_futils_mmap_free(git_map *map);
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
- * @return
- * - 0 if found;
- * - GIT_ENOTFOUND if not found;
- * - -1 on an unspecified OS related error.
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/
extern int git_futils_find_global_file(git_buf *path, const char *filename);
/**
+ * Find an "XDG" file (i.e. one in user's XDG config path).
+ *
+ * @param pathbuf buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_futils_find_xdg_file(git_buf *path, const char *filename);
+
+/**
* Find a "system" file (i.e. one shared for all users of the system).
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
- * @return
- * - 0 if found;
- * - GIT_ENOTFOUND if not found;
- * - -1 on an unspecified OS related error.
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/
extern int git_futils_find_system_file(git_buf *path, const char *filename);
+typedef enum {
+ GIT_FUTILS_DIR_SYSTEM = 0,
+ GIT_FUTILS_DIR_GLOBAL = 1,
+ GIT_FUTILS_DIR_XDG = 2,
+ GIT_FUTILS_DIR__MAX = 3,
+} git_futils_dir_t;
+
+/**
+ * Get the search path for global/system/xdg files
+ *
+ * @param out pointer to git_buf containing search path
+ * @param which which list of paths to return
+ * @return 0 on success, <0 on failure
+ */
+extern int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which);
+
+/**
+ * Get search path into a preallocated buffer
+ *
+ * @param out String buffer to write into
+ * @param outlen Size of string buffer
+ * @param which Which search path to return
+ * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure
+ */
+
+extern int git_futils_dirs_get_str(
+ char *out, size_t outlen, git_futils_dir_t which);
+
+/**
+ * Set search paths for global/system/xdg files
+ *
+ * The first occurrence of the magic string "$PATH" in the new value will
+ * be replaced with the old value of the search path.
+ *
+ * @param which Which search path to modify
+ * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR)
+ * @return 0 on success, <0 on failure (allocation error)
+ */
+extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths);
+
+/**
+ * Release / reset all search paths
+ */
+extern void git_futils_dirs_free(void);
/**
* Create a "fake" symlink (text file containing the target path).
diff --git a/src/filter.c b/src/filter.c
index f0bfb7980..9f749dcbd 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -48,7 +48,8 @@ void git_filters_free(git_vector *filters)
int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
{
- unsigned int i, src;
+ size_t i;
+ unsigned int src;
git_buf *dbuffer[2];
dbuffer[0] = source;
diff --git a/src/filter.h b/src/filter.h
index 0ca71656b..42a44ebdb 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -9,7 +9,6 @@
#include "common.h"
#include "buffer.h"
-#include "buf_text.h"
#include "git2/odb.h"
#include "git2/repository.h"
diff --git a/src/global.c b/src/global.c
index 4d37fa1d2..b7fd8e257 100644
--- a/src/global.c
+++ b/src/global.c
@@ -7,7 +7,8 @@
#include "common.h"
#include "global.h"
#include "hash.h"
-#include "git2/threads.h"
+#include "fileops.h"
+#include "git2/threads.h"
#include "thread-utils.h"
@@ -82,6 +83,7 @@ void git_threads_shutdown(void)
/* Shut down any subsystems that have global state */
git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
@@ -139,6 +141,7 @@ void git_threads_shutdown(void)
/* Shut down any subsystems that have global state */
git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
@@ -171,7 +174,9 @@ int git_threads_init(void)
void git_threads_shutdown(void)
{
- /* noop */
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
diff --git a/src/graph.c b/src/graph.c
index cb1727924..277f588ca 100644
--- a/src/graph.c
+++ b/src/graph.c
@@ -147,25 +147,25 @@ on_error:
}
int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
- const git_oid *one, const git_oid *two)
+ const git_oid *local, const git_oid *upstream)
{
git_revwalk *walk;
- git_commit_list_node *commit1, *commit2;
+ git_commit_list_node *commit_u, *commit_l;
if (git_revwalk_new(&walk, repo) < 0)
return -1;
- commit2 = git_revwalk__commit_lookup(walk, two);
- if (commit2 == NULL)
+ commit_u = git_revwalk__commit_lookup(walk, upstream);
+ if (commit_u == NULL)
goto on_error;
- commit1 = git_revwalk__commit_lookup(walk, one);
- if (commit1 == NULL)
+ commit_l = git_revwalk__commit_lookup(walk, local);
+ if (commit_l == NULL)
goto on_error;
- if (mark_parents(walk, commit1, commit2) < 0)
+ if (mark_parents(walk, commit_l, commit_u) < 0)
goto on_error;
- if (ahead_behind(commit1, commit2, ahead, behind) < 0)
+ if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)
goto on_error;
git_revwalk_free(walk);
diff --git a/src/hashsig.c b/src/hashsig.c
index e9c5164a4..3a75aaaed 100644
--- a/src/hashsig.c
+++ b/src/hashsig.c
@@ -6,6 +6,7 @@
*/
#include "hashsig.h"
#include "fileops.h"
+#include "util.h"
typedef uint32_t hashsig_t;
typedef uint64_t hashsig_state;
@@ -19,7 +20,7 @@ typedef uint64_t hashsig_state;
#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
-typedef int (GIT_STDLIB_CALL *hashsig_cmp)(const void *a, const void *b);
+typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
typedef struct {
int size, asize;
@@ -53,15 +54,17 @@ static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
h->cmp = cmp;
}
-static int GIT_STDLIB_CALL hashsig_cmp_max(const void *a, const void *b)
+static int hashsig_cmp_max(const void *a, const void *b, void *payload)
{
hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
return (av < bv) ? -1 : (av > bv) ? 1 : 0;
}
-static int GIT_STDLIB_CALL hashsig_cmp_min(const void *a, const void *b)
+static int hashsig_cmp_min(const void *a, const void *b, void *payload)
{
hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
return (av > bv) ? -1 : (av < bv) ? 1 : 0;
}
@@ -69,7 +72,7 @@ static void hashsig_heap_up(hashsig_heap *h, int el)
{
int parent_el = HEAP_PARENT_OF(el);
- while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el]) > 0) {
+ while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {
hashsig_t t = h->values[el];
h->values[el] = h->values[parent_el];
h->values[parent_el] = t;
@@ -92,10 +95,10 @@ static void hashsig_heap_down(hashsig_heap *h, int el)
lv = h->values[lel];
rv = h->values[rel];
- if (h->cmp(&v, &lv) < 0 && h->cmp(&v, &rv) < 0)
+ if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)
break;
- swapel = (h->cmp(&lv, &rv) < 0) ? lel : rel;
+ swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;
h->values[el] = h->values[swapel];
h->values[swapel] = v;
@@ -107,13 +110,13 @@ static void hashsig_heap_down(hashsig_heap *h, int el)
static void hashsig_heap_sort(hashsig_heap *h)
{
/* only need to do this at the end for signature comparison */
- qsort(h->values, h->size, sizeof(hashsig_t), h->cmp);
+ git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);
}
static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
{
/* if heap is full, pop top if new element should replace it */
- if (h->size == h->asize && h->cmp(&val, &h->values[0]) > 0) {
+ if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) {
h->size--;
h->values[0] = h->values[h->size];
hashsig_heap_down(h, 0);
@@ -343,7 +346,7 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
/* hash heaps are sorted - just look for overlap vs total */
for (i = 0, j = 0; i < a->size && j < b->size; ) {
- cmp = a->cmp(&a->values[i], &b->values[j]);
+ cmp = a->cmp(&a->values[i], &b->values[j], NULL);
if (cmp < 0)
++i;
diff --git a/src/ignore.c b/src/ignore.c
index 5edc5b65b..17779522c 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -1,11 +1,10 @@
#include "git2/ignore.h"
#include "ignore.h"
+#include "attr.h"
#include "path.h"
#include "config.h"
#define GIT_IGNORE_INTERNAL "[internal]exclude"
-#define GIT_IGNORE_FILE_INREPO "info/exclude"
-#define GIT_IGNORE_FILE ".gitignore"
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
diff --git a/src/ignore.h b/src/ignore.h
index 5a15afcca..5af8e8e7d 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -9,6 +9,11 @@
#include "repository.h"
#include "vector.h"
+#include "attr_file.h"
+
+#define GIT_IGNORE_FILE ".gitignore"
+#define GIT_IGNORE_FILE_INREPO "info/exclude"
+#define GIT_IGNORE_FILE_XDG "ignore"
/* The git_ignores structure maintains three sets of ignores:
* - internal ignores
diff --git a/src/index.c b/src/index.c
index 4deafd77f..6290ec4e8 100644
--- a/src/index.c
+++ b/src/index.c
@@ -317,7 +317,7 @@ void git_index_free(git_index *index)
void git_index_clear(git_index *index)
{
- unsigned int i;
+ size_t i;
assert(index);
@@ -786,7 +786,7 @@ int git_index_remove(git_index *index, const char *path, int stage)
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
- error = git_vector_remove(&index->entries, (unsigned int)position);
+ error = git_vector_remove(&index->entries, position);
if (!error)
index_entry_free(entry);
@@ -1129,7 +1129,7 @@ int git_index_reuc_remove(git_index *index, size_t position)
git_vector_sort(&index->reuc);
reuc = git_vector_get(&index->reuc, position);
- error = git_vector_remove(&index->reuc, (unsigned int)position);
+ error = git_vector_remove(&index->reuc, position);
if (!error)
index_entry_reuc_free(reuc);
@@ -1679,54 +1679,3 @@ git_repository *git_index_owner(const git_index *index)
{
return INDEX_OWNER(index);
}
-
-int git_index_read_tree_match(
- git_index *index, git_tree *tree, git_strarray *strspec)
-{
-#if 0
- git_iterator *iter = NULL;
- const git_index_entry *entry;
- char *pfx = NULL;
- git_vector pathspec = GIT_VECTOR_INIT;
- git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
-#endif
-
- if (!git_pathspec_is_interesting(strspec))
- return git_index_read_tree(index, tree);
-
- return git_index_read_tree(index, tree);
-
-#if 0
- /* The following loads the matches into the index, but doesn't
- * erase obsoleted entries (e.g. you load a blob at "a/b" which
- * should obsolete a blob at "a/b/c/d" since b is no longer a tree)
- */
-
- if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0)
- return -1;
-
- pfx = git_pathspec_prefix(strspec);
-
- if ((error = git_iterator_for_tree_range(&iter, tree, pfx, pfx)) < 0 ||
- (error = git_iterator_current(iter, &entry)) < 0)
- goto cleanup;
-
- while (entry != NULL) {
- if (git_pathspec_match_path(
- &pathspec, entry->path, false, false, NULL) &&
- (error = git_index_add(index, entry)) < 0)
- goto cleanup;
-
- if ((error = git_iterator_advance(iter, &entry)) < 0)
- goto cleanup;
- }
-
-cleanup:
- git_iterator_free(iter);
- git_pathspec_free(&pathspec);
- git_pool_clear(&pathpool);
- git__free(pfx);
-
- return error;
-#endif
-}
diff --git a/src/index.h b/src/index.h
index 2beaa6375..9498907b6 100644
--- a/src/index.h
+++ b/src/index.h
@@ -50,7 +50,4 @@ extern int git_index_entry__cmp_icase(const void *a, const void *b);
extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
-extern int git_index_read_tree_match(
- git_index *index, git_tree *tree, git_strarray *strspec);
-
#endif
diff --git a/src/indexer.c b/src/indexer.c
index c7e142baf..2cfbd3a5a 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -415,6 +415,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
}
if (!idx->parsed_header) {
+ unsigned int total_objects;
+
if ((unsigned)idx->pack->mwf.size < sizeof(hdr))
return 0;
@@ -427,20 +429,24 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
/* for now, limit to 2^32 objects */
assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));
+ if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects))
+ total_objects = (unsigned int)idx->nr_objects;
+ else
+ total_objects = UINT_MAX;
idx->pack->idx_cache = git_oidmap_alloc();
GITERR_CHECK_ALLOC(idx->pack->idx_cache);
idx->pack->has_cache = 1;
- if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0)
+ if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0)
return -1;
- if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0)
+ if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0)
return -1;
stats->received_objects = 0;
processed = stats->indexed_objects = 0;
- stats->total_objects = (unsigned int)idx->nr_objects;
+ stats->total_objects = total_objects;
do_progress_callback(idx, stats);
}
diff --git a/src/iterator.c b/src/iterator.c
index 8ad639d6b..805a3c987 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -14,26 +14,45 @@
#define ITERATOR_SET_CB(P,NAME_LC) do { \
(P)->cb.current = NAME_LC ## _iterator__current; \
- (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
(P)->cb.advance = NAME_LC ## _iterator__advance; \
+ (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \
(P)->cb.seek = NAME_LC ## _iterator__seek; \
(P)->cb.reset = NAME_LC ## _iterator__reset; \
+ (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
(P)->cb.free = NAME_LC ## _iterator__free; \
} while (0)
-#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \
+#define ITERATOR_CASE_FLAGS \
+ (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)
+
+#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \
(P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \
GITERR_CHECK_ALLOC(P); \
(P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
- (P)->base.cb = &(P)->cb; \
+ (P)->base.cb = &(P)->cb; \
ITERATOR_SET_CB(P,NAME_LC); \
+ (P)->base.repo = (REPO); \
(P)->base.start = start ? git__strdup(start) : NULL; \
(P)->base.end = end ? git__strdup(end) : NULL; \
if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
git__free(P); return -1; } \
(P)->base.prefixcomp = git__prefixcmp; \
+ (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \
+ if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
+ (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
} while (0)
+#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+
+#define iterator__end(I) ((git_iterator *)(I))->end
+#define iterator__past_end(I,PATH) \
+ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
+
+
static int iterator__reset_range(
git_iterator *iter, const char *start, const char *end)
{
@@ -54,7 +73,7 @@ static int iterator__reset_range(
return 0;
}
-static int iterator_update_ignore_case(
+static int iterator__update_ignore_case(
git_iterator *iter,
git_iterator_flag_t flags)
{
@@ -76,42 +95,46 @@ static int iterator_update_ignore_case(
else if (ignore_case == 0)
iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
- iter->prefixcomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ?
+ iter->prefixcomp = iterator__ignore_case(iter) ?
git__prefixcmp_icase : git__prefixcmp;
return error;
}
-static int empty_iterator__no_item(
- git_iterator *iter, const git_index_entry **entry)
+GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
+{
+ if (entry) *entry = NULL;
+}
+
+
+static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
{
- GIT_UNUSED(iter);
- *entry = NULL;
+ GIT_UNUSED(i);
+ iterator__clear_entry(e);
return 0;
}
-static int empty_iterator__at_end(git_iterator *iter)
+static int empty_iterator__seek(git_iterator *i, const char *p)
{
- GIT_UNUSED(iter);
- return 1;
+ GIT_UNUSED(i); GIT_UNUSED(p);
+ return -1;
}
-static int empty_iterator__reset(
- git_iterator *iter, const char *start, const char *end)
+static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)
{
- GIT_UNUSED(iter); GIT_UNUSED(start); GIT_UNUSED(end);
+ GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);
return 0;
}
-static int empty_iterator__seek(git_iterator *iter, const char *prefix)
+static int empty_iterator__at_end(git_iterator *i)
{
- GIT_UNUSED(iter); GIT_UNUSED(prefix);
- return -1;
+ GIT_UNUSED(i);
+ return 1;
}
-static void empty_iterator__free(git_iterator *iter)
+static void empty_iterator__free(git_iterator *i)
{
- GIT_UNUSED(iter);
+ GIT_UNUSED(i);
}
typedef struct {
@@ -119,332 +142,430 @@ typedef struct {
git_iterator_callbacks cb;
} empty_iterator;
-int git_iterator_for_nothing(git_iterator **iter, git_iterator_flag_t flags)
+int git_iterator_for_nothing(
+ git_iterator **iter,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
{
- empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
- GITERR_CHECK_ALLOC(i);
+ empty_iterator *i;
- i->base.type = GIT_ITERATOR_TYPE_EMPTY;
- i->base.cb = &i->cb;
- i->base.flags = flags;
- i->cb.current = empty_iterator__no_item;
- i->cb.at_end = empty_iterator__at_end;
- i->cb.advance = empty_iterator__no_item;
- i->cb.seek = empty_iterator__seek;
- i->cb.reset = empty_iterator__reset;
- i->cb.free = empty_iterator__free;
+#define empty_iterator__current empty_iterator__noop
+#define empty_iterator__advance empty_iterator__noop
+#define empty_iterator__advance_into empty_iterator__noop
- *iter = (git_iterator *)i;
+ ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+
+ *iter = (git_iterator *)i;
return 0;
}
+typedef struct tree_iterator_entry tree_iterator_entry;
+struct tree_iterator_entry {
+ tree_iterator_entry *parent;
+ const git_tree_entry *te;
+ git_tree *tree;
+};
+
typedef struct tree_iterator_frame tree_iterator_frame;
struct tree_iterator_frame {
- tree_iterator_frame *next, *prev;
- git_tree *tree;
- char *start;
+ tree_iterator_frame *up, *down;
+
+ size_t n_entries; /* items in this frame */
+ size_t current; /* start of currently active range in frame */
+ size_t next; /* start of next range in frame */
+
+ const char *start;
size_t startlen;
- size_t index;
- void **icase_map;
- void *icase_data[GIT_FLEX_ARRAY];
+
+ tree_iterator_entry *entries[GIT_FLEX_ARRAY];
};
typedef struct {
git_iterator base;
git_iterator_callbacks cb;
- tree_iterator_frame *stack, *tail;
+ tree_iterator_frame *head, *root;
+ git_pool pool;
git_index_entry entry;
git_buf path;
+ int path_ambiguities;
bool path_has_filename;
+ int (*strncomp)(const char *a, const char *b, size_t sz);
} tree_iterator;
-GIT_INLINE(const git_tree_entry *)tree_iterator__tree_entry(tree_iterator *ti)
-{
- tree_iterator_frame *tf = ti->stack;
-
- if (tf->index >= git_tree_entrycount(tf->tree))
- return NULL;
-
- return git_tree_entry_byindex(
- tf->tree, tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index);
-}
-
static char *tree_iterator__current_filename(
tree_iterator *ti, const git_tree_entry *te)
{
if (!ti->path_has_filename) {
if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
return NULL;
+
+ if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)
+ return NULL;
+
ti->path_has_filename = true;
}
return ti->path.ptr;
}
-static void tree_iterator__free_frame(tree_iterator_frame *tf)
+static void tree_iterator__rewrite_filename(tree_iterator *ti)
{
- if (!tf)
- return;
+ tree_iterator_entry *scan = ti->head->entries[ti->head->current];
+ ssize_t strpos = ti->path.size;
+ const git_tree_entry *te;
- git_tree_free(tf->tree);
- tf->tree = NULL;
+ if (strpos && ti->path.ptr[strpos - 1] == '/')
+ strpos--;
- git__free(tf);
+ for (; scan && (te = scan->te); scan = scan->parent) {
+ strpos -= te->filename_len;
+ memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);
+ strpos -= 1; /* separator */
+ }
}
-static bool tree_iterator__pop_frame(tree_iterator *ti)
+static int tree_iterator__te_cmp(
+ const git_tree_entry *a,
+ const git_tree_entry *b,
+ int (*compare)(const char *, const char *, size_t))
{
- tree_iterator_frame *tf = ti->stack;
-
- /* don't free the initial tree/frame */
- if (!tf->next)
- return false;
+ return git_path_cmp(
+ a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
+ b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
+ compare);
+}
- ti->stack = tf->next;
- ti->stack->prev = NULL;
+static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)
+{
+ const tree_iterator_entry *ae = a, *be = b;
+ int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);
- tree_iterator__free_frame(tf);
+ if (!cmp) {
+ /* stabilize sort order among equivalent names */
+ if (!ae->parent->te || !be->parent->te)
+ cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);
+ else
+ cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);
+ }
- return true;
+ return cmp;
}
-static int tree_iterator__to_end(tree_iterator *ti)
+static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
{
- while (tree_iterator__pop_frame(ti)) /* pop all */;
- ti->stack->index = git_tree_entrycount(ti->stack->tree);
- return 0;
+ const tree_iterator_frame *tf = key;
+ const git_tree_entry *te = ((tree_iterator_entry *)val)->te;
+
+ return git_path_cmp(
+ tf->start, tf->startlen, false,
+ te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
+ ((tree_iterator *)p)->strncomp);
}
-static int tree_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
{
- tree_iterator *ti = (tree_iterator *)self;
- const git_tree_entry *te = tree_iterator__tree_entry(ti);
+ int error;
+ const git_tree_entry *te, *last = NULL;
- if (entry)
- *entry = NULL;
+ tf->next = tf->current;
- if (te == NULL)
- return 0;
+ for (; tf->next < tf->n_entries; tf->next++, last = te) {
+ te = tf->entries[tf->next]->te;
- ti->entry.mode = te->attr;
- git_oid_cpy(&ti->entry.oid, &te->oid);
+ if (last && tree_iterator__te_cmp(last, te, ti->strncomp))
+ break;
- ti->entry.path = tree_iterator__current_filename(ti, te);
- if (ti->entry.path == NULL)
- return -1;
+ /* load trees for items in [current,next) range */
+ if (git_tree_entry__is_tree(te) &&
+ (error = git_tree_lookup(
+ &tf->entries[tf->next]->tree, ti->base.repo, &te->oid)) < 0)
+ return error;
+ }
- if (ti->base.end && ti->base.prefixcomp(ti->entry.path, ti->base.end) > 0)
- return tree_iterator__to_end(ti);
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities++;
- if (entry)
- *entry = &ti->entry;
+ if (last && !tree_iterator__current_filename(ti, last))
+ return -1;
return 0;
}
-static int tree_iterator__at_end(git_iterator *self)
+GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)
{
- return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
+ return (ti->head->current < ti->head->n_entries &&
+ ti->head->entries[ti->head->current]->tree != NULL);
}
-static int tree_iterator__icase_map_cmp(const void *a, const void *b, void *data)
+static int tree_iterator__push_frame(tree_iterator *ti)
{
- git_tree *tree = data;
- const git_tree_entry *te1 = git_tree_entry_byindex(tree, (size_t)a);
- const git_tree_entry *te2 = git_tree_entry_byindex(tree, (size_t)b);
+ int error = 0;
+ tree_iterator_frame *head = ti->head, *tf = NULL;
+ size_t i, n_entries = 0;
- return te1 ? (te2 ? git_tree_entry_icmp(te1, te2) : 1) : -1;
-}
+ if (head->current >= head->n_entries || !head->entries[head->current]->tree)
+ return 0;
-static int tree_iterator__frame_start_icmp(const void *key, const void *el)
-{
- const tree_iterator_frame *tf = (const tree_iterator_frame *)key;
- const git_tree_entry *te = git_tree_entry_byindex(tf->tree, (size_t)el);
- size_t minlen = min(tf->startlen, te->filename_len);
+ for (i = head->current; i < head->next; ++i)
+ n_entries += git_tree_entrycount(head->entries[i]->tree);
- return git__strncasecmp(tf->start, te->filename, minlen);
-}
+ tf = git__calloc(sizeof(tree_iterator_frame) +
+ n_entries * sizeof(tree_iterator_entry *), 1);
+ GITERR_CHECK_ALLOC(tf);
-static void tree_iterator__frame_seek_start(tree_iterator_frame *tf)
-{
- if (!tf->start)
- tf->index = 0;
- else if (!tf->icase_map)
- tf->index = git_tree__prefix_position(tf->tree, tf->start);
- else {
- if (!git__bsearch(
- tf->icase_map, git_tree_entrycount(tf->tree),
- tf, tree_iterator__frame_start_icmp, &tf->index))
- {
- while (tf->index > 0) {
- /* move back while previous entry is still prefixed */
- if (tree_iterator__frame_start_icmp(
- tf, (const void *)(tf->index - 1)))
- break;
- tf->index--;
- }
+ tf->n_entries = n_entries;
+
+ tf->up = head;
+ head->down = tf;
+ ti->head = tf;
+
+ for (i = head->current, n_entries = 0; i < head->next; ++i) {
+ git_tree *tree = head->entries[i]->tree;
+ size_t j, max_j = git_tree_entrycount(tree);
+
+ for (j = 0; j < max_j; ++j) {
+ tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->parent = head->entries[i];
+ entry->te = git_tree_entry_byindex(tree, j);
+ entry->tree = NULL;
+
+ tf->entries[n_entries++] = entry;
}
}
+
+ /* if ignore_case, sort entries case insensitively */
+ if (iterator__ignore_case(ti))
+ git__tsort_r(
+ (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);
+
+ /* pick tf->current based on "start" (or start at zero) */
+ if (head->startlen > 0) {
+ git__bsearch_r((void **)tf->entries, tf->n_entries, head,
+ tree_iterator__search_cmp, ti, &tf->current);
+
+ while (tf->current &&
+ !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))
+ tf->current--;
+
+ if ((tf->start = strchr(head->start, '/')) != NULL) {
+ tf->start++;
+ tf->startlen = strlen(tf->start);
+ }
+ }
+
+ ti->path_has_filename = false;
+
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
+
+ /* autoexpand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__push_frame(ti);
+
+ return 0;
}
-static tree_iterator_frame *tree_iterator__alloc_frame(
- tree_iterator *ti, git_tree *tree, char *start)
+static bool tree_iterator__move_to_next(
+ tree_iterator *ti, tree_iterator_frame *tf)
{
- size_t i, max_i = git_tree_entrycount(tree);
- tree_iterator_frame *tf =
- git__calloc(1, sizeof(tree_iterator_frame) + max_i * sizeof(void *));
- if (!tf)
- return NULL;
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities--;
- tf->tree = tree;
+ if (!tf->up) { /* at root */
+ tf->current = tf->next;
+ return false;
+ }
- if (start && *start) {
- tf->start = start;
- tf->startlen = strlen(start);
+ for (; tf->current < tf->next; tf->current++) {
+ git_tree_free(tf->entries[tf->current]->tree);
+ tf->entries[tf->current]->tree = NULL;
}
- if (!max_i)
- return tf;
+ return (tf->current < tf->n_entries);
+}
- if ((ti->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0) {
- tf->icase_map = tf->icase_data;
+static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
+{
+ tree_iterator_frame *tf = ti->head;
- for (i = 0; i < max_i; ++i)
- tf->icase_map[i] = (void *)i;
+ if (!tf->up)
+ return false;
- git__tsort_r(
- tf->icase_map, max_i, tree_iterator__icase_map_cmp, tf->tree);
+ ti->head = tf->up;
+ ti->head->down = NULL;
+
+ tree_iterator__move_to_next(ti, tf);
+
+ if (!final) { /* if final, don't bother to clean up */
+ git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries);
+ git_buf_rtruncate_at_char(&ti->path, '/');
}
- tree_iterator__frame_seek_start(tf);
+ git__free(tf);
- return tf;
+ return true;
}
-static int tree_iterator__expand_tree(tree_iterator *ti)
+static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
{
- int error;
- git_tree *subtree;
- const git_tree_entry *te = tree_iterator__tree_entry(ti);
- tree_iterator_frame *tf;
- char *relpath;
+ while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
- while (te != NULL && git_tree_entry__is_tree(te)) {
- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
- return -1;
+ if (!final) {
+ ti->head->current = to_end ? ti->head->n_entries : 0;
+ ti->path_ambiguities = 0;
+ git_buf_clear(&ti->path);
+ }
- /* check that we have not passed the range end */
- if (ti->base.end != NULL &&
- ti->base.prefixcomp(ti->path.ptr, ti->base.end) > 0)
- return tree_iterator__to_end(ti);
+ return 0;
+}
- if ((error = git_tree_lookup(&subtree, ti->base.repo, &te->oid)) < 0)
- return error;
+static int tree_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
+ const git_tree_entry *te;
- relpath = NULL;
+ iterator__clear_entry(entry);
- /* apply range start to new frame if relevant */
- if (ti->stack->start &&
- ti->base.prefixcomp(ti->stack->start, te->filename) == 0)
- {
- if (ti->stack->start[te->filename_len] == '/')
- relpath = ti->stack->start + te->filename_len + 1;
- }
+ if (tf->current >= tf->n_entries)
+ return 0;
+ te = tf->entries[tf->current]->te;
- if ((tf = tree_iterator__alloc_frame(ti, subtree, relpath)) == NULL)
- return -1;
+ ti->entry.mode = te->attr;
+ git_oid_cpy(&ti->entry.oid, &te->oid);
- tf->next = ti->stack;
- ti->stack = tf;
- tf->next->prev = tf;
+ ti->entry.path = tree_iterator__current_filename(ti, te);
+ GITERR_CHECK_ALLOC(ti->entry.path);
- te = tree_iterator__tree_entry(ti);
- }
+ if (ti->path_ambiguities > 0)
+ tree_iterator__rewrite_filename(ti);
+
+ if (iterator__past_end(ti, ti->entry.path))
+ return tree_iterator__pop_all(ti, true, false);
+
+ if (entry)
+ *entry = &ti->entry;
return 0;
}
-static int tree_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+static int tree_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
{
int error = 0;
tree_iterator *ti = (tree_iterator *)self;
- const git_tree_entry *te = NULL;
- if (entry != NULL)
- *entry = NULL;
+ iterator__clear_entry(entry);
- if (ti->path_has_filename) {
- git_buf_rtruncate_at_char(&ti->path, '/');
- ti->path_has_filename = false;
- }
+ if (tree_iterator__at_tree(ti) &&
+ !(error = tree_iterator__push_frame(ti)))
+ error = tree_iterator__current(entry, self);
- while (1) {
- ++ti->stack->index;
+ return error;
+}
- if ((te = tree_iterator__tree_entry(ti)) != NULL)
- break;
+static int tree_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error;
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
+
+ iterator__clear_entry(entry);
+
+ if (tf->current > tf->n_entries)
+ return 0;
- if (!tree_iterator__pop_frame(ti))
- break; /* no frames left to pop */
+ if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
+ tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+ if (ti->path_has_filename) {
git_buf_rtruncate_at_char(&ti->path, '/');
+ ti->path_has_filename = false;
}
- if (te && git_tree_entry__is_tree(te))
- error = tree_iterator__expand_tree(ti);
+ /* scan forward and up, advancing in frame or popping frame when done */
+ while (!tree_iterator__move_to_next(ti, tf) &&
+ tree_iterator__pop_frame(ti, false))
+ tf = ti->head;
- if (!error)
- error = tree_iterator__current(self, entry);
+ /* find next and load trees */
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
- return error;
+ /* deal with include_trees / auto_expand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+
+ return tree_iterator__current(entry, self);
}
static int tree_iterator__seek(git_iterator *self, const char *prefix)
{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
- /* pop stack until matches prefix */
- /* seek item in current frame matching prefix */
- /* push stack which matches prefix */
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
return -1;
}
-static void tree_iterator__free(git_iterator *self)
+static int tree_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
{
tree_iterator *ti = (tree_iterator *)self;
- while (tree_iterator__pop_frame(ti)) /* pop all */;
+ tree_iterator__pop_all(ti, false, false);
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
- tree_iterator__free_frame(ti->stack);
- ti->stack = ti->tail = NULL;
+ return tree_iterator__push_frame(ti); /* re-expand root tree */
+}
- git_buf_free(&ti->path);
+static int tree_iterator__at_end(git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+ return (ti->head->current >= ti->head->n_entries);
}
-static int tree_iterator__reset(
- git_iterator *self, const char *start, const char *end)
+static void tree_iterator__free(git_iterator *self)
{
tree_iterator *ti = (tree_iterator *)self;
- while (tree_iterator__pop_frame(ti)) /* pop all */;
+ tree_iterator__pop_all(ti, true, false);
- if (iterator__reset_range(self, start, end) < 0)
- return -1;
+ git_tree_free(ti->head->entries[0]->tree);
+ git__free(ti->head);
+ git_pool_clear(&ti->pool);
+ git_buf_free(&ti->path);
+}
- /* reset start position */
- tree_iterator__frame_seek_start(ti->stack);
+static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
+{
+ size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);
+ tree_iterator_frame *root = git__calloc(sz, sizeof(char));
+ GITERR_CHECK_ALLOC(root);
- git_buf_clear(&ti->path);
- ti->path_has_filename = false;
+ root->n_entries = 1;
+ root->next = 1;
+ root->start = ti->base.start;
+ root->startlen = root->start ? strlen(root->start) : 0;
+ root->entries[0] = git_pool_mallocz(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(root->entries[0]);
+ root->entries[0]->tree = tree;
+
+ ti->head = ti->root = root;
- return tree_iterator__expand_tree(ti);
+ return 0;
}
-int git_iterator_for_tree_range(
+int git_iterator_for_tree(
git_iterator **iter,
git_tree *tree,
git_iterator_flag_t flags,
@@ -455,21 +576,20 @@ int git_iterator_for_tree_range(
tree_iterator *ti;
if (tree == NULL)
- return git_iterator_for_nothing(iter, flags);
+ return git_iterator_for_nothing(iter, flags, start, end);
if ((error = git_tree__dup(&tree, tree)) < 0)
return error;
- ITERATOR_BASE_INIT(ti, tree, TREE);
-
- ti->base.repo = git_tree_owner(tree);
+ ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
- if ((error = iterator_update_ignore_case((git_iterator *)ti, flags)) < 0)
+ if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)
goto fail;
+ ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp;
- ti->stack = ti->tail = tree_iterator__alloc_frame(ti, tree, ti->base.start);
-
- if ((error = tree_iterator__expand_tree(ti)) < 0)
+ if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 ||
+ (error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
+ (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */
goto fail;
*iter = (git_iterator *)ti;
@@ -486,14 +606,94 @@ typedef struct {
git_iterator_callbacks cb;
git_index *index;
size_t current;
+ /* when not in autoexpand mode, use these to represent "tree" state */
+ git_buf partial;
+ size_t partial_pos;
+ char restore_terminator;
+ git_index_entry tree_entry;
} index_iterator;
+static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
+{
+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
+
+ if (ie != NULL && iterator__past_end(ii, ie->path)) {
+ ii->current = git_index_entrycount(ii->index);
+ ie = NULL;
+ }
+
+ return ie;
+}
+
+static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii)
+{
+ const git_index_entry *ie;
+
+ while ((ie = index_iterator__index_entry(ii)) != NULL &&
+ git_index_entry_stage(ie) != 0)
+ ii->current++;
+
+ return ie;
+}
+
+static void index_iterator__next_prefix_tree(index_iterator *ii)
+{
+ const char *slash;
+
+ if (!iterator__include_trees(ii))
+ return;
+
+ slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');
+
+ if (slash != NULL) {
+ ii->partial_pos = (slash - ii->partial.ptr) + 1;
+ ii->restore_terminator = ii->partial.ptr[ii->partial_pos];
+ ii->partial.ptr[ii->partial_pos] = '\0';
+ } else {
+ ii->partial_pos = ii->partial.size;
+ }
+
+ if (index_iterator__index_entry(ii) == NULL)
+ ii->partial_pos = ii->partial.size;
+}
+
+static int index_iterator__first_prefix_tree(index_iterator *ii)
+{
+ const git_index_entry *ie = index_iterator__skip_conflicts(ii);
+ const char *scan, *prior, *slash;
+
+ if (!ie || !iterator__include_trees(ii))
+ return 0;
+
+ /* find longest common prefix with prior index entry */
+ for (scan = slash = ie->path, prior = ii->partial.ptr;
+ *scan && *scan == *prior; ++scan, ++prior)
+ if (*scan == '/')
+ slash = scan;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = (slash - ie->path) + 1;
+ index_iterator__next_prefix_tree(ii);
+
+ return 0;
+}
+
+#define index_iterator__at_tree(I) \
+ (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)
+
static int index_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
index_iterator *ii = (index_iterator *)self;
const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ ii->tree_entry.path = ii->partial.ptr;
+ ie = &ii->tree_entry;
+ }
+
if (entry)
*entry = ie;
@@ -506,47 +706,59 @@ static int index_iterator__at_end(git_iterator *self)
return (ii->current >= git_index_entrycount(ii->index));
}
-static void index_iterator__skip_conflicts(
- index_iterator *ii)
+static int index_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
{
+ index_iterator *ii = (index_iterator *)self;
size_t entrycount = git_index_entrycount(ii->index);
const git_index_entry *ie;
- while (ii->current < entrycount) {
- ie = git_index_get_byindex(ii->index, ii->current);
+ if (index_iterator__at_tree(ii)) {
+ if (iterator__do_autoexpand(ii)) {
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ } else {
+ /* advance to sibling tree (i.e. find entry with new prefix) */
+ while (ii->current < entrycount) {
+ ii->current++;
+
+ if (!(ie = git_index_get_byindex(ii->index, ii->current)) ||
+ ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)
+ break;
+ }
- if (ie == NULL ||
- (ii->base.end != NULL &&
- ii->base.prefixcomp(ie->path, ii->base.end) > 0)) {
- ii->current = entrycount;
- break;
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
}
+ } else {
+ if (ii->current < entrycount)
+ ii->current++;
- if (git_index_entry_stage(ie) == 0)
- break;
-
- ii->current++;
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
}
+
+ return index_iterator__current(entry, self);
}
-static int index_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+static int index_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
{
index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
- if (ii->current < git_index_entrycount(ii->index))
- ii->current++;
-
- index_iterator__skip_conflicts(ii);
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ if (ii->restore_terminator)
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ }
- return index_iterator__current(self, entry);
+ return index_iterator__current(entry, self);
}
static int index_iterator__seek(git_iterator *self, const char *prefix)
{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
- /* find last item before prefix */
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
return -1;
}
@@ -554,11 +766,31 @@ static int index_iterator__reset(
git_iterator *self, const char *start, const char *end)
{
index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie;
+
if (iterator__reset_range(self, start, end) < 0)
return -1;
+
ii->current = ii->base.start ?
git_index__prefix_position(ii->index, ii->base.start) : 0;
- index_iterator__skip_conflicts(ii);
+
+ if ((ie = index_iterator__skip_conflicts(ii)) == NULL)
+ return 0;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = 0;
+
+ if (ii->base.start) {
+ size_t startlen = strlen(ii->base.start);
+
+ ii->partial_pos = (startlen > ii->partial.size) ?
+ ii->partial.size : startlen;
+ }
+
+ index_iterator__next_prefix_tree(ii);
+
return 0;
}
@@ -567,9 +799,11 @@ static void index_iterator__free(git_iterator *self)
index_iterator *ii = (index_iterator *)self;
git_index_free(ii->index);
ii->index = NULL;
+
+ git_buf_free(&ii->partial);
}
-int git_iterator_for_index_range(
+int git_iterator_for_index(
git_iterator **iter,
git_index *index,
git_iterator_flag_t flags,
@@ -578,18 +812,19 @@ int git_iterator_for_index_range(
{
index_iterator *ii;
- GIT_UNUSED(flags);
+ ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));
- ITERATOR_BASE_INIT(ii, index, INDEX);
-
- ii->base.repo = git_index_owner(index);
if (index->ignore_case) {
ii->base.flags |= GIT_ITERATOR_IGNORE_CASE;
ii->base.prefixcomp = git__prefixcmp_icase;
}
+
ii->index = index;
GIT_REFCOUNT_INC(index);
+ git_buf_init(&ii->partial, 0);
+ ii->tree_entry.mode = GIT_FILEMODE_TREE;
+
index_iterator__reset((git_iterator *)ii, NULL, NULL);
*iter = (git_iterator *)ii;
@@ -598,6 +833,8 @@ int git_iterator_for_index_range(
}
+#define WORKDIR_MAX_DEPTH 100
+
typedef struct workdir_iterator_frame workdir_iterator_frame;
struct workdir_iterator_frame {
workdir_iterator_frame *next;
@@ -609,12 +846,12 @@ typedef struct {
git_iterator base;
git_iterator_callbacks cb;
workdir_iterator_frame *stack;
- int (*entrycmp)(const void *pfx, const void *item);
git_ignores ignores;
git_index_entry entry;
git_buf path;
size_t root_len;
int is_ignored;
+ int depth;
} workdir_iterator;
GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps)
@@ -643,7 +880,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(
{
workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
git_vector_cmp entry_compare = CASESELECT(
- (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0,
+ iterator__ignore_case(wi),
git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
if (wf == NULL)
@@ -670,16 +907,11 @@ static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
static int workdir_iterator__update_entry(workdir_iterator *wi);
-static int workdir_iterator__entry_cmp_case(const void *pfx, const void *item)
-{
- const git_path_with_stat *ps = item;
- return git__prefixcmp((const char *)pfx, ps->path);
-}
-
-static int workdir_iterator__entry_cmp_icase(const void *pfx, const void *item)
+static int workdir_iterator__entry_cmp(const void *i, const void *item)
{
+ const workdir_iterator *wi = (const workdir_iterator *)i;
const git_path_with_stat *ps = item;
- return git__prefixcmp_icase((const char *)pfx, ps->path);
+ return wi->base.prefixcomp(wi->base.start, ps->path);
}
static void workdir_iterator__seek_frame_start(
@@ -690,7 +922,7 @@ static void workdir_iterator__seek_frame_start(
if (wi->base.start)
git_vector_bsearch2(
- &wf->index, &wf->entries, wi->entrycmp, wi->base.start);
+ &wf->index, &wf->entries, workdir_iterator__entry_cmp, wi);
else
wf->index = 0;
@@ -701,12 +933,13 @@ static void workdir_iterator__seek_frame_start(
static int workdir_iterator__expand_dir(workdir_iterator *wi)
{
int error;
- workdir_iterator_frame *wf = workdir_iterator__alloc_frame(wi);
+ workdir_iterator_frame *wf;
+
+ wf = workdir_iterator__alloc_frame(wi);
GITERR_CHECK_ALLOC(wf);
error = git_path_dirload_with_stat(
- wi->path.ptr, wi->root_len,
- (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0,
+ wi->path.ptr, wi->root_len, iterator__ignore_case(wi),
wi->base.start, wi->base.end, &wf->entries);
if (error < 0 || wf->entries.length == 0) {
@@ -714,6 +947,13 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi)
return GIT_ENOTFOUND;
}
+ if (++(wi->depth) > WORKDIR_MAX_DEPTH) {
+ giterr_set(GITERR_REPOSITORY,
+ "Working directory is too deep (%d)", wi->depth);
+ workdir_iterator__free_frame(wf);
+ return -1;
+ }
+
workdir_iterator__seek_frame_start(wi, wf);
/* only push new ignores if this is not top level directory */
@@ -729,10 +969,11 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi)
}
static int workdir_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
workdir_iterator *wi = (workdir_iterator *)self;
- *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
+ if (entry)
+ *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
return 0;
}
@@ -741,21 +982,56 @@ static int workdir_iterator__at_end(git_iterator *self)
return (((workdir_iterator *)self)->entry.path == NULL);
}
+static int workdir_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ iterator__clear_entry(entry);
+
+ /* workdir iterator will allow you to explicitly advance into a
+ * commit/submodule (as well as a tree) to avoid some cases where an
+ * entry is mislabeled as a submodule in the working directory
+ */
+ if (wi->entry.path != NULL &&
+ (wi->entry.mode == GIT_FILEMODE_TREE ||
+ wi->entry.mode == GIT_FILEMODE_COMMIT))
+ /* returns GIT_ENOTFOUND if the directory is empty */
+ error = workdir_iterator__expand_dir(wi);
+
+ if (!error && entry)
+ error = workdir_iterator__current(entry, iter);
+
+ return error;
+}
+
static int workdir_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
- int error;
+ int error = 0;
workdir_iterator *wi = (workdir_iterator *)self;
workdir_iterator_frame *wf;
git_path_with_stat *next;
+ /* given include_trees & autoexpand, we might have to go into a tree */
+ if (iterator__do_autoexpand(wi) &&
+ wi->entry.path != NULL &&
+ wi->entry.mode == GIT_FILEMODE_TREE)
+ {
+ error = workdir_iterator__advance_into(entry, self);
+
+ /* continue silently past empty directories if autoexpanding */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+ error = 0;
+ }
+
if (entry != NULL)
*entry = NULL;
- if (wi->entry.path == NULL)
- return 0;
-
- while (1) {
+ while (wi->entry.path != NULL) {
wf = wi->stack;
next = git_vector_get(&wf->entries, ++wf->index);
@@ -774,6 +1050,7 @@ static int workdir_iterator__advance(
}
wi->stack = wf->next;
+ wi->depth--;
workdir_iterator__free_frame(wf);
git_ignore__pop_dir(&wi->ignores);
}
@@ -781,7 +1058,7 @@ static int workdir_iterator__advance(
error = workdir_iterator__update_entry(wi);
if (!error && entry != NULL)
- error = workdir_iterator__current(self, entry);
+ error = workdir_iterator__current(entry, self);
return error;
}
@@ -807,6 +1084,7 @@ static int workdir_iterator__reset(
workdir_iterator__free_frame(wf);
git_ignore__pop_dir(&wi->ignores);
}
+ wi->depth = 0;
if (iterator__reset_range(self, start, end) < 0)
return -1;
@@ -832,6 +1110,7 @@ static void workdir_iterator__free(git_iterator *self)
static int workdir_iterator__update_entry(workdir_iterator *wi)
{
+ int error = 0;
git_path_with_stat *ps =
git_vector_get(&wi->stack->entries, wi->stack->index);
@@ -841,19 +1120,18 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
if (!ps)
return 0;
+ /* skip over .git entries */
+ if (path_is_dotgit(ps))
+ return workdir_iterator__advance(NULL, (git_iterator *)wi);
+
if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)
return -1;
- if (wi->base.end &&
- wi->base.prefixcomp(wi->path.ptr + wi->root_len, wi->base.end) > 0)
+ if (iterator__past_end(wi, wi->path.ptr + wi->root_len))
return 0;
wi->entry.path = ps->path;
- /* skip over .git entries */
- if (path_is_dotgit(ps))
- return workdir_iterator__advance((git_iterator *)wi, NULL);
-
wi->is_ignored = -1;
git_index_entry__init_from_stat(&wi->entry, &ps->st);
@@ -867,26 +1145,34 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
return 0;
}
+ /* if this isn't a tree, then we're done */
+ if (wi->entry.mode != GIT_FILEMODE_TREE)
+ return 0;
+
/* detect submodules */
- if (S_ISDIR(wi->entry.mode)) {
- int res = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);
- bool is_submodule = (res == 0);
- if (res == GIT_ENOTFOUND)
- giterr_clear();
-
- /* if submodule, mark as GITLINK and remove trailing slash */
- if (is_submodule) {
- size_t len = strlen(wi->entry.path);
- assert(wi->entry.path[len - 1] == '/');
- wi->entry.path[len - 1] = '\0';
- wi->entry.mode = S_IFGITLINK;
- }
+ error = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+
+ if (error == GIT_EEXISTS) /* if contains .git, treat as untracked submod */
+ error = 0;
+
+ /* if submodule, mark as GITLINK and remove trailing slash */
+ if (!error) {
+ size_t len = strlen(wi->entry.path);
+ assert(wi->entry.path[len - 1] == '/');
+ wi->entry.path[len - 1] = '\0';
+ wi->entry.mode = S_IFGITLINK;
+ return 0;
}
- return 0;
+ if (iterator__include_trees(wi))
+ return 0;
+
+ return workdir_iterator__advance(NULL, (git_iterator *)wi);
}
-int git_iterator_for_workdir_range(
+int git_iterator_for_workdir(
git_iterator **iter,
git_repository *repo,
git_iterator_flag_t flags,
@@ -899,13 +1185,12 @@ int git_iterator_for_workdir_range(
assert(iter && repo);
if ((error = git_repository__ensure_not_bare(
- repo, "scan working directory")) < 0)
+ repo, "scan working directory")) < 0)
return error;
- ITERATOR_BASE_INIT(wi, workdir, WORKDIR);
- wi->base.repo = repo;
+ ITERATOR_BASE_INIT(wi, workdir, WORKDIR, repo);
- if ((error = iterator_update_ignore_case((git_iterator *)wi, flags)) < 0)
+ if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0)
goto fail;
if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
@@ -915,10 +1200,7 @@ int git_iterator_for_workdir_range(
git__free(wi);
return -1;
}
-
wi->root_len = wi->path.size;
- wi->entrycmp = (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0 ?
- workdir_iterator__entry_cmp_icase : workdir_iterator__entry_cmp_case;
if ((error = workdir_iterator__expand_dir(wi)) < 0) {
if (error != GIT_ENOTFOUND)
@@ -935,161 +1217,6 @@ fail:
}
-typedef struct {
- /* replacement callbacks */
- git_iterator_callbacks cb;
- /* original iterator values */
- git_iterator_callbacks *orig;
- git_iterator_type_t orig_type;
- /* spoolandsort data */
- git_vector entries;
- git_pool entry_pool;
- git_pool string_pool;
- size_t position;
-} spoolandsort_callbacks;
-
-static int spoolandsort_iterator__current(
- git_iterator *self, const git_index_entry **entry)
-{
- spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
-
- *entry = (const git_index_entry *)
- git_vector_get(&scb->entries, scb->position);
-
- return 0;
-}
-
-static int spoolandsort_iterator__at_end(git_iterator *self)
-{
- spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
-
- return 0 == scb->entries.length || scb->entries.length - 1 <= scb->position;
-}
-
-static int spoolandsort_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
-{
- spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
-
- *entry = (const git_index_entry *)
- git_vector_get(&scb->entries, ++scb->position);
-
- return 0;
-}
-
-static int spoolandsort_iterator__seek(git_iterator *self, const char *prefix)
-{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
-
- return -1;
-}
-
-static int spoolandsort_iterator__reset(
- git_iterator *self, const char *start, const char *end)
-{
- spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
-
- GIT_UNUSED(start); GIT_UNUSED(end);
-
- scb->position = 0;
-
- return 0;
-}
-
-static void spoolandsort_iterator__free_callbacks(spoolandsort_callbacks *scb)
-{
- git_pool_clear(&scb->string_pool);
- git_pool_clear(&scb->entry_pool);
- git_vector_free(&scb->entries);
- git__free(scb);
-}
-
-void git_iterator_spoolandsort_pop(git_iterator *self)
-{
- spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
-
- if (self->type != GIT_ITERATOR_TYPE_SPOOLANDSORT)
- return;
-
- self->cb = scb->orig;
- self->type = scb->orig_type;
- self->flags ^= GIT_ITERATOR_IGNORE_CASE;
-
- spoolandsort_iterator__free_callbacks(scb);
-}
-
-static void spoolandsort_iterator__free(git_iterator *self)
-{
- git_iterator_spoolandsort_pop(self);
- self->cb->free(self);
-}
-
-int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case)
-{
- const git_index_entry *item;
- spoolandsort_callbacks *scb;
- int (*entrycomp)(const void *a, const void *b);
-
- if (((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) == (ignore_case != 0))
- return 0;
-
- if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
- iter->flags = (iter->flags ^ GIT_ITERATOR_IGNORE_CASE);
- return 0;
- }
-
- scb = git__calloc(1, sizeof(spoolandsort_callbacks));
- GITERR_CHECK_ALLOC(scb);
-
- ITERATOR_SET_CB(scb,spoolandsort);
-
- scb->orig = iter->cb;
- scb->orig_type = iter->type;
- scb->position = 0;
-
- entrycomp = ignore_case ? git_index_entry__cmp_icase : git_index_entry__cmp;
-
- if (git_vector_init(&scb->entries, 16, entrycomp) < 0 ||
- git_pool_init(&scb->entry_pool, sizeof(git_index_entry), 0) < 0 ||
- git_pool_init(&scb->string_pool, 1, 0) < 0 ||
- git_iterator_current(iter, &item) < 0)
- goto fail;
-
- while (item) {
- git_index_entry *clone = git_pool_malloc(&scb->entry_pool, 1);
- if (!clone)
- goto fail;
-
- memcpy(clone, item, sizeof(git_index_entry));
-
- if (item->path) {
- clone->path = git_pool_strdup(&scb->string_pool, item->path);
- if (!clone->path)
- goto fail;
- }
-
- if (git_vector_insert(&scb->entries, clone) < 0)
- goto fail;
-
- if (git_iterator_advance(iter, &item) < 0)
- goto fail;
- }
-
- git_vector_sort(&scb->entries);
-
- iter->cb = (git_iterator_callbacks *)scb;
- iter->type = GIT_ITERATOR_TYPE_SPOOLANDSORT;
- iter->flags ^= GIT_ITERATOR_IGNORE_CASE;
-
- return 0;
-
-fail:
- spoolandsort_iterator__free_callbacks(scb);
- return -1;
-}
-
-
void git_iterator_free(git_iterator *iter)
{
if (iter == NULL)
@@ -1105,110 +1232,93 @@ void git_iterator_free(git_iterator *iter)
git__free(iter);
}
-git_index *git_iterator_index_get_index(git_iterator *iter)
+int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
{
- if (iter->type == GIT_ITERATOR_TYPE_INDEX)
- return ((index_iterator *)iter)->index;
+ bool desire_ignore_case = (ignore_case != 0);
- if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT &&
- ((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_TYPE_INDEX)
- return ((index_iterator *)iter)->index;
+ if (iterator__ignore_case(iter) == desire_ignore_case)
+ return 0;
- return NULL;
+ if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
+ if (desire_ignore_case)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Cannot currently set ignore case on non-empty iterators");
+ return -1;
+ }
+
+ return 0;
}
-git_iterator_type_t git_iterator_inner_type(git_iterator *iter)
+git_index *git_iterator_get_index(git_iterator *iter)
{
- if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT)
- return ((spoolandsort_callbacks *)iter->cb)->orig_type;
-
- return iter->type;
+ if (iter->type == GIT_ITERATOR_TYPE_INDEX)
+ return ((index_iterator *)iter)->index;
+ return NULL;
}
int git_iterator_current_tree_entry(
- git_iterator *iter, const git_tree_entry **tree_entry)
+ const git_tree_entry **tree_entry, git_iterator *iter)
{
- *tree_entry = (iter->type != GIT_ITERATOR_TYPE_TREE) ? NULL :
- tree_iterator__tree_entry((tree_iterator *)iter);
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ *tree_entry = NULL;
+ else {
+ tree_iterator_frame *tf = ((tree_iterator *)iter)->head;
+ *tree_entry = (tf->current < tf->n_entries) ?
+ tf->entries[tf->current]->te : NULL;
+ }
+
return 0;
}
int git_iterator_current_parent_tree(
+ const git_tree **tree_ptr,
git_iterator *iter,
- const char *parent_path,
- const git_tree **tree_ptr)
+ const char *parent_path)
{
tree_iterator *ti = (tree_iterator *)iter;
tree_iterator_frame *tf;
const char *scan = parent_path;
- int (*strncomp)(const char *a, const char *b, size_t sz);
-
- if (iter->type != GIT_ITERATOR_TYPE_TREE || ti->stack == NULL)
- goto notfound;
+ const git_tree_entry *te;
- strncomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ?
- git__strncasecmp : git__strncmp;
+ *tree_ptr = NULL;
- for (tf = ti->tail; tf != NULL; tf = tf->prev) {
- const git_tree_entry *te;
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ return 0;
- if (!*scan) {
- *tree_ptr = tf->tree;
+ for (tf = ti->root; *scan; ) {
+ if (!(tf = tf->down) ||
+ tf->current >= tf->n_entries ||
+ !(te = tf->entries[tf->current]->te) ||
+ ti->strncomp(scan, te->filename, te->filename_len) != 0)
return 0;
- }
-
- te = git_tree_entry_byindex(tf->tree,
- tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index);
-
- if (strncomp(scan, te->filename, te->filename_len) != 0)
- goto notfound;
scan += te->filename_len;
-
- if (*scan) {
- if (*scan != '/')
- goto notfound;
+ if (*scan == '/')
scan++;
- }
}
-notfound:
- *tree_ptr = NULL;
+ *tree_ptr = tf->entries[tf->current]->tree;
return 0;
}
-int git_iterator_current_is_ignored(git_iterator *iter)
+bool git_iterator_current_is_ignored(git_iterator *iter)
{
workdir_iterator *wi = (workdir_iterator *)iter;
if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- return 0;
+ return false;
if (wi->is_ignored != -1)
- return wi->is_ignored;
+ return (bool)(wi->is_ignored != 0);
if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
- wi->is_ignored = 1;
+ wi->is_ignored = true;
- return wi->is_ignored;
-}
-
-int git_iterator_advance_into_directory(
- git_iterator *iter, const git_index_entry **entry)
-{
- workdir_iterator *wi = (workdir_iterator *)iter;
-
- if (iter->type == GIT_ITERATOR_TYPE_WORKDIR &&
- wi->entry.path &&
- (wi->entry.mode == GIT_FILEMODE_TREE ||
- wi->entry.mode == GIT_FILEMODE_COMMIT))
- {
- if (workdir_iterator__expand_dir(wi) < 0)
- /* if error loading or if empty, skip the directory. */
- return workdir_iterator__advance(iter, entry);
- }
-
- return entry ? git_iterator_current(iter, entry) : 0;
+ return (bool)wi->is_ignored;
}
int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
@@ -1216,8 +1326,7 @@ int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
const git_index_entry *entry;
/* a "done" iterator is after every prefix */
- if (git_iterator_current(iter, &entry) < 0 ||
- entry == NULL)
+ if (git_iterator_current(&entry, iter) < 0 || entry == NULL)
return 1;
/* a NULL prefix is after any valid iterator */
@@ -1227,7 +1336,7 @@ int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
return iter->prefixcomp(entry->path, path_prefix);
}
-int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path)
+int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
{
workdir_iterator *wi = (workdir_iterator *)iter;
@@ -1238,4 +1347,3 @@ int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path)
return 0;
}
-
diff --git a/src/iterator.h b/src/iterator.h
index a9bccfca8..4a4e6a9d8 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -12,11 +12,6 @@
#include "vector.h"
#include "buffer.h"
-#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX) \
- (((ITER).flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? \
- git__prefixcmp_icase((STR), (PREFIX)) : \
- git__prefixcmp((STR), (PREFIX)))
-
typedef struct git_iterator git_iterator;
typedef enum {
@@ -24,20 +19,26 @@ typedef enum {
GIT_ITERATOR_TYPE_TREE = 1,
GIT_ITERATOR_TYPE_INDEX = 2,
GIT_ITERATOR_TYPE_WORKDIR = 3,
- GIT_ITERATOR_TYPE_SPOOLANDSORT = 4
} git_iterator_type_t;
typedef enum {
- GIT_ITERATOR_IGNORE_CASE = (1 << 0), /* ignore_case */
- GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), /* force ignore_case off */
+ /** ignore case for entry sort order */
+ GIT_ITERATOR_IGNORE_CASE = (1 << 0),
+ /** force case sensitivity for entry sort order */
+ GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1),
+ /** return tree items in addition to blob items */
+ GIT_ITERATOR_INCLUDE_TREES = (1 << 2),
+ /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
+ GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3),
} git_iterator_flag_t;
typedef struct {
- int (*current)(git_iterator *, const git_index_entry **);
- int (*at_end)(git_iterator *);
- int (*advance)(git_iterator *, const git_index_entry **);
+ int (*current)(const git_index_entry **, git_iterator *);
+ int (*advance)(const git_index_entry **, git_iterator *);
+ int (*advance_into)(const git_index_entry **, git_iterator *);
int (*seek)(git_iterator *, const char *prefix);
int (*reset)(git_iterator *, const char *start, const char *end);
+ int (*at_end)(git_iterator *);
void (*free)(git_iterator *);
} git_iterator_callbacks;
@@ -52,86 +53,92 @@ struct git_iterator {
};
extern int git_iterator_for_nothing(
- git_iterator **out, git_iterator_flag_t flags);
+ git_iterator **out,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
/* tree iterators will match the ignore_case value from the index of the
* repository, unless you override with a non-zero flag value
*/
-extern int git_iterator_for_tree_range(
+extern int git_iterator_for_tree(
git_iterator **out,
git_tree *tree,
git_iterator_flag_t flags,
const char *start,
const char *end);
-GIT_INLINE(int) git_iterator_for_tree(git_iterator **out, git_tree *tree)
-{
- return git_iterator_for_tree_range(out, tree, 0, NULL, NULL);
-}
-
/* index iterators will take the ignore_case value from the index; the
* ignore_case flags are not used
*/
-extern int git_iterator_for_index_range(
+extern int git_iterator_for_index(
git_iterator **out,
git_index *index,
git_iterator_flag_t flags,
const char *start,
const char *end);
-GIT_INLINE(int) git_iterator_for_index(git_iterator **out, git_index *index)
-{
- return git_iterator_for_index_range(out, index, 0, NULL, NULL);
-}
-
/* workdir iterators will match the ignore_case value from the index of the
* repository, unless you override with a non-zero flag value
*/
-extern int git_iterator_for_workdir_range(
+extern int git_iterator_for_workdir(
git_iterator **out,
git_repository *repo,
git_iterator_flag_t flags,
const char *start,
const char *end);
-GIT_INLINE(int) git_iterator_for_workdir(git_iterator **out, git_repository *repo)
-{
- return git_iterator_for_workdir_range(out, repo, 0, NULL, NULL);
-}
-
extern void git_iterator_free(git_iterator *iter);
-/* Spool all iterator values, resort with alternative ignore_case value
- * and replace callbacks with spoolandsort alternates.
- */
-extern int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case);
-
-/* Restore original callbacks - not required in most circumstances */
-extern void git_iterator_spoolandsort_pop(git_iterator *iter);
-
-/* Entry is not guaranteed to be fully populated. For a tree iterator,
- * we will only populate the mode, oid and path, for example. For a workdir
- * iterator, we will not populate the oid.
+/* Return a git_index_entry structure for the current value the iterator
+ * is looking at or NULL if the iterator is at the end.
+ *
+ * The entry may noy be fully populated. Tree iterators will only have a
+ * value mode, OID, and path. Workdir iterators will not have an OID (but
+ * you can use `git_iterator_current_oid()` to calculate it on demand).
*
* You do not need to free the entry. It is still "owned" by the iterator.
- * Once you call `git_iterator_advance`, then content of the old entry is
- * no longer guaranteed to be valid.
+ * Once you call `git_iterator_advance()` then the old entry is no longer
+ * guaranteed to be valid - it may be freed or just overwritten in place.
*/
GIT_INLINE(int) git_iterator_current(
- git_iterator *iter, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->cb->current(iter, entry);
+ return iter->cb->current(entry, iter);
}
-GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
+/**
+ * Advance to the next item for the iterator.
+ *
+ * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If
+ * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree
+ * item will skip over all the items under that tree.
+ */
+GIT_INLINE(int) git_iterator_advance(
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->cb->at_end(iter);
+ return iter->cb->advance(entry, iter);
}
-GIT_INLINE(int) git_iterator_advance(
- git_iterator *iter, const git_index_entry **entry)
+/**
+ * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).
+ *
+ * git_iterator_advance() steps through all items being iterated over
+ * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),
+ * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next
+ * sibling of a tree instead of going to the first child of the tree. In
+ * that case, use this function to advance to the first child of the tree.
+ *
+ * If the current item is not a tree, this is a no-op.
+ *
+ * For working directory iterators only, a tree (i.e. directory) can be
+ * empty. In that case, this function returns GIT_ENOTFOUND and does not
+ * advance. That can't happen for tree and index iterators.
+ */
+GIT_INLINE(int) git_iterator_advance_into(
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->cb->advance(iter, entry);
+ return iter->cb->advance_into(entry, iter);
}
GIT_INLINE(int) git_iterator_seek(
@@ -146,6 +153,11 @@ GIT_INLINE(int) git_iterator_reset(
return iter->cb->reset(iter, start, end);
}
+GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
+{
+ return iter->cb->at_end(iter);
+}
+
GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
{
return iter->type;
@@ -166,47 +178,28 @@ GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
}
+extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);
+
extern int git_iterator_current_tree_entry(
- git_iterator *iter, const git_tree_entry **tree_entry);
+ const git_tree_entry **entry_out, git_iterator *iter);
extern int git_iterator_current_parent_tree(
- git_iterator *iter, const char *parent_path, const git_tree **tree_ptr);
+ const git_tree **tree_out, git_iterator *iter, const char *parent_path);
-extern int git_iterator_current_is_ignored(git_iterator *iter);
-
-/**
- * Iterate into a workdir directory.
- *
- * Workdir iterators do not automatically descend into directories (so that
- * when comparing two iterator entries you can detect a newly created
- * directory in the workdir). As a result, you may get S_ISDIR items from
- * a workdir iterator. If you wish to iterate over the contents of the
- * directories you encounter, then call this function when you encounter
- * a directory.
- *
- * If there are no files in the directory, this will end up acting like a
- * regular advance and will skip past the directory, so you should be
- * prepared for that case.
- *
- * On non-workdir iterators or if not pointing at a directory, this is a
- * no-op and will not advance the iterator.
- */
-extern int git_iterator_advance_into_directory(
- git_iterator *iter, const git_index_entry **entry);
+extern bool git_iterator_current_is_ignored(git_iterator *iter);
extern int git_iterator_cmp(
git_iterator *iter, const char *path_prefix);
/**
- * Get the full path of the current item from a workdir iterator.
- * This will return NULL for a non-workdir iterator.
+ * Get full path of the current item from a workdir iterator. This will
+ * return NULL for a non-workdir iterator. The git_buf is still owned by
+ * the iterator; this is exposed just for efficiency.
*/
extern int git_iterator_current_workdir_path(
- git_iterator *iter, git_buf **path);
-
-
-extern git_index *git_iterator_index_get_index(git_iterator *iter);
+ git_buf **path, git_iterator *iter);
-extern git_iterator_type_t git_iterator_inner_type(git_iterator *iter);
+/* Return index pointer if index iterator, else NULL */
+extern git_index *git_iterator_get_index(git_iterator *iter);
#endif
diff --git a/src/mwindow.c b/src/mwindow.c
index cb2ef78b0..b35503d46 100644
--- a/src/mwindow.c
+++ b/src/mwindow.c
@@ -33,7 +33,7 @@ static git_mwindow_ctl mem_ctl;
void git_mwindow_free_all(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
- unsigned int i;
+ size_t i;
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
@@ -115,7 +115,7 @@ static void git_mwindow_scan_lru(
static int git_mwindow_close_lru(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
- unsigned int i;
+ size_t i;
git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
/* FIXME: Does this give us any advantage? */
@@ -288,7 +288,7 @@ void git_mwindow_file_deregister(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
git_mwindow_file *cur;
- unsigned int i;
+ size_t i;
if (git_mutex_lock(&git__mwindow_mutex))
return;
diff --git a/src/notes.c b/src/notes.c
index f5537db3f..ef48ac88e 100644
--- a/src/notes.c
+++ b/src/notes.c
@@ -531,14 +531,11 @@ void git_note_free(git_note *note)
static int process_entry_path(
const char* entry_path,
- const git_oid *note_oid,
- git_note_foreach_cb note_cb,
- void *payload)
+ git_oid *annotated_object_id)
{
int error = -1;
size_t i = 0, j = 0, len;
git_buf buf = GIT_BUF_INIT;
- git_oid annotated_object_id;
if ((error = git_buf_puts(&buf, entry_path)) < 0)
goto cleanup;
@@ -571,11 +568,7 @@ static int process_entry_path(
goto cleanup;
}
- if ((error = git_oid_fromstr(&annotated_object_id, buf.ptr)) < 0)
- goto cleanup;
-
- if (note_cb(note_oid, &annotated_object_id, payload))
- error = GIT_EUSER;
+ error = git_oid_fromstr(annotated_object_id, buf.ptr);
cleanup:
git_buf_free(&buf);
@@ -583,32 +576,86 @@ cleanup:
}
int git_note_foreach(
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload)
+{
+ int error;
+ git_note_iterator *iter = NULL;
+ git_oid note_id, annotated_id;
+
+ if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)
+ return error;
+
+ while (!(error = git_note_next(&note_id, &annotated_id, iter))) {
+ if (note_cb(&note_id, &annotated_id, payload)) {
+ error = GIT_EUSER;
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_note_iterator_free(iter);
+ return error;
+}
+
+
+void git_note_iterator_free(git_note_iterator *it)
+{
+ if (it == NULL)
+ return;
+
+ git_iterator_free(it);
+}
+
+
+int git_note_iterator_new(
+ git_note_iterator **it,
git_repository *repo,
- const char *notes_ref,
- git_note_foreach_cb note_cb,
- void *payload)
+ const char *notes_ref)
{
int error;
- git_iterator *iter = NULL;
- git_tree *tree = NULL;
git_commit *commit = NULL;
- const git_index_entry *item;
-
- if (!(error = retrieve_note_tree_and_commit(
- &tree, &commit, repo, &notes_ref)) &&
- !(error = git_iterator_for_tree(&iter, tree)))
- error = git_iterator_current(iter, &item);
+ git_tree *tree = NULL;
- while (!error && item) {
- error = process_entry_path(item->path, &item->oid, note_cb, payload);
+ error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
+ if (error < 0)
+ goto cleanup;
- if (!error)
- error = git_iterator_advance(iter, &item);
- }
+ if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0)
+ git_iterator_free(*it);
- git_iterator_free(iter);
+cleanup:
git_tree_free(tree);
git_commit_free(commit);
return error;
}
+
+int git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it)
+{
+ int error;
+ const git_index_entry *item;
+
+ if ((error = git_iterator_current(&item, it)) < 0)
+ goto exit;
+
+ if (item != NULL) {
+ git_oid_cpy(note_id, &item->oid);
+ error = process_entry_path(item->path, annotated_id);
+
+ if (error >= 0)
+ error = git_iterator_advance(NULL, it);
+ } else {
+ error = GIT_ITEROVER;
+ }
+
+exit:
+ return error;
+}
diff --git a/src/notes.h b/src/notes.h
index 2f119e3c3..39e18b621 100644
--- a/src/notes.h
+++ b/src/notes.h
@@ -10,6 +10,7 @@
#include "common.h"
#include "git2/oid.h"
+#include "git2/types.h"
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
diff --git a/src/odb.c b/src/odb.c
index 24381e70e..c98df247c 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -32,6 +32,8 @@ typedef struct
int is_alternate;
} backend_internal;
+size_t git_odb__cache_size = GIT_DEFAULT_CACHE_SIZE;
+
static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
@@ -351,7 +353,7 @@ int git_odb_new(git_odb **out)
git_odb *db = git__calloc(1, sizeof(*db));
GITERR_CHECK_ALLOC(db);
- if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 ||
+ if (git_cache_init(&db->cache, git_odb__cache_size, &free_odb_object) < 0 ||
git_vector_init(&db->backends, 4, backend_sort_cmp) < 0)
{
git__free(db);
@@ -499,7 +501,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
static void odb_free(git_odb *db)
{
- unsigned int i;
+ size_t i;
for (i = 0; i < db->backends.length; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
@@ -527,7 +529,7 @@ void git_odb_free(git_odb *db)
int git_odb_exists(git_odb *db, const git_oid *id)
{
git_odb_object *object;
- unsigned int i;
+ size_t i;
bool found = false;
bool refreshed = false;
@@ -577,7 +579,7 @@ int git_odb__read_header_or_object(
git_odb_object **out, size_t *len_p, git_otype *type_p,
git_odb *db, const git_oid *id)
{
- unsigned int i;
+ size_t i;
int error = GIT_ENOTFOUND;
git_odb_object *object;
@@ -619,7 +621,7 @@ int git_odb__read_header_or_object(
int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
{
- unsigned int i;
+ size_t i;
int error;
bool refreshed = false;
git_rawobj raw;
@@ -664,7 +666,7 @@ attempt_lookup:
int git_odb_read_prefix(
git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len)
{
- unsigned int i;
+ size_t i;
int error = GIT_ENOTFOUND;
git_oid found_full_oid = {{0}};
git_rawobj raw;
@@ -743,7 +745,7 @@ int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload)
int git_odb_write(
git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
git_odb_stream *stream;
@@ -785,7 +787,7 @@ int git_odb_write(
int git_odb_open_wstream(
git_odb_stream **stream, git_odb *db, size_t size, git_otype type)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
assert(stream && db);
@@ -812,7 +814,7 @@ int git_odb_open_wstream(
int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
assert(stream && db);
@@ -833,7 +835,7 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi
int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
assert(out && db);
@@ -864,7 +866,7 @@ void *git_odb_backend_malloc(git_odb_backend *backend, size_t len)
int git_odb_refresh(struct git_odb *db)
{
- unsigned int i;
+ size_t i;
assert(db);
for (i = 0; i < db->backends.length; ++i) {
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 9779ecd25..7240a4ac7 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -132,9 +132,6 @@ struct pack_writepack {
*
***********************************************************/
-static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p);
-static int pack_window_contains(git_mwindow *win, off_t offset);
-
static int packfile_sort__cb(const void *a_, const void *b_);
static int packfile_load__cb(void *_data, git_buf *path);
@@ -162,23 +159,6 @@ static int pack_entry_find_prefix(
*
***********************************************************/
-GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p)
-{
- GIT_UNUSED(backend);
- git_mwindow_free_all(&p->mwf);
-}
-
-GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset)
-{
- /* We must promise at least 20 bytes (one hash) after the
- * offset is available from this window, otherwise the offset
- * is not actually in this window and a different window (which
- * has that one hash excess) must be used. This is to support
- * the object header and delta base parsing routines below.
- */
- return git_mwindow_contains(win, offset + 20);
-}
-
static int packfile_sort__cb(const void *a_, const void *b_)
{
const struct git_pack_file *a = a_;
@@ -215,7 +195,7 @@ static int packfile_load__cb(void *_data, git_buf *path)
struct pack_backend *backend = (struct pack_backend *)_data;
struct git_pack_file *pack;
int error;
- unsigned int i;
+ size_t i;
if (git__suffixcmp(path->ptr, ".idx") != 0)
return 0; /* not an index */
@@ -242,7 +222,7 @@ static int pack_entry_find_inner(
const git_oid *oid,
struct git_pack_file *last_found)
{
- unsigned int i;
+ size_t i;
if (last_found &&
git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0)
@@ -286,7 +266,7 @@ static unsigned pack_entry_find_prefix_inner(
struct git_pack_file *last_found)
{
int error;
- unsigned int i;
+ size_t i;
unsigned found = 0;
if (last_found) {
@@ -530,7 +510,7 @@ static int pack_backend__writepack(struct git_odb_writepack **out,
static void pack_backend__free(git_odb_backend *_backend)
{
struct pack_backend *backend;
- unsigned int i;
+ size_t i;
assert(_backend);
diff --git a/src/oid.c b/src/oid.c
index 25c6fce22..ab69eeb17 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -25,7 +25,7 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
int v;
if (length > GIT_OID_HEXSZ)
- length = GIT_OID_HEXSZ;
+ return oid_error_invalid("too long");
for (p = 0; p < length - 1; p += 2) {
v = (git__fromhex(str[p + 0]) << 4)
@@ -51,6 +51,11 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
return 0;
}
+int git_oid_fromstrp(git_oid *out, const char *str)
+{
+ return git_oid_fromstrn(out, str, strlen(str));
+}
+
int git_oid_fromstr(git_oid *out, const char *str)
{
return git_oid_fromstrn(out, str, GIT_OID_HEXSZ);
diff --git a/src/path.c b/src/path.c
index 263cf9e7c..6437979d5 100644
--- a/src/path.c
+++ b/src/path.c
@@ -679,37 +679,14 @@ int git_path_apply_relative(git_buf *target, const char *relpath)
int git_path_cmp(
const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2)
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t))
{
unsigned char c1, c2;
size_t len = len1 < len2 ? len1 : len2;
int cmp;
- cmp = memcmp(name1, name2, len);
- if (cmp)
- return cmp;
-
- c1 = name1[len];
- c2 = name2[len];
-
- if (c1 == '\0' && isdir1)
- c1 = '/';
-
- if (c2 == '\0' && isdir2)
- c2 = '/';
-
- return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
-}
-
-int git_path_icmp(
- const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2)
-{
- unsigned char c1, c2;
- size_t len = len1 < len2 ? len1 : len2;
- int cmp;
-
- cmp = strncasecmp(name1, name2, len);
+ cmp = compare(name1, name2, len);
if (cmp)
return cmp;
@@ -900,15 +877,22 @@ int git_path_dirload_with_stat(
if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
continue;
+ git_buf_truncate(&full, prefix_len);
+
if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
(error = git_path_lstat(full.ptr, &ps->st)) < 0)
break;
- git_buf_truncate(&full, prefix_len);
-
if (S_ISDIR(ps->st.st_mode)) {
- ps->path[ps->path_len++] = '/';
- ps->path[ps->path_len] = '\0';
+ if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0)
+ break;
+
+ if (p_access(full.ptr, F_OK) == 0) {
+ ps->st.st_mode = GIT_FILEMODE_COMMIT;
+ } else {
+ ps->path[ps->path_len++] = '/';
+ ps->path[ps->path_len] = '\0';
+ }
}
}
diff --git a/src/path.h b/src/path.h
index feefd65d1..ead4fa338 100644
--- a/src/path.h
+++ b/src/path.h
@@ -265,12 +265,8 @@ extern int git_path_direach(
*/
extern int git_path_cmp(
const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2);
-
-/** Path sort function that is case insensitive */
-extern int git_path_icmp(
- const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2);
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t));
/**
* Invoke callback up path directory by directory until the ceiling is
diff --git a/src/pool.c b/src/pool.c
index 64b5c6b00..b3cd49665 100644
--- a/src/pool.c
+++ b/src/pool.c
@@ -10,6 +10,10 @@ struct git_pool_page {
char data[GIT_FLEX_ARRAY];
};
+struct pool_freelist {
+ struct pool_freelist *next;
+};
+
#define GIT_POOL_MIN_USABLE 4
#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*)
@@ -150,7 +154,7 @@ void *git_pool_malloc(git_pool *pool, uint32_t items)
pool->has_multi_item_alloc = 1;
else if (pool->free_list != NULL) {
ptr = pool->free_list;
- pool->free_list = *((void **)pool->free_list);
+ pool->free_list = ((struct pool_freelist *)pool->free_list)->next;
return ptr;
}
@@ -235,10 +239,31 @@ char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
void git_pool_free(git_pool *pool, void *ptr)
{
- assert(pool && ptr && pool->item_size >= sizeof(void*));
+ struct pool_freelist *item = ptr;
+
+ assert(pool && pool->item_size >= sizeof(void*));
+
+ if (item) {
+ item->next = pool->free_list;
+ pool->free_list = item;
+ }
+}
+
+void git_pool_free_array(git_pool *pool, size_t count, void **ptrs)
+{
+ struct pool_freelist **items = (struct pool_freelist **)ptrs;
+ size_t i;
+
+ assert(pool && ptrs && pool->item_size >= sizeof(void*));
+
+ if (!count)
+ return;
+
+ for (i = count - 1; i > 0; --i)
+ items[i]->next = items[i - 1];
- *((void **)ptr) = pool->free_list;
- pool->free_list = ptr;
+ items[i]->next = pool->free_list;
+ pool->free_list = items[count - 1];
}
uint32_t git_pool__open_pages(git_pool *pool)
diff --git a/src/pool.h b/src/pool.h
index 2b262a588..5ac9b764f 100644
--- a/src/pool.h
+++ b/src/pool.h
@@ -126,6 +126,13 @@ extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b);
*/
extern void git_pool_free(git_pool *pool, void *ptr);
+/**
+ * Push an array of pool allocated blocks efficiently onto the free list.
+ *
+ * This has the same constraints as `git_pool_free()` above.
+ */
+extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs);
+
/*
* Misc utilities
*/
diff --git a/src/posix.h b/src/posix.h
index 9dd0c94d3..719c8a04c 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -32,14 +32,13 @@ typedef int git_file;
* Standard POSIX Methods
*
* All the methods starting with the `p_` prefix are
- * direct ports of the standard POSIX methods.
+ * direct ports of the standard POSIX methods.
*
* Some of the methods are slightly wrapped to provide
* saner defaults. Some of these methods are emulated
* in Windows platforns.
*
* Use your manpages to check the docs on these.
- * Straightforward
*/
extern int p_read(git_file fd, void *buf, size_t cnt);
diff --git a/src/push.c b/src/push.c
index 628df7ac4..37f641812 100644
--- a/src/push.c
+++ b/src/push.c
@@ -83,18 +83,6 @@ static void free_refspec(push_spec *spec)
git__free(spec);
}
-static void free_status(push_status *status)
-{
- if (status == NULL)
- return;
-
- if (status->msg)
- git__free(status->msg);
-
- git__free(status->ref);
- git__free(status);
-}
-
static int check_rref(char *ref)
{
if (git__prefixcmp(ref, "refs/")) {
@@ -225,8 +213,11 @@ int git_push_update_tips(git_push *push)
error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));
if (!error) {
- if ((error = git_reference_delete(remote_ref)) < 0)
+ if ((error = git_reference_delete(remote_ref)) < 0) {
+ git_reference_free(remote_ref);
goto on_error;
+ }
+ git_reference_free(remote_ref);
} else if (error == GIT_ENOTFOUND)
giterr_clear();
else
@@ -526,6 +517,18 @@ int git_push_status_foreach(git_push *push,
return 0;
}
+void git_push_status_free(push_status *status)
+{
+ if (status == NULL)
+ return;
+
+ if (status->msg)
+ git__free(status->msg);
+
+ git__free(status->ref);
+ git__free(status);
+}
+
void git_push_free(git_push *push)
{
push_spec *spec;
@@ -541,7 +544,7 @@ void git_push_free(git_push *push)
git_vector_free(&push->specs);
git_vector_foreach(&push->status, i, status) {
- free_status(status);
+ git_push_status_free(status);
}
git_vector_free(&push->status);
diff --git a/src/push.h b/src/push.h
index 629583189..e982b8385 100644
--- a/src/push.h
+++ b/src/push.h
@@ -41,4 +41,11 @@ struct git_push {
unsigned pb_parallelism;
};
+/**
+ * Free the given push status object
+ *
+ * @param status The push status object
+ */
+void git_push_status_free(push_status *status);
+
#endif
diff --git a/src/refdb.c b/src/refdb.c
new file mode 100644
index 000000000..0d2064343
--- /dev/null
+++ b/src/refdb.c
@@ -0,0 +1,177 @@
+/*
+ * 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 "common.h"
+#include "posix.h"
+#include "git2/object.h"
+#include "git2/refs.h"
+#include "git2/refdb.h"
+#include "hash.h"
+#include "refdb.h"
+#include "refs.h"
+
+#include "git2/refdb_backend.h"
+
+int git_refdb_new(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+
+ assert(out && repo);
+
+ db = git__calloc(1, sizeof(*db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->repo = repo;
+
+ *out = db;
+ GIT_REFCOUNT_INC(db);
+ return 0;
+}
+
+int git_refdb_open(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+ git_refdb_backend *dir;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (git_refdb_new(&db, repo) < 0)
+ return -1;
+
+ /* Add the default (filesystem) backend */
+ if (git_refdb_backend_fs(&dir, repo, db) < 0) {
+ git_refdb_free(db);
+ return -1;
+ }
+
+ db->repo = repo;
+ db->backend = dir;
+
+ *out = db;
+ return 0;
+}
+
+int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+{
+ if (db->backend) {
+ if(db->backend->free)
+ db->backend->free(db->backend);
+ else
+ git__free(db->backend);
+ }
+
+ db->backend = backend;
+
+ return 0;
+}
+
+int git_refdb_compress(git_refdb *db)
+{
+ assert(db);
+
+ if (db->backend->compress) {
+ return db->backend->compress(db->backend);
+ }
+
+ return 0;
+}
+
+void git_refdb_free(git_refdb *db)
+{
+ if (db->backend) {
+ if(db->backend->free)
+ db->backend->free(db->backend);
+ else
+ git__free(db->backend);
+ }
+
+ git__free(db);
+}
+
+int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
+{
+ assert(exists && refdb && refdb->backend);
+
+ return refdb->backend->exists(exists, refdb->backend, ref_name);
+}
+
+int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
+{
+ assert(db && db->backend && ref_name);
+
+ return db->backend->lookup(out, db->backend, ref_name);
+}
+
+int git_refdb_foreach(
+ git_refdb *db,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ assert(db && db->backend);
+
+ return db->backend->foreach(db->backend, list_flags, callback, payload);
+}
+
+struct glob_cb_data {
+ const char *glob;
+ git_reference_foreach_cb callback;
+ void *payload;
+};
+
+static int fromglob_cb(const char *reference_name, void *payload)
+{
+ struct glob_cb_data *data = (struct glob_cb_data *)payload;
+
+ if (!p_fnmatch(data->glob, reference_name, 0))
+ return data->callback(reference_name, data->payload);
+
+ return 0;
+}
+
+int git_refdb_foreach_glob(
+ git_refdb *db,
+ const char *glob,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ int error;
+ struct glob_cb_data data;
+
+ assert(db && db->backend && glob && callback);
+
+ if(db->backend->foreach_glob != NULL)
+ error = db->backend->foreach_glob(db->backend,
+ glob, list_flags, callback, payload);
+ else {
+ data.glob = glob;
+ data.callback = callback;
+ data.payload = payload;
+
+ error = db->backend->foreach(db->backend,
+ list_flags, fromglob_cb, &data);
+ }
+
+ return error;
+}
+
+int git_refdb_write(git_refdb *db, const git_reference *ref)
+{
+ assert(db && db->backend);
+
+ return db->backend->write(db->backend, ref);
+}
+
+int git_refdb_delete(struct git_refdb *db, const git_reference *ref)
+{
+ assert(db && db->backend);
+
+ return db->backend->delete(db->backend, ref);
+}
diff --git a/src/refdb.h b/src/refdb.h
new file mode 100644
index 000000000..0969711b9
--- /dev/null
+++ b/src/refdb.h
@@ -0,0 +1,46 @@
+/*
+ * 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_refdb_h__
+#define INCLUDE_refdb_h__
+
+#include "git2/refdb.h"
+#include "repository.h"
+
+struct git_refdb {
+ git_refcount rc;
+ git_repository *repo;
+ git_refdb_backend *backend;
+};
+
+int git_refdb_exists(
+ int *exists,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_lookup(
+ git_reference **out,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_foreach(
+ git_refdb *refdb,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+int git_refdb_foreach_glob(
+ git_refdb *refdb,
+ const char *glob,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+int git_refdb_write(git_refdb *refdb, const git_reference *ref);
+
+int git_refdb_delete(struct git_refdb *refdb, const git_reference *ref);
+
+#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
new file mode 100644
index 000000000..f00bd72a0
--- /dev/null
+++ b/src/refdb_fs.c
@@ -0,0 +1,1023 @@
+/*
+ * 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 "refs.h"
+#include "hash.h"
+#include "repository.h"
+#include "fileops.h"
+#include "pack.h"
+#include "reflog.h"
+#include "config.h"
+#include "refdb.h"
+#include "refdb_fs.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/refdb.h>
+#include <git2/refdb_backend.h>
+
+GIT__USE_STRMAP;
+
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
+
+enum {
+ GIT_PACKREF_HAS_PEEL = 1,
+ GIT_PACKREF_WAS_LOOSE = 2
+};
+
+struct packref {
+ git_oid oid;
+ git_oid peel;
+ char flags;
+ char name[GIT_FLEX_ARRAY];
+};
+
+typedef struct refdb_fs_backend {
+ git_refdb_backend parent;
+
+ git_repository *repo;
+ const char *path;
+ git_refdb *refdb;
+
+ git_refcache refcache;
+} refdb_fs_backend;
+
+static int reference_read(
+ git_buf *file_content,
+ time_t *mtime,
+ const char *repo_path,
+ const char *ref_name,
+ int *updated)
+{
+ git_buf path = GIT_BUF_INIT;
+ int result;
+
+ assert(file_content && repo_path && ref_name);
+
+ /* Determine the full path of the file */
+ if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
+ return -1;
+
+ result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated);
+ git_buf_free(&path);
+
+ return result;
+}
+
+static int packed_parse_oid(
+ struct packref **ref_out,
+ const char **buffer_out,
+ const char *buffer_end)
+{
+ struct packref *ref = NULL;
+
+ const char *buffer = *buffer_out;
+ const char *refname_begin, *refname_end;
+
+ size_t refname_len;
+ git_oid id;
+
+ refname_begin = (buffer + GIT_OID_HEXSZ + 1);
+ if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
+ goto corrupt;
+
+ /* Is this a valid object id? */
+ if (git_oid_fromstr(&id, buffer) < 0)
+ goto corrupt;
+
+ refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
+ if (refname_end == NULL)
+ refname_end = buffer_end;
+
+ if (refname_end[-1] == '\r')
+ refname_end--;
+
+ refname_len = refname_end - refname_begin;
+
+ ref = git__malloc(sizeof(struct packref) + refname_len + 1);
+ GITERR_CHECK_ALLOC(ref);
+
+ memcpy(ref->name, refname_begin, refname_len);
+ ref->name[refname_len] = 0;
+
+ git_oid_cpy(&ref->oid, &id);
+
+ ref->flags = 0;
+
+ *ref_out = ref;
+ *buffer_out = refname_end + 1;
+
+ return 0;
+
+corrupt:
+ git__free(ref);
+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
+ return -1;
+}
+
+static int packed_parse_peel(
+ struct packref *tag_ref,
+ const char **buffer_out,
+ const char *buffer_end)
+{
+ const char *buffer = *buffer_out + 1;
+
+ assert(buffer[-1] == '^');
+
+ /* Ensure it's not the first entry of the file */
+ if (tag_ref == NULL)
+ goto corrupt;
+
+ /* Ensure reference is a tag */
+ if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
+ goto corrupt;
+
+ if (buffer + GIT_OID_HEXSZ > buffer_end)
+ goto corrupt;
+
+ /* Is this a valid object id? */
+ if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
+ goto corrupt;
+
+ buffer = buffer + GIT_OID_HEXSZ;
+ if (*buffer == '\r')
+ buffer++;
+
+ if (buffer != buffer_end) {
+ if (*buffer == '\n')
+ buffer++;
+ else
+ goto corrupt;
+ }
+
+ *buffer_out = buffer;
+ return 0;
+
+corrupt:
+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
+ return -1;
+}
+
+static int packed_load(refdb_fs_backend *backend)
+{
+ int result, updated;
+ git_buf packfile = GIT_BUF_INIT;
+ const char *buffer_start, *buffer_end;
+ git_refcache *ref_cache = &backend->refcache;
+
+ /* First we make sure we have allocated the hash table */
+ if (ref_cache->packfile == NULL) {
+ ref_cache->packfile = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(ref_cache->packfile);
+ }
+
+ result = reference_read(&packfile, &ref_cache->packfile_time,
+ backend->path, GIT_PACKEDREFS_FILE, &updated);
+
+ /*
+ * If we couldn't find the file, we need to clear the table and
+ * return. On any other error, we return that error. If everything
+ * went fine and the file wasn't updated, then there's nothing new
+ * for us here, so just return. Anything else means we need to
+ * refresh the packed refs.
+ */
+ if (result == GIT_ENOTFOUND) {
+ git_strmap_clear(ref_cache->packfile);
+ return 0;
+ }
+
+ if (result < 0)
+ return -1;
+
+ if (!updated)
+ return 0;
+
+ /*
+ * At this point, we want to refresh the packed refs. We already
+ * have the contents in our buffer.
+ */
+ git_strmap_clear(ref_cache->packfile);
+
+ buffer_start = (const char *)packfile.ptr;
+ buffer_end = (const char *)(buffer_start) + packfile.size;
+
+ while (buffer_start < buffer_end && buffer_start[0] == '#') {
+ buffer_start = strchr(buffer_start, '\n');
+ if (buffer_start == NULL)
+ goto parse_failed;
+
+ buffer_start++;
+ }
+
+ while (buffer_start < buffer_end) {
+ int err;
+ struct packref *ref = NULL;
+
+ if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
+ goto parse_failed;
+
+ if (buffer_start[0] == '^') {
+ if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
+ goto parse_failed;
+ }
+
+ git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
+ if (err < 0)
+ goto parse_failed;
+ }
+
+ git_buf_free(&packfile);
+ return 0;
+
+parse_failed:
+ git_strmap_free(ref_cache->packfile);
+ ref_cache->packfile = NULL;
+ git_buf_free(&packfile);
+ return -1;
+}
+
+static int loose_parse_oid(git_oid *oid, git_buf *file_content)
+{
+ size_t len;
+ const char *str;
+
+ len = git_buf_len(file_content);
+ if (len < GIT_OID_HEXSZ)
+ goto corrupted;
+
+ /* str is guranteed to be zero-terminated */
+ str = git_buf_cstr(file_content);
+
+ /* we need to get 40 OID characters from the file */
+ if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
+ goto corrupted;
+
+ /* If the file is longer than 40 chars, the 41st must be a space */
+ str += GIT_OID_HEXSZ;
+ if (*str == '\0' || git__isspace(*str))
+ return 0;
+
+corrupted:
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return -1;
+}
+
+static int loose_lookup_to_packfile(
+ struct packref **ref_out,
+ refdb_fs_backend *backend,
+ const char *name)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ struct packref *ref = NULL;
+ size_t name_len;
+
+ *ref_out = NULL;
+
+ if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0)
+ return -1;
+
+ git_buf_rtrim(&ref_file);
+
+ name_len = strlen(name);
+ ref = git__malloc(sizeof(struct packref) + name_len + 1);
+ GITERR_CHECK_ALLOC(ref);
+
+ memcpy(ref->name, name, name_len);
+ ref->name[name_len] = 0;
+
+ if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
+ git_buf_free(&ref_file);
+ git__free(ref);
+ return -1;
+ }
+
+ ref->flags = GIT_PACKREF_WAS_LOOSE;
+
+ *ref_out = ref;
+ git_buf_free(&ref_file);
+ return 0;
+}
+
+
+static int _dirent_loose_load(void *data, git_buf *full_path)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)data;
+ void *old_ref = NULL;
+ struct packref *ref;
+ const char *file_path;
+ int err;
+
+ if (git_path_isdir(full_path->ptr) == true)
+ return git_path_direach(full_path, _dirent_loose_load, backend);
+
+ file_path = full_path->ptr + strlen(backend->path);
+
+ if (loose_lookup_to_packfile(&ref, backend, file_path) < 0)
+ return -1;
+
+ git_strmap_insert2(
+ backend->refcache.packfile, ref->name, ref, old_ref, err);
+ if (err < 0) {
+ git__free(ref);
+ return -1;
+ }
+
+ git__free(old_ref);
+ return 0;
+}
+
+/*
+ * Load all the loose references from the repository
+ * into the in-memory Packfile, and build a vector with
+ * all the references so it can be written back to
+ * disk.
+ */
+static int packed_loadloose(refdb_fs_backend *backend)
+{
+ git_buf refs_path = GIT_BUF_INIT;
+ int result;
+
+ /* the packfile must have been previously loaded! */
+ assert(backend->refcache.packfile);
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ /*
+ * Load all the loose files from disk into the Packfile table.
+ * This will overwrite any old packed entries with their
+ * updated loose versions
+ */
+ result = git_path_direach(&refs_path, _dirent_loose_load, backend);
+ git_buf_free(&refs_path);
+
+ return result;
+}
+
+static int refdb_fs_backend__exists(
+ int *exists,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
+ return -1;
+
+ if (git_path_isfile(ref_path.ptr) == true ||
+ git_strmap_exists(backend->refcache.packfile, ref_path.ptr))
+ *exists = 1;
+ else
+ *exists = 0;
+
+ git_buf_free(&ref_path);
+ return 0;
+}
+
+static const char *loose_parse_symbolic(git_buf *file_content)
+{
+ const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
+ const char *refname_start;
+
+ refname_start = (const char *)file_content->ptr;
+
+ if (git_buf_len(file_content) < header_len + 1) {
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return NULL;
+ }
+
+ /*
+ * Assume we have already checked for the header
+ * before calling this function
+ */
+ refname_start += header_len;
+
+ return refname_start;
+}
+
+static int loose_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ const char *target;
+ git_oid oid;
+ git_buf ref_file = GIT_BUF_INIT;
+ int error = 0;
+
+ error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL);
+
+ if (error < 0)
+ goto done;
+
+ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
+ git_buf_rtrim(&ref_file);
+
+ if ((target = loose_parse_symbolic(&ref_file)) == NULL) {
+ error = -1;
+ goto done;
+ }
+
+ *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);
+ } else {
+ if ((error = loose_parse_oid(&oid, &ref_file)) < 0)
+ goto done;
+
+ *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL);
+ }
+
+ if (*out == NULL)
+ error = -1;
+
+done:
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int packed_map_entry(
+ struct packref **entry,
+ khiter_t *pos,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ git_strmap *packfile_refs;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ /* Look up on the packfile */
+ packfile_refs = backend->refcache.packfile;
+
+ *pos = git_strmap_lookup_index(packfile_refs, ref_name);
+
+ if (!git_strmap_valid_index(packfile_refs, *pos)) {
+ giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name);
+ return GIT_ENOTFOUND;
+ }
+
+ *entry = git_strmap_value_at(packfile_refs, *pos);
+
+ return 0;
+}
+
+static int packed_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ struct packref *entry;
+ khiter_t pos;
+ int error = 0;
+
+ if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0)
+ return error;
+
+ if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL)
+ return -1;
+
+ return 0;
+}
+
+static int refdb_fs_backend__lookup(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend;
+ int result;
+
+ assert(_backend);
+
+ backend = (refdb_fs_backend *)_backend;
+
+ if ((result = loose_lookup(out, backend, ref_name)) == 0)
+ return 0;
+
+ /* only try to lookup this reference on the packfile if it
+ * wasn't found on the loose refs; not if there was a critical error */
+ if (result == GIT_ENOTFOUND) {
+ giterr_clear();
+ result = packed_lookup(out, backend, ref_name);
+ }
+
+ return result;
+}
+
+struct dirent_list_data {
+ refdb_fs_backend *backend;
+ size_t repo_path_len;
+ unsigned int list_type:2;
+
+ git_reference_foreach_cb callback;
+ void *callback_payload;
+ int callback_error;
+};
+
+static git_ref_t loose_guess_rtype(const git_buf *full_path)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ git_ref_t type;
+
+ type = GIT_REF_INVALID;
+
+ if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
+ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
+ type = GIT_REF_SYMBOLIC;
+ else
+ type = GIT_REF_OID;
+ }
+
+ git_buf_free(&ref_file);
+ return type;
+}
+
+static int _dirent_loose_listall(void *_data, git_buf *full_path)
+{
+ struct dirent_list_data *data = (struct dirent_list_data *)_data;
+ const char *file_path = full_path->ptr + data->repo_path_len;
+
+ if (git_path_isdir(full_path->ptr) == true)
+ return git_path_direach(full_path, _dirent_loose_listall, _data);
+
+ /* do not add twice a reference that exists already in the packfile */
+ if (git_strmap_exists(data->backend->refcache.packfile, file_path))
+ return 0;
+
+ if (data->list_type != GIT_REF_LISTALL) {
+ if ((data->list_type & loose_guess_rtype(full_path)) == 0)
+ return 0; /* we are filtering out this reference */
+ }
+
+ /* Locked references aren't returned */
+ if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
+ return 0;
+
+ if (data->callback(file_path, data->callback_payload))
+ data->callback_error = GIT_EUSER;
+
+ return data->callback_error;
+}
+
+static int refdb_fs_backend__foreach(
+ git_refdb_backend *_backend,
+ unsigned int list_type,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ refdb_fs_backend *backend;
+ int result;
+ struct dirent_list_data data;
+ git_buf refs_path = GIT_BUF_INIT;
+ const char *ref_name;
+ void *ref = NULL;
+
+ GIT_UNUSED(ref);
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ /* list all the packed references first */
+ if (list_type & GIT_REF_OID) {
+ git_strmap_foreach(backend->refcache.packfile, ref_name, ref, {
+ if (callback(ref_name, payload))
+ return GIT_EUSER;
+ });
+ }
+
+ /* now list the loose references, trying not to
+ * duplicate the ref names already in the packed-refs file */
+
+ data.repo_path_len = strlen(backend->path);
+ data.list_type = list_type;
+ data.backend = backend;
+ data.callback = callback;
+ data.callback_payload = payload;
+ data.callback_error = 0;
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
+
+ git_buf_free(&refs_path);
+
+ return data.callback_error ? GIT_EUSER : result;
+}
+
+static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ /* Remove a possibly existing empty directory hierarchy
+ * which name would collide with the reference name
+ */
+ if (git_futils_rmdir_r(ref->name, backend->path,
+ GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)
+ return -1;
+
+ if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
+ git_buf_free(&ref_path);
+ return -1;
+ }
+
+ git_buf_free(&ref_path);
+
+ if (ref->type == GIT_REF_OID) {
+ char oid[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(oid, &ref->target.oid);
+ oid[GIT_OID_HEXSZ] = '\0';
+
+ git_filebuf_printf(&file, "%s\n", oid);
+
+ } else if (ref->type == GIT_REF_SYMBOLIC) {
+ git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
+ } else {
+ assert(0); /* don't let this happen */
+ }
+
+ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
+}
+
+static int packed_sort(const void *a, const void *b)
+{
+ const struct packref *ref_a = (const struct packref *)a;
+ const struct packref *ref_b = (const struct packref *)b;
+
+ return strcmp(ref_a->name, ref_b->name);
+}
+
+/*
+ * Find out what object this reference resolves to.
+ *
+ * For references that point to a 'big' tag (e.g. an
+ * actual tag object on the repository), we need to
+ * cache on the packfile the OID of the object to
+ * which that 'big tag' is pointing to.
+ */
+static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
+{
+ git_object *object;
+
+ if (ref->flags & GIT_PACKREF_HAS_PEEL)
+ return 0;
+
+ /*
+ * Only applies to tags, i.e. references
+ * in the /refs/tags folder
+ */
+ if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
+ return 0;
+
+ /*
+ * Find the tagged object in the repository
+ */
+ if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ /*
+ * If the tagged object is a Tag object, we need to resolve it;
+ * if the ref is actually a 'weak' ref, we don't need to resolve
+ * anything.
+ */
+ if (git_object_type(object) == GIT_OBJ_TAG) {
+ git_tag *tag = (git_tag *)object;
+
+ /*
+ * Find the object pointed at by this tag
+ */
+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));
+ ref->flags |= GIT_PACKREF_HAS_PEEL;
+
+ /*
+ * The reference has now cached the resolved OID, and is
+ * marked at such. When written to the packfile, it'll be
+ * accompanied by this resolved oid
+ */
+ }
+
+ git_object_free(object);
+ return 0;
+}
+
+/*
+ * Write a single reference into a packfile
+ */
+static int packed_write_ref(struct packref *ref, git_filebuf *file)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(oid, &ref->oid);
+ oid[GIT_OID_HEXSZ] = 0;
+
+ /*
+ * For references that peel to an object in the repo, we must
+ * write the resulting peel on a separate line, e.g.
+ *
+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
+ *
+ * This obviously only applies to tags.
+ * The required peels have already been loaded into `ref->peel_target`.
+ */
+ if (ref->flags & GIT_PACKREF_HAS_PEEL) {
+ char peel[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(peel, &ref->peel);
+ peel[GIT_OID_HEXSZ] = 0;
+
+ if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
+ return -1;
+ } else {
+ if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Remove all loose references
+ *
+ * Once we have successfully written a packfile,
+ * all the loose references that were packed must be
+ * removed from disk.
+ *
+ * This is a dangerous method; make sure the packfile
+ * is well-written, because we are destructing references
+ * here otherwise.
+ */
+static int packed_remove_loose(
+ refdb_fs_backend *backend,
+ git_vector *packing_list)
+{
+ size_t i;
+ git_buf full_path = GIT_BUF_INIT;
+ int failed = 0;
+
+ for (i = 0; i < packing_list->length; ++i) {
+ struct packref *ref = git_vector_get(packing_list, i);
+
+ if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
+ continue;
+
+ if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
+ return -1; /* critical; do not try to recover on oom */
+
+ if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
+ if (failed)
+ continue;
+
+ giterr_set(GITERR_REFERENCE,
+ "Failed to remove loose reference '%s' after packing: %s",
+ full_path.ptr, strerror(errno));
+
+ failed = 1;
+ }
+
+ /*
+ * if we fail to remove a single file, this is *not* good,
+ * but we should keep going and remove as many as possible.
+ * After we've removed as many files as possible, we return
+ * the error code anyway.
+ */
+ }
+
+ git_buf_free(&full_path);
+ return failed ? -1 : 0;
+}
+
+/*
+ * Write all the contents in the in-memory packfile to disk.
+ */
+static int packed_write(refdb_fs_backend *backend)
+{
+ git_filebuf pack_file = GIT_FILEBUF_INIT;
+ size_t i;
+ git_buf pack_file_path = GIT_BUF_INIT;
+ git_vector packing_list;
+ unsigned int total_refs;
+
+ assert(backend && backend->refcache.packfile);
+
+ total_refs =
+ (unsigned int)git_strmap_num_entries(backend->refcache.packfile);
+
+ if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
+ return -1;
+
+ /* Load all the packfile into a vector */
+ {
+ struct packref *reference;
+
+ /* cannot fail: vector already has the right size */
+ git_strmap_foreach_value(backend->refcache.packfile, reference, {
+ git_vector_insert(&packing_list, reference);
+ });
+ }
+
+ /* sort the vector so the entries appear sorted on the packfile */
+ git_vector_sort(&packing_list);
+
+ /* Now we can open the file! */
+ if (git_buf_joinpath(&pack_file_path,
+ backend->path, GIT_PACKEDREFS_FILE) < 0)
+ goto cleanup_memory;
+
+ if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
+ goto cleanup_packfile;
+
+ /* Packfiles have a header... apparently
+ * This is in fact not required, but we might as well print it
+ * just for kicks */
+ if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
+ goto cleanup_packfile;
+
+ for (i = 0; i < packing_list.length; ++i) {
+ struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
+
+ if (packed_find_peel(backend, ref) < 0)
+ goto cleanup_packfile;
+
+ if (packed_write_ref(ref, &pack_file) < 0)
+ goto cleanup_packfile;
+ }
+
+ /* if we've written all the references properly, we can commit
+ * the packfile to make the changes effective */
+ if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
+ goto cleanup_memory;
+
+ /* when and only when the packfile has been properly written,
+ * we can go ahead and remove the loose refs */
+ if (packed_remove_loose(backend, &packing_list) < 0)
+ goto cleanup_memory;
+
+ {
+ struct stat st;
+ if (p_stat(pack_file_path.ptr, &st) == 0)
+ backend->refcache.packfile_time = st.st_mtime;
+ }
+
+ git_vector_free(&packing_list);
+ git_buf_free(&pack_file_path);
+
+ /* we're good now */
+ return 0;
+
+cleanup_packfile:
+ git_filebuf_cleanup(&pack_file);
+
+cleanup_memory:
+ git_vector_free(&packing_list);
+ git_buf_free(&pack_file_path);
+
+ return -1;
+}
+
+static int refdb_fs_backend__write(
+ git_refdb_backend *_backend,
+ const git_reference *ref)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ return loose_write(backend, ref);
+}
+
+static int refdb_fs_backend__delete(
+ git_refdb_backend *_backend,
+ const git_reference *ref)
+{
+ refdb_fs_backend *backend;
+ git_repository *repo;
+ git_buf loose_path = GIT_BUF_INIT;
+ struct packref *pack_ref;
+ khiter_t pack_ref_pos;
+ int error = 0, pack_error;
+ bool loose_deleted;
+
+ assert(_backend);
+ assert(ref);
+
+ backend = (refdb_fs_backend *)_backend;
+ repo = backend->repo;
+
+ /* If a loose reference exists, remove it from the filesystem */
+
+ if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0)
+ return -1;
+
+ if (git_path_isfile(loose_path.ptr)) {
+ error = p_unlink(loose_path.ptr);
+ loose_deleted = 1;
+ }
+
+ git_buf_free(&loose_path);
+
+ if (error != 0)
+ return error;
+
+ /* If a packed reference exists, remove it from the packfile and repack */
+
+ if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) {
+ git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos);
+ git__free(pack_ref);
+
+ error = packed_write(backend);
+ }
+
+ if (pack_error == GIT_ENOTFOUND)
+ error = loose_deleted ? 0 : GIT_ENOTFOUND;
+ else
+ error = pack_error;
+
+ return error;
+}
+
+static int refdb_fs_backend__compress(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0 || /* load the existing packfile */
+ packed_loadloose(backend) < 0 || /* add all the loose refs */
+ packed_write(backend) < 0) /* write back to disk */
+ return -1;
+
+ return 0;
+}
+
+static void refcache_free(git_refcache *refs)
+{
+ assert(refs);
+
+ if (refs->packfile) {
+ struct packref *reference;
+
+ git_strmap_foreach_value(refs->packfile, reference, {
+ git__free(reference);
+ });
+
+ git_strmap_free(refs->packfile);
+ }
+}
+
+static void refdb_fs_backend__free(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ refcache_free(&backend->refcache);
+ git__free(backend);
+}
+
+int git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repository,
+ git_refdb *refdb)
+{
+ refdb_fs_backend *backend;
+
+ backend = git__calloc(1, sizeof(refdb_fs_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->repo = repository;
+ backend->path = repository->path_repository;
+ backend->refdb = refdb;
+
+ backend->parent.exists = &refdb_fs_backend__exists;
+ backend->parent.lookup = &refdb_fs_backend__lookup;
+ backend->parent.foreach = &refdb_fs_backend__foreach;
+ backend->parent.write = &refdb_fs_backend__write;
+ backend->parent.delete = &refdb_fs_backend__delete;
+ backend->parent.compress = &refdb_fs_backend__compress;
+ backend->parent.free = &refdb_fs_backend__free;
+
+ *backend_out = (git_refdb_backend *)backend;
+ return 0;
+}
diff --git a/src/refdb_fs.h b/src/refdb_fs.h
new file mode 100644
index 000000000..79e296833
--- /dev/null
+++ b/src/refdb_fs.h
@@ -0,0 +1,15 @@
+/*
+ * 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_refdb_fs_h__
+#define INCLUDE_refdb_fs_h__
+
+typedef struct {
+ git_strmap *packfile;
+ time_t packfile_time;
+} git_refcache;
+
+#endif
diff --git a/src/reflog.c b/src/reflog.c
index 432680b99..8c133fe53 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -163,7 +163,7 @@ fail:
void git_reflog_free(git_reflog *reflog)
{
- unsigned int i;
+ size_t i;
git_reflog_entry *entry;
if (reflog == NULL)
diff --git a/src/refs.c b/src/refs.c
index 113cadad5..dde2f51a9 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -11,11 +11,15 @@
#include "fileops.h"
#include "pack.h"
#include "reflog.h"
+#include "refdb.h"
#include <git2/tag.h>
#include <git2/object.h>
#include <git2/oid.h>
#include <git2/branch.h>
+#include <git2/refs.h>
+#include <git2/refdb.h>
+#include <git2/refdb_backend.h>
GIT__USE_STRMAP;
@@ -27,786 +31,55 @@ enum {
GIT_PACKREF_WAS_LOOSE = 2
};
-struct packref {
- git_oid oid;
- git_oid peel;
- char flags;
- char name[GIT_FLEX_ARRAY];
-};
-
-static int reference_read(
- git_buf *file_content,
- time_t *mtime,
- const char *repo_path,
- const char *ref_name,
- int *updated);
-
-/* loose refs */
-static int loose_parse_symbolic(git_reference *ref, git_buf *file_content);
-static int loose_parse_oid(git_oid *ref, git_buf *file_content);
-static int loose_lookup(git_reference *ref);
-static int loose_lookup_to_packfile(struct packref **ref_out,
- git_repository *repo, const char *name);
-static int loose_write(git_reference *ref);
-
-/* packed refs */
-static int packed_parse_peel(struct packref *tag_ref,
- const char **buffer_out, const char *buffer_end);
-static int packed_parse_oid(struct packref **ref_out,
- const char **buffer_out, const char *buffer_end);
-static int packed_load(git_repository *repo);
-static int packed_loadloose(git_repository *repository);
-static int packed_write_ref(struct packref *ref, git_filebuf *file);
-static int packed_find_peel(git_repository *repo, struct packref *ref);
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
-static int packed_sort(const void *a, const void *b);
-static int packed_lookup(git_reference *ref);
-static int packed_write(git_repository *repo);
-
-/* internal helpers */
-static int reference_path_available(git_repository *repo,
- const char *ref, const char *old_ref);
-static int reference_delete(git_reference *ref);
-static int reference_lookup(git_reference *ref);
-
-void git_reference_free(git_reference *reference)
-{
- if (reference == NULL)
- return;
-
- git__free(reference->name);
- reference->name = NULL;
-
- if (reference->flags & GIT_REF_SYMBOLIC) {
- git__free(reference->target.symbolic);
- reference->target.symbolic = NULL;
- }
-
- git__free(reference);
-}
-
-static int reference_alloc(
- git_reference **ref_out,
- git_repository *repo,
- const char *name)
-{
- git_reference *reference = NULL;
-
- assert(ref_out && repo && name);
-
- reference = git__malloc(sizeof(git_reference));
- GITERR_CHECK_ALLOC(reference);
-
- memset(reference, 0x0, sizeof(git_reference));
- reference->owner = repo;
-
- reference->name = git__strdup(name);
- GITERR_CHECK_ALLOC(reference->name);
-
- *ref_out = reference;
- return 0;
-}
-
-static int reference_read(
- git_buf *file_content,
- time_t *mtime,
- const char *repo_path,
- const char *ref_name,
- int *updated)
-{
- git_buf path = GIT_BUF_INIT;
- int result;
-
- assert(file_content && repo_path && ref_name);
-
- /* Determine the full path of the file */
- if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
- return -1;
-
- result = git_futils_readbuffer_updated(
- file_content, path.ptr, mtime, NULL, updated);
- git_buf_free(&path);
-
- return result;
-}
-
-static int loose_parse_symbolic(git_reference *ref, git_buf *file_content)
-{
- const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
- const char *refname_start;
-
- refname_start = (const char *)file_content->ptr;
-
- if (git_buf_len(file_content) < header_len + 1) {
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
- return -1;
- }
-
- /*
- * Assume we have already checked for the header
- * before calling this function
- */
- refname_start += header_len;
-
- ref->target.symbolic = git__strdup(refname_start);
- GITERR_CHECK_ALLOC(ref->target.symbolic);
-
- return 0;
-}
-
-static int loose_parse_oid(git_oid *oid, git_buf *file_content)
-{
- size_t len;
- const char *str;
-
- len = git_buf_len(file_content);
- if (len < GIT_OID_HEXSZ)
- goto corrupted;
-
- /* str is guranteed to be zero-terminated */
- str = git_buf_cstr(file_content);
-
- /* we need to get 40 OID characters from the file */
- if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
- goto corrupted;
-
- /* If the file is longer than 40 chars, the 41st must be a space */
- str += GIT_OID_HEXSZ;
- if (*str == '\0' || git__isspace(*str))
- return 0;
-
-corrupted:
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
- return -1;
-}
-
-static git_ref_t loose_guess_rtype(const git_buf *full_path)
-{
- git_buf ref_file = GIT_BUF_INIT;
- git_ref_t type;
-
- type = GIT_REF_INVALID;
-
- if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
- type = GIT_REF_SYMBOLIC;
- else
- type = GIT_REF_OID;
- }
-
- git_buf_free(&ref_file);
- return type;
-}
-
-static int loose_lookup(git_reference *ref)
-{
- int result, updated;
- git_buf ref_file = GIT_BUF_INIT;
-
- result = reference_read(&ref_file, &ref->mtime,
- ref->owner->path_repository, ref->name, &updated);
-
- if (result < 0)
- return result;
-
- if (!updated)
- return 0;
-
- if (ref->flags & GIT_REF_SYMBOLIC) {
- git__free(ref->target.symbolic);
- ref->target.symbolic = NULL;
- }
-
- ref->flags = 0;
-
- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
- ref->flags |= GIT_REF_SYMBOLIC;
- git_buf_rtrim(&ref_file);
- result = loose_parse_symbolic(ref, &ref_file);
- } else {
- ref->flags |= GIT_REF_OID;
- result = loose_parse_oid(&ref->target.oid, &ref_file);
- }
-
- git_buf_free(&ref_file);
- return result;
-}
-
-static int loose_lookup_to_packfile(
- struct packref **ref_out,
- git_repository *repo,
- const char *name)
-{
- git_buf ref_file = GIT_BUF_INIT;
- struct packref *ref = NULL;
- size_t name_len;
-
- *ref_out = NULL;
-
- if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0)
- return -1;
-
- git_buf_rtrim(&ref_file);
-
- name_len = strlen(name);
- ref = git__malloc(sizeof(struct packref) + name_len + 1);
- GITERR_CHECK_ALLOC(ref);
-
- memcpy(ref->name, name, name_len);
- ref->name[name_len] = 0;
-
- if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
- git_buf_free(&ref_file);
- git__free(ref);
- return -1;
- }
-
- ref->flags = GIT_PACKREF_WAS_LOOSE;
-
- *ref_out = ref;
- git_buf_free(&ref_file);
- return 0;
-}
-
-static int loose_write(git_reference *ref)
-{
- git_filebuf file = GIT_FILEBUF_INIT;
- git_buf ref_path = GIT_BUF_INIT;
- struct stat st;
-
- /* Remove a possibly existing empty directory hierarchy
- * which name would collide with the reference name
- */
- if (git_futils_rmdir_r(ref->name, ref->owner->path_repository,
- GIT_RMDIR_SKIP_NONEMPTY) < 0)
- return -1;
-
- if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
- if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
- git_buf_free(&ref_path);
- return -1;
- }
-
- git_buf_free(&ref_path);
-
- if (ref->flags & GIT_REF_OID) {
- char oid[GIT_OID_HEXSZ + 1];
-
- git_oid_fmt(oid, &ref->target.oid);
- oid[GIT_OID_HEXSZ] = '\0';
-
- git_filebuf_printf(&file, "%s\n", oid);
-
- } else if (ref->flags & GIT_REF_SYMBOLIC) {
- git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
- } else {
- assert(0); /* don't let this happen */
- }
-
- if (p_stat(ref_path.ptr, &st) == 0)
- ref->mtime = st.st_mtime;
-
- return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
-}
-
-static int packed_parse_peel(
- struct packref *tag_ref,
- const char **buffer_out,
- const char *buffer_end)
-{
- const char *buffer = *buffer_out + 1;
-
- assert(buffer[-1] == '^');
-
- /* Ensure it's not the first entry of the file */
- if (tag_ref == NULL)
- goto corrupt;
-
- /* Ensure reference is a tag */
- if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
- goto corrupt;
-
- if (buffer + GIT_OID_HEXSZ > buffer_end)
- goto corrupt;
-
- /* Is this a valid object id? */
- if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
- goto corrupt;
-
- buffer = buffer + GIT_OID_HEXSZ;
- if (*buffer == '\r')
- buffer++;
-
- if (buffer != buffer_end) {
- if (*buffer == '\n')
- buffer++;
- else
- goto corrupt;
- }
-
- *buffer_out = buffer;
- return 0;
-
-corrupt:
- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
- return -1;
-}
-
-static int packed_parse_oid(
- struct packref **ref_out,
- const char **buffer_out,
- const char *buffer_end)
-{
- struct packref *ref = NULL;
-
- const char *buffer = *buffer_out;
- const char *refname_begin, *refname_end;
-
- size_t refname_len;
- git_oid id;
-
- refname_begin = (buffer + GIT_OID_HEXSZ + 1);
- if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
- goto corrupt;
-
- /* Is this a valid object id? */
- if (git_oid_fromstr(&id, buffer) < 0)
- goto corrupt;
-
- refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
- if (refname_end == NULL)
- refname_end = buffer_end;
-
- if (refname_end[-1] == '\r')
- refname_end--;
- refname_len = refname_end - refname_begin;
-
- ref = git__malloc(sizeof(struct packref) + refname_len + 1);
- GITERR_CHECK_ALLOC(ref);
-
- memcpy(ref->name, refname_begin, refname_len);
- ref->name[refname_len] = 0;
-
- git_oid_cpy(&ref->oid, &id);
-
- ref->flags = 0;
-
- *ref_out = ref;
- *buffer_out = refname_end + 1;
-
- return 0;
-
-corrupt:
- git__free(ref);
- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
- return -1;
-}
-
-static int packed_load(git_repository *repo)
-{
- int result, updated;
- git_buf packfile = GIT_BUF_INIT;
- const char *buffer_start, *buffer_end;
- git_refcache *ref_cache = &repo->references;
-
- /* First we make sure we have allocated the hash table */
- if (ref_cache->packfile == NULL) {
- ref_cache->packfile = git_strmap_alloc();
- GITERR_CHECK_ALLOC(ref_cache->packfile);
- }
-
- result = reference_read(&packfile, &ref_cache->packfile_time,
- repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
-
- /*
- * If we couldn't find the file, we need to clear the table and
- * return. On any other error, we return that error. If everything
- * went fine and the file wasn't updated, then there's nothing new
- * for us here, so just return. Anything else means we need to
- * refresh the packed refs.
- */
- if (result == GIT_ENOTFOUND) {
- git_strmap_clear(ref_cache->packfile);
- return 0;
- }
-
- if (result < 0)
- return -1;
-
- if (!updated)
- return 0;
-
- /*
- * At this point, we want to refresh the packed refs. We already
- * have the contents in our buffer.
- */
- git_strmap_clear(ref_cache->packfile);
-
- buffer_start = (const char *)packfile.ptr;
- buffer_end = (const char *)(buffer_start) + packfile.size;
-
- while (buffer_start < buffer_end && buffer_start[0] == '#') {
- buffer_start = strchr(buffer_start, '\n');
- if (buffer_start == NULL)
- goto parse_failed;
-
- buffer_start++;
- }
-
- while (buffer_start < buffer_end) {
- int err;
- struct packref *ref = NULL;
-
- if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
- goto parse_failed;
-
- if (buffer_start[0] == '^') {
- if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
- goto parse_failed;
- }
-
- git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
- if (err < 0)
- goto parse_failed;
- }
-
- git_buf_free(&packfile);
- return 0;
-
-parse_failed:
- git_strmap_free(ref_cache->packfile);
- ref_cache->packfile = NULL;
- git_buf_free(&packfile);
- return -1;
-}
-
-
-struct dirent_list_data {
- git_repository *repo;
- size_t repo_path_len;
- unsigned int list_flags;
-
- int (*callback)(const char *, void *);
- void *callback_payload;
- int callback_error;
-};
-
-static int _dirent_loose_listall(void *_data, git_buf *full_path)
-{
- struct dirent_list_data *data = (struct dirent_list_data *)_data;
- const char *file_path = full_path->ptr + data->repo_path_len;
-
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_listall, _data);
-
- /* do not add twice a reference that exists already in the packfile */
- if ((data->list_flags & GIT_REF_PACKED) != 0 &&
- git_strmap_exists(data->repo->references.packfile, file_path))
- return 0;
-
- if (data->list_flags != GIT_REF_LISTALL) {
- if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
- return 0; /* we are filtering out this reference */
- }
-
- /* Locked references aren't returned */
- if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
- return 0;
-
- if (data->callback(file_path, data->callback_payload))
- data->callback_error = GIT_EUSER;
-
- return data->callback_error;
-}
-
-static int _dirent_loose_load(void *data, git_buf *full_path)
-{
- git_repository *repository = (git_repository *)data;
- void *old_ref = NULL;
- struct packref *ref;
- const char *file_path;
- int err;
-
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_load, repository);
-
- file_path = full_path->ptr + strlen(repository->path_repository);
-
- if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)
- return -1;
-
- git_strmap_insert2(
- repository->references.packfile, ref->name, ref, old_ref, err);
- if (err < 0) {
- git__free(ref);
- return -1;
- }
-
- git__free(old_ref);
- return 0;
-}
-
-/*
- * Load all the loose references from the repository
- * into the in-memory Packfile, and build a vector with
- * all the references so it can be written back to
- * disk.
- */
-static int packed_loadloose(git_repository *repository)
+git_reference *git_reference__alloc(
+ git_refdb *refdb,
+ const char *name,
+ const git_oid *oid,
+ const char *symbolic)
{
- git_buf refs_path = GIT_BUF_INIT;
- int result;
-
- /* the packfile must have been previously loaded! */
- assert(repository->references.packfile);
-
- if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)
- return -1;
-
- /*
- * Load all the loose files from disk into the Packfile table.
- * This will overwrite any old packed entries with their
- * updated loose versions
- */
- result = git_path_direach(&refs_path, _dirent_loose_load, repository);
- git_buf_free(&refs_path);
-
- return result;
-}
+ git_reference *ref;
+ size_t namelen;
-/*
- * Write a single reference into a packfile
- */
-static int packed_write_ref(struct packref *ref, git_filebuf *file)
-{
- char oid[GIT_OID_HEXSZ + 1];
+ assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic)));
- git_oid_fmt(oid, &ref->oid);
- oid[GIT_OID_HEXSZ] = 0;
+ namelen = strlen(name);
- /*
- * For references that peel to an object in the repo, we must
- * write the resulting peel on a separate line, e.g.
- *
- * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
- * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
- *
- * This obviously only applies to tags.
- * The required peels have already been loaded into `ref->peel_target`.
- */
- if (ref->flags & GIT_PACKREF_HAS_PEEL) {
- char peel[GIT_OID_HEXSZ + 1];
- git_oid_fmt(peel, &ref->peel);
- peel[GIT_OID_HEXSZ] = 0;
+ if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
+ return NULL;
- if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
- return -1;
+ if (oid) {
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
} else {
- if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
- return -1;
- }
-
- return 0;
-}
-
-/*
- * Find out what object this reference resolves to.
- *
- * For references that point to a 'big' tag (e.g. an
- * actual tag object on the repository), we need to
- * cache on the packfile the OID of the object to
- * which that 'big tag' is pointing to.
- */
-static int packed_find_peel(git_repository *repo, struct packref *ref)
-{
- git_object *object;
-
- if (ref->flags & GIT_PACKREF_HAS_PEEL)
- return 0;
-
- /*
- * Only applies to tags, i.e. references
- * in the /refs/tags folder
- */
- if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
- return 0;
+ ref->type = GIT_REF_SYMBOLIC;
- /*
- * Find the tagged object in the repository
- */
- if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)
- return -1;
-
- /*
- * If the tagged object is a Tag object, we need to resolve it;
- * if the ref is actually a 'weak' ref, we don't need to resolve
- * anything.
- */
- if (git_object_type(object) == GIT_OBJ_TAG) {
- git_tag *tag = (git_tag *)object;
-
- /*
- * Find the object pointed at by this tag
- */
- git_oid_cpy(&ref->peel, git_tag_target_id(tag));
- ref->flags |= GIT_PACKREF_HAS_PEEL;
-
- /*
- * The reference has now cached the resolved OID, and is
- * marked at such. When written to the packfile, it'll be
- * accompanied by this resolved oid
- */
- }
-
- git_object_free(object);
- return 0;
-}
-
-/*
- * Remove all loose references
- *
- * Once we have successfully written a packfile,
- * all the loose references that were packed must be
- * removed from disk.
- *
- * This is a dangerous method; make sure the packfile
- * is well-written, because we are destructing references
- * here otherwise.
- */
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
-{
- unsigned int i;
- git_buf full_path = GIT_BUF_INIT;
- int failed = 0;
-
- for (i = 0; i < packing_list->length; ++i) {
- struct packref *ref = git_vector_get(packing_list, i);
-
- if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
- continue;
-
- if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0)
- return -1; /* critical; do not try to recover on oom */
-
- if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
- if (failed)
- continue;
-
- giterr_set(GITERR_REFERENCE,
- "Failed to remove loose reference '%s' after packing: %s",
- full_path.ptr, strerror(errno));
-
- failed = 1;
+ if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) {
+ git__free(ref);
+ return NULL;
}
-
- /*
- * if we fail to remove a single file, this is *not* good,
- * but we should keep going and remove as many as possible.
- * After we've removed as many files as possible, we return
- * the error code anyway.
- */
}
- git_buf_free(&full_path);
- return failed ? -1 : 0;
-}
-
-static int packed_sort(const void *a, const void *b)
-{
- const struct packref *ref_a = (const struct packref *)a;
- const struct packref *ref_b = (const struct packref *)b;
+ ref->db = refdb;
+ memcpy(ref->name, name, namelen + 1);
- return strcmp(ref_a->name, ref_b->name);
+ return ref;
}
-/*
- * Write all the contents in the in-memory packfile to disk.
- */
-static int packed_write(git_repository *repo)
+void git_reference_free(git_reference *reference)
{
- git_filebuf pack_file = GIT_FILEBUF_INIT;
- unsigned int i;
- git_buf pack_file_path = GIT_BUF_INIT;
- git_vector packing_list;
- unsigned int total_refs;
-
- assert(repo && repo->references.packfile);
-
- total_refs =
- (unsigned int)git_strmap_num_entries(repo->references.packfile);
-
- if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
- return -1;
-
- /* Load all the packfile into a vector */
- {
- struct packref *reference;
-
- /* cannot fail: vector already has the right size */
- git_strmap_foreach_value(repo->references.packfile, reference, {
- git_vector_insert(&packing_list, reference);
- });
- }
-
- /* sort the vector so the entries appear sorted on the packfile */
- git_vector_sort(&packing_list);
-
- /* Now we can open the file! */
- if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)
- goto cleanup_memory;
-
- if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
- goto cleanup_packfile;
-
- /* Packfiles have a header... apparently
- * This is in fact not required, but we might as well print it
- * just for kicks */
- if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
- goto cleanup_packfile;
-
- for (i = 0; i < packing_list.length; ++i) {
- struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
-
- if (packed_find_peel(repo, ref) < 0)
- goto cleanup_packfile;
+ if (reference == NULL)
+ return;
- if (packed_write_ref(ref, &pack_file) < 0)
- goto cleanup_packfile;
+ if (reference->type == GIT_REF_SYMBOLIC) {
+ git__free(reference->target.symbolic);
+ reference->target.symbolic = NULL;
}
- /* if we've written all the references properly, we can commit
- * the packfile to make the changes effective */
- if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
- goto cleanup_memory;
-
- /* when and only when the packfile has been properly written,
- * we can go ahead and remove the loose refs */
- if (packed_remove_loose(repo, &packing_list) < 0)
- goto cleanup_memory;
-
- {
- struct stat st;
- if (p_stat(pack_file_path.ptr, &st) == 0)
- repo->references.packfile_time = st.st_mtime;
- }
-
- git_vector_free(&packing_list);
- git_buf_free(&pack_file_path);
-
- /* we're good now */
- return 0;
-
-cleanup_packfile:
- git_filebuf_cleanup(&pack_file);
+ reference->db = NULL;
+ reference->type = GIT_REF_INVALID;
-cleanup_memory:
- git_vector_free(&packing_list);
- git_buf_free(&pack_file_path);
-
- return -1;
+ git__free(reference);
}
struct reference_available_t {
@@ -863,28 +136,6 @@ static int reference_path_available(
return 0;
}
-static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
-{
- git_buf ref_path = GIT_BUF_INIT;
-
- if (packed_load(repo) < 0)
- return -1;
-
- if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)
- return -1;
-
- if (git_path_isfile(ref_path.ptr) == true ||
- git_strmap_exists(repo->references.packfile, ref_path.ptr))
- {
- *exists = 1;
- } else {
- *exists = 0;
- }
-
- git_buf_free(&ref_path);
- return 0;
-}
-
/*
* Check if a reference could be written to disk, based on:
*
@@ -900,6 +151,11 @@ static int reference_can_write(
const char *previous_name,
int force)
{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
/* see if the reference shares a path with an existing reference;
* if a path is shared, we cannot create the reference, even when forcing */
if (reference_path_available(repo, refname, previous_name) < 0)
@@ -910,7 +166,7 @@ static int reference_can_write(
if (!force) {
int exists;
- if (reference_exists(&exists, repo, refname) < 0)
+ if (git_refdb_exists(&exists, refdb, refname) < 0)
return -1;
/* We cannot proceed if the reference already exists and we're not forcing
@@ -937,139 +193,9 @@ static int reference_can_write(
return 0;
}
-
-static int packed_lookup(git_reference *ref)
-{
- struct packref *pack_ref = NULL;
- git_strmap *packfile_refs;
- khiter_t pos;
-
- if (packed_load(ref->owner) < 0)
- return -1;
-
- /* maybe the packfile hasn't changed at all, so we don't
- * have to re-lookup the reference */
- if ((ref->flags & GIT_REF_PACKED) &&
- ref->mtime == ref->owner->references.packfile_time)
- return 0;
-
- if (ref->flags & GIT_REF_SYMBOLIC) {
- git__free(ref->target.symbolic);
- ref->target.symbolic = NULL;
- }
-
- /* Look up on the packfile */
- packfile_refs = ref->owner->references.packfile;
- pos = git_strmap_lookup_index(packfile_refs, ref->name);
- if (!git_strmap_valid_index(packfile_refs, pos)) {
- giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name);
- return GIT_ENOTFOUND;
- }
-
- pack_ref = git_strmap_value_at(packfile_refs, pos);
-
- ref->flags = GIT_REF_OID | GIT_REF_PACKED;
- ref->mtime = ref->owner->references.packfile_time;
- git_oid_cpy(&ref->target.oid, &pack_ref->oid);
-
- return 0;
-}
-
-static int reference_lookup(git_reference *ref)
-{
- int result;
-
- result = loose_lookup(ref);
- if (result == 0)
- return 0;
-
- /* only try to lookup this reference on the packfile if it
- * wasn't found on the loose refs; not if there was a critical error */
- if (result == GIT_ENOTFOUND) {
- giterr_clear();
- result = packed_lookup(ref);
- if (result == 0)
- return 0;
- }
-
- /* unexpected error; free the reference */
- git_reference_free(ref);
- return result;
-}
-
-/*
- * Delete a reference.
- * This is an internal method; the reference is removed
- * from disk or the packfile, but the pointer is not freed
- */
-static int reference_delete(git_reference *ref)
-{
- int result;
-
- assert(ref);
-
- /* If the reference is packed, this is an expensive operation.
- * We need to reload the packfile, remove the reference from the
- * packing list, and repack */
- if (ref->flags & GIT_REF_PACKED) {
- git_strmap *packfile_refs;
- struct packref *packref;
- khiter_t pos;
-
- /* load the existing packfile */
- if (packed_load(ref->owner) < 0)
- return -1;
-
- packfile_refs = ref->owner->references.packfile;
- pos = git_strmap_lookup_index(packfile_refs, ref->name);
- if (!git_strmap_valid_index(packfile_refs, pos)) {
- giterr_set(GITERR_REFERENCE,
- "Reference %s stopped existing in the packfile", ref->name);
- return -1;
- }
-
- packref = git_strmap_value_at(packfile_refs, pos);
- git_strmap_delete_at(packfile_refs, pos);
-
- git__free(packref);
- if (packed_write(ref->owner) < 0)
- return -1;
-
- /* If the reference is loose, we can just remove the reference
- * from the filesystem */
- } else {
- git_reference *ref_in_pack;
- git_buf full_path = GIT_BUF_INIT;
-
- if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
- result = p_unlink(full_path.ptr);
- git_buf_free(&full_path); /* done with path at this point */
-
- if (result < 0) {
- giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);
- return -1;
- }
-
- /* When deleting a loose reference, we have to ensure that an older
- * packed version of it doesn't exist */
- if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) {
- assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
- return git_reference_delete(ref_in_pack);
- }
-
- giterr_clear();
- }
-
- return 0;
-}
-
int git_reference_delete(git_reference *ref)
{
- int result = reference_delete(ref);
- git_reference_free(ref);
- return result;
+ return git_refdb_delete(ref->db, ref);
}
int git_reference_lookup(git_reference **ref_out,
@@ -1098,8 +224,11 @@ int git_reference_lookup_resolved(
const char *name,
int max_nesting)
{
- git_reference *scan;
- int result, nesting;
+ char scan_name[GIT_REFNAME_MAX];
+ git_ref_t scan_type;
+ int error = 0, nesting;
+ git_reference *ref = NULL;
+ git_refdb *refdb;
assert(ref_out && repo && name);
@@ -1109,48 +238,39 @@ int git_reference_lookup_resolved(
max_nesting = MAX_NESTING_LEVEL;
else if (max_nesting < 0)
max_nesting = DEFAULT_NESTING_LEVEL;
+
+ strncpy(scan_name, name, GIT_REFNAME_MAX);
+ scan_type = GIT_REF_SYMBOLIC;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
- scan = git__calloc(1, sizeof(git_reference));
- GITERR_CHECK_ALLOC(scan);
-
- scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));
- GITERR_CHECK_ALLOC(scan->name);
-
- if ((result = git_reference__normalize_name_lax(
- scan->name,
- GIT_REFNAME_MAX,
- name)) < 0) {
- git_reference_free(scan);
- return result;
- }
-
- scan->target.symbolic = git__strdup(scan->name);
- GITERR_CHECK_ALLOC(scan->target.symbolic);
-
- scan->owner = repo;
- scan->flags = GIT_REF_SYMBOLIC;
+ if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)
+ return error;
for (nesting = max_nesting;
- nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0;
+ nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
nesting--)
{
- if (nesting != max_nesting)
- strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX);
-
- scan->mtime = 0;
+ if (nesting != max_nesting) {
+ strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);
+ git_reference_free(ref);
+ }
- if ((result = reference_lookup(scan)) < 0)
- return result; /* lookup git_reference_free on scan already */
+ if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
+ return error;
+
+ scan_type = ref->type;
}
- if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) {
+ if (scan_type != GIT_REF_OID && max_nesting != 0) {
giterr_set(GITERR_REFERENCE,
"Cannot resolve reference (>%u levels deep)", max_nesting);
- git_reference_free(scan);
+ git_reference_free(ref);
return -1;
}
- *ref_out = scan;
+ *ref_out = ref;
return 0;
}
@@ -1160,20 +280,7 @@ int git_reference_lookup_resolved(
git_ref_t git_reference_type(const git_reference *ref)
{
assert(ref);
-
- if (ref->flags & GIT_REF_OID)
- return GIT_REF_OID;
-
- if (ref->flags & GIT_REF_SYMBOLIC)
- return GIT_REF_SYMBOLIC;
-
- return GIT_REF_INVALID;
-}
-
-int git_reference_is_packed(git_reference *ref)
-{
- assert(ref);
- return !!(ref->flags & GIT_REF_PACKED);
+ return ref->type;
}
const char *git_reference_name(const git_reference *ref)
@@ -1185,14 +292,14 @@ const char *git_reference_name(const git_reference *ref)
git_repository *git_reference_owner(const git_reference *ref)
{
assert(ref);
- return ref->owner;
+ return ref->db->repo;
}
const git_oid *git_reference_target(const git_reference *ref)
{
assert(ref);
- if ((ref->flags & GIT_REF_OID) == 0)
+ if (ref->type != GIT_REF_OID)
return NULL;
return &ref->target.oid;
@@ -1202,48 +309,45 @@ const char *git_reference_symbolic_target(const git_reference *ref)
{
assert(ref);
- if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
+ if (ref->type != GIT_REF_SYMBOLIC)
return NULL;
return ref->target.symbolic;
}
-int git_reference_symbolic_create(
+static int reference__create(
git_reference **ref_out,
git_repository *repo,
const char *name,
- const char *target,
+ const git_oid *oid,
+ const char *symbolic,
int force)
{
char normalized[GIT_REFNAME_MAX];
+ git_refdb *refdb;
git_reference *ref = NULL;
- int error;
-
- if ((error = git_reference__normalize_name_lax(
- normalized,
- sizeof(normalized),
- name)) < 0)
- return error;
-
- if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ int error = 0;
+
+ if (ref_out)
+ *ref_out = NULL;
+
+ if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 ||
+ (error = reference_can_write(repo, normalized, NULL, force)) < 0 ||
+ (error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
return error;
-
- if (reference_alloc(&ref, repo, normalized) < 0)
+
+ if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL)
return -1;
- ref->flags |= GIT_REF_SYMBOLIC;
-
- /* set the target; this will normalize the name automatically
- * and write the reference on disk */
- if (git_reference_symbolic_set_target(ref, target) < 0) {
+ if ((error = git_refdb_write(refdb, ref)) < 0) {
git_reference_free(ref);
- return -1;
+ return error;
}
- if (ref_out == NULL) {
+
+ if (ref_out == NULL)
git_reference_free(ref);
- } else {
+ else
*ref_out = ref;
- }
return 0;
}
@@ -1252,232 +356,164 @@ int git_reference_create(
git_reference **ref_out,
git_repository *repo,
const char *name,
- const git_oid *id,
+ const git_oid *oid,
int force)
{
- int error;
- git_reference *ref = NULL;
- char normalized[GIT_REFNAME_MAX];
+ git_odb *odb;
+ int error = 0;
- if ((error = git_reference__normalize_name_lax(
- normalized,
- sizeof(normalized),
- name)) < 0)
- return error;
-
- if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ assert(repo && name && oid);
+
+ /* Sanity check the reference being created - target must exist. */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
return error;
-
- if (reference_alloc(&ref, repo, name) < 0)
- return -1;
-
- ref->flags |= GIT_REF_OID;
-
- /* set the oid; this will write the reference on disk */
- if (git_reference_set_target(ref, id) < 0) {
- git_reference_free(ref);
+
+ if (!git_odb_exists(odb, oid)) {
+ giterr_set(GITERR_REFERENCE,
+ "Target OID for the reference doesn't exist on the repository");
return -1;
}
-
- if (ref_out == NULL) {
- git_reference_free(ref);
- } else {
- *ref_out = ref;
- }
-
- return 0;
+
+ return reference__create(ref_out, repo, name, oid, NULL, force);
}
-/*
- * Change the OID target of a reference.
- *
- * For both loose and packed references, just change
- * the oid in memory and (over)write the file in disk.
- *
- * We do not repack packed references because of performance
- * reasons.
- */
-int git_reference_set_target(git_reference *ref, const git_oid *id)
-{
- git_odb *odb = NULL;
- if ((ref->flags & GIT_REF_OID) == 0) {
- giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
- return -1;
- }
+int git_reference_symbolic_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force)
+{
+ char normalized[GIT_REFNAME_MAX];
+ int error = 0;
- assert(ref->owner);
+ assert(repo && name && target);
+
+ if ((error = git_reference__normalize_name_lax(
+ normalized, sizeof(normalized), target)) < 0)
+ return error;
- if (git_repository_odb__weakptr(&odb, ref->owner) < 0)
- return -1;
+ return reference__create(ref_out, repo, name, NULL, normalized, force);
+}
- /* Don't let the user create references to OIDs that
- * don't exist in the ODB */
- if (!git_odb_exists(odb, id)) {
- giterr_set(GITERR_REFERENCE,
- "Target OID for the reference doesn't exist on the repository");
+int git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id)
+{
+ assert(out && ref && id);
+
+ if (ref->type != GIT_REF_OID) {
+ giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
return -1;
}
- /* Update the OID value on `ref` */
- git_oid_cpy(&ref->target.oid, id);
-
- /* Write back to disk */
- return loose_write(ref);
+ return git_reference_create(out, ref->db->repo, ref->name, id, 1);
}
-/*
- * Change the target of a symbolic reference.
- *
- * This is easy because symrefs cannot be inside
- * a pack. We just change the target in memory
- * and overwrite the file on disk.
- */
-int git_reference_symbolic_set_target(git_reference *ref, const char *target)
+int git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target)
{
- int error;
- char normalized[GIT_REFNAME_MAX];
-
- if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
+ assert(out && ref && target);
+
+ if (ref->type != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_REFERENCE,
"Cannot set symbolic target on a direct reference");
return -1;
}
-
- if ((error = git_reference__normalize_name_lax(
- normalized,
- sizeof(normalized),
- target)) < 0)
- return error;
-
- git__free(ref->target.symbolic);
- ref->target.symbolic = git__strdup(normalized);
- GITERR_CHECK_ALLOC(ref->target.symbolic);
-
- return loose_write(ref);
+
+ return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);
}
-int git_reference_rename(git_reference *ref, const char *new_name, int force)
+int git_reference_rename(
+ git_reference **out,
+ git_reference *ref,
+ const char *new_name,
+ int force)
{
- int result;
unsigned int normalization_flags;
- git_buf aux_path = GIT_BUF_INIT;
char normalized[GIT_REFNAME_MAX];
bool should_head_be_updated = false;
-
- normalization_flags = ref->flags & GIT_REF_SYMBOLIC ?
- GIT_REF_FORMAT_ALLOW_ONELEVEL
- : GIT_REF_FORMAT_NORMAL;
-
- if ((result = git_reference_normalize_name(
- normalized,
- sizeof(normalized),
- new_name,
- normalization_flags)) < 0)
- return result;
-
- if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0)
- return result;
-
- /* Initialize path now so we won't get an allocation failure once
- * we actually start removing things. */
- if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)
- return -1;
-
- /*
- * Check if we have to update HEAD.
- */
- if ((should_head_be_updated = git_branch_is_head(ref)) < 0)
- goto cleanup;
-
- /*
- * Now delete the old ref and remove an possibly existing directory
- * named `new_name`. Note that using the internal `reference_delete`
- * method deletes the ref from disk but doesn't free the pointer, so
- * we can still access the ref's attributes for creating the new one
- */
- if (reference_delete(ref) < 0)
- goto cleanup;
+ git_reference *result = NULL;
+ git_oid *oid;
+ const char *symbolic;
+ int error = 0;
+ int reference_has_log;
+
+ *out = NULL;
+
+ 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 ||
+ (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0)
+ return error;
/*
- * Finally we can create the new reference.
+ * Create the new reference.
*/
- if (ref->flags & GIT_REF_SYMBOLIC) {
- result = git_reference_symbolic_create(
- NULL, ref->owner, new_name, ref->target.symbolic, force);
+ if (ref->type == GIT_REF_OID) {
+ oid = &ref->target.oid;
+ symbolic = NULL;
} else {
- result = git_reference_create(
- NULL, ref->owner, new_name, &ref->target.oid, force);
+ oid = NULL;
+ symbolic = ref->target.symbolic;
}
+
+ if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL)
+ return -1;
- if (result < 0)
- goto rollback;
+ /* Check if we have to update HEAD. */
+ if ((error = git_branch_is_head(ref)) < 0)
+ goto on_error;
- /*
- * Update HEAD it was poiting to the reference being renamed.
- */
- if (should_head_be_updated &&
- git_repository_set_head(ref->owner, new_name) < 0) {
- giterr_set(GITERR_REFERENCE,
- "Failed to update HEAD after renaming reference");
- goto cleanup;
- }
-
- /*
- * Rename the reflog file, if it exists.
- */
- if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0))
- goto cleanup;
+ should_head_be_updated = (error > 0);
- /*
- * Change the name of the reference given by the user.
- */
- git__free(ref->name);
- ref->name = git__strdup(new_name);
+ /* Now delete the old ref and save the new one. */
+ if ((error = git_refdb_delete(ref->db, ref)) < 0)
+ goto on_error;
+
+ /* Save the new reference. */
+ if ((error = git_refdb_write(ref->db, result)) < 0)
+ goto rollback;
+
+ /* Update HEAD it was poiting to the reference being renamed. */
+ if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) {
+ giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
+ goto on_error;
+ }
- /* The reference is no longer packed */
- ref->flags &= ~GIT_REF_PACKED;
+ /* Rename the reflog file, if it exists. */
+ reference_has_log = git_reference_has_log(ref);
+ if (reference_has_log < 0) {
+ error = reference_has_log;
+ goto on_error;
+ }
+ if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
+ goto on_error;
- git_buf_free(&aux_path);
- return 0;
+ *out = result;
-cleanup:
- git_buf_free(&aux_path);
- return -1;
+ return error;
rollback:
- /*
- * Try to create the old reference again, ignore failures
- */
- if (ref->flags & GIT_REF_SYMBOLIC)
- git_reference_symbolic_create(
- NULL, ref->owner, ref->name, ref->target.symbolic, 0);
- else
- git_reference_create(
- NULL, ref->owner, ref->name, &ref->target.oid, 0);
+ git_refdb_write(ref->db, ref);
- /* The reference is no longer packed */
- ref->flags &= ~GIT_REF_PACKED;
+on_error:
+ git_reference_free(result);
- git_buf_free(&aux_path);
- return -1;
+ return error;
}
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
{
- if (ref->flags & GIT_REF_OID)
- return git_reference_lookup(ref_out, ref->owner, ref->name);
+ if (ref->type == GIT_REF_OID)
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
else
- return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1);
-}
-
-int git_reference_packall(git_repository *repo)
-{
- if (packed_load(repo) < 0 || /* load the existing packfile */
- packed_loadloose(repo) < 0 || /* add all the loose refs */
- packed_write(repo) < 0) /* write back to disk */
- return -1;
-
- return 0;
+ return git_reference_lookup_resolved(ref_out, ref->db->repo,
+ ref->target.symbolic, -1);
}
int git_reference_foreach(
@@ -1486,43 +522,10 @@ int git_reference_foreach(
git_reference_foreach_cb callback,
void *payload)
{
- int result;
- struct dirent_list_data data;
- git_buf refs_path = GIT_BUF_INIT;
-
- /* list all the packed references first */
- if (list_flags & GIT_REF_PACKED) {
- const char *ref_name;
- void *ref = NULL;
- GIT_UNUSED(ref);
-
- if (packed_load(repo) < 0)
- return -1;
-
- git_strmap_foreach(repo->references.packfile, ref_name, ref, {
- if (callback(ref_name, payload))
- return GIT_EUSER;
- });
- }
-
- /* now list the loose references, trying not to
- * duplicate the ref names already in the packed-refs file */
+ git_refdb *refdb;
+ git_repository_refdb__weakptr(&refdb, repo);
- data.repo_path_len = strlen(repo->path_repository);
- data.list_flags = list_flags;
- data.repo = repo;
- data.callback = callback;
- data.callback_payload = payload;
- data.callback_error = 0;
-
- if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)
- return -1;
-
- result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
-
- git_buf_free(&refs_path);
-
- return data.callback_error ? GIT_EUSER : result;
+ return git_refdb_foreach(refdb, list_flags, callback, payload);
}
static int cb__reflist_add(const char *ref, void *data)
@@ -1556,26 +559,6 @@ int git_reference_list(
return 0;
}
-int git_reference_reload(git_reference *ref)
-{
- return reference_lookup(ref);
-}
-
-void git_repository__refcache_free(git_refcache *refs)
-{
- assert(refs);
-
- if (refs->packfile) {
- struct packref *reference;
-
- git_strmap_foreach_value(refs->packfile, reference, {
- git__free(reference);
- });
-
- git_strmap_free(refs->packfile);
- }
-}
-
static int is_valid_ref_char(char ch)
{
if ((unsigned) ch <= ' ')
@@ -1667,6 +650,9 @@ int git_reference__normalize_name(
process_flags = flags;
current = (char *)name;
+ if (*current == '/')
+ goto cleanup;
+
if (normalize)
git_buf_clear(buf);
@@ -1795,89 +781,62 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2)
assert(ref1 && ref2);
/* let's put symbolic refs before OIDs */
- if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK))
- return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1;
+ if (ref1->type != ref2->type)
+ return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1;
- if (ref1->flags & GIT_REF_SYMBOLIC)
+ if (ref1->type == GIT_REF_SYMBOLIC)
return strcmp(ref1->target.symbolic, ref2->target.symbolic);
return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);
}
-/* Update the reference named `ref_name` so it points to `oid` */
-int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name)
+static int reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ int nesting)
{
git_reference *ref;
- int res;
+ int error = 0;
- res = git_reference_lookup(&ref, repo, ref_name);
+ if (nesting > MAX_NESTING_LEVEL)
+ return GIT_ENOTFOUND;
+
+ error = git_reference_lookup(&ref, repo, ref_name);
- /* If we haven't found the reference at all, we assume we need to create
- * a new reference and that's it */
- if (res == GIT_ENOTFOUND) {
+ /* If we haven't found the reference at all, create a new reference. */
+ if (error == GIT_ENOTFOUND) {
giterr_clear();
- return git_reference_create(NULL, repo, ref_name, oid, 1);
+ return git_reference_create(NULL, repo, ref_name, oid, 0);
}
-
- if (res < 0)
- return -1;
-
- /* If we have found a reference, but it's symbolic, we need to update
- * the direct reference it points to */
+
+ if (error < 0)
+ return error;
+
+ /* If the ref is a symbolic reference, follow its target. */
if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
- git_reference *aux;
- const char *sym_target;
-
- /* The target pointed at by this reference */
- sym_target = git_reference_symbolic_target(ref);
-
- /* resolve the reference to the target it points to */
- res = git_reference_resolve(&aux, ref);
-
- /*
- * if the symbolic reference pointed to an inexisting ref,
- * this is means we're creating a new branch, for example.
- * We need to create a new direct reference with that name
- */
- if (res == GIT_ENOTFOUND) {
- giterr_clear();
- res = git_reference_create(NULL, repo, sym_target, oid, 1);
- git_reference_free(ref);
- return res;
- }
-
- /* free the original symbolic reference now; not before because
- * we're using the `sym_target` pointer */
+ error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
+ nesting+1);
git_reference_free(ref);
-
- if (res < 0)
- return -1;
-
- /* store the newly found direct reference in its place */
- ref = aux;
+ } else {
+ git_reference_free(ref);
+ error = git_reference_create(NULL, repo, ref_name, oid, 1);
}
-
- /* ref is made to point to `oid`: ref is either the original reference,
- * or the target of the symbolic reference we've looked up */
- res = git_reference_set_target(ref, oid);
- git_reference_free(ref);
- return res;
+
+ return error;
}
-struct glob_cb_data {
- const char *glob;
- int (*callback)(const char *, void *);
- void *payload;
-};
-
-static int fromglob_cb(const char *reference_name, void *payload)
+/*
+ * Starting with the reference given by `ref_name`, follows symbolic
+ * references until a direct reference is found and updated the OID
+ * on that direct reference to `oid`.
+ */
+int git_reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid)
{
- struct glob_cb_data *data = (struct glob_cb_data *)payload;
-
- if (!p_fnmatch(data->glob, reference_name, 0))
- return data->callback(reference_name, data->payload);
-
- return 0;
+ return reference__update_terminal(repo, ref_name, oid, 0);
}
int git_reference_foreach_glob(
@@ -1889,16 +848,13 @@ int git_reference_foreach_glob(
void *payload),
void *payload)
{
- struct glob_cb_data data;
+ git_refdb *refdb;
assert(repo && glob && callback);
- data.glob = glob;
- data.callback = callback;
- data.payload = payload;
+ git_repository_refdb__weakptr(&refdb, repo);
- return git_reference_foreach(
- repo, list_flags, fromglob_cb, &data);
+ return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload);
}
int git_reference_has_log(
@@ -1909,7 +865,8 @@ int git_reference_has_log(
assert(ref);
- if (git_buf_join_n(&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0)
+ if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository,
+ GIT_REFLOG_DIR, ref->name) < 0)
return -1;
result = git_path_isfile(git_buf_cstr(&path));
diff --git a/src/refs.h b/src/refs.h
index 7bd1ae68a..7d63c3fbd 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -10,6 +10,7 @@
#include "common.h"
#include "git2/oid.h"
#include "git2/refs.h"
+#include "git2/refdb.h"
#include "strmap.h"
#include "buffer.h"
@@ -47,28 +48,22 @@
#define GIT_REFNAME_MAX 1024
struct git_reference {
- unsigned int flags;
- git_repository *owner;
- char *name;
- time_t mtime;
+ git_refdb *db;
+
+ git_ref_t type;
union {
git_oid oid;
char *symbolic;
} target;
+
+ char name[0];
};
-typedef struct {
- git_strmap *packfile;
- time_t packfile_time;
-} git_refcache;
-
-void git_repository__refcache_free(git_refcache *refs);
-
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);
int git_reference__is_valid_name(const char *refname, unsigned int flags);
-int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name);
int git_reference__is_branch(const char *ref_name);
int git_reference__is_remote(const char *ref_name);
diff --git a/src/remote.c b/src/remote.c
index 0a1f2b856..a6f62d6a5 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1175,6 +1175,7 @@ static int rename_one_remote_reference(
int error = -1;
git_buf new_name = GIT_BUF_INIT;
git_reference *reference = NULL;
+ git_reference *newref = NULL;
if (git_buf_printf(
&new_name,
@@ -1186,10 +1187,11 @@ static int rename_one_remote_reference(
if (git_reference_lookup(&reference, repo, reference_name) < 0)
goto cleanup;
- error = git_reference_rename(reference, git_buf_cstr(&new_name), 0);
+ error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0);
+ git_reference_free(reference);
cleanup:
- git_reference_free(reference);
+ git_reference_free(newref);
git_buf_free(&new_name);
return error;
}
@@ -1252,7 +1254,7 @@ static int rename_fetch_refspecs(
goto cleanup;
/* Is it an in-memory remote? */
- if (remote->name == '\0') {
+ if (!remote->name) {
error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
goto cleanup;
}
diff --git a/src/repository.c b/src/repository.c
index 278abfaf2..0ad7449ba 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -8,6 +8,7 @@
#include <ctype.h>
#include "git2/object.h"
+#include "git2/refdb.h"
#include "common.h"
#include "repository.h"
@@ -39,6 +40,15 @@ static void drop_odb(git_repository *repo)
}
}
+static void drop_refdb(git_repository *repo)
+{
+ if (repo->_refdb != NULL) {
+ GIT_REFCOUNT_OWN(repo->_refdb, NULL);
+ git_refdb_free(repo->_refdb);
+ repo->_refdb = NULL;
+ }
+}
+
static void drop_config(git_repository *repo)
{
if (repo->_config != NULL) {
@@ -65,7 +75,6 @@ void git_repository_free(git_repository *repo)
return;
git_cache_free(&repo->objects);
- git_repository__refcache_free(&repo->references);
git_attr_cache_flush(repo);
git_submodule_config_free(repo);
@@ -75,6 +84,7 @@ void git_repository_free(git_repository *repo)
drop_config(repo);
drop_index(repo);
drop_odb(repo);
+ drop_refdb(repo);
git__free(repo);
}
@@ -600,6 +610,45 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb)
GIT_REFCOUNT_INC(odb);
}
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
+{
+ assert(out && repo);
+
+ if (repo->_refdb == NULL) {
+ int res;
+
+ res = git_refdb_open(&repo->_refdb, repo);
+
+ if (res < 0)
+ return -1;
+
+ GIT_REFCOUNT_OWN(repo->_refdb, repo);
+ }
+
+ *out = repo->_refdb;
+ return 0;
+}
+
+int git_repository_refdb(git_refdb **out, git_repository *repo)
+{
+ if (git_repository_refdb__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+void git_repository_set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ assert (repo && refdb);
+
+ drop_refdb(repo);
+
+ repo->_refdb = refdb;
+ GIT_REFCOUNT_OWN(repo->_refdb, repo);
+ GIT_REFCOUNT_INC(refdb);
+}
+
int git_repository_index__weakptr(git_index **out, git_repository *repo)
{
assert(out && repo);
diff --git a/src/repository.h b/src/repository.h
index f19758fe4..cc2f8c2b8 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -19,8 +19,9 @@
#include "buffer.h"
#include "odb.h"
#include "object.h"
-#include "attr.h"
+#include "attrcache.h"
#include "strmap.h"
+#include "refdb.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
@@ -79,11 +80,11 @@ enum {
/** Internal structure for repository object */
struct git_repository {
git_odb *_odb;
+ git_refdb *_refdb;
git_config *_config;
git_index *_index;
git_cache objects;
- git_refcache references;
git_attr_cache attrcache;
git_strmap *submodules;
@@ -112,6 +113,7 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo);
*/
int git_repository_config__weakptr(git_config **out, git_repository *repo);
int git_repository_odb__weakptr(git_odb **out, git_repository *repo);
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo);
int git_repository_index__weakptr(git_index **out, git_repository *repo);
/*
diff --git a/src/reset.c b/src/reset.c
index 700aac808..c1e1f865e 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -13,48 +13,10 @@
#include "git2/reset.h"
#include "git2/checkout.h"
#include "git2/merge.h"
+#include "git2/refs.h"
#define ERROR_MSG "Cannot perform reset"
-static int update_head(git_repository *repo, git_object *commit)
-{
- int error;
- git_reference *head = NULL, *target = NULL;
-
- error = git_repository_head(&head, repo);
-
- if (error < 0 && error != GIT_EORPHANEDHEAD)
- return error;
-
- if (error == GIT_EORPHANEDHEAD) {
- giterr_clear();
-
- /*
- * TODO: This is a bit weak as this doesn't support chained
- * symbolic references. yet.
- */
- if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
- goto cleanup;
-
- if ((error = git_reference_create(
- &target,
- repo,
- git_reference_symbolic_target(head),
- git_object_id(commit), 0)) < 0)
- goto cleanup;
- } else {
- if ((error = git_reference_set_target(head, git_object_id(commit))) < 0)
- goto cleanup;
- }
-
- error = 0;
-
-cleanup:
- git_reference_free(head);
- git_reference_free(target);
- return error;
-}
-
int git_reset_default(
git_repository *repo,
git_object *target,
@@ -167,7 +129,8 @@ int git_reset(
}
/* move HEAD to the new target */
- if ((error = update_head(repo, commit)) < 0)
+ if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,
+ git_object_id(commit))) < 0)
goto cleanup;
if (reset_type == GIT_RESET_HARD) {
diff --git a/src/revparse.c b/src/revparse.c
index 7f1497130..2ba29383e 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -10,6 +10,7 @@
#include "common.h"
#include "buffer.h"
#include "tree.h"
+#include "refdb.h"
#include "git2.h"
@@ -510,8 +511,8 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
while (!(error = git_revwalk_next(&oid, walk))) {
- if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) &&
- (error != GIT_ENOTFOUND))
+ error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
+ if ((error < 0) && (error != GIT_ENOTFOUND))
return -1;
if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
@@ -634,7 +635,7 @@ static int extract_how_many(int *n, const char *spec, size_t *pos)
} while (spec[(*pos)] == kind && kind == '~');
if (git__isdigit(spec[*pos])) {
- if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0)
+ if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)
return GIT_EINVALIDSPEC;
accumulated += (parsed - 1);
@@ -656,7 +657,7 @@ static int object_from_reference(git_object **object, git_reference *reference)
if (git_reference_resolve(&resolved, reference) < 0)
return -1;
- error = git_object_lookup(object, reference->owner, git_reference_target(resolved), GIT_OBJ_ANY);
+ error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);
git_reference_free(resolved);
return error;
diff --git a/src/stash.c b/src/stash.c
index e78985063..355c5dc9c 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -645,13 +645,15 @@ int git_stash_drop(
if (max == 1) {
error = git_reference_delete(stash);
+ git_reference_free(stash);
stash = NULL;
} else if (index == 0) {
const git_reflog_entry *entry;
entry = git_reflog_entry_byindex(reflog, 0);
-
- error = git_reference_set_target(stash, &entry->oid_cur);
+
+ git_reference_free(stash);
+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1);
}
cleanup:
diff --git a/src/status.c b/src/status.c
index 282cb396b..ac6b4379b 100644
--- a/src/status.c
+++ b/src/status.c
@@ -80,22 +80,37 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
typedef struct {
git_status_cb cb;
void *payload;
+ const git_status_options *opts;
} status_user_callback;
static int status_invoke_cb(
- git_diff_delta *i2h, git_diff_delta *w2i, void *payload)
+ git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
{
status_user_callback *usercb = payload;
const char *path = NULL;
unsigned int status = 0;
- if (w2i) {
- path = w2i->old_file.path;
- status |= workdir_delta2status(w2i->status);
+ if (i2w) {
+ path = i2w->old_file.path;
+ status |= workdir_delta2status(i2w->status);
}
- if (i2h) {
- path = i2h->old_file.path;
- status |= index_delta2status(i2h->status);
+ if (h2i) {
+ path = h2i->old_file.path;
+ status |= index_delta2status(h2i->status);
+ }
+
+ /* if excluding submodules and this is a submodule everywhere */
+ if (usercb->opts &&
+ (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ {
+ bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED);
+ bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED);
+ bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED);
+
+ if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) &&
+ (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) &&
+ (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT))
+ return 0;
}
return usercb->cb(path, status, usercb->payload);
@@ -109,7 +124,7 @@ int git_status_foreach_ext(
{
int err = 0;
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
- git_diff_list *idx2head = NULL, *wd2idx = NULL;
+ git_diff_list *head2idx = NULL, *idx2wd = NULL;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
@@ -142,34 +157,42 @@ int git_status_foreach_ext(
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
- /* TODO: support EXCLUDE_SUBMODULES flag */
-
- if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
- (err = git_diff_tree_to_index(&idx2head, repo, head, NULL, &diffopt)) < 0)
- goto cleanup;
+ if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
+ if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+
+ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
+ if (err < 0)
+ goto cleanup;
+ }
- if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_diff_index_to_workdir(&wd2idx, repo, NULL, &diffopt)) < 0)
- goto cleanup;
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
+ err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
+ if (err < 0)
+ goto cleanup;
+ }
usercb.cb = cb;
usercb.payload = payload;
+ usercb.opts = opts;
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
if ((err = git_diff__paired_foreach(
- idx2head, NULL, status_invoke_cb, &usercb)) < 0)
+ head2idx, NULL, status_invoke_cb, &usercb)) < 0)
goto cleanup;
- git_diff_list_free(idx2head);
- idx2head = NULL;
+ git_diff_list_free(head2idx);
+ head2idx = NULL;
}
- err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb);
+ err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
cleanup:
git_tree_free(head);
- git_diff_list_free(idx2head);
- git_diff_list_free(wd2idx);
+ git_diff_list_free(head2idx);
+ git_diff_list_free(idx2wd);
if (err == GIT_EUSER)
giterr_clear();
diff --git a/src/submodule.c b/src/submodule.c
index 359306498..066a881cb 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -694,7 +694,7 @@ int git_submodule_open(
git_buf_free(&path);
/* if we have opened the submodule successfully, let's grab the HEAD OID */
- if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
+ if (!error) {
if (!git_reference_name_to_id(
&submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
@@ -1135,10 +1135,10 @@ static int load_submodule_config_from_index(
const git_index_entry *entry;
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
- (error = git_iterator_for_index(&i, index)) < 0)
+ (error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0)
return error;
- error = git_iterator_current(i, &entry);
+ error = git_iterator_current(&entry, i);
while (!error && entry != NULL) {
@@ -1154,7 +1154,7 @@ static int load_submodule_config_from_index(
git_oid_cpy(gitmodules_oid, &entry->oid);
}
- error = git_iterator_advance(i, &entry);
+ error = git_iterator_advance(&entry, i);
}
git_iterator_free(i);
@@ -1173,12 +1173,12 @@ static int load_submodule_config_from_head(
if ((error = git_repository_head_tree(&head, repo)) < 0)
return error;
- if ((error = git_iterator_for_tree(&i, head)) < 0) {
+ if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {
git_tree_free(head);
return error;
}
- error = git_iterator_current(i, &entry);
+ error = git_iterator_current(&entry, i);
while (!error && entry != NULL) {
@@ -1195,7 +1195,7 @@ static int load_submodule_config_from_head(
git_oid_cpy(gitmodules_oid, &entry->oid);
}
- error = git_iterator_advance(i, &entry);
+ error = git_iterator_advance(&entry, i);
}
git_iterator_free(i);
@@ -1497,7 +1497,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
- if ((git_diff_num_deltas(diff) - untracked) > 0)
+ if (git_diff_num_deltas(diff) != untracked)
*status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff);
diff --git a/src/tag.c b/src/tag.c
index 592299e40..735ba7e1d 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -131,7 +131,7 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
buffer = search + 1;
tag->tagger = NULL;
- if (*buffer != '\n') {
+ if (buffer < buffer_end && *buffer != '\n') {
tag->tagger = git__malloc(sizeof(git_signature));
GITERR_CHECK_ALLOC(tag->tagger);
@@ -376,9 +376,9 @@ on_error:
int git_tag_delete(git_repository *repo, const char *tag_name)
{
- int error;
git_reference *tag_ref;
git_buf ref_name = GIT_BUF_INIT;
+ int error;
error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name);
@@ -387,7 +387,10 @@ int git_tag_delete(git_repository *repo, const char *tag_name)
if (error < 0)
return error;
- return git_reference_delete(tag_ref);
+ if ((error = git_reference_delete(tag_ref)) == 0)
+ git_reference_free(tag_ref);
+
+ return error;
}
int git_tag__parse(git_tag *tag, git_odb_object *obj)
@@ -426,8 +429,7 @@ int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)
data.cb_data = cb_data;
data.repo = repo;
- return git_reference_foreach(
- repo, GIT_REF_OID | GIT_REF_PACKED, &tags_cb, &data);
+ return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data);
}
typedef struct {
diff --git a/src/trace.c b/src/trace.c
new file mode 100644
index 000000000..159ac91cc
--- /dev/null
+++ b/src/trace.c
@@ -0,0 +1,39 @@
+/*
+ * 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 "buffer.h"
+#include "common.h"
+#include "global.h"
+#include "trace.h"
+#include "git2/trace.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data git_trace__data = {0};
+
+#endif
+
+int git_trace_set(git_trace_level_t level, git_trace_callback callback)
+{
+#ifdef GIT_TRACE
+ assert(level == 0 || callback != NULL);
+
+ git_trace__data.level = level;
+ git_trace__data.callback = callback;
+ GIT_MEMORY_BARRIER;
+
+ return 0;
+#else
+ GIT_UNUSED(level);
+ GIT_UNUSED(callback);
+
+ giterr_set(GITERR_INVALID,
+ "This version of libgit2 was not built with tracing.");
+ return -1;
+#endif
+}
+
diff --git a/src/trace.h b/src/trace.h
new file mode 100644
index 000000000..f4bdff88a
--- /dev/null
+++ b/src/trace.h
@@ -0,0 +1,56 @@
+/*
+ * 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_trace_h__
+#define INCLUDE_trace_h__
+
+#include <stdarg.h>
+
+#include <git2/trace.h>
+#include "buffer.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data {
+ git_trace_level_t level;
+ git_trace_callback callback;
+};
+
+extern struct git_trace_data git_trace__data;
+
+GIT_INLINE(void) git_trace__write_fmt(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ git_trace_callback callback = git_trace__data.callback;
+ git_buf message = GIT_BUF_INIT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ git_buf_vprintf(&message, fmt, ap);
+ va_end(ap);
+
+ callback(level, git_buf_cstr(&message));
+
+ git_buf_free(&message);
+}
+
+#define git_trace_level() (git_trace__data.level)
+#define git_trace(l, ...) { \
+ if (git_trace__data.level >= l && \
+ git_trace__data.callback != NULL) { \
+ git_trace__write_fmt(l, __VA_ARGS__); \
+ } \
+ }
+
+#else
+
+#define git_trace_level() ((void)0)
+#define git_trace(lvl, ...) ((void)0)
+
+#endif
+
+#endif
diff --git a/src/transports/http.c b/src/transports/http.c
index 964bafb19..eca06ead2 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -45,12 +45,14 @@ typedef struct {
git_smart_subtransport_stream parent;
const char *service;
const char *service_url;
+ char *redirect_url;
const char *verb;
char *chunk_buffer;
unsigned chunk_buffer_len;
unsigned sent_request : 1,
received_response : 1,
- chunked : 1;
+ chunked : 1,
+ redirect_count : 3;
} http_stream;
typedef struct {
@@ -76,6 +78,7 @@ typedef struct {
git_buf parse_header_value;
char parse_buffer_data[2048];
char *content_type;
+ char *location;
git_vector www_authenticate;
enum last_cb last_cb;
int parse_error;
@@ -126,7 +129,12 @@ static int gen_request(
if (!t->path)
t->path = "/";
- git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
+ /* If we were redirected, make sure to respect that here */
+ if (s->redirect_url)
+ git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url);
+ else
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
+
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
git_buf_printf(buf, "Host: %s\r\n", t->host);
@@ -186,17 +194,25 @@ static int on_header_ready(http_subtransport *t)
{
git_buf *name = &t->parse_header_name;
git_buf *value = &t->parse_header_value;
- char *dup;
- if (!t->content_type && !strcasecmp("Content-Type", git_buf_cstr(name))) {
- t->content_type = git__strdup(git_buf_cstr(value));
- GITERR_CHECK_ALLOC(t->content_type);
+ if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
}
else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
- dup = git__strdup(git_buf_cstr(value));
+ char *dup = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(dup);
+
git_vector_insert(&t->www_authenticate, dup);
}
+ else if (!strcasecmp("Location", git_buf_cstr(name))) {
+ if (!t->location) {
+ t->location= git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->location);
+ }
+ }
return 0;
}
@@ -279,6 +295,38 @@ static int on_headers_complete(http_parser *parser)
}
}
+ /* Check for a redirect.
+ * Right now we only permit a redirect to the same hostname. */
+ if ((parser->status_code == 301 ||
+ parser->status_code == 302 ||
+ (parser->status_code == 303 && get_verb == s->verb) ||
+ parser->status_code == 307) &&
+ t->location) {
+
+ if (s->redirect_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (t->location[0] != '/') {
+ giterr_set(GITERR_NET, "Only relative redirects are supported");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* Set the redirect URL on the stream. This is a transfer of
+ * ownership of the memory. */
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ s->redirect_url = t->location;
+ t->location = NULL;
+
+ t->connected = 0;
+ s->redirect_count++;
+
+ return t->parse_error = PARSE_ERROR_REPLAY;
+ }
+
/* Check for a 200 HTTP status code. */
if (parser->status_code != 200) {
giterr_set(GITERR_NET,
@@ -371,6 +419,9 @@ static void clear_parser_state(http_subtransport *t)
git__free(t->content_type);
t->content_type = NULL;
+ git__free(t->location);
+ t->location = NULL;
+
git_vector_foreach(&t->www_authenticate, i, entry)
git__free(entry);
@@ -405,6 +456,37 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
return 0;
}
+static int http_connect(http_subtransport *t)
+{
+ int flags = 0;
+
+ if (t->connected &&
+ http_should_keep_alive(&t->parser) &&
+ http_body_is_final(&t->parser))
+ return 0;
+
+ if (t->socket.socket)
+ gitno_close(&t->socket);
+
+ if (t->use_ssl) {
+ int tflags;
+
+ if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0)
+ 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->host, t->port, flags) < 0)
+ return -1;
+
+ t->connected = 1;
+ return 0;
+}
+
static int http_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
@@ -491,6 +573,10 @@ replay:
* will have signaled us that we should replay the request. */
if (PARSE_ERROR_REPLAY == t->parse_error) {
s->sent_request = 0;
+
+ if (http_connect(t) < 0)
+ return -1;
+
goto replay;
}
@@ -627,6 +713,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream)
if (s->chunk_buffer)
git__free(s->chunk_buffer);
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
git__free(s);
}
@@ -734,7 +823,7 @@ static int http_action(
{
http_subtransport *t = (http_subtransport *)subtransport;
const char *default_port = NULL;
- int flags = 0, ret;
+ int ret;
if (!stream)
return -1;
@@ -761,30 +850,8 @@ static int http_action(
t->path = strchr(url, '/');
}
- if (!t->connected ||
- !http_should_keep_alive(&t->parser) ||
- !http_body_is_final(&t->parser)) {
-
- if (t->socket.socket)
- gitno_close(&t->socket);
-
- if (t->use_ssl) {
- int transport_flags;
-
- if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0)
- return -1;
-
- flags |= GITNO_CONNECT_SSL;
-
- if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags)
- flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
- }
-
- if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
- return -1;
-
- t->connected = 1;
- }
+ if (http_connect(t) < 0)
+ return -1;
switch (action)
{
diff --git a/src/transports/local.c b/src/transports/local.c
index 44431d587..ce89bb213 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -16,6 +16,7 @@
#include "git2/pack.h"
#include "git2/commit.h"
#include "git2/revparse.h"
+#include "git2/push.h"
#include "pack-objects.h"
#include "refs.h"
#include "posix.h"
@@ -23,6 +24,8 @@
#include "buffer.h"
#include "repository.h"
#include "odb.h"
+#include "push.h"
+#include "remote.h"
typedef struct {
git_transport parent;
@@ -79,8 +82,10 @@ static int add_ref(transport_local *t, const char *name)
head = NULL;
- /* If it's not an annotated tag, just get out */
- if (git_object_type(obj) != GIT_OBJ_TAG) {
+ /* If it's not an annotated tag, or if we're mocking
+ * git-receive-pack, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG ||
+ t->direction != GIT_DIRECTION_FETCH) {
git_object_free(obj);
return 0;
}
@@ -119,14 +124,14 @@ static int store_refs(transport_local *t)
assert(t);
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
+ git_vector_init(&t->refs, ref_names.count, NULL) < 0)
goto on_error;
/* Sort the references first */
git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
- /* Add HEAD */
- if (add_ref(t, GIT_HEAD_FILE) < 0)
+ /* Add HEAD iff direction is fetch */
+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
goto on_error;
for (i = 0; i < ref_names.count; ++i) {
@@ -245,6 +250,191 @@ static int local_negotiate_fetch(
return 0;
}
+static int local_push_copy_object(
+ git_odb *local_odb,
+ git_odb *remote_odb,
+ git_pobject *obj)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+ git_odb_stream *odb_stream;
+ size_t odb_obj_size;
+ git_otype odb_obj_type;
+ git_oid remote_odb_obj_oid;
+
+ /* Object already exists in the remote ODB; do nothing and return 0*/
+ if (git_odb_exists(remote_odb, &obj->id))
+ return 0;
+
+ if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
+ return error;
+
+ odb_obj_size = git_odb_object_size(odb_obj);
+ odb_obj_type = git_odb_object_type(odb_obj);
+
+ if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
+ odb_obj_size, odb_obj_type)) < 0)
+ goto on_error;
+
+ if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj),
+ odb_obj_size) < 0 ||
+ odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
+ error = -1;
+ } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) {
+ giterr_set(GITERR_ODB, "Error when writing object to remote odb "
+ "during local push operation. Remote odb object oid does not "
+ "match local oid.");
+ error = -1;
+ }
+
+ odb_stream->free(odb_stream);
+
+on_error:
+ git_odb_object_free(odb_obj);
+ return error;
+}
+
+static int local_push_update_remote_ref(
+ git_repository *remote_repo,
+ const char *lref,
+ const char *rref,
+ git_oid *loid,
+ git_oid *roid)
+{
+ int error;
+ git_reference *remote_ref = NULL;
+
+ /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
+ rref = rref ? rref : lref;
+
+ if (lref) {
+ /* Create or update a ref */
+ if ((error = git_reference_create(NULL, remote_repo, rref, loid,
+ !git_oid_iszero(roid))) < 0)
+ return error;
+ } else {
+ /* Delete a ref */
+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ return error;
+ }
+
+ if ((error = git_reference_delete(remote_ref)) < 0)
+ return error;
+
+ git_reference_free(remote_ref);
+ }
+
+ return 0;
+}
+
+static int local_push(
+ git_transport *transport,
+ git_push *push)
+{
+ transport_local *t = (transport_local *)transport;
+ git_odb *remote_odb = NULL;
+ git_odb *local_odb = NULL;
+ git_repository *remote_repo = NULL;
+ push_spec *spec;
+ char *url = NULL;
+ int error;
+ unsigned int i;
+ size_t j;
+
+ if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0)
+ return error;
+
+ /* We don't currently support pushing locally to non-bare repos. Proper
+ non-bare repo push support would require checking configs to see if
+ we should override the default 'don't let this happen' behavior */
+ if (!remote_repo->is_bare) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
+ (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
+ goto on_error;
+
+ for (i = 0; i < push->pb->nr_objects; i++) {
+ if ((error = local_push_copy_object(local_odb, remote_odb,
+ &push->pb->object_list[i])) < 0)
+ goto on_error;
+ }
+
+ push->unpack_ok = 1;
+
+ git_vector_foreach(&push->specs, j, spec) {
+ push_status *status;
+ const git_error *last;
+ char *ref = spec->rref ? spec->rref : spec->lref;
+
+ status = git__calloc(sizeof(push_status), 1);
+ if (!status)
+ goto on_error;
+
+ status->ref = git__strdup(ref);
+ if (!status->ref) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
+ &spec->loid, &spec->roid);
+
+ switch (error) {
+ case GIT_OK:
+ break;
+ case GIT_EINVALIDSPEC:
+ status->msg = git__strdup("funny refname");
+ break;
+ case GIT_ENOTFOUND:
+ status->msg = git__strdup("Remote branch not found to delete");
+ break;
+ default:
+ last = giterr_last();
+
+ if (last && last->message)
+ status->msg = git__strdup(last->message);
+ else
+ status->msg = git__strdup("Unspecified error encountered");
+ break;
+ }
+
+ /* failed to allocate memory for a status message */
+ if (error < 0 && !status->msg) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ /* failed to insert the ref update status */
+ if ((error = git_vector_insert(&push->status, status)) < 0) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+ }
+
+ if (push->specs.length) {
+ int flags = t->flags;
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url,
+ push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_repository_free(remote_repo);
+ git__free(url);
+
+ return error;
+}
+
typedef struct foreach_data {
git_transfer_progress *stats;
git_transfer_progress_callback progress_cb;
@@ -379,30 +569,39 @@ static void local_cancel(git_transport *transport)
static int local_close(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
+ size_t i;
+ git_remote_head *head;
t->connected = 0;
- git_repository_free(t->repo);
- t->repo = NULL;
+
+ if (t->repo) {
+ git_repository_free(t->repo);
+ t->repo = NULL;
+ }
+
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+
+ git_vector_free(&t->refs);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
return 0;
}
static void local_free(git_transport *transport)
{
- unsigned int i;
- transport_local *t = (transport_local *) transport;
- git_vector *vec = &t->refs;
- git_remote_head *head;
-
- assert(transport);
+ transport_local *t = (transport_local *)transport;
- git_vector_foreach (vec, i, head) {
- git__free(head->name);
- git__free(head);
- }
- git_vector_free(vec);
+ /* Close the transport, if it's still open. */
+ local_close(transport);
- git__free(t->url);
+ /* Free the transport */
git__free(t);
}
@@ -423,6 +622,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param)
t->parent.connect = local_connect;
t->parent.negotiate_fetch = local_negotiate_fetch;
t->parent.download_pack = local_download_pack;
+ t->parent.push = local_push;
t->parent.close = local_close;
t->parent.free = local_free;
t->parent.ls = local_ls;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index e820488f6..bfcce0c08 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -24,7 +24,7 @@ static int git_smart__recv_cb(gitno_buffer *buf)
buf->offset += bytes_read;
if (t->packetsize_cb)
- t->packetsize_cb((int)bytes_read, t->packetsize_payload);
+ t->packetsize_cb(bytes_read, t->packetsize_payload);
return (int)(buf->offset - old_len);
}
diff --git a/src/transports/smart.h b/src/transports/smart.h
index a9e894b65..c52401a3c 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -88,6 +88,7 @@ typedef git_pkt_data git_pkt_progress;
typedef struct {
enum git_pkt_type type;
+ int len;
char error[GIT_FLEX_ARRAY];
} git_pkt_err;
diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c
index 51edd9179..99da37567 100644
--- a/src/transports/smart_pkt.c
+++ b/src/transports/smart_pkt.c
@@ -122,6 +122,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
memcpy(pkt->error, line, len);
pkt->error[len] = '\0';
@@ -166,6 +167,25 @@ static int progress_pkt(git_pkt **out, const char *line, size_t len)
return 0;
}
+static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+
+ return 0;
+}
+
/*
* Parse an other-ref line.
*/
@@ -380,6 +400,8 @@ int git_pkt_parse_line(
ret = data_pkt(head, line, len);
else if (*line == GIT_SIDE_BAND_PROGRESS)
ret = progress_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_ERROR)
+ ret = sideband_error_pkt(head, line, len);
else if (!git__prefixcmp(line, "ACK"))
ret = ack_pkt(head, line, len);
else if (!git__prefixcmp(line, "NAK"))
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 75494b2c7..8acedeb49 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -454,7 +454,7 @@ int git_smart__download_pack(
/* We might have something in the buffer already from negotiate_fetch */
if (t->buffer.offset > 0)
- t->packetsize_cb((int)t->buffer.offset, t->packetsize_payload);
+ t->packetsize_cb(t->buffer.offset, t->packetsize_payload);
}
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
@@ -536,7 +536,8 @@ static int gen_pktline(git_buf *buf, git_push *push)
if (i == 0) {
++len; /* '\0' */
if (push->report_status)
- len += strlen(GIT_CAP_REPORT_STATUS);
+ len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+ len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
}
git_oid_fmt(old_id, &spec->roid);
@@ -546,8 +547,13 @@ static int gen_pktline(git_buf *buf, git_push *push)
if (i == 0) {
git_buf_putc(buf, '\0');
- if (push->report_status)
+ /* Core git always starts their capabilities string with a space */
+ if (push->report_status) {
+ git_buf_putc(buf, ' ');
git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);
}
git_buf_putc(buf, '\n');
@@ -557,6 +563,74 @@ static int gen_pktline(git_buf *buf, git_push *push)
return git_buf_oom(buf) ? -1 : 0;
}
+static int add_push_report_pkt(git_push *push, git_pkt *pkt)
+{
+ push_status *status;
+
+ switch (pkt->type) {
+ case GIT_PKT_OK:
+ status = git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->msg = NULL;
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ if (!status->ref ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_NG:
+ status = git__calloc(sizeof(push_status), 1);
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ if (!status->ref || !status->msg ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_UNPACK:
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ break;
+ case GIT_PKT_FLUSH:
+ return GIT_ITEROVER;
+ default:
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)
+{
+ git_pkt *pkt;
+ const char *line = data_pkt->data, *line_end;
+ size_t line_len = data_pkt->len;
+ int error;
+
+ while (line_len > 0) {
+ error = git_pkt_parse_line(&pkt, line, &line_end, line_len);
+
+ if (error < 0)
+ return error;
+
+ /* Advance in the buffer */
+ line_len -= (line_end - line);
+ line = line_end;
+
+ error = add_push_report_pkt(push, pkt);
+
+ git_pkt_free(pkt);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ return error;
+ }
+
+ return 0;
+}
+
static int parse_report(gitno_buffer *buf, git_push *push)
{
git_pkt *pkt;
@@ -586,46 +660,33 @@ static int parse_report(gitno_buffer *buf, git_push *push)
gitno_consume(buf, line_end);
- if (pkt->type == GIT_PKT_OK) {
- push_status *status = git__malloc(sizeof(push_status));
- GITERR_CHECK_ALLOC(status);
- status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
- status->msg = NULL;
- git_pkt_free(pkt);
- if (git_vector_insert(&push->status, status) < 0) {
- git__free(status);
- return -1;
- }
- continue;
- }
+ error = 0;
- if (pkt->type == GIT_PKT_NG) {
- push_status *status = git__malloc(sizeof(push_status));
- GITERR_CHECK_ALLOC(status);
- status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
- status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
- git_pkt_free(pkt);
- if (git_vector_insert(&push->status, status) < 0) {
- git__free(status);
- return -1;
- }
- continue;
+ switch (pkt->type) {
+ case GIT_PKT_DATA:
+ /* This is a sideband packet which contains other packets */
+ error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt);
+ break;
+ case GIT_PKT_ERR:
+ giterr_set(GITERR_NET, "report-status: Error reported: %s",
+ ((git_pkt_err *)pkt)->error);
+ error = -1;
+ break;
+ case GIT_PKT_PROGRESS:
+ break;
+ default:
+ error = add_push_report_pkt(push, pkt);
+ break;
}
- if (pkt->type == GIT_PKT_UNPACK) {
- push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
- git_pkt_free(pkt);
- continue;
- }
+ git_pkt_free(pkt);
- if (pkt->type == GIT_PKT_FLUSH) {
- git_pkt_free(pkt);
+ /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */
+ if (error == GIT_ITEROVER)
return 0;
- }
- git_pkt_free(pkt);
- giterr_set(GITERR_NET, "report-status: protocol error");
- return -1;
+ if (error < 0)
+ return error;
}
}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 970fa53bd..e502001cb 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -58,6 +58,7 @@ typedef struct {
const char *service_url;
const wchar_t *verb;
HINTERNET request;
+ wchar_t *request_uri;
char *chunk_buffer;
unsigned chunk_buffer_len;
HANDLE post_body;
@@ -145,10 +146,10 @@ static int winhttp_stream_connect(winhttp_stream *s)
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
git_buf buf = GIT_BUF_INIT;
char *proxy_url = NULL;
- wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
+ wchar_t ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE;
- int error = -1;
+ int error = -1, wide_len;
/* Prepare URL */
git_buf_printf(&buf, "%s%s", t->path, s->service_url);
@@ -156,13 +157,31 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (git_buf_oom(&buf))
return -1;
- git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
+ /* Convert URL to wide characters */
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ s->request_uri = git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!s->request_uri)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, s->request_uri, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
/* Establish request */
s->request = WinHttpOpenRequest(
t->connection,
s->verb,
- url,
+ s->request_uri,
NULL,
WINHTTP_NO_REFERER,
types,
@@ -179,19 +198,36 @@ static int winhttp_stream_connect(winhttp_stream *s)
if (proxy_url) {
WINHTTP_PROXY_INFO proxy_info;
- size_t wide_len;
+ wchar_t *proxy_wide;
- git__utf8_to_16(url, GIT_WIN_PATH, proxy_url);
+ /* Convert URL to wide characters */
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ proxy_url, -1, NULL, 0);
- wide_len = wcslen(url);
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ proxy_wide = git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!proxy_wide)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ proxy_url, -1, proxy_wide, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ git__free(proxy_wide);
+ goto on_error;
+ }
/* Strip any trailing forward slash on the proxy URL;
* WinHTTP doesn't like it if one is present */
- if (L'/' == url[wide_len - 1])
- url[wide_len - 1] = L'\0';
+ if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2])
+ proxy_wide[wide_len - 2] = L'\0';
proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
- proxy_info.lpszProxy = url;
+ proxy_info.lpszProxy = proxy_wide;
proxy_info.lpszProxyBypass = NULL;
if (!WinHttpSetOption(s->request,
@@ -199,8 +235,11 @@ static int winhttp_stream_connect(winhttp_stream *s)
&proxy_info,
sizeof(WINHTTP_PROXY_INFO))) {
giterr_set(GITERR_OS, "Failed to set proxy");
+ git__free(proxy_wide);
goto on_error;
}
+
+ git__free(proxy_wide);
}
/* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
@@ -348,8 +387,15 @@ static int winhttp_stream_read(
winhttp_stream *s = (winhttp_stream *)stream;
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
DWORD dw_bytes_read;
+ char replay_count = 0;
replay:
+ /* Enforce a reasonable cap on the number of replays */
+ if (++replay_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects or authentication replays");
+ return -1;
+ }
+
/* Connect if necessary */
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
@@ -445,10 +491,70 @@ replay:
WINHTTP_HEADER_NAME_BY_INDEX,
&status_code, &status_code_length,
WINHTTP_NO_HEADER_INDEX)) {
- giterr_set(GITERR_OS, "Failed to retreive status code");
+ giterr_set(GITERR_OS, "Failed to retrieve status code");
return -1;
}
+ /* The implementation of WinHTTP prior to Windows 7 will not
+ * redirect to an identical URI. Some Git hosters use self-redirects
+ * as part of their DoS mitigation strategy. Check first to see if we
+ * have a redirect status code, and that we haven't already streamed
+ * a post body. (We can't replay a streamed POST.) */
+ if (!s->chunked &&
+ (HTTP_STATUS_MOVED == status_code ||
+ HTTP_STATUS_REDIRECT == status_code ||
+ (HTTP_STATUS_REDIRECT_METHOD == status_code &&
+ get_verb == s->verb) ||
+ HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
+
+ /* Check for Windows 7. This workaround is only necessary on
+ * Windows Vista and earlier. Windows 7 is version 6.1. */
+ if (!git_has_win32_version(6, 1)) {
+ wchar_t *location;
+ DWORD location_length;
+ int redirect_cmp;
+
+ /* OK, fetch the Location header from the redirect. */
+ if (WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WINHTTP_NO_OUTPUT_BUFFER,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ return -1;
+ }
+
+ location = git__malloc(location_length);
+ GITERR_CHECK_ALLOC(location);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ location,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ git__free(location);
+ return -1;
+ }
+
+ /* Compare the Location header with the request URI */
+ redirect_cmp = wcscmp(location, s->request_uri);
+ git__free(location);
+
+ if (!redirect_cmp) {
+ /* Replay the request */
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ goto replay;
+ }
+ }
+ }
+
/* Handle authentication failures */
if (HTTP_STATUS_DENIED == status_code &&
get_verb == s->verb && t->owner->cred_acquire_cb) {
@@ -560,11 +666,11 @@ static int winhttp_stream_write_single(
return 0;
}
-static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch)
+static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
{
UUID uuid;
RPC_STATUS status = UuidCreate(&uuid);
- int result;
+ HRESULT result;
if (RPC_S_OK != status &&
RPC_S_UUID_LOCAL_ONLY != status &&
@@ -573,17 +679,19 @@ static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch)
return -1;
}
- if (buffer_len_cch < (UUID_LENGTH_CCH + 1)) {
- giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name");
+ if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer too small for name of temp file");
return -1;
}
- result = wsprintfW(buffer, L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+ result = StringCbPrintfW(
+ buffer, buffer_len_cch,
+ L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
uuid.Data1, uuid.Data2, uuid.Data3,
uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
- if (result != UUID_LENGTH_CCH) {
+ if (FAILED(result)) {
giterr_set(GITERR_OS, "Unable to generate name for temp file");
return -1;
}
@@ -602,17 +710,10 @@ static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
len = wcslen(buffer);
- /* 1 prefix character for the backslash, 1 postfix for
- * the null terminator */
- if (buffer_len_cch - len < 1 + UUID_LENGTH_CCH + 1) {
- giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name");
- return -1;
- }
-
- if (buffer[len - 1] != '\\')
+ if (buffer[len - 1] != '\\' && len < buffer_len_cch)
buffer[len++] = '\\';
- if (put_uuid_string(&buffer[len], UUID_LENGTH_CCH + 1) < 0)
+ if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
return -1;
return 0;
@@ -752,6 +853,11 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
s->post_body = NULL;
}
+ if (s->request_uri) {
+ git__free(s->request_uri);
+ s->request_uri = NULL;
+ }
+
if (s->request) {
WinHttpCloseHandle(s->request);
s->request = NULL;
@@ -881,7 +987,7 @@ static int winhttp_receivepack(
{
/* WinHTTP only supports Transfer-Encoding: chunked
* on Windows Vista (NT 6.0) and higher. */
- s->chunked = LOBYTE(LOWORD(GetVersion())) >= 6;
+ s->chunked = git_has_win32_version(6, 0);
if (s->chunked)
s->parent.write = winhttp_stream_write_chunked;
diff --git a/src/tree.c b/src/tree.c
index 11123a18a..17b3c378d 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -55,23 +55,28 @@ static int valid_entry_name(const char *filename)
strcmp(filename, DOT_GIT) != 0));
}
-int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
+static int entry_sort_cmp(const void *a, const void *b)
{
+ const git_tree_entry *e1 = (const git_tree_entry *)a;
+ const git_tree_entry *e2 = (const git_tree_entry *)b;
+
return git_path_cmp(
e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
- e2->filename, e2->filename_len, git_tree_entry__is_tree(e2));
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncmp);
}
-int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
+int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
{
- return git_path_icmp(
- e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
- e2->filename, e2->filename_len, git_tree_entry__is_tree(e2));
+ return entry_sort_cmp(e1, e2);
}
-static int entry_sort_cmp(const void *a, const void *b)
+int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
{
- return git_tree_entry_cmp((const git_tree_entry *)a, (const git_tree_entry *)b);
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncasecmp);
}
static git_tree_entry *alloc_entry(const char *filename)
diff --git a/src/tsort.c b/src/tsort.c
index 97473be91..4885e435b 100644
--- a/src/tsort.c
+++ b/src/tsort.c
@@ -24,7 +24,7 @@
#endif
static int binsearch(
- void **dst, const void *x, size_t size, git__tsort_r_cmp cmp, void *payload)
+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
{
int l, c, r;
void *lx, *cx;
@@ -71,7 +71,7 @@ static int binsearch(
/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
static void bisort(
- void **dst, size_t start, size_t size, git__tsort_r_cmp cmp, void *payload)
+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
{
size_t i;
void *x;
@@ -102,7 +102,7 @@ struct tsort_run {
struct tsort_store {
size_t alloc;
- git__tsort_r_cmp cmp;
+ git__sort_r_cmp cmp;
void *payload;
void **storage;
};
@@ -334,7 +334,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
while (0)
void git__tsort_r(
- void **dst, size_t size, git__tsort_r_cmp cmp, void *payload)
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
{
struct tsort_store _store, *store = &_store;
struct tsort_run run_stack[128];
diff --git a/src/unix/posix.h b/src/unix/posix.h
index c738b531d..f4886c5d1 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -8,6 +8,7 @@
#define INCLUDE_posix__w32_h__
#include <stdio.h>
+#include <sys/param.h>
#define p_lstat(p,b) lstat(p,b)
#define p_readlink(a, b, c) readlink(a, b, c)
diff --git a/src/util.c b/src/util.c
index 059108ece..44ac1af73 100644
--- a/src/util.c
+++ b/src/util.c
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <ctype.h>
#include "posix.h"
+#include "fileops.h"
#ifdef _MSC_VER
# include <Shlwapi.h>
@@ -37,14 +38,32 @@ int git_libgit2_capabilities()
/* Declarations for tuneable settings */
extern size_t git_mwindow__window_size;
extern size_t git_mwindow__mapped_limit;
+extern size_t git_odb__cache_size;
-void git_libgit2_opts(int key, ...)
+static int config_level_to_futils_dir(int config_level)
{
+ int val = -1;
+
+ switch (config_level) {
+ case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_FUTILS_DIR_SYSTEM; break;
+ case GIT_CONFIG_LEVEL_XDG: val = GIT_FUTILS_DIR_XDG; break;
+ case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_FUTILS_DIR_GLOBAL; break;
+ default:
+ giterr_set(
+ GITERR_INVALID, "Invalid config path selector %d", config_level);
+ }
+
+ return val;
+}
+
+int git_libgit2_opts(int key, ...)
+{
+ int error = 0;
va_list ap;
va_start(ap, key);
- switch(key) {
+ switch (key) {
case GIT_OPT_SET_MWINDOW_SIZE:
git_mwindow__window_size = va_arg(ap, size_t);
break;
@@ -60,9 +79,33 @@ void git_libgit2_opts(int key, ...)
case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
*(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
break;
+
+ case GIT_OPT_GET_SEARCH_PATH:
+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0) {
+ char *out = va_arg(ap, char *);
+ size_t outlen = va_arg(ap, size_t);
+
+ error = git_futils_dirs_get_str(out, outlen, error);
+ }
+ break;
+
+ case GIT_OPT_SET_SEARCH_PATH:
+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0)
+ error = git_futils_dirs_set(error, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_GET_ODB_CACHE_SIZE:
+ *(va_arg(ap, size_t *)) = git_odb__cache_size;
+ break;
+
+ case GIT_OPT_SET_ODB_CACHE_SIZE:
+ git_odb__cache_size = va_arg(ap, size_t);
+ break;
}
va_end(ap);
+
+ return error;
}
void git_strarray_free(git_strarray *array)
@@ -72,6 +115,8 @@ void git_strarray_free(git_strarray *array)
git__free(array->strings[i]);
git__free(array->strings);
+
+ memset(array, 0, sizeof(*array));
}
int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
@@ -89,8 +134,10 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
GITERR_CHECK_ALLOC(tgt->strings);
for (i = 0; i < src->count; ++i) {
- tgt->strings[tgt->count] = git__strdup(src->strings[i]);
+ if (!src->strings[i])
+ continue;
+ tgt->strings[tgt->count] = git__strdup(src->strings[i]);
if (!tgt->strings[tgt->count]) {
git_strarray_free(tgt);
memset(tgt, 0, sizeof(*tgt));
@@ -607,3 +654,56 @@ size_t git__unescape(char *str)
return (pos - str);
}
+
+#if defined(GIT_WIN32) || defined(BSD)
+typedef struct {
+ git__sort_r_cmp cmp;
+ void *payload;
+} git__qsort_r_glue;
+
+static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
+ void *payload, const void *a, const void *b)
+{
+ git__qsort_r_glue *glue = payload;
+ return glue->cmp(a, b, glue->payload);
+}
+#endif
+
+void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
+{
+#if defined(__MINGW32__)
+ git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
+#elif defined(GIT_WIN32)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
+#elif defined(BSD)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);
+#else
+ qsort_r(els, nel, elsize, cmp, payload);
+#endif
+}
+
+void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload)
+{
+ uint8_t *base = els;
+ uint8_t *end = base + nel * elsize;
+ uint8_t *i, *j;
+ bool freeswap = !swapel;
+
+ if (freeswap)
+ swapel = git__malloc(elsize);
+
+ for (i = base + elsize; i < end; i += elsize)
+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {
+ memcpy(swapel, j, elsize);
+ memcpy(j, j - elsize, elsize);
+ memcpy(j - elsize, swapel, elsize);
+ }
+
+ if (freeswap)
+ git__free(swapel);
+}
diff --git a/src/util.h b/src/util.h
index 9dbcb6a4f..c0f271997 100644
--- a/src/util.h
+++ b/src/util.h
@@ -146,11 +146,17 @@ typedef int (*git__tsort_cmp)(const void *a, const void *b);
extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);
-typedef int (*git__tsort_r_cmp)(const void *a, const void *b, void *payload);
+typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);
extern void git__tsort_r(
- void **dst, size_t size, git__tsort_r_cmp cmp, void *payload);
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);
+extern void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
+
+extern void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload);
/**
* @param position If non-NULL, this will be set to the position where the
diff --git a/src/win32/error.c b/src/win32/error.c
index 3851ff099..4a9a0631f 100644
--- a/src/win32/error.c
+++ b/src/win32/error.c
@@ -8,34 +8,70 @@
#include "common.h"
#include "error.h"
+#ifdef GIT_WINHTTP
+# include <winhttp.h>
+#endif
+
+#define WC_ERR_INVALID_CHARS 0x80
+
char *git_win32_get_error_message(DWORD error_code)
{
LPWSTR lpMsgBuf = NULL;
+ HMODULE hModule = NULL;
+ char *utf8_msg = NULL;
+ int utf8_size;
+ DWORD dwFlags =
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
if (!error_code)
return NULL;
- if (FormatMessageW(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPWSTR)&lpMsgBuf, 0, NULL)) {
- int utf8_size = WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, NULL, 0, NULL, NULL);
-
- char *lpMsgBuf_utf8 = git__malloc(utf8_size * sizeof(char));
- if (lpMsgBuf_utf8 == NULL) {
- LocalFree(lpMsgBuf);
- return NULL;
+#ifdef GIT_WINHTTP
+ /* Errors raised by WinHTTP are not in the system resource table */
+ if (error_code >= WINHTTP_ERROR_BASE &&
+ error_code <= WINHTTP_ERROR_LAST)
+ hModule = GetModuleHandleW(L"winhttp");
+#endif
+
+ GIT_UNUSED(hModule);
+
+ if (hModule)
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ else
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (FormatMessageW(dwFlags, hModule, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&lpMsgBuf, 0, NULL)) {
+
+ /* Invalid code point check supported on Vista+ only */
+ if (git_has_win32_version(6, 0))
+ dwFlags = WC_ERR_INVALID_CHARS;
+ else
+ dwFlags = 0;
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags,
+ lpMsgBuf, -1, NULL, 0, NULL, NULL);
+
+ if (!utf8_size) {
+ assert(0);
+ goto on_error;
}
- if (!WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, lpMsgBuf_utf8, utf8_size, NULL, NULL)) {
- LocalFree(lpMsgBuf);
- git__free(lpMsgBuf_utf8);
- return NULL;
+
+ utf8_msg = git__malloc(utf8_size);
+
+ if (!utf8_msg)
+ goto on_error;
+
+ if (!WideCharToMultiByte(CP_UTF8, dwFlags,
+ lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) {
+ git__free(utf8_msg);
+ goto on_error;
}
+on_error:
LocalFree(lpMsgBuf);
- return lpMsgBuf_utf8;
}
- return NULL;
+
+ return utf8_msg;
}
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
index 6fc7c7513..bc36b6b45 100644
--- a/src/win32/findfile.c
+++ b/src/win32/findfile.c
@@ -10,23 +10,34 @@
#include "findfile.h"
#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+
#ifndef _WIN64
#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
#else
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#endif
-int win32_expand_path(struct win32_path *s_root, const wchar_t *templ)
+int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ)
{
s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
return s_root->len ? 0 : -1;
}
-int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename)
+static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16)
+{
+ char temp_utf8[GIT_PATH_MAX];
+
+ git__utf16_to_8(temp_utf8, path_utf16);
+ git_path_mkposix(temp_utf8);
+
+ return git_buf_sets(path_utf8, temp_utf8);
+}
+
+int git_win32__find_file(
+ git_buf *path, const struct git_win32__path *root, const char *filename)
{
size_t len, alloc_len;
wchar_t *file_utf16 = NULL;
- char file_utf8[GIT_PATH_MAX];
if (!root || !filename || (len = strlen(filename)) == 0)
return GIT_ENOTFOUND;
@@ -50,15 +61,13 @@ int win32_find_file(git_buf *path, const struct win32_path *root, const char *fi
return GIT_ENOTFOUND;
}
- git__utf16_to_8(file_utf8, file_utf16);
- git_path_mkposix(file_utf8);
- git_buf_sets(path, file_utf8);
-
+ win32_path_utf16_to_8(path, file_utf16);
git__free(file_utf16);
+
return 0;
}
-wchar_t* win32_nextpath(wchar_t *path, wchar_t *buf, size_t buflen)
+static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
{
wchar_t term, *base = path;
@@ -77,80 +86,153 @@ wchar_t* win32_nextpath(wchar_t *path, wchar_t *buf, size_t buflen)
return (path != base) ? path : NULL;
}
-int win32_find_system_file_using_path(git_buf *path, const char *filename)
+static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)
{
- wchar_t * env = NULL;
- struct win32_path root;
+ wchar_t *env = _wgetenv(L"PATH"), lastch;
+ struct git_win32__path root;
+ size_t gitexe_len = wcslen(gitexe);
- env = _wgetenv(L"PATH");
if (!env)
return -1;
- // search in all paths defined in PATH
- while ((env = win32_nextpath(env, root.path, MAX_PATH - 1)) != NULL && *root.path)
- {
- wchar_t * pfin = root.path + wcslen(root.path) - 1; // last char of the current path entry
+ while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {
+ root.len = (DWORD)wcslen(root.path);
+ lastch = root.path[root.len - 1];
+
+ /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
+ if (lastch != L'/' && lastch != L'\\') {
+ root.path[root.len++] = L'\\';
+ root.path[root.len] = L'\0';
+ }
- // ensure trailing slash
- if (*pfin != L'/' && *pfin != L'\\')
- wcscpy(++pfin, L"\\"); // we have enough space left, MAX_PATH - 1 is used in nextpath above
+ if (root.len + gitexe_len >= MAX_PATH)
+ continue;
+ wcscpy(&root.path[root.len], gitexe);
- root.len = (DWORD)wcslen(root.path) + 1;
+ if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
+ /* replace "bin\\" or "cmd\\" with "etc\\" */
+ wcscpy(&root.path[root.len - 4], L"etc\\");
- if (win32_find_file(path, &root, "git.cmd") == 0 || win32_find_file(path, &root, "git.exe") == 0) {
- // we found the cmd or bin directory of a git installaton
- if (root.len > 5) {
- wcscpy(root.path + wcslen(root.path) - 4, L"etc\\");
- if (win32_find_file(path, &root, filename) == 0)
- return 0;
- }
+ win32_path_utf16_to_8(buf, root.path);
+ return 0;
}
}
-
+
return GIT_ENOTFOUND;
}
-int win32_find_system_file_using_registry(git_buf *path, const char *filename)
+static int win32_find_git_in_registry(
+ git_buf *buf, const HKEY hieve, const wchar_t *key)
{
- struct win32_path root;
+ HKEY hKey;
+ DWORD dwType = REG_SZ;
+ struct git_win32__path path16;
+
+ assert(buf);
+
+ path16.len = 0;
+
+ if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {
+ if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,
+ (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS)
+ {
+ /* InstallLocation points to the root of the git directory */
- if (win32_find_msysgit_in_registry(&root, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL)) {
- if (win32_find_msysgit_in_registry(&root, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL)) {
- giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory");
- return -1;
+ if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */
+ giterr_set(GITERR_OS, "Cannot locate git - path too long");
+ return -1;
+ }
+
+ wcscat(path16.path, L"etc\\");
+ path16.len += 4;
+
+ win32_path_utf16_to_8(buf, path16.path);
}
- }
- if (win32_find_file(path, &root, filename) < 0) {
- giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
- git_buf_clear(path);
- return GIT_ENOTFOUND;
+ RegCloseKey(hKey);
}
- return 0;
+ return path16.len ? 0 : GIT_ENOTFOUND;
}
-int win32_find_msysgit_in_registry(struct win32_path *root, const HKEY hieve, const wchar_t *key)
+static int win32_find_existing_dirs(
+ git_buf *out, const wchar_t *tmpl[], char *temp[])
{
- HKEY hKey;
- DWORD dwType = REG_SZ;
- DWORD dwSize = MAX_PATH;
+ struct git_win32__path path16;
+ git_buf buf = GIT_BUF_INIT;
- assert(root);
+ git_buf_clear(out);
- root->len = 0;
- if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {
- if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)&root->path, &dwSize) == ERROR_SUCCESS) {
- // InstallLocation points to the root of the msysgit directory
- if (dwSize + 4 > MAX_PATH) {// 4 = wcslen(L"etc\\")
- giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory - path too long");
- return -1;
- }
- wcscat(root->path, L"etc\\");
- root->len = (DWORD)wcslen(root->path) + 1;
+ for (; *tmpl != NULL; tmpl++) {
+ if (!git_win32__expand_path(&path16, *tmpl) &&
+ path16.path[0] != L'%' &&
+ !_waccess(path16.path, F_OK))
+ {
+ win32_path_utf16_to_8(&buf, path16.path);
+
+ if (buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
}
}
- RegCloseKey(hKey);
- return root->len ? 0 : GIT_ENOTFOUND;
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_system_dirs(git_buf *out)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* directories where git.exe & git.cmd are found */
+ if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size)
+ git_buf_set(out, buf.ptr, buf.size);
+ else
+ git_buf_clear(out);
+
+ if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ /* directories where git is installed according to registry */
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_global_dirs(git_buf *out)
+{
+ char *temp[3];
+ static const wchar_t *global_tmpls[4] = {
+ L"%HOME%\\",
+ L"%HOMEDRIVE%%HOMEPATH%\\",
+ L"%USERPROFILE%\\",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls, temp);
+}
+
+int git_win32__find_xdg_dirs(git_buf *out)
+{
+ char *temp[6];
+ static const wchar_t *global_tmpls[7] = {
+ L"%XDG_CONFIG_HOME%\\git",
+ L"%APPDATA%\\git",
+ L"%LOCALAPPDATA%\\git",
+ L"%HOME%\\.config\\git",
+ L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
+ L"%USERPROFILE%\\.config\\git",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls, temp);
}
+
diff --git a/src/win32/findfile.h b/src/win32/findfile.h
index 47fe71596..fc79e1b72 100644
--- a/src/win32/findfile.h
+++ b/src/win32/findfile.h
@@ -8,17 +8,20 @@
#ifndef INCLUDE_git_findfile_h__
#define INCLUDE_git_findfile_h__
-struct win32_path {
+struct git_win32__path {
wchar_t path[MAX_PATH];
DWORD len;
};
-int win32_expand_path(struct win32_path *s_root, const wchar_t *templ);
+extern int git_win32__expand_path(
+ struct git_win32__path *s_root, const wchar_t *templ);
-int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename);
-int win32_find_system_file_using_path(git_buf *path, const char *filename);
-int win32_find_system_file_using_registry(git_buf *path, const char *filename);
-int win32_find_msysgit_in_registry(struct win32_path *root, const HKEY hieve, const wchar_t *key);
+extern int git_win32__find_file(
+ git_buf *path, const struct git_win32__path *root, const char *filename);
+
+extern int git_win32__find_system_dirs(git_buf *out);
+extern int git_win32__find_global_dirs(git_buf *out);
+extern int git_win32__find_xdg_dirs(git_buf *out);
#endif
diff --git a/src/win32/version.h b/src/win32/version.h
new file mode 100644
index 000000000..483962b57
--- /dev/null
+++ b/src/win32/version.h
@@ -0,0 +1,20 @@
+/*
+ * 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_win32_version_h__
+#define INCLUDE_win32_version_h__
+
+#include <windows.h>
+
+GIT_INLINE(int) git_has_win32_version(int major, int minor)
+{
+ WORD wVersion = LOWORD(GetVersion());
+
+ return LOBYTE(wVersion) > major ||
+ (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor);
+}
+
+#endif