summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/array.h72
-rw-r--r--src/attr.c39
-rw-r--r--src/attr_file.c5
-rw-r--r--src/attr_file.h6
-rw-r--r--src/attrcache.h8
-rw-r--r--src/bitvec.h75
-rw-r--r--src/blob.c111
-rw-r--r--src/blob.h13
-rw-r--r--src/branch.c116
-rw-r--r--src/buf_text.c2
-rw-r--r--src/buffer.c9
-rw-r--r--src/buffer.h1
-rw-r--r--src/cache.c278
-rw-r--r--src/cache.h53
-rw-r--r--src/checkout.c250
-rw-r--r--src/clone.c116
-rw-r--r--src/commit.c183
-rw-r--r--src/commit.h10
-rw-r--r--src/commit_list.c22
-rw-r--r--src/config.c164
-rw-r--r--src/config.h3
-rw-r--r--src/config_cache.c31
-rw-r--r--src/config_file.c31
-rw-r--r--src/crlf.c6
-rw-r--r--src/date.c6
-rw-r--r--src/delta.c2
-rw-r--r--src/diff.c1043
-rw-r--r--src/diff.h74
-rw-r--r--src/diff_driver.c406
-rw-r--r--src/diff_driver.h49
-rw-r--r--src/diff_file.c440
-rw-r--r--src/diff_file.h58
-rw-r--r--src/diff_output.c1819
-rw-r--r--src/diff_output.h93
-rw-r--r--src/diff_patch.c1044
-rw-r--r--src/diff_patch.h46
-rw-r--r--src/diff_print.c456
-rw-r--r--src/diff_tform.c837
-rw-r--r--src/diff_xdiff.c166
-rw-r--r--src/diff_xdiff.h28
-rw-r--r--src/fetch.c15
-rw-r--r--src/fileops.c159
-rw-r--r--src/fileops.h19
-rw-r--r--src/global.c21
-rw-r--r--src/global.h8
-rw-r--r--src/hash/hash_generic.h6
-rw-r--r--src/hash/hash_win32.h8
-rw-r--r--src/hashsig.c215
-rw-r--r--src/ignore.c96
-rw-r--r--src/ignore.h11
-rw-r--r--src/index.c860
-rw-r--r--src/index.h12
-rw-r--r--src/indexer.c42
-rw-r--r--src/iterator.c710
-rw-r--r--src/iterator.h62
-rw-r--r--src/merge.c1926
-rw-r--r--src/merge.h137
-rw-r--r--src/merge_file.c174
-rw-r--r--src/merge_file.h71
-rw-r--r--src/mwindow.c2
-rw-r--r--src/notes.c93
-rw-r--r--src/object.c216
-rw-r--r--src/object.h1
-rw-r--r--src/object_api.c129
-rw-r--r--src/odb.c216
-rw-r--r--src/odb.h7
-rw-r--r--src/odb_loose.c48
-rw-r--r--src/odb_pack.c123
-rw-r--r--src/oid.c89
-rw-r--r--src/oid.h33
-rw-r--r--src/oidmap.h10
-rw-r--r--src/pack-objects.c80
-rw-r--r--src/pack.c161
-rw-r--r--src/pack.h4
-rw-r--r--src/path.h2
-rw-r--r--src/pathspec.c630
-rw-r--r--src/pathspec.h45
-rw-r--r--src/pool.c5
-rw-r--r--src/posix.c4
-rw-r--r--src/posix.h3
-rw-r--r--src/pqueue.h6
-rw-r--r--src/push.c43
-rw-r--r--src/refdb.c144
-rw-r--r--src/refdb.h32
-rw-r--r--src/refdb_fs.c543
-rw-r--r--src/reflog.c4
-rw-r--r--src/refs.c574
-rw-r--r--src/refs.h7
-rw-r--r--src/refspec.c53
-rw-r--r--src/refspec.h4
-rw-r--r--src/remote.c700
-rw-r--r--src/remote.h8
-rw-r--r--src/repository.c360
-rw-r--r--src/repository.h31
-rw-r--r--src/reset.c2
-rw-r--r--src/revparse.c236
-rw-r--r--src/revwalk.c21
-rw-r--r--src/signature.c32
-rw-r--r--src/stash.c17
-rw-r--r--src/status.c415
-rw-r--r--src/status.h23
-rw-r--r--src/submodule.c722
-rw-r--r--src/submodule.h74
-rw-r--r--src/tag.c73
-rw-r--r--src/tag.h5
-rw-r--r--src/thread-utils.h150
-rw-r--r--src/trace.c3
-rw-r--r--src/trace.h6
-rw-r--r--src/transport.c28
-rw-r--r--src/transports/cred.c108
-rw-r--r--src/transports/local.c48
-rw-r--r--src/transports/smart.c14
-rw-r--r--src/transports/smart_protocol.c46
-rw-r--r--src/transports/ssh.c531
-rw-r--r--src/transports/winhttp.c4
-rw-r--r--src/tree.c54
-rw-r--r--src/tree.h6
-rw-r--r--src/unix/posix.h2
-rw-r--r--src/unix/realpath.c2
-rw-r--r--src/util.c52
-rw-r--r--src/util.h60
-rw-r--r--src/vector.c8
-rw-r--r--src/vector.h9
-rw-r--r--src/win32/dir.c2
-rw-r--r--src/win32/error.c2
-rw-r--r--src/win32/findfile.c9
-rw-r--r--src/win32/git2.rc10
-rw-r--r--src/win32/posix_w32.c144
-rw-r--r--src/win32/pthread.c24
-rw-r--r--src/win32/pthread.h11
130 files changed, 14216 insertions, 5870 deletions
diff --git a/src/array.h b/src/array.h
new file mode 100644
index 000000000..c25a1b29e
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,72 @@
+/*
+ * 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_array_h__
+#define INCLUDE_array_h__
+
+#include "util.h"
+
+/*
+ * Use this to declare a typesafe resizable array of items, a la:
+ *
+ * git_array_t(int) my_ints = GIT_ARRAY_INIT;
+ * ...
+ * int *i = git_array_alloc(my_ints);
+ * GITERR_CHECK_ALLOC(i);
+ * ...
+ * git_array_clear(my_ints);
+ *
+ * You may also want to do things like:
+ *
+ * typedef git_array_t(my_struct) my_struct_array_t;
+ */
+#define git_array_t(type) struct { type *ptr; uint32_t size, asize; }
+
+#define GIT_ARRAY_INIT { NULL, 0, 0 }
+
+#define git_array_init(a) \
+ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
+
+#define git_array_init_to_size(a, desired) \
+ do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
+
+#define git_array_clear(a) \
+ do { git__free((a).ptr); git_array_init(a); } while (0)
+
+#define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr)
+
+
+typedef git_array_t(char) git_array_generic_t;
+
+/* use a generic array for growth so this can return the new item */
+GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
+{
+ git_array_generic_t *a = _a;
+ uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2;
+ char *new_array = git__realloc(a->ptr, new_size * item_size);
+ if (!new_array) {
+ git_array_clear(*a);
+ return NULL;
+ } else {
+ a->ptr = new_array; a->asize = new_size; a->size++;
+ return a->ptr + (a->size - 1) * item_size;
+ }
+}
+
+#define git_array_alloc(a) \
+ ((a).size >= (a).asize) ? \
+ git_array_grow(&(a), sizeof(*(a).ptr)) : \
+ (a).ptr ? &(a).ptr[(a).size++] : NULL
+
+#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
+
+#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
+
+#define git_array_size(a) (a).size
+
+#define git_array_valid_index(a, i) ((i) < (a).size)
+
+#endif
diff --git a/src/attr.c b/src/attr.c
index 979fecc14..6cdff29f9 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -36,7 +36,7 @@ static int collect_attr_files(
int git_attr_get(
const char **value,
- git_repository *repo,
+ git_repository *repo,
uint32_t flags,
const char *pathname,
const char *name)
@@ -88,10 +88,10 @@ typedef struct {
int git_attr_get_many(
const char **values,
- git_repository *repo,
+ git_repository *repo,
uint32_t flags,
const char *pathname,
- size_t num_attr,
+ size_t num_attr,
const char **names)
{
int error;
@@ -151,7 +151,7 @@ cleanup:
int git_attr_foreach(
- git_repository *repo,
+ git_repository *repo,
uint32_t flags,
const char *pathname,
int (*callback)(const char *name, const char *value, void *payload),
@@ -312,7 +312,7 @@ static int load_attr_blob_from_index(
entry = git_index_get_byindex(index, pos);
- if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
+ if (old_oid && git_oid__cmp(old_oid, &entry->oid) == 0)
return GIT_ENOTFOUND;
if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 0)
@@ -596,26 +596,33 @@ static int collect_attr_files(
}
static int attr_cache__lookup_path(
- const char **out, git_config *cfg, const char *key, const char *fallback)
+ char **out, git_config *cfg, const char *key, const char *fallback)
{
git_buf buf = GIT_BUF_INIT;
int error;
+ const char *cfgval = NULL;
- if (!(error = git_config_get_string(out, cfg, key)))
- return 0;
+ *out = NULL;
+
+ if (!(error = git_config_get_string(&cfgval, cfg, key))) {
- if (error == GIT_ENOTFOUND) {
+ /* expand leading ~/ as needed */
+ if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
+ !git_futils_find_global_file(&buf, &cfgval[2]))
+ *out = git_buf_detach(&buf);
+ else if (cfgval)
+ *out = git__strdup(cfgval);
+
+ } else if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
if (!git_futils_find_xdg_file(&buf, fallback))
*out = git_buf_detach(&buf);
- else
- *out = NULL;
-
- git_buf_free(&buf);
}
+ git_buf_free(&buf);
+
return error;
}
@@ -696,6 +703,12 @@ void git_attr_cache_flush(
git_pool_clear(&cache->pool);
+ git__free(cache->cfg_attr_file);
+ cache->cfg_attr_file = NULL;
+
+ git__free(cache->cfg_excl_file);
+ cache->cfg_excl_file = NULL;
+
cache->initialized = 0;
}
diff --git a/src/attr_file.c b/src/attr_file.c
index 93f6df1d9..ca5f2137c 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -423,7 +423,8 @@ int git_attr_fnmatch__parse_shellglob_format(
*base = scan;
- spec->length = scan - pattern;
+ if ((spec->length = scan - pattern) == 0)
+ return GIT_ENOTFOUND;
if (pattern[spec->length - 1] == '/') {
spec->length--;
@@ -523,7 +524,7 @@ int git_attr_assignment__parse(
assert(assigns && !assigns->length);
- assigns->_cmp = sort_by_hash_and_name;
+ git_vector_set_cmp(assigns, sort_by_hash_and_name);
while (*scan && *scan != '\n') {
const char *name_start, *value_start;
diff --git a/src/attr_file.h b/src/attr_file.h
index 8ca7e4eb7..afea1e115 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -47,14 +47,14 @@ typedef struct {
typedef struct {
git_refcount unused;
const char *name;
- uint32_t name_hash;
+ uint32_t name_hash;
} git_attr_name;
typedef struct {
git_refcount rc; /* for macros */
char *name;
- uint32_t name_hash;
- const char *value;
+ uint32_t name_hash;
+ const char *value;
} git_attr_assignment;
typedef struct {
diff --git a/src/attrcache.h b/src/attrcache.h
index 12cec4bfb..077633b87 100644
--- a/src/attrcache.h
+++ b/src/attrcache.h
@@ -13,10 +13,10 @@
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_strmap *files; /* hash path to git_attr_file of rules */
+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */
+ char *cfg_attr_file; /* cached value of core.attributesfile */
+ char *cfg_excl_file; /* cached value of core.excludesfile */
} git_attr_cache;
extern int git_attr_cache__init(git_repository *repo);
diff --git a/src/bitvec.h b/src/bitvec.h
new file mode 100644
index 000000000..fd6f0ccf8
--- /dev/null
+++ b/src/bitvec.h
@@ -0,0 +1,75 @@
+/*
+ * 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_bitvec_h__
+#define INCLUDE_bitvec_h__
+
+#include "util.h"
+
+/*
+ * This is a silly little fixed length bit vector type that will store
+ * vectors of 64 bits or less directly in the structure and allocate
+ * memory for vectors longer than 64 bits. You can use the two versions
+ * transparently through the API and avoid heap allocation completely when
+ * using a short bit vector as a result.
+ */
+typedef struct {
+ size_t length;
+ union {
+ uint64_t *words;
+ uint64_t bits;
+ } u;
+} git_bitvec;
+
+GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
+{
+ memset(bv, 0x0, sizeof(*bv));
+
+ if (capacity >= 64) {
+ bv->length = (capacity / 64) + 1;
+ bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
+ if (!bv->u.words)
+ return -1;
+ }
+
+ return 0;
+}
+
+#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
+#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
+
+GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ uint64_t mask = GIT_BITVEC_MASK(bit);
+
+ if (on)
+ *word |= mask;
+ else
+ *word &= ~mask;
+}
+
+GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ return (*word & GIT_BITVEC_MASK(bit)) != 0;
+}
+
+GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
+{
+ if (!bv->length)
+ bv->u.bits = 0;
+ else
+ memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
+}
+
+GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
+{
+ if (bv->length)
+ git__free(bv->u.words);
+}
+
+#endif
diff --git a/src/blob.c b/src/blob.c
index c0514fc13..5bb51f7cf 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -8,8 +8,10 @@
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
+#include "git2/odb_backend.h"
#include "common.h"
+#include "filebuf.h"
#include "blob.h"
#include "filter.h"
#include "buf_text.h"
@@ -17,32 +19,34 @@
const void *git_blob_rawcontent(const git_blob *blob)
{
assert(blob);
- return blob->odb_object->raw.data;
+ return git_odb_object_data(blob->odb_object);
}
git_off_t git_blob_rawsize(const git_blob *blob)
{
assert(blob);
- return (git_off_t)blob->odb_object->raw.len;
+ return (git_off_t)git_odb_object_size(blob->odb_object);
}
int git_blob__getbuf(git_buf *buffer, git_blob *blob)
{
return git_buf_set(
- buffer, blob->odb_object->raw.data, blob->odb_object->raw.len);
+ buffer,
+ git_odb_object_data(blob->odb_object),
+ git_odb_object_size(blob->odb_object));
}
-void git_blob__free(git_blob *blob)
+void git_blob__free(void *blob)
{
- git_odb_object_free(blob->odb_object);
+ git_odb_object_free(((git_blob *)blob)->odb_object);
git__free(blob);
}
-int git_blob__parse(git_blob *blob, git_odb_object *odb_obj)
+int git_blob__parse(void *blob, git_odb_object *odb_obj)
{
assert(blob);
git_cached_obj_incref((git_cached_obj *)odb_obj);
- blob->odb_object = odb_obj;
+ ((git_blob *)blob)->odb_object = odb_obj;
return 0;
}
@@ -101,6 +105,7 @@ static int write_file_stream(
static int write_file_filtered(
git_oid *oid,
+ git_off_t *size,
git_odb *odb,
const char *full_path,
git_vector *filters)
@@ -119,8 +124,11 @@ static int write_file_filtered(
git_buf_free(&source);
/* Write the file to disk if it was properly filtered */
- if (!error)
+ if (!error) {
+ *size = dest.size;
+
error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB);
+ }
git_buf_free(&dest);
return error;
@@ -148,21 +156,46 @@ static int write_symlink(
return error;
}
-static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters)
+int git_blob__create_from_paths(
+ git_oid *oid,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *content_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool try_load_filters)
{
int error;
struct stat st;
git_odb *odb = NULL;
git_off_t size;
+ mode_t mode;
+ git_buf path = GIT_BUF_INIT;
assert(hint_path || !try_load_filters);
- if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0)
- return error;
+ if (!content_path) {
+ if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
+ return GIT_EBAREREPO;
+
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(repo), hint_path) < 0)
+ return -1;
+
+ content_path = path.ptr;
+ }
+
+ if ((error = git_path_lstat(content_path, &st)) < 0 ||
+ (error = git_repository_odb(&odb, repo)) < 0)
+ goto done;
+
+ if (out_st)
+ memcpy(out_st, &st, sizeof(st));
size = st.st_size;
+ mode = hint_mode ? hint_mode : st.st_mode;
- if (S_ISLNK(st.st_mode)) {
+ if (S_ISLNK(mode)) {
error = write_symlink(oid, odb, content_path, (size_t)size);
} else {
git_vector write_filters = GIT_VECTOR_INIT;
@@ -183,7 +216,8 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
error = write_file_stream(oid, odb, content_path, size);
} else {
/* We need to apply one or more filters */
- error = write_file_filtered(oid, odb, content_path, &write_filters);
+ error = write_file_filtered(
+ oid, &size, odb, content_path, &write_filters);
}
git_filters_free(&write_filters);
@@ -203,34 +237,21 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
*/
}
+done:
+ git_odb_free(odb);
+ git_buf_free(&path);
+
return error;
}
-int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path)
+int git_blob_create_fromworkdir(
+ git_oid *oid, git_repository *repo, const char *path)
{
- git_buf full_path = GIT_BUF_INIT;
- const char *workdir;
- int error;
-
- if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)
- return error;
-
- workdir = git_repository_workdir(repo);
-
- if (git_buf_joinpath(&full_path, workdir, path) < 0) {
- git_buf_free(&full_path);
- return -1;
- }
-
- 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;
+ return git_blob__create_from_paths(oid, NULL, repo, NULL, path, 0, true);
}
-int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path)
+int git_blob_create_fromdisk(
+ git_oid *oid, git_repository *repo, const char *path)
{
int error;
git_buf full_path = GIT_BUF_INIT;
@@ -247,8 +268,8 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat
if (workdir && !git__prefixcmp(hintpath, workdir))
hintpath += strlen(workdir);
- error = blob_create_internal(
- oid, repo, git_buf_cstr(&full_path), hintpath, true);
+ error = git_blob__create_from_paths(
+ oid, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
git_buf_free(&full_path);
return error;
@@ -268,12 +289,9 @@ int git_blob_create_fromchunks(
git_filebuf file = GIT_FILEBUF_INIT;
git_buf path = GIT_BUF_INIT;
- if (git_buf_join_n(
- &path, '/', 3,
- git_repository_path(repo),
- GIT_OBJECTS_DIR,
- "streamed") < 0)
- goto cleanup;
+ if (git_buf_joinpath(
+ &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed") < 0)
+ goto cleanup;
content = git__malloc(BUFFER_SIZE);
GITERR_CHECK_ALLOC(content);
@@ -299,7 +317,8 @@ int git_blob_create_fromchunks(
if (git_filebuf_flush(&file) < 0)
goto cleanup;
- error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL);
+ error = git_blob__create_from_paths(
+ oid, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL);
cleanup:
git_buf_free(&path);
@@ -314,8 +333,8 @@ int git_blob_is_binary(git_blob *blob)
assert(blob);
- content.ptr = blob->odb_object->raw.data;
- content.size = min(blob->odb_object->raw.len, 4000);
+ content.ptr = blob->odb_object->buffer;
+ content.size = min(blob->odb_object->cached.size, 4000);
return git_buf_text_is_binary(&content);
}
diff --git a/src/blob.h b/src/blob.h
index 524734b1f..4cd9f1e0c 100644
--- a/src/blob.h
+++ b/src/blob.h
@@ -17,8 +17,17 @@ struct git_blob {
git_odb_object *odb_object;
};
-void git_blob__free(git_blob *blob);
-int git_blob__parse(git_blob *blob, git_odb_object *obj);
+void git_blob__free(void *blob);
+int git_blob__parse(void *blob, git_odb_object *obj);
int git_blob__getbuf(git_buf *buffer, git_blob *blob);
+extern int git_blob__create_from_paths(
+ git_oid *out_oid,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *full_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool apply_filters);
+
#endif
diff --git a/src/branch.c b/src/branch.c
index e7088790e..7064fa7fc 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -11,6 +11,7 @@
#include "config.h"
#include "refspec.h"
#include "refs.h"
+#include "remote.h"
#include "git2/branch.h"
@@ -123,40 +124,48 @@ on_error:
return error;
}
-typedef struct {
- git_branch_foreach_cb branch_cb;
- void *callback_payload;
- unsigned int branch_type;
-} branch_foreach_filter;
-
-static int branch_foreach_cb(const char *branch_name, void *payload)
-{
- branch_foreach_filter *filter = (branch_foreach_filter *)payload;
-
- if (filter->branch_type & GIT_BRANCH_LOCAL &&
- git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
- return filter->branch_cb(branch_name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, filter->callback_payload);
-
- if (filter->branch_type & GIT_BRANCH_REMOTE &&
- git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)
- return filter->branch_cb(branch_name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, filter->callback_payload);
-
- return 0;
-}
-
int git_branch_foreach(
git_repository *repo,
unsigned int list_flags,
- git_branch_foreach_cb branch_cb,
+ git_branch_foreach_cb callback,
void *payload)
{
- branch_foreach_filter filter;
+ git_reference_iterator *iter;
+ git_reference *ref;
+ int error = 0;
+
+ if (git_reference_iterator_new(&iter, repo) < 0)
+ return -1;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ if (list_flags & GIT_BRANCH_LOCAL &&
+ git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0) {
+ if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR),
+ GIT_BRANCH_LOCAL, payload)) {
+ error = GIT_EUSER;
+ }
+ }
+
+ if (list_flags & GIT_BRANCH_REMOTE &&
+ git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0) {
+ if (callback(ref->name + strlen(GIT_REFS_REMOTES_DIR),
+ GIT_BRANCH_REMOTE, payload)) {
+ error = GIT_EUSER;
+ }
+ }
+
+ git_reference_free(ref);
- filter.branch_cb = branch_cb;
- filter.branch_type = list_flags;
- filter.callback_payload = payload;
+ /* check if the callback has cancelled iteration */
+ if (error == GIT_EUSER)
+ break;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
- return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter);
+ git_reference_iterator_free(iter);
+ return error;
}
int git_branch_move(
@@ -175,18 +184,21 @@ 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 ||
- (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)
+ error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name);
+ if (error < 0)
goto done;
+ git_buf_printf(&old_config_section,
+ "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
+
+ git_buf_printf(&new_config_section, "branch.%s", new_branch_name);
+
if ((error = git_config_rename_section(git_reference_owner(branch),
git_buf_cstr(&old_config_section),
git_buf_cstr(&new_config_section))) < 0)
goto done;
-
- if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)
- goto done;
+
+ error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force);
done:
git_buf_free(&new_reference_name);
@@ -275,6 +287,8 @@ int git_branch_upstream__name(
goto cleanup;
if (!*remote_name || !*merge_name) {
+ giterr_set(GITERR_REFERENCE,
+ "branch '%s' does not have an upstream", canonical_branch_name);
error = GIT_ENOTFOUND;
goto cleanup;
}
@@ -283,12 +297,10 @@ int git_branch_upstream__name(
if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
goto cleanup;
- refspec = git_remote_fetchspec(remote);
- if (refspec == NULL
- || refspec->src == NULL
- || refspec->dst == NULL) {
- error = GIT_ENOTFOUND;
- goto cleanup;
+ refspec = git_remote__matching_refspec(remote, merge_name);
+ if (!refspec) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
}
if (git_refspec_transform_r(&buf, refspec, merge_name) < 0)
@@ -333,11 +345,8 @@ static int remote_name(git_buf *buf, git_repository *repo, const char *canonical
if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
continue;
- fetchspec = git_remote_fetchspec(remote);
-
- /* Defensivly check that we have a fetchspec */
- if (fetchspec &&
- git_refspec_dst_matches(fetchspec, canonical_branch_name)) {
+ fetchspec = git_remote__matching_dst_refspec(remote, canonical_branch_name);
+ if (fetchspec) {
/* If we have not already set out yet, then set
* it to the matching remote name. Otherwise
* multiple remotes match this reference, and it
@@ -346,6 +355,9 @@ static int remote_name(git_buf *buf, git_repository *repo, const char *canonical
remote_name = remote_list.strings[i];
} else {
git_remote_free(remote);
+
+ giterr_set(GITERR_REFERENCE,
+ "Reference '%s' is ambiguous", canonical_branch_name);
error = GIT_EAMBIGUOUS;
goto cleanup;
}
@@ -358,6 +370,8 @@ static int remote_name(git_buf *buf, git_repository *repo, const char *canonical
git_buf_clear(buf);
error = git_buf_puts(buf, remote_name);
} else {
+ giterr_set(GITERR_REFERENCE,
+ "Could not determine remote for '%s'", canonical_branch_name);
error = GIT_ENOTFOUND;
}
@@ -377,7 +391,7 @@ int git_branch_remote_name(char *buffer, size_t buffer_len, git_repository *repo
if (buffer)
git_buf_copy_cstr(buffer, buffer_len, &buf);
- ret = git_buf_len(&buf) + 1;
+ ret = (int)git_buf_len(&buf) + 1;
git_buf_free(&buf);
return ret;
@@ -494,8 +508,11 @@ int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
local = 1;
else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
local = 0;
- else
+ else {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot set upstream for branch '%s'", shortname);
return GIT_ENOTFOUND;
+ }
/*
* If it's local, the remote is "." and the branch name is
@@ -515,16 +532,17 @@ int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
goto on_error;
if (local) {
- if (git_buf_puts(&value, git_reference_name(branch)) < 0)
+ git_buf_clear(&value);
+ if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
goto on_error;
} else {
/* Get the remoe-tracking branch's refname in its repo */
if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
goto on_error;
- fetchspec = git_remote_fetchspec(remote);
+ fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
git_buf_clear(&value);
- if (git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0)
+ if (!fetchspec || git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0)
goto on_error;
git_remote_free(remote);
diff --git a/src/buf_text.c b/src/buf_text.c
index 443454b5f..472339def 100644
--- a/src/buf_text.c
+++ b/src/buf_text.c
@@ -262,7 +262,7 @@ bool git_buf_text_gather_stats(
while (scan < end) {
unsigned char c = *scan++;
- if ((c > 0x1F && c < 0x7F) || c > 0x9f)
+ if (c > 0x1F && c != 0x7F)
stats->printable++;
else switch (c) {
case '\0':
diff --git a/src/buffer.c b/src/buffer.c
index 6e3ffe560..b5b2fd678 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -259,6 +259,15 @@ void git_buf_truncate(git_buf *buf, size_t len)
}
}
+void git_buf_shorten(git_buf *buf, size_t amount)
+{
+ if (amount > buf->size)
+ amount = buf->size;
+
+ buf->size = buf->size - amount;
+ buf->ptr[buf->size] = '\0';
+}
+
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
{
ssize_t idx = git_buf_rfind_next(buf, separator);
diff --git a/src/buffer.h b/src/buffer.h
index 5402f3827..f3e1d506f 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -91,6 +91,7 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap);
void git_buf_clear(git_buf *buf);
void git_buf_consume(git_buf *buf, const char *end);
void git_buf_truncate(git_buf *buf, size_t len);
+void git_buf_shorten(git_buf *buf, size_t amount);
void git_buf_rtruncate_at_char(git_buf *path, char separator);
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
diff --git a/src/cache.c b/src/cache.c
index e7f333577..36ce66570 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -11,100 +11,270 @@
#include "thread-utils.h"
#include "util.h"
#include "cache.h"
+#include "odb.h"
+#include "object.h"
#include "git2/oid.h"
-int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
+GIT__USE_OIDMAP
+
+bool git_cache__enabled = true;
+ssize_t git_cache__max_storage = (256 * 1024 * 1024);
+git_atomic_ssize git_cache__current_storage = {0};
+
+static size_t git_cache__max_object_size[8] = {
+ 0, /* GIT_OBJ__EXT1 */
+ 4096, /* GIT_OBJ_COMMIT */
+ 4096, /* GIT_OBJ_TREE */
+ 0, /* GIT_OBJ_BLOB */
+ 4096, /* GIT_OBJ_TAG */
+ 0, /* GIT_OBJ__EXT2 */
+ 0, /* GIT_OBJ_OFS_DELTA */
+ 0 /* GIT_OBJ_REF_DELTA */
+};
+
+int git_cache_set_max_object_size(git_otype type, size_t size)
+{
+ if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
+ giterr_set(GITERR_INVALID, "type out of range");
+ return -1;
+ }
+
+ git_cache__max_object_size[type] = size;
+ return 0;
+}
+
+void git_cache_dump_stats(git_cache *cache)
{
- if (size < 8)
- size = 8;
- size = git__size_t_powerof2(size);
+ git_cached_obj *object;
- cache->size_mask = size - 1;
- cache->lru_count = 0;
- cache->free_obj = free_ptr;
+ if (kh_size(cache->map) == 0)
+ return;
- git_mutex_init(&cache->lock);
+ printf("Cache %p: %d items cached, %d bytes\n",
+ cache, kh_size(cache->map), (int)cache->used_memory);
- cache->nodes = git__malloc(size * sizeof(git_cached_obj *));
- GITERR_CHECK_ALLOC(cache->nodes);
+ kh_foreach_value(cache->map, object, {
+ char oid_str[9];
+ printf(" %s%c %s (%d)\n",
+ git_object_type2string(object->type),
+ object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
+ git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
+ (int)object->size
+ );
+ });
+}
- memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *));
+int git_cache_init(git_cache *cache)
+{
+ memset(cache, 0, sizeof(*cache));
+ cache->map = git_oidmap_alloc();
+ if (git_mutex_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize cache mutex");
+ return -1;
+ }
return 0;
}
+/* called with lock */
+static void clear_cache(git_cache *cache)
+{
+ git_cached_obj *evict = NULL;
+
+ if (kh_size(cache->map) == 0)
+ return;
+
+ kh_foreach_value(cache->map, evict, {
+ git_cached_obj_decref(evict);
+ });
+
+ kh_clear(oid, cache->map);
+ git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
+ cache->used_memory = 0;
+}
+
+void git_cache_clear(git_cache *cache)
+{
+ if (git_mutex_lock(&cache->lock) < 0)
+ return;
+
+ clear_cache(cache);
+
+ git_mutex_unlock(&cache->lock);
+}
+
void git_cache_free(git_cache *cache)
{
- size_t i;
+ git_cache_clear(cache);
+ git_oidmap_free(cache->map);
+ git_mutex_free(&cache->lock);
+ git__memzero(cache, sizeof(*cache));
+}
+
+/* Called with lock */
+static void cache_evict_entries(git_cache *cache)
+{
+ uint32_t seed = rand();
+ size_t evict_count = 8;
+ ssize_t evicted_memory = 0;
- for (i = 0; i < (cache->size_mask + 1); ++i) {
- if (cache->nodes[i] != NULL)
- git_cached_obj_decref(cache->nodes[i], cache->free_obj);
+ /* do not infinite loop if there's not enough entries to evict */
+ if (evict_count > kh_size(cache->map)) {
+ clear_cache(cache);
+ return;
}
- git_mutex_free(&cache->lock);
- git__free(cache->nodes);
+ while (evict_count > 0) {
+ khiter_t pos = seed++ % kh_end(cache->map);
+
+ if (kh_exist(cache->map, pos)) {
+ git_cached_obj *evict = kh_val(cache->map, pos);
+
+ evict_count--;
+ evicted_memory += evict->size;
+ git_cached_obj_decref(evict);
+
+ kh_del(oid, cache->map, pos);
+ }
+ }
+
+ cache->used_memory -= evicted_memory;
+ git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
}
-void *git_cache_get(git_cache *cache, const git_oid *oid)
+static bool cache_should_store(git_otype object_type, size_t object_size)
{
- uint32_t hash;
- git_cached_obj *node = NULL, *result = NULL;
+ size_t max_size = git_cache__max_object_size[object_type];
+ return git_cache__enabled && object_size < max_size;
+}
- memcpy(&hash, oid->id, sizeof(hash));
+static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
+{
+ khiter_t pos;
+ git_cached_obj *entry = NULL;
- if (git_mutex_lock(&cache->lock)) {
- giterr_set(GITERR_THREAD, "unable to lock cache mutex");
+ if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
return NULL;
- }
- {
- node = cache->nodes[hash & cache->size_mask];
+ pos = kh_get(oid, cache->map, oid);
+ if (pos != kh_end(cache->map)) {
+ entry = kh_val(cache->map, pos);
- if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) {
- git_cached_obj_incref(node);
- result = node;
+ if (flags && entry->flags != flags) {
+ entry = NULL;
+ } else {
+ git_cached_obj_incref(entry);
}
}
+
git_mutex_unlock(&cache->lock);
- return result;
+ return entry;
}
-void *git_cache_try_store(git_cache *cache, void *_entry)
+static void *cache_store(git_cache *cache, git_cached_obj *entry)
{
- git_cached_obj *entry = _entry;
- uint32_t hash;
+ khiter_t pos;
- memcpy(&hash, &entry->oid, sizeof(uint32_t));
+ git_cached_obj_incref(entry);
- if (git_mutex_lock(&cache->lock)) {
- giterr_set(GITERR_THREAD, "unable to lock cache mutex");
- return NULL;
+ if (!git_cache__enabled && cache->used_memory > 0) {
+ git_cache_clear(cache);
+ return entry;
}
- {
- git_cached_obj *node = cache->nodes[hash & cache->size_mask];
+ if (!cache_should_store(entry->type, entry->size))
+ return entry;
- /* increase the refcount on this object, because
- * the cache now owns it */
- git_cached_obj_incref(entry);
+ if (git_mutex_lock(&cache->lock) < 0)
+ return entry;
- if (node == NULL) {
- cache->nodes[hash & cache->size_mask] = entry;
- } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {
- git_cached_obj_decref(entry, cache->free_obj);
- entry = node;
- } else {
- git_cached_obj_decref(node, cache->free_obj);
- cache->nodes[hash & cache->size_mask] = entry;
+ /* soften the load on the cache */
+ if (git_cache__current_storage.val > git_cache__max_storage)
+ cache_evict_entries(cache);
+
+ pos = kh_get(oid, cache->map, &entry->oid);
+
+ /* not found */
+ if (pos == kh_end(cache->map)) {
+ int rval;
+
+ pos = kh_put(oid, cache->map, &entry->oid, &rval);
+ if (rval >= 0) {
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ git_cached_obj_incref(entry);
+ cache->used_memory += entry->size;
+ git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
}
+ }
+ /* found */
+ else {
+ git_cached_obj *stored_entry = kh_val(cache->map, pos);
- /* increase the refcount again, because we are
- * returning it to the user */
- git_cached_obj_incref(entry);
+ if (stored_entry->flags == entry->flags) {
+ git_cached_obj_decref(entry);
+ git_cached_obj_incref(stored_entry);
+ entry = stored_entry;
+ } else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
+ entry->flags == GIT_CACHE_STORE_PARSED) {
+ git_cached_obj_decref(stored_entry);
+ git_cached_obj_incref(entry);
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ } else {
+ /* NO OP */
+ }
}
- git_mutex_unlock(&cache->lock);
+ git_mutex_unlock(&cache->lock);
return entry;
}
+
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_RAW;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+void *git_cache_store_parsed(git_cache *cache, git_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_PARSED;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
+}
+
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
+}
+
+void *git_cache_get_any(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
+}
+
+void git_cached_obj_decref(void *_obj)
+{
+ git_cached_obj *obj = _obj;
+
+ if (git_atomic_dec(&obj->refcount) == 0) {
+ switch (obj->flags) {
+ case GIT_CACHE_STORE_RAW:
+ git_odb_object__free(_obj);
+ break;
+
+ case GIT_CACHE_STORE_PARSED:
+ git_object__free(_obj);
+ break;
+
+ default:
+ git__free(_obj);
+ break;
+ }
+ }
+}
diff --git a/src/cache.h b/src/cache.h
index 7034ec268..53fbcf4e9 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -12,43 +12,56 @@
#include "git2/odb.h"
#include "thread-utils.h"
+#include "oidmap.h"
-#define GIT_DEFAULT_CACHE_SIZE 128
-
-typedef void (*git_cached_obj_freeptr)(void *);
+enum {
+ GIT_CACHE_STORE_ANY = 0,
+ GIT_CACHE_STORE_RAW = 1,
+ GIT_CACHE_STORE_PARSED = 2
+};
typedef struct {
- git_oid oid;
+ git_oid oid;
+ int16_t type; /* git_otype value */
+ uint16_t flags; /* GIT_CACHE_STORE value */
+ size_t size;
git_atomic refcount;
} git_cached_obj;
typedef struct {
- git_cached_obj **nodes;
- git_mutex lock;
-
- unsigned int lru_count;
- size_t size_mask;
- git_cached_obj_freeptr free_obj;
+ git_oidmap *map;
+ git_mutex lock;
+ ssize_t used_memory;
} git_cache;
-int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr);
+extern bool git_cache__enabled;
+extern ssize_t git_cache__max_storage;
+extern git_atomic_ssize git_cache__current_storage;
+
+int git_cache_set_max_object_size(git_otype type, size_t size);
+
+int git_cache_init(git_cache *cache);
void git_cache_free(git_cache *cache);
+void git_cache_clear(git_cache *cache);
-void *git_cache_try_store(git_cache *cache, void *entry);
-void *git_cache_get(git_cache *cache, const git_oid *oid);
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry);
+void *git_cache_store_parsed(git_cache *cache, git_object *entry);
-GIT_INLINE(void) git_cached_obj_incref(void *_obj)
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid);
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid);
+void *git_cache_get_any(git_cache *cache, const git_oid *oid);
+
+GIT_INLINE(size_t) git_cache_size(git_cache *cache)
{
- git_cached_obj *obj = _obj;
- git_atomic_inc(&obj->refcount);
+ return (size_t)kh_size(cache->map);
}
-GIT_INLINE(void) git_cached_obj_decref(void *_obj, git_cached_obj_freeptr free_obj)
+GIT_INLINE(void) git_cached_obj_incref(void *_obj)
{
git_cached_obj *obj = _obj;
-
- if (git_atomic_dec(&obj->refcount) == 0)
- free_obj(obj);
+ git_atomic_inc(&obj->refcount);
}
+void git_cached_obj_decref(void *_obj);
+
#endif
diff --git a/src/checkout.c b/src/checkout.c
index 24fa21024..ec9da7e2e 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -16,9 +16,11 @@
#include "git2/config.h"
#include "git2/diff.h"
#include "git2/submodule.h"
+#include "git2/sys/index.h"
#include "refs.h"
#include "repository.h"
+#include "index.h"
#include "filter.h"
#include "blob.h"
#include "diff.h"
@@ -119,6 +121,7 @@ static bool checkout_is_workdir_modified(
const git_index_entry *wditem)
{
git_oid oid;
+ const git_index_entry *ie;
/* handle "modified" submodule */
if (wditem->mode == GIT_FILEMODE_COMMIT) {
@@ -137,7 +140,18 @@ static bool checkout_is_workdir_modified(
if (!sm_oid)
return false;
- return (git_oid_cmp(&baseitem->oid, sm_oid) != 0);
+ return (git_oid__cmp(&baseitem->oid, sm_oid) != 0);
+ }
+
+ /* Look at the cache to decide if the workdir is modified. If not,
+ * we can simply compare the oid in the cache to the baseitem instead
+ * of hashing the file.
+ */
+ if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) {
+ if (wditem->mtime.seconds == ie->mtime.seconds &&
+ wditem->mtime.nanoseconds == ie->mtime.nanoseconds &&
+ wditem->file_size == ie->file_size)
+ return (git_oid__cmp(&baseitem->oid, &ie->oid) != 0);
}
/* depending on where base is coming from, we may or may not know
@@ -151,7 +165,7 @@ static bool checkout_is_workdir_modified(
wditem->file_size, &oid) < 0)
return false;
- return (git_oid_cmp(&baseitem->oid, &oid) != 0);
+ return (git_oid__cmp(&baseitem->oid, &oid) != 0);
}
#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
@@ -176,6 +190,10 @@ static int checkout_action_common(
action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
CHECKOUT_ACTION__UPDATE_SUBMODULE;
+ /* to "update" a symlink, we must remove the old one first */
+ if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL)
+ action |= CHECKOUT_ACTION__REMOVE;
+
notify = GIT_CHECKOUT_NOTIFY_UPDATED;
}
@@ -202,9 +220,11 @@ static int checkout_action_no_wd(
action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
- case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break;
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
+ break;
case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
@@ -226,10 +246,10 @@ static int checkout_action_wd_only(
bool remove = false;
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
- if (!git_pathspec_match_path(
+ if (!git_pathspec__match(
pathspec, wd->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
- git_iterator_ignore_case(workdir), NULL))
+ git_iterator_ignore_case(workdir), NULL, NULL))
return 0;
/* check if item is tracked in the index but not in the checkout diff */
@@ -454,6 +474,7 @@ static int checkout_action(
int cmp = -1, act;
int (*strcomp)(const char *, const char *) = data->diff->strcomp;
int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
+ int error;
/* move workdir iterator to follow along with deltas */
@@ -477,9 +498,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(&wd, workdir) < 0)
+ error = git_iterator_advance_into_or_over(&wd, workdir);
+ if (error && error != GIT_ITEROVER)
goto fail;
-
*wditem_ptr = wd;
continue;
}
@@ -494,8 +515,10 @@ static int checkout_action(
}
/* case 1 - handle wd item (if it matches pathspec) */
- if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||
- git_iterator_advance(&wd, workdir) < 0)
+ if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0)
+ goto fail;
+ if ((error = git_iterator_advance(&wd, workdir)) < 0 &&
+ error != GIT_ITEROVER)
goto fail;
*wditem_ptr = wd;
@@ -518,8 +541,9 @@ 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(&wd, workdir) < 0)
- wd = NULL;
+ if ((error = git_iterator_advance_into(&wd, workdir)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto fail;
*wditem_ptr = wd;
return act;
}
@@ -529,8 +553,9 @@ static int checkout_action(
delta->old_file.mode == GIT_FILEMODE_COMMIT)
{
act = checkout_action_with_wd(data, delta, wd);
- if (git_iterator_advance(&wd, workdir) < 0)
- wd = NULL;
+ if ((error = git_iterator_advance(&wd, workdir)) < 0 &&
+ error != GIT_ITEROVER)
+ goto fail;
*wditem_ptr = wd;
return act;
}
@@ -561,6 +586,9 @@ static int checkout_remaining_wd_items(
error = git_iterator_advance(&wd, workdir);
}
+ if (error == GIT_ITEROVER)
+ error = 0;
+
return error;
}
@@ -579,10 +607,11 @@ static int checkout_get_actions(
uint32_t *actions = NULL;
if (data->opts.paths.count > 0 &&
- git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)
+ git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0)
return -1;
- if ((error = git_iterator_current(&wditem, workdir)) < 0)
+ if ((error = git_iterator_current(&wditem, workdir)) < 0 &&
+ error != GIT_ITEROVER)
goto fail;
deltas = &data->diff->deltas;
@@ -630,7 +659,7 @@ static int checkout_get_actions(
goto fail;
}
- git_pathspec_free(&pathspec);
+ git_pathspec__vfree(&pathspec);
git_pool_clear(&pathpool);
return 0;
@@ -641,7 +670,7 @@ fail:
*actions_ptr = NULL;
git__free(actions);
- git_pathspec_free(&pathspec);
+ git_pathspec__vfree(&pathspec);
git_pool_clear(&pathpool);
return error;
@@ -655,33 +684,26 @@ static int buffer_to_file(
int file_open_flags,
mode_t file_mode)
{
- int fd, error;
+ int error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
return error;
- if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
- giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
- return fd;
- }
-
- if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
- giterr_set(GITERR_OS, "Could not write to '%s'", path);
- (void)p_close(fd);
- } else {
- if ((error = p_close(fd)) < 0)
- giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ if ((error = git_futils_writebuffer(
+ buffer, path, file_open_flags, file_mode)) < 0)
+ return error;
- if ((error = p_stat(path, st)) < 0)
- giterr_set(GITERR_OS, "Error while statting '%s'", path);
+ if (st != NULL && (error = p_stat(path, st)) < 0) {
+ giterr_set(GITERR_OS, "Error while statting '%s'", path);
+ return error;
}
- if (!error &&
- (file_mode & 0100) != 0 &&
- (error = p_chmod(path, file_mode)) < 0)
+ if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) {
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+ return error;
+ }
- return error;
+ return 0;
}
static int blob_content_to_file(
@@ -698,8 +720,8 @@ static int blob_content_to_file(
git_vector filters = GIT_VECTOR_INIT;
/* Create a fake git_buf from the blob raw data... */
- filtered.ptr = blob->odb_object->raw.data;
- filtered.size = blob->odb_object->raw.len;
+ filtered.ptr = (void *)git_blob_rawcontent(blob);
+ filtered.size = (size_t)git_blob_rawsize(blob);
/* ... and make sure it doesn't get unexpectedly freed */
dont_free_filtered = true;
@@ -747,17 +769,24 @@ cleanup:
}
static int blob_content_to_link(
- struct stat *st, git_blob *blob, const char *path, int can_symlink)
+ struct stat *st,
+ git_blob *blob,
+ const char *path,
+ mode_t dir_mode,
+ int can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
+ if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
+ return error;
+
if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
return error;
if (can_symlink) {
if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
- giterr_set(GITERR_CHECKOUT, "Could not create symlink %s\n", path);
+ giterr_set(GITERR_OS, "Could not create symlink %s\n", path);
} else {
error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
}
@@ -792,6 +821,31 @@ static int checkout_update_index(
return git_index_add(data->index, &entry);
}
+static int checkout_submodule_update_index(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ struct stat st;
+
+ /* update the index unless prevented */
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
+ return 0;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
+ return -1;
+
+ if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
+ return GIT_ENOTFOUND;
+ }
+
+ st.st_mode = GIT_FILEMODE_COMMIT;
+
+ return checkout_update_index(data, file, &st);
+}
+
static int checkout_submodule(
checkout_data *data,
const git_diff_file *file)
@@ -804,12 +858,21 @@ static int checkout_submodule(
return 0;
if ((error = git_futils_mkdir(
- file->path, git_repository_workdir(data->repo),
+ file->path, data->opts.target_directory,
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
return error;
- if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0)
+ if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0) {
+ /* I've observed repos with submodules in the tree that do not
+ * have a .gitmodules - core Git just makes an empty directory
+ */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return checkout_submodule_update_index(data, file);
+ }
+
return error;
+ }
/* TODO: Support checkout_strategy options. Two circumstances:
* 1 - submodule already checked out, but we need to move the HEAD
@@ -820,26 +883,7 @@ static int checkout_submodule(
* command should probably be able to. Do we need a submodule callback?
*/
- /* update the index unless prevented */
- if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) {
- struct stat st;
-
- git_buf_truncate(&data->path, data->workdir_len);
- if (git_buf_puts(&data->path, file->path) < 0)
- return -1;
-
- if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) {
- giterr_set(
- GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
- return error;
- }
-
- st.st_mode = GIT_FILEMODE_COMMIT;
-
- error = checkout_update_index(data, file, &st);
- }
-
- return error;
+ return checkout_submodule_update_index(data, file);
}
static void report_progress(
@@ -897,7 +941,7 @@ static int checkout_blob(
if (S_ISLNK(file->mode))
error = blob_content_to_link(
- &st, blob, git_buf_cstr(&data->path), data->can_symlink);
+ &st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink);
else
error = blob_content_to_file(
&st, blob, git_buf_cstr(&data->path), file->mode, &data->opts);
@@ -938,6 +982,9 @@ static int checkout_remove_the_old(
uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
+ if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES)
+ flg |= GIT_RMDIR_SKIP_NONEMPTY;
+
git_buf_truncate(&data->path, data->workdir_len);
git_vector_foreach(&data->diff->deltas, i, delta) {
@@ -983,7 +1030,7 @@ static int checkout_deferred_remove(git_repository *repo, const char *path)
{
#if 0
int error = git_futils_rmdir_r(
- path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS);
+ path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS);
if (error == GIT_ENOTFOUND) {
error = 0;
@@ -1107,7 +1154,6 @@ static int checkout_data_init(
git_checkout_opts *proposed)
{
int error = 0;
- git_config *cfg;
git_repository *repo = git_iterator_owner(target);
memset(data, 0, sizeof(*data));
@@ -1117,10 +1163,8 @@ static int checkout_data_init(
return -1;
}
- if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
- return error;
-
- if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ if ((!proposed || !proposed->target_directory) &&
+ (error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
return error;
data->repo = repo;
@@ -1133,9 +1177,19 @@ static int checkout_data_init(
else
memmove(&data->opts, proposed, sizeof(git_checkout_opts));
+ if (!data->opts.target_directory)
+ data->opts.target_directory = git_repository_workdir(repo);
+ else if (!git_path_isdir(data->opts.target_directory) &&
+ (error = git_futils_mkdir(data->opts.target_directory, NULL,
+ GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
+ goto cleanup;
+
/* refresh config and index content unless NO_REFRESH is given */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
- if ((error = git_config_refresh(cfg)) < 0)
+ git_config *cfg;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 ||
+ (error = git_config_refresh(cfg)) < 0)
goto cleanup;
/* if we are checking out the index, don't reload,
@@ -1172,19 +1226,13 @@ static int checkout_data_init(
data->pfx = git_pathspec_prefix(&data->opts.paths);
- error = git_config_get_bool(&data->can_symlink, cfg, "core.symlinks");
- if (error < 0) {
- if (error != GIT_ENOTFOUND)
- goto cleanup;
-
- /* If "core.symlinks" is not found anywhere, default to true. */
- data->can_symlink = true;
- giterr_clear();
- error = 0;
- }
+ if ((error = git_repository__cvar(
+ &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0)
+ goto cleanup;
if (!data->opts.baseline) {
data->opts_free_baseline = true;
+
error = checkout_lookup_head_tree(&data->opts.baseline, repo);
if (error == GIT_EORPHANEDHEAD) {
@@ -1198,7 +1246,8 @@ static int checkout_data_init(
if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
(error = git_pool_init(&data->pool, 1, 0)) < 0 ||
- (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0)
+ (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
+ (error = git_path_to_dir(&data->path)) < 0)
goto cleanup;
data->workdir_len = git_buf_len(&data->path);
@@ -1246,11 +1295,13 @@ 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(
- &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
+ (error = git_iterator_for_workdir_ext(
+ &workdir, data.repo, data.opts.target_directory,
+ 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)
+ &baseline, data.opts.baseline,
+ iterflags, data.pfx, data.pfx)) < 0)
goto cleanup;
/* Should not have case insensitivity mismatch */
@@ -1318,8 +1369,19 @@ int git_checkout_index(
int error;
git_iterator *index_i;
- if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0)
- return error;
+ if (!index && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or index to checkout");
+ return -1;
+ }
+ if (index && repo && git_index_owner(index) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Index to checkout does not match repository");
+ return -1;
+ }
+
+ if (!repo)
+ repo = git_index_owner(index);
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error;
@@ -1343,8 +1405,19 @@ int git_checkout_tree(
git_tree *tree = NULL;
git_iterator *tree_i = NULL;
- if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0)
- return error;
+ if (!treeish && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or tree to checkout");
+ return -1;
+ }
+ if (treeish && repo && git_object_owner(treeish) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Object to checkout does not match repository");
+ return -1;
+ }
+
+ if (!repo)
+ repo = git_object_owner(treeish);
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
giterr_set(
@@ -1369,8 +1442,7 @@ int git_checkout_head(
git_tree *head = NULL;
git_iterator *head_i = NULL;
- if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0)
- return error;
+ assert(repo);
if (!(error = checkout_lookup_head_tree(&head, repo)) &&
!(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL)))
diff --git a/src/clone.c b/src/clone.c
index 0bbccd44b..5c11872cc 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -21,6 +21,7 @@
#include "fileops.h"
#include "refs.h"
#include "path.h"
+#include "repository.h"
static int create_branch(
git_reference **branch,
@@ -132,14 +133,14 @@ static int reference_matches_remote_head(
return 0;
}
- if (git_oid_cmp(&head_info->remote_head_oid, &oid) == 0) {
+ if (git_oid__cmp(&head_info->remote_head_oid, &oid) == 0) {
/* Determine the local reference name from the remote tracking one */
if (git_refspec_transform_l(
- &head_info->branchname,
+ &head_info->branchname,
head_info->refspec,
reference_name) < 0)
return -1;
-
+
if (git_buf_len(&head_info->branchname) > 0) {
if (git_buf_sets(
&head_info->branchname,
@@ -187,6 +188,7 @@ static int get_head_callback(git_remote_head *head, void *payload)
static int update_head_to_remote(git_repository *repo, git_remote *remote)
{
int retcode = -1;
+ git_refspec dummy_spec;
git_remote_head *remote_head;
struct head_info head_info;
git_buf remote_master_name = GIT_BUF_INIT;
@@ -202,7 +204,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
/* Get the remote's HEAD. This is always the first ref in remote->refs. */
remote_head = NULL;
-
+
if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head))
return -1;
@@ -211,9 +213,14 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
git_buf_init(&head_info.branchname, 16);
head_info.repo = repo;
- head_info.refspec = git_remote_fetchspec(remote);
+ head_info.refspec = git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE);
head_info.found = 0;
-
+
+ if (head_info.refspec == NULL) {
+ memset(&dummy_spec, 0, sizeof(git_refspec));
+ head_info.refspec = &dummy_spec;
+ }
+
/* Determine the remote tracking reference name from the local master */
if (git_refspec_transform_r(
&remote_master_name,
@@ -235,9 +242,8 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
}
/* Not master. Check all the other refs. */
- if (git_reference_foreach(
+ if (git_reference_foreach_name(
repo,
- GIT_REF_LISTALL,
reference_matches_remote_head,
&head_info) < 0)
goto cleanup;
@@ -269,7 +275,7 @@ static int update_head_to_branch(
int retcode;
git_buf remote_branch_name = GIT_BUF_INIT;
git_reference* remote_ref = NULL;
-
+
assert(options->checkout_branch);
if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
@@ -317,18 +323,24 @@ static int create_and_configure_origin(
(error = git_remote_set_callbacks(origin, options->remote_callbacks)) < 0)
goto on_error;
- if (options->fetch_spec &&
- (error = git_remote_set_fetchspec(origin, options->fetch_spec)) < 0)
- goto on_error;
+ if (options->fetch_spec) {
+ git_remote_clear_refspecs(origin);
+ if ((error = git_remote_add_fetch(origin, options->fetch_spec)) < 0)
+ goto on_error;
+ }
if (options->push_spec &&
- (error = git_remote_set_pushspec(origin, options->push_spec)) < 0)
+ (error = git_remote_add_push(origin, options->push_spec)) < 0)
goto on_error;
if (options->pushurl &&
(error = git_remote_set_pushurl(origin, options->pushurl)) < 0)
goto on_error;
+ if (options->transport_flags == GIT_TRANSPORTFLAGS_NO_CHECK_CERT) {
+ git_remote_check_cert(origin, 0);
+ }
+
if ((error = git_remote_save(origin)) < 0)
goto on_error;
@@ -347,50 +359,48 @@ static int setup_remotes_and_fetch(
const git_clone_options *options)
{
int retcode = GIT_ERROR;
- git_remote *origin;
+ git_remote *origin = NULL;
/* Construct an origin remote */
- if (!create_and_configure_origin(&origin, repo, url, options)) {
- git_remote_set_update_fetchhead(origin, 0);
-
- /* Connect and download everything */
- if (!git_remote_connect(origin, GIT_DIRECTION_FETCH)) {
- if (!(retcode = git_remote_download(origin, options->fetch_progress_cb,
- options->fetch_progress_payload))) {
- /* Create "origin/foo" branches for all remote branches */
- if (!git_remote_update_tips(origin)) {
- /* Point HEAD to the requested branch */
- if (options->checkout_branch) {
- if (!update_head_to_branch(repo, options))
- retcode = 0;
- }
- /* Point HEAD to the same ref as the remote's head */
- else if (!update_head_to_remote(repo, origin)) {
- retcode = 0;
- }
- }
- }
- git_remote_disconnect(origin);
- }
- git_remote_free(origin);
- }
+ if ((retcode = create_and_configure_origin(&origin, repo, url, options)) < 0)
+ goto on_error;
- return retcode;
-}
+ git_remote_set_update_fetchhead(origin, 0);
+
+ /* If the download_tags value has not been specified, then make sure to
+ * download tags as well. It is set here because we want to download tags
+ * on the initial clone, but do not want to persist the value in the
+ * configuration file.
+ */
+ if (origin->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_AUTO &&
+ ((retcode = git_remote_add_fetch(origin, "refs/tags/*:refs/tags/*")) < 0))
+ goto on_error;
+ /* Connect and download everything */
+ if ((retcode = git_remote_connect(origin, GIT_DIRECTION_FETCH)) < 0)
+ goto on_error;
-static bool path_is_okay(const char *path)
-{
- /* The path must either not exist, or be an empty directory */
- if (!git_path_exists(path)) return true;
- if (!git_path_is_empty_dir(path)) {
- giterr_set(GITERR_INVALID,
- "'%s' exists and is not an empty directory", path);
- return false;
- }
- return true;
+ if ((retcode = git_remote_download(origin, options->fetch_progress_cb,
+ options->fetch_progress_payload)) < 0)
+ goto on_error;
+
+ /* Create "origin/foo" branches for all remote branches */
+ if ((retcode = git_remote_update_tips(origin)) < 0)
+ goto on_error;
+
+ /* Point HEAD to the requested branch */
+ if (options->checkout_branch)
+ retcode = update_head_to_branch(repo, options);
+ /* Point HEAD to the same ref as the remote's head */
+ else
+ retcode = update_head_to_remote(repo, origin);
+
+on_error:
+ git_remote_free(origin);
+ return retcode;
}
+
static bool should_checkout(
git_repository *repo,
bool is_bare,
@@ -417,7 +427,6 @@ static void normalize_options(git_clone_options *dst, const git_clone_options *s
/* Provide defaults for null pointers */
if (!dst->remote_name) dst->remote_name = "origin";
- if (!dst->remote_autotag) dst->remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
}
int git_clone(
@@ -436,7 +445,10 @@ int git_clone(
normalize_options(&normOptions, options);
GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
- if (!path_is_okay(local_path)) {
+ /* Only clone to a new directory or an empty directory */
+ if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
+ giterr_set(GITERR_INVALID,
+ "'%s' exists and is not an empty directory", local_path);
return GIT_ERROR;
}
diff --git a/src/commit.c b/src/commit.c
index c7b83ed43..15a195fe5 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -9,6 +9,7 @@
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/signature.h"
+#include "git2/sys/commit.h"
#include "common.h"
#include "odb.h"
@@ -18,42 +19,33 @@
#include <stdarg.h>
-static void clear_parents(git_commit *commit)
+void git_commit__free(void *_commit)
{
- size_t i;
+ git_commit *commit = _commit;
- for (i = 0; i < commit->parent_ids.length; ++i) {
- git_oid *parent = git_vector_get(&commit->parent_ids, i);
- git__free(parent);
- }
-
- git_vector_clear(&commit->parent_ids);
-}
-
-void git_commit__free(git_commit *commit)
-{
- clear_parents(commit);
- git_vector_free(&commit->parent_ids);
+ git_array_clear(commit->parent_ids);
git_signature_free(commit->author);
git_signature_free(commit->committer);
+ git__free(commit->raw_header);
git__free(commit->message);
git__free(commit->message_encoding);
+
git__free(commit);
}
int git_commit_create_v(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message_encoding,
- const char *message,
- const git_tree *tree,
- int parent_count,
- ...)
+ git_oid *oid,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ int parent_count,
+ ...)
{
va_list ap;
int i, res;
@@ -76,30 +68,28 @@ int git_commit_create_v(
return res;
}
-int git_commit_create(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message_encoding,
- const char *message,
- const git_tree *tree,
- int parent_count,
- const git_commit *parents[])
+int git_commit_create_from_oids(
+ git_oid *oid,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ int parent_count,
+ const git_oid *parents[])
{
git_buf commit = GIT_BUF_INIT;
int i;
git_odb *odb;
- assert(git_object_owner((const git_object *)tree) == repo);
+ assert(oid && repo && tree && parent_count >= 0);
- git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree));
+ git_oid__writebuf(&commit, "tree ", tree);
- for (i = 0; i < parent_count; ++i) {
- assert(git_object_owner((const git_object *)parents[i]) == repo);
- git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i]));
- }
+ for (i = 0; i < parent_count; ++i)
+ git_oid__writebuf(&commit, "parent ", parents[i]);
git_signature__writebuf(&commit, "author ", author);
git_signature__writebuf(&commit, "committer ", committer);
@@ -131,14 +121,74 @@ on_error:
return -1;
}
-int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
+int git_commit_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ int parent_count,
+ const git_commit *parents[])
{
- const char *buffer = data;
- const char *buffer_end = (const char *)data + len;
+ int retval, i;
+ const git_oid **parent_oids;
+
+ assert(parent_count >= 0);
+ assert(git_object_owner((const git_object *)tree) == repo);
+
+ parent_oids = git__malloc(parent_count * sizeof(git_oid *));
+ GITERR_CHECK_ALLOC(parent_oids);
+
+ for (i = 0; i < parent_count; ++i) {
+ assert(git_object_owner((const git_object *)parents[i]) == repo);
+ parent_oids[i] = git_object_id((const git_object *)parents[i]);
+ }
+
+ retval = git_commit_create_from_oids(
+ oid, repo, update_ref, author, committer,
+ message_encoding, message,
+ git_object_id((const git_object *)tree), parent_count, parent_oids);
+
+ git__free((void *)parent_oids);
+
+ return retval;
+}
+
+int git_commit__parse(void *_commit, git_odb_object *odb_obj)
+{
+ git_commit *commit = _commit;
+ const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
+ const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
git_oid parent_id;
+ uint32_t parent_count = 0;
+ size_t header_len;
+
+ /* find end-of-header (counting parents as we go) */
+ for (buffer = buffer_start; buffer < buffer_end; ++buffer) {
+ if (!strncmp("\n\n", buffer, 2)) {
+ ++buffer;
+ break;
+ }
+ if (!strncmp("\nparent ", buffer, strlen("\nparent ")))
+ ++parent_count;
+ }
- if (git_vector_init(&commit->parent_ids, 4, NULL) < 0)
- return -1;
+ header_len = buffer - buffer_start;
+ commit->raw_header = git__strndup(buffer_start, header_len);
+ GITERR_CHECK_ALLOC(commit->raw_header);
+
+ /* point "buffer" to header data */
+ buffer = commit->raw_header;
+ buffer_end = commit->raw_header + header_len;
+
+ if (parent_count < 1)
+ parent_count = 1;
+
+ git_array_init_to_size(commit->parent_ids, parent_count);
+ GITERR_CHECK_ARRAY(commit->parent_ids);
if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
goto bad_buffer;
@@ -148,13 +198,10 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
*/
while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
- git_oid *new_id = git__malloc(sizeof(git_oid));
+ git_oid *new_id = git_array_alloc(commit->parent_ids);
GITERR_CHECK_ALLOC(new_id);
git_oid_cpy(new_id, &parent_id);
-
- if (git_vector_insert(&commit->parent_ids, new_id) < 0)
- return -1;
}
commit->author = git__malloc(sizeof(git_signature));
@@ -170,8 +217,8 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
return -1;
- /* Parse add'l header entries until blank line found */
- while (buffer < buffer_end && *buffer != '\n') {
+ /* Parse add'l header entries */
+ while (buffer < buffer_end) {
const char *eoln = buffer;
while (eoln < buffer_end && *eoln != '\n')
++eoln;
@@ -185,15 +232,18 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
if (eoln < buffer_end && *eoln == '\n')
++eoln;
-
buffer = eoln;
}
- /* buffer is now at the end of the header, double-check and move forward into the message */
- if (buffer < buffer_end && *buffer == '\n')
- buffer++;
+ /* point "buffer" to data after header */
+ buffer = git_odb_object_data(odb_obj);
+ buffer_end = buffer + git_odb_object_size(odb_obj);
- /* parse commit message */
+ buffer += header_len;
+ if (*buffer == '\n')
+ ++buffer;
+
+ /* extract commit message */
if (buffer <= buffer_end) {
commit->message = git__strndup(buffer, buffer_end - buffer);
GITERR_CHECK_ALLOC(commit->message);
@@ -206,12 +256,6 @@ bad_buffer:
return -1;
}
-int git_commit__parse(git_commit *commit, git_odb_object *obj)
-{
- assert(commit);
- return git_commit__parse_buffer(commit, obj->raw.data, obj->raw.len);
-}
-
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
_rvalue git_commit_##_name(const git_commit *commit) \
{\
@@ -223,9 +267,10 @@ GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
GIT_COMMIT_GETTER(const char *, message, commit->message)
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
+GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
-GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length)
+GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
@@ -234,14 +279,16 @@ int git_commit_tree(git_tree **tree_out, const git_commit *commit)
return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
}
-const git_oid *git_commit_parent_id(git_commit *commit, unsigned int n)
+const git_oid *git_commit_parent_id(
+ const git_commit *commit, unsigned int n)
{
assert(commit);
- return git_vector_get(&commit->parent_ids, n);
+ return git_array_get(commit->parent_ids, n);
}
-int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
+int git_commit_parent(
+ git_commit **parent, const git_commit *commit, unsigned int n)
{
const git_oid *parent_id;
assert(commit);
@@ -260,7 +307,7 @@ int git_commit_nth_gen_ancestor(
const git_commit *commit,
unsigned int n)
{
- git_commit *current, *parent;
+ git_commit *current, *parent = NULL;
int error;
assert(ancestor && commit);
diff --git a/src/commit.h b/src/commit.h
index 1ab164c0b..22fc898a1 100644
--- a/src/commit.h
+++ b/src/commit.h
@@ -10,14 +10,14 @@
#include "git2/commit.h"
#include "tree.h"
#include "repository.h"
-#include "vector.h"
+#include "array.h"
#include <time.h>
struct git_commit {
git_object object;
- git_vector parent_ids;
+ git_array_t(git_oid) parent_ids;
git_oid tree_id;
git_signature *author;
@@ -25,10 +25,10 @@ struct git_commit {
char *message_encoding;
char *message;
+ char *raw_header;
};
-void git_commit__free(git_commit *c);
-int git_commit__parse(git_commit *commit, git_odb_object *obj);
+void git_commit__free(void *commit);
+int git_commit__parse(void *commit, git_odb_object *obj);
-int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len);
#endif
diff --git a/src/commit_list.c b/src/commit_list.c
index 603dd754a..64416e54d 100644
--- a/src/commit_list.c
+++ b/src/commit_list.c
@@ -36,7 +36,7 @@ git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_
git_commit_list *p;
while ((p = *pp) != NULL) {
- if (git_commit_list_time_cmp(p->item, item) < 0)
+ if (git_commit_list_time_cmp(p->item, item) > 0)
break;
pp = &p->next;
@@ -100,12 +100,15 @@ git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
return item;
}
-static int commit_quick_parse(git_revwalk *walk, git_commit_list_node *commit, git_rawobj *raw)
+static int commit_quick_parse(
+ git_revwalk *walk,
+ git_commit_list_node *commit,
+ const uint8_t *buffer,
+ size_t buffer_len)
{
const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;
- unsigned char *buffer = raw->data;
- unsigned char *buffer_end = buffer + raw->len;
- unsigned char *parents_start, *committer_start;
+ const uint8_t *buffer_end = buffer + buffer_len;
+ const uint8_t *parents_start, *committer_start;
int i, parents = 0;
int commit_time;
@@ -124,7 +127,7 @@ static int commit_quick_parse(git_revwalk *walk, git_commit_list_node *commit, g
for (i = 0; i < parents; ++i) {
git_oid oid;
- if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0)
+ if (git_oid_fromstr(&oid, (const char *)buffer + strlen("parent ")) < 0)
return -1;
commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);
@@ -182,11 +185,14 @@ int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
return error;
- if (obj->raw.type != GIT_OBJ_COMMIT) {
+ if (obj->cached.type != GIT_OBJ_COMMIT) {
giterr_set(GITERR_INVALID, "Object is no commit object");
error = -1;
} else
- error = commit_quick_parse(walk, commit, &obj->raw);
+ error = commit_quick_parse(
+ walk, commit,
+ (const uint8_t *)git_odb_object_data(obj),
+ git_odb_object_size(obj));
git_odb_object_free(obj);
return error;
diff --git a/src/config.c b/src/config.c
index 5379b0ec5..2a058549f 100644
--- a/src/config.c
+++ b/src/config.c
@@ -9,6 +9,7 @@
#include "fileops.h"
#include "config.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "vector.h"
#include "buf_text.h"
#include "config_file.h"
@@ -22,7 +23,7 @@ typedef struct {
git_refcount rc;
git_config_backend *file;
- unsigned int level;
+ git_config_level_t level;
} file_internal;
static void file_internal_free(file_internal *internal)
@@ -39,12 +40,14 @@ static void config_free(git_config *cfg)
size_t i;
file_internal *internal;
- for(i = 0; i < cfg->files.length; ++i){
+ for (i = 0; i < cfg->files.length; ++i) {
internal = git_vector_get(&cfg->files, i);
GIT_REFCOUNT_DEC(internal, file_internal_free);
}
git_vector_free(&cfg->files);
+
+ git__memzero(cfg, sizeof(*cfg));
git__free(cfg);
}
@@ -86,17 +89,19 @@ int git_config_new(git_config **out)
int git_config_add_file_ondisk(
git_config *cfg,
const char *path,
- unsigned int level,
+ git_config_level_t level,
int force)
{
git_config_backend *file = NULL;
+ struct stat st;
int res;
assert(cfg && path);
- if (!git_path_isfile(path)) {
- giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path);
- return GIT_ENOTFOUND;
+ res = p_stat(path, &st);
+ if (res < 0 && errno != ENOENT) {
+ giterr_set(GITERR_CONFIG, "Error stat'ing config file '%s'", path);
+ return -1;
}
if (git_config_file__ondisk(&file, path) < 0)
@@ -135,11 +140,11 @@ int git_config_open_ondisk(git_config **out, const char *path)
static int find_internal_file_by_level(
file_internal **internal_out,
const git_config *cfg,
- int level)
+ git_config_level_t level)
{
int pos = -1;
file_internal *internal;
- unsigned int i;
+ size_t i;
/* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
* which has the highest level. As config files are stored in a vector
@@ -150,14 +155,14 @@ static int find_internal_file_by_level(
pos = 0;
} else {
git_vector_foreach(&cfg->files, i, internal) {
- if (internal->level == (unsigned int)level)
- pos = i;
+ if (internal->level == level)
+ pos = (int)i;
}
}
if (pos == -1) {
giterr_set(GITERR_CONFIG,
- "No config file exists for the given level '%i'", level);
+ "No config file exists for the given level '%i'", (int)level);
return GIT_ENOTFOUND;
}
@@ -172,21 +177,21 @@ static int duplicate_level(void **old_raw, void *new_raw)
GIT_UNUSED(new_raw);
- giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (*old)->level);
+ giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (int)(*old)->level);
return GIT_EEXISTS;
}
static void try_remove_existing_file_internal(
git_config *cfg,
- unsigned int level)
+ git_config_level_t level)
{
int pos = -1;
file_internal *internal;
- unsigned int i;
+ size_t i;
git_vector_foreach(&cfg->files, i, internal) {
if (internal->level == level)
- pos = i;
+ pos = (int)i;
}
if (pos == -1)
@@ -203,7 +208,7 @@ static void try_remove_existing_file_internal(
static int git_config__add_internal(
git_config *cfg,
file_internal *internal,
- unsigned int level,
+ git_config_level_t level,
int force)
{
int result;
@@ -224,10 +229,18 @@ static int git_config__add_internal(
return 0;
}
+int git_config_open_global(git_config **cfg_out, git_config *cfg)
+{
+ if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG))
+ return 0;
+
+ return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL);
+}
+
int git_config_open_level(
- git_config **cfg_out,
- const git_config *cfg_parent,
- unsigned int level)
+ git_config **cfg_out,
+ const git_config *cfg_parent,
+ git_config_level_t level)
{
git_config *cfg;
file_internal *internal;
@@ -252,7 +265,7 @@ int git_config_open_level(
int git_config_add_backend(
git_config *cfg,
git_config_backend *file,
- unsigned int level,
+ git_config_level_t level,
int force)
{
file_internal *internal;
@@ -292,6 +305,9 @@ int git_config_refresh(git_config *cfg)
error = file->refresh(file);
}
+ if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
+ git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+
return error;
}
@@ -325,21 +341,30 @@ int git_config_foreach_match(
return ret;
}
+/**************
+ * Setters
+ **************/
+
+static int config_error_nofiles(const char *name)
+{
+ giterr_set(GITERR_CONFIG,
+ "Cannot set value for '%s' when no config files exist", name);
+ return GIT_ENOTFOUND;
+}
+
int git_config_delete_entry(git_config *cfg, const char *name)
{
git_config_backend *file;
file_internal *internal;
internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
file = internal->file;
return file->del(file, name);
}
-/**************
- * Setters
- **************/
-
int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
{
char str_value[32]; /* All numbers should fit in here */
@@ -359,6 +384,7 @@ int git_config_set_bool(git_config *cfg, const char *name, int value)
int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
+ int error;
git_config_backend *file;
file_internal *internal;
@@ -368,9 +394,16 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
}
internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
file = internal->file;
- return file->set(file, name, value);
+ error = file->set(file, name, value);
+
+ if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
+ git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+
+ return error;
}
/***********
@@ -426,19 +459,28 @@ static int get_string_at_file(const char **out, const git_config_backend *file,
return res;
}
+static int config_error_notfound(const char *name)
+{
+ giterr_set(GITERR_CONFIG, "Config value '%s' was not found", name);
+ return GIT_ENOTFOUND;
+}
+
static int get_string(const char **out, const git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
+ int res;
git_vector_foreach(&cfg->files, i, internal) {
- int res = get_string_at_file(out, internal->file, name);
+ if (!internal || !internal->file)
+ continue;
+ res = get_string_at_file(out, internal->file, name);
if (res != GIT_ENOTFOUND)
return res;
}
- return GIT_ENOTFOUND;
+ return config_error_notfound(name);
}
int git_config_get_bool(int *out, const git_config *cfg, const char *name)
@@ -468,25 +510,31 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co
{
file_internal *internal;
unsigned int i;
+ git_config_backend *file;
+ int ret;
*out = NULL;
git_vector_foreach(&cfg->files, i, internal) {
- git_config_backend *file = internal->file;
- int ret = file->get(file, name, out);
+ if (!internal || !internal->file)
+ continue;
+ file = internal->file;
+
+ ret = file->get(file, name, out);
if (ret != GIT_ENOTFOUND)
return ret;
}
- return GIT_ENOTFOUND;
+ return config_error_notfound(name);
}
-int git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp,
- git_config_foreach_cb cb, void *payload)
+int git_config_get_multivar(
+ const git_config *cfg, const char *name, const char *regexp,
+ git_config_foreach_cb cb, void *payload)
{
file_internal *internal;
git_config_backend *file;
- int ret = GIT_ENOTFOUND;
+ int ret = GIT_ENOTFOUND, err;
size_t i;
/*
@@ -495,13 +543,17 @@ int git_config_get_multivar(const git_config *cfg, const char *name, const char
*/
for (i = cfg->files.length; i > 0; --i) {
internal = git_vector_get(&cfg->files, i - 1);
+ if (!internal || !internal->file)
+ continue;
file = internal->file;
- ret = file->get_multivar(file, name, regexp, cb, payload);
- if (ret < 0 && ret != GIT_ENOTFOUND)
- return ret;
+
+ if (!(err = file->get_multivar(file, name, regexp, cb, payload)))
+ ret = 0;
+ else if (err != GIT_ENOTFOUND)
+ return err;
}
- return 0;
+ return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0;
}
int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
@@ -510,6 +562,8 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex
file_internal *internal;
internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
file = internal->file;
return file->set_multivar(file, name, regexp, value);
@@ -570,17 +624,46 @@ int git_config_find_system(char *system_config_path, size_t length)
system_config_path, length, git_config_find_system_r);
}
+int git_config__global_location(git_buf *buf)
+{
+ const git_buf *paths;
+ const char *sep, *start;
+ size_t len;
+
+ if (git_futils_dirs_get(&paths, GIT_FUTILS_DIR_GLOBAL) < 0)
+ return -1;
+
+ /* no paths, so give up */
+ if (git_buf_len(paths) == 0)
+ return -1;
+
+ start = git_buf_cstr(paths);
+ sep = strchr(start, GIT_PATH_LIST_SEPARATOR);
+
+ if (sep)
+ len = sep - start;
+ else
+ len = paths->size;
+
+ if (git_buf_set(buf, start, len) < 0)
+ return -1;
+
+ return git_buf_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL);
+}
+
int git_config_open_default(git_config **out)
{
int error;
git_config *cfg = NULL;
git_buf buf = GIT_BUF_INIT;
- error = git_config_new(&cfg);
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
- if (!error && !git_config_find_global_r(&buf))
+ if (!git_config_find_global_r(&buf) || !git_config__global_location(&buf)) {
error = git_config_add_file_ondisk(cfg, buf.ptr,
GIT_CONFIG_LEVEL_GLOBAL, 0);
+ }
if (!error && !git_config_find_xdg_r(&buf))
error = git_config_add_file_ondisk(cfg, buf.ptr,
@@ -592,7 +675,7 @@ int git_config_open_default(git_config **out)
git_buf_free(&buf);
- if (error && cfg) {
+ if (error) {
git_config_free(cfg);
cfg = NULL;
}
@@ -605,6 +688,7 @@ int git_config_open_default(git_config **out)
/***********
* Parsers
***********/
+
int git_config_lookup_map_value(
int *out,
const git_cvar_map *maps,
diff --git a/src/config.h b/src/config.h
index c43e47e82..c5c11ae14 100644
--- a/src/config.h
+++ b/src/config.h
@@ -28,6 +28,9 @@ extern int git_config_find_global_r(git_buf *global_config_path);
extern int git_config_find_xdg_r(git_buf *system_config_path);
extern int git_config_find_system_r(git_buf *system_config_path);
+
+extern int git_config__global_location(git_buf *buf);
+
extern int git_config_rename_section(
git_repository *repo,
const char *old_section_name, /* eg "branch.dummy" */
diff --git a/src/config_cache.c b/src/config_cache.c
index 2f36df7d1..84de3a5ed 100644
--- a/src/config_cache.c
+++ b/src/config_cache.c
@@ -26,7 +26,7 @@ struct map_data {
* files that have the text property set. Alternatives are lf, crlf
* and native, which uses the platform's native line ending. The default
* value is native. See gitattributes(5) for more information on
- * end-of-line conversion.
+ * end-of-line conversion.
*/
static git_cvar_map _cvar_map_eol[] = {
{GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET},
@@ -37,7 +37,7 @@ static git_cvar_map _cvar_map_eol[] = {
/*
* core.autocrlf
- * Setting this variable to "true" is almost the same as setting
+ * Setting this variable to "true" is almost the same as setting
* the text attribute to "auto" on all files except that text files are
* not guaranteed to be normalized: files that contain CRLF in the
* repository will not be touched. Use this setting if you want to have
@@ -51,9 +51,22 @@ static git_cvar_map _cvar_map_autocrlf[] = {
{GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}
};
+/*
+ * Generic map for integer values
+ */
+static git_cvar_map _cvar_map_int[] = {
+ {GIT_CVAR_INT32, NULL, 0},
+};
+
static struct map_data _cvar_maps[] = {
{"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT},
- {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT}
+ {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT},
+ {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT },
+ {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT },
+ {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT },
+ {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
+ {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
+ {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT },
};
int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
@@ -69,12 +82,16 @@ int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
if (error < 0)
return error;
- error = git_config_get_mapped(out,
- config, data->cvar_name, data->maps, data->map_count);
+ if (data->maps)
+ error = git_config_get_mapped(
+ out, config, data->cvar_name, data->maps, data->map_count);
+ else
+ error = git_config_get_bool(out, config, data->cvar_name);
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
*out = data->default_value;
-
+ }
else if (error < 0)
return error;
diff --git a/src/config_file.c b/src/config_file.c
index 8b51ab21b..1d7b4fb38 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -12,6 +12,7 @@
#include "buffer.h"
#include "buf_text.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "git2/types.h"
#include "strmap.h"
@@ -80,10 +81,10 @@ typedef struct {
time_t file_mtime;
size_t file_size;
- unsigned int level;
+ git_config_level_t level;
} diskfile_backend;
-static int config_parse(diskfile_backend *cfg_file, unsigned int level);
+static int config_parse(diskfile_backend *cfg_file, git_config_level_t level);
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
static char *escape_value(const char *ptr);
@@ -180,7 +181,7 @@ static void free_vars(git_strmap *values)
git_strmap_free(values);
}
-static int config_open(git_config_backend *cfg, unsigned int level)
+static int config_open(git_config_backend *cfg, git_config_level_t level)
{
int res;
diskfile_backend *b = (diskfile_backend *)cfg;
@@ -295,7 +296,7 @@ cleanup:
static int config_set(git_config_backend *cfg, const char *name, const char *value)
{
- cvar_t *var = NULL, *old_var;
+ cvar_t *var = NULL, *old_var = NULL;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key, *esc_value = NULL;
khiter_t pos;
@@ -481,8 +482,10 @@ static int config_set_multivar(
pos = git_strmap_lookup_index(b->values, key);
if (!git_strmap_valid_index(b->values, pos)) {
+ /* If we don't have it, behave like a normal set */
+ result = config_set(cfg, name, value);
git__free(key);
- return GIT_ENOTFOUND;
+ return result;
}
var = git_strmap_value_at(b->values, pos);
@@ -789,6 +792,11 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
}
switch (c) {
+ case 0:
+ set_parse_error(cfg, 0, "Unexpected end-of-line in section header");
+ git_buf_free(&buf);
+ return -1;
+
case '"':
++quote_marks;
continue;
@@ -798,6 +806,12 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con
switch (c) {
case '"':
+ if (&line[rpos-1] == last_quote) {
+ set_parse_error(cfg, 0, "Missing closing quotation mark in section header");
+ git_buf_free(&buf);
+ return -1;
+ }
+
case '\\':
break;
@@ -962,7 +976,7 @@ static int strip_comments(char *line, int in_quotes)
return quote_count;
}
-static int config_parse(diskfile_backend *cfg_file, unsigned int level)
+static int config_parse(diskfile_backend *cfg_file, git_config_level_t level)
{
int c;
char *current_section = NULL;
@@ -1290,6 +1304,9 @@ static char *escape_value(const char *ptr)
assert(ptr);
len = strlen(ptr);
+ if (!len)
+ return git__calloc(1, sizeof(char));
+
git_buf_grow(&buf, len);
while (*ptr != '\0') {
@@ -1392,7 +1409,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i
* standard, this character **has** to be last one in the buf, with
* no whitespace after it */
assert(is_multiline_var(value->ptr));
- git_buf_truncate(value, git_buf_len(value) - 1);
+ git_buf_shorten(value, 1);
proc_line = fixup_line(line, in_quotes);
if (proc_line == NULL) {
diff --git a/src/crlf.c b/src/crlf.c
index 81268da83..65039f9cc 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -5,14 +5,16 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
+#include "git2/attr.h"
+#include "git2/blob.h"
+#include "git2/index.h"
+
#include "common.h"
#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;
diff --git a/src/date.c b/src/date.c
index ce1721a0b..48841e4f9 100644
--- a/src/date.c
+++ b/src/date.c
@@ -823,8 +823,8 @@ static void pending_number(struct tm *tm, int *num)
}
static git_time_t approxidate_str(const char *date,
- const struct timeval *tv,
- int *error_ret)
+ const struct timeval *tv,
+ int *error_ret)
{
int number = 0;
int touched = 0;
@@ -866,7 +866,7 @@ int git__date_parse(git_time_t *out, const char *date)
int offset, error_ret=0;
if (!parse_date_basic(date, &timestamp, &offset)) {
- *out = timestamp;
+ *out = timestamp;
return 0;
}
diff --git a/src/delta.c b/src/delta.c
index 3252dbf14..b3435ba87 100644
--- a/src/delta.c
+++ b/src/delta.c
@@ -168,7 +168,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
memset(hash, 0, hsize * sizeof(*hash));
/* allocate an array to count hash entries */
- hash_count = calloc(hsize, sizeof(*hash_count));
+ hash_count = git__calloc(hsize, sizeof(*hash_count));
if (!hash_count) {
git__free(index);
return NULL;
diff --git a/src/diff.c b/src/diff.c
index 37c89f3f1..e875d09b3 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -11,9 +11,14 @@
#include "attr_file.h"
#include "filter.h"
#include "pathspec.h"
+#include "index.h"
+#include "odb.h"
+#include "submodule.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)
+#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
+ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
@@ -73,15 +78,11 @@ static int diff_delta__from_one(
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(
+ if (!git_pathspec__match(
&diff->pathspec, entry->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
- &matched_pathspec))
+ &matched_pathspec, NULL))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
@@ -130,16 +131,12 @@ static int diff_delta__from_two(
{
git_diff_delta *delta;
int notify_res;
+ const char *canonical_path = old_entry->path;
if (status == GIT_DELTA_UNMODIFIED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 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;
@@ -149,7 +146,7 @@ static int diff_delta__from_two(
new_mode = temp_mode;
}
- delta = diff_delta__alloc(diff, status, old_entry->path);
+ delta = diff_delta__alloc(diff, status, canonical_path);
GITERR_CHECK_ALLOC(delta);
git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
@@ -194,21 +191,21 @@ static git_diff_delta *diff_delta__last_for_item(
switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
case GIT_DELTA_DELETED:
- if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)
+ if (git_oid__cmp(&delta->old_file.oid, &item->oid) == 0)
return delta;
break;
case GIT_DELTA_ADDED:
- if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+ if (git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
return delta;
break;
case GIT_DELTA_UNTRACKED:
if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
- git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+ git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
return delta;
break;
case GIT_DELTA_MODIFIED:
- if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
- git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+ if (git_oid__cmp(&delta->old_file.oid, &item->oid) == 0 ||
+ git_oid__cmp(&delta->new_file.oid, &item->oid) == 0)
return delta;
break;
default:
@@ -229,10 +226,35 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
return git_pool_strndup(pool, prefix, len + 1);
}
+GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
+{
+ const char *str = delta->old_file.path;
+
+ if (!str ||
+ delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_COPIED)
+ str = delta->new_file.path;
+
+ return str;
+}
+
+const char *git_diff_delta__path(const git_diff_delta *delta)
+{
+ return diff_delta__path(delta);
+}
+
int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
- int val = strcmp(da->old_file.path, db->old_file.path);
+ int val = strcmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff_delta__casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
@@ -267,67 +289,176 @@ static int config_bool(git_config *cfg, const char *name, int defvalue)
return val;
}
-static git_diff_list *git_diff_list_alloc(
- git_repository *repo, const git_diff_options *opts)
+static int config_int(git_config *cfg, const char *name, int defvalue)
{
- git_config *cfg;
+ int val = defvalue;
+
+ if (git_config_get_int32(&val, cfg, name) < 0)
+ giterr_clear();
+
+ return val;
+}
+
+static const char *diff_mnemonic_prefix(
+ git_iterator_type_t type, bool left_side)
+{
+ const char *pfx = "";
+
+ switch (type) {
+ case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
+ case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
+ case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
+ default: break;
+ }
+
+ /* note: without a deeper look at pathspecs, there is no easy way
+ * to get the (o)bject / (w)ork tree mnemonics working...
+ */
+
+ return pfx;
+}
+
+static git_diff_list *diff_list_alloc(
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter)
+{
+ git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
- if (diff == NULL)
+ if (!diff)
return NULL;
+ assert(repo && old_iter && new_iter);
+
GIT_REFCOUNT_INC(diff);
diff->repo = repo;
+ diff->old_src = old_iter->type;
+ diff->new_src = new_iter->type;
+ memcpy(&diff->opts, &dflt, sizeof(diff->opts));
if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
- git_pool_init(&diff->pool, 1, 0) < 0)
- goto fail;
+ git_pool_init(&diff->pool, 1, 0) < 0) {
+ git_diff_list_free(diff);
+ return NULL;
+ }
+
+ /* Use case-insensitive compare if either iterator has
+ * the ignore_case bit set */
+ if (!git_iterator_ignore_case(old_iter) &&
+ !git_iterator_ignore_case(new_iter)) {
+ diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
+
+ diff->strcomp = git__strcmp;
+ diff->strncomp = git__strncmp;
+ diff->pfxcomp = git__prefixcmp;
+ diff->entrycomp = git_index_entry__cmp;
+ } else {
+ diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+
+ diff->strcomp = git__strcasecmp;
+ diff->strncomp = git__strncasecmp;
+ diff->pfxcomp = git__prefixcmp_icase;
+ diff->entrycomp = git_index_entry__cmp_icase;
+
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
+ }
+
+ return diff;
+}
+
+static int diff_list_apply_options(
+ git_diff_list *diff,
+ const git_diff_options *opts)
+{
+ git_config *cfg;
+ git_repository *repo = diff->repo;
+ git_pool *pool = &diff->pool;
+ int val;
+
+ if (opts) {
+ /* copy user options (except case sensitivity info from iterators) */
+ bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE);
+ memcpy(&diff->opts, opts, sizeof(diff->opts));
+ DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase);
+
+ /* initialize pathspec from options */
+ if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
+ return -1;
+ }
+
+ /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
+ diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
+
+ /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT))
+ diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
/* load config values that affect diff behavior */
if (git_repository_config__weakptr(&cfg, repo) < 0)
- goto fail;
- if (config_bool(cfg, "core.symlinks", 1))
+ return -1;
+
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
- if (config_bool(cfg, "core.ignorestat", 0))
+
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
- if (config_bool(cfg, "core.filemode", 1))
+
+ if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
+ !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
- if (config_bool(cfg, "core.trustctime", 1))
+
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_TRUSTCTIME) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
+
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
- /* TODO: there are certain config settings where even if we were
- * not given an options structure, we need the diff list to have one
- * so that we can store the altered default values.
- *
- * - diff.ignoreSubmodules
- * - diff.mnemonicprefix
- * - diff.noprefix
- */
+ /* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;
+
+ /* If not given explicit `opts`, check `diff.xyz` configs */
+ if (!opts) {
+ diff->opts.context_lines = config_int(cfg, "diff.context", 3);
- if (opts == NULL) {
- /* Make sure we default to 3 lines */
- diff->opts.context_lines = 3;
- return diff;
+ /* add other defaults here */
}
- memcpy(&diff->opts, opts, sizeof(git_diff_options));
+ /* if ignore_submodules not explicitly set, check diff config */
+ if (diff->opts.ignore_submodules <= 0) {
+ const char *str;
- if(opts->flags & GIT_DIFF_IGNORE_FILEMODE)
- diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS;
+ if (git_config_get_string(&str , cfg, "diff.ignoreSubmodules") < 0)
+ giterr_clear();
+ else if (str != NULL &&
+ git_submodule_parse_ignore(&diff->opts.ignore_submodules, str) < 0)
+ giterr_clear();
+ }
- /* pathspec init will do nothing for empty pathspec */
- if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0)
- goto fail;
+ /* if either prefix is not set, figure out appropriate value */
+ if (!diff->opts.old_prefix || !diff->opts.new_prefix) {
+ const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
+ const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
- /* TODO: handle config diff.mnemonicprefix, diff.noprefix */
+ if (config_bool(cfg, "diff.noprefix", 0)) {
+ use_old = use_new = "";
+ } else if (config_bool(cfg, "diff.mnemonicprefix", 0)) {
+ use_old = diff_mnemonic_prefix(diff->old_src, true);
+ use_new = diff_mnemonic_prefix(diff->new_src, false);
+ }
- diff->opts.old_prefix = diff_strdup_prefix(&diff->pool,
- opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT);
- diff->opts.new_prefix = diff_strdup_prefix(&diff->pool,
- opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT);
+ if (!diff->opts.old_prefix)
+ diff->opts.old_prefix = use_old;
+ if (!diff->opts.new_prefix)
+ diff->opts.new_prefix = use_new;
+ }
+ /* strdup prefix from pool so we're not dependent on external data */
+ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix);
+ diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix);
if (!diff->opts.old_prefix || !diff->opts.new_prefix)
- goto fail;
+ return -1;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
const char *swap = diff->opts.old_prefix;
@@ -335,15 +466,7 @@ static git_diff_list *git_diff_list_alloc(
diff->opts.new_prefix = swap;
}
- /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
- diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
-
- return diff;
-
-fail:
- git_diff_list_free(diff);
- return NULL;
+ return 0;
}
static void diff_list_free(git_diff_list *diff)
@@ -357,8 +480,10 @@ static void diff_list_free(git_diff_list *diff)
}
git_vector_free(&diff->deltas);
- git_pathspec_free(&diff->pathspec);
+ git_pathspec__vfree(&diff->pathspec);
git_pool_clear(&diff->pool);
+
+ git__memzero(diff, sizeof(*diff));
git__free(diff);
}
@@ -445,31 +570,95 @@ cleanup:
return result;
}
+static bool diff_time_eq(
+ const git_index_time *a, const git_index_time *b, bool use_nanos)
+{
+ return a->seconds == b->seconds &&
+ (!use_nanos || a->nanoseconds == b->nanoseconds);
+}
+
+typedef struct {
+ git_repository *repo;
+ git_iterator *old_iter;
+ git_iterator *new_iter;
+ const git_index_entry *oitem;
+ const git_index_entry *nitem;
+ git_buf ignore_prefix;
+} diff_in_progress;
+
#define MODE_BITS_MASK 0000777
+static int maybe_modified_submodule(
+ git_delta_t *status,
+ git_oid *found_oid,
+ git_diff_list *diff,
+ diff_in_progress *info)
+{
+ int error = 0;
+ git_submodule *sub;
+ unsigned int sm_status = 0;
+ git_submodule_ignore_t ign = diff->opts.ignore_submodules;
+
+ *status = GIT_DELTA_UNMODIFIED;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
+ ign == GIT_SUBMODULE_IGNORE_ALL)
+ return 0;
+
+ if ((error = git_submodule_lookup(
+ &sub, diff->repo, info->nitem->path)) < 0) {
+
+ /* GIT_EEXISTS means dir with .git in it was found - ignore it */
+ if (error == GIT_EEXISTS) {
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
+ return 0;
+
+ if ((error = git_submodule__status(
+ &sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
+ return error;
+
+ /* check IS_WD_UNMODIFIED because this case is only used
+ * when the new side of the diff is the working directory
+ */
+ if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
+ *status = GIT_DELTA_MODIFIED;
+
+ /* now that we have a HEAD OID, check if HEAD moved */
+ if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
+ !git_oid_equal(&info->oitem->oid, found_oid))
+ *status = GIT_DELTA_MODIFIED;
+
+ return 0;
+}
+
static int maybe_modified(
- git_iterator *old_iter,
- const git_index_entry *oitem,
- git_iterator *new_iter,
- const git_index_entry *nitem,
- git_diff_list *diff)
+ git_diff_list *diff,
+ diff_in_progress *info)
{
- git_oid noid, *use_noid = NULL;
+ git_oid noid;
git_delta_t status = GIT_DELTA_MODIFIED;
+ const git_index_entry *oitem = info->oitem;
+ const git_index_entry *nitem = info->nitem;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
- bool new_is_workdir = (new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
+ bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
const char *matched_pathspec;
- GIT_UNUSED(old_iter);
-
- if (!git_pathspec_match_path(
+ if (!git_pathspec__match(
&diff->pathspec, oitem->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
- &matched_pathspec))
+ &matched_pathspec, NULL))
return 0;
+ memset(&noid, 0, sizeof(noid));
+
/* on platforms with no symlinks, preserve mode of existing symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
@@ -502,63 +691,40 @@ static int maybe_modified(
}
}
- /* if oids and modes match, then file is unmodified */
- else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode)
+ /* if oids and modes match (and are valid), then file is unmodified */
+ else if (git_oid_equal(&oitem->oid, &nitem->oid) &&
+ omode == nmode &&
+ !git_oid_iszero(&oitem->oid))
status = GIT_DELTA_UNMODIFIED;
/* if we have an unknown OID and a workdir iterator, then check some
* circumstances that can accelerate things or need special handling
*/
else if (git_oid_iszero(&nitem->oid) && new_is_workdir) {
- /* TODO: add check against index file st_mtime to avoid racy-git */
+ bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
+ bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
- /* if the stat data looks exactly alike, then assume the same */
- if (omode == nmode &&
- oitem->file_size == nitem->file_size &&
- (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
- (oitem->ctime.seconds == nitem->ctime.seconds)) &&
- oitem->mtime.seconds == nitem->mtime.seconds &&
- (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
- (oitem->dev == nitem->dev)) &&
- oitem->ino == nitem->ino &&
- oitem->uid == nitem->uid &&
- oitem->gid == nitem->gid)
- status = GIT_DELTA_UNMODIFIED;
+ status = GIT_DELTA_UNMODIFIED;
- else if (S_ISGITLINK(nmode)) {
- int err;
- git_submodule *sub;
-
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
- status = GIT_DELTA_UNMODIFIED;
- 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;
- if (git_submodule_status(&sm_status, sub) < 0)
- return -1;
-
- /* check IS_WD_UNMODIFIED because this case is only used
- * when the new side of the diff is the working directory
- */
- status = GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)
- ? GIT_DELTA_UNMODIFIED : GIT_DELTA_MODIFIED;
-
- /* grab OID while we are here */
- if (git_oid_iszero(&nitem->oid)) {
- const git_oid *sm_oid = git_submodule_wd_id(sub);
- if (sm_oid != NULL) {
- git_oid_cpy(&noid, sm_oid);
- use_noid = &noid;
- }
- }
- }
+ /* TODO: add check against index file st_mtime to avoid racy-git */
+
+ if (S_ISGITLINK(nmode)) {
+ if (maybe_modified_submodule(&status, &noid, diff, info) < 0)
+ return -1;
}
+
+ /* if the stat data looks different, then mark modified - this just
+ * means that the OID will be recalculated below to confirm change
+ */
+ else if (omode != nmode ||
+ oitem->file_size != nitem->file_size ||
+ !diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
+ (use_ctime &&
+ !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
+ oitem->ino != nitem->ino ||
+ oitem->uid != nitem->uid ||
+ oitem->gid != nitem->gid)
+ status = GIT_DELTA_MODIFIED;
}
/* if mode is GITLINK and submodules are ignored, then skip */
@@ -569,12 +735,11 @@ static int maybe_modified(
/* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now
*/
- if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
- if (!use_noid) {
+ if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
+ if (git_oid_iszero(&noid)) {
if (git_diff__oid_for_file(diff->repo,
nitem->path, nitem->mode, nitem->file_size, &noid) < 0)
return -1;
- use_noid = &noid;
}
/* if oid matches, then mark unmodified (except submodules, where
@@ -582,12 +747,13 @@ static int maybe_modified(
* matches between the index and the workdir HEAD)
*/
if (omode == nmode && !S_ISGITLINK(omode) &&
- git_oid_equal(&oitem->oid, use_noid))
+ git_oid_equal(&oitem->oid, &noid))
status = GIT_DELTA_UNMODIFIED;
}
return diff_delta__from_two(
- diff, status, oitem, omode, nitem, nmode, use_noid, matched_pathspec);
+ diff, status, oitem, omode, nitem, nmode,
+ git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
}
static bool entry_is_prefixed(
@@ -607,237 +773,347 @@ static bool entry_is_prefixed(
item->path[pathlen] == '/');
}
-static int diff_list_init_from_iterators(
- git_diff_list *diff,
- git_iterator *old_iter,
- git_iterator *new_iter)
+static int diff_scan_inside_untracked_dir(
+ git_diff_list *diff, diff_in_progress *info, git_delta_t *delta_type)
{
- diff->old_src = old_iter->type;
- diff->new_src = new_iter->type;
+ int error = 0;
+ git_buf base = GIT_BUF_INIT;
+ bool is_ignored;
- /* Use case-insensitive compare if either iterator has
- * the ignore_case bit set */
- if (!git_iterator_ignore_case(old_iter) &&
- !git_iterator_ignore_case(new_iter))
- {
- diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
+ *delta_type = GIT_DELTA_IGNORED;
+ git_buf_sets(&base, info->nitem->path);
- diff->strcomp = git__strcmp;
- diff->strncomp = git__strncmp;
- diff->pfxcomp = git__prefixcmp;
- diff->entrycomp = git_index_entry__cmp;
- } else {
- diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+ /* advance into untracked directory */
+ if ((error = git_iterator_advance_into(&info->nitem, info->new_iter)) < 0) {
- diff->strcomp = git__strcasecmp;
- diff->strncomp = git__strncasecmp;
- diff->pfxcomp = git__prefixcmp_icase;
- diff->entrycomp = git_index_entry__cmp_icase;
+ /* skip ahead if empty */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = git_iterator_advance(&info->nitem, info->new_iter);
+ }
+
+ goto done;
}
- return 0;
+ /* look for actual untracked file */
+ while (info->nitem != NULL &&
+ !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+ is_ignored = git_iterator_current_is_ignored(info->new_iter);
+
+ /* need to recurse into non-ignored directories */
+ if (!is_ignored && S_ISDIR(info->nitem->mode)) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
+
+ if (!error)
+ continue;
+ else if (error == GIT_ENOTFOUND) {
+ error = 0;
+ is_ignored = true; /* treat empty as ignored */
+ } else
+ break; /* real error, must stop */
+ }
+
+ /* found a non-ignored item - treat parent dir as untracked */
+ if (!is_ignored) {
+ *delta_type = GIT_DELTA_UNTRACKED;
+ break;
+ }
+
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
+ break;
+ }
+
+ /* finish off scan */
+ while (info->nitem != NULL &&
+ !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
+ break;
+ }
+
+done:
+ git_buf_free(&base);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
}
-int git_diff__from_iterators(
- git_diff_list **diff_ptr,
- git_repository *repo,
- git_iterator *old_iter,
- git_iterator *new_iter,
- const git_diff_options *opts)
+static int handle_unmatched_new_item(
+ git_diff_list *diff, diff_in_progress *info)
{
int error = 0;
- const git_index_entry *oitem, *nitem;
- git_buf ignore_prefix = GIT_BUF_INIT;
- git_diff_list *diff = git_diff_list_alloc(repo, opts);
-
- *diff_ptr = NULL;
+ const git_index_entry *nitem = info->nitem;
+ git_delta_t delta_type = GIT_DELTA_UNTRACKED;
+ bool contains_oitem;
- if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
- goto fail;
+ /* check if this is a prefix of the other side */
+ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
- 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;
+ /* check if this is contained in an ignored parent directory */
+ if (git_buf_len(&info->ignore_prefix)) {
+ if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0)
+ delta_type = GIT_DELTA_IGNORED;
+ else
+ git_buf_clear(&info->ignore_prefix);
}
- if (git_iterator_current(&oitem, old_iter) < 0 ||
- git_iterator_current(&nitem, new_iter) < 0)
- goto fail;
+ if (nitem->mode == GIT_FILEMODE_TREE) {
+ bool recurse_into_dir = contains_oitem;
- /* run iterators building diffs */
- while (oitem || nitem) {
- int cmp = oitem ? (nitem ? diff->entrycomp(oitem, nitem) : -1) : 1;
+ /* if not already inside an ignored dir, check if this is ignored */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ git_iterator_current_is_ignored(info->new_iter)) {
+ delta_type = GIT_DELTA_IGNORED;
+ git_buf_sets(&info->ignore_prefix, nitem->path);
+ }
- /* create DELETED records for old items not matched in new */
- if (cmp < 0) {
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
- goto fail;
+ /* check if user requests recursion into this type of dir */
+ recurse_into_dir = contains_oitem ||
+ (delta_type == GIT_DELTA_UNTRACKED &&
+ 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 (recurse_into_dir) {
+ git_buf *full = NULL;
+ if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
+ return -1;
+ if (full && git_path_contains_dir(full, DOT_GIT))
+ recurse_into_dir = false;
+ }
- /* if we are generating TYPECHANGE records then check for that
- * instead of just generating a DELETE record
- */
- 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 */
- git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
- if (last) {
- last->status = GIT_DELTA_TYPECHANGE;
- last->new_file.mode = GIT_FILEMODE_TREE;
- }
+ /* still have to look into untracked directories to match core git -
+ * with no untracked files, directory is treated as ignored
+ */
+ if (!recurse_into_dir &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_FAST_UNTRACKED_DIRS))
+ {
+ git_diff_delta *last;
+
+ /* attempt to insert record for this directory */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
+ return error;
+
+ /* if delta wasn't created (because of rules), just skip ahead */
+ last = diff_delta__last_for_item(diff, nitem);
+ if (!last)
+ return git_iterator_advance(&info->nitem, info->new_iter);
+
+ /* iterate into dir looking for an actual untracked file */
+ if (diff_scan_inside_untracked_dir(diff, info, &delta_type) < 0)
+ return -1;
+
+ /* it iteration changed delta type, the update the record */
+ if (delta_type == GIT_DELTA_IGNORED) {
+ last->status = GIT_DELTA_IGNORED;
- /* If new_iter is a workdir iterator, then this situation
- * will certainly be followed by a series of untracked items.
- * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
- */
- if (S_ISDIR(nitem->mode) &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
- {
- if (git_iterator_advance(&nitem, new_iter) < 0)
- goto fail;
+ /* remove the record if we don't want ignored records */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
+ git_vector_pop(&diff->deltas);
+ git__free(last);
}
}
- if (git_iterator_advance(&oitem, old_iter) < 0)
- goto fail;
+ return 0;
}
- /* create ADDED, TRACKED, or IGNORED records for new items not
- * matched in old (and/or descend into directories as needed)
- */
- else if (cmp > 0) {
- git_delta_t delta_type = GIT_DELTA_UNTRACKED;
- bool contains_oitem = entry_is_prefixed(diff, oitem, nitem);
-
- /* check if contained in ignored parent directory */
- if (git_buf_len(&ignore_prefix) &&
- diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
- delta_type = GIT_DELTA_IGNORED;
-
- if (S_ISDIR(nitem->mode)) {
- /* recurse into directory only if there are tracked items in
- * it or if the user requested the contents of untracked
- * directories and it is not under an ignored directory.
- */
- bool recurse_into_dir =
- (delta_type == GIT_DELTA_UNTRACKED &&
- 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_into_dir) {
- git_buf *full = NULL;
- if (git_iterator_current_workdir_path(&full, new_iter) < 0)
- goto fail;
- if (git_path_contains_dir(full, DOT_GIT))
- recurse_into_dir = false;
- }
+ /* try to advance into directory if necessary */
+ if (recurse_into_dir) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
- /* 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;
-
- /* 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 real error or no error, proceed with iteration */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
- 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 either skip
+ * it or ignore it
+ */
+ if (contains_oitem)
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ delta_type = GIT_DELTA_IGNORED;
+ }
+ }
- /* if directory is empty, can't advance into it, so skip */
- if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = git_iterator_advance(&nitem, new_iter);
+ /* In core git, the next two checks are effectively reversed --
+ * i.e. when an file contained in an ignored directory is explicitly
+ * ignored, it shows up as an ignored file in the diff list, even though
+ * other untracked files in the same directory are skipped completely.
+ *
+ * To me, this seems odd. If the directory is ignored and the file is
+ * untracked, we should skip it consistently, regardless of whether it
+ * happens to match a pattern in the ignore file.
+ *
+ * To match the core git behavior, reverse the following two if checks
+ * so that individual file ignores are checked before container
+ * directory exclusions are used to skip the file.
+ */
+ else if (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
+ /* item contained in ignored directory, so skip over it */
+ return git_iterator_advance(&info->nitem, info->new_iter);
- git_buf_clear(&ignore_prefix);
- }
+ else if (git_iterator_current_is_ignored(info->new_iter))
+ delta_type = GIT_DELTA_IGNORED;
- if (error < 0)
- goto fail;
- continue;
- }
- }
+ else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ delta_type = GIT_DELTA_ADDED;
- /* In core git, the next two "else if" clauses are effectively
- * reversed -- i.e. when an untracked file contained in an
- * ignored directory is individually ignored, it shows up as an
- * ignored file in the diff list, even though other untracked
- * files in the same directory are skipped completely.
- *
- * To me, this is odd. If the directory is ignored and the file
- * is untracked, we should skip it consistently, regardless of
- * whether it happens to match a pattern in the ignore file.
- *
- * To match the core git behavior, just reverse the following
- * two "else if" cases so that individual file ignores are
- * checked before container directory exclusions are used to
- * skip the file.
- */
- else if (delta_type == GIT_DELTA_IGNORED &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {
- if (git_iterator_advance(&nitem, new_iter) < 0)
- goto fail;
- continue; /* ignored parent directory, so skip completely */
- }
+ else if (nitem->mode == GIT_FILEMODE_COMMIT) {
+ git_submodule *sm;
- else if (git_iterator_current_is_ignored(new_iter))
- delta_type = GIT_DELTA_IGNORED;
+ /* ignore things that are not actual submodules */
+ if (git_submodule_lookup(&sm, info->repo, nitem->path) != 0) {
+ giterr_clear();
+ delta_type = GIT_DELTA_IGNORED;
+ }
+ }
- else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- delta_type = GIT_DELTA_ADDED;
+ /* Actually create the record for this item if necessary */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
+ return error;
- if (diff_delta__from_one(diff, delta_type, nitem) < 0)
- goto fail;
+ /* If user requested TYPECHANGE records, then check for that instead of
+ * just generating an ADDED/UNTRACKED record
+ */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ contains_oitem)
+ {
+ /* this entry was prefixed with a tree - make TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->old_file.mode = GIT_FILEMODE_TREE;
+ }
+ }
- /* if we are generating TYPECHANGE records then check for that
- * instead of just generating an ADDED/UNTRACKED record
- */
- if (delta_type != GIT_DELTA_IGNORED &&
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
- contains_oitem)
- {
- /* this entry was prefixed with a tree - make TYPECHANGE */
- git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
- if (last) {
- last->status = GIT_DELTA_TYPECHANGE;
- last->old_file.mode = GIT_FILEMODE_TREE;
- }
- }
+ return git_iterator_advance(&info->nitem, info->new_iter);
+}
+
+static int handle_unmatched_old_item(
+ git_diff_list *diff, diff_in_progress *info)
+{
+ int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem);
+ if (error < 0)
+ return error;
- if (git_iterator_advance(&nitem, new_iter) < 0)
- goto fail;
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating a DELETE record
+ */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ entry_is_prefixed(diff, info->nitem, info->oitem))
+ {
+ /* this entry has become a tree! convert to TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->new_file.mode = GIT_FILEMODE_TREE;
}
+ /* If new_iter is a workdir iterator, then this situation
+ * will certainly be followed by a series of untracked items.
+ * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
+ */
+ if (S_ISDIR(info->nitem->mode) &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ }
+
+ return git_iterator_advance(&info->oitem, info->old_iter);
+}
+
+static int handle_matched_item(
+ git_diff_list *diff, diff_in_progress *info)
+{
+ int error = 0;
+
+ if ((error = maybe_modified(diff, info)) < 0)
+ return error;
+
+ if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) ||
+ error == GIT_ITEROVER)
+ error = git_iterator_advance(&info->nitem, info->new_iter);
+
+ return error;
+}
+
+int git_diff__from_iterators(
+ git_diff_list **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ diff_in_progress info;
+ git_diff_list *diff;
+
+ *diff_ptr = NULL;
+
+ diff = diff_list_alloc(repo, old_iter, new_iter);
+ GITERR_CHECK_ALLOC(diff);
+
+ info.repo = repo;
+ info.old_iter = old_iter;
+ info.new_iter = new_iter;
+ git_buf_init(&info.ignore_prefix, 0);
+
+ /* make iterators have matching icase behavior */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
+ if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
+ (error = git_iterator_set_ignore_case(new_iter, true)) < 0)
+ goto cleanup;
+ }
+
+ /* finish initialization */
+ if ((error = diff_list_apply_options(diff, opts)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_current(&info.oitem, old_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ goto cleanup;
+ if ((error = git_iterator_current(&info.nitem, new_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ goto cleanup;
+ error = 0;
+
+ /* run iterators building diffs */
+ while (!error && (info.oitem || info.nitem)) {
+ int cmp = info.oitem ?
+ (info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
+
+ /* create DELETED records for old items not matched in new */
+ if (cmp < 0)
+ error = handle_unmatched_old_item(diff, &info);
+
+ /* create ADDED, TRACKED, or IGNORED records for new items not
+ * matched in old (and/or descend into directories as needed)
+ */
+ else if (cmp > 0)
+ error = handle_unmatched_new_item(diff, &info);
+
/* otherwise item paths match, so create MODIFIED record
* (or ADDED and DELETED pair if type changed)
*/
- else {
- assert(oitem && nitem && cmp == 0);
+ else
+ error = handle_matched_item(diff, &info);
- 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;
- }
+ /* because we are iterating over two lists, ignore ITEROVER */
+ if (error == GIT_ITEROVER)
+ error = 0;
}
- *diff_ptr = diff;
-
-fail:
- if (!*diff_ptr) {
+cleanup:
+ if (!error)
+ *diff_ptr = diff;
+ else
git_diff_list_free(diff);
- error = -1;
- }
- git_buf_free(&ignore_prefix);
+ git_buf_free(&info.ignore_prefix);
return error;
}
@@ -859,12 +1135,20 @@ int git_diff_tree_to_tree(
const git_diff_options *opts)
{
int error = 0;
+ git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
assert(diff && repo);
+ /* for tree to tree diff, be case sensitive even if the index is
+ * currently case insensitive, unless the user explicitly asked
+ * for case insensitivity
+ */
+ if (opts && (opts->flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
+ iflag = GIT_ITERATOR_IGNORE_CASE;
+
DIFF_FROM_ITERATORS(
- git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
- git_iterator_for_tree(&b, new_tree, 0, pfx, pfx)
+ git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
+ git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
);
return error;
@@ -878,17 +1162,40 @@ int git_diff_tree_to_index(
const git_diff_options *opts)
{
int error = 0;
+ bool reset_index_ignore_case = false;
assert(diff && repo);
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error;
+ if (index->ignore_case) {
+ git_index__set_ignore_case(index, false);
+ reset_index_ignore_case = true;
+ }
+
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
git_iterator_for_index(&b, index, 0, pfx, pfx)
);
+ if (reset_index_ignore_case) {
+ git_index__set_ignore_case(index, true);
+
+ if (!error) {
+ git_diff_list *d = *diff;
+
+ d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+ d->strcomp = git__strcasecmp;
+ d->strncomp = git__strncasecmp;
+ d->pfxcomp = git__prefixcmp_icase;
+ d->entrycomp = git_index_entry__cmp_icase;
+
+ git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp);
+ git_vector_sort(&d->deltas);
+ }
+ }
+
return error;
}
@@ -933,3 +1240,107 @@ int git_diff_tree_to_workdir(
return error;
}
+
+size_t git_diff_num_deltas(git_diff_list *diff)
+{
+ assert(diff);
+ return (size_t)diff->deltas.length;
+}
+
+size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
+{
+ size_t i, count = 0;
+ git_diff_delta *delta;
+
+ assert(diff);
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ count += (delta->status == type);
+ }
+
+ return count;
+}
+
+int git_diff_is_sorted_icase(const git_diff_list *diff)
+{
+ return (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
+}
+
+int git_diff__paired_foreach(
+ git_diff_list *head2idx,
+ git_diff_list *idx2wd,
+ int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
+ void *payload)
+{
+ int cmp;
+ git_diff_delta *h2i, *i2w;
+ size_t i, j, i_max, j_max;
+ int (*strcomp)(const char *, const char *) = git__strcmp;
+ bool icase_mismatch;
+
+ i_max = head2idx ? head2idx->deltas.length : 0;
+ j_max = idx2wd ? idx2wd->deltas.length : 0;
+ if (!i_max && !j_max)
+ return 0;
+
+ /* At some point, tree-to-index diffs will probably never ignore case,
+ * even if that isn't true now. Index-to-workdir diffs may or may not
+ * ignore case, but the index filename for the idx2wd diff should
+ * still be using the canonical case-preserving name.
+ *
+ * Therefore the main thing we need to do here is make sure the diffs
+ * are traversed in a compatible order. To do this, we temporarily
+ * resort a mismatched diff to get the order correct.
+ */
+ icase_mismatch =
+ (head2idx != NULL && idx2wd != NULL &&
+ ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE));
+
+ /* force case-sensitive delta sort */
+ if (icase_mismatch) {
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
+ git_vector_sort(&head2idx->deltas);
+ } else {
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+ }
+ else if (head2idx != NULL && head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
+ strcomp = git__strcasecmp;
+
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
+ h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
+ i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
+
+ cmp = !i2w ? -1 : !h2i ? 1 :
+ strcomp(h2i->new_file.path, i2w->old_file.path);
+
+ if (cmp < 0) {
+ if (cb(h2i, NULL, payload))
+ return GIT_EUSER;
+ i++;
+ } else if (cmp > 0) {
+ if (cb(NULL, i2w, payload))
+ return GIT_EUSER;
+ j++;
+ } else {
+ if (cb(h2i, i2w, payload))
+ return GIT_EUSER;
+ i++; j++;
+ }
+ }
+
+ /* restore case-insensitive delta sort */
+ if (icase_mismatch) {
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
+ git_vector_sort(&head2idx->deltas);
+ } else {
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/diff.h b/src/diff.h
index 8e3cbcd46..bec7e27d7 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -16,6 +16,7 @@
#include "iterator.h"
#include "repository.h"
#include "pool.h"
+#include "odb.h"
#define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/"
@@ -26,17 +27,31 @@ enum {
GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
+ GIT_DIFFCAPS_TRUST_NANOSECS = (1 << 5), /* use stat time nanoseconds */
};
+#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
+#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
+
enum {
GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
- GIT_DIFF_FLAG__TO_DELETE = (1 << 11), /* delete entry during rename det. */
- GIT_DIFF_FLAG__TO_SPLIT = (1 << 12), /* split entry during rename det. */
+ GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
+ GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
+
+ GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
+ GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
+ GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
+ GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
};
+#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
+
+#define GIT_DIFF__VERBOSE (1 << 30)
+
struct git_diff_list {
git_refcount rc;
git_repository *repo;
@@ -60,10 +75,20 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff);
extern int git_diff_delta__cmp(const void *a, const void *b);
+extern int git_diff_delta__casecmp(const void *a, const void *b);
+
+extern const char *git_diff_delta__path(const git_diff_delta *delta);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
+extern int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int oid_strlen);
+
extern int git_diff__oid_for_file(
git_repository *, const char *, uint16_t, git_off_t, git_oid *);
@@ -74,5 +99,50 @@ extern int git_diff__from_iterators(
git_iterator *new_iter,
const git_diff_options *opts);
+extern int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ void *payload);
+
+extern int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p);
+
+extern int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
+
+extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
+
+extern int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload);
+
+/*
+ * Sometimes a git_diff_file will have a zero size; this attempts to
+ * fill in the size without loading the blob if possible. If that is
+ * not possible, then it will return the git_odb_object that had to be
+ * loaded and the caller can use it or dispose of it as needed.
+ */
+GIT_INLINE(int) git_diff_file__resolve_zero_size(
+ git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
+{
+ int error;
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ error = git_odb__read_header_or_object(
+ odb_obj, &len, &type, odb, &file->oid);
+
+ git_odb_free(odb);
+
+ if (!error)
+ file->size = (git_off_t)len;
+
+ return error;
+}
+
#endif
diff --git a/src/diff_driver.c b/src/diff_driver.c
new file mode 100644
index 000000000..e82dfa50d
--- /dev/null
+++ b/src/diff_driver.c
@@ -0,0 +1,406 @@
+/*
+ * 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 "git2/attr.h"
+
+#include "diff.h"
+#include "diff_patch.h"
+#include "diff_driver.h"
+#include "strmap.h"
+#include "map.h"
+#include "buf_text.h"
+#include "repository.h"
+
+GIT__USE_STRMAP;
+
+typedef enum {
+ DIFF_DRIVER_AUTO = 0,
+ DIFF_DRIVER_BINARY = 1,
+ DIFF_DRIVER_TEXT = 2,
+ DIFF_DRIVER_PATTERNLIST = 3,
+} git_diff_driver_t;
+
+enum {
+ DIFF_CONTEXT_FIND_NORMAL = 0,
+ DIFF_CONTEXT_FIND_ICASE = (1 << 0),
+ DIFF_CONTEXT_FIND_EXT = (1 << 1),
+};
+
+/* data for finding function context for a given file type */
+struct git_diff_driver {
+ git_diff_driver_t type;
+ uint32_t binary_flags;
+ uint32_t other_flags;
+ git_array_t(regex_t) fn_patterns;
+ regex_t word_pattern;
+ char name[GIT_FLEX_ARRAY];
+};
+
+struct git_diff_driver_registry {
+ git_strmap *drivers;
+};
+
+#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
+
+static git_diff_driver global_drivers[3] = {
+ { DIFF_DRIVER_AUTO, 0, 0, },
+ { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
+ { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 },
+};
+
+git_diff_driver_registry *git_diff_driver_registry_new()
+{
+ git_diff_driver_registry *reg =
+ git__calloc(1, sizeof(git_diff_driver_registry));
+ if (!reg)
+ return NULL;
+
+ if ((reg->drivers = git_strmap_alloc()) == NULL) {
+ git_diff_driver_registry_free(reg);
+ return NULL;
+ }
+
+ return reg;
+}
+
+void git_diff_driver_registry_free(git_diff_driver_registry *reg)
+{
+ git_diff_driver *drv;
+
+ if (!reg)
+ return;
+
+ git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
+ git_strmap_free(reg->drivers);
+ git__free(reg);
+}
+
+static int diff_driver_add_funcname(
+ git_diff_driver *drv, const char *name, int regex_flags)
+{
+ int error;
+ regex_t re, *re_ptr;
+
+ if ((error = regcomp(&re, name, regex_flags)) != 0) {
+ /* TODO: warning about bad regex instead of failure */
+ error = giterr_set_regex(&re, error);
+ regfree(&re);
+ return error;
+ }
+
+ re_ptr = git_array_alloc(drv->fn_patterns);
+ GITERR_CHECK_ALLOC(re_ptr);
+
+ memcpy(re_ptr, &re, sizeof(re));
+ return 0;
+}
+
+static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_funcname(payload, entry->value, REG_EXTENDED);
+}
+
+static int diff_driver_funcname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_funcname(payload, entry->value, 0);
+}
+
+static git_diff_driver_registry *git_repository_driver_registry(
+ git_repository *repo)
+{
+ if (!repo->diff_drivers) {
+ git_diff_driver_registry *reg = git_diff_driver_registry_new();
+ reg = git__compare_and_swap(&repo->diff_drivers, NULL, reg);
+
+ if (reg != NULL) /* if we race, free losing allocation */
+ git_diff_driver_registry_free(reg);
+ }
+
+ if (!repo->diff_drivers)
+ giterr_set(GITERR_REPOSITORY, "Unable to create diff driver registry");
+
+ return repo->diff_drivers;
+}
+
+static int git_diff_driver_load(
+ git_diff_driver **out, git_repository *repo, const char *driver_name)
+{
+ int error = 0, bval;
+ git_diff_driver_registry *reg;
+ git_diff_driver *drv;
+ size_t namelen = strlen(driver_name);
+ khiter_t pos;
+ git_config *cfg;
+ git_buf name = GIT_BUF_INIT;
+ const char *val;
+ bool found_driver = false;
+
+ reg = git_repository_driver_registry(repo);
+ if (!reg)
+ return -1;
+ else {
+ pos = git_strmap_lookup_index(reg->drivers, driver_name);
+ if (git_strmap_valid_index(reg->drivers, pos)) {
+ *out = git_strmap_value_at(reg->drivers, pos);
+ return 0;
+ }
+ }
+
+ /* if you can't read config for repo, just use default driver */
+ if (git_repository_config__weakptr(&cfg, repo) < 0) {
+ giterr_clear();
+ return GIT_ENOTFOUND;
+ }
+
+ drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1);
+ GITERR_CHECK_ALLOC(drv);
+ drv->type = DIFF_DRIVER_AUTO;
+ memcpy(drv->name, driver_name, namelen);
+
+ if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
+ goto done;
+ if ((error = git_config_get_string(&val, cfg, name.ptr)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ /* diff.<driver>.binary unspecified, so just continue */
+ giterr_clear();
+ } else if (git_config_parse_bool(&bval, val) < 0) {
+ /* TODO: warn that diff.<driver>.binary has invalid value */
+ giterr_clear();
+ } else if (bval) {
+ /* if diff.<driver>.binary is true, just return the binary driver */
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ goto done;
+ } else {
+ /* if diff.<driver>.binary is false, force binary checks off */
+ /* but still may have custom function context patterns, etc. */
+ drv->binary_flags = GIT_DIFF_FORCE_TEXT;
+ found_driver = true;
+ }
+
+ /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "xfuncname", strlen("xfuncname"));
+ if ((error = git_config_get_multivar(
+ cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "funcname", strlen("funcname"));
+ if ((error = git_config_get_multivar(
+ cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.funcname, so just continue */
+ }
+
+ /* if we found any patterns, set driver type to use correct callback */
+ if (git_array_size(drv->fn_patterns) > 0) {
+ drv->type = DIFF_DRIVER_PATTERNLIST;
+ found_driver = true;
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "wordregex", strlen("wordregex"));
+ if ((error = git_config_get_string(&val, cfg, name.ptr)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.wordregex, so just continue */
+ } else if ((error = regcomp(&drv->word_pattern, val, REG_EXTENDED)) != 0) {
+ /* TODO: warning about bad regex instead of failure */
+ error = giterr_set_regex(&drv->word_pattern, error);
+ goto done;
+ } else {
+ found_driver = true;
+ }
+
+ /* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
+ * diff in drv->other_flags
+ */
+
+ /* if no driver config found at all, fall back on AUTO driver */
+ if (!found_driver)
+ goto done;
+
+ /* store driver in registry */
+ git_strmap_insert(reg->drivers, drv->name, drv, error);
+ if (error < 0)
+ goto done;
+
+ *out = drv;
+
+done:
+ git_buf_free(&name);
+
+ if (!*out)
+ *out = &global_drivers[DIFF_DRIVER_AUTO];
+
+ if (drv && drv != *out)
+ git_diff_driver_free(drv);
+
+ return error;
+}
+
+int git_diff_driver_lookup(
+ git_diff_driver **out, git_repository *repo, const char *path)
+{
+ int error = 0;
+ const char *value;
+
+ assert(out);
+
+ if (!repo || !path || !strlen(path))
+ goto use_auto;
+
+ if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0)
+ return error;
+
+ if (GIT_ATTR_UNSPECIFIED(value))
+ /* just use the auto value */;
+ else if (GIT_ATTR_FALSE(value))
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ else if (GIT_ATTR_TRUE(value))
+ *out = &global_drivers[DIFF_DRIVER_TEXT];
+
+ /* otherwise look for driver information in config and build driver */
+ else if ((error = git_diff_driver_load(out, repo, value)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ return error;
+ else
+ giterr_clear();
+ }
+
+use_auto:
+ if (!*out)
+ *out = &global_drivers[DIFF_DRIVER_AUTO];
+
+ return 0;
+}
+
+void git_diff_driver_free(git_diff_driver *driver)
+{
+ size_t i;
+
+ if (!driver)
+ return;
+
+ for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
+ regfree(git_array_get(driver->fn_patterns, i));
+ git_array_clear(driver->fn_patterns);
+
+ regfree(&driver->word_pattern);
+
+ git__free(driver);
+}
+
+void git_diff_driver_update_options(
+ uint32_t *option_flags, git_diff_driver *driver)
+{
+ if ((*option_flags & FORCE_DIFFABLE) == 0)
+ *option_flags |= driver->binary_flags;
+
+ *option_flags |= driver->other_flags;
+}
+
+int git_diff_driver_content_is_binary(
+ git_diff_driver *driver, const char *content, size_t content_len)
+{
+ const git_buf search = { (char *)content, 0, min(content_len, 4000) };
+
+ GIT_UNUSED(driver);
+
+ /* TODO: provide encoding / binary detection callbacks that can
+ * be UTF-8 aware, etc. For now, instead of trying to be smart,
+ * let's just use the simple NUL-byte detection that core git uses.
+ */
+
+ /* previously was: if (git_buf_text_is_binary(&search)) */
+ if (git_buf_text_contains_nul(&search))
+ return 1;
+
+ return 0;
+}
+
+static int diff_context_line__simple(
+ git_diff_driver *driver, const char *line, size_t line_len)
+{
+ GIT_UNUSED(driver);
+ GIT_UNUSED(line_len);
+ return (git__isalpha(*line) || *line == '_' || *line == '$');
+}
+
+static int diff_context_line__pattern_match(
+ git_diff_driver *driver, const char *line, size_t line_len)
+{
+ size_t i;
+
+ GIT_UNUSED(line_len);
+
+ for (i = 0; i < git_array_size(driver->fn_patterns); ++i) {
+ if (!regexec(git_array_get(driver->fn_patterns, i), line, 0, NULL, 0))
+ return true;
+ }
+
+ return false;
+}
+
+static long diff_context_find(
+ const char *line,
+ long line_len,
+ char *out,
+ long out_size,
+ void *payload)
+{
+ git_diff_find_context_payload *ctxt = payload;
+
+ if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
+ return -1;
+ git_buf_rtrim(&ctxt->line);
+
+ if (!ctxt->line.size)
+ return -1;
+
+ if (!ctxt->match_line ||
+ !ctxt->match_line(ctxt->driver, ctxt->line.ptr, ctxt->line.size))
+ return -1;
+
+ if (out_size > (long)ctxt->line.size)
+ out_size = (long)ctxt->line.size;
+ memcpy(out, ctxt->line.ptr, (size_t)out_size);
+
+ return out_size;
+}
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver)
+{
+ *findfn_out = driver ? diff_context_find : NULL;
+
+ memset(payload_out, 0, sizeof(*payload_out));
+ if (driver) {
+ payload_out->driver = driver;
+ payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
+ diff_context_line__pattern_match : diff_context_line__simple;
+ git_buf_init(&payload_out->line, 0);
+ }
+}
+
+void git_diff_find_context_clear(git_diff_find_context_payload *payload)
+{
+ if (payload) {
+ git_buf_free(&payload->line);
+ payload->driver = NULL;
+ }
+}
+
diff --git a/src/diff_driver.h b/src/diff_driver.h
new file mode 100644
index 000000000..9d3f18660
--- /dev/null
+++ b/src/diff_driver.h
@@ -0,0 +1,49 @@
+/*
+ * 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_diff_driver_h__
+#define INCLUDE_diff_driver_h__
+
+#include "common.h"
+#include "buffer.h"
+
+typedef struct git_diff_driver_registry git_diff_driver_registry;
+
+git_diff_driver_registry *git_diff_driver_registry_new(void);
+void git_diff_driver_registry_free(git_diff_driver_registry *);
+
+typedef struct git_diff_driver git_diff_driver;
+
+int git_diff_driver_lookup(git_diff_driver **, git_repository *, const char *);
+void git_diff_driver_free(git_diff_driver *);
+
+/* diff option flags to force off and on for this driver */
+void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *);
+
+/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */
+int git_diff_driver_content_is_binary(
+ git_diff_driver *, const char *content, size_t content_len);
+
+typedef long (*git_diff_find_context_fn)(
+ const char *, long, char *, long, void *);
+
+typedef int (*git_diff_find_context_line)(
+ git_diff_driver *, const char *, size_t);
+
+typedef struct {
+ git_diff_driver *driver;
+ git_diff_find_context_line match_line;
+ git_buf line;
+} git_diff_find_context_payload;
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver);
+
+void git_diff_find_context_clear(git_diff_find_context_payload *);
+
+#endif
diff --git a/src/diff_file.c b/src/diff_file.c
new file mode 100644
index 000000000..bcfef13cd
--- /dev/null
+++ b/src/diff_file.c
@@ -0,0 +1,440 @@
+/*
+ * 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 "git2/blob.h"
+#include "git2/submodule.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "odb.h"
+#include "fileops.h"
+#include "filter.h"
+
+#define DIFF_MAX_FILESIZE 0x20000000
+
+static bool diff_file_content_binary_by_size(git_diff_file_content *fc)
+{
+ /* if we have diff opts, check max_size vs file size */
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 &&
+ fc->opts_max_size > 0 &&
+ fc->file->size > fc->opts_max_size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+
+ return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0);
+}
+
+static void diff_file_content_binary_by_content(git_diff_file_content *fc)
+{
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ switch (git_diff_driver_content_is_binary(
+ fc->driver, fc->map.data, fc->map.len)) {
+ case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break;
+ case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break;
+ default: break;
+ }
+}
+
+static int diff_file_content_init_common(
+ git_diff_file_content *fc, const git_diff_options *opts)
+{
+ fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL;
+
+ if (opts && opts->max_size >= 0)
+ fc->opts_max_size = opts->max_size ?
+ opts->max_size : DIFF_MAX_FILESIZE;
+
+ if (fc->src == GIT_ITERATOR_TYPE_EMPTY)
+ fc->src = GIT_ITERATOR_TYPE_TREE;
+
+ if (!fc->driver &&
+ git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ /* give driver a chance to modify options */
+ git_diff_driver_update_options(&fc->opts_flags, fc->driver);
+
+ /* make sure file is conceivable mmap-able */
+ if ((git_off_t)((size_t)fc->file->size) != fc->file->size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ /* check if user is forcing text diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ }
+ /* check if user is forcing binary diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ }
+
+ diff_file_content_binary_by_size(fc);
+
+ if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->map.len = 0;
+ fc->map.data = "";
+ }
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff_list *diff,
+ size_t delta_index,
+ bool use_old)
+{
+ git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index);
+ bool has_data = true;
+
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = diff->repo;
+ fc->file = use_old ? &delta->old_file : &delta->new_file;
+ fc->src = use_old ? diff->old_src : diff->new_src;
+
+ if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ has_data = !use_old; break;
+ case GIT_DELTA_DELETED:
+ has_data = use_old; break;
+ case GIT_DELTA_UNTRACKED:
+ has_data = !use_old &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) != 0;
+ break;
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_COPIED:
+ case GIT_DELTA_RENAMED:
+ break;
+ default:
+ has_data = false;
+ break;
+ }
+
+ if (!has_data)
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+
+ return diff_file_content_init_common(fc, &diff->opts);
+}
+
+int git_diff_file_content__init_from_blob(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_blob *blob,
+ git_diff_file *as_file)
+{
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = repo;
+ fc->file = as_file;
+ fc->blob = blob;
+
+ if (!blob) {
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+ } else {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ fc->file->size = git_blob_rawsize(blob);
+ fc->file->mode = GIT_FILEMODE_BLOB;
+ git_oid_cpy(&fc->file->oid, git_blob_id(blob));
+
+ fc->map.len = (size_t)fc->file->size;
+ fc->map.data = (char *)git_blob_rawcontent(blob);
+ }
+
+ return diff_file_content_init_common(fc, opts);
+}
+
+int git_diff_file_content__init_from_raw(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const char *buf,
+ size_t buflen,
+ git_diff_file *as_file)
+{
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = repo;
+ fc->file = as_file;
+
+ if (!buf) {
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+ } else {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ fc->file->size = buflen;
+ fc->file->mode = GIT_FILEMODE_BLOB;
+ git_odb_hash(&fc->file->oid, buf, buflen, GIT_OBJ_BLOB);
+
+ fc->map.len = buflen;
+ fc->map.data = (char *)buf;
+ }
+
+ return diff_file_content_init_common(fc, opts);
+}
+
+static int diff_file_content_commit_to_str(
+ git_diff_file_content *fc, bool check_status)
+{
+ char oid[GIT_OID_HEXSZ+1];
+ git_buf content = GIT_BUF_INIT;
+ const char *status = "";
+
+ if (check_status) {
+ int error = 0;
+ git_submodule *sm = NULL;
+ unsigned int sm_status = 0;
+ const git_oid *sm_head;
+
+ if ((error = git_submodule_lookup(&sm, fc->repo, fc->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 ((fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0 &&
+ ((sm_head = git_submodule_wd_id(sm)) != NULL ||
+ (sm_head = git_submodule_head_id(sm)) != NULL))
+ {
+ git_oid_cpy(&fc->file->oid, sm_head);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ status = "-dirty";
+ }
+
+ git_oid_tostr(oid, sizeof(oid), &fc->file->oid);
+ if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0)
+ return -1;
+
+ fc->map.len = git_buf_len(&content);
+ fc->map.data = git_buf_detach(&content);
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ return 0;
+}
+
+static int diff_file_content_load_blob(git_diff_file_content *fc)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+
+ if (git_oid_iszero(&fc->file->oid))
+ return 0;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, false);
+
+ /* if we don't know size, try to peek at object header first */
+ if (!fc->file->size) {
+ if ((error = git_diff_file__resolve_zero_size(
+ fc->file, &odb_obj, fc->repo)) < 0)
+ return error;
+ }
+
+ if (diff_file_content_binary_by_size(fc))
+ return 0;
+
+ if (odb_obj != NULL) {
+ error = git_object__from_odb_object(
+ (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJ_BLOB);
+ git_odb_object_free(odb_obj);
+ } else {
+ error = git_blob_lookup(
+ (git_blob **)&fc->blob, fc->repo, &fc->file->oid);
+ }
+
+ if (!error) {
+ fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
+ fc->map.data = (void *)git_blob_rawcontent(fc->blob);
+ fc->map.len = (size_t)git_blob_rawsize(fc->blob);
+ }
+
+ return error;
+}
+
+static int diff_file_content_load_workdir_symlink(
+ git_diff_file_content *fc, git_buf *path)
+{
+ ssize_t alloc_len, read_len;
+
+ /* link path on disk could be UTF-16, so prepare a buffer that is
+ * big enough to handle some UTF-8 data expansion
+ */
+ alloc_len = (ssize_t)(fc->file->size * 2) + 1;
+
+ fc->map.data = git__calloc(alloc_len, sizeof(char));
+ GITERR_CHECK_ALLOC(fc->map.data);
+
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len);
+ if (read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path);
+ return -1;
+ }
+
+ fc->map.len = read_len;
+ return 0;
+}
+
+static int diff_file_content_load_workdir_file(
+ git_diff_file_content *fc, git_buf *path)
+{
+ int error = 0;
+ git_vector filters = GIT_VECTOR_INIT;
+ git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
+ git_file fd = git_futils_open_ro(git_buf_cstr(path));
+
+ if (fd < 0)
+ return fd;
+
+ if (!fc->file->size &&
+ !(fc->file->size = git_futils_filesize(fd)))
+ goto cleanup;
+
+ if (diff_file_content_binary_by_size(fc))
+ goto cleanup;
+
+ if ((error = git_filters_load(
+ &filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0)
+ goto cleanup;
+ /* error >= is a filter count */
+
+ if (error == 0) {
+ if (!(error = git_futils_mmap_ro(
+ &fc->map, fd, 0, (size_t)fc->file->size)))
+ fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
+ else /* fall through to try readbuffer below */
+ giterr_clear();
+ }
+
+ if (error != 0) {
+ error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size);
+ if (error < 0)
+ goto cleanup;
+
+ if (!filters.length)
+ git_buf_swap(&filtered, &raw);
+ else
+ error = git_filters_apply(&filtered, &raw, &filters);
+
+ if (!error) {
+ fc->map.len = git_buf_len(&filtered);
+ fc->map.data = git_buf_detach(&filtered);
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ }
+
+ git_buf_free(&raw);
+ git_buf_free(&filtered);
+ }
+
+cleanup:
+ git_filters_free(&filters);
+ p_close(fd);
+
+ return error;
+}
+
+static int diff_file_content_load_workdir(git_diff_file_content *fc)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, true);
+
+ if (fc->file->mode == GIT_FILEMODE_TREE)
+ return 0;
+
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(fc->repo), fc->file->path) < 0)
+ return -1;
+
+ if (S_ISLNK(fc->file->mode))
+ error = diff_file_content_load_workdir_symlink(fc, &path);
+ else
+ error = diff_file_content_load_workdir_file(fc, &path);
+
+ /* once data is loaded, update OID if we didn't have it previously */
+ if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
+ error = git_odb_hash(
+ &fc->file->oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_diff_file_content__load(git_diff_file_content *fc)
+{
+ int error = 0;
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ return 0;
+
+ if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ return 0;
+
+ if (fc->src == GIT_ITERATOR_TYPE_WORKDIR)
+ error = diff_file_content_load_workdir(fc);
+ else
+ error = diff_file_content_load_blob(fc);
+ if (error)
+ return error;
+
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+void git_diff_file_content__unload(git_diff_file_content *fc)
+{
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0)
+ return;
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) {
+ git__free(fc->map.data);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
+ }
+ else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
+ git_futils_mmap_free(&fc->map);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
+ }
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) {
+ git_blob_free((git_blob *)fc->blob);
+ fc->blob = NULL;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB;
+ }
+
+ fc->flags &= ~GIT_DIFF_FLAG__LOADED;
+}
+
+void git_diff_file_content__clear(git_diff_file_content *fc)
+{
+ git_diff_file_content__unload(fc);
+
+ /* for now, nothing else to do */
+}
diff --git a/src/diff_file.h b/src/diff_file.h
new file mode 100644
index 000000000..fb08cca6a
--- /dev/null
+++ b/src/diff_file.h
@@ -0,0 +1,58 @@
+/*
+ * 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_diff_file_h__
+#define INCLUDE_diff_file_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_driver.h"
+#include "map.h"
+
+/* expanded information for one side of a delta */
+typedef struct {
+ git_repository *repo;
+ git_diff_file *file;
+ git_diff_driver *driver;
+ uint32_t flags;
+ uint32_t opts_flags;
+ git_off_t opts_max_size;
+ git_iterator_type_t src;
+ const git_blob *blob;
+ git_map map;
+} git_diff_file_content;
+
+extern int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff_list *diff,
+ size_t delta_index,
+ bool use_old);
+
+extern int git_diff_file_content__init_from_blob(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_blob *blob,
+ git_diff_file *as_file);
+
+extern int git_diff_file_content__init_from_raw(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const char *buf,
+ size_t buflen,
+ git_diff_file *as_file);
+
+/* this loads the blob/file-on-disk as needed */
+extern int git_diff_file_content__load(git_diff_file_content *fc);
+
+/* this releases the blob/file-in-memory */
+extern void git_diff_file_content__unload(git_diff_file_content *fc);
+
+/* this unloads and also releases any other resources */
+extern void git_diff_file_content__clear(git_diff_file_content *fc);
+
+#endif
diff --git a/src/diff_output.c b/src/diff_output.c
deleted file mode 100644
index 34a3e506c..000000000
--- a/src/diff_output.c
+++ /dev/null
@@ -1,1819 +0,0 @@
-/*
- * 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 "git2/attr.h"
-#include "git2/oid.h"
-#include "git2/submodule.h"
-#include "diff_output.h"
-#include <ctype.h>
-#include "fileops.h"
-#include "filter.h"
-#include "buf_text.h"
-
-static int read_next_int(const char **str, int *value)
-{
- const char *scan = *str;
- int v = 0, digits = 0;
- /* find next digit */
- for (scan = *str; *scan && !isdigit(*scan); scan++);
- /* parse next number */
- for (; isdigit(*scan); scan++, digits++)
- v = (v * 10) + (*scan - '0');
- *str = scan;
- *value = v;
- return (digits > 0) ? 0 : -1;
-}
-
-static int parse_hunk_header(git_diff_range *range, const char *header)
-{
- /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
- if (*header != '@')
- return -1;
- if (read_next_int(&header, &range->old_start) < 0)
- return -1;
- if (*header == ',') {
- if (read_next_int(&header, &range->old_lines) < 0)
- return -1;
- } else
- range->old_lines = 1;
- if (read_next_int(&header, &range->new_start) < 0)
- return -1;
- if (*header == ',') {
- if (read_next_int(&header, &range->new_lines) < 0)
- return -1;
- } else
- range->new_lines = 1;
- if (range->old_start < 0 || range->new_start < 0)
- return -1;
-
- return 0;
-}
-
-#define KNOWN_BINARY_FLAGS (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
-#define NOT_BINARY_FLAGS (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
-
-static int update_file_is_binary_by_attr(
- git_repository *repo, git_diff_file *file)
-{
- const char *value;
-
- /* because of blob diffs, cannot assume path is set */
- if (!file->path || !strlen(file->path))
- return 0;
-
- if (git_attr_get(&value, repo, 0, file->path, "diff") < 0)
- return -1;
-
- if (GIT_ATTR_FALSE(value))
- file->flags |= GIT_DIFF_FLAG_BINARY;
- else if (GIT_ATTR_TRUE(value))
- file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
- /* otherwise leave file->flags alone */
-
- return 0;
-}
-
-static void update_delta_is_binary(git_diff_delta *delta)
-{
- if ((delta->old_file.flags & GIT_DIFF_FLAG_BINARY) != 0 ||
- (delta->new_file.flags & GIT_DIFF_FLAG_BINARY) != 0)
- delta->flags |= GIT_DIFF_FLAG_BINARY;
-
- else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
- (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
- delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
-
- /* otherwise leave delta->flags binary value untouched */
-}
-
-/* returns if we forced binary setting (and no further checks needed) */
-static bool diff_delta_is_binary_forced(
- diff_context *ctxt,
- git_diff_delta *delta)
-{
- /* return true if binary-ness has already been settled */
- if ((delta->flags & KNOWN_BINARY_FLAGS) != 0)
- return true;
-
- /* make sure files are conceivably mmap-able */
- if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size ||
- (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size)
- {
- delta->old_file.flags |= GIT_DIFF_FLAG_BINARY;
- delta->new_file.flags |= GIT_DIFF_FLAG_BINARY;
- delta->flags |= GIT_DIFF_FLAG_BINARY;
- return true;
- }
-
- /* check if user is forcing us to text diff these files */
- if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {
- delta->old_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;
- delta->new_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;
- delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
- return true;
- }
-
- return false;
-}
-
-static int diff_delta_is_binary_by_attr(
- diff_context *ctxt, git_diff_patch *patch)
-{
- int error = 0, mirror_new;
- git_diff_delta *delta = patch->delta;
-
- if (diff_delta_is_binary_forced(ctxt, delta))
- return 0;
-
- /* check diff attribute +, -, or 0 */
- if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0)
- return -1;
-
- mirror_new = (delta->new_file.path == delta->old_file.path ||
- ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0);
- if (mirror_new)
- delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
- else
- error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);
-
- update_delta_is_binary(delta);
-
- return error;
-}
-
-static int diff_delta_is_binary_by_content(
- diff_context *ctxt,
- git_diff_delta *delta,
- git_diff_file *file,
- const git_map *map)
-{
- const git_buf search = { map->data, 0, min(map->len, 4000) };
-
- if (diff_delta_is_binary_forced(ctxt, delta))
- return 0;
-
- /* TODO: provide encoding / binary detection callbacks that can
- * be UTF-8 aware, etc. For now, instead of trying to be smart,
- * let's just use the simple NUL-byte detection that core git uses.
- */
-
- /* previously was: if (git_buf_text_is_binary(&search)) */
- if (git_buf_text_contains_nul(&search))
- file->flags |= GIT_DIFF_FLAG_BINARY;
- else
- file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
-
- update_delta_is_binary(delta);
-
- return 0;
-}
-
-static int diff_delta_is_binary_by_size(
- diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)
-{
- git_off_t threshold = MAX_DIFF_FILESIZE;
-
- if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
- return 0;
-
- if (ctxt && ctxt->opts) {
- if (ctxt->opts->max_size < 0)
- return 0;
-
- if (ctxt->opts->max_size > 0)
- threshold = ctxt->opts->max_size;
- }
-
- if (file->size > threshold)
- file->flags |= GIT_DIFF_FLAG_BINARY;
-
- update_delta_is_binary(delta);
-
- return 0;
-}
-
-static void setup_xdiff_options(
- const git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
-{
- memset(cfg, 0, sizeof(xdemitconf_t));
- memset(param, 0, sizeof(xpparam_t));
-
- cfg->ctxlen =
- (!opts) ? 3 : opts->context_lines;
- cfg->interhunkctxlen =
- (!opts) ? 0 : opts->interhunk_lines;
-
- if (!opts)
- return;
-
- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE)
- param->flags |= XDF_WHITESPACE_FLAGS;
- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
- param->flags |= XDF_IGNORE_WHITESPACE_CHANGE;
- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
- param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
-}
-
-
-static int get_blob_content(
- diff_context *ctxt,
- git_diff_delta *delta,
- git_diff_file *file,
- git_map *map,
- git_blob **blob)
-{
- int error;
- git_odb_object *odb_obj = NULL;
-
- if (git_oid_iszero(&file->oid))
- return 0;
-
- if (file->mode == GIT_FILEMODE_COMMIT)
- {
- char oidstr[GIT_OID_HEXSZ+1];
- git_buf content = GIT_BUF_INIT;
-
- git_oid_fmt(oidstr, &file->oid);
- oidstr[GIT_OID_HEXSZ] = 0;
- git_buf_printf(&content, "Subproject commit %s\n", oidstr );
-
- map->data = git_buf_detach(&content);
- map->len = strlen(map->data);
-
- file->flags |= GIT_DIFF_FLAG__FREE_DATA;
- return 0;
- }
-
- if (!file->size) {
- git_odb *odb;
- size_t len;
- git_otype type;
-
- /* peek at object header to avoid loading if too large */
- if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 ||
- (error = git_odb__read_header_or_object(
- &odb_obj, &len, &type, odb, &file->oid)) < 0)
- return error;
-
- assert(type == GIT_OBJ_BLOB);
-
- file->size = len;
- }
-
- /* if blob is too large to diff, mark as binary */
- if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)
- return error;
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- return 0;
-
- if (odb_obj != NULL) {
- error = git_object__from_odb_object(
- (git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB);
- git_odb_object_free(odb_obj);
- } else
- error = git_blob_lookup(blob, ctxt->repo, &file->oid);
-
- if (error)
- return error;
-
- map->data = (void *)git_blob_rawcontent(*blob);
- map->len = (size_t)git_blob_rawsize(*blob);
-
- return diff_delta_is_binary_by_content(ctxt, delta, file, map);
-}
-
-static int get_workdir_sm_content(
- diff_context *ctxt,
- git_diff_file *file,
- git_map *map)
-{
- int error = 0;
- git_buf content = GIT_BUF_INIT;
- git_submodule* sm = NULL;
- unsigned int sm_status = 0;
- const char* sm_status_text = "";
- char oidstr[GIT_OID_HEXSZ+1];
-
- 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) {
- const git_oid* sm_head;
-
- if ((sm_head = git_submodule_wd_id(sm)) != NULL ||
- (sm_head = git_submodule_head_id(sm)) != NULL)
- {
- git_oid_cpy(&file->oid, sm_head);
- file->flags |= GIT_DIFF_FLAG_VALID_OID;
- }
- }
-
- git_oid_fmt(oidstr, &file->oid);
- oidstr[GIT_OID_HEXSZ] = '\0';
-
- if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
- sm_status_text = "-dirty";
-
- git_buf_printf(&content, "Subproject commit %s%s\n",
- oidstr, sm_status_text);
-
- map->data = git_buf_detach(&content);
- map->len = strlen(map->data);
-
- file->flags |= GIT_DIFF_FLAG__FREE_DATA;
-
- 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,
- git_diff_file *file,
- git_map *map)
-{
- int error = 0;
- git_buf path = GIT_BUF_INIT;
- const char *wd = git_repository_workdir(ctxt->repo);
-
- if (S_ISGITLINK(file->mode))
- return get_workdir_sm_content(ctxt, file, map);
-
- if (S_ISDIR(file->mode))
- return 0;
-
- if (git_buf_joinpath(&path, wd, file->path) < 0)
- return -1;
-
- if (S_ISLNK(file->mode)) {
- ssize_t alloc_len, read_len;
-
- file->flags |= GIT_DIFF_FLAG__FREE_DATA;
- file->flags |= GIT_DIFF_FLAG_BINARY;
-
- /* link path on disk could be UTF-16, so prepare a buffer that is
- * big enough to handle some UTF-8 data expansion
- */
- alloc_len = (ssize_t)(file->size * 2) + 1;
-
- map->data = git__malloc(alloc_len);
- GITERR_CHECK_ALLOC(map->data);
-
- 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;
- goto cleanup;
- }
-
- map->len = read_len;
- }
- else {
- git_file fd = git_futils_open_ro(path.ptr);
- git_vector filters = GIT_VECTOR_INIT;
-
- if (fd < 0) {
- error = fd;
- goto cleanup;
- }
-
- 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)
- goto close_and_cleanup;
-
- error = git_filters_load(
- &filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB);
- if (error < 0)
- goto close_and_cleanup;
-
- if (error == 0) { /* note: git_filters_load returns filter count */
- error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size);
- 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);
- p_close(fd);
- }
-
- /* once data is loaded, update OID if we didn't have it previously */
- if (!error && (file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
- error = git_odb_hash(
- &file->oid, map->data, map->len, GIT_OBJ_BLOB);
- if (!error)
- file->flags |= GIT_DIFF_FLAG_VALID_OID;
- }
-
- if (!error)
- error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
-
-cleanup:
- git_buf_free(&path);
- return error;
-}
-
-static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
-{
- if (blob != NULL)
- git_blob_free(blob);
-
- if (file->flags & GIT_DIFF_FLAG__FREE_DATA) {
- git__free(map->data);
- map->data = "";
- map->len = 0;
- file->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
- }
- else if (file->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
- git_futils_mmap_free(map);
- map->data = "";
- map->len = 0;
- file->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
- }
-}
-
-
-static int diff_context_init(
- diff_context *ctxt,
- git_diff_list *diff,
- git_repository *repo,
- const git_diff_options *opts,
- git_diff_file_cb file_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_data_cb data_cb,
- void *payload)
-{
- memset(ctxt, 0, sizeof(diff_context));
-
- if (!repo && diff)
- repo = diff->repo;
-
- if (!opts && diff)
- opts = &diff->opts;
-
- ctxt->repo = repo;
- ctxt->diff = diff;
- ctxt->opts = opts;
- ctxt->file_cb = file_cb;
- ctxt->hunk_cb = hunk_cb;
- ctxt->data_cb = data_cb;
- ctxt->payload = payload;
- ctxt->error = 0;
-
- setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
-
- return 0;
-}
-
-static int diff_delta_file_callback(
- diff_context *ctxt, git_diff_delta *delta, size_t idx)
-{
- float progress;
-
- if (!ctxt->file_cb)
- return 0;
-
- progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;
-
- if (ctxt->file_cb(delta, progress, ctxt->payload) != 0)
- ctxt->error = GIT_EUSER;
-
- return ctxt->error;
-}
-
-static void diff_patch_init(
- diff_context *ctxt, git_diff_patch *patch)
-{
- memset(patch, 0, sizeof(git_diff_patch));
-
- patch->diff = ctxt->diff;
- patch->ctxt = ctxt;
-
- if (patch->diff) {
- patch->old_src = patch->diff->old_src;
- patch->new_src = patch->diff->new_src;
- } else {
- patch->old_src = patch->new_src = GIT_ITERATOR_TYPE_TREE;
- }
-}
-
-static git_diff_patch *diff_patch_alloc(
- diff_context *ctxt, git_diff_delta *delta)
-{
- git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));
- if (!patch)
- return NULL;
-
- diff_patch_init(ctxt, patch);
-
- git_diff_list_addref(patch->diff);
-
- GIT_REFCOUNT_INC(patch);
-
- patch->delta = delta;
- patch->flags = GIT_DIFF_PATCH_ALLOCATED;
-
- return patch;
-}
-
-static int diff_patch_load(
- diff_context *ctxt, git_diff_patch *patch)
-{
- int error = 0;
- git_diff_delta *delta = patch->delta;
- bool check_if_unmodified = false;
-
- if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
- return 0;
-
- error = diff_delta_is_binary_by_attr(ctxt, patch);
-
- patch->old_data.data = "";
- patch->old_data.len = 0;
- patch->old_blob = NULL;
-
- patch->new_data.data = "";
- patch->new_data.len = 0;
- patch->new_blob = NULL;
-
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- goto cleanup;
-
- if (!ctxt->hunk_cb &&
- !ctxt->data_cb &&
- (ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)
- goto cleanup;
-
- switch (delta->status) {
- case GIT_DELTA_ADDED:
- delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- break;
- case GIT_DELTA_DELETED:
- delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- break;
- case GIT_DELTA_MODIFIED:
- break;
- case GIT_DELTA_UNTRACKED:
- delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)
- delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- break;
- default:
- delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
- break;
- }
-
-#define CHECK_UNMODIFIED (GIT_DIFF_FLAG__NO_DATA | GIT_DIFF_FLAG_VALID_OID)
-
- check_if_unmodified =
- (delta->old_file.flags & CHECK_UNMODIFIED) == 0 &&
- (delta->new_file.flags & CHECK_UNMODIFIED) == 0;
-
- /* Always try to load workdir content first, since it may need to be
- * filtered (and hence use 2x memory) and we want to minimize the max
- * memory footprint during diff.
- */
-
- if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
- patch->old_src == GIT_ITERATOR_TYPE_WORKDIR) {
- if ((error = get_workdir_content(
- ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
- goto cleanup;
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- goto cleanup;
- }
-
- if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
- patch->new_src == GIT_ITERATOR_TYPE_WORKDIR) {
- if ((error = get_workdir_content(
- ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
- goto cleanup;
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- goto cleanup;
- }
-
- if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
- patch->old_src != GIT_ITERATOR_TYPE_WORKDIR) {
- if ((error = get_blob_content(
- ctxt, delta, &delta->old_file,
- &patch->old_data, &patch->old_blob)) < 0)
- goto cleanup;
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- goto cleanup;
- }
-
- if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
- patch->new_src != GIT_ITERATOR_TYPE_WORKDIR) {
- if ((error = get_blob_content(
- ctxt, delta, &delta->new_file,
- &patch->new_data, &patch->new_blob)) < 0)
- goto cleanup;
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
- goto cleanup;
- }
-
- /* if we did not previously have the definitive oid, we may have
- * incorrect status and need to switch this to UNMODIFIED.
- */
- if (check_if_unmodified &&
- delta->old_file.mode == delta->new_file.mode &&
- !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
- {
- delta->status = GIT_DELTA_UNMODIFIED;
-
- if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
- goto cleanup;
- }
-
-cleanup:
- if ((delta->flags & KNOWN_BINARY_FLAGS) == 0)
- update_delta_is_binary(delta);
-
- if (!error) {
- patch->flags |= GIT_DIFF_PATCH_LOADED;
-
- /* patch is diffable only for non-binary, modified files where at
- * least one side has data and there is actual change in the data
- */
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
- delta->status != GIT_DELTA_UNMODIFIED &&
- (patch->old_data.len || patch->new_data.len) &&
- (patch->old_data.len != patch->new_data.len ||
- !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid)))
- patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
- }
-
- return error;
-}
-
-static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)
-{
- git_diff_patch *patch = priv;
- diff_context *ctxt = patch->ctxt;
-
- if (len == 1) {
- ctxt->error = parse_hunk_header(&ctxt->range, bufs[0].ptr);
- if (ctxt->error < 0)
- return ctxt->error;
-
- if (ctxt->hunk_cb != NULL &&
- ctxt->hunk_cb(patch->delta, &ctxt->range,
- bufs[0].ptr, bufs[0].size, ctxt->payload))
- ctxt->error = GIT_EUSER;
- }
-
- if (len == 2 || len == 3) {
- /* expect " "/"-"/"+", then data */
- char origin =
- (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
- (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
- GIT_DIFF_LINE_CONTEXT;
-
- if (ctxt->data_cb != NULL &&
- ctxt->data_cb(patch->delta, &ctxt->range,
- origin, bufs[1].ptr, bufs[1].size, ctxt->payload))
- ctxt->error = GIT_EUSER;
- }
-
- if (len == 3 && !ctxt->error) {
- /* If we have a '+' and a third buf, then we have added a line
- * without a newline and the old code had one, so DEL_EOFNL.
- * If we have a '-' and a third buf, then we have removed a line
- * with out a newline but added a blank line, so ADD_EOFNL.
- */
- char origin =
- (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
- (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
- GIT_DIFF_LINE_CONTEXT;
-
- if (ctxt->data_cb != NULL &&
- ctxt->data_cb(patch->delta, &ctxt->range,
- origin, bufs[2].ptr, bufs[2].size, ctxt->payload))
- ctxt->error = GIT_EUSER;
- }
-
- return ctxt->error;
-}
-
-static int diff_patch_generate(
- diff_context *ctxt, git_diff_patch *patch)
-{
- int error = 0;
- xdemitcb_t xdiff_callback;
- mmfile_t old_xdiff_data, new_xdiff_data;
-
- if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
- return 0;
-
- if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
- if ((error = diff_patch_load(ctxt, patch)) < 0)
- return error;
-
- if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
- return 0;
-
- if (!ctxt->file_cb && !ctxt->hunk_cb)
- return 0;
-
- patch->ctxt = ctxt;
-
- memset(&xdiff_callback, 0, sizeof(xdiff_callback));
- xdiff_callback.outf = diff_patch_cb;
- xdiff_callback.priv = patch;
-
- old_xdiff_data.ptr = patch->old_data.data;
- old_xdiff_data.size = patch->old_data.len;
- new_xdiff_data.ptr = patch->new_data.data;
- new_xdiff_data.size = patch->new_data.len;
-
- xdl_diff(&old_xdiff_data, &new_xdiff_data,
- &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);
-
- error = ctxt->error;
-
- if (!error)
- patch->flags |= GIT_DIFF_PATCH_DIFFED;
-
- return error;
-}
-
-static void diff_patch_unload(git_diff_patch *patch)
-{
- if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {
- patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);
-
- patch->hunks_size = 0;
- patch->lines_size = 0;
- }
-
- if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) {
- patch->flags = (patch->flags & ~GIT_DIFF_PATCH_LOADED);
-
- release_content(
- &patch->delta->old_file, &patch->old_data, patch->old_blob);
- release_content(
- &patch->delta->new_file, &patch->new_data, patch->new_blob);
- }
-}
-
-static void diff_patch_free(git_diff_patch *patch)
-{
- diff_patch_unload(patch);
-
- git__free(patch->lines);
- patch->lines = NULL;
- patch->lines_asize = 0;
-
- git__free(patch->hunks);
- patch->hunks = NULL;
- patch->hunks_asize = 0;
-
- if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))
- return;
-
- patch->flags = 0;
-
- git_diff_list_free(patch->diff); /* decrements refcount */
-
- git__free(patch);
-}
-
-#define MAX_HUNK_STEP 128
-#define MIN_HUNK_STEP 8
-#define MAX_LINE_STEP 256
-#define MIN_LINE_STEP 8
-
-static int diff_patch_hunk_cb(
- const git_diff_delta *delta,
- const git_diff_range *range,
- const char *header,
- size_t header_len,
- void *payload)
-{
- git_diff_patch *patch = payload;
- diff_patch_hunk *hunk;
-
- GIT_UNUSED(delta);
-
- if (patch->hunks_size >= patch->hunks_asize) {
- size_t new_size;
- diff_patch_hunk *new_hunks;
-
- if (patch->hunks_asize > MAX_HUNK_STEP)
- new_size = patch->hunks_asize + MAX_HUNK_STEP;
- else
- new_size = patch->hunks_asize * 2;
- if (new_size < MIN_HUNK_STEP)
- new_size = MIN_HUNK_STEP;
-
- new_hunks = git__realloc(
- patch->hunks, new_size * sizeof(diff_patch_hunk));
- if (!new_hunks)
- return -1;
-
- patch->hunks = new_hunks;
- patch->hunks_asize = new_size;
- }
-
- hunk = &patch->hunks[patch->hunks_size++];
-
- memcpy(&hunk->range, range, sizeof(hunk->range));
-
- assert(header_len + 1 < sizeof(hunk->header));
- memcpy(&hunk->header, header, header_len);
- hunk->header[header_len] = '\0';
- hunk->header_len = header_len;
-
- hunk->line_start = patch->lines_size;
- hunk->line_count = 0;
-
- patch->oldno = range->old_start;
- patch->newno = range->new_start;
-
- return 0;
-}
-
-static int diff_patch_line_cb(
- const git_diff_delta *delta,
- const git_diff_range *range,
- char line_origin,
- const char *content,
- size_t content_len,
- void *payload)
-{
- git_diff_patch *patch = payload;
- diff_patch_hunk *hunk;
- diff_patch_line *line;
-
- GIT_UNUSED(delta);
- GIT_UNUSED(range);
-
- assert(patch->hunks_size > 0);
- assert(patch->hunks != NULL);
-
- hunk = &patch->hunks[patch->hunks_size - 1];
-
- if (patch->lines_size >= patch->lines_asize) {
- size_t new_size;
- diff_patch_line *new_lines;
-
- if (patch->lines_asize > MAX_LINE_STEP)
- new_size = patch->lines_asize + MAX_LINE_STEP;
- else
- new_size = patch->lines_asize * 2;
- if (new_size < MIN_LINE_STEP)
- new_size = MIN_LINE_STEP;
-
- new_lines = git__realloc(
- patch->lines, new_size * sizeof(diff_patch_line));
- if (!new_lines)
- return -1;
-
- patch->lines = new_lines;
- patch->lines_asize = new_size;
- }
-
- line = &patch->lines[patch->lines_size++];
-
- line->ptr = content;
- line->len = content_len;
- line->origin = line_origin;
-
- /* do some bookkeeping so we can provide old/new line numbers */
-
- for (line->lines = 0; content_len > 0; --content_len) {
- if (*content++ == '\n')
- ++line->lines;
- }
-
- switch (line_origin) {
- case GIT_DIFF_LINE_ADDITION:
- line->oldno = -1;
- line->newno = patch->newno;
- patch->newno += line->lines;
- break;
- case GIT_DIFF_LINE_DELETION:
- line->oldno = patch->oldno;
- line->newno = -1;
- patch->oldno += line->lines;
- break;
- default:
- line->oldno = patch->oldno;
- line->newno = patch->newno;
- patch->oldno += line->lines;
- patch->newno += line->lines;
- break;
- }
-
- hunk->line_count++;
-
- return 0;
-}
-
-static int diff_required(git_diff_list *diff, const char *action)
-{
- if (!diff) {
- giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
- return -1;
- }
-
- return 0;
-}
-
-int git_diff_foreach(
- git_diff_list *diff,
- git_diff_file_cb file_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_data_cb data_cb,
- void *payload)
-{
- int error = 0;
- diff_context ctxt;
- size_t idx;
- git_diff_patch patch;
-
- if (diff_required(diff, "git_diff_foreach") < 0)
- return -1;
-
- if (diff_context_init(
- &ctxt, diff, NULL, NULL, file_cb, hunk_cb, data_cb, payload) < 0)
- return -1;
-
- diff_patch_init(&ctxt, &patch);
-
- git_vector_foreach(&diff->deltas, idx, patch.delta) {
-
- /* check flags against patch status */
- if (git_diff_delta__should_skip(ctxt.opts, patch.delta))
- continue;
-
- if (!(error = diff_patch_load(&ctxt, &patch))) {
-
- /* invoke file callback */
- error = diff_delta_file_callback(&ctxt, patch.delta, idx);
-
- /* generate diffs and invoke hunk and line callbacks */
- if (!error)
- error = diff_patch_generate(&ctxt, &patch);
-
- diff_patch_unload(&patch);
- }
-
- if (error < 0)
- break;
- }
-
- if (error == GIT_EUSER)
- giterr_clear(); /* don't let error message leak */
-
- return error;
-}
-
-
-typedef struct {
- git_diff_list *diff;
- git_diff_data_cb print_cb;
- void *payload;
- git_buf *buf;
-} diff_print_info;
-
-static char pick_suffix(int mode)
-{
- if (S_ISDIR(mode))
- return '/';
- else if (mode & 0100) //-V536
- /* in git, modes are very regular, so we must have 0100755 mode */
- return '*';
- else
- return ' ';
-}
-
-char git_diff_status_char(git_delta_t status)
-{
- char code;
-
- switch (status) {
- case GIT_DELTA_ADDED: code = 'A'; break;
- case GIT_DELTA_DELETED: code = 'D'; break;
- case GIT_DELTA_MODIFIED: code = 'M'; break;
- case GIT_DELTA_RENAMED: code = 'R'; break;
- case GIT_DELTA_COPIED: code = 'C'; break;
- case GIT_DELTA_IGNORED: code = 'I'; break;
- case GIT_DELTA_UNTRACKED: code = '?'; break;
- default: code = ' '; break;
- }
-
- return code;
-}
-
-static int print_compact(
- const git_diff_delta *delta, float progress, void *data)
-{
- diff_print_info *pi = data;
- char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
-
- GIT_UNUSED(progress);
-
- if (code == ' ')
- return 0;
-
- old_suffix = pick_suffix(delta->old_file.mode);
- new_suffix = pick_suffix(delta->new_file.mode);
-
- git_buf_clear(pi->buf);
-
- if (delta->old_file.path != delta->new_file.path &&
- pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)
- git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
- delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
- else if (delta->old_file.mode != delta->new_file.mode &&
- delta->old_file.mode != 0 && delta->new_file.mode != 0)
- git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
- delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);
- else if (old_suffix != ' ')
- git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
- else
- git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path);
-
- if (git_buf_oom(pi->buf))
- return -1;
-
- if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
- {
- giterr_clear();
- return GIT_EUSER;
- }
-
- return 0;
-}
-
-int git_diff_print_compact(
- git_diff_list *diff,
- git_diff_data_cb print_cb,
- void *payload)
-{
- int error;
- git_buf buf = GIT_BUF_INIT;
- diff_print_info pi;
-
- pi.diff = diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &buf;
-
- error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
-
- git_buf_free(&buf);
-
- return error;
-}
-
-static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
-{
- char start_oid[8], end_oid[8];
-
- /* TODO: Determine a good actual OID range to print */
- git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid);
- git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid);
-
- /* TODO: Match git diff more closely */
- if (delta->old_file.mode == delta->new_file.mode) {
- git_buf_printf(pi->buf, "index %s..%s %o\n",
- start_oid, end_oid, delta->old_file.mode);
- } else {
- if (delta->old_file.mode == 0) {
- git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode);
- } else if (delta->new_file.mode == 0) {
- git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode);
- } else {
- git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode);
- git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode);
- }
- git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);
- }
-
- if (git_buf_oom(pi->buf))
- return -1;
-
- return 0;
-}
-
-static int print_patch_file(
- const git_diff_delta *delta, float progress, void *data)
-{
- diff_print_info *pi = data;
- const char *oldpfx = pi->diff->opts.old_prefix;
- const char *oldpath = delta->old_file.path;
- const char *newpfx = pi->diff->opts.new_prefix;
- const char *newpath = delta->new_file.path;
-
- GIT_UNUSED(progress);
-
- if (S_ISDIR(delta->new_file.mode) ||
- delta->status == GIT_DELTA_UNMODIFIED ||
- delta->status == GIT_DELTA_IGNORED ||
- (delta->status == GIT_DELTA_UNTRACKED &&
- (pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
- return 0;
-
- if (!oldpfx)
- oldpfx = DIFF_OLD_PREFIX_DEFAULT;
-
- if (!newpfx)
- newpfx = DIFF_NEW_PREFIX_DEFAULT;
-
- git_buf_clear(pi->buf);
- git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
-
- if (print_oid_range(pi, delta) < 0)
- return -1;
-
- if (git_oid_iszero(&delta->old_file.oid)) {
- oldpfx = "";
- oldpath = "/dev/null";
- }
- if (git_oid_iszero(&delta->new_file.oid)) {
- newpfx = "";
- newpath = "/dev/null";
- }
-
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) {
- git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
- git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
- }
-
- if (git_buf_oom(pi->buf))
- return -1;
-
- if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
- {
- giterr_clear();
- return GIT_EUSER;
- }
-
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
- return 0;
-
- git_buf_clear(pi->buf);
- git_buf_printf(
- pi->buf, "Binary files %s%s and %s%s differ\n",
- oldpfx, oldpath, newpfx, newpath);
- if (git_buf_oom(pi->buf))
- return -1;
-
- if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
- {
- giterr_clear();
- return GIT_EUSER;
- }
-
- return 0;
-}
-
-static int print_patch_hunk(
- const git_diff_delta *d,
- const git_diff_range *r,
- const char *header,
- size_t header_len,
- void *data)
-{
- diff_print_info *pi = data;
-
- if (S_ISDIR(d->new_file.mode))
- return 0;
-
- git_buf_clear(pi->buf);
- if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
- return -1;
-
- if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
- {
- giterr_clear();
- return GIT_EUSER;
- }
-
- return 0;
-}
-
-static int print_patch_line(
- const git_diff_delta *delta,
- const git_diff_range *range,
- char line_origin, /* GIT_DIFF_LINE value from above */
- const char *content,
- size_t content_len,
- void *data)
-{
- diff_print_info *pi = data;
-
- if (S_ISDIR(delta->new_file.mode))
- return 0;
-
- git_buf_clear(pi->buf);
-
- if (line_origin == GIT_DIFF_LINE_ADDITION ||
- line_origin == GIT_DIFF_LINE_DELETION ||
- line_origin == GIT_DIFF_LINE_CONTEXT)
- git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);
- else if (content_len > 0)
- git_buf_printf(pi->buf, "%.*s", (int)content_len, content);
-
- if (git_buf_oom(pi->buf))
- return -1;
-
- if (pi->print_cb(delta, range, line_origin,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
- {
- giterr_clear();
- return GIT_EUSER;
- }
-
- return 0;
-}
-
-int git_diff_print_patch(
- git_diff_list *diff,
- git_diff_data_cb print_cb,
- void *payload)
-{
- int error;
- git_buf buf = GIT_BUF_INIT;
- diff_print_info pi;
-
- pi.diff = diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &buf;
-
- error = git_diff_foreach(
- diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
-
- git_buf_free(&buf);
-
- return error;
-}
-
-static void set_data_from_blob(
- const git_blob *blob, git_map *map, git_diff_file *file)
-{
- if (blob) {
- file->size = git_blob_rawsize(blob);
- git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
- file->mode = 0644;
-
- map->len = (size_t)file->size;
- map->data = (char *)git_blob_rawcontent(blob);
- } else {
- file->size = 0;
- file->flags |= GIT_DIFF_FLAG__NO_DATA;
-
- map->len = 0;
- map->data = "";
- }
-}
-
-static void set_data_from_buffer(
- const char *buffer, size_t buffer_len, git_map *map, git_diff_file *file)
-{
- file->size = (git_off_t)buffer_len;
- file->mode = 0644;
- map->len = buffer_len;
-
- if (!buffer) {
- file->flags |= GIT_DIFF_FLAG__NO_DATA;
- map->data = NULL;
- } else {
- map->data = (char *)buffer;
- git_odb_hash(&file->oid, buffer, buffer_len, GIT_OBJ_BLOB);
- }
-}
-
-typedef struct {
- diff_context ctxt;
- git_diff_delta delta;
- git_diff_patch patch;
-} diff_single_data;
-
-static int diff_single_init(
- diff_single_data *data,
- git_repository *repo,
- const git_diff_options *opts,
- git_diff_file_cb file_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_data_cb data_cb,
- void *payload)
-{
- GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
-
- memset(data, 0, sizeof(*data));
-
- if (diff_context_init(
- &data->ctxt, NULL, repo, opts,
- file_cb, hunk_cb, data_cb, payload) < 0)
- return -1;
-
- diff_patch_init(&data->ctxt, &data->patch);
-
- return 0;
-}
-
-static int diff_single_apply(diff_single_data *data)
-{
- int error;
- git_diff_delta *delta = &data->delta;
- bool has_old = ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
- bool has_new = ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
-
- /* finish setting up fake git_diff_delta record and loaded data */
-
- data->patch.delta = delta;
- delta->flags = delta->flags & ~KNOWN_BINARY_FLAGS;
-
- delta->status = has_new ?
- (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
- (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
-
- if (git_oid_cmp(&delta->new_file.oid, &delta->old_file.oid) == 0)
- delta->status = GIT_DELTA_UNMODIFIED;
-
- if ((error = diff_delta_is_binary_by_content(
- &data->ctxt, delta, &delta->old_file, &data->patch.old_data)) < 0 ||
- (error = diff_delta_is_binary_by_content(
- &data->ctxt, delta, &delta->new_file, &data->patch.new_data)) < 0)
- goto cleanup;
-
- data->patch.flags |= GIT_DIFF_PATCH_LOADED;
-
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
- delta->status != GIT_DELTA_UNMODIFIED)
- data->patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
-
- /* do diffs */
-
- if (!(error = diff_delta_file_callback(&data->ctxt, delta, 1)))
- error = diff_patch_generate(&data->ctxt, &data->patch);
-
-cleanup:
- if (error == GIT_EUSER)
- giterr_clear();
-
- diff_patch_unload(&data->patch);
-
- return error;
-}
-
-int git_diff_blobs(
- const git_blob *old_blob,
- const git_blob *new_blob,
- const git_diff_options *options,
- git_diff_file_cb file_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_data_cb data_cb,
- void *payload)
-{
- int error;
- diff_single_data d;
- git_repository *repo =
- new_blob ? git_object_owner((const git_object *)new_blob) :
- old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
-
- if (!repo) /* Hmm, given two NULL blobs, silently do no callbacks? */
- return 0;
-
- if ((error = diff_single_init(
- &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
- return error;
-
- if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
- const git_blob *swap = old_blob;
- old_blob = new_blob;
- new_blob = swap;
- }
-
- set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
- set_data_from_blob(new_blob, &d.patch.new_data, &d.delta.new_file);
-
- return diff_single_apply(&d);
-}
-
-int git_diff_blob_to_buffer(
- const git_blob *old_blob,
- const char *buf,
- size_t buflen,
- const git_diff_options *options,
- git_diff_file_cb file_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_data_cb data_cb,
- void *payload)
-{
- int error;
- diff_single_data d;
- git_repository *repo =
- old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
-
- if (!repo && !buf) /* Hmm, given NULLs, silently do no callbacks? */
- return 0;
-
- if ((error = diff_single_init(
- &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
- return error;
-
- if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
- set_data_from_buffer(buf, buflen, &d.patch.old_data, &d.delta.old_file);
- set_data_from_blob(old_blob, &d.patch.new_data, &d.delta.new_file);
- } else {
- set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
- set_data_from_buffer(buf, buflen, &d.patch.new_data, &d.delta.new_file);
- }
-
- return diff_single_apply(&d);
-}
-
-size_t git_diff_num_deltas(git_diff_list *diff)
-{
- assert(diff);
- return (size_t)diff->deltas.length;
-}
-
-size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
-{
- size_t i, count = 0;
- git_diff_delta *delta;
-
- assert(diff);
-
- git_vector_foreach(&diff->deltas, i, delta) {
- count += (delta->status == type);
- }
-
- return count;
-}
-
-int git_diff_get_patch(
- git_diff_patch **patch_ptr,
- const git_diff_delta **delta_ptr,
- git_diff_list *diff,
- size_t idx)
-{
- int error;
- diff_context ctxt;
- git_diff_delta *delta;
- git_diff_patch *patch;
-
- if (patch_ptr)
- *patch_ptr = NULL;
- if (delta_ptr)
- *delta_ptr = NULL;
-
- if (diff_required(diff, "git_diff_get_patch") < 0)
- return -1;
-
- if (diff_context_init(
- &ctxt, diff, NULL, NULL,
- NULL, diff_patch_hunk_cb, diff_patch_line_cb, NULL) < 0)
- return -1;
-
- delta = git_vector_get(&diff->deltas, idx);
- if (!delta) {
- giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
- return GIT_ENOTFOUND;
- }
-
- if (delta_ptr)
- *delta_ptr = delta;
-
- if (!patch_ptr &&
- ((delta->flags & KNOWN_BINARY_FLAGS) != 0 ||
- (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
- return 0;
-
- if (git_diff_delta__should_skip(ctxt.opts, delta))
- return 0;
-
- /* Don't load the patch if the user doesn't want it */
- if (!patch_ptr)
- return 0;
-
- patch = diff_patch_alloc(&ctxt, delta);
- if (!patch)
- return -1;
-
- if (!(error = diff_patch_load(&ctxt, patch))) {
- ctxt.payload = patch;
-
- error = diff_patch_generate(&ctxt, patch);
-
- if (error == GIT_EUSER)
- error = ctxt.error;
- }
-
- if (error)
- git_diff_patch_free(patch);
- else if (patch_ptr)
- *patch_ptr = patch;
-
- return error;
-}
-
-void git_diff_patch_free(git_diff_patch *patch)
-{
- if (patch)
- GIT_REFCOUNT_DEC(patch, diff_patch_free);
-}
-
-const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
-{
- assert(patch);
- return patch->delta;
-}
-
-size_t git_diff_patch_num_hunks(git_diff_patch *patch)
-{
- assert(patch);
- return patch->hunks_size;
-}
-
-int git_diff_patch_line_stats(
- size_t *total_ctxt,
- size_t *total_adds,
- size_t *total_dels,
- const git_diff_patch *patch)
-{
- size_t totals[3], idx;
-
- memset(totals, 0, sizeof(totals));
-
- for (idx = 0; idx < patch->lines_size; ++idx) {
- switch (patch->lines[idx].origin) {
- case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
- case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
- case GIT_DIFF_LINE_DELETION: totals[2]++; break;
- default:
- /* diff --stat and --numstat don't count EOFNL marks because
- * they will always be paired with a ADDITION or DELETION line.
- */
- break;
- }
- }
-
- if (total_ctxt)
- *total_ctxt = totals[0];
- if (total_adds)
- *total_adds = totals[1];
- if (total_dels)
- *total_dels = totals[2];
-
- return 0;
-}
-
-int git_diff_patch_get_hunk(
- const git_diff_range **range,
- const char **header,
- size_t *header_len,
- size_t *lines_in_hunk,
- git_diff_patch *patch,
- size_t hunk_idx)
-{
- diff_patch_hunk *hunk;
-
- assert(patch);
-
- if (hunk_idx >= patch->hunks_size) {
- if (range) *range = NULL;
- if (header) *header = NULL;
- if (header_len) *header_len = 0;
- if (lines_in_hunk) *lines_in_hunk = 0;
- return GIT_ENOTFOUND;
- }
-
- hunk = &patch->hunks[hunk_idx];
-
- if (range) *range = &hunk->range;
- if (header) *header = hunk->header;
- if (header_len) *header_len = hunk->header_len;
- if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
-
- return 0;
-}
-
-int git_diff_patch_num_lines_in_hunk(
- git_diff_patch *patch,
- size_t hunk_idx)
-{
- assert(patch);
-
- if (hunk_idx >= patch->hunks_size)
- return GIT_ENOTFOUND;
- else
- return (int)patch->hunks[hunk_idx].line_count;
-}
-
-int git_diff_patch_get_line_in_hunk(
- char *line_origin,
- const char **content,
- size_t *content_len,
- int *old_lineno,
- int *new_lineno,
- git_diff_patch *patch,
- size_t hunk_idx,
- size_t line_of_hunk)
-{
- diff_patch_hunk *hunk;
- diff_patch_line *line;
-
- assert(patch);
-
- if (hunk_idx >= patch->hunks_size)
- goto notfound;
- hunk = &patch->hunks[hunk_idx];
-
- if (line_of_hunk >= hunk->line_count)
- goto notfound;
-
- line = &patch->lines[hunk->line_start + line_of_hunk];
-
- if (line_origin) *line_origin = line->origin;
- if (content) *content = line->ptr;
- if (content_len) *content_len = line->len;
- if (old_lineno) *old_lineno = (int)line->oldno;
- if (new_lineno) *new_lineno = (int)line->newno;
-
- return 0;
-
-notfound:
- if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
- if (content) *content = NULL;
- if (content_len) *content_len = 0;
- if (old_lineno) *old_lineno = -1;
- if (new_lineno) *new_lineno = -1;
-
- return GIT_ENOTFOUND;
-}
-
-static int print_to_buffer_cb(
- const git_diff_delta *delta,
- const git_diff_range *range,
- char line_origin,
- const char *content,
- size_t content_len,
- void *payload)
-{
- git_buf *output = payload;
- GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
- return git_buf_put(output, content, content_len);
-}
-
-int git_diff_patch_print(
- git_diff_patch *patch,
- git_diff_data_cb print_cb,
- void *payload)
-{
- int error;
- git_buf temp = GIT_BUF_INIT;
- diff_print_info pi;
- size_t h, l;
-
- assert(patch && print_cb);
-
- pi.diff = patch->diff;
- pi.print_cb = print_cb;
- pi.payload = payload;
- pi.buf = &temp;
-
- error = print_patch_file(patch->delta, 0, &pi);
-
- for (h = 0; h < patch->hunks_size && !error; ++h) {
- diff_patch_hunk *hunk = &patch->hunks[h];
-
- error = print_patch_hunk(
- patch->delta, &hunk->range, hunk->header, hunk->header_len, &pi);
-
- for (l = 0; l < hunk->line_count && !error; ++l) {
- diff_patch_line *line = &patch->lines[hunk->line_start + l];
-
- error = print_patch_line(
- patch->delta, &hunk->range,
- line->origin, line->ptr, line->len, &pi);
- }
- }
-
- git_buf_free(&temp);
-
- return error;
-}
-
-int git_diff_patch_to_str(
- char **string,
- git_diff_patch *patch)
-{
- int error;
- git_buf output = GIT_BUF_INIT;
-
- error = git_diff_patch_print(patch, print_to_buffer_cb, &output);
-
- /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
- * meaning a memory allocation failure, so just map to -1...
- */
- if (error == GIT_EUSER)
- error = -1;
-
- *string = git_buf_detach(&output);
-
- return error;
-}
-
-int git_diff__paired_foreach(
- git_diff_list *idx2head,
- git_diff_list *wd2idx,
- int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
- void *payload)
-{
- int cmp;
- git_diff_delta *i2h, *w2i;
- size_t i, j, i_max, j_max;
- int (*strcomp)(const char *, const char *);
-
- i_max = idx2head ? idx2head->deltas.length : 0;
- j_max = wd2idx ? wd2idx->deltas.length : 0;
-
- /* Get appropriate strcmp function */
- strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL;
-
- /* Assert both iterators use matching ignore-case. If this function ever
- * supports merging diffs that are not sorted by the same function, then
- * it will need to spool and sort on one of the results before merging
- */
- if (idx2head && wd2idx) {
- assert(idx2head->strcomp == wd2idx->strcomp);
- }
-
- for (i = 0, j = 0; i < i_max || j < j_max; ) {
- i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
- w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
-
- cmp = !w2i ? -1 : !i2h ? 1 :
- strcomp(i2h->old_file.path, w2i->old_file.path);
-
- if (cmp < 0) {
- if (cb(i2h, NULL, payload))
- return GIT_EUSER;
- i++;
- } else if (cmp > 0) {
- if (cb(NULL, w2i, payload))
- return GIT_EUSER;
- j++;
- } else {
- if (cb(i2h, w2i, payload))
- return GIT_EUSER;
- i++; j++;
- }
- }
-
- return 0;
-}
diff --git a/src/diff_output.h b/src/diff_output.h
deleted file mode 100644
index 083355676..000000000
--- a/src/diff_output.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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_diff_output_h__
-#define INCLUDE_diff_output_h__
-
-#include "git2/blob.h"
-#include "diff.h"
-#include "map.h"
-#include "xdiff/xdiff.h"
-
-#define MAX_DIFF_FILESIZE 0x20000000
-
-enum {
- GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
- GIT_DIFF_PATCH_PREPPED = (1 << 1),
- GIT_DIFF_PATCH_LOADED = (1 << 2),
- GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
- GIT_DIFF_PATCH_DIFFED = (1 << 4),
-};
-
-/* context for performing diffs */
-typedef struct {
- git_repository *repo;
- git_diff_list *diff;
- const git_diff_options *opts;
- git_diff_file_cb file_cb;
- git_diff_hunk_cb hunk_cb;
- git_diff_data_cb data_cb;
- void *payload;
- int error;
- git_diff_range range;
- xdemitconf_t xdiff_config;
- xpparam_t xdiff_params;
-} diff_context;
-
-/* cached information about a single span in a diff */
-typedef struct diff_patch_line diff_patch_line;
-struct diff_patch_line {
- const char *ptr;
- size_t len;
- size_t lines, oldno, newno;
- char origin;
-};
-
-/* cached information about a hunk in a diff */
-typedef struct diff_patch_hunk diff_patch_hunk;
-struct diff_patch_hunk {
- git_diff_range range;
- char header[128];
- size_t header_len;
- size_t line_start;
- size_t line_count;
-};
-
-struct git_diff_patch {
- git_refcount rc;
- git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */
- git_diff_delta *delta;
- diff_context *ctxt; /* only valid while generating patch */
- git_iterator_type_t old_src;
- git_iterator_type_t new_src;
- git_blob *old_blob;
- git_blob *new_blob;
- git_map old_data;
- git_map new_data;
- uint32_t flags;
- diff_patch_hunk *hunks;
- size_t hunks_asize, hunks_size;
- diff_patch_line *lines;
- size_t lines_asize, lines_size;
- size_t oldno, newno;
-};
-
-/* context for performing diff on a single delta */
-typedef struct {
- git_diff_patch *patch;
- uint32_t prepped : 1;
- uint32_t loaded : 1;
- uint32_t diffable : 1;
- uint32_t diffed : 1;
-} diff_delta_context;
-
-extern int git_diff__paired_foreach(
- git_diff_list *idx2head,
- git_diff_list *wd2idx,
- int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
- void *payload);
-
-#endif
diff --git a/src/diff_patch.c b/src/diff_patch.c
new file mode 100644
index 000000000..cc45b6ddb
--- /dev/null
+++ b/src/diff_patch.c
@@ -0,0 +1,1044 @@
+/*
+ * 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 "diff.h"
+#include "diff_file.h"
+#include "diff_driver.h"
+#include "diff_patch.h"
+#include "diff_xdiff.h"
+#include "fileops.h"
+
+/* cached information about a single span in a diff */
+typedef struct diff_patch_line diff_patch_line;
+struct diff_patch_line {
+ const char *ptr;
+ size_t len;
+ size_t lines, oldno, newno;
+ char origin;
+};
+
+/* cached information about a hunk in a diff */
+typedef struct diff_patch_hunk diff_patch_hunk;
+struct diff_patch_hunk {
+ git_diff_range range;
+ char header[128];
+ size_t header_len;
+ size_t line_start;
+ size_t line_count;
+};
+
+struct git_diff_patch {
+ git_refcount rc;
+ git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ git_diff_delta *delta;
+ size_t delta_index;
+ git_diff_file_content ofile;
+ git_diff_file_content nfile;
+ uint32_t flags;
+ git_array_t(diff_patch_hunk) hunks;
+ git_array_t(diff_patch_line) lines;
+ size_t oldno, newno;
+ size_t content_size, context_size, header_size;
+ git_pool flattened;
+};
+
+enum {
+ GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
+ GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
+ GIT_DIFF_PATCH_LOADED = (1 << 2),
+ GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
+ GIT_DIFF_PATCH_DIFFED = (1 << 4),
+ GIT_DIFF_PATCH_FLATTENED = (1 << 5),
+};
+
+static void diff_output_init(git_diff_output*, const git_diff_options*,
+ git_diff_file_cb, git_diff_hunk_cb, git_diff_data_cb, void*);
+
+static void diff_output_to_patch(git_diff_output *, git_diff_patch *);
+
+static void diff_patch_update_binary(git_diff_patch *patch)
+{
+ if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
+
+ else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
+ (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
+ patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+}
+
+static void diff_patch_init_common(git_diff_patch *patch)
+{
+ diff_patch_update_binary(patch);
+
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ patch->flags |= GIT_DIFF_PATCH_LOADED; /* set LOADED but not DIFFABLE */
+
+ patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
+
+ if (patch->diff)
+ git_diff_list_addref(patch->diff);
+}
+
+static int diff_patch_init_from_diff(
+ git_diff_patch *patch, git_diff_list *diff, size_t delta_index)
+{
+ int error = 0;
+
+ memset(patch, 0, sizeof(*patch));
+ patch->diff = diff;
+ patch->delta = git_vector_get(&diff->deltas, delta_index);
+ patch->delta_index = delta_index;
+
+ if ((error = git_diff_file_content__init_from_diff(
+ &patch->ofile, diff, delta_index, true)) < 0 ||
+ (error = git_diff_file_content__init_from_diff(
+ &patch->nfile, diff, delta_index, false)) < 0)
+ return error;
+
+ diff_patch_init_common(patch);
+
+ return 0;
+}
+
+static int diff_patch_alloc_from_diff(
+ git_diff_patch **out,
+ git_diff_list *diff,
+ size_t delta_index)
+{
+ int error;
+ git_diff_patch *patch = git__calloc(1, sizeof(git_diff_patch));
+ GITERR_CHECK_ALLOC(patch);
+
+ if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
+ patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
+ GIT_REFCOUNT_INC(patch);
+ } else {
+ git__free(patch);
+ patch = NULL;
+ }
+
+ *out = patch;
+ return error;
+}
+
+static int diff_patch_load(git_diff_patch *patch, git_diff_output *output)
+{
+ int error = 0;
+ bool incomplete_data;
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
+ return 0;
+
+ /* if no hunk and data callbacks and user doesn't care if data looks
+ * binary, then there is no need to actually load the data
+ */
+ if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
+ output && !output->hunk_cb && !output->data_cb)
+ return 0;
+
+ incomplete_data =
+ (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0) &&
+ ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0));
+
+ /* always try to load workdir content first because filtering may
+ * need 2x data size and this minimizes peak memory footprint
+ */
+ if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+ if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+
+ /* once workdir has been tried, load other data as needed */
+ if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+ if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+
+ /* if previously missing an oid, and now that we have it the two sides
+ * are the same (and not submodules), update MODIFIED -> UNMODIFIED
+ */
+ if (incomplete_data &&
+ patch->ofile.file->mode == patch->nfile.file->mode &&
+ patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
+ git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) &&
+ patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
+ patch->delta->status = GIT_DELTA_UNMODIFIED;
+
+cleanup:
+ diff_patch_update_binary(patch);
+
+ if (!error) {
+ /* patch is diffable only for non-binary, modified files where
+ * at least one side has data and the data actually changed
+ */
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
+ patch->delta->status != GIT_DELTA_UNMODIFIED &&
+ (patch->ofile.map.len || patch->nfile.map.len) &&
+ (patch->ofile.map.len != patch->nfile.map.len ||
+ !git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid)))
+ patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
+
+ patch->flags |= GIT_DIFF_PATCH_LOADED;
+ }
+
+ return error;
+}
+
+static int diff_patch_file_callback(
+ git_diff_patch *patch, git_diff_output *output)
+{
+ float progress;
+
+ if (!output->file_cb)
+ return 0;
+
+ progress = patch->diff ?
+ ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
+
+ if (output->file_cb(patch->delta, progress, output->payload) != 0)
+ output->error = GIT_EUSER;
+
+ return output->error;
+}
+
+static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output)
+{
+ int error = 0;
+
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
+ return 0;
+
+ /* if we are not looking at the hunks and lines, don't do the diff */
+ if (!output->hunk_cb && !output->data_cb)
+ return 0;
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
+ (error = diff_patch_load(patch, output)) < 0)
+ return error;
+
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
+ return 0;
+
+ if (output->diff_cb != NULL &&
+ !(error = output->diff_cb(output, patch)))
+ patch->flags |= GIT_DIFF_PATCH_DIFFED;
+
+ return error;
+}
+
+static void diff_patch_free(git_diff_patch *patch)
+{
+ git_diff_file_content__clear(&patch->ofile);
+ git_diff_file_content__clear(&patch->nfile);
+
+ git_array_clear(patch->lines);
+ git_array_clear(patch->hunks);
+
+ git_diff_list_free(patch->diff); /* decrements refcount */
+ patch->diff = NULL;
+
+ git_pool_clear(&patch->flattened);
+
+ if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
+ git__free(patch);
+}
+
+static int diff_required(git_diff_list *diff, const char *action)
+{
+ if (diff)
+ return 0;
+ giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
+ return -1;
+}
+
+int git_diff_foreach(
+ git_diff_list *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ size_t idx;
+ git_diff_patch patch;
+
+ if (diff_required(diff, "git_diff_foreach") < 0)
+ return -1;
+
+ diff_output_init(
+ &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, &diff->opts);
+
+ git_vector_foreach(&diff->deltas, idx, patch.delta) {
+
+ /* check flags against patch status */
+ if (git_diff_delta__should_skip(&diff->opts, patch.delta))
+ continue;
+
+ if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) {
+
+ error = diff_patch_file_callback(&patch, &xo.output);
+
+ if (!error)
+ error = diff_patch_generate(&patch, &xo.output);
+
+ git_diff_patch_free(&patch);
+ }
+
+ if (error < 0)
+ break;
+ }
+
+ if (error == GIT_EUSER)
+ giterr_clear(); /* don't leave error message set invalidly */
+ return error;
+}
+
+typedef struct {
+ git_diff_patch patch;
+ git_diff_delta delta;
+ char paths[GIT_FLEX_ARRAY];
+} diff_patch_with_delta;
+
+static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
+{
+ int error = 0;
+ git_diff_patch *patch = &pd->patch;
+ bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+ bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+
+ pd->delta.status = has_new ?
+ (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
+ (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
+
+ if (git_oid_equal(&patch->nfile.file->oid, &patch->ofile.file->oid))
+ pd->delta.status = GIT_DELTA_UNMODIFIED;
+
+ patch->delta = &pd->delta;
+
+ diff_patch_init_common(patch);
+
+ if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
+ !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
+ return error;
+
+ error = diff_patch_file_callback(patch, (git_diff_output *)xo);
+
+ if (!error)
+ error = diff_patch_generate(patch, (git_diff_output *)xo);
+
+ if (error == GIT_EUSER)
+ giterr_clear(); /* don't leave error message set invalidly */
+
+ return error;
+}
+
+static int diff_patch_from_blobs(
+ diff_patch_with_delta *pd,
+ git_xdiff_output *xo,
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_repository *repo =
+ new_blob ? git_object_owner((const git_object *)new_blob) :
+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
+
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
+
+ if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
+ const git_blob *tmp_blob;
+ const char *tmp_path;
+ tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob;
+ tmp_path = old_path; old_path = new_path; new_path = tmp_path;
+ }
+
+ pd->patch.delta = &pd->delta;
+
+ pd->delta.old_file.path = old_path;
+ pd->delta.new_file.path = new_path;
+
+ if ((error = git_diff_file_content__init_from_blob(
+ &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 ||
+ (error = git_diff_file_content__init_from_blob(
+ &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 0)
+ return error;
+
+ return diff_single_generate(pd, xo);
+}
+
+static int diff_patch_with_delta_alloc(
+ diff_patch_with_delta **out,
+ const char **old_path,
+ const char **new_path)
+{
+ diff_patch_with_delta *pd;
+ size_t old_len = *old_path ? strlen(*old_path) : 0;
+ size_t new_len = *new_path ? strlen(*new_path) : 0;
+
+ *out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2);
+ GITERR_CHECK_ALLOC(pd);
+
+ pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
+
+ if (*old_path) {
+ memcpy(&pd->paths[0], *old_path, old_len);
+ *old_path = &pd->paths[0];
+ } else if (*new_path)
+ *old_path = &pd->paths[old_len + 1];
+
+ if (*new_path) {
+ memcpy(&pd->paths[old_len + 1], *new_path, new_len);
+ *new_path = &pd->paths[old_len + 1];
+ } else if (*old_path)
+ *new_path = &pd->paths[0];
+
+ return 0;
+}
+
+int git_diff_blobs(
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ diff_patch_with_delta pd;
+ git_xdiff_output xo;
+
+ memset(&pd, 0, sizeof(pd));
+ memset(&xo, 0, sizeof(xo));
+
+ diff_output_init(
+ &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, opts);
+
+ if (!old_path && new_path)
+ old_path = new_path;
+ else if (!new_path && old_path)
+ new_path = old_path;
+
+ error = diff_patch_from_blobs(
+ &pd, &xo, old_blob, old_path, new_blob, new_path, opts);
+
+ git_diff_patch_free(&pd.patch);
+
+ return error;
+}
+
+int git_diff_patch_from_blobs(
+ git_diff_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ diff_patch_with_delta *pd;
+ git_xdiff_output xo;
+
+ assert(out);
+ *out = NULL;
+
+ if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0)
+ return -1;
+
+ memset(&xo, 0, sizeof(xo));
+
+ diff_output_to_patch(&xo.output, &pd->patch);
+ git_xdiff_init(&xo, opts);
+
+ error = diff_patch_from_blobs(
+ pd, &xo, old_blob, old_path, new_blob, new_path, opts);
+
+ if (!error)
+ *out = (git_diff_patch *)pd;
+ else
+ git_diff_patch_free((git_diff_patch *)pd);
+
+ return error;
+}
+
+static int diff_patch_from_blob_and_buffer(
+ diff_patch_with_delta *pd,
+ git_xdiff_output *xo,
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_repository *repo =
+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
+
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
+
+ pd->patch.delta = &pd->delta;
+
+ if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
+ pd->delta.old_file.path = buf_path;
+ pd->delta.new_file.path = old_path;
+
+ if (!(error = git_diff_file_content__init_from_raw(
+ &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file)))
+ error = git_diff_file_content__init_from_blob(
+ &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file);
+ } else {
+ pd->delta.old_file.path = old_path;
+ pd->delta.new_file.path = buf_path;
+
+ if (!(error = git_diff_file_content__init_from_blob(
+ &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)))
+ error = git_diff_file_content__init_from_raw(
+ &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file);
+ }
+
+ if (error < 0)
+ return error;
+
+ return diff_single_generate(pd, xo);
+}
+
+int git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ diff_patch_with_delta pd;
+ git_xdiff_output xo;
+
+ memset(&pd, 0, sizeof(pd));
+ memset(&xo, 0, sizeof(xo));
+
+ diff_output_init(
+ &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, opts);
+
+ if (!old_path && buf_path)
+ old_path = buf_path;
+ else if (!buf_path && old_path)
+ buf_path = old_path;
+
+ error = diff_patch_from_blob_and_buffer(
+ &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
+
+ git_diff_patch_free(&pd.patch);
+
+ return error;
+}
+
+int git_diff_patch_from_blob_and_buffer(
+ git_diff_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ diff_patch_with_delta *pd;
+ git_xdiff_output xo;
+
+ assert(out);
+ *out = NULL;
+
+ if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0)
+ return -1;
+
+ memset(&xo, 0, sizeof(xo));
+
+ diff_output_to_patch(&xo.output, &pd->patch);
+ git_xdiff_init(&xo, opts);
+
+ error = diff_patch_from_blob_and_buffer(
+ pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
+
+ if (!error)
+ *out = (git_diff_patch *)pd;
+ else
+ git_diff_patch_free((git_diff_patch *)pd);
+
+ return error;
+}
+
+int git_diff_get_patch(
+ git_diff_patch **patch_ptr,
+ const git_diff_delta **delta_ptr,
+ git_diff_list *diff,
+ size_t idx)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ git_diff_delta *delta = NULL;
+ git_diff_patch *patch = NULL;
+
+ if (patch_ptr) *patch_ptr = NULL;
+ if (delta_ptr) *delta_ptr = NULL;
+
+ if (diff_required(diff, "git_diff_get_patch") < 0)
+ return -1;
+
+ delta = git_vector_get(&diff->deltas, idx);
+ if (!delta) {
+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
+ return GIT_ENOTFOUND;
+ }
+
+ if (delta_ptr)
+ *delta_ptr = delta;
+
+ if (git_diff_delta__should_skip(&diff->opts, delta))
+ return 0;
+
+ /* don't load the patch data unless we need it for binary check */
+ if (!patch_ptr &&
+ ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
+ (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
+ return 0;
+
+ if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
+ return error;
+
+ diff_output_to_patch(&xo.output, patch);
+ git_xdiff_init(&xo, &diff->opts);
+
+ error = diff_patch_file_callback(patch, &xo.output);
+
+ if (!error)
+ error = diff_patch_generate(patch, &xo.output);
+
+ if (!error) {
+ /* if cumulative diff size is < 0.5 total size, flatten the patch */
+ /* unload the file content */
+ }
+
+ if (error || !patch_ptr)
+ git_diff_patch_free(patch);
+ else
+ *patch_ptr = patch;
+
+ if (error == GIT_EUSER)
+ giterr_clear(); /* don't leave error message set invalidly */
+ return error;
+}
+
+void git_diff_patch_free(git_diff_patch *patch)
+{
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, diff_patch_free);
+}
+
+const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
+{
+ assert(patch);
+ return patch->delta;
+}
+
+size_t git_diff_patch_num_hunks(git_diff_patch *patch)
+{
+ assert(patch);
+ return git_array_size(patch->hunks);
+}
+
+int git_diff_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_diff_patch *patch)
+{
+ size_t totals[3], idx;
+
+ memset(totals, 0, sizeof(totals));
+
+ for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
+ diff_patch_line *line = git_array_get(patch->lines, idx);
+ if (!line)
+ continue;
+
+ switch (line->origin) {
+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;
+ default:
+ /* diff --stat and --numstat don't count EOFNL marks because
+ * they will always be paired with a ADDITION or DELETION line.
+ */
+ break;
+ }
+ }
+
+ if (total_ctxt)
+ *total_ctxt = totals[0];
+ if (total_adds)
+ *total_adds = totals[1];
+ if (total_dels)
+ *total_dels = totals[2];
+
+ return 0;
+}
+
+static int diff_error_outofrange(const char *thing)
+{
+ giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
+ return GIT_ENOTFOUND;
+}
+
+int git_diff_patch_get_hunk(
+ const git_diff_range **range,
+ const char **header,
+ size_t *header_len,
+ size_t *lines_in_hunk,
+ git_diff_patch *patch,
+ size_t hunk_idx)
+{
+ diff_patch_hunk *hunk;
+ assert(patch);
+
+ hunk = git_array_get(patch->hunks, hunk_idx);
+
+ if (!hunk) {
+ if (range) *range = NULL;
+ if (header) *header = NULL;
+ if (header_len) *header_len = 0;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return diff_error_outofrange("hunk");
+ }
+
+ if (range) *range = &hunk->range;
+ if (header) *header = hunk->header;
+ if (header_len) *header_len = hunk->header_len;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
+ return 0;
+}
+
+int git_diff_patch_num_lines_in_hunk(git_diff_patch *patch, size_t hunk_idx)
+{
+ diff_patch_hunk *hunk;
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
+ return diff_error_outofrange("hunk");
+ return (int)hunk->line_count;
+}
+
+int git_diff_patch_get_line_in_hunk(
+ char *line_origin,
+ const char **content,
+ size_t *content_len,
+ int *old_lineno,
+ int *new_lineno,
+ git_diff_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk)
+{
+ diff_patch_hunk *hunk;
+ diff_patch_line *line;
+ const char *thing;
+
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
+ thing = "hunk";
+ goto notfound;
+ }
+
+ if (line_of_hunk >= hunk->line_count ||
+ !(line = git_array_get(
+ patch->lines, hunk->line_start + line_of_hunk))) {
+ thing = "line";
+ goto notfound;
+ }
+
+ if (line_origin) *line_origin = line->origin;
+ if (content) *content = line->ptr;
+ if (content_len) *content_len = line->len;
+ if (old_lineno) *old_lineno = (int)line->oldno;
+ if (new_lineno) *new_lineno = (int)line->newno;
+
+ return 0;
+
+notfound:
+ if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
+ if (content) *content = NULL;
+ if (content_len) *content_len = 0;
+ if (old_lineno) *old_lineno = -1;
+ if (new_lineno) *new_lineno = -1;
+
+ return diff_error_outofrange(thing);
+}
+
+size_t git_diff_patch_size(
+ git_diff_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers)
+{
+ size_t out;
+
+ assert(patch);
+
+ out = patch->content_size;
+
+ if (!include_context)
+ out -= patch->context_size;
+
+ if (include_hunk_headers)
+ out += patch->header_size;
+
+ if (include_file_headers) {
+ git_buf file_header = GIT_BUF_INIT;
+
+ if (git_diff_delta__format_file_header(
+ &file_header, patch->delta, NULL, NULL, 0) < 0)
+ giterr_clear();
+ else
+ out += git_buf_len(&file_header);
+
+ git_buf_free(&file_header);
+ }
+
+ return out;
+}
+
+git_diff_list *git_diff_patch__diff(git_diff_patch *patch)
+{
+ return patch->diff;
+}
+
+git_diff_driver *git_diff_patch__driver(git_diff_patch *patch)
+{
+ /* ofile driver is representative for whole patch */
+ return patch->ofile.driver;
+}
+
+void git_diff_patch__old_data(
+ char **ptr, size_t *len, git_diff_patch *patch)
+{
+ *ptr = patch->ofile.map.data;
+ *len = patch->ofile.map.len;
+}
+
+void git_diff_patch__new_data(
+ char **ptr, size_t *len, git_diff_patch *patch)
+{
+ *ptr = patch->nfile.map.data;
+ *len = patch->nfile.map.len;
+}
+
+int git_diff_patch__invoke_callbacks(
+ git_diff_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb line_cb,
+ void *payload)
+{
+ int error = 0;
+ uint32_t i, j;
+
+ if (file_cb)
+ error = file_cb(patch->delta, 0, payload);
+
+ if (!hunk_cb && !line_cb)
+ return error;
+
+ for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
+ diff_patch_hunk *h = git_array_get(patch->hunks, i);
+
+ error = hunk_cb(
+ patch->delta, &h->range, h->header, h->header_len, payload);
+
+ if (!line_cb)
+ continue;
+
+ for (j = 0; !error && j < h->line_count; ++j) {
+ diff_patch_line *l =
+ git_array_get(patch->lines, h->line_start + j);
+
+ error = line_cb(
+ patch->delta, &h->range, l->origin, l->ptr, l->len, payload);
+ }
+ }
+
+ return error;
+}
+
+
+static int diff_patch_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
+ return 0;
+}
+
+static int diff_patch_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ const char *header,
+ size_t header_len,
+ void *payload)
+{
+ git_diff_patch *patch = payload;
+ diff_patch_hunk *hunk;
+
+ GIT_UNUSED(delta);
+
+ hunk = git_array_alloc(patch->hunks);
+ GITERR_CHECK_ALLOC(hunk);
+
+ memcpy(&hunk->range, range, sizeof(hunk->range));
+
+ assert(header_len + 1 < sizeof(hunk->header));
+ memcpy(&hunk->header, header, header_len);
+ hunk->header[header_len] = '\0';
+ hunk->header_len = header_len;
+
+ patch->header_size += header_len;
+
+ hunk->line_start = git_array_size(patch->lines);
+ hunk->line_count = 0;
+
+ patch->oldno = range->old_start;
+ patch->newno = range->new_start;
+
+ return 0;
+}
+
+static int diff_patch_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len,
+ void *payload)
+{
+ git_diff_patch *patch = payload;
+ diff_patch_hunk *hunk;
+ diff_patch_line *line;
+ const char *content_end = content + content_len;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(range);
+
+ hunk = git_array_last(patch->hunks);
+ GITERR_CHECK_ALLOC(hunk);
+
+ line = git_array_alloc(patch->lines);
+ GITERR_CHECK_ALLOC(line);
+
+ line->ptr = content;
+ line->len = content_len;
+ line->origin = line_origin;
+
+ /* do some bookkeeping so we can provide old/new line numbers */
+
+ line->lines = 0;
+ while (content < content_end)
+ if (*content++ == '\n')
+ ++line->lines;
+
+ patch->content_size += content_len;
+
+ switch (line_origin) {
+ case GIT_DIFF_LINE_ADDITION:
+ patch->content_size += 1;
+ case GIT_DIFF_LINE_DEL_EOFNL:
+ line->oldno = -1;
+ line->newno = patch->newno;
+ patch->newno += line->lines;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ patch->content_size += 1;
+ case GIT_DIFF_LINE_ADD_EOFNL:
+ line->oldno = patch->oldno;
+ line->newno = -1;
+ patch->oldno += line->lines;
+ break;
+ case GIT_DIFF_LINE_CONTEXT:
+ patch->content_size += 1;
+ patch->context_size += 1;
+ case GIT_DIFF_LINE_CONTEXT_EOFNL:
+ patch->context_size += content_len;
+ line->oldno = patch->oldno;
+ line->newno = patch->newno;
+ patch->oldno += line->lines;
+ patch->newno += line->lines;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ hunk->line_count++;
+
+ return 0;
+}
+
+static void diff_output_init(
+ git_diff_output *out,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ GIT_UNUSED(opts);
+
+ memset(out, 0, sizeof(*out));
+
+ out->file_cb = file_cb;
+ out->hunk_cb = hunk_cb;
+ out->data_cb = data_cb;
+ out->payload = payload;
+}
+
+static void diff_output_to_patch(git_diff_output *out, git_diff_patch *patch)
+{
+ diff_output_init(
+ out, NULL,
+ diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch);
+}
diff --git a/src/diff_patch.h b/src/diff_patch.h
new file mode 100644
index 000000000..56af14600
--- /dev/null
+++ b/src/diff_patch.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_diff_patch_h__
+#define INCLUDE_diff_patch_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "array.h"
+
+extern git_diff_list *git_diff_patch__diff(git_diff_patch *);
+
+extern git_diff_driver *git_diff_patch__driver(git_diff_patch *);
+
+extern void git_diff_patch__old_data(char **, size_t *, git_diff_patch *);
+extern void git_diff_patch__new_data(char **, size_t *, git_diff_patch *);
+
+extern int git_diff_patch__invoke_callbacks(
+ git_diff_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb line_cb,
+ void *payload);
+
+typedef struct git_diff_output git_diff_output;
+struct git_diff_output {
+ /* these callbacks are issued with the diff data */
+ git_diff_file_cb file_cb;
+ git_diff_hunk_cb hunk_cb;
+ git_diff_data_cb data_cb;
+ void *payload;
+
+ /* this records the actual error in cases where it may be obscured */
+ int error;
+
+ /* this callback is used to do the diff and drive the other callbacks.
+ * see diff_xdiff.h for how to use this in practice for now.
+ */
+ int (*diff_cb)(git_diff_output *output, git_diff_patch *patch);
+};
+
+#endif
diff --git a/src/diff_print.c b/src/diff_print.c
new file mode 100644
index 000000000..4ddd72443
--- /dev/null
+++ b/src/diff_print.c
@@ -0,0 +1,456 @@
+/*
+ * 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 "diff.h"
+#include "diff_patch.h"
+#include "buffer.h"
+
+typedef struct {
+ git_diff_list *diff;
+ git_diff_data_cb print_cb;
+ void *payload;
+ git_buf *buf;
+ int oid_strlen;
+} diff_print_info;
+
+static int diff_print_info_init(
+ diff_print_info *pi,
+ git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload)
+{
+ pi->diff = diff;
+ pi->print_cb = cb;
+ pi->payload = payload;
+ pi->buf = out;
+
+ if (!diff || !diff->repo)
+ pi->oid_strlen = GIT_ABBREV_DEFAULT;
+ else if (git_repository__cvar(
+ &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
+ return -1;
+
+ pi->oid_strlen += 1; /* for NUL byte */
+
+ if (pi->oid_strlen < 2)
+ pi->oid_strlen = 2;
+ else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
+ pi->oid_strlen = GIT_OID_HEXSZ + 1;
+
+ return 0;
+}
+
+static char diff_pick_suffix(int mode)
+{
+ if (S_ISDIR(mode))
+ return '/';
+ else if (mode & 0100) /* -V536 */
+ /* in git, modes are very regular, so we must have 0100755 mode */
+ return '*';
+ else
+ return ' ';
+}
+
+char git_diff_status_char(git_delta_t status)
+{
+ char code;
+
+ switch (status) {
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ default: code = ' '; break;
+ }
+
+ return code;
+}
+
+static int callback_error(void)
+{
+ giterr_clear();
+ return GIT_EUSER;
+}
+
+static int diff_print_one_compact(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
+ int (*strcomp)(const char *, const char *) =
+ pi->diff ? pi->diff->strcomp : git__strcmp;
+
+ GIT_UNUSED(progress);
+
+ if (code == ' ')
+ return 0;
+
+ old_suffix = diff_pick_suffix(delta->old_file.mode);
+ new_suffix = diff_pick_suffix(delta->new_file.mode);
+
+ git_buf_clear(out);
+
+ if (delta->old_file.path != delta->new_file.path &&
+ strcomp(delta->old_file.path,delta->new_file.path) != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (delta->old_file.mode != delta->new_file.mode &&
+ delta->old_file.mode != 0 && delta->new_file.mode != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (old_suffix != ' ')
+ git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
+ else
+ git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(out), git_buf_len(out), pi->payload))
+ return callback_error();
+
+ return 0;
+}
+
+/* print a git_diff_list to a print callback in compact format */
+int git_diff_print_compact(
+ git_diff_list *diff,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int diff_print_one_raw(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ char code = git_diff_status_char(delta->status);
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ GIT_UNUSED(progress);
+
+ if (code == ' ')
+ return 0;
+
+ git_buf_clear(out);
+
+ git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
+ git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
+
+ git_buf_printf(
+ out, ":%06o %06o %s... %s... %c",
+ delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
+
+ if (delta->similarity > 0)
+ git_buf_printf(out, "%03u", delta->similarity);
+
+ if (delta->old_file.path != delta->new_file.path)
+ git_buf_printf(
+ out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
+ else
+ git_buf_printf(
+ out, "\t%s\n", delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(out), git_buf_len(out), pi->payload))
+ return callback_error();
+
+ return 0;
+}
+
+/* print a git_diff_list to a print callback in raw output format */
+int git_diff_print_raw(
+ git_diff_list *diff,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int diff_print_oid_range(
+ git_buf *out, const git_diff_delta *delta, int oid_strlen)
+{
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid);
+ git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid);
+
+ /* TODO: Match git diff more closely */
+ if (delta->old_file.mode == delta->new_file.mode) {
+ git_buf_printf(out, "index %s..%s %o\n",
+ start_oid, end_oid, delta->old_file.mode);
+ } else {
+ if (delta->old_file.mode == 0) {
+ git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
+ } else if (delta->new_file.mode == 0) {
+ git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
+ } else {
+ git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
+ }
+ git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
+ }
+
+ if (git_buf_oom(out))
+ return -1;
+
+ return 0;
+}
+
+static int diff_delta_format_with_paths(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ const char *template)
+{
+ const char *oldpath = delta->old_file.path;
+ const char *newpath = delta->new_file.path;
+
+ if (git_oid_iszero(&delta->old_file.oid)) {
+ oldpfx = "";
+ oldpath = "/dev/null";
+ }
+ if (git_oid_iszero(&delta->new_file.oid)) {
+ newpfx = "";
+ newpath = "/dev/null";
+ }
+
+ return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
+}
+
+int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int oid_strlen)
+{
+ if (!oldpfx)
+ oldpfx = DIFF_OLD_PREFIX_DEFAULT;
+ if (!newpfx)
+ newpfx = DIFF_NEW_PREFIX_DEFAULT;
+ if (!oid_strlen)
+ oid_strlen = GIT_ABBREV_DEFAULT + 1;
+
+ git_buf_clear(out);
+
+ git_buf_printf(out, "diff --git %s%s %s%s\n",
+ oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
+
+ if (diff_print_oid_range(out, delta, oid_strlen) < 0)
+ return -1;
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ diff_delta_format_with_paths(
+ out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int diff_print_patch_file(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ const char *oldpfx =
+ pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ const char *newpfx =
+ pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+ uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL;
+
+ GIT_UNUSED(progress);
+
+ if (S_ISDIR(delta->new_file.mode) ||
+ delta->status == GIT_DELTA_UNMODIFIED ||
+ delta->status == GIT_DELTA_IGNORED ||
+ (delta->status == GIT_DELTA_UNTRACKED &&
+ (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
+ return 0;
+
+ if (git_diff_delta__format_file_header(
+ pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0)
+ return -1;
+
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ return callback_error();
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ return 0;
+
+ git_buf_clear(pi->buf);
+
+ if (diff_delta_format_with_paths(
+ pi->buf, delta, oldpfx, newpfx,
+ "Binary files %s%s and %s%s differ\n") < 0)
+ return -1;
+
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ return callback_error();
+
+ return 0;
+}
+
+static int diff_print_patch_hunk(
+ const git_diff_delta *d,
+ const git_diff_range *r,
+ const char *header,
+ size_t header_len,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(d->new_file.mode))
+ return 0;
+
+ git_buf_clear(pi->buf);
+ if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
+ return -1;
+
+ if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ return callback_error();
+
+ return 0;
+}
+
+static int diff_print_patch_line(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin, /* GIT_DIFF_LINE value from above */
+ const char *content,
+ size_t content_len,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(delta->new_file.mode))
+ return 0;
+
+ git_buf_clear(pi->buf);
+
+ if (line_origin == GIT_DIFF_LINE_ADDITION ||
+ line_origin == GIT_DIFF_LINE_DELETION ||
+ line_origin == GIT_DIFF_LINE_CONTEXT)
+ git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);
+ else if (content_len > 0)
+ git_buf_printf(pi->buf, "%.*s", (int)content_len, content);
+
+ if (git_buf_oom(pi->buf))
+ return -1;
+
+ if (pi->print_cb(delta, range, line_origin,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ return callback_error();
+
+ return 0;
+}
+
+/* print a git_diff_list to an output callback in patch format */
+int git_diff_print_patch(
+ git_diff_list *diff,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
+ error = git_diff_foreach(
+ diff, diff_print_patch_file, diff_print_patch_hunk,
+ diff_print_patch_line, &pi);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+/* print a git_diff_patch to an output callback */
+int git_diff_patch_print(
+ git_diff_patch *patch,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ assert(patch && print_cb);
+
+ if (!(error = diff_print_info_init(
+ &pi, &temp, git_diff_patch__diff(patch), print_cb, payload)))
+ error = git_diff_patch__invoke_callbacks(
+ patch, diff_print_patch_file, diff_print_patch_hunk,
+ diff_print_patch_line, &pi);
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
+static int diff_print_to_buffer_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len,
+ void *payload)
+{
+ git_buf *output = payload;
+ GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
+ return git_buf_put(output, content, content_len);
+}
+
+/* print a git_diff_patch to a string buffer */
+int git_diff_patch_to_str(
+ char **string,
+ git_diff_patch *patch)
+{
+ int error;
+ git_buf output = GIT_BUF_INIT;
+
+ error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output);
+
+ /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
+ * meaning a memory allocation failure, so just map to -1...
+ */
+ if (error == GIT_EUSER)
+ error = -1;
+
+ *string = git_buf_detach(&output);
+
+ return error;
+}
diff --git a/src/diff_tform.c b/src/diff_tform.c
index efcb19d95..ba35d3c14 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -5,10 +5,14 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "diff.h"
+
#include "git2/config.h"
#include "git2/blob.h"
+
+#include "diff.h"
#include "hashsig.h"
+#include "path.h"
+#include "fileops.h"
static git_diff_delta *diff_delta__dup(
const git_diff_delta *d, git_pool *pool)
@@ -18,12 +22,15 @@ static git_diff_delta *diff_delta__dup(
return NULL;
memcpy(delta, d, sizeof(git_diff_delta));
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
- delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
- if (delta->old_file.path == NULL)
- goto fail;
+ if (d->old_file.path != NULL) {
+ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
+ if (delta->old_file.path == NULL)
+ goto fail;
+ }
- if (d->new_file.path != d->old_file.path) {
+ if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) {
delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
if (delta->new_file.path == NULL)
goto fail;
@@ -170,7 +177,7 @@ int git_diff_merge(
return error;
}
-static int find_similar__hashsig_for_file(
+int git_diff_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;
@@ -178,7 +185,7 @@ static int find_similar__hashsig_for_file(
GIT_UNUSED(f);
error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
-
+
if (error == GIT_EBUFS) {
error = 0;
giterr_clear();
@@ -187,15 +194,15 @@ static int find_similar__hashsig_for_file(
return error;
}
-static int find_similar__hashsig_for_buf(
+int git_diff_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);
error = git_hashsig_create((git_hashsig **)out, buf, len, opt);
-
+
if (error == GIT_EBUFS) {
error = 0;
giterr_clear();
@@ -204,13 +211,13 @@ static int find_similar__hashsig_for_buf(
return error;
}
-static void find_similar__hashsig_free(void *sig, void *payload)
+void git_diff_find_similar__hashsig_free(void *sig, void *payload)
{
GIT_UNUSED(payload);
git_hashsig_free(sig);
}
-static int find_similar__calc_similarity(
+int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload)
{
GIT_UNUSED(payload);
@@ -220,7 +227,7 @@ static int find_similar__calc_similarity(
#define DEFAULT_THRESHOLD 50
#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
-#define DEFAULT_TARGET_LIMIT 200
+#define DEFAULT_RENAME_LIMIT 200
static int normalize_find_opts(
git_diff_list *diff,
@@ -253,12 +260,25 @@ static int normalize_find_opts(
/* some flags imply others */
+ if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) {
+ /* if we are only looking for exact matches, then don't turn
+ * MODIFIED items into ADD/DELETE pairs because it's too picky
+ */
+ opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES);
+
+ /* similarly, don't look for self-rewrites to split */
+ opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+ }
+
if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
opts->flags |= GIT_DIFF_FIND_RENAMES;
if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
opts->flags |= GIT_DIFF_FIND_COPIES;
+ if (opts->flags & GIT_DIFF_BREAK_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_REWRITES;
+
#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
if (USE_DEFAULT(opts->rename_threshold))
@@ -275,15 +295,15 @@ static int normalize_find_opts(
#undef USE_DEFAULT
- if (!opts->target_limit) {
+ if (!opts->rename_limit) {
int32_t limit = 0;
- opts->target_limit = DEFAULT_TARGET_LIMIT;
+ opts->rename_limit = DEFAULT_RENAME_LIMIT;
if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)
giterr_clear();
else if (limit > 0)
- opts->target_limit = limit;
+ opts->rename_limit = limit;
}
/* assign the internal metric with whitespace flag as payload */
@@ -291,10 +311,10 @@ static int normalize_find_opts(
opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
GITERR_CHECK_ALLOC(opts->metric);
- opts->metric->file_signature = find_similar__hashsig_for_file;
- opts->metric->buffer_signature = find_similar__hashsig_for_buf;
- opts->metric->free_signature = find_similar__hashsig_free;
- opts->metric->similarity = find_similar__calc_similarity;
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
opts->metric->payload = (void *)GIT_HASHSIG_IGNORE_WHITESPACE;
@@ -307,11 +327,12 @@ static int normalize_find_opts(
return 0;
}
-static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
+static int apply_splits_and_deletes(
+ git_diff_list *diff, size_t expected_size, bool actually_split)
{
git_vector onto = GIT_VECTOR_INIT;
size_t i;
- git_diff_delta *delta;
+ git_diff_delta *delta, *deleted;
if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
return -1;
@@ -321,9 +342,11 @@ static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
continue;
- if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
- git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
- if (!deleted)
+ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) {
+ delta->similarity = 0;
+
+ /* make new record for DELETED side of split */
+ if (!(deleted = diff_delta__dup(delta, &diff->pool)))
goto on_error;
deleted->status = GIT_DELTA_DELETED;
@@ -334,32 +357,46 @@ static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
if (git_vector_insert(&onto, deleted) < 0)
goto on_error;
- delta->status = GIT_DELTA_ADDED;
+ if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR)
+ delta->status = GIT_DELTA_UNTRACKED;
+ else
+ delta->status = GIT_DELTA_ADDED;
memset(&delta->old_file, 0, sizeof(delta->old_file));
delta->old_file.path = delta->new_file.path;
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
}
+ /* clean up delta before inserting into new list */
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (delta->status != GIT_DELTA_COPIED &&
+ delta->status != GIT_DELTA_RENAMED &&
+ (delta->status != GIT_DELTA_MODIFIED || actually_split))
+ delta->similarity = 0;
+
+ /* insert into new list */
if (git_vector_insert(&onto, delta) < 0)
goto on_error;
}
/* cannot return an error past this point */
- git_vector_foreach(&diff->deltas, i, delta)
+
+ /* free deltas from old list that didn't make it to the new one */
+ git_vector_foreach(&diff->deltas, i, delta) {
if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
git__free(delta);
+ }
/* swap new delta list into place */
- git_vector_sort(&onto);
git_vector_swap(&diff->deltas, &onto);
git_vector_free(&onto);
+ git_vector_sort(&diff->deltas);
return 0;
on_error:
git_vector_foreach(&onto, i, delta)
git__free(delta);
-
git_vector_free(&onto);
return -1;
@@ -371,312 +408,646 @@ GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx)
return (idx & 1) ? &delta->new_file : &delta->old_file;
}
-static int similarity_calc(
- git_diff_list *diff,
- git_diff_find_options *opts,
- size_t file_idx,
+typedef struct {
+ size_t idx;
+ git_iterator_type_t src;
+ git_repository *repo;
+ git_diff_file *file;
+ git_buf data;
+ git_odb_object *odb_obj;
+ git_blob *blob;
+} similarity_info;
+
+static int similarity_init(
+ similarity_info *info, git_diff_list *diff, size_t file_idx)
+{
+ info->idx = file_idx;
+ info->src = (file_idx & 1) ? diff->new_src : diff->old_src;
+ info->repo = diff->repo;
+ info->file = similarity_get_file(diff, file_idx);
+ info->odb_obj = NULL;
+ info->blob = NULL;
+ git_buf_init(&info->data, 0);
+
+ if (info->file->size > 0)
+ return 0;
+
+ return git_diff_file__resolve_zero_size(
+ info->file, &info->odb_obj, info->repo);
+}
+
+static int similarity_sig(
+ similarity_info *info,
+ const git_diff_find_options *opts,
void **cache)
{
int error = 0;
- git_diff_file *file = similarity_get_file(diff, file_idx);
- git_iterator_type_t src = (file_idx & 1) ? diff->old_src : diff->new_src;
-
- if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */
- git_buf path = GIT_BUF_INIT;
+ git_diff_file *file = info->file;
- /* TODO: apply wd-to-odb filters to file data if necessary */
+ if (info->src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_buf_joinpath(
+ &info->data, git_repository_workdir(info->repo), file->path)) < 0)
+ return error;
- if (!(error = git_buf_joinpath(
- &path, git_repository_workdir(diff->repo), file->path)))
- error = opts->metric->file_signature(
- &cache[file_idx], file, path.ptr, opts->metric->payload);
+ /* if path is not a regular file, just skip this item */
+ if (!git_path_isfile(info->data.ptr))
+ return 0;
- git_buf_free(&path);
- } else { /* compute hashsig from blob buffer */
- git_blob *blob = NULL;
- git_off_t blobsize;
+ /* TODO: apply wd-to-odb filters to file data if necessary */
- /* TODO: add max size threshold a la diff? */
+ error = opts->metric->file_signature(
+ &cache[info->idx], info->file,
+ info->data.ptr, opts->metric->payload);
+ } else {
+ /* if we didn't initially know the size, we might have an odb_obj
+ * around from earlier, so convert that, otherwise load the blob now
+ */
+ if (info->odb_obj != NULL)
+ error = git_object__from_odb_object(
+ (git_object **)&info->blob, info->repo,
+ info->odb_obj, GIT_OBJ_BLOB);
+ else
+ error = git_blob_lookup(&info->blob, info->repo, &file->oid);
- if ((error = git_blob_lookup(&blob, diff->repo, &file->oid)) < 0)
- return error;
+ if (error < 0) {
+ /* if lookup fails, just skip this item in similarity calc */
+ giterr_clear();
+ } else {
+ size_t sz;
- blobsize = git_blob_rawsize(blob);
- if (!git__is_sizet(blobsize)) /* ? what to do ? */
- blobsize = (size_t)-1;
+ /* index size may not be actual blob size if filtered */
+ if (file->size != git_blob_rawsize(info->blob))
+ file->size = git_blob_rawsize(info->blob);
- error = opts->metric->buffer_signature(
- &cache[file_idx], file, git_blob_rawcontent(blob),
- (size_t)blobsize, opts->metric->payload);
+ sz = (size_t)(git__is_sizet(file->size) ? file->size : -1);
- git_blob_free(blob);
+ error = opts->metric->buffer_signature(
+ &cache[info->idx], info->file,
+ git_blob_rawcontent(info->blob), sz, opts->metric->payload);
+ }
}
return error;
}
+static void similarity_unload(similarity_info *info)
+{
+ if (info->odb_obj)
+ git_odb_object_free(info->odb_obj);
+
+ if (info->blob)
+ git_blob_free(info->blob);
+ else
+ git_buf_free(&info->data);
+}
+
+#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
+
+/* - score < 0 means files cannot be compared
+ * - score >= 100 means files are exact match
+ * - score == 0 means files are completely different
+ */
static int similarity_measure(
+ int *score,
git_diff_list *diff,
- git_diff_find_options *opts,
+ const git_diff_find_options *opts,
void **cache,
size_t a_idx,
size_t b_idx)
{
- int score = 0;
git_diff_file *a_file = similarity_get_file(diff, a_idx);
git_diff_file *b_file = similarity_get_file(diff, b_idx);
+ bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
+ int error = 0;
+ similarity_info a_info, b_info;
+
+ *score = -1;
+ /* don't try to compare files of different types */
if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
return 0;
- if (git_oid_cmp(&a_file->oid, &b_file->oid) == 0)
- return 100;
+ /* if exact match is requested, force calculation of missing OIDs now */
+ if (exact_match) {
+ if (git_oid_iszero(&a_file->oid) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(diff->repo, a_file->path,
+ a_file->mode, a_file->size, &a_file->oid))
+ a_file->flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (git_oid_iszero(&b_file->oid) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(diff->repo, b_file->path,
+ b_file->mode, b_file->size, &b_file->oid))
+ b_file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ /* check OID match as a quick test */
+ if (git_oid__cmp(&a_file->oid, &b_file->oid) == 0) {
+ *score = 100;
+ return 0;
+ }
+
+ /* don't calculate signatures if we are doing exact match */
+ if (exact_match) {
+ *score = 0;
+ return 0;
+ }
+
+ memset(&a_info, 0, sizeof(a_info));
+ memset(&b_info, 0, sizeof(b_info));
+
+ /* set up similarity data (will try to update missing file sizes) */
+ if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0)
+ goto cleanup;
+
+ /* check if file sizes are nowhere near each other */
+ if (a_file->size > 127 &&
+ b_file->size > 127 &&
+ (a_file->size > (b_file->size << 3) ||
+ b_file->size > (a_file->size << 3)))
+ goto cleanup;
/* update signature cache if needed */
- if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0)
- 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])
+ if (!cache[a_idx]) {
+ if ((error = similarity_sig(&a_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+ if (!cache[b_idx]) {
+ if ((error = similarity_sig(&b_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+
+ /* calculate similarity provided that the metric choose to process
+ * both the a and b files (some may not if file is too big, etc).
+ */
+ if (cache[a_idx] && cache[b_idx])
+ error = opts->metric->similarity(
+ score, cache[a_idx], cache[b_idx], opts->metric->payload);
+
+cleanup:
+ similarity_unload(&a_info);
+ similarity_unload(&b_info);
+
+ return error;
+}
+
+static int calc_self_similarity(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ int error, similarity = -1;
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0)
return 0;
- /* compare signatures */
- if (opts->metric->similarity(
- &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)
- return -1;
+ error = similarity_measure(
+ &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1);
+ if (error < 0)
+ return error;
- /* clip score */
- if (score < 0)
- score = 0;
- else if (score > 100)
- score = 100;
+ if (similarity >= 0) {
+ delta->similarity = (uint32_t)similarity;
+ delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY;
+ }
- return score;
+ return 0;
}
-#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0)
+static bool is_rename_target(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't plain blobs */
+ if (!GIT_MODE_ISBLOB(delta->new_file.mode))
+ return false;
+
+ /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as
+ * targets; maybe include UNTRACKED and IGNORED if requested.
+ */
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ return false;
+
+ case GIT_DELTA_MODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+
+ case GIT_DELTA_UNTRACKED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
+ return false;
+ break;
+
+ case GIT_DELTA_IGNORED:
+ return false;
+
+ default: /* all other status values should be checked */
+ break;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET;
+ return true;
+}
+
+static bool is_rename_source(
+ git_diff_list *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't blobs */
+ if (!GIT_MODE_ISBLOB(delta->old_file.mode))
+ return false;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_IGNORED:
+ return false;
+
+ case GIT_DELTA_DELETED:
+ case GIT_DELTA_TYPECHANGE:
+ break;
+
+ case GIT_DELTA_UNMODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ return false;
+ break;
+
+ default: /* MODIFIED, RENAMED, COPIED */
+ /* if we're finding copies, this could be a source */
+ if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ break;
+
+ /* otherwise, this is only a source if we can split it */
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE;
+ return true;
+}
+
+GIT_INLINE(bool) delta_is_split(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_TYPECHANGE ||
+ (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0);
+}
+
+GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_UNTRACKED ||
+ delta->status == GIT_DELTA_IGNORED);
+}
+
+GIT_INLINE(void) delta_make_rename(
+ git_diff_delta *to, const git_diff_delta *from, uint32_t similarity)
+{
+ to->status = GIT_DELTA_RENAMED;
+ to->similarity = similarity;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+}
+
+typedef struct {
+ uint32_t idx;
+ uint32_t similarity;
+} diff_find_match;
int git_diff_find_similar(
git_diff_list *diff,
git_diff_find_options *given_opts)
{
- size_t i, j, cache_size, *matches;
+ size_t s, t;
int error = 0, similarity;
- git_diff_delta *from, *to;
+ git_diff_delta *src, *tgt;
git_diff_find_options opts;
- size_t tried_targets, num_rewrites = 0;
- void **cache;
+ size_t num_deltas, num_srcs = 0, num_tgts = 0;
+ size_t tried_srcs = 0, tried_tgts = 0;
+ size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
+ void **sigcache; /* cache of similarity metric file signatures */
+ diff_find_match *tgt2src = NULL;
+ diff_find_match *src2tgt = NULL;
+ diff_find_match *tgt2src_copy = NULL;
+ diff_find_match *best_match;
+ git_diff_file swap;
if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
return error;
- /* TODO: maybe abort if deltas.length > target_limit ??? */
+ num_deltas = diff->deltas.length;
- cache_size = diff->deltas.length * 2; /* must store b/c length may change */
- cache = git__calloc(cache_size, sizeof(void *));
- GITERR_CHECK_ALLOC(cache);
+ /* TODO: maybe abort if deltas.length > rename_limit ??? */
+ if (!git__is_uint32(num_deltas))
+ return 0;
- matches = git__calloc(diff->deltas.length, sizeof(size_t));
- GITERR_CHECK_ALLOC(matches);
+ sigcache = git__calloc(num_deltas * 2, sizeof(void *));
+ GITERR_CHECK_ALLOC(sigcache);
- /* first break MODIFIED records that are too different (if requested) */
+ /* Label rename sources and targets
+ *
+ * This will also set self-similarity scores for MODIFIED files and
+ * mark them for splitting if break-rewrites is enabled
+ */
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ if (is_rename_source(diff, &opts, t, sigcache))
+ ++num_srcs;
- if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) {
- git_vector_foreach(&diff->deltas, i, from) {
- if (from->status != GIT_DELTA_MODIFIED)
- continue;
+ if (is_rename_target(diff, &opts, t, sigcache))
+ ++num_tgts;
+ }
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * i + 1);
+ /* if there are no candidate srcs or tgts, we're done */
+ if (!num_srcs || !num_tgts)
+ goto cleanup;
- if (similarity < 0) {
- error = similarity;
- goto cleanup;
- }
+ src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(src2tgt);
+ tgt2src = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src);
- if ((unsigned int)similarity < opts.break_rewrite_threshold) {
- from->flags |= GIT_DIFF_FLAG__TO_SPLIT;
- num_rewrites++;
- }
- }
+ if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
+ tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src_copy);
}
- /* next find the most similar delta for each rename / copy candidate */
-
- git_vector_foreach(&diff->deltas, i, from) {
- tried_targets = 0;
+ /*
+ * Find best-fit matches for rename / copy candidates
+ */
- /* skip things that aren't blobs */
- if (GIT_MODE_TYPE(from->old_file.mode) !=
- GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
- continue;
+find_best_matches:
+ tried_tgts = num_bumped = 0;
- /* don't check UNMODIFIED files as source unless given option */
- if (from->status == GIT_DELTA_UNMODIFIED &&
- !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
continue;
- /* skip all but DELETED files unless copy detection is on */
- if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES) &&
- from->status != GIT_DELTA_DELETED &&
- (from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
- continue;
+ tried_srcs = 0;
- git_vector_foreach(&diff->deltas, j, to) {
- if (i == j)
+ git_vector_foreach(&diff->deltas, s, src) {
+ /* skip things that are not rename sources */
+ if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0)
continue;
- /* skip things that aren't blobs */
- if (GIT_MODE_TYPE(to->new_file.mode) !=
- GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
- continue;
+ /* calculate similarity for this pair and find best match */
+ if (s == t)
+ similarity = -1; /* don't measure self-similarity here */
+ else if ((error = similarity_measure(
+ &similarity, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0)
+ goto cleanup;
- switch (to->status) {
- case GIT_DELTA_ADDED:
- case GIT_DELTA_UNTRACKED:
- case GIT_DELTA_RENAMED:
- case GIT_DELTA_COPIED:
- break;
- case GIT_DELTA_MODIFIED:
- if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
- continue;
- break;
- default:
- /* only the above status values should be checked */
+ if (similarity < 0)
continue;
- }
-
- /* cap on maximum files we'll examine (per "from" file) */
- if (++tried_targets > opts.target_limit)
- break;
-
- /* calculate similarity and see if this pair beats the
- * similarity score of the current best pair.
- */
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * j + 1);
- if (similarity < 0) {
- error = similarity;
- goto cleanup;
+ /* is this a better rename? */
+ if (tgt2src[t].similarity < (uint32_t)similarity &&
+ src2tgt[s].similarity < (uint32_t)similarity)
+ {
+ /* eject old mapping */
+ if (src2tgt[s].similarity > 0) {
+ tgt2src[src2tgt[s].idx].similarity = 0;
+ num_bumped++;
+ }
+ if (tgt2src[t].similarity > 0) {
+ src2tgt[tgt2src[t].idx].similarity = 0;
+ num_bumped++;
+ }
+
+ /* write new mapping */
+ tgt2src[t].idx = s;
+ tgt2src[t].similarity = (uint32_t)similarity;
+ src2tgt[s].idx = t;
+ src2tgt[s].similarity = (uint32_t)similarity;
}
- if (to->similarity < (unsigned int)similarity) {
- to->similarity = (unsigned int)similarity;
- matches[j] = i + 1;
+ /* keep best absolute match for copies */
+ if (tgt2src_copy != NULL &&
+ tgt2src_copy[t].similarity < (uint32_t)similarity)
+ {
+ tgt2src_copy[t].idx = s;
+ tgt2src_copy[t].similarity = (uint32_t)similarity;
}
+
+ if (++tried_srcs >= num_srcs)
+ break;
+
+ /* cap on maximum targets we'll examine (per "tgt" file) */
+ if (tried_srcs > opts.rename_limit)
+ break;
}
+
+ if (++tried_tgts >= num_tgts)
+ break;
}
- /* next rewrite the diffs with renames / copies */
+ if (num_bumped > 0) /* try again if we bumped some items */
+ goto find_best_matches;
+
+ /*
+ * Rewrite the diffs with renames / copies
+ */
+
+ tried_tgts = 0;
- git_vector_foreach(&diff->deltas, j, to) {
- if (!matches[j]) {
- assert(to->similarity == 0);
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
continue;
- }
- i = matches[j] - 1;
- from = GIT_VECTOR_GET(&diff->deltas, i);
- assert(from);
-
- /* four possible outcomes here:
- * 1. old DELETED and if over rename threshold,
- * new becomes RENAMED and old goes away
- * 2. old SPLIT and if over rename threshold,
- * new becomes RENAMED and old becomes ADDED (clear SPLIT)
- * 3. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and
- * old is more similar to new than it is to itself, in which
- * case, new becomes RENAMED and old becomed ADDED
- * 4. otherwise if over copy threshold, new becomes COPIED
+ /* check if this delta was the target of a similarity */
+ if (tgt2src[t].similarity)
+ best_match = &tgt2src[t];
+ else if (tgt2src_copy && tgt2src_copy[t].similarity)
+ best_match = &tgt2src_copy[t];
+ else
+ continue;
+
+ s = best_match->idx;
+ src = GIT_VECTOR_GET(&diff->deltas, s);
+
+ /* possible scenarios:
+ * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
+ * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE
+ * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME
+ * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT
+ * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
*/
- if (from->status == GIT_DELTA_DELETED) {
- if (to->similarity < opts.rename_threshold) {
- to->similarity = 0;
- continue;
- }
+ if (src->status == GIT_DELTA_DELETED) {
+
+ if (delta_is_new_only(tgt)) {
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
- from->flags |= GIT_DIFF_FLAG__TO_DELETE;
- num_rewrites++;
+ delta_make_rename(tgt, src, best_match->similarity);
- continue;
- }
+ src->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ num_rewrites++;
+ } else {
+ assert(delta_is_split(tgt));
- if (from->status == GIT_DELTA_MODIFIED &&
- (from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
- {
- if (to->similarity < opts.rename_threshold) {
- to->similarity = 0;
- continue;
- }
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
- from->status = GIT_DELTA_ADDED;
- from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
- memset(&from->old_file, 0, sizeof(from->old_file));
- num_rewrites--;
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
- continue;
- }
+ src->status = GIT_DELTA_DELETED;
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+ memset(&src->new_file, 0, sizeof(src->new_file));
+ src->new_file.path = src->old_file.path;
+ src->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
- if (from->status == GIT_DELTA_MODIFIED &&
- FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
- to->similarity > opts.rename_threshold)
- {
- similarity = similarity_measure(
- diff, &opts, cache, 2 * i, 2 * i + 1);
+ num_updates++;
- if (similarity < 0) {
- error = similarity;
- goto cleanup;
+ if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = (uint32_t)s;
+ }
}
+ }
- if ((unsigned int)similarity < opts.rename_from_rewrite_threshold) {
- to->status = GIT_DELTA_RENAMED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ else if (delta_is_split(src)) {
- from->status = GIT_DELTA_ADDED;
- memset(&from->old_file, 0, sizeof(from->old_file));
- from->old_file.path = to->old_file.path;
- from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+ if (delta_is_new_only(tgt)) {
- continue;
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
+
+ delta_make_rename(tgt, src, best_match->similarity);
+
+ src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
+ GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
+ memset(&src->old_file, 0, sizeof(src->old_file));
+ src->old_file.path = src->new_file.path;
+ src->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+
+ num_updates++;
+ } else {
+ assert(delta_is_split(src));
+
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
+
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
+ num_updates++;
+
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+
+ /* if we've just swapped the new element into the correct
+ * place, clear the SPLIT flag
+ */
+ if (tgt2src[s].idx == t &&
+ tgt2src[s].similarity >
+ opts.rename_from_rewrite_threshold) {
+ src->status = GIT_DELTA_RENAMED;
+ src->similarity = tgt2src[s].similarity;
+ tgt2src[s].similarity = 0;
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+ /* otherwise, if we just overwrote a source, update mapping */
+ else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = (uint32_t)s;
+ }
+
+ num_updates++;
}
}
- if (to->similarity < opts.copy_threshold) {
- to->similarity = 0;
- continue;
- }
+ else if (delta_is_new_only(tgt)) {
+ if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES))
+ continue;
+
+ if (tgt2src_copy[t].similarity < opts.copy_threshold)
+ continue;
+
+ /* always use best possible source for copy */
+ best_match = &tgt2src_copy[t];
+ src = GIT_VECTOR_GET(&diff->deltas, best_match->idx);
+
+ tgt->status = GIT_DELTA_COPIED;
+ tgt->similarity = best_match->similarity;
+ memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file));
- /* convert "to" to a COPIED record */
- to->status = GIT_DELTA_COPIED;
- memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ num_updates++;
+ }
}
- if (num_rewrites > 0) {
- assert(num_rewrites < diff->deltas.length);
+ /*
+ * Actually split and delete entries as needed
+ */
+ if (num_rewrites > 0 || num_updates > 0)
error = apply_splits_and_deletes(
- diff, diff->deltas.length - num_rewrites);
- }
+ diff, diff->deltas.length - num_rewrites,
+ FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES));
cleanup:
- git__free(matches);
+ git__free(tgt2src);
+ git__free(src2tgt);
+ git__free(tgt2src_copy);
- for (i = 0; i < cache_size; ++i) {
- if (cache[i] != NULL)
- opts.metric->free_signature(cache[i], opts.metric->payload);
+ for (t = 0; t < num_deltas * 2; ++t) {
+ if (sigcache[t] != NULL)
+ opts.metric->free_signature(sigcache[t], opts.metric->payload);
}
- git__free(cache);
+ git__free(sigcache);
if (!given_opts || !given_opts->metric)
git__free(opts.metric);
diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c
new file mode 100644
index 000000000..7694fb996
--- /dev/null
+++ b/src/diff_xdiff.c
@@ -0,0 +1,166 @@
+/*
+ * 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 "diff.h"
+#include "diff_driver.h"
+#include "diff_patch.h"
+#include "diff_xdiff.h"
+
+static int git_xdiff_scan_int(const char **str, int *value)
+{
+ const char *scan = *str;
+ int v = 0, digits = 0;
+ /* find next digit */
+ for (scan = *str; *scan && !git__isdigit(*scan); scan++);
+ /* parse next number */
+ for (; git__isdigit(*scan); scan++, digits++)
+ v = (v * 10) + (*scan - '0');
+ *str = scan;
+ *value = v;
+ return (digits > 0) ? 0 : -1;
+}
+
+static int git_xdiff_parse_hunk(git_diff_range *range, const char *header)
+{
+ /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
+ if (*header != '@')
+ return -1;
+ if (git_xdiff_scan_int(&header, &range->old_start) < 0)
+ return -1;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &range->old_lines) < 0)
+ return -1;
+ } else
+ range->old_lines = 1;
+ if (git_xdiff_scan_int(&header, &range->new_start) < 0)
+ return -1;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &range->new_lines) < 0)
+ return -1;
+ } else
+ range->new_lines = 1;
+ if (range->old_start < 0 || range->new_start < 0)
+ return -1;
+
+ return 0;
+}
+
+typedef struct {
+ git_xdiff_output *xo;
+ git_diff_patch *patch;
+ git_diff_range range;
+} git_xdiff_info;
+
+static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
+{
+ git_xdiff_info *info = priv;
+ git_diff_patch *patch = info->patch;
+ const git_diff_delta *delta = git_diff_patch_delta(patch);
+ git_diff_output *output = &info->xo->output;
+
+ if (len == 1) {
+ output->error = git_xdiff_parse_hunk(&info->range, bufs[0].ptr);
+ if (output->error < 0)
+ return output->error;
+
+ if (output->hunk_cb != NULL &&
+ output->hunk_cb(delta, &info->range,
+ bufs[0].ptr, bufs[0].size, output->payload))
+ output->error = GIT_EUSER;
+ }
+
+ if (len == 2 || len == 3) {
+ /* expect " "/"-"/"+", then data */
+ char origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
+ GIT_DIFF_LINE_CONTEXT;
+
+ if (output->data_cb != NULL &&
+ output->data_cb(delta, &info->range,
+ origin, bufs[1].ptr, bufs[1].size, output->payload))
+ output->error = GIT_EUSER;
+ }
+
+ if (len == 3 && !output->error) {
+ /* If we have a '+' and a third buf, then we have added a line
+ * without a newline and the old code had one, so DEL_EOFNL.
+ * If we have a '-' and a third buf, then we have removed a line
+ * with out a newline but added a blank line, so ADD_EOFNL.
+ */
+ char origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
+ GIT_DIFF_LINE_CONTEXT_EOFNL;
+
+ if (output->data_cb != NULL &&
+ output->data_cb(delta, &info->range,
+ origin, bufs[2].ptr, bufs[2].size, output->payload))
+ output->error = GIT_EUSER;
+ }
+
+ return output->error;
+}
+
+static int git_xdiff(git_diff_output *output, git_diff_patch *patch)
+{
+ git_xdiff_output *xo = (git_xdiff_output *)output;
+ git_xdiff_info info;
+ git_diff_find_context_payload findctxt;
+ mmfile_t xd_old_data, xd_new_data;
+
+ memset(&info, 0, sizeof(info));
+ info.patch = patch;
+ info.xo = xo;
+
+ xo->callback.priv = &info;
+
+ git_diff_find_context_init(
+ &xo->config.find_func, &findctxt, git_diff_patch__driver(patch));
+ xo->config.find_func_priv = &findctxt;
+
+ if (xo->config.find_func != NULL)
+ xo->config.flags |= XDL_EMIT_FUNCNAMES;
+ else
+ xo->config.flags &= ~XDL_EMIT_FUNCNAMES;
+
+ /* TODO: check ofile.opts_flags to see if driver-specific per-file
+ * updates are needed to xo->params.flags
+ */
+
+ git_diff_patch__old_data(&xd_old_data.ptr, &xd_old_data.size, patch);
+ git_diff_patch__new_data(&xd_new_data.ptr, &xd_new_data.size, patch);
+
+ xdl_diff(&xd_old_data, &xd_new_data,
+ &xo->params, &xo->config, &xo->callback);
+
+ git_diff_find_context_clear(&findctxt);
+
+ return xo->output.error;
+}
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts)
+{
+ uint32_t flags = opts ? opts->flags : GIT_DIFF_NORMAL;
+
+ xo->output.diff_cb = git_xdiff;
+
+ memset(&xo->config, 0, sizeof(xo->config));
+ xo->config.ctxlen = opts ? opts->context_lines : 3;
+ xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0;
+
+ memset(&xo->params, 0, sizeof(xo->params));
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE)
+ xo->params.flags |= XDF_WHITESPACE_FLAGS;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+ memset(&xo->callback, 0, sizeof(xo->callback));
+ xo->callback.outf = git_xdiff_cb;
+}
diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h
new file mode 100644
index 000000000..c547b00cf
--- /dev/null
+++ b/src/diff_xdiff.h
@@ -0,0 +1,28 @@
+/*
+ * 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_diff_xdiff_h__
+#define INCLUDE_diff_xdiff_h__
+
+#include "diff.h"
+#include "diff_patch.h"
+#include "xdiff/xdiff.h"
+
+/* A git_xdiff_output is a git_diff_output with extra fields necessary
+ * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
+ * of the output to use xdiff to generate the diffs.
+ */
+typedef struct {
+ git_diff_output output;
+
+ xdemitconf_t config;
+ xpparam_t params;
+ xdemitcb_t callback;
+} git_xdiff_output;
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts);
+
+#endif
diff --git a/src/fetch.c b/src/fetch.c
index b60a95232..03fad5fec 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -16,6 +16,8 @@
#include "pack.h"
#include "fetch.h"
#include "netops.h"
+#include "repository.h"
+#include "refs.h"
struct filter_payload {
git_remote *remote;
@@ -34,10 +36,16 @@ static int filter_ref__cb(git_remote_head *head, void *payload)
if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0)
p->found_head = 1;
- else if (git_refspec_src_matches(p->spec, head->name))
+ else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ /*
+ * If tagopt is --tags, then we only use the default
+ * tags refspec and ignore the remote's
+ */
+ if (git_refspec_src_matches(p->tagspec, head->name))
match = 1;
- else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&
- git_refspec_src_matches(p->tagspec, head->name))
+ else
+ return 0;
+ } else if (git_remote__matching_refspec(p->remote, head->name))
match = 1;
if (!match)
@@ -68,7 +76,6 @@ static int filter_wants(git_remote *remote)
* not interested in any particular branch but just the remote's
* HEAD, which will be stored in FETCH_HEAD after the fetch.
*/
- p.spec = git_remote_fetchspec(remote);
p.tagspec = &tagspec;
p.found_head = 0;
p.remote = remote;
diff --git a/src/fileops.c b/src/fileops.c
index d6244711f..7f8418d7a 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -61,9 +61,11 @@ int git_futils_creat_locked(const char *path, const mode_t mode)
wchar_t buf[GIT_WIN_PATH];
git__utf8_to_16(buf, GIT_WIN_PATH, path);
- fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
+ fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC |
+ O_EXCL | O_BINARY | O_CLOEXEC, mode);
#else
- fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC |
+ O_EXCL | O_BINARY | O_CLOEXEC, mode);
#endif
if (fd < 0) {
@@ -202,6 +204,33 @@ int git_futils_readbuffer(git_buf *buf, const char *path)
return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
}
+int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int flags, mode_t mode)
+{
+ int fd, error = 0;
+
+ if (flags <= 0)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+ if (!mode)
+ mode = GIT_FILEMODE_BLOB;
+
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ return error;
+ }
+
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ return error;
+}
+
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{
if (git_futils_mkpath2file(to, dirmode) < 0)
@@ -253,8 +282,9 @@ int git_futils_mkdir(
{
int error = -1;
git_buf make_path = GIT_BUF_INIT;
- ssize_t root = 0;
- char lastch, *tail;
+ ssize_t root = 0, min_root_len;
+ char lastch = '/', *tail;
+ struct stat st;
/* build path and find "root" where we should start calling mkdir */
if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
@@ -262,7 +292,7 @@ int git_futils_mkdir(
if (make_path.size == 0) {
giterr_set(GITERR_OS, "Attempt to create empty path");
- goto fail;
+ goto done;
}
/* remove trailing slashes on path */
@@ -279,19 +309,32 @@ int git_futils_mkdir(
if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
git_buf_rtruncate_at_char(&make_path, '/');
+ /* if nothing left after truncation, then we're done! */
+ if (!make_path.size) {
+ error = 0;
+ goto done;
+ }
+
/* if we are not supposed to make the whole path, reset root */
if ((flags & GIT_MKDIR_PATH) == 0)
root = git_buf_rfind(&make_path, '/');
+ /* advance root past drive name or network mount prefix */
+ min_root_len = git_path_root(make_path.ptr);
+ if (root < min_root_len)
+ root = min_root_len;
+ while (root >= 0 && make_path.ptr[root] == '/')
+ ++root;
+
/* clip root to make_path length */
- if (root >= (ssize_t)make_path.size)
- root = (ssize_t)make_path.size - 1;
+ if (root > (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
if (root < 0)
root = 0;
- tail = & make_path.ptr[root];
+ /* walk down tail of path making each directory */
+ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
- while (*tail) {
/* advance tail to include next path component */
while (*tail == '/')
tail++;
@@ -301,68 +344,48 @@ int git_futils_mkdir(
/* truncate path at next component */
lastch = *tail;
*tail = '\0';
+ st.st_mode = 0;
/* make directory */
if (p_mkdir(make_path.ptr, mode) < 0) {
- int already_exists = 0;
-
- switch (errno) {
- case EEXIST:
- if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
- !git_path_isdir(make_path.ptr)) {
- giterr_set(
- GITERR_OS, "Existing path is not a directory '%s'",
- make_path.ptr);
- error = GIT_ENOTFOUND;
- goto fail;
- }
-
- already_exists = 1;
- break;
- case ENOSYS:
- /* Solaris can generate this error if you try to mkdir
- * a path which is already a mount point. In that case,
- * the path does already exist; but it's not implied by
- * the definition of the error, so let's recheck */
- if (git_path_isdir(make_path.ptr)) {
- already_exists = 1;
- break;
- }
-
- /* Fall through */
- errno = ENOSYS;
- default:
- giterr_set(GITERR_OS, "Failed to make directory '%s'",
- make_path.ptr);
- goto fail;
+ int tmp_errno = errno;
+
+ /* ignore error if directory already exists */
+ if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ errno = tmp_errno;
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
+ goto done;
}
- if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) {
- giterr_set(GITERR_OS, "Directory already exists '%s'",
- make_path.ptr);
+ /* with exclusive create, existing dir is an error */
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'", make_path.ptr);
error = GIT_EEXISTS;
- goto fail;
+ goto done;
}
}
- /* chmod if requested */
- if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
- ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
- {
- if (p_chmod(make_path.ptr, mode) < 0) {
- giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
- make_path.ptr);
- goto fail;
- }
+ /* chmod if requested and necessary */
+ if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
+ (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
+ st.st_mode != mode &&
+ (error = p_chmod(make_path.ptr, mode)) < 0) {
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
+ goto done;
}
-
- *tail = lastch;
}
- git_buf_free(&make_path);
- return 0;
+ error = 0;
+
+ /* check that full path really is a directory if requested & needed */
+ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ lastch != '\0' &&
+ (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
+ giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
+ error = GIT_ENOTFOUND;
+ }
-fail:
+done:
git_buf_free(&make_path);
return error;
}
@@ -444,7 +467,7 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
if (data->error < 0) {
if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
- (errno == ENOTEMPTY || errno == EEXIST))
+ (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
data->error = 0;
else
futils__error_cannot_rmdir(path->ptr, NULL);
@@ -480,7 +503,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
if (en == ENOENT || en == ENOTDIR) {
giterr_clear();
error = 0;
- } else if (en == ENOTEMPTY || en == EEXIST) {
+ } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
giterr_clear();
error = GIT_ITEROVER;
} else {
@@ -605,6 +628,18 @@ static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {
git_futils_guess_xdg_dirs,
};
+int git_futils_dirs_global_init(void)
+{
+ git_futils_dir_t i;
+ const git_buf *path;
+ int error = 0;
+
+ for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++)
+ error = git_futils_dirs_get(&path, i);
+
+ return error;
+}
+
static int git_futils_check_selector(git_futils_dir_t which)
{
if (which < GIT_FUTILS_DIR__MAX)
@@ -988,8 +1023,10 @@ int git_futils_filestamp_check(
if (stamp == NULL)
return 1;
- if (p_stat(path, &st) < 0)
+ if (p_stat(path, &st) < 0) {
+ giterr_set(GITERR_OS, "Could not stat '%s'", path);
return GIT_ENOTFOUND;
+ }
if (stamp->mtime == (git_time_t)st.st_mtime &&
stamp->size == (git_off_t)st.st_size &&
diff --git a/src/fileops.h b/src/fileops.h
index 627a6923d..5adedfc57 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -22,6 +22,9 @@ extern int git_futils_readbuffer_updated(
git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
+extern int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int open_flags, mode_t mode);
+
/**
* File utils
*
@@ -223,6 +226,7 @@ extern git_off_t git_futils_filesize(git_file fd);
#define GIT_MODE_PERMS_MASK 0777
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
+#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
/**
* Convert a mode_t from the OS to a legal git mode_t value.
@@ -240,7 +244,7 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode);
* @param out buffer to populate with the mapping information.
* @param fd open descriptor to configure the mapping from.
* @param begin first byte to map, this should be page aligned.
- * @param end number of bytes to map.
+ * @param len number of bytes to map.
* @return
* - 0 on success;
* - -1 on error.
@@ -274,7 +278,7 @@ extern void git_futils_mmap_free(git_map *map);
/**
* Find a "global" file (i.e. one in a user's home directory).
*
- * @param pathbuf buffer to write the full path into
+ * @param path 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
*/
@@ -283,7 +287,7 @@ 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 path 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
*/
@@ -292,7 +296,7 @@ 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 path 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
*/
@@ -306,6 +310,13 @@ typedef enum {
} git_futils_dir_t;
/**
+ * Configures global data for configuration file search paths.
+ *
+ * @return 0 on success, <0 on failure
+ */
+extern int git_futils_dirs_global_init(void);
+
+/**
* Get the search path for global/system/xdg files
*
* @param out pointer to git_buf containing search path
diff --git a/src/global.c b/src/global.c
index b7fd8e257..a06d0c81f 100644
--- a/src/global.c
+++ b/src/global.c
@@ -61,13 +61,12 @@ int git_threads_init(void)
return 0;
_tls_index = TlsAlloc();
- git_mutex_init(&git__mwindow_mutex);
+ if (git_mutex_init(&git__mwindow_mutex))
+ return -1;
/* Initialize any other subsystems that have global state */
- if ((error = git_hash_global_init()) >= 0)
- _tls_init = 1;
-
- if (error == 0)
+ if ((error = git_hash_global_init()) >= 0 &&
+ (error = git_futils_dirs_global_init()) >= 0)
_tls_init = 1;
GIT_MEMORY_BARRIER;
@@ -121,11 +120,13 @@ int git_threads_init(void)
if (_tls_init)
return 0;
- git_mutex_init(&git__mwindow_mutex);
+ if (git_mutex_init(&git__mwindow_mutex))
+ return -1;
pthread_key_create(&_tls_key, &cb__free_status);
/* Initialize any other subsystems that have global state */
- if ((error = git_hash_global_init()) >= 0)
+ if ((error = git_hash_global_init()) >= 0 &&
+ (error = git_futils_dirs_global_init()) >= 0)
_tls_init = 1;
GIT_MEMORY_BARRIER;
@@ -135,6 +136,12 @@ int git_threads_init(void)
void git_threads_shutdown(void)
{
+ if (_tls_init) {
+ void *ptr = pthread_getspecific(_tls_key);
+ pthread_setspecific(_tls_key, NULL);
+ git__free(ptr);
+ }
+
pthread_key_delete(_tls_key);
_tls_init = 0;
git_mutex_free(&git__mwindow_mutex);
diff --git a/src/global.h b/src/global.h
index f0ad1df29..badbc0883 100644
--- a/src/global.h
+++ b/src/global.h
@@ -10,14 +10,6 @@
#include "mwindow.h"
#include "hash.h"
-#if defined(GIT_THREADS) && defined(_MSC_VER)
-# define GIT_MEMORY_BARRIER MemoryBarrier()
-#elif defined(GIT_THREADS)
-# define GIT_MEMORY_BARRIER __sync_synchronize()
-#else
-# define GIT_MEMORY_BARRIER /* noop */
-#endif
-
typedef struct {
git_error *last_error;
git_error error_t;
diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h
index b731de8b3..6b60c98c4 100644
--- a/src/hash/hash_generic.h
+++ b/src/hash/hash_generic.h
@@ -11,9 +11,9 @@
#include "hash.h"
struct git_hash_ctx {
- unsigned long long size;
- unsigned int H[5];
- unsigned int W[16];
+ unsigned long long size;
+ unsigned int H[5];
+ unsigned int W[16];
};
#define git_hash_global_init() 0
diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h
index daa769b59..2eee5ca79 100644
--- a/src/hash/hash_win32.h
+++ b/src/hash/hash_win32.h
@@ -48,10 +48,10 @@ struct hash_cryptoapi_prov {
/* Function declarations for CNG */
typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)(
- HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
- LPCWSTR pszAlgId,
- LPCWSTR pszImplementation,
- DWORD dwFlags);
+ HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ DWORD dwFlags);
typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)(
HANDLE /* BCRYPT_HANDLE */ hObject,
diff --git a/src/hashsig.c b/src/hashsig.c
index 3a75aaaed..109f966ba 100644
--- a/src/hashsig.c
+++ b/src/hashsig.c
@@ -13,12 +13,15 @@ typedef uint64_t hashsig_state;
#define HASHSIG_SCALE 100
-#define HASHSIG_HASH_WINDOW 32
-#define HASHSIG_HASH_START 0
+#define HASHSIG_MAX_RUN 80
+#define HASHSIG_HASH_START 0x012345678ABCDEF0LL
#define HASHSIG_HASH_SHIFT 5
-#define HASHSIG_HASH_MASK 0x7FFFFFFF
+
+#define HASHSIG_HASH_MIX(S,CH) \
+ (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH)
#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
+#define HASHSIG_HEAP_MIN_SIZE 4
typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
@@ -28,14 +31,6 @@ typedef struct {
hashsig_t values[HASHSIG_HEAP_SIZE];
} hashsig_heap;
-typedef struct {
- hashsig_state state, shift_n;
- char window[HASHSIG_HASH_WINDOW];
- int win_len, win_pos, saw_lf;
-} hashsig_in_progress;
-
-#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 }
-
struct git_hashsig {
hashsig_heap mins;
hashsig_heap maxs;
@@ -43,8 +38,8 @@ struct git_hashsig {
int considered;
};
-#define HEAP_LCHILD_OF(I) (((I)*2)+1)
-#define HEAP_RCHILD_OF(I) (((I)*2)+2)
+#define HEAP_LCHILD_OF(I) (((I)<<1)+1)
+#define HEAP_RCHILD_OF(I) (((I)<<1)+2)
#define HEAP_PARENT_OF(I) (((I)-1)>>1)
static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
@@ -115,134 +110,109 @@ static void hashsig_heap_sort(hashsig_heap *h)
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], NULL) > 0) {
- h->size--;
- h->values[0] = h->values[h->size];
- hashsig_heap_down(h, 0);
- }
-
/* if heap is not full, insert new element */
if (h->size < h->asize) {
h->values[h->size++] = val;
hashsig_heap_up(h, h->size - 1);
}
-}
-
-GIT_INLINE(bool) hashsig_include_char(
- char ch, git_hashsig_option_t opt, int *saw_lf)
-{
- if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch))
- return false;
-
- if (opt & GIT_HASHSIG_SMART_WHITESPACE) {
- if (ch == '\r' || (*saw_lf && git__isspace(ch)))
- return false;
- *saw_lf = (ch == '\n');
+ /* if heap is full, pop top if new element should replace it */
+ else if (h->cmp(&val, &h->values[0], NULL) > 0) {
+ h->size--;
+ h->values[0] = h->values[h->size];
+ hashsig_heap_down(h, 0);
}
- return true;
}
-static void hashsig_initial_window(
- git_hashsig *sig,
- const char **data,
- size_t size,
- hashsig_in_progress *prog)
-{
- hashsig_state state, shift_n;
- int win_len;
- const char *scan, *end;
-
- /* init until we have processed at least HASHSIG_HASH_WINDOW data */
-
- if (prog->win_len >= HASHSIG_HASH_WINDOW)
- return;
-
- state = prog->state;
- win_len = prog->win_len;
- shift_n = prog->shift_n;
-
- scan = *data;
- end = scan + size;
-
- while (scan < end && win_len < HASHSIG_HASH_WINDOW) {
- char ch = *scan++;
-
- if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))
- continue;
-
- state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK;
-
- if (!win_len)
- shift_n = 1;
- else
- shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;
-
- prog->window[win_len++] = ch;
- }
-
- /* insert initial hash if we just finished */
+typedef struct {
+ int use_ignores;
+ uint8_t ignore_ch[256];
+} hashsig_in_progress;
- if (win_len == HASHSIG_HASH_WINDOW) {
- hashsig_heap_insert(&sig->mins, (hashsig_t)state);
- hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
- sig->considered = 1;
+static void hashsig_in_progress_init(
+ hashsig_in_progress *prog, git_hashsig *sig)
+{
+ int i;
+
+ switch (sig->opt) {
+ case GIT_HASHSIG_IGNORE_WHITESPACE:
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace_nonlf(i);
+ prog->use_ignores = 1;
+ break;
+ case GIT_HASHSIG_SMART_WHITESPACE:
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace(i);
+ prog->use_ignores = 1;
+ break;
+ default:
+ memset(prog, 0, sizeof(*prog));
+ break;
}
-
- prog->state = state;
- prog->win_len = win_len;
- prog->shift_n = shift_n;
-
- *data = scan;
}
+#define HASHSIG_IN_PROGRESS_INIT { 1 }
+
static int hashsig_add_hashes(
git_hashsig *sig,
- const char *data,
+ const uint8_t *data,
size_t size,
hashsig_in_progress *prog)
{
- const char *scan = data, *end = data + size;
- hashsig_state state, shift_n, rmv;
-
- if (prog->win_len < HASHSIG_HASH_WINDOW)
- hashsig_initial_window(sig, &scan, size, prog);
-
- state = prog->state;
- shift_n = prog->shift_n;
-
- /* advance window, adding new chars and removing old */
-
- for (; scan < end; ++scan) {
- char ch = *scan;
-
- if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))
- continue;
-
- rmv = shift_n * prog->window[prog->win_pos];
+ const uint8_t *scan = data, *end = data + size;
+ hashsig_state state = HASHSIG_HASH_START;
+ int use_ignores = prog->use_ignores, len;
+ uint8_t ch;
+
+ while (scan < end) {
+ state = HASHSIG_HASH_START;
+
+ for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) {
+ ch = *scan;
+
+ if (use_ignores)
+ for (; scan < end && git__isspace_nonlf(ch); ch = *scan)
+ ++scan;
+ else if (sig->opt != GIT_HASHSIG_NORMAL)
+ for (; scan < end && ch == '\r'; ch = *scan)
+ ++scan;
+
+ /* peek at next character to decide what to do next */
+ if (sig->opt == GIT_HASHSIG_SMART_WHITESPACE)
+ use_ignores = (ch == '\n');
+
+ if (scan >= end)
+ break;
+ ++scan;
+
+ /* check run terminator */
+ if (ch == '\n' || ch == '\0')
+ break;
+
+ ++len;
+ HASHSIG_HASH_MIX(state, ch);
+ }
- state = (state - rmv) & HASHSIG_HASH_MASK;
- state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;
- state = (state + ch) & HASHSIG_HASH_MASK;
+ if (len > 0) {
+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);
+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
- hashsig_heap_insert(&sig->mins, (hashsig_t)state);
- hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
- sig->considered++;
+ sig->considered++;
- prog->window[prog->win_pos] = ch;
- prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW;
+ while (scan < end && (*scan == '\n' || !*scan))
+ ++scan;
+ }
}
- prog->state = state;
+ prog->use_ignores = use_ignores;
return 0;
}
static int hashsig_finalize_hashes(git_hashsig *sig)
{
- if (sig->mins.size < HASHSIG_HEAP_SIZE) {
+ if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE) {
giterr_set(GITERR_INVALID,
"File too small for similarity signature calculation");
return GIT_EBUFS;
@@ -274,11 +244,13 @@ int git_hashsig_create(
git_hashsig_option_t opts)
{
int error;
- hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;
+ hashsig_in_progress prog;
git_hashsig *sig = hashsig_alloc(opts);
GITERR_CHECK_ALLOC(sig);
- error = hashsig_add_hashes(sig, buf, buflen, &prog);
+ hashsig_in_progress_init(&prog, sig);
+
+ error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog);
if (!error)
error = hashsig_finalize_hashes(sig);
@@ -296,10 +268,10 @@ int git_hashsig_create_fromfile(
const char *path,
git_hashsig_option_t opts)
{
- char buf[4096];
+ uint8_t buf[0x1000];
ssize_t buflen = 0;
int error = 0, fd;
- hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;
+ hashsig_in_progress prog;
git_hashsig *sig = hashsig_alloc(opts);
GITERR_CHECK_ALLOC(sig);
@@ -308,6 +280,8 @@ int git_hashsig_create_fromfile(
return fd;
}
+ hashsig_in_progress_init(&prog, sig);
+
while (!error) {
if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
if ((error = (int)buflen) < 0)
@@ -362,7 +336,12 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
{
- return (hashsig_heap_compare(&a->mins, &b->mins) +
- hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
+ /* if we have fewer than the maximum number of elements, then just use
+ * one array since the two arrays will be the same
+ */
+ if (a->mins.size < HASHSIG_HEAP_SIZE)
+ return hashsig_heap_compare(&a->mins, &b->mins);
+ else
+ return (hashsig_heap_compare(&a->mins, &b->mins) +
+ hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
}
-
diff --git a/src/ignore.c b/src/ignore.c
index dae974b6e..7d8280403 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -15,24 +15,14 @@ static int parse_ignore_file(
git_attr_fnmatch *match = NULL;
const char *scan = NULL;
char *context = NULL;
- bool ignore_case = false;
- git_config *cfg = NULL;
- int val;
-
- /* Prefer to have the caller pass in a git_ignores as the parsedata object.
- * If they did not, then we can (much more slowly) find the value of
- * ignore_case by using the repository object. */
- if (parsedata != NULL) {
- ignore_case = ((git_ignores *)parsedata)->ignore_case;
- } else {
- if ((error = git_repository_config(&cfg, repo)) < 0)
- return error;
-
- if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
- ignore_case = (val != 0);
+ int ignore_case = false;
- git_config_free(cfg);
- }
+ /* Prefer to have the caller pass in a git_ignores as the parsedata
+ * object. If they did not, then look up the value of ignore_case */
+ if (parsedata != NULL)
+ ignore_case = ((git_ignores *)parsedata)->ignore_case;
+ else if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
+ return error;
if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) {
context = ignores->key + 2;
@@ -109,8 +99,6 @@ int git_ignore__for_path(
{
int error = 0;
const char *workdir = git_repository_workdir(repo);
- git_config *cfg = NULL;
- int val;
assert(ignores);
@@ -118,17 +106,11 @@ int git_ignore__for_path(
git_buf_init(&ignores->dir, 0);
ignores->ign_internal = NULL;
- /* Set the ignore_case flag appropriately */
- if ((error = git_repository_config(&cfg, repo)) < 0)
+ /* Read the ignore_case flag */
+ if ((error = git_repository__cvar(
+ &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
goto cleanup;
- if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
- ignores->ignore_case = (val != 0);
- else
- ignores->ignore_case = 0;
-
- git_config_free(cfg);
-
if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 ||
(error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 ||
(error = git_attr_cache__init(repo)) < 0)
@@ -358,3 +340,61 @@ cleanup:
return error;
}
+
+int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo,
+ git_vector *vspec,
+ bool no_fnmatch)
+{
+ int error = 0;
+ size_t i;
+ git_attr_fnmatch *match;
+ int ignored;
+ git_buf path = GIT_BUF_INIT;
+ const char *wd, *filename;
+ git_index *idx;
+
+ if ((error = git_repository__ensure_not_bare(
+ repo, "validate pathspec")) < 0 ||
+ (error = git_repository_index(&idx, repo)) < 0)
+ return error;
+
+ wd = git_repository_workdir(repo);
+
+ git_vector_foreach(vspec, i, match) {
+ /* skip wildcard matches (if they are being used) */
+ if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
+ !no_fnmatch)
+ continue;
+
+ filename = match->pattern;
+
+ /* if file is already in the index, it's fine */
+ if (git_index_get_bypath(idx, filename, 0) != NULL)
+ continue;
+
+ if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
+ break;
+
+ /* is there a file on disk that matches this exactly? */
+ if (!git_path_isfile(path.ptr))
+ continue;
+
+ /* is that file ignored? */
+ if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
+ break;
+
+ if (ignored) {
+ giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
+ filename);
+ error = GIT_EINVALIDSPEC;
+ break;
+ }
+ }
+
+ git_index_free(idx);
+ git_buf_free(&path);
+
+ return error;
+}
+
diff --git a/src/ignore.h b/src/ignore.h
index 5af8e8e7d..cc114b001 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -28,7 +28,7 @@ typedef struct {
git_attr_file *ign_internal;
git_vector ign_path;
git_vector ign_global;
- unsigned int ignore_case:1;
+ int ignore_case;
} git_ignores;
extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign);
@@ -41,4 +41,13 @@ extern void git_ignore__free(git_ignores *ign);
extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored);
+/* command line Git sometimes generates an error message if given a
+ * pathspec that contains an exact match to an ignored file (provided
+ * --force isn't also given). This makes it easy to check it that has
+ * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored
+ * exact matches (that are not already present in the index).
+ */
+extern int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo, git_vector *pathspec, bool no_fnmatch);
+
#endif
diff --git a/src/index.c b/src/index.c
index 6290ec4e8..cbdd43bdc 100644
--- a/src/index.c
+++ b/src/index.c
@@ -15,10 +15,14 @@
#include "hash.h"
#include "iterator.h"
#include "pathspec.h"
+#include "ignore.h"
+#include "blob.h"
+
#include "git2/odb.h"
#include "git2/oid.h"
#include "git2/blob.h"
#include "git2/config.h"
+#include "git2/sys/index.h"
#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
#define short_entry_size(len) entry_size(struct entry_short, len)
@@ -35,6 +39,7 @@ static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;
static const unsigned int INDEX_HEADER_SIG = 0x44495243;
static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};
static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'};
+static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'};
#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx)))
@@ -97,16 +102,9 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
static bool is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file);
-static int index_find(size_t *at_pos, git_index *index, const char *path, int stage);
-
static void index_entry_free(git_index_entry *entry);
static void index_entry_reuc_free(git_index_reuc_entry *reuc);
-GIT_INLINE(int) index_entry_stage(const git_index_entry *entry)
-{
- return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
-}
-
static int index_srch(const void *key, const void *array_member)
{
const struct entry_srch_key *srch_key = key;
@@ -115,8 +113,8 @@ static int index_srch(const void *key, const void *array_member)
ret = strcmp(srch_key->path, entry->path);
- if (ret == 0)
- ret = srch_key->stage - index_entry_stage(entry);
+ if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY)
+ ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry);
return ret;
}
@@ -129,8 +127,8 @@ static int index_isrch(const void *key, const void *array_member)
ret = strcasecmp(srch_key->path, entry->path);
- if (ret == 0)
- ret = srch_key->stage - index_entry_stage(entry);
+ if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY)
+ ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry);
return ret;
}
@@ -168,7 +166,7 @@ static int index_cmp(const void *a, const void *b)
diff = strcmp(entry_a->path, entry_b->path);
if (diff == 0)
- diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
return diff;
}
@@ -182,11 +180,56 @@ static int index_icmp(const void *a, const void *b)
diff = strcasecmp(entry_a->path, entry_b->path);
if (diff == 0)
- diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
return diff;
}
+static int conflict_name_cmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcmp(name_a->ours, name_b->ours);
+}
+
+/**
+ * TODO: enable this when resolving case insensitive conflicts
+ */
+#if 0
+static int conflict_name_icmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcasecmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcasecmp(name_a->ours, name_b->ours);
+}
+#endif
+
static int reuc_srch(const void *key, const void *array_member)
{
const git_index_reuc_entry *reuc = array_member;
@@ -217,6 +260,22 @@ static int reuc_icmp(const void *a, const void *b)
return strcasecmp(info_a->path, info_b->path);
}
+static void index_entry_reuc_free(git_index_reuc_entry *reuc)
+{
+ if (!reuc)
+ return;
+ git__free(reuc->path);
+ git__free(reuc);
+}
+
+static void index_entry_free(git_index_entry *entry)
+{
+ if (!entry)
+ return;
+ git__free(entry->path);
+ git__free(entry);
+}
+
static unsigned int index_create_mode(unsigned int mode)
{
if (S_ISLNK(mode))
@@ -246,16 +305,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case)
{
index->ignore_case = ignore_case;
- index->entries._cmp = ignore_case ? index_icmp : index_cmp;
index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
index->entries_search = ignore_case ? index_isrch : index_srch;
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
- index->entries.sorted = 0;
+
+ git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp);
git_vector_sort(&index->entries);
- index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;
index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
- index->reuc.sorted = 0;
+
+ git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp);
git_vector_sort(&index->reuc);
}
@@ -278,6 +337,7 @@ int git_index_open(git_index **index_out, const char *index_path)
}
if (git_vector_init(&index->entries, 32, index_cmp) < 0 ||
+ git_vector_init(&index->names, 32, conflict_name_cmp) < 0 ||
git_vector_init(&index->reuc, 32, reuc_cmp) < 0)
return -1;
@@ -301,9 +361,12 @@ static void index_free(git_index *index)
{
git_index_clear(index);
git_vector_free(&index->entries);
+ git_vector_free(&index->names);
git_vector_free(&index->reuc);
git__free(index->index_file_path);
+
+ git__memzero(index, sizeof(*index));
git__free(index);
}
@@ -315,22 +378,24 @@ void git_index_free(git_index *index)
GIT_REFCOUNT_DEC(index, index_free);
}
-void git_index_clear(git_index *index)
+static void index_entries_free(git_vector *entries)
{
size_t i;
- assert(index);
+ for (i = 0; i < entries->length; ++i)
+ index_entry_free(git__swap(entries->contents[i], NULL));
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *e;
- e = git_vector_get(&index->entries, i);
- git__free(e->path);
- git__free(e);
- }
- git_vector_clear(&index->entries);
+ git_vector_clear(entries);
+}
+
+void git_index_clear(git_index *index)
+{
+ assert(index);
+ index_entries_free(&index->entries);
git_index_reuc_clear(index);
-
+ git_index_name_clear(index);
+
git_futils_filestamp_set(&index->stamp, NULL);
git_tree_cache_free(index->tree);
@@ -352,19 +417,18 @@ int git_index_set_caps(git_index *index, unsigned int caps)
old_ignore_case = index->ignore_case;
if (caps == GIT_INDEXCAP_FROM_OWNER) {
- git_config *cfg;
+ git_repository *repo = INDEX_OWNER(index);
int val;
- if (INDEX_OWNER(index) == NULL ||
- git_repository_config__weakptr(&cfg, INDEX_OWNER(index)) < 0)
- return create_index_error(-1,
- "Cannot get repository config to set index caps");
+ if (!repo)
+ return create_index_error(
+ -1, "Cannot access repository to set index caps");
- if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORECASE))
index->ignore_case = (val != 0);
- if (git_config_get_bool(&val, cfg, "core.filemode") == 0)
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE))
index->distrust_filemode = (val == 0);
- if (git_config_get_bool(&val, cfg, "core.symlinks") == 0)
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS))
index->no_symlinks = (val == 0);
}
else {
@@ -453,6 +517,12 @@ int git_index_write(git_index *index)
return 0;
}
+const char * git_index_path(git_index *index)
+{
+ assert(index);
+ return index->index_file_path;
+}
+
int git_index_write_tree(git_oid *oid, git_index *index)
{
git_repository *repo;
@@ -497,8 +567,10 @@ const git_index_entry *git_index_get_bypath(
git_vector_sort(&index->entries);
- if (index_find(&pos, index, path, stage) < 0)
+ if (git_index__find(&pos, index, path, stage) < 0) {
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
return NULL;
+ }
return git_index_get_byindex(index, pos);
}
@@ -533,42 +605,23 @@ int git_index_entry__cmp_icase(const void *a, const void *b)
return strcasecmp(entry_a->path, entry_b->path);
}
-static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path)
+static int index_entry_init(
+ git_index_entry **entry_out, git_index *index, const char *rel_path)
{
+ int error = 0;
git_index_entry *entry = NULL;
struct stat st;
git_oid oid;
- const char *workdir;
- git_buf full_path = GIT_BUF_INIT;
- int error;
if (INDEX_OWNER(index) == NULL)
return create_index_error(-1,
"Could not initialize index entry. "
"Index is not backed up by an existing repository.");
- workdir = git_repository_workdir(INDEX_OWNER(index));
-
- if (!workdir)
- return create_index_error(GIT_EBAREREPO,
- "Could not initialize index entry. Repository is bare");
-
- if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)
- return error;
-
- if ((error = git_path_lstat(full_path.ptr, &st)) < 0) {
- git_buf_free(&full_path);
- return error;
- }
-
- git_buf_free(&full_path); /* done with full path */
-
- /* There is no need to validate the rel_path here, since it will be
- * immediately validated by the call to git_blob_create_fromfile.
- */
-
- /* write the blob to disk and get the oid */
- if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0)
+ /* write the blob to disk and get the oid and stat info */
+ error = git_blob__create_from_paths(
+ &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
+ if (error < 0)
return error;
entry = git__calloc(1, sizeof(git_index_entry));
@@ -586,8 +639,9 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
const char *path,
- int ancestor_mode, git_oid *ancestor_oid,
- int our_mode, git_oid *our_oid, int their_mode, git_oid *their_oid)
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const git_oid *their_oid)
{
git_index_reuc_entry *reuc = NULL;
@@ -615,15 +669,6 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
return 0;
}
-static void index_entry_reuc_free(git_index_reuc_entry *reuc)
-{
- if (!reuc)
- return;
-
- git__free(reuc->path);
- git__free(reuc);
-}
-
static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
{
git_index_entry *entry;
@@ -642,14 +687,6 @@ static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
return entry;
}
-static void index_entry_free(git_index_entry *entry)
-{
- if (!entry)
- return;
- git__free(entry->path);
- git__free(entry);
-}
-
static int index_insert(git_index *index, git_index_entry *entry, int replace)
{
size_t path_length, position;
@@ -668,7 +705,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
entry->flags |= GIT_IDXENTRY_NAMEMASK;
/* look if an entry with this path already exists */
- if (!index_find(&position, index, entry->path, index_entry_stage(entry))) {
+ if (!git_index__find(
+ &position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) {
existing = (git_index_entry **)&index->entries.contents[position];
/* update filemode to existing values if stat is not trusted */
@@ -681,8 +719,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
if (!replace || !existing)
return git_vector_insert(&index->entries, entry);
- /* exists, replace it */
- git__free((*existing)->path);
+ /* exists, replace it (preserving name from existing entry) */
+ git__free(entry->path);
+ entry->path = (*existing)->path;
git__free(*existing);
*existing = entry;
@@ -691,9 +730,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
static int index_conflict_to_reuc(git_index *index, const char *path)
{
- git_index_entry *conflict_entries[3];
+ const git_index_entry *conflict_entries[3];
int ancestor_mode, our_mode, their_mode;
- git_oid *ancestor_oid, *our_oid, *their_oid;
+ git_oid const *ancestor_oid, *our_oid, *their_oid;
int ret;
if ((ret = git_index_conflict_get(&conflict_entries[0],
@@ -779,8 +818,11 @@ int git_index_remove(git_index *index, const char *path, int stage)
git_vector_sort(&index->entries);
- if (index_find(&position, index, path, stage) < 0)
+ if (git_index__find(&position, index, path, stage) < 0) {
+ giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d",
+ path, stage);
return GIT_ENOTFOUND;
+ }
entry = git_vector_get(&index->entries, position);
if (entry != NULL)
@@ -813,7 +855,7 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage)
if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)
break;
- if (index_entry_stage(entry) != stage) {
+ if (GIT_IDXENTRY_STAGE(entry) != stage) {
++pos;
continue;
}
@@ -832,7 +874,8 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage)
return error;
}
-static int index_find(size_t *at_pos, git_index *index, const char *path, int stage)
+int git_index__find(
+ size_t *at_pos, git_index *index, const char *path, int stage)
{
struct entry_srch_key srch_key;
@@ -841,7 +884,8 @@ static int index_find(size_t *at_pos, git_index *index, const char *path, int st
srch_key.path = path;
srch_key.stage = stage;
- return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key);
+ return git_vector_bsearch2(
+ at_pos, &index->entries, index->entries_search, &srch_key);
}
int git_index_find(size_t *at_pos, git_index *index, const char *path)
@@ -927,53 +971,80 @@ on_error:
return ret;
}
-int git_index_conflict_get(git_index_entry **ancestor_out,
- git_index_entry **our_out,
- git_index_entry **their_out,
- git_index *index, const char *path)
+static int index_conflict__get_byindex(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ size_t n)
{
- size_t pos, posmax;
- int stage;
- git_index_entry *conflict_entry;
- int error = GIT_ENOTFOUND;
+ const git_index_entry *conflict_entry;
+ const char *path = NULL;
+ size_t count;
+ int stage, len = 0;
- assert(ancestor_out && our_out && their_out && index && path);
+ assert(ancestor_out && our_out && their_out && index);
*ancestor_out = NULL;
*our_out = NULL;
*their_out = NULL;
- if (git_index_find(&pos, index, path) < 0)
- return GIT_ENOTFOUND;
-
- for (posmax = git_index_entrycount(index); pos < posmax; ++pos) {
+ for (count = git_index_entrycount(index); n < count; ++n) {
+ conflict_entry = git_vector_get(&index->entries, n);
- conflict_entry = git_vector_get(&index->entries, pos);
-
- if (index->entries_cmp_path(conflict_entry->path, path) != 0)
+ if (path && index->entries_cmp_path(conflict_entry->path, path) != 0)
break;
- stage = index_entry_stage(conflict_entry);
+ stage = GIT_IDXENTRY_STAGE(conflict_entry);
+ path = conflict_entry->path;
switch (stage) {
case 3:
*their_out = conflict_entry;
- error = 0;
+ len++;
break;
case 2:
*our_out = conflict_entry;
- error = 0;
+ len++;
break;
case 1:
*ancestor_out = conflict_entry;
- error = 0;
+ len++;
break;
default:
break;
};
}
- return error;
+ return len;
+}
+
+int git_index_conflict_get(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ const char *path)
+{
+ size_t pos;
+ int len = 0;
+
+ assert(ancestor_out && our_out && their_out && index && path);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ if (git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ if ((len = index_conflict__get_byindex(
+ ancestor_out, our_out, their_out, index, pos)) < 0)
+ return len;
+ else if (len == 0)
+ return GIT_ENOTFOUND;
+
+ return 0;
}
int git_index_conflict_remove(git_index *index, const char *path)
@@ -995,7 +1066,7 @@ int git_index_conflict_remove(git_index *index, const char *path)
if (index->entries_cmp_path(conflict_entry->path, path) != 0)
break;
- if (index_entry_stage(conflict_entry) == 0) {
+ if (GIT_IDXENTRY_STAGE(conflict_entry) == 0) {
pos++;
continue;
}
@@ -1014,7 +1085,7 @@ static int index_conflicts_match(const git_vector *v, size_t idx)
{
git_index_entry *entry = git_vector_get(v, idx);
- if (index_entry_stage(entry) > 0) {
+ if (GIT_IDXENTRY_STAGE(entry) > 0) {
index_entry_free(entry);
return 1;
}
@@ -1036,20 +1107,151 @@ int git_index_has_conflicts(const git_index *index)
assert(index);
git_vector_foreach(&index->entries, i, entry) {
- if (index_entry_stage(entry) > 0)
+ if (GIT_IDXENTRY_STAGE(entry) > 0)
return 1;
}
return 0;
}
+int git_index_conflict_iterator_new(
+ git_index_conflict_iterator **iterator_out,
+ git_index *index)
+{
+ git_index_conflict_iterator *it = NULL;
+
+ assert(iterator_out && index);
+
+ it = git__calloc(1, sizeof(git_index_conflict_iterator));
+ GITERR_CHECK_ALLOC(it);
+
+ it->index = index;
+
+ *iterator_out = it;
+ return 0;
+}
+
+int git_index_conflict_next(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index_conflict_iterator *iterator)
+{
+ const git_index_entry *entry;
+ int len;
+
+ assert(ancestor_out && our_out && their_out && iterator);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ while (iterator->cur < iterator->index->entries.length) {
+ entry = git_index_get_byindex(iterator->index, iterator->cur);
+
+ if (git_index_entry_stage(entry) > 0) {
+ if ((len = index_conflict__get_byindex(
+ ancestor_out,
+ our_out,
+ their_out,
+ iterator->index,
+ iterator->cur)) < 0)
+ return len;
+
+ iterator->cur += len;
+ return 0;
+ }
+
+ iterator->cur++;
+ }
+
+ return GIT_ITEROVER;
+}
+
+void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator)
+{
+ if (iterator == NULL)
+ return;
+
+ git__free(iterator);
+}
+
+unsigned int git_index_name_entrycount(git_index *index)
+{
+ assert(index);
+ return (unsigned int)index->names.length;
+}
+
+const git_index_name_entry *git_index_name_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+
+ git_vector_sort(&index->names);
+ return git_vector_get(&index->names, n);
+}
+
+int git_index_name_add(git_index *index,
+ const char *ancestor, const char *ours, const char *theirs)
+{
+ git_index_name_entry *conflict_name;
+
+ assert ((ancestor && ours) || (ancestor && theirs) || (ours && theirs));
+
+ conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ if (ancestor) {
+ conflict_name->ancestor = git__strdup(ancestor);
+ GITERR_CHECK_ALLOC(conflict_name->ancestor);
+ }
+
+ if (ours) {
+ conflict_name->ours = git__strdup(ours);
+ GITERR_CHECK_ALLOC(conflict_name->ours);
+ }
+
+ if (theirs) {
+ conflict_name->theirs = git__strdup(theirs);
+ GITERR_CHECK_ALLOC(conflict_name->theirs);
+ }
+
+ return git_vector_insert(&index->names, conflict_name);
+}
+
+void git_index_name_clear(git_index *index)
+{
+ size_t i;
+ git_index_name_entry *conflict_name;
+
+ assert(index);
+
+ git_vector_foreach(&index->names, i, conflict_name) {
+ if (conflict_name->ancestor)
+ git__free(conflict_name->ancestor);
+
+ if (conflict_name->ours)
+ git__free(conflict_name->ours);
+
+ if (conflict_name->theirs)
+ git__free(conflict_name->theirs);
+
+ git__free(conflict_name);
+ }
+
+ git_vector_clear(&index->names);
+}
+
unsigned int git_index_reuc_entrycount(git_index *index)
{
assert(index);
return (unsigned int)index->reuc.length;
}
-static int index_reuc_insert(git_index *index, git_index_reuc_entry *reuc, int replace)
+static int index_reuc_insert(
+ git_index *index,
+ git_index_reuc_entry *reuc,
+ int replace)
{
git_index_reuc_entry **existing = NULL;
size_t position;
@@ -1071,9 +1273,9 @@ static int index_reuc_insert(git_index *index, git_index_reuc_entry *reuc, int r
}
int git_index_reuc_add(git_index *index, const char *path,
- int ancestor_mode, git_oid *ancestor_oid,
- int our_mode, git_oid *our_oid,
- int their_mode, git_oid *their_oid)
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const git_oid *their_oid)
{
git_index_reuc_entry *reuc = NULL;
int error = 0;
@@ -1140,14 +1342,11 @@ int git_index_reuc_remove(git_index *index, size_t position)
void git_index_reuc_clear(git_index *index)
{
size_t i;
- git_index_reuc_entry *reuc;
assert(index);
- git_vector_foreach(&index->reuc, i, reuc) {
- git__free(reuc->path);
- git__free(reuc);
- }
+ for (i = 0; i < index->reuc.length; ++i)
+ index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
git_vector_clear(&index->reuc);
}
@@ -1164,8 +1363,9 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
size_t len;
int i;
- /* This gets called multiple times, the vector might already be initialized */
- if (index->reuc._alloc_size == 0 && git_vector_init(&index->reuc, 16, reuc_cmp) < 0)
+ /* If called multiple times, the vector might already be initialized */
+ if (index->reuc._alloc_size == 0 &&
+ git_vector_init(&index->reuc, 16, reuc_cmp) < 0)
return -1;
while (size) {
@@ -1175,12 +1375,9 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
if (size <= len)
return index_error_invalid("reading reuc entries");
- lost = git__malloc(sizeof(git_index_reuc_entry));
+ lost = git__calloc(1, sizeof(git_index_reuc_entry));
GITERR_CHECK_ALLOC(lost);
- if (git_vector_insert(&index->reuc, lost) < 0)
- return -1;
-
/* read NUL-terminated pathname for entry */
lost->path = git__strdup(buffer);
GITERR_CHECK_ALLOC(lost->path);
@@ -1194,14 +1391,18 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||
!endptr || endptr == buffer || *endptr ||
- (unsigned)tmp > UINT_MAX)
+ (unsigned)tmp > UINT_MAX) {
+ index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry stage");
+ }
lost->mode[i] = tmp;
len = (endptr + 1) - buffer;
- if (size <= len)
+ if (size <= len) {
+ index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry stage");
+ }
size -= len;
buffer += len;
@@ -1211,13 +1412,19 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
for (i = 0; i < 3; i++) {
if (!lost->mode[i])
continue;
- if (size < 20)
+ if (size < 20) {
+ index_entry_reuc_free(lost);
return index_error_invalid("reading reuc entry oid");
+ }
git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
size -= 20;
buffer += 20;
}
+
+ /* entry was read successfully - insert into reuc vector */
+ if (git_vector_insert(&index->reuc, lost) < 0)
+ return -1;
}
/* entries are guaranteed to be sorted on-disk */
@@ -1226,6 +1433,52 @@ static int read_reuc(git_index *index, const char *buffer, size_t size)
return 0;
}
+
+static int read_conflict_names(git_index *index, const char *buffer, size_t size)
+{
+ size_t len;
+
+ /* This gets called multiple times, the vector might already be initialized */
+ if (index->names._alloc_size == 0 &&
+ git_vector_init(&index->names, 16, conflict_name_cmp) < 0)
+ return -1;
+
+#define read_conflict_name(ptr) \
+ len = strlen(buffer) + 1; \
+ if (size < len) \
+ return index_error_invalid("reading conflict name entries"); \
+ \
+ if (len == 1) \
+ ptr = NULL; \
+ else { \
+ ptr = git__malloc(len); \
+ GITERR_CHECK_ALLOC(ptr); \
+ memcpy(ptr, buffer, len); \
+ } \
+ \
+ buffer += len; \
+ size -= len;
+
+ while (size) {
+ git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ read_conflict_name(conflict_name->ancestor);
+ read_conflict_name(conflict_name->ours);
+ read_conflict_name(conflict_name->theirs);
+
+ if (git_vector_insert(&index->names, conflict_name) < 0)
+ return -1;
+ }
+
+#undef read_conflict_name
+
+ /* entries are guaranteed to be sorted on-disk */
+ index->names.sorted = 1;
+
+ return 0;
+}
+
static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size)
{
size_t path_length, entry_size;
@@ -1318,7 +1571,8 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
total_size = dest.extension_size + sizeof(struct index_extension);
- if (buffer_size - total_size < INDEX_FOOTER_SIZE)
+ if (buffer_size < total_size ||
+ buffer_size - total_size < INDEX_FOOTER_SIZE)
return 0;
/* optional extension */
@@ -1330,6 +1584,9 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
} else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {
if (read_reuc(index, buffer + 8, dest.extension_size) < 0)
return 0;
+ } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) {
+ if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0)
+ return 0;
}
/* else, unsupported extension. We cannot parse this, but we can skip
* it by returning `total_size */
@@ -1345,7 +1602,7 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
{
unsigned int i;
- struct index_header header;
+ struct index_header header = { 0 };
git_oid checksum_calculated, checksum_expected;
#define seek_forward(_increase) { \
@@ -1401,7 +1658,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
/* see if we have read any bytes from the extension */
if (extension_size == 0)
- return index_error_invalid("extension size is zero");
+ return index_error_invalid("extension is truncated");
seek_forward(extension_size);
}
@@ -1412,7 +1669,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
/* 160-bit SHA-1 over the content of the index file before this checksum. */
git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);
- if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0)
+ if (git_oid__cmp(&checksum_calculated, &checksum_expected) != 0)
return index_error_invalid("calculated checksum does not match expected");
#undef seek_forward
@@ -1543,6 +1800,61 @@ static int write_extension(git_filebuf *file, struct index_extension *header, gi
return error;
}
+static int create_name_extension_data(git_buf *name_buf, git_index_name_entry *conflict_name)
+{
+ int error = 0;
+
+ if (conflict_name->ancestor == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->ours == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->theirs == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1);
+
+on_error:
+ return error;
+}
+
+static int write_name_extension(git_index *index, git_filebuf *file)
+{
+ git_buf name_buf = GIT_BUF_INIT;
+ git_vector *out = &index->names;
+ git_index_name_entry *conflict_name;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, conflict_name) {
+ if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4);
+ extension.extension_size = (uint32_t)name_buf.size;
+
+ error = write_extension(file, &extension, &name_buf);
+
+ git_buf_free(&name_buf);
+
+done:
+ return error;
+}
+
static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)
{
int i;
@@ -1613,6 +1925,10 @@ static int write_index(git_index *index, git_filebuf *file)
/* TODO: write tree cache extension */
+ /* write the rename conflict extension */
+ if (index->names.length > 0 && write_name_extension(index, file) < 0)
+ return -1;
+
/* write the reuc extension */
if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)
return -1;
@@ -1626,18 +1942,20 @@ static int write_index(git_index *index, git_filebuf *file)
int git_index_entry_stage(const git_index_entry *entry)
{
- return index_entry_stage(entry);
+ return GIT_IDXENTRY_STAGE(entry);
}
typedef struct read_tree_data {
- git_index *index;
- git_transfer_progress *stats;
+ git_vector *old_entries;
+ git_vector *new_entries;
+ git_vector_cmp entries_search;
} read_tree_data;
-static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)
+static int read_tree_cb(
+ const char *root, const git_tree_entry *tentry, void *payload)
{
- git_index *index = (git_index *)data;
- git_index_entry *entry = NULL;
+ read_tree_data *data = payload;
+ git_index_entry *entry = NULL, *old_entry;
git_buf path = GIT_BUF_INIT;
if (git_tree_entry__is_tree(tentry))
@@ -1652,6 +1970,25 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
entry->mode = tentry->attr;
entry->oid = tentry->oid;
+ /* look for corresponding old entry and copy data to new entry */
+ if (data->old_entries) {
+ size_t pos;
+ struct entry_srch_key skey;
+
+ skey.path = path.ptr;
+ skey.stage = 0;
+
+ if (!git_vector_bsearch2(
+ &pos, data->old_entries, data->entries_search, &skey) &&
+ (old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
+ entry->mode == old_entry->mode &&
+ git_oid_equal(&entry->oid, &old_entry->oid))
+ {
+ memcpy(entry, old_entry, sizeof(*entry));
+ entry->flags_extended = 0;
+ }
+ }
+
if (path.size < GIT_IDXENTRY_NAMEMASK)
entry->flags = path.size & GIT_IDXENTRY_NAMEMASK;
else
@@ -1660,7 +1997,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
entry->path = git_buf_detach(&path);
git_buf_free(&path);
- if (git_vector_insert(&index->entries, entry) < 0) {
+ if (git_vector_insert(data->new_entries, entry) < 0) {
index_entry_free(entry);
return -1;
}
@@ -1670,12 +2007,247 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
int git_index_read_tree(git_index *index, const git_tree *tree)
{
+ int error = 0;
+ git_vector entries = GIT_VECTOR_INIT;
+ read_tree_data data;
+
+ git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */
+
+ data.old_entries = &index->entries;
+ data.new_entries = &entries;
+ data.entries_search = index->entries_search;
+
+ git_vector_sort(&index->entries);
+
+ error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data);
+
+ git_vector_sort(&entries);
+
git_index_clear(index);
- return git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, index);
+ git_vector_swap(&entries, &index->entries);
+ git_vector_free(&entries);
+
+ return error;
}
git_repository *git_index_owner(const git_index *index)
{
return INDEX_OWNER(index);
}
+
+int git_index_add_all(
+ git_index *index,
+ const git_strarray *paths,
+ unsigned int flags,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error;
+ git_repository *repo;
+ git_iterator *wditer = NULL;
+ const git_index_entry *wd = NULL;
+ git_index_entry *entry;
+ git_pathspec ps;
+ const char *match;
+ size_t existing;
+ bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0;
+ int ignorecase;
+ git_oid blobid;
+
+ assert(index);
+
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not add paths to index. "
+ "Index is not backed up by an existing repository.");
+
+ repo = INDEX_OWNER(index);
+ if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0)
+ return error;
+
+ if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0)
+ return -1;
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ /* optionally check that pathspec doesn't mention any ignored files */
+ if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 &&
+ (flags & GIT_INDEX_ADD_FORCE) == 0 &&
+ (error = git_ignore__check_pathspec_for_exact_ignores(
+ repo, &ps.pathspec, no_fnmatch)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_for_workdir(
+ &wditer, repo, 0, ps.prefix, ps.prefix)) < 0)
+ goto cleanup;
+
+ while (!(error = git_iterator_advance(&wd, wditer))) {
+
+ /* check if path actually matches */
+ if (!git_pathspec__match(
+ &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match, NULL))
+ continue;
+
+ /* skip ignored items that are not already in the index */
+ if ((flags & GIT_INDEX_ADD_FORCE) == 0 &&
+ git_iterator_current_is_ignored(wditer) &&
+ git_index__find(&existing, index, wd->path, 0) < 0)
+ continue;
+
+ /* issue notification callback if requested */
+ if (cb && (error = cb(wd->path, match, payload)) != 0) {
+ if (error > 0) /* return > 0 means skip this one */
+ continue;
+ if (error < 0) { /* return < 0 means abort */
+ giterr_clear();
+ error = GIT_EUSER;
+ break;
+ }
+ }
+
+ /* TODO: Should we check if the file on disk is already an exact
+ * match to the file in the index and skip this work if it is?
+ */
+
+ /* write the blob to disk and get the oid */
+ if ((error = git_blob_create_fromworkdir(&blobid, repo, wd->path)) < 0)
+ break;
+
+ /* make the new entry to insert */
+ if ((entry = index_entry_dup(wd)) == NULL) {
+ error = -1;
+ break;
+ }
+ entry->oid = blobid;
+
+ /* add working directory item to index */
+ if ((error = index_insert(index, entry, 1)) < 0) {
+ index_entry_free(entry);
+ break;
+ }
+
+ git_tree_cache_invalidate_path(index->tree, wd->path);
+
+ /* add implies conflict resolved, move conflict entries to REUC */
+ if ((error = index_conflict_to_reuc(index, wd->path)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ break;
+ giterr_clear();
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+cleanup:
+ git_iterator_free(wditer);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+enum {
+ INDEX_ACTION_NONE = 0,
+ INDEX_ACTION_UPDATE = 1,
+ INDEX_ACTION_REMOVE = 2,
+};
+
+static int index_apply_to_all(
+ git_index *index,
+ int action,
+ const git_strarray *paths,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = 0;
+ size_t i;
+ git_pathspec ps;
+ const char *match;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(index);
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ git_vector_sort(&index->entries);
+
+ for (i = 0; !error && i < index->entries.length; ++i) {
+ git_index_entry *entry = git_vector_get(&index->entries, i);
+
+ /* check if path actually matches */
+ if (!git_pathspec__match(
+ &ps.pathspec, entry->path, false, index->ignore_case,
+ &match, NULL))
+ continue;
+
+ /* issue notification callback if requested */
+ if (cb && (error = cb(entry->path, match, payload)) != 0) {
+ if (error > 0) { /* return > 0 means skip this one */
+ error = 0;
+ continue;
+ }
+ if (error < 0) { /* return < 0 means abort */
+ giterr_clear();
+ error = GIT_EUSER;
+ break;
+ }
+ }
+
+ /* index manipulation may alter entry, so don't depend on it */
+ if ((error = git_buf_sets(&path, entry->path)) < 0)
+ break;
+
+ switch (action) {
+ case INDEX_ACTION_NONE:
+ break;
+ case INDEX_ACTION_UPDATE:
+ error = git_index_add_bypath(index, path.ptr);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+
+ error = git_index_remove_bypath(index, path.ptr);
+
+ if (!error) /* back up foreach if we removed this */
+ i--;
+ }
+ break;
+ case INDEX_ACTION_REMOVE:
+ if (!(error = git_index_remove_bypath(index, path.ptr)))
+ i--; /* back up foreach if we removed this */
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown index action %d", action);
+ error = -1;
+ break;
+ }
+ }
+
+ git_buf_free(&path);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+int git_index_remove_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ return index_apply_to_all(
+ index, INDEX_ACTION_REMOVE, pathspec, cb, payload);
+}
+
+int git_index_update_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ return index_apply_to_all(
+ index, INDEX_ACTION_UPDATE, pathspec, cb, payload);
+}
diff --git a/src/index.h b/src/index.h
index 9498907b6..40577e105 100644
--- a/src/index.h
+++ b/src/index.h
@@ -33,6 +33,7 @@ struct git_index {
git_tree_cache *tree;
+ git_vector names;
git_vector reuc;
git_vector_cmp entries_cmp_path;
@@ -41,13 +42,22 @@ struct git_index {
git_vector_cmp reuc_search;
};
-extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st);
+struct git_index_conflict_iterator {
+ git_index *index;
+ size_t cur;
+};
+
+extern void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st);
extern size_t git_index__prefix_position(git_index *index, const char *path);
extern int git_index_entry__cmp(const void *a, const void *b);
extern int git_index_entry__cmp_icase(const void *a, const void *b);
+extern int git_index__find(
+ size_t *at_pos, git_index *index, const char *path, int stage);
+
extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
#endif
diff --git a/src/indexer.c b/src/indexer.c
index 2cfbd3a5a..09f962934 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -9,7 +9,6 @@
#include "git2/indexer.h"
#include "git2/object.h"
-#include "git2/oid.h"
#include "common.h"
#include "pack.h"
@@ -17,6 +16,7 @@
#include "posix.h"
#include "pack.h"
#include "filebuf.h"
+#include "oid.h"
#include "oidmap.h"
#define UINT31_MAX (0x7FFFFFFF)
@@ -60,36 +60,19 @@ const git_oid *git_indexer_stream_hash(const git_indexer_stream *idx)
static int open_pack(struct git_pack_file **out, const char *filename)
{
- size_t namelen;
struct git_pack_file *pack;
- struct stat st;
- int fd;
- namelen = strlen(filename);
- pack = git__calloc(1, sizeof(struct git_pack_file) + namelen + 1);
- GITERR_CHECK_ALLOC(pack);
-
- memcpy(pack->pack_name, filename, namelen + 1);
-
- if (p_stat(filename, &st) < 0) {
- giterr_set(GITERR_OS, "Failed to stat packfile.");
- goto cleanup;
- }
+ if (git_packfile_alloc(&pack, filename) < 0)
+ return -1;
- if ((fd = p_open(pack->pack_name, O_RDONLY)) < 0) {
+ if ((pack->mwf.fd = p_open(pack->pack_name, O_RDONLY)) < 0) {
giterr_set(GITERR_OS, "Failed to open packfile.");
- goto cleanup;
+ git_packfile_free(pack);
+ return -1;
}
- pack->mwf.fd = fd;
- pack->mwf.size = (git_off_t)st.st_size;
-
*out = pack;
return 0;
-
-cleanup:
- git__free(pack);
- return -1;
}
static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack)
@@ -120,7 +103,7 @@ static int objects_cmp(const void *a, const void *b)
const struct entry *entrya = a;
const struct entry *entryb = b;
- return git_oid_cmp(&entrya->oid, &entryb->oid);
+ return git_oid__cmp(&entrya->oid, &entryb->oid);
}
int git_indexer_stream_new(
@@ -276,7 +259,7 @@ static int store_object(git_indexer_stream *idx)
entry = git__calloc(1, sizeof(*entry));
GITERR_CHECK_ALLOC(entry);
- pentry = git__malloc(sizeof(struct git_pack_entry));
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
GITERR_CHECK_ALLOC(pentry);
git_hash_final(&oid, ctx);
@@ -342,10 +325,10 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
/* FIXME: Parse the object instead of hashing it */
if (git_odb__hashobj(&oid, obj) < 0) {
giterr_set(GITERR_INDEXER, "Failed to hash object");
- return -1;
+ goto on_error;
}
- pentry = git__malloc(sizeof(struct git_pack_entry));
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
GITERR_CHECK_ALLOC(pentry);
git_oid_cpy(&pentry->sha1, &oid);
@@ -391,7 +374,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
{
int error = -1;
struct git_pack_header hdr;
- size_t processed;
+ size_t processed;
git_mwindow_file *mwf = &idx->pack->mwf;
assert(idx && data && stats);
@@ -404,7 +387,6 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
/* Make sure we set the new size of the pack */
if (idx->opened_pack) {
idx->pack->mwf.size += size;
- //printf("\nadding %zu for %zu\n", size, idx->pack->mwf.size);
} else {
if (open_pack(&idx->pack, idx->pack_file.path_lock) < 0)
return -1;
@@ -620,7 +602,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *
git_vector_sort(&idx->objects);
git_buf_sets(&filename, idx->pack->pack_name);
- git_buf_truncate(&filename, filename.size - strlen("pack"));
+ git_buf_shorten(&filename, strlen("pack"));
git_buf_puts(&filename, "idx");
if (git_buf_oom(&filename))
return -1;
diff --git a/src/iterator.c b/src/iterator.c
index 5b5ed9525..5917f63fd 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -7,6 +7,7 @@
#include "iterator.h"
#include "tree.h"
+#include "index.h"
#include "ignore.h"
#include "buffer.h"
#include "git2/submodule.h"
@@ -26,8 +27,6 @@
(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; \
ITERATOR_SET_CB(P,NAME_LC); \
@@ -48,6 +47,9 @@
#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+#define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
+#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS)
+
#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)
@@ -70,6 +72,8 @@ static int iterator__reset_range(
GITERR_CHECK_ALLOC(iter->end);
}
+ iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
return 0;
}
@@ -111,7 +115,7 @@ static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
{
GIT_UNUSED(i);
iterator__clear_entry(e);
- return 0;
+ return GIT_ITEROVER;
}
static int empty_iterator__seek(git_iterator *i, const char *p)
@@ -148,7 +152,8 @@ int git_iterator_for_nothing(
const char *start,
const char *end)
{
- empty_iterator *i;
+ empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
+ GITERR_CHECK_ALLOC(i);
#define empty_iterator__current empty_iterator__noop
#define empty_iterator__advance empty_iterator__noop
@@ -194,6 +199,7 @@ typedef struct {
git_buf path;
int path_ambiguities;
bool path_has_filename;
+ bool entry_is_current;
int (*strncomp)(const char *a, const char *b, size_t sz);
} tree_iterator;
@@ -267,9 +273,28 @@ static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
((tree_iterator *)p)->strncomp);
}
+static bool tree_iterator__move_to_next(
+ tree_iterator *ti, tree_iterator_frame *tf)
+{
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities--;
+
+ if (!tf->up) { /* at root */
+ tf->current = tf->next;
+ return false;
+ }
+
+ for (; tf->current < tf->next; tf->current++) {
+ git_tree_free(tf->entries[tf->current]->tree);
+ tf->entries[tf->current]->tree = NULL;
+ }
+
+ return (tf->current < tf->n_entries);
+}
+
static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
{
- int error;
+ int error = 0;
const git_tree_entry *te, *last = NULL;
tf->next = tf->current;
@@ -280,18 +305,23 @@ static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
if (last && tree_iterator__te_cmp(last, te, ti->strncomp))
break;
- /* 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;
+ /* try to load trees for items in [current,next) range */
+ if (!error && git_tree_entry__is_tree(te))
+ error = git_tree_lookup(
+ &tf->entries[tf->next]->tree, ti->base.repo, &te->oid);
}
if (tf->next > tf->current + 1)
ti->path_ambiguities++;
+ /* if a tree lookup failed, advance over this span and return failure */
+ if (error < 0) {
+ tree_iterator__move_to_next(ti, tf);
+ return error;
+ }
+
if (last && !tree_iterator__current_filename(ti, last))
- return -1;
+ return -1; /* must have been allocation failure */
return 0;
}
@@ -309,7 +339,7 @@ static int tree_iterator__push_frame(tree_iterator *ti)
size_t i, n_entries = 0;
if (head->current >= head->n_entries || !head->entries[head->current]->tree)
- return 0;
+ return GIT_ITEROVER;
for (i = head->current; i < head->next; ++i)
n_entries += git_tree_entrycount(head->entries[i]->tree);
@@ -360,7 +390,7 @@ static int tree_iterator__push_frame(tree_iterator *ti)
}
}
- ti->path_has_filename = false;
+ ti->path_has_filename = ti->entry_is_current = false;
if ((error = tree_iterator__set_next(ti, tf)) < 0)
return error;
@@ -372,25 +402,6 @@ static int tree_iterator__push_frame(tree_iterator *ti)
return 0;
}
-static bool tree_iterator__move_to_next(
- tree_iterator *ti, tree_iterator_frame *tf)
-{
- if (tf->next > tf->current + 1)
- ti->path_ambiguities--;
-
- if (!tf->up) { /* at root */
- tf->current = tf->next;
- return false;
- }
-
- for (; tf->current < tf->next; tf->current++) {
- git_tree_free(tf->entries[tf->current]->tree);
- tf->entries[tf->current]->tree = NULL;
- }
-
- return (tf->current < tf->n_entries);
-}
-
static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
{
tree_iterator_frame *tf = ti->head;
@@ -413,7 +424,7 @@ static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
return true;
}
-static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
+static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
{
while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
@@ -422,22 +433,18 @@ static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
ti->path_ambiguities = 0;
git_buf_clear(&ti->path);
}
-
- return 0;
}
-static int tree_iterator__current(
- const git_index_entry **entry, git_iterator *self)
+static int tree_iterator__update_entry(tree_iterator *ti)
{
- tree_iterator *ti = (tree_iterator *)self;
- tree_iterator_frame *tf = ti->head;
- const git_tree_entry *te;
+ tree_iterator_frame *tf;
+ const git_tree_entry *te;
- iterator__clear_entry(entry);
+ if (ti->entry_is_current)
+ return 0;
- if (tf->current >= tf->n_entries)
- return 0;
- te = tf->entries[tf->current]->te;
+ tf = ti->head;
+ te = tf->entries[tf->current]->te;
ti->entry.mode = te->attr;
git_oid_cpy(&ti->entry.oid, &te->oid);
@@ -448,12 +455,36 @@ static int tree_iterator__current(
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 (iterator__past_end(ti, ti->entry.path)) {
+ tree_iterator__pop_all(ti, true, false);
+ return GIT_ITEROVER;
+ }
+
+ ti->entry_is_current = true;
+
+ return 0;
+}
+
+static int tree_iterator__current(
+ 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 GIT_ITEROVER;
+
+ if ((error = tree_iterator__update_entry(ti)) < 0)
+ return error;
if (entry)
*entry = &ti->entry;
+ ti->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
return 0;
}
@@ -465,8 +496,10 @@ static int tree_iterator__advance_into(
iterator__clear_entry(entry);
- if (tree_iterator__at_tree(ti) &&
- !(error = tree_iterator__push_frame(ti)))
+ if (tree_iterator__at_tree(ti))
+ error = tree_iterator__push_frame(ti);
+
+ if (!error && entry)
error = tree_iterator__current(entry, self);
return error;
@@ -481,8 +514,11 @@ static int tree_iterator__advance(
iterator__clear_entry(entry);
- if (tf->current > tf->n_entries)
- return 0;
+ if (tf->current >= tf->n_entries)
+ return GIT_ITEROVER;
+
+ if (!iterator__has_been_accessed(ti))
+ return tree_iterator__current(entry, self);
if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
tree_iterator__at_tree(ti))
@@ -490,7 +526,7 @@ static int tree_iterator__advance(
if (ti->path_has_filename) {
git_buf_rtruncate_at_char(&ti->path, '/');
- ti->path_has_filename = false;
+ ti->path_has_filename = ti->entry_is_current = false;
}
/* scan forward and up, advancing in frame or popping frame when done */
@@ -581,6 +617,9 @@ int git_iterator_for_tree(
if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
return error;
+ ti = git__calloc(1, sizeof(tree_iterator));
+ GITERR_CHECK_ALLOC(ti);
+
ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)
@@ -697,7 +736,9 @@ static int index_iterator__current(
if (entry)
*entry = ie;
- return 0;
+ ii->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ return (ie != NULL) ? 0 : GIT_ITEROVER;
}
static int index_iterator__at_end(git_iterator *self)
@@ -713,6 +754,9 @@ static int index_iterator__advance(
size_t entrycount = git_index_entrycount(ii->index);
const git_index_entry *ie;
+ if (!iterator__has_been_accessed(ii))
+ return index_iterator__current(entry, self);
+
if (index_iterator__at_tree(ii)) {
if (iterator__do_autoexpand(ii)) {
ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
@@ -810,7 +854,8 @@ int git_iterator_for_index(
const char *start,
const char *end)
{
- index_iterator *ii;
+ index_iterator *ii = git__calloc(1, sizeof(index_iterator));
+ GITERR_CHECK_ALLOC(ii);
ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));
@@ -833,237 +878,240 @@ int git_iterator_for_index(
}
-#define WORKDIR_MAX_DEPTH 100
-
-typedef struct workdir_iterator_frame workdir_iterator_frame;
-struct workdir_iterator_frame {
- workdir_iterator_frame *next;
+typedef struct fs_iterator_frame fs_iterator_frame;
+struct fs_iterator_frame {
+ fs_iterator_frame *next;
git_vector entries;
size_t index;
};
-typedef struct {
+typedef struct fs_iterator fs_iterator;
+struct fs_iterator {
git_iterator base;
git_iterator_callbacks cb;
- workdir_iterator_frame *stack;
- git_ignores ignores;
+ fs_iterator_frame *stack;
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)
-{
- if (!ps)
- return false;
- else {
- const char *path = ps->path;
- size_t len = ps->path_len;
-
- if (len < 4)
- return false;
- if (path[len - 1] == '/')
- len--;
- if (tolower(path[len - 1]) != 't' ||
- tolower(path[len - 2]) != 'i' ||
- tolower(path[len - 3]) != 'g' ||
- tolower(path[len - 4]) != '.')
- return false;
- return (len == 4 || path[len - 5] == '/');
- }
-}
+ int (*enter_dir_cb)(fs_iterator *self);
+ int (*leave_dir_cb)(fs_iterator *self);
+ int (*update_entry_cb)(fs_iterator *self);
+};
+
+#define FS_MAX_DEPTH 100
-static workdir_iterator_frame *workdir_iterator__alloc_frame(
- workdir_iterator *wi)
+static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi)
{
- workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
+ fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame));
git_vector_cmp entry_compare = CASESELECT(
- iterator__ignore_case(wi),
+ iterator__ignore_case(fi),
git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
- if (wf == NULL)
- return NULL;
-
- if (git_vector_init(&wf->entries, 0, entry_compare) != 0) {
- git__free(wf);
- return NULL;
+ if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) {
+ git__free(ff);
+ ff = NULL;
}
- return wf;
+ return ff;
}
-static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
+static void fs_iterator__free_frame(fs_iterator_frame *ff)
{
- unsigned int i;
+ size_t i;
git_path_with_stat *path;
- git_vector_foreach(&wf->entries, i, path)
+ git_vector_foreach(&ff->entries, i, path)
git__free(path);
- git_vector_free(&wf->entries);
- git__free(wf);
+ git_vector_free(&ff->entries);
+ git__free(ff);
+}
+
+static void fs_iterator__pop_frame(
+ fs_iterator *fi, fs_iterator_frame *ff, bool pop_last)
+{
+ if (fi && fi->stack == ff) {
+ if (!ff->next && !pop_last) {
+ memset(&fi->entry, 0, sizeof(fi->entry));
+ return;
+ }
+
+ if (fi->leave_dir_cb)
+ (void)fi->leave_dir_cb(fi);
+
+ fi->stack = ff->next;
+ fi->depth--;
+ }
+
+ fs_iterator__free_frame(ff);
}
-static int workdir_iterator__update_entry(workdir_iterator *wi);
+static int fs_iterator__update_entry(fs_iterator *fi);
+static int fs_iterator__advance_over(
+ const git_index_entry **entry, git_iterator *self);
-static int workdir_iterator__entry_cmp(const void *i, const void *item)
+static int fs_iterator__entry_cmp(const void *i, const void *item)
{
- const workdir_iterator *wi = (const workdir_iterator *)i;
+ const fs_iterator *fi = (const fs_iterator *)i;
const git_path_with_stat *ps = item;
- return wi->base.prefixcomp(wi->base.start, ps->path);
+ return fi->base.prefixcomp(fi->base.start, ps->path);
}
-static void workdir_iterator__seek_frame_start(
- workdir_iterator *wi, workdir_iterator_frame *wf)
+static void fs_iterator__seek_frame_start(
+ fs_iterator *fi, fs_iterator_frame *ff)
{
- if (!wf)
+ if (!ff)
return;
- if (wi->base.start)
+ if (fi->base.start)
git_vector_bsearch2(
- &wf->index, &wf->entries, workdir_iterator__entry_cmp, wi);
+ &ff->index, &ff->entries, fs_iterator__entry_cmp, fi);
else
- wf->index = 0;
-
- if (path_is_dotgit(git_vector_get(&wf->entries, wf->index)))
- wf->index++;
+ ff->index = 0;
}
-static int workdir_iterator__expand_dir(workdir_iterator *wi)
+static int fs_iterator__expand_dir(fs_iterator *fi)
{
int error;
- workdir_iterator_frame *wf;
+ fs_iterator_frame *ff;
+
+ if (fi->depth > FS_MAX_DEPTH) {
+ giterr_set(GITERR_REPOSITORY,
+ "Directory nesting is too deep (%d)", fi->depth);
+ return -1;
+ }
- wf = workdir_iterator__alloc_frame(wi);
- GITERR_CHECK_ALLOC(wf);
+ ff = fs_iterator__alloc_frame(fi);
+ GITERR_CHECK_ALLOC(ff);
error = git_path_dirload_with_stat(
- wi->path.ptr, wi->root_len, iterator__ignore_case(wi),
- wi->base.start, wi->base.end, &wf->entries);
+ fi->path.ptr, fi->root_len, iterator__ignore_case(fi),
+ fi->base.start, fi->base.end, &ff->entries);
- if (error < 0 || wf->entries.length == 0) {
- workdir_iterator__free_frame(wf);
- return GIT_ENOTFOUND;
+ if (error < 0) {
+ fs_iterator__free_frame(ff);
+ fs_iterator__advance_over(NULL, (git_iterator *)fi);
+ return error;
}
- 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;
+ if (ff->entries.length == 0) {
+ fs_iterator__free_frame(ff);
+ return GIT_ENOTFOUND;
}
- workdir_iterator__seek_frame_start(wi, wf);
+ fs_iterator__seek_frame_start(fi, ff);
- /* only push new ignores if this is not top level directory */
- if (wi->stack != NULL) {
- ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/');
- (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);
- }
+ ff->next = fi->stack;
+ fi->stack = ff;
+ fi->depth++;
- wf->next = wi->stack;
- wi->stack = wf;
+ if (fi->enter_dir_cb && (error = fi->enter_dir_cb(fi)) < 0)
+ return error;
- return workdir_iterator__update_entry(wi);
+ return fs_iterator__update_entry(fi);
}
-static int workdir_iterator__current(
+static int fs_iterator__current(
const git_index_entry **entry, git_iterator *self)
{
- workdir_iterator *wi = (workdir_iterator *)self;
+ fs_iterator *fi = (fs_iterator *)self;
+ const git_index_entry *fe = (fi->entry.path == NULL) ? NULL : &fi->entry;
+
if (entry)
- *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
- return 0;
+ *entry = fe;
+
+ fi->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ return (fe != NULL) ? 0 : GIT_ITEROVER;
}
-static int workdir_iterator__at_end(git_iterator *self)
+static int fs_iterator__at_end(git_iterator *self)
{
- return (((workdir_iterator *)self)->entry.path == NULL);
+ return (((fs_iterator *)self)->entry.path == NULL);
}
-static int workdir_iterator__advance_into(
+static int fs_iterator__advance_into(
const git_index_entry **entry, git_iterator *iter)
{
int error = 0;
- workdir_iterator *wi = (workdir_iterator *)iter;
+ fs_iterator *fi = (fs_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
+ /* Allow you to explicitly advance into a commit/submodule (as well as a
+ * tree) to avoid cases where an entry is mislabeled as a submodule in
+ * the working directory. The fs iterator will never have COMMMIT
+ * entries on it's own, but a wrapper might add them.
*/
- if (wi->entry.path != NULL &&
- (wi->entry.mode == GIT_FILEMODE_TREE ||
- wi->entry.mode == GIT_FILEMODE_COMMIT))
+ if (fi->entry.path != NULL &&
+ (fi->entry.mode == GIT_FILEMODE_TREE ||
+ fi->entry.mode == GIT_FILEMODE_COMMIT))
/* returns GIT_ENOTFOUND if the directory is empty */
- error = workdir_iterator__expand_dir(wi);
+ error = fs_iterator__expand_dir(fi);
if (!error && entry)
- error = workdir_iterator__current(entry, iter);
+ error = fs_iterator__current(entry, iter);
+
+ if (!error && !fi->entry.path)
+ error = GIT_ITEROVER;
return error;
}
-static int workdir_iterator__advance(
+static int fs_iterator__advance_over(
const git_index_entry **entry, git_iterator *self)
{
int error = 0;
- workdir_iterator *wi = (workdir_iterator *)self;
- workdir_iterator_frame *wf;
+ fs_iterator *fi = (fs_iterator *)self;
+ fs_iterator_frame *ff;
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;
- while (wi->entry.path != NULL) {
- wf = wi->stack;
- next = git_vector_get(&wf->entries, ++wf->index);
+ while (fi->entry.path != NULL) {
+ ff = fi->stack;
+ next = git_vector_get(&ff->entries, ++ff->index);
- if (next != NULL) {
- /* match git's behavior of ignoring anything named ".git" */
- if (path_is_dotgit(next))
- continue;
- /* else found a good entry */
+ if (next != NULL)
break;
- }
- /* pop stack if anything is left to pop */
- if (!wf->next) {
- memset(&wi->entry, 0, sizeof(wi->entry));
- return 0;
- }
-
- wi->stack = wf->next;
- wi->depth--;
- workdir_iterator__free_frame(wf);
- git_ignore__pop_dir(&wi->ignores);
+ fs_iterator__pop_frame(fi, ff, false);
}
- error = workdir_iterator__update_entry(wi);
+ error = fs_iterator__update_entry(fi);
if (!error && entry != NULL)
- error = workdir_iterator__current(entry, self);
+ error = fs_iterator__current(entry, self);
return error;
}
-static int workdir_iterator__seek(git_iterator *self, const char *prefix)
+static int fs_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ fs_iterator *fi = (fs_iterator *)self;
+
+ if (!iterator__has_been_accessed(fi))
+ return fs_iterator__current(entry, self);
+
+ /* given include_trees & autoexpand, we might have to go into a tree */
+ if (iterator__do_autoexpand(fi) &&
+ fi->entry.path != NULL &&
+ fi->entry.mode == GIT_FILEMODE_TREE)
+ {
+ int error = fs_iterator__advance_into(entry, self);
+ if (error != GIT_ENOTFOUND)
+ return error;
+ /* continue silently past empty directories if autoexpanding */
+ giterr_clear();
+ }
+
+ return fs_iterator__advance_over(entry, self);
+}
+
+static int fs_iterator__seek(git_iterator *self, const char *prefix)
{
GIT_UNUSED(self);
GIT_UNUSED(prefix);
@@ -1073,108 +1121,210 @@ static int workdir_iterator__seek(git_iterator *self, const char *prefix)
return 0;
}
-static int workdir_iterator__reset(
+static int fs_iterator__reset(
git_iterator *self, const char *start, const char *end)
{
- workdir_iterator *wi = (workdir_iterator *)self;
+ int error;
+ fs_iterator *fi = (fs_iterator *)self;
- while (wi->stack != NULL && wi->stack->next != NULL) {
- workdir_iterator_frame *wf = wi->stack;
- wi->stack = wf->next;
- workdir_iterator__free_frame(wf);
- git_ignore__pop_dir(&wi->ignores);
- }
- wi->depth = 0;
+ while (fi->stack != NULL && fi->stack->next != NULL)
+ fs_iterator__pop_frame(fi, fi->stack, false);
+ fi->depth = 0;
- if (iterator__reset_range(self, start, end) < 0)
- return -1;
+ if ((error = iterator__reset_range(self, start, end)) < 0)
+ return error;
+
+ fs_iterator__seek_frame_start(fi, fi->stack);
- workdir_iterator__seek_frame_start(wi, wi->stack);
+ error = fs_iterator__update_entry(fi);
+ if (error == GIT_ITEROVER)
+ error = 0;
- return workdir_iterator__update_entry(wi);
+ return error;
}
-static void workdir_iterator__free(git_iterator *self)
+static void fs_iterator__free(git_iterator *self)
{
- workdir_iterator *wi = (workdir_iterator *)self;
+ fs_iterator *fi = (fs_iterator *)self;
- while (wi->stack != NULL) {
- workdir_iterator_frame *wf = wi->stack;
- wi->stack = wf->next;
- workdir_iterator__free_frame(wf);
- }
+ while (fi->stack != NULL)
+ fs_iterator__pop_frame(fi, fi->stack, true);
- git_ignore__free(&wi->ignores);
- git_buf_free(&wi->path);
+ git_buf_free(&fi->path);
}
-static int workdir_iterator__update_entry(workdir_iterator *wi)
+static int fs_iterator__update_entry(fs_iterator *fi)
{
- int error = 0;
- git_path_with_stat *ps =
- git_vector_get(&wi->stack->entries, wi->stack->index);
+ git_path_with_stat *ps;
- git_buf_truncate(&wi->path, wi->root_len);
- memset(&wi->entry, 0, sizeof(wi->entry));
+ memset(&fi->entry, 0, sizeof(fi->entry));
+ if (!fi->stack)
+ return GIT_ITEROVER;
+
+ ps = git_vector_get(&fi->stack->entries, fi->stack->index);
if (!ps)
- return 0;
+ return GIT_ITEROVER;
- /* skip over .git entries */
- if (path_is_dotgit(ps))
- return workdir_iterator__advance(NULL, (git_iterator *)wi);
+ git_buf_truncate(&fi->path, fi->root_len);
+ if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
+ return -1;
- if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)
+ if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
+ return GIT_ITEROVER;
+
+ fi->entry.path = ps->path;
+ git_index_entry__init_from_stat(&fi->entry, &ps->st);
+
+ /* need different mode here to keep directories during iteration */
+ fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
+
+ /* allow wrapper to check/update the entry (can force skip) */
+ if (fi->update_entry_cb &&
+ fi->update_entry_cb(fi) == GIT_ENOTFOUND)
+ return fs_iterator__advance_over(NULL, (git_iterator *)fi);
+
+ /* if this is a tree and trees aren't included, then skip */
+ if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
+ int error = fs_iterator__advance_into(NULL, (git_iterator *)fi);
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+ return fs_iterator__advance_over(NULL, (git_iterator *)fi);
+ }
+
+ return 0;
+}
+
+static int fs_iterator__initialize(
+ git_iterator **out, fs_iterator *fi, const char *root)
+{
+ int error;
+
+ if (git_buf_sets(&fi->path, root) < 0 || git_path_to_dir(&fi->path) < 0) {
+ git__free(fi);
return -1;
+ }
+ fi->root_len = fi->path.size;
- if (iterator__past_end(wi, wi->path.ptr + wi->root_len))
- return 0;
+ if ((error = fs_iterator__expand_dir(fi)) < 0) {
+ if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) {
+ giterr_clear();
+ error = 0;
+ } else {
+ git_iterator_free((git_iterator *)fi);
+ fi = NULL;
+ }
+ }
- wi->entry.path = ps->path;
+ *out = (git_iterator *)fi;
+ return error;
+}
- wi->is_ignored = -1;
+int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ fs_iterator *fi = git__calloc(1, sizeof(fs_iterator));
+ GITERR_CHECK_ALLOC(fi);
- git_index_entry__init_from_stat(&wi->entry, &ps->st);
+ ITERATOR_BASE_INIT(fi, fs, FS, NULL);
- /* need different mode here to keep directories during iteration */
- wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ fi->base.flags |= GIT_ITERATOR_IGNORE_CASE;
- /* if this is a file type we don't handle, treat as ignored */
- if (wi->entry.mode == 0) {
- wi->is_ignored = 1;
- return 0;
+ return fs_iterator__initialize(out, fi, root);
+}
+
+
+typedef struct {
+ fs_iterator fi;
+ git_ignores ignores;
+ int is_ignored;
+} workdir_iterator;
+
+GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
+{
+ size_t len;
+
+ if (!path || (len = path->size) < 4)
+ return false;
+
+ if (path->ptr[len - 1] == '/')
+ len--;
+
+ if (tolower(path->ptr[len - 1]) != 't' ||
+ tolower(path->ptr[len - 2]) != 'i' ||
+ tolower(path->ptr[len - 3]) != 'g' ||
+ tolower(path->ptr[len - 4]) != '.')
+ return false;
+
+ return (len == 4 || path->ptr[len - 5] == '/');
+}
+
+static int workdir_iterator__enter_dir(fs_iterator *fi)
+{
+ /* only push new ignores if this is not top level directory */
+ if (fi->stack->next != NULL) {
+ workdir_iterator *wi = (workdir_iterator *)fi;
+ ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/');
+
+ (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]);
}
- /* if this isn't a tree, then we're done */
- if (wi->entry.mode != GIT_FILEMODE_TREE)
- return 0;
+ return 0;
+}
- /* detect submodules */
- error = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);
- if (error == GIT_ENOTFOUND)
- giterr_clear();
+static int workdir_iterator__leave_dir(fs_iterator *fi)
+{
+ workdir_iterator *wi = (workdir_iterator *)fi;
+ git_ignore__pop_dir(&wi->ignores);
+ return 0;
+}
- if (error == GIT_EEXISTS) /* if contains .git, treat as untracked submod */
- error = 0;
+static int workdir_iterator__update_entry(fs_iterator *fi)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)fi;
- /* 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;
+ /* skip over .git entries */
+ if (workdir_path_is_dotgit(&fi->path))
+ return GIT_ENOTFOUND;
+
+ /* reset is_ignored since we haven't checked yet */
+ wi->is_ignored = -1;
+
+ /* check if apparent tree entries are actually submodules */
+ if (fi->entry.mode != GIT_FILEMODE_TREE)
return 0;
+
+ error = git_submodule_lookup(NULL, fi->base.repo, fi->entry.path);
+ if (error < 0)
+ giterr_clear();
+
+ /* mark submodule (or any dir with .git) as GITLINK and remove slash */
+ if (!error || error == GIT_EEXISTS) {
+ fi->entry.mode = S_IFGITLINK;
+ fi->entry.path[strlen(fi->entry.path) - 1] = '\0';
}
- if (iterator__include_trees(wi))
- return 0;
+ return 0;
+}
- return workdir_iterator__advance(NULL, (git_iterator *)wi);
+static void workdir_iterator__free(git_iterator *self)
+{
+ workdir_iterator *wi = (workdir_iterator *)self;
+ fs_iterator__free(self);
+ git_ignore__free(&wi->ignores);
}
-int git_iterator_for_workdir(
- git_iterator **iter,
+int git_iterator_for_workdir_ext(
+ git_iterator **out,
git_repository *repo,
+ const char *repo_workdir,
git_iterator_flag_t flags,
const char *start,
const char *end)
@@ -1182,38 +1332,31 @@ int git_iterator_for_workdir(
int error;
workdir_iterator *wi;
- assert(iter && repo);
-
- if ((error = git_repository__ensure_not_bare(
- repo, "scan working directory")) < 0)
- return error;
+ if (!repo_workdir) {
+ if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
+ return GIT_EBAREREPO;
+ repo_workdir = git_repository_workdir(repo);
+ }
- ITERATOR_BASE_INIT(wi, workdir, WORKDIR, repo);
+ /* initialize as an fs iterator then do overrides */
+ wi = git__calloc(1, sizeof(workdir_iterator));
+ GITERR_CHECK_ALLOC(wi);
+ ITERATOR_BASE_INIT((&wi->fi), fs, FS, repo);
- if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0)
- goto fail;
+ wi->fi.base.type = GIT_ITERATOR_TYPE_WORKDIR;
+ wi->fi.cb.free = workdir_iterator__free;
+ wi->fi.enter_dir_cb = workdir_iterator__enter_dir;
+ wi->fi.leave_dir_cb = workdir_iterator__leave_dir;
+ wi->fi.update_entry_cb = workdir_iterator__update_entry;
- if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
- git_path_to_dir(&wi->path) < 0 ||
- git_ignore__for_path(repo, "", &wi->ignores) < 0)
+ if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 ||
+ (error = git_ignore__for_path(repo, "", &wi->ignores)) < 0)
{
- git__free(wi);
- return -1;
- }
- wi->root_len = wi->path.size;
-
- if ((error = workdir_iterator__expand_dir(wi)) < 0) {
- if (error != GIT_ENOTFOUND)
- goto fail;
- giterr_clear();
+ git_iterator_free((git_iterator *)wi);
+ return error;
}
- *iter = (git_iterator *)wi;
- return 0;
-
-fail:
- git_iterator_free((git_iterator *)wi);
- return error;
+ return fs_iterator__initialize(out, &wi->fi, repo_workdir);
}
@@ -1315,7 +1458,8 @@ bool git_iterator_current_is_ignored(git_iterator *iter)
if (wi->is_ignored != -1)
return (bool)(wi->is_ignored != 0);
- if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
+ if (git_ignore__lookup(
+ &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0)
wi->is_ignored = true;
return (bool)wi->is_ignored;
@@ -1340,10 +1484,10 @@ int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
{
workdir_iterator *wi = (workdir_iterator *)iter;
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->entry.path)
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->fi.entry.path)
*path = NULL;
else
- *path = &wi->path;
+ *path = &wi->fi.path;
return 0;
}
diff --git a/src/iterator.h b/src/iterator.h
index 4a4e6a9d8..ea88fa6a2 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -19,6 +19,7 @@ typedef enum {
GIT_ITERATOR_TYPE_TREE = 1,
GIT_ITERATOR_TYPE_INDEX = 2,
GIT_ITERATOR_TYPE_WORKDIR = 3,
+ GIT_ITERATOR_TYPE_FS = 4,
} git_iterator_type_t;
typedef enum {
@@ -78,14 +79,35 @@ extern int git_iterator_for_index(
const char *start,
const char *end);
+extern int git_iterator_for_workdir_ext(
+ git_iterator **out,
+ git_repository *repo,
+ const char *repo_workdir,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
/* 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(
+GIT_INLINE(int) git_iterator_for_workdir(
git_iterator **out,
git_repository *repo,
git_iterator_flag_t flags,
const char *start,
+ const char *end)
+{
+ return git_iterator_for_workdir_ext(out, repo, NULL, flags, start, end);
+}
+
+/* for filesystem iterators, you have to explicitly pass in the ignore_case
+ * behavior that you desire
+ */
+extern int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_flag_t flags,
+ const char *start,
const char *end);
extern void git_iterator_free(git_iterator *iter);
@@ -131,9 +153,9 @@ GIT_INLINE(int) git_iterator_advance(
*
* 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.
+ * For filesystem and working directory iterators, 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)
@@ -141,18 +163,50 @@ GIT_INLINE(int) git_iterator_advance_into(
return iter->cb->advance_into(entry, iter);
}
+/**
+ * Advance into a tree or skip over it if it is empty.
+ *
+ * Because `git_iterator_advance_into` may return GIT_ENOTFOUND if the
+ * directory is empty (only with filesystem and working directory
+ * iterators) and a common response is to just call `git_iterator_advance`
+ * when that happens, this bundles the two into a single simple call.
+ */
+GIT_INLINE(int) git_iterator_advance_into_or_over(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ int error = iter->cb->advance_into(entry, iter);
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = iter->cb->advance(entry, iter);
+ }
+ return error;
+}
+
+/* Seek is currently unimplemented */
GIT_INLINE(int) git_iterator_seek(
git_iterator *iter, const char *prefix)
{
return iter->cb->seek(iter, prefix);
}
+/**
+ * Go back to the start of the iteration.
+ *
+ * This resets the iterator to the start of the iteration. It also allows
+ * you to reset the `start` and `end` pathname boundaries of the iteration
+ * when doing so.
+ */
GIT_INLINE(int) git_iterator_reset(
git_iterator *iter, const char *start, const char *end)
{
return iter->cb->reset(iter, start, end);
}
+/**
+ * Check if the iterator is at the end
+ *
+ * @return 0 if not at end, >0 if at end
+ */
GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
{
return iter->cb->at_end(iter);
diff --git a/src/merge.c b/src/merge.c
index e0010d6a4..2e94ce1cd 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -5,48 +5,58 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
#include "repository.h"
#include "revwalk.h"
-#include "buffer.h"
+#include "commit_list.h"
#include "merge.h"
+#include "path.h"
#include "refs.h"
+#include "object.h"
+#include "iterator.h"
+#include "refs.h"
+#include "diff.h"
+#include "checkout.h"
+#include "tree.h"
+#include "merge_file.h"
+#include "blob.h"
+#include "hashsig.h"
+#include "oid.h"
+#include "index.h"
+#include "filebuf.h"
+
+#include "git2/types.h"
#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/commit.h"
#include "git2/merge.h"
+#include "git2/refs.h"
#include "git2/reset.h"
-#include "commit_list.h"
-
-int git_repository_merge_cleanup(git_repository *repo)
-{
- int error = 0;
- git_buf merge_head_path = GIT_BUF_INIT,
- merge_mode_path = GIT_BUF_INIT,
- merge_msg_path = GIT_BUF_INIT;
-
- assert(repo);
+#include "git2/checkout.h"
+#include "git2/signature.h"
+#include "git2/config.h"
+#include "git2/tree.h"
+#include "git2/sys/index.h"
- if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
- git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
- git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
- return -1;
-
- if (git_path_isfile(merge_head_path.ptr)) {
- if ((error = p_unlink(merge_head_path.ptr)) < 0)
- goto cleanup;
- }
+#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
- if (git_path_isfile(merge_mode_path.ptr))
- (void)p_unlink(merge_mode_path.ptr);
+typedef enum {
+ TREE_IDX_ANCESTOR = 0,
+ TREE_IDX_OURS = 1,
+ TREE_IDX_THEIRS = 2
+} merge_tree_index_t;
- if (git_path_isfile(merge_msg_path.ptr))
- (void)p_unlink(merge_msg_path.ptr);
+/* Tracks D/F conflicts */
+struct merge_diff_df_data {
+ const char *df_path;
+ const char *prev_path;
+ git_merge_diff *prev_conflict;
+};
-cleanup:
- git_buf_free(&merge_msg_path);
- git_buf_free(&merge_mode_path);
- git_buf_free(&merge_head_path);
- return error;
-}
+/* Merge base computation */
int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)
{
@@ -86,6 +96,7 @@ int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_
goto cleanup;
if (!result) {
+ giterr_set(GITERR_MERGE, "No merge base found");
error = GIT_ENOTFOUND;
goto cleanup;
}
@@ -131,7 +142,7 @@ int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const
if (!result) {
git_revwalk_free(walk);
- giterr_clear();
+ giterr_set(GITERR_MERGE, "No merge base found");
return GIT_ENOTFOUND;
}
@@ -177,7 +188,7 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l
return -1;
if (git_commit_list_parse(walk, one) < 0)
- return -1;
+ return -1;
one->flags |= PARENT1;
if (git_pqueue_insert(&list, one) < 0)
@@ -294,3 +305,1854 @@ cleanup:
return error;
}
+
+GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b)
+{
+ int value = 0;
+
+ if (a->path == NULL)
+ return (b->path == NULL) ? 0 : 1;
+
+ if ((value = a->mode - b->mode) == 0 &&
+ (value = git_oid__cmp(&a->oid, &b->oid)) == 0)
+ value = strcmp(a->path, b->path);
+
+ return value;
+}
+
+/* Conflict resolution */
+
+static int merge_conflict_resolve_trivial(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed, ours_theirs_differ;
+ git_index_entry const *result = NULL;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ if (conflict->our_status == GIT_DELTA_RENAMED ||
+ conflict->their_status == GIT_DELTA_RENAMED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+ ours_theirs_differ = ours_changed && theirs_changed &&
+ index_entry_cmp(&conflict->our_entry, &conflict->their_entry);
+
+ /*
+ * Note: with only one ancestor, some cases are not distinct:
+ *
+ * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge
+ * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge
+ * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge
+ *
+ * Note that the two cases that take D/F conflicts into account
+ * specifically do not need to be explicitly tested, as D/F conflicts
+ * would fail the *empty* test:
+ *
+ * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head
+ * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote
+ *
+ * Note that many of these cases need not be explicitly tested, as
+ * they simply degrade to "all different" cases (eg, 11):
+ *
+ * 4: ancest:(empty)^, head:head, remote:remote = result:no merge
+ * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge
+ * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge
+ * 11: ancest:ancest+, head:head, remote:remote = result:no merge
+ */
+
+ /* 5ALT: ancest:*, head:head, remote:head = result:head */
+ if (ours_changed && !ours_empty && !ours_theirs_differ)
+ result = &conflict->our_entry;
+ /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */
+ else if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 0;
+ /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 0;
+ /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 0;
+ /* 13: ancest:ancest+, head:head, remote:ancest = result:head */
+ else if (ours_changed && !theirs_changed)
+ result = &conflict->our_entry;
+ /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */
+ else if (!ours_changed && theirs_changed)
+ result = &conflict->their_entry;
+ else
+ *resolved = 0;
+
+ if (result != NULL &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(*result) &&
+ (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0)
+ *resolved = 1;
+
+ /* Note: trivial resolution does not update the REUC. */
+
+ return error;
+}
+
+static int merge_conflict_resolve_one_removed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+
+ /* Removed in both */
+ if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 1;
+ /* Removed in ours */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 1;
+ /* Removed in theirs */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 1;
+
+ if (*resolved)
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+
+static int merge_conflict_resolve_one_renamed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_renamed, theirs_renamed;
+ int ours_changed, theirs_changed;
+ git_index_entry *merged;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return 0;
+
+ ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED);
+ theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED);
+
+ if (!ours_renamed && !theirs_renamed)
+ return 0;
+
+ /* Reject one file in a 2->1 conflict */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_changed = (git_oid__cmp(&conflict->ancestor_entry.oid, &conflict->our_entry.oid) != 0);
+ theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.oid, &conflict->their_entry.oid) != 0);
+
+ /* if both are modified (and not to a common target) require a merge */
+ if (ours_changed && theirs_changed &&
+ git_oid__cmp(&conflict->our_entry.oid, &conflict->their_entry.oid) != 0)
+ return 0;
+
+ if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL)
+ return -1;
+
+ if (ours_changed)
+ memcpy(merged, &conflict->our_entry, sizeof(git_index_entry));
+ else
+ memcpy(merged, &conflict->their_entry, sizeof(git_index_entry));
+
+ if (ours_renamed)
+ merged->path = conflict->our_entry.path;
+ else
+ merged->path = conflict->their_entry.path;
+
+ *resolved = 1;
+
+ git_vector_insert(&diff_list->staged, merged);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+static int merge_conflict_resolve_automerge(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ unsigned int automerge_flags)
+{
+ git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
+ ours = GIT_MERGE_FILE_INPUT_INIT,
+ theirs = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_result result = GIT_MERGE_FILE_RESULT_INIT;
+ git_index_entry *index_entry;
+ git_odb *odb = NULL;
+ git_oid automerge_oid;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (automerge_flags == GIT_MERGE_AUTOMERGE_NONE)
+ return 0;
+
+ /* Reject D/F conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE)
+ return 0;
+
+ /* Reject link/file conflicts. */
+ if ((S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->our_entry.mode)) ||
+ (S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->their_entry.mode)))
+ return 0;
+
+ /* Reject name conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0)
+ return 0;
+
+ if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 ||
+ (error = git_merge_file_input_from_index_entry(&ancestor, diff_list->repo, &conflict->ancestor_entry)) < 0 ||
+ (error = git_merge_file_input_from_index_entry(&ours, diff_list->repo, &conflict->our_entry)) < 0 ||
+ (error = git_merge_file_input_from_index_entry(&theirs, diff_list->repo, &conflict->their_entry)) < 0 ||
+ (error = git_merge_files(&result, &ancestor, &ours, &theirs, automerge_flags)) < 0 ||
+ !result.automergeable ||
+ (error = git_odb_write(&automerge_oid, odb, result.data, result.len, GIT_OBJ_BLOB)) < 0)
+ goto done;
+
+ if ((index_entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL)
+ GITERR_CHECK_ALLOC(index_entry);
+
+ index_entry->path = git_pool_strdup(&diff_list->pool, result.path);
+ GITERR_CHECK_ALLOC(index_entry->path);
+
+ index_entry->file_size = result.len;
+ index_entry->mode = result.mode;
+ git_oid_cpy(&index_entry->oid, &automerge_oid);
+
+ git_vector_insert(&diff_list->staged, index_entry);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ *resolved = 1;
+
+done:
+ git_merge_file_input_free(&ancestor);
+ git_merge_file_input_free(&ours);
+ git_merge_file_input_free(&theirs);
+ git_merge_file_result_free(&result);
+ git_odb_free(odb);
+
+ return error;
+}
+
+static int merge_conflict_resolve(
+ int *out,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ unsigned int automerge_flags)
+{
+ int resolved = 0;
+ int error = 0;
+
+ *out = 0;
+
+ if ((error = merge_conflict_resolve_trivial(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (automerge_flags != GIT_MERGE_AUTOMERGE_NONE) {
+ if (!resolved && (error = merge_conflict_resolve_one_removed(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, automerge_flags)) < 0)
+ goto done;
+ }
+
+ *out = resolved;
+
+done:
+ return error;
+}
+
+/* Rename detection and coalescing */
+
+struct merge_diff_similarity {
+ unsigned char similarity;
+ size_t other_idx;
+};
+
+static int index_entry_similarity_exact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_tree_opts *opts)
+{
+ GIT_UNUSED(repo);
+ GIT_UNUSED(a_idx);
+ GIT_UNUSED(b_idx);
+ GIT_UNUSED(cache);
+ GIT_UNUSED(opts);
+
+ if (git_oid__cmp(&a->oid, &b->oid) == 0)
+ return 100;
+
+ return 0;
+}
+
+static int index_entry_similarity_calc(
+ void **out,
+ git_repository *repo,
+ git_index_entry *entry,
+ const git_merge_tree_opts *opts)
+{
+ git_blob *blob;
+ git_diff_file diff_file = {{{0}}};
+ git_off_t blobsize;
+ int error;
+
+ *out = NULL;
+
+ if ((error = git_blob_lookup(&blob, repo, &entry->oid)) < 0)
+ return error;
+
+ git_oid_cpy(&diff_file.oid, &entry->oid);
+ diff_file.path = entry->path;
+ diff_file.size = entry->file_size;
+ diff_file.mode = entry->mode;
+ diff_file.flags = 0;
+
+ blobsize = git_blob_rawsize(blob);
+
+ /* file too big for rename processing */
+ if (!git__is_sizet(blobsize))
+ return 0;
+
+ error = opts->metric->buffer_signature(out, &diff_file,
+ git_blob_rawcontent(blob), (size_t)blobsize,
+ opts->metric->payload);
+
+ git_blob_free(blob);
+
+ return error;
+}
+
+static int index_entry_similarity_inexact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_tree_opts *opts)
+{
+ int score = 0;
+ int error = 0;
+
+ if (GIT_MODE_TYPE(a->mode) != GIT_MODE_TYPE(b->mode))
+ return 0;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0)
+ return error;
+
+ /* 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(
+ &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)
+ return -1;
+
+ /* clip score */
+ if (score < 0)
+ score = 0;
+ else if (score > 100)
+ score = 100;
+
+ return score;
+}
+
+static int merge_diff_mark_similarity(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ int (*similarity_fn)(git_repository *, git_index_entry *, size_t, git_index_entry *, size_t, void **, const git_merge_tree_opts *),
+ void **cache,
+ const git_merge_tree_opts *opts)
+{
+ size_t i, j;
+ git_merge_diff *conflict_src, *conflict_tgt;
+ int similarity;
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict_src) {
+ /* Items can be the source of a rename iff they have an item in the
+ * ancestor slot and lack an item in the ours or theirs slot. */
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) ||
+ (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)))
+ continue;
+
+ git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) {
+ size_t our_idx = diff_list->conflicts.length + j;
+ size_t their_idx = (diff_list->conflicts.length * 2) + j;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry))
+ continue;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts);
+
+ if (similarity == GIT_EBUFS)
+ continue;
+ else if (similarity < 0)
+ return similarity;
+
+ if (similarity > similarity_ours[i].similarity &&
+ similarity > similarity_ours[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_ours[i].similarity > 0)
+ similarity_ours[similarity_ours[i].other_idx].similarity = 0;
+
+ if (similarity_ours[j].similarity > 0)
+ similarity_ours[similarity_ours[j].other_idx].similarity = 0;
+
+ similarity_ours[i].similarity = similarity;
+ similarity_ours[i].other_idx = j;
+
+ similarity_ours[j].similarity = similarity;
+ similarity_ours[j].other_idx = i;
+ }
+ }
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts);
+
+ if (similarity > similarity_theirs[i].similarity &&
+ similarity > similarity_theirs[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_theirs[i].similarity > 0)
+ similarity_theirs[similarity_theirs[i].other_idx].similarity = 0;
+
+ if (similarity_theirs[j].similarity > 0)
+ similarity_theirs[similarity_theirs[j].other_idx].similarity = 0;
+
+ similarity_theirs[i].similarity = similarity;
+ similarity_theirs[i].other_idx = j;
+
+ similarity_theirs[j].similarity = similarity;
+ similarity_theirs[j].other_idx = i;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Rename conflicts:
+ *
+ * Ancestor Ours Theirs
+ *
+ * 0a A A A No rename
+ * b A A* A No rename (ours was rewritten)
+ * c A A A* No rename (theirs rewritten)
+ * 1a A A B[A] Rename or rename/edit
+ * b A B[A] A (automergeable)
+ * 2 A B[A] B[A] Both renamed (automergeable)
+ * 3a A B[A] Rename/delete
+ * b A B[A] (same)
+ * 4a A B[A] B Rename/add [B~ours B~theirs]
+ * b A B B[A] (same)
+ * 5 A B[A] C[A] Both renamed ("1 -> 2")
+ * 6 A C[A] Both renamed ("2 -> 1")
+ * B C[B] [C~ours C~theirs] (automergeable)
+ */
+static void merge_diff_mark_rename_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ bool ours_renamed,
+ size_t ours_source_idx,
+ struct merge_diff_similarity *similarity_theirs,
+ bool theirs_renamed,
+ size_t theirs_source_idx,
+ git_merge_diff *target,
+ const git_merge_tree_opts *opts)
+{
+ git_merge_diff *ours_source = NULL, *theirs_source = NULL;
+
+ if (ours_renamed)
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ if (theirs_renamed)
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ /* Detect 2->1 conflicts */
+ if (ours_renamed && theirs_renamed) {
+ /* Both renamed to the same target name. */
+ if (ours_source_idx == theirs_source_idx)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED;
+ else {
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ }
+ } else if (ours_renamed) {
+ /* If our source was also renamed in theirs, this is a 1->2 */
+ if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) {
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry))
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ } else if (theirs_renamed) {
+ /* If their source was also renamed in ours, this is a 1->2 */
+ if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold)
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) {
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry))
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ }
+}
+
+GIT_INLINE(void) merge_diff_coalesce_rename(
+ git_index_entry *source_entry,
+ git_delta_t *source_status,
+ git_index_entry *target_entry,
+ git_delta_t *target_status)
+{
+ /* Coalesce the rename target into the rename source. */
+ memcpy(source_entry, target_entry, sizeof(git_index_entry));
+ *source_status = GIT_DELTA_RENAMED;
+
+ memset(target_entry, 0x0, sizeof(git_index_entry));
+ *target_status = GIT_DELTA_UNMODIFIED;
+}
+
+static void merge_diff_list_coalesce_renames(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ const git_merge_tree_opts *opts)
+{
+ size_t i;
+ bool ours_renamed = 0, theirs_renamed = 0;
+ size_t ours_source_idx = 0, theirs_source_idx = 0;
+ git_merge_diff *ours_source, *theirs_source, *target;
+
+ for (i = 0; i < diff_list->conflicts.length; i++) {
+ target = diff_list->conflicts.contents[i];
+
+ ours_renamed = 0;
+ theirs_renamed = 0;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) &&
+ similarity_ours[i].similarity >= opts->rename_threshold) {
+ ours_source_idx = similarity_ours[i].other_idx;
+
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ merge_diff_coalesce_rename(
+ &ours_source->our_entry,
+ &ours_source->our_status,
+ &target->our_entry,
+ &target->our_status);
+
+ similarity_ours[ours_source_idx].similarity = 0;
+ similarity_ours[i].similarity = 0;
+
+ ours_renamed = 1;
+ }
+
+ /* insufficient to determine direction */
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) &&
+ similarity_theirs[i].similarity >= opts->rename_threshold) {
+ theirs_source_idx = similarity_theirs[i].other_idx;
+
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ merge_diff_coalesce_rename(
+ &theirs_source->their_entry,
+ &theirs_source->their_status,
+ &target->their_entry,
+ &target->their_status);
+
+ similarity_theirs[theirs_source_idx].similarity = 0;
+ similarity_theirs[i].similarity = 0;
+
+ theirs_renamed = 1;
+ }
+
+ merge_diff_mark_rename_conflict(diff_list,
+ similarity_ours, ours_renamed, ours_source_idx,
+ similarity_theirs, theirs_renamed, theirs_source_idx,
+ target, opts);
+ }
+}
+
+static int merge_diff_empty(const git_vector *conflicts, size_t idx)
+{
+ git_merge_diff *conflict = conflicts->contents[idx];
+
+ return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry));
+}
+
+static void merge_diff_list_count_candidates(
+ git_merge_diff_list *diff_list,
+ size_t *src_count,
+ size_t *tgt_count)
+{
+ git_merge_diff *entry;
+ size_t i;
+
+ *src_count = 0;
+ *tgt_count = 0;
+
+ git_vector_foreach(&diff_list->conflicts, i, entry) {
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) &&
+ (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry)))
+ src_count++;
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry))
+ tgt_count++;
+ }
+}
+
+int git_merge_diff_list__find_renames(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ const git_merge_tree_opts *opts)
+{
+ struct merge_diff_similarity *similarity_ours, *similarity_theirs;
+ void **cache = NULL;
+ size_t cache_size = 0;
+ size_t src_count, tgt_count, i;
+ int error = 0;
+
+ assert(diff_list && opts);
+
+ if ((opts->flags & GIT_MERGE_TREE_FIND_RENAMES) == 0)
+ return 0;
+
+ similarity_ours = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_ours);
+
+ similarity_theirs = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_theirs);
+
+ /* Calculate similarity between items that were deleted from the ancestor
+ * and added in the other branch.
+ */
+ if ((error = merge_diff_mark_similarity(repo, diff_list, similarity_ours,
+ similarity_theirs, index_entry_similarity_exact, NULL, opts)) < 0)
+ goto done;
+
+ if (diff_list->conflicts.length <= opts->target_limit) {
+ cache_size = diff_list->conflicts.length * 3;
+ cache = git__calloc(cache_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(cache);
+
+ merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count);
+
+ if (src_count > opts->target_limit || tgt_count > opts->target_limit) {
+ /* TODO: report! */
+ } else {
+ if ((error = merge_diff_mark_similarity(
+ repo, diff_list, similarity_ours, similarity_theirs,
+ index_entry_similarity_inexact, cache, opts)) < 0)
+ goto done;
+ }
+ }
+
+ /* For entries that are appropriately similar, merge the new name's entry
+ * into the old name.
+ */
+ merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts);
+
+ /* And remove any entries that were merged and are now empty. */
+ git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty);
+
+done:
+ if (cache != NULL) {
+ for (i = 0; i < cache_size; ++i) {
+ if (cache[i] != NULL)
+ opts->metric->free_signature(cache[i], opts->metric->payload);
+ }
+
+ git__free(cache);
+ }
+
+ git__free(similarity_ours);
+ git__free(similarity_theirs);
+
+ return error;
+}
+
+/* Directory/file conflict handling */
+
+GIT_INLINE(const char *) merge_diff_path(
+ const git_merge_diff *conflict)
+{
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ return conflict->ancestor_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry))
+ return conflict->our_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return conflict->their_entry.path;
+
+ return NULL;
+}
+
+GIT_INLINE(bool) merge_diff_any_side_added_or_modified(
+ const git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED ||
+ conflict->our_status == GIT_DELTA_MODIFIED ||
+ conflict->their_status == GIT_DELTA_ADDED ||
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child)
+{
+ size_t child_len = strlen(child);
+ size_t parent_len = strlen(parent);
+
+ if (child_len < parent_len ||
+ strncmp(parent, child, parent_len) != 0)
+ return 0;
+
+ return (child[parent_len] == '/');
+}
+
+GIT_INLINE(int) merge_diff_detect_df_conflict(
+ struct merge_diff_df_data *df_data,
+ git_merge_diff *conflict)
+{
+ const char *cur_path = merge_diff_path(conflict);
+
+ /* Determine if this is a D/F conflict or the child of one */
+ if (df_data->df_path &&
+ path_is_prefixed(df_data->df_path, cur_path))
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+ else if(df_data->df_path)
+ df_data->df_path = NULL;
+ else if (df_data->prev_path &&
+ merge_diff_any_side_added_or_modified(df_data->prev_conflict) &&
+ merge_diff_any_side_added_or_modified(conflict) &&
+ path_is_prefixed(df_data->prev_path, cur_path)) {
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+
+ df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE;
+ df_data->df_path = df_data->prev_path;
+ }
+
+ df_data->prev_path = cur_path;
+ df_data->prev_conflict = conflict;
+
+ return 0;
+}
+
+/* Conflict handling */
+
+GIT_INLINE(int) merge_diff_detect_type(
+ git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED &&
+ conflict->their_status == GIT_DELTA_ADDED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_ADDED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_DELETED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else
+ conflict->type = GIT_MERGE_DIFF_NONE;
+
+ return 0;
+}
+
+GIT_INLINE(int) index_entry_dup(
+ git_index_entry *out,
+ git_pool *pool,
+ const git_index_entry *src)
+{
+ if (src != NULL) {
+ memcpy(out, src, sizeof(git_index_entry));
+
+ if ((out->path = git_pool_strdup(pool, src->path)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) merge_delta_type_from_index_entries(
+ const git_index_entry *ancestor,
+ const git_index_entry *other)
+{
+ if (ancestor == NULL && other == NULL)
+ return GIT_DELTA_UNMODIFIED;
+ else if (ancestor == NULL && other != NULL)
+ return GIT_DELTA_ADDED;
+ else if (ancestor != NULL && other == NULL)
+ return GIT_DELTA_DELETED;
+ else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if (git_oid__cmp(&ancestor->oid, &other->oid) ||
+ ancestor->mode != other->mode)
+ return GIT_DELTA_MODIFIED;
+
+ return GIT_DELTA_UNMODIFIED;
+}
+
+static git_merge_diff *merge_diff_from_index_entries(
+ git_merge_diff_list *diff_list,
+ const git_index_entry **entries)
+{
+ git_merge_diff *conflict;
+ git_pool *pool = &diff_list->pool;
+
+ if ((conflict = git_pool_malloc(pool, sizeof(git_merge_diff))) == NULL)
+ return NULL;
+
+ if (index_entry_dup(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 ||
+ index_entry_dup(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 ||
+ index_entry_dup(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0)
+ return NULL;
+
+ conflict->our_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]);
+ conflict->their_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]);
+
+ return conflict;
+}
+
+/* Merge trees */
+
+static int merge_index_insert_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_df_data *merge_df_data,
+ const git_index_entry *tree_items[3])
+{
+ git_merge_diff *conflict;
+
+ if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL ||
+ merge_diff_detect_type(conflict) < 0 ||
+ merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 ||
+ git_vector_insert(&diff_list->conflicts, conflict) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int merge_index_insert_unmodified(
+ git_merge_diff_list *diff_list,
+ const git_index_entry *tree_items[3])
+{
+ int error = 0;
+ git_index_entry *entry;
+
+ entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if ((error = index_entry_dup(entry, &diff_list->pool, tree_items[0])) >= 0)
+ error = git_vector_insert(&diff_list->staged, entry);
+
+ return error;
+}
+
+int git_merge_diff_list__find_differences(
+ git_merge_diff_list *diff_list,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree)
+{
+ git_iterator *iterators[3] = {0};
+ const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3];
+ git_vector_cmp entry_compare = git_index_entry__cmp;
+ struct merge_diff_df_data df_data = {0};
+ int cur_item_modified;
+ size_t i, j;
+ int error = 0;
+
+ assert(diff_list && our_tree && their_tree);
+
+ if ((error = git_iterator_for_tree(&iterators[TREE_IDX_ANCESTOR], (git_tree *)ancestor_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_tree(&iterators[TREE_IDX_OURS], (git_tree *)our_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_tree(&iterators[TREE_IDX_THEIRS], (git_tree *)their_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
+ goto done;
+
+ /* Set up the iterators */
+ for (i = 0; i < 3; i++) {
+ error = git_iterator_current(&items[i], iterators[i]);
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+
+ while (true) {
+ for (i = 0; i < 3; i++)
+ cur_items[i] = NULL;
+
+ best_cur_item = NULL;
+ cur_item_modified = 0;
+
+ /* Find the next path(s) to consume from each iterator */
+ for (i = 0; i < 3; i++) {
+ if (items[i] == NULL) {
+ cur_item_modified = 1;
+ continue;
+ }
+
+ if (best_cur_item == NULL) {
+ best_cur_item = items[i];
+ cur_items[i] = items[i];
+ } else {
+ int path_diff = entry_compare(items[i], best_cur_item);
+
+ if (path_diff < 0) {
+ /*
+ * Found an item that sorts before our current item, make
+ * our current item this one.
+ */
+ for (j = 0; j < i; j++)
+ cur_items[j] = NULL;
+
+ cur_item_modified = 1;
+ best_cur_item = items[i];
+ cur_items[i] = items[i];
+ } else if (path_diff > 0) {
+ /* No entry for the current item, this is modified */
+ cur_item_modified = 1;
+ } else if (path_diff == 0) {
+ cur_items[i] = items[i];
+
+ if (!cur_item_modified)
+ cur_item_modified = index_entry_cmp(best_cur_item, items[i]);
+ }
+ }
+ }
+
+ if (best_cur_item == NULL)
+ break;
+
+ if (cur_item_modified)
+ error = merge_index_insert_conflict(diff_list, &df_data, cur_items);
+ else
+ error = merge_index_insert_unmodified(diff_list, cur_items);
+ if (error < 0)
+ goto done;
+
+ /* Advance each iterator that participated */
+ for (i = 0; i < 3; i++) {
+ if (cur_items[i] == NULL)
+ continue;
+
+ error = git_iterator_advance(&items[i], iterators[i]);
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+ }
+
+done:
+ for (i = 0; i < 3; i++)
+ git_iterator_free(iterators[i]);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
+{
+ git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list));
+
+ if (diff_list == NULL)
+ return NULL;
+
+ diff_list->repo = repo;
+
+ if (git_vector_init(&diff_list->staged, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->conflicts, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->resolved, 0, NULL) < 0 ||
+ git_pool_init(&diff_list->pool, 1, 0) < 0)
+ return NULL;
+
+ return diff_list;
+}
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list)
+{
+ if (!diff_list)
+ return;
+
+ git_vector_free(&diff_list->staged);
+ git_vector_free(&diff_list->conflicts);
+ git_vector_free(&diff_list->resolved);
+ git_pool_clear(&diff_list->pool);
+ git__free(diff_list);
+}
+
+static int merge_tree_normalize_opts(
+ git_repository *repo,
+ git_merge_tree_opts *opts,
+ const git_merge_tree_opts *given)
+{
+ git_config *cfg = NULL;
+ int error = 0;
+
+ assert(repo && opts);
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_merge_tree_opts));
+ else {
+ git_merge_tree_opts init = GIT_MERGE_TREE_OPTS_INIT;
+ memcpy(opts, &init, sizeof(init));
+
+ opts->flags = GIT_MERGE_TREE_FIND_RENAMES;
+ opts->rename_threshold = GIT_MERGE_TREE_RENAME_THRESHOLD;
+ }
+
+ if (!opts->target_limit) {
+ int32_t limit = 0;
+
+ opts->target_limit = GIT_MERGE_TREE_TARGET_LIMIT;
+
+ if (git_config_get_int32(&limit, cfg, "merge.renameLimit") < 0) {
+ giterr_clear();
+
+ if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)
+ giterr_clear();
+ }
+
+ if (limit > 0)
+ opts->target_limit = limit;
+ }
+
+ /* assign the internal metric with whitespace flag as payload */
+ if (!opts->metric) {
+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
+ GITERR_CHECK_ALLOC(opts->metric);
+
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
+
+ if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_IGNORE_WHITESPACE;
+ else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_NORMAL;
+ else
+ opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;
+ }
+
+ return 0;
+}
+
+
+static int merge_index_insert_reuc(
+ git_index *index,
+ size_t idx,
+ const git_index_entry *entry)
+{
+ const git_index_reuc_entry *reuc;
+ int mode[3] = { 0, 0, 0 };
+ git_oid const *oid[3] = { NULL, NULL, NULL };
+ size_t i;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry))
+ return 0;
+
+ if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) {
+ for (i = 0; i < 3; i++) {
+ mode[i] = reuc->mode[i];
+ oid[i] = &reuc->oid[i];
+ }
+ }
+
+ mode[idx] = entry->mode;
+ oid[idx] = &entry->oid;
+
+ return git_index_reuc_add(index, entry->path,
+ mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]);
+}
+
+int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list)
+{
+ git_index *index;
+ size_t i;
+ git_index_entry *entry;
+ git_merge_diff *conflict;
+ int error = 0;
+
+ *out = NULL;
+
+ if ((error = git_index_new(&index)) < 0)
+ return error;
+
+ git_vector_foreach(&diff_list->staged, i, entry) {
+ if ((error = git_index_add(index, entry)) < 0)
+ goto on_error;
+ }
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0)
+ goto on_error;
+ }
+
+ /* Add each rename entry to the rename portion of the index. */
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const char *ancestor_path, *our_path, *their_path;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ continue;
+
+ ancestor_path = conflict->ancestor_entry.path;
+
+ our_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ conflict->our_entry.path : NULL;
+
+ their_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ conflict->their_entry.path : NULL;
+
+ if ((our_path && strcmp(ancestor_path, our_path) != 0) ||
+ (their_path && strcmp(ancestor_path, their_path) != 0)) {
+ if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0)
+ goto on_error;
+ }
+ }
+
+ /* Add each entry in the resolved conflict to the REUC independently, since
+ * the paths may differ due to renames. */
+ git_vector_foreach(&diff_list->resolved, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if (ancestor != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0)
+ goto on_error;
+
+ if (ours != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0)
+ goto on_error;
+
+ if (theirs != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0)
+ goto on_error;
+ }
+
+ *out = index;
+ return 0;
+
+on_error:
+ git_index_free(index);
+
+ return error;
+}
+
+int git_merge_trees(
+ git_index **out,
+ git_repository *repo,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree,
+ const git_merge_tree_opts *given_opts)
+{
+ git_merge_diff_list *diff_list;
+ git_merge_tree_opts opts;
+ git_merge_diff *conflict;
+ git_vector changes;
+ size_t i;
+ int error = 0;
+
+ assert(out && repo && our_tree && their_tree);
+
+ *out = NULL;
+
+ if ((error = merge_tree_normalize_opts(repo, &opts, given_opts)) < 0)
+ return error;
+
+ diff_list = git_merge_diff_list__alloc(repo);
+ GITERR_CHECK_ALLOC(diff_list);
+
+ if ((error = git_merge_diff_list__find_differences(diff_list, ancestor_tree, our_tree, their_tree)) < 0 ||
+ (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0)
+ goto done;
+
+ memcpy(&changes, &diff_list->conflicts, sizeof(git_vector));
+ git_vector_clear(&diff_list->conflicts);
+
+ git_vector_foreach(&changes, i, conflict) {
+ int resolved = 0;
+
+ if ((error = merge_conflict_resolve(&resolved, diff_list, conflict, opts.automerge_flags)) < 0)
+ goto done;
+
+ if (!resolved)
+ git_vector_insert(&diff_list->conflicts, conflict);
+ }
+
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ error = index_from_diff_list(out, diff_list);
+
+done:
+ git_merge_diff_list__free(diff_list);
+
+ return error;
+}
+
+/* Merge setup / cleanup */
+
+static int write_orig_head(
+ git_repository *repo,
+ const git_merge_head *our_head)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char orig_oid_str[GIT_OID_HEXSZ + 1];
+ int error = 0;
+
+ assert(repo && our_head);
+
+ git_oid_tostr(orig_oid_str, GIT_OID_HEXSZ+1, &our_head->oid);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) == 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", orig_oid_str)) == 0)
+ error = git_filebuf_commit(&file, 0666);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_head(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char merge_oid_str[GIT_OID_HEXSZ + 1];
+ size_t i;
+ int error = 0;
+
+ assert(repo && heads);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ git_oid_tostr(merge_oid_str, GIT_OID_HEXSZ+1, &heads[i]->oid);
+
+ if ((error = git_filebuf_printf(&file, "%s\n", merge_oid_str)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&file, 0666);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_mode(git_repository *repo, unsigned int flags)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ /* For future expansion */
+ GIT_UNUSED(flags);
+
+ assert(repo);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file, 0666);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+struct merge_msg_entry {
+ const git_merge_head *merge_head;
+ bool written;
+};
+
+static int msg_entry_is_branch(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0);
+}
+
+static int msg_entry_is_tracking(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0);
+}
+
+static int msg_entry_is_tag(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0);
+}
+
+static int msg_entry_is_remote(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ if (entry->written == 0 &&
+ entry->merge_head->remote_url != NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0)
+ {
+ struct merge_msg_entry *existing;
+
+ /* Match only branches from the same remote */
+ if (entries->length == 0)
+ return 1;
+
+ existing = git_vector_get(entries, 0);
+
+ return (git__strcmp(existing->merge_head->remote_url,
+ entry->merge_head->remote_url) == 0);
+ }
+
+ return 0;
+}
+
+static int msg_entry_is_oid(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 0 &&
+ merge_msg_entry->merge_head->ref_name == NULL &&
+ merge_msg_entry->merge_head->remote_url == NULL);
+}
+
+static int merge_msg_entry_written(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 1);
+}
+
+static int merge_msg_entries(
+ git_vector *v,
+ const struct merge_msg_entry *entries,
+ size_t len,
+ int (*match)(const struct merge_msg_entry *entry, git_vector *entries))
+{
+ size_t i;
+ int matches, total = 0;
+
+ git_vector_clear(v);
+
+ for (i = 0; i < len; i++) {
+ if ((matches = match(&entries[i], v)) < 0)
+ return matches;
+ else if (!matches)
+ continue;
+
+ git_vector_insert(v, (struct merge_msg_entry *)&entries[i]);
+ total++;
+ }
+
+ return total;
+}
+
+static int merge_msg_write_entries(
+ git_filebuf *file,
+ git_vector *entries,
+ const char *item_name,
+ const char *item_plural_name,
+ size_t ref_name_skip,
+ const char *source,
+ char sep)
+{
+ struct merge_msg_entry *entry;
+ size_t i;
+ int error = 0;
+
+ if (entries->length == 0)
+ return 0;
+
+ if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "%s ",
+ (entries->length == 1) ? item_name : item_plural_name)) < 0)
+ goto done;
+
+ git_vector_foreach(entries, i, entry) {
+ if (i > 0 &&
+ (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0)
+ goto done;
+
+ entry->written = 1;
+ }
+
+ if (source)
+ error = git_filebuf_printf(file, " of %s", source);
+
+done:
+ return error;
+}
+
+static int merge_msg_write_branches(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_tracking(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "remote-tracking branch", "remote-tracking branches", 0, NULL, sep);
+}
+
+static int merge_msg_write_tags(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_remotes(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ const char *source;
+
+ if (entries->length == 0)
+ return 0;
+
+ source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url;
+
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep);
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ char oid_str[GIT_OID_HEXSZ + 1];
+ struct merge_msg_entry *entries;
+ git_vector matching = GIT_VECTOR_INIT;
+ size_t i;
+ char sep = 0;
+ int error = 0;
+
+ assert(repo && heads);
+
+ entries = git__calloc(heads_len, sizeof(struct merge_msg_entry));
+ GITERR_CHECK_ALLOC(entries);
+
+ if (git_vector_init(&matching, heads_len, NULL) < 0) {
+ git__free(entries);
+ return -1;
+ }
+
+ for (i = 0; i < heads_len; i++)
+ entries[i].merge_head = heads[i];
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0 ||
+ (error = git_filebuf_write(&file, "Merge ", 6)) < 0)
+ goto cleanup;
+
+ /*
+ * This is to emulate the format of MERGE_MSG by core git.
+ *
+ * Core git will write all the commits specified by OID, in the order
+ * provided, until the first named branch or tag is reached, at which
+ * point all branches will be written in the order provided, then all
+ * tags, then all remote tracking branches and finally all commits that
+ * were specified by OID that were not already written.
+ *
+ * Yes. Really.
+ */
+ for (i = 0; i < heads_len; i++) {
+ if (!msg_entry_is_oid(&entries[i]))
+ break;
+
+ git_oid_fmt(oid_str, &entries[i].merge_head->oid);
+ oid_str[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git_filebuf_printf(&file, "%scommit '%s'", (i > 0) ? "; " : "", oid_str)) < 0)
+ goto cleanup;
+
+ entries[i].written = 1;
+ }
+
+ if (i)
+ sep = ';';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 ||
+ (error = merge_msg_write_branches(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 ||
+ (error = merge_msg_write_tracking(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 ||
+ (error = merge_msg_write_tags(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ /* We should never be called with multiple remote branches, but handle
+ * it in case we are... */
+ while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) {
+ if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if (merge_msg_entry_written(&entries[i]))
+ continue;
+
+ git_oid_fmt(oid_str, &entries[i].merge_head->oid);
+ oid_str[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git_filebuf_printf(&file, "; commit '%s'", oid_str)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_filebuf_printf(&file, "\n")) < 0 ||
+ (error = git_filebuf_commit(&file, 0666)) < 0)
+ goto cleanup;
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ git_vector_free(&matching);
+ git__free(entries);
+
+ return error;
+}
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *heads[],
+ size_t heads_len,
+ unsigned int flags)
+{
+ int error = 0;
+
+ assert (repo && our_head && heads);
+
+ if ((error = write_orig_head(repo, our_head)) == 0 &&
+ (error = write_merge_head(repo, heads, heads_len)) == 0 &&
+ (error = write_merge_mode(repo, flags)) == 0) {
+ error = write_merge_msg(repo, heads, heads_len);
+ }
+
+ return error;
+}
+
+int git_repository_merge_cleanup(git_repository *repo)
+{
+ int error = 0;
+ git_buf merge_head_path = GIT_BUF_INIT,
+ merge_mode_path = GIT_BUF_INIT,
+ merge_msg_path = GIT_BUF_INIT;
+
+ assert(repo);
+
+ if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
+ git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if (git_path_isfile(merge_head_path.ptr)) {
+ if ((error = p_unlink(merge_head_path.ptr)) < 0)
+ goto cleanup;
+ }
+
+ if (git_path_isfile(merge_mode_path.ptr))
+ (void)p_unlink(merge_mode_path.ptr);
+
+ if (git_path_isfile(merge_msg_path.ptr))
+ (void)p_unlink(merge_msg_path.ptr);
+
+cleanup:
+ git_buf_free(&merge_msg_path);
+ git_buf_free(&merge_mode_path);
+ git_buf_free(&merge_head_path);
+
+ return error;
+}
+
+/* Merge heads are the input to merge */
+
+static int merge_head_init(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *ref_name,
+ const char *remote_url,
+ const git_oid *oid)
+{
+ git_merge_head *head;
+ int error = 0;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ head = git__calloc(1, sizeof(git_merge_head));
+ GITERR_CHECK_ALLOC(head);
+
+ if (ref_name) {
+ head->ref_name = git__strdup(ref_name);
+ GITERR_CHECK_ALLOC(head->ref_name);
+ }
+
+ if (remote_url) {
+ head->remote_url = git__strdup(remote_url);
+ GITERR_CHECK_ALLOC(head->remote_url);
+ }
+
+ git_oid_cpy(&head->oid, oid);
+
+ if ((error = git_commit_lookup(&head->commit, repo, &head->oid)) < 0) {
+ git_merge_head_free(head);
+ return error;
+ }
+
+ *out = head;
+ return error;
+}
+
+int git_merge_head_from_ref(
+ git_merge_head **out,
+ git_repository *repo,
+ git_reference *ref)
+{
+ git_reference *resolved;
+ int error = 0;
+
+ assert(out && repo && ref);
+
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return error;
+
+ error = merge_head_init(out, repo, git_reference_name(ref), NULL,
+ git_reference_target(resolved));
+
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_merge_head_from_oid(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_oid *oid)
+{
+ assert(out && repo && oid);
+
+ return merge_head_init(out, repo, NULL, NULL, oid);
+}
+
+int git_merge_head_from_fetchhead(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_url,
+ const git_oid *oid)
+{
+ assert(repo && branch_name && remote_url && oid);
+
+ return merge_head_init(out, repo, branch_name, remote_url, oid);
+}
+
+void git_merge_head_free(git_merge_head *head)
+{
+ if (head == NULL)
+ return;
+
+ if (head->commit != NULL)
+ git_object_free((git_object *)head->commit);
+
+ if (head->ref_name != NULL)
+ git__free(head->ref_name);
+
+ if (head->remote_url != NULL)
+ git__free(head->remote_url);
+
+ git__free(head);
+}
diff --git a/src/merge.h b/src/merge.h
index 22c644270..ba6725de9 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -7,16 +7,143 @@
#ifndef INCLUDE_merge_h__
#define INCLUDE_merge_h__
-#include "git2/types.h"
-#include "git2/merge.h"
-#include "commit_list.h"
#include "vector.h"
+#include "commit_list.h"
+#include "pool.h"
+
+#include "git2/merge.h"
+#include "git2/types.h"
#define GIT_MERGE_MSG_FILE "MERGE_MSG"
#define GIT_MERGE_MODE_FILE "MERGE_MODE"
-#define MERGE_CONFIG_FILE_MODE 0666
+#define GIT_MERGE_TREE_RENAME_THRESHOLD 50
+#define GIT_MERGE_TREE_TARGET_LIMIT 1000
+
+/** Types of changes when files are merged from branch to branch. */
+typedef enum {
+ /* No conflict - a change only occurs in one branch. */
+ GIT_MERGE_DIFF_NONE = 0,
+
+ /* Occurs when a file is modified in both branches. */
+ GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0),
+
+ /* Occurs when a file is added in both branches. */
+ GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1),
+
+ /* Occurs when a file is deleted in both branches. */
+ GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2),
+
+ /* Occurs when a file is modified in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3),
+
+ /* Occurs when a file is renamed in one branch and modified in the other. */
+ GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4),
+
+ /* Occurs when a file is renamed in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5),
+
+ /* Occurs when a file is renamed in one branch and a file with the same
+ * name is added in the other. Eg, A->B and new file B. Core git calls
+ * this a "rename/delete". */
+ GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6),
+
+ /* Occurs when both a file is renamed to the same name in the ours and
+ * theirs branches. Eg, A->B and A->B in both. Automergeable. */
+ GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7),
+
+ /* Occurs when a file is renamed to different names in the ours and theirs
+ * branches. Eg, A->B and A->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8),
+
+ /* Occurs when two files are renamed to the same name in the ours and
+ * theirs branches. Eg, A->C and B->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9),
+
+ /* Occurs when an item at a path in one branch is a directory, and an
+ * item at the same path in a different branch is a file. */
+ GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10),
+
+ /* The child of a folder that is in a directory/file conflict. */
+ GIT_MERGE_DIFF_DF_CHILD = (1 << 11),
+} git_merge_diff_type_t;
+
+
+typedef struct {
+ git_repository *repo;
+ git_pool pool;
+
+ /* Vector of git_index_entry that represent the merged items that
+ * have been staged, either because only one side changed, or because
+ * the two changes were non-conflicting and mergeable. These items
+ * will be written as staged entries in the main index.
+ */
+ git_vector staged;
+
+ /* Vector of git_merge_diff entries that represent the conflicts that
+ * have not been automerged. These items will be written to high-stage
+ * entries in the main index.
+ */
+ git_vector conflicts;
+
+ /* Vector of git_merge_diff that have been automerged. These items
+ * will be written to the REUC when the index is produced.
+ */
+ git_vector resolved;
+} git_merge_diff_list;
+
+/**
+ * Description of changes to one file across three trees.
+ */
+typedef struct {
+ git_merge_diff_type_t type;
+
+ git_index_entry ancestor_entry;
+
+ git_index_entry our_entry;
+ git_delta_t our_status;
+
+ git_index_entry their_entry;
+ git_delta_t their_status;
+} git_merge_diff;
+
+/** Internal structure for merge inputs */
+struct git_merge_head {
+ char *ref_name;
+ char *remote_url;
+
+ git_oid oid;
+ git_commit *commit;
+};
+
+int git_merge__bases_many(
+ git_commit_list **out,
+ git_revwalk *walk,
+ git_commit_list_node *one,
+ git_vector *twos);
+
+/*
+ * Three-way tree differencing
+ */
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);
+
+int git_merge_diff_list__find_differences(git_merge_diff_list *merge_diff_list,
+ const git_tree *ancestor_tree,
+ const git_tree *ours_tree,
+ const git_tree *theirs_tree);
+
+int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_tree_opts *opts);
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list);
+
+/* Merge metadata setup */
-int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos);
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *their_heads[],
+ size_t their_heads_len,
+ unsigned int flags);
#endif
diff --git a/src/merge_file.c b/src/merge_file.c
new file mode 100644
index 000000000..c3477ccb9
--- /dev/null
+++ b/src/merge_file.c
@@ -0,0 +1,174 @@
+/*
+ * 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 "repository.h"
+#include "merge_file.h"
+
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/index.h"
+
+#include "xdiff/xdiff.h"
+
+#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0)
+
+GIT_INLINE(const char *) merge_file_best_path(
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs)
+{
+ if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
+ if (strcmp(ours->path, theirs->path) == 0)
+ return ours->path;
+
+ return NULL;
+ }
+
+ if (strcmp(ancestor->path, ours->path) == 0)
+ return theirs->path;
+ else if(strcmp(ancestor->path, theirs->path) == 0)
+ return ours->path;
+
+ return NULL;
+}
+
+GIT_INLINE(int) merge_file_best_mode(
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs)
+{
+ /*
+ * If ancestor didn't exist and either ours or theirs is executable,
+ * assume executable. Otherwise, if any mode changed from the ancestor,
+ * use that one.
+ */
+ if (GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) {
+ if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE ||
+ theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ return GIT_FILEMODE_BLOB;
+ }
+
+ if (ancestor->mode == ours->mode)
+ return theirs->mode;
+ else if(ancestor->mode == theirs->mode)
+ return ours->mode;
+
+ return 0;
+}
+
+int git_merge_file_input_from_index_entry(
+ git_merge_file_input *input,
+ git_repository *repo,
+ const git_index_entry *entry)
+{
+ git_odb *odb = NULL;
+ int error = 0;
+
+ assert(input && repo && entry);
+
+ if (entry->mode == 0)
+ return 0;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0 ||
+ (error = git_odb_read(&input->odb_object, odb, &entry->oid)) < 0)
+ goto done;
+
+ input->mode = entry->mode;
+ input->path = git__strdup(entry->path);
+ input->mmfile.size = git_odb_object_size(input->odb_object);
+ input->mmfile.ptr = (char *)git_odb_object_data(input->odb_object);
+
+ if (input->label == NULL)
+ input->label = entry->path;
+
+done:
+ git_odb_free(odb);
+
+ return error;
+}
+
+int git_merge_file_input_from_diff_file(
+ git_merge_file_input *input,
+ git_repository *repo,
+ const git_diff_file *file)
+{
+ git_odb *odb = NULL;
+ int error = 0;
+
+ assert(input && repo && file);
+
+ if (file->mode == 0)
+ return 0;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0 ||
+ (error = git_odb_read(&input->odb_object, odb, &file->oid)) < 0)
+ goto done;
+
+ input->mode = file->mode;
+ input->path = git__strdup(file->path);
+ input->mmfile.size = git_odb_object_size(input->odb_object);
+ input->mmfile.ptr = (char *)git_odb_object_data(input->odb_object);
+
+ if (input->label == NULL)
+ input->label = file->path;
+
+done:
+ git_odb_free(odb);
+
+ return error;
+}
+
+int git_merge_files(
+ git_merge_file_result *out,
+ git_merge_file_input *ancestor,
+ git_merge_file_input *ours,
+ git_merge_file_input *theirs,
+ git_merge_automerge_flags flags)
+{
+ xmparam_t xmparam;
+ mmbuffer_t mmbuffer;
+ int xdl_result;
+ int error = 0;
+
+ assert(out && ancestor && ours && theirs);
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if (!GIT_MERGE_FILE_SIDE_EXISTS(ours) || !GIT_MERGE_FILE_SIDE_EXISTS(theirs))
+ return 0;
+
+ memset(&xmparam, 0x0, sizeof(xmparam_t));
+ xmparam.ancestor = ancestor->label;
+ xmparam.file1 = ours->label;
+ xmparam.file2 = theirs->label;
+
+ out->path = merge_file_best_path(ancestor, ours, theirs);
+ out->mode = merge_file_best_mode(ancestor, ours, theirs);
+
+ if (flags == GIT_MERGE_AUTOMERGE_FAVOR_OURS)
+ xmparam.favor = XDL_MERGE_FAVOR_OURS;
+
+ if (flags == GIT_MERGE_AUTOMERGE_FAVOR_THEIRS)
+ xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
+
+ if ((xdl_result = xdl_merge(&ancestor->mmfile, &ours->mmfile,
+ &theirs->mmfile, &xmparam, &mmbuffer)) < 0) {
+ giterr_set(GITERR_MERGE, "Failed to merge files.");
+ error = -1;
+ goto done;
+ }
+
+ out->automergeable = (xdl_result == 0);
+ out->data = (unsigned char *)mmbuffer.ptr;
+ out->len = mmbuffer.size;
+
+done:
+ return error;
+}
diff --git a/src/merge_file.h b/src/merge_file.h
new file mode 100644
index 000000000..0af2f0a57
--- /dev/null
+++ b/src/merge_file.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_filediff_h__
+#define INCLUDE_filediff_h__
+
+#include "xdiff/xdiff.h"
+
+#include "git2/merge.h"
+
+typedef struct {
+ const char *label;
+ char *path;
+ unsigned int mode;
+ mmfile_t mmfile;
+
+ git_odb_object *odb_object;
+} git_merge_file_input;
+
+#define GIT_MERGE_FILE_INPUT_INIT {0}
+
+typedef struct {
+ bool automergeable;
+
+ const char *path;
+ int mode;
+
+ unsigned char *data;
+ size_t len;
+} git_merge_file_result;
+
+#define GIT_MERGE_FILE_RESULT_INIT {0}
+
+int git_merge_file_input_from_index_entry(
+ git_merge_file_input *input,
+ git_repository *repo,
+ const git_index_entry *entry);
+
+int git_merge_file_input_from_diff_file(
+ git_merge_file_input *input,
+ git_repository *repo,
+ const git_diff_file *file);
+
+int git_merge_files(
+ git_merge_file_result *out,
+ git_merge_file_input *ancestor,
+ git_merge_file_input *ours,
+ git_merge_file_input *theirs,
+ git_merge_automerge_flags flags);
+
+GIT_INLINE(void) git_merge_file_input_free(git_merge_file_input *input)
+{
+ assert(input);
+ git__free(input->path);
+ git_odb_object_free(input->odb_object);
+}
+
+GIT_INLINE(void) git_merge_file_result_free(git_merge_file_result *filediff)
+{
+ if (filediff == NULL)
+ return;
+
+ /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */
+ if (filediff->data != NULL)
+ free(filediff->data);
+}
+
+#endif
diff --git a/src/mwindow.c b/src/mwindow.c
index b35503d46..7e5fcdfbc 100644
--- a/src/mwindow.c
+++ b/src/mwindow.c
@@ -162,7 +162,7 @@ static git_mwindow *new_window(
git_mwindow *w;
w = git__malloc(sizeof(*w));
-
+
if (w == NULL)
return NULL;
diff --git a/src/notes.c b/src/notes.c
index ef48ac88e..beace1b50 100644
--- a/src/notes.c
+++ b/src/notes.c
@@ -13,6 +13,12 @@
#include "iterator.h"
#include "signature.h"
+static int note_error_notfound(void)
+{
+ giterr_set(GITERR_INVALID, "Note could not be found");
+ return GIT_ENOTFOUND;
+}
+
static int find_subtree_in_current_level(
git_tree **out,
git_repository *repo,
@@ -26,7 +32,7 @@ static int find_subtree_in_current_level(
*out = NULL;
if (parent == NULL)
- return GIT_ENOTFOUND;
+ return note_error_notfound();
for (i = 0; i < git_tree_entrycount(parent); i++) {
entry = git_tree_entry_byindex(parent, i);
@@ -44,7 +50,7 @@ static int find_subtree_in_current_level(
return GIT_EEXISTS;
}
- return GIT_ENOTFOUND;
+ return note_error_notfound();
}
static int find_subtree_r(git_tree **out, git_tree *root,
@@ -56,9 +62,8 @@ static int find_subtree_r(git_tree **out, git_tree *root,
*out = NULL;
error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
- if (error == GIT_EEXISTS) {
+ if (error == GIT_EEXISTS)
return git_tree_lookup(out, repo, git_tree_id(root));
- }
if (error < 0)
return error;
@@ -85,7 +90,8 @@ static int find_blob(git_oid *blob, git_tree *tree, const char *target)
return 0;
}
}
- return GIT_ENOTFOUND;
+
+ return note_error_notfound();
}
static int tree_write(
@@ -316,8 +322,8 @@ static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
return 0;
}
-static int note_lookup(git_note **out, git_repository *repo,
- git_tree *tree, const char *target)
+static int note_lookup(
+ git_note **out, git_repository *repo, git_tree *tree, const char *target)
{
int error, fanout = 0;
git_oid oid;
@@ -382,6 +388,7 @@ static int note_get_default_ref(const char **out, git_repository *repo)
ret = git_config_get_string(out, cfg, "core.notesRef");
if (ret == GIT_ENOTFOUND) {
+ giterr_clear();
*out = GIT_NOTES_DEFAULT_REF;
return 0;
}
@@ -432,12 +439,10 @@ int git_note_read(git_note **out, git_repository *repo,
target = git_oid_allocfmt(oid);
GITERR_CHECK_ALLOC(target);
- if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
- goto cleanup;
-
- error = note_lookup(out, repo, tree, target);
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, repo, &notes_ref)))
+ error = note_lookup(out, repo, tree, target);
-cleanup:
git__free(target);
git_tree_free(tree);
git_commit_free(commit);
@@ -489,13 +494,11 @@ int git_note_remove(git_repository *repo, const char *notes_ref,
target = git_oid_allocfmt(oid);
GITERR_CHECK_ALLOC(target);
- if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
- goto cleanup;
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, repo, &notes_ref)))
+ error = note_remove(
+ repo, author, committer, notes_ref, tree, target, &commit);
- error = note_remove(repo, author, committer, notes_ref,
- tree, target, &commit);
-
-cleanup:
git__free(target);
git_commit_free(commit);
git_tree_free(tree);
@@ -533,7 +536,7 @@ static int process_entry_path(
const char* entry_path,
git_oid *annotated_object_id)
{
- int error = -1;
+ int error = 0;
size_t i = 0, j = 0, len;
git_buf buf = GIT_BUF_INIT;
@@ -576,30 +579,30 @@ cleanup:
}
int git_note_foreach(
- git_repository *repo,
- const char *notes_ref,
- git_note_foreach_cb note_cb,
- void *payload)
+ 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;
+ 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;
+ 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;
- }
- }
+ 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;
+ if (error == GIT_ITEROVER)
+ error = 0;
- git_note_iterator_free(iter);
- return error;
+ git_note_iterator_free(iter);
+ return error;
}
@@ -644,18 +647,12 @@ int git_note_next(
const git_index_entry *item;
if ((error = git_iterator_current(&item, it)) < 0)
- goto exit;
+ return error;
- if (item != NULL) {
- git_oid_cpy(note_id, &item->oid);
- error = process_entry_path(item->path, annotated_id);
+ git_oid_cpy(note_id, &item->oid);
- if (error >= 0)
- error = git_iterator_advance(NULL, it);
- } else {
- error = GIT_ITEROVER;
- }
+ if (!(error = process_entry_path(item->path, annotated_id)))
+ git_iterator_advance(NULL, it);
-exit:
return error;
}
diff --git a/src/object.c b/src/object.c
index 80fe51152..9b8ccdd3e 100644
--- a/src/object.c
+++ b/src/object.c
@@ -18,65 +18,38 @@
static const int OBJECT_BASE_SIZE = 4096;
-static struct {
+typedef struct {
const char *str; /* type name string */
- int loose; /* valid loose object type flag */
size_t size; /* size in bytes of the object structure */
-} git_objects_table[] = {
+
+ int (*parse)(void *self, git_odb_object *obj);
+ void (*free)(void *self);
+} git_object_def;
+
+static git_object_def git_objects_table[] = {
/* 0 = GIT_OBJ__EXT1 */
- { "", 0, 0},
+ { "", 0, NULL, NULL },
/* 1 = GIT_OBJ_COMMIT */
- { "commit", 1, sizeof(struct git_commit)},
+ { "commit", sizeof(git_commit), git_commit__parse, git_commit__free },
/* 2 = GIT_OBJ_TREE */
- { "tree", 1, sizeof(struct git_tree) },
+ { "tree", sizeof(git_tree), git_tree__parse, git_tree__free },
/* 3 = GIT_OBJ_BLOB */
- { "blob", 1, sizeof(struct git_blob) },
+ { "blob", sizeof(git_blob), git_blob__parse, git_blob__free },
/* 4 = GIT_OBJ_TAG */
- { "tag", 1, sizeof(struct git_tag) },
+ { "tag", sizeof(git_tag), git_tag__parse, git_tag__free },
/* 5 = GIT_OBJ__EXT2 */
- { "", 0, 0 },
-
+ { "", 0, NULL, NULL },
/* 6 = GIT_OBJ_OFS_DELTA */
- { "OFS_DELTA", 0, 0 },
-
+ { "OFS_DELTA", 0, NULL, NULL },
/* 7 = GIT_OBJ_REF_DELTA */
- { "REF_DELTA", 0, 0 }
+ { "REF_DELTA", 0, NULL, NULL },
};
-static int create_object(git_object **object_out, git_otype type)
-{
- git_object *object = NULL;
-
- assert(object_out);
-
- *object_out = NULL;
-
- switch (type) {
- case GIT_OBJ_COMMIT:
- case GIT_OBJ_TAG:
- case GIT_OBJ_BLOB:
- case GIT_OBJ_TREE:
- object = git__malloc(git_object__size(type));
- GITERR_CHECK_ALLOC(object);
- memset(object, 0x0, git_object__size(type));
- break;
-
- default:
- giterr_set(GITERR_INVALID, "The given type is invalid");
- return -1;
- }
-
- object->type = type;
-
- *object_out = object;
- return 0;
-}
-
int git_object__from_odb_object(
git_object **object_out,
git_repository *repo,
@@ -84,49 +57,55 @@ int git_object__from_odb_object(
git_otype type)
{
int error;
+ size_t object_size;
+ git_object_def *def;
git_object *object = NULL;
- if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {
- giterr_set(GITERR_INVALID, "The requested type does not match the type in the ODB");
+ assert(object_out);
+ *object_out = NULL;
+
+ /* Validate type match */
+ if (type != GIT_OBJ_ANY && type != odb_obj->cached.type) {
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in the ODB");
return GIT_ENOTFOUND;
}
- type = odb_obj->raw.type;
+ if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
+ giterr_set(GITERR_INVALID, "The requested type is invalid");
+ return GIT_ENOTFOUND;
+ }
- if ((error = create_object(&object, type)) < 0)
- return error;
+ /* Allocate and initialize base object */
+ object = git__calloc(1, object_size);
+ GITERR_CHECK_ALLOC(object);
- /* Initialize parent object */
git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
+ object->cached.type = odb_obj->cached.type;
+ object->cached.size = odb_obj->cached.size;
object->repo = repo;
- switch (type) {
- case GIT_OBJ_COMMIT:
- error = git_commit__parse((git_commit *)object, odb_obj);
- break;
-
- case GIT_OBJ_TREE:
- error = git_tree__parse((git_tree *)object, odb_obj);
- break;
+ /* Parse raw object data */
+ def = &git_objects_table[odb_obj->cached.type];
+ assert(def->free && def->parse);
- case GIT_OBJ_TAG:
- error = git_tag__parse((git_tag *)object, odb_obj);
- break;
+ if ((error = def->parse(object, odb_obj)) < 0)
+ def->free(object);
+ else
+ *object_out = git_cache_store_parsed(&repo->objects, object);
- case GIT_OBJ_BLOB:
- error = git_blob__parse((git_blob *)object, odb_obj);
- break;
+ return error;
+}
- default:
- break;
- }
+void git_object__free(void *obj)
+{
+ git_otype type = ((git_object *)obj)->cached.type;
- if (error < 0)
- git_object__free(object);
+ if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) ||
+ !git_objects_table[type].free)
+ git__free(obj);
else
- *object_out = git_cache_try_store(&repo->objects, object);
-
- return error;
+ git_objects_table[type].free(obj);
}
int git_object_lookup_prefix(
@@ -138,13 +117,15 @@ int git_object_lookup_prefix(
{
git_object *object = NULL;
git_odb *odb = NULL;
- git_odb_object *odb_obj;
+ git_odb_object *odb_obj = NULL;
int error = 0;
assert(repo && object_out && id);
- if (len < GIT_OID_MINPREFIXLEN)
+ if (len < GIT_OID_MINPREFIXLEN) {
+ giterr_set(GITERR_OBJECT, "Ambiguous lookup - OID prefix is too short");
return GIT_EAMBIGUOUS;
+ }
error = git_repository_odb__weakptr(&odb, repo);
if (error < 0)
@@ -154,27 +135,38 @@ int git_object_lookup_prefix(
len = GIT_OID_HEXSZ;
if (len == GIT_OID_HEXSZ) {
+ git_cached_obj *cached = NULL;
+
/* We want to match the full id : we can first look up in the cache,
* since there is no need to check for non ambiguousity
*/
- object = git_cache_get(&repo->objects, id);
- if (object != NULL) {
- if (type != GIT_OBJ_ANY && type != object->type) {
- git_object_free(object);
- giterr_set(GITERR_INVALID, "The requested type does not match the type in ODB");
- return GIT_ENOTFOUND;
+ cached = git_cache_get_any(&repo->objects, id);
+ if (cached != NULL) {
+ if (cached->flags == GIT_CACHE_STORE_PARSED) {
+ object = (git_object *)cached;
+
+ if (type != GIT_OBJ_ANY && type != object->cached.type) {
+ git_object_free(object);
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in ODB");
+ return GIT_ENOTFOUND;
+ }
+
+ *object_out = object;
+ return 0;
+ } else if (cached->flags == GIT_CACHE_STORE_RAW) {
+ odb_obj = (git_odb_object *)cached;
+ } else {
+ assert(!"Wrong caching type in the global object cache");
}
-
- *object_out = object;
- return 0;
+ } else {
+ /* Object was not found in the cache, let's explore the backends.
+ * We could just use git_odb_read_unique_short_oid,
+ * it is the same cost for packed and loose object backends,
+ * but it may be much more costly for sqlite and hiredis.
+ */
+ error = git_odb_read(&odb_obj, odb, id);
}
-
- /* Object was not found in the cache, let's explore the backends.
- * We could just use git_odb_read_unique_short_oid,
- * it is the same cost for packed and loose object backends,
- * but it may be much more costly for sqlite and hiredis.
- */
- error = git_odb_read(&odb_obj, odb, id);
} else {
git_oid short_oid;
@@ -211,41 +203,12 @@ int git_object_lookup(git_object **object_out, git_repository *repo, const git_o
return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
}
-void git_object__free(void *_obj)
-{
- git_object *object = (git_object *)_obj;
-
- assert(object);
-
- switch (object->type) {
- case GIT_OBJ_COMMIT:
- git_commit__free((git_commit *)object);
- break;
-
- case GIT_OBJ_TREE:
- git_tree__free((git_tree *)object);
- break;
-
- case GIT_OBJ_TAG:
- git_tag__free((git_tag *)object);
- break;
-
- case GIT_OBJ_BLOB:
- git_blob__free((git_blob *)object);
- break;
-
- default:
- git__free(object);
- break;
- }
-}
-
void git_object_free(git_object *object)
{
if (object == NULL)
return;
- git_cached_obj_decref((git_cached_obj *)object, git_object__free);
+ git_cached_obj_decref(object);
}
const git_oid *git_object_id(const git_object *obj)
@@ -257,7 +220,7 @@ const git_oid *git_object_id(const git_object *obj)
git_otype git_object_type(const git_object *obj)
{
assert(obj);
- return obj->type;
+ return obj->cached.type;
}
git_repository *git_object_owner(const git_object *obj)
@@ -293,7 +256,7 @@ int git_object_typeisloose(git_otype type)
if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
return 0;
- return git_objects_table[type].loose;
+ return (git_objects_table[type].size > 0) ? 1 : 0;
}
size_t git_object__size(git_otype type)
@@ -350,18 +313,17 @@ int git_object_peel(
git_object *source, *deref = NULL;
int error;
- if (target_type != GIT_OBJ_TAG &&
- target_type != GIT_OBJ_COMMIT &&
- target_type != GIT_OBJ_TREE &&
- target_type != GIT_OBJ_BLOB &&
- target_type != GIT_OBJ_ANY)
- return GIT_EINVALIDSPEC;
-
assert(object && peeled);
if (git_object_type(object) == target_type)
return git_object_dup(peeled, (git_object *)object);
+ assert(target_type == GIT_OBJ_TAG ||
+ target_type == GIT_OBJ_COMMIT ||
+ target_type == GIT_OBJ_TREE ||
+ target_type == GIT_OBJ_BLOB ||
+ target_type == GIT_OBJ_ANY);
+
source = (git_object *)object;
while (!(error = dereference_object(&deref, source))) {
diff --git a/src/object.h b/src/object.h
index c1e50593c..d187c55b7 100644
--- a/src/object.h
+++ b/src/object.h
@@ -11,7 +11,6 @@
struct git_object {
git_cached_obj cached;
git_repository *repo;
- git_otype type;
};
/* fully free the object; internal method, DO NOT EXPORT */
diff --git a/src/object_api.c b/src/object_api.c
new file mode 100644
index 000000000..838bba323
--- /dev/null
+++ b/src/object_api.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2/object.h"
+
+#include "common.h"
+#include "repository.h"
+
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+
+/**
+ * Blob
+ */
+int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_COMMIT);
+}
+
+int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_COMMIT);
+}
+
+void git_commit_free(git_commit *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_commit_id(const git_commit *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_commit_owner(const git_commit *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+
+/**
+ * Tree
+ */
+int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TREE);
+}
+
+int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TREE);
+}
+
+void git_tree_free(git_tree *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tree_id(const git_tree *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tree_owner(const git_tree *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+
+/**
+ * Tag
+ */
+int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TAG);
+}
+
+int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TAG);
+}
+
+void git_tag_free(git_tag *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tag_id(const git_tag *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tag_owner(const git_tag *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+/**
+ * Blob
+ */
+int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_BLOB);
+}
+
+int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_BLOB);
+}
+
+void git_blob_free(git_blob *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_blob_id(const git_blob *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_blob_owner(const git_blob *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
diff --git a/src/odb.c b/src/odb.c
index c98df247c..23eb4e12e 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -8,11 +8,13 @@
#include "common.h"
#include <zlib.h>
#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
#include "filter.h"
+#include "repository.h"
#include "git2/odb_backend.h"
#include "git2/oid.h"
@@ -29,10 +31,19 @@ typedef struct
{
git_odb_backend *backend;
int priority;
- int is_alternate;
+ bool is_alternate;
+ ino_t disk_inode;
} backend_internal;
-size_t git_odb__cache_size = GIT_DEFAULT_CACHE_SIZE;
+static git_cache *odb_cache(git_odb *odb)
+{
+ if (odb->rc.owner != NULL) {
+ git_repository *owner = odb->rc.owner;
+ return &owner->objects;
+ }
+
+ return &odb->own_cache;
+}
static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
@@ -54,6 +65,7 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj)
if (!git_object_typeisloose(obj->type))
return -1;
+
if (!obj->data && obj->len != 0)
return -1;
@@ -70,23 +82,24 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj)
}
-static git_odb_object *new_odb_object(const git_oid *oid, git_rawobj *source)
+static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source)
{
- git_odb_object *object = git__malloc(sizeof(git_odb_object));
- memset(object, 0x0, sizeof(git_odb_object));
+ git_odb_object *object = git__calloc(1, sizeof(git_odb_object));
- git_oid_cpy(&object->cached.oid, oid);
- memcpy(&object->raw, source, sizeof(git_rawobj));
+ if (object != NULL) {
+ git_oid_cpy(&object->cached.oid, oid);
+ object->cached.type = source->type;
+ object->cached.size = source->len;
+ object->buffer = source->data;
+ }
return object;
}
-static void free_odb_object(void *o)
+void git_odb_object__free(void *object)
{
- git_odb_object *object = (git_odb_object *)o;
-
if (object != NULL) {
- git__free(object->raw.data);
+ git__free(((git_odb_object *)object)->buffer);
git__free(object);
}
}
@@ -98,17 +111,17 @@ const git_oid *git_odb_object_id(git_odb_object *object)
const void *git_odb_object_data(git_odb_object *object)
{
- return object->raw.data;
+ return object->buffer;
}
size_t git_odb_object_size(git_odb_object *object)
{
- return object->raw.len;
+ return object->cached.size;
}
git_otype git_odb_object_type(git_odb_object *object)
{
- return object->raw.type;
+ return object->cached.type;
}
void git_odb_object_free(git_odb_object *object)
@@ -116,7 +129,7 @@ void git_odb_object_free(git_odb_object *object)
if (object == NULL)
return;
- git_cached_obj_decref((git_cached_obj *)object, &free_odb_object);
+ git_cached_obj_decref(object);
}
int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
@@ -219,6 +232,7 @@ int git_odb__hashlink(git_oid *out, const char *path)
link_data[size] = '\0';
if (read_len != (ssize_t)size) {
giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path);
+ git__free(link_data);
return -1;
}
@@ -353,9 +367,8 @@ 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_odb__cache_size, &free_odb_object) < 0 ||
- git_vector_init(&db->backends, 4, backend_sort_cmp) < 0)
- {
+ if (git_cache_init(&db->own_cache) < 0 ||
+ git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) {
git__free(db);
return -1;
}
@@ -365,7 +378,9 @@ int git_odb_new(git_odb **out)
return 0;
}
-static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int priority, int is_alternate)
+static int add_backend_internal(
+ git_odb *odb, git_odb_backend *backend,
+ int priority, bool is_alternate, ino_t disk_inode)
{
backend_internal *internal;
@@ -382,6 +397,7 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio
internal->backend = backend;
internal->priority = priority;
internal->is_alternate = is_alternate;
+ internal->disk_inode = disk_inode;
if (git_vector_insert(&odb->backends, internal) < 0) {
git__free(internal);
@@ -395,26 +411,86 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio
int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority)
{
- return add_backend_internal(odb, backend, priority, 0);
+ return add_backend_internal(odb, backend, priority, false, 0);
}
int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
{
- return add_backend_internal(odb, backend, priority, 1);
+ return add_backend_internal(odb, backend, priority, true, 0);
+}
+
+size_t git_odb_num_backends(git_odb *odb)
+{
+ assert(odb);
+ return odb->backends.length;
+}
+
+static int git_odb__error_unsupported_in_backend(const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - unsupported in the loaded odb backends", action);
+ return -1;
+}
+
+
+int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos)
+{
+ backend_internal *internal;
+
+ assert(odb && odb);
+ internal = git_vector_get(&odb->backends, pos);
+
+ if (internal && internal->backend) {
+ *out = internal->backend;
+ return 0;
+ }
+
+ giterr_set(GITERR_ODB, "No ODB backend loaded at index " PRIuZ, pos);
+ return GIT_ENOTFOUND;
}
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth)
+static int add_default_backends(
+ git_odb *db, const char *objects_dir,
+ bool as_alternates, int alternate_depth)
{
+ size_t i;
+ struct stat st;
+ ino_t inode;
git_odb_backend *loose, *packed;
+ /* TODO: inodes are not really relevant on Win32, so we need to find
+ * a cross-platform workaround for this */
+#ifdef GIT_WIN32
+ GIT_UNUSED(i);
+ GIT_UNUSED(st);
+
+ inode = 0;
+#else
+ if (p_stat(objects_dir, &st) < 0) {
+ if (as_alternates)
+ return 0;
+
+ giterr_set(GITERR_ODB, "Failed to load object database in '%s'", objects_dir);
+ return -1;
+ }
+
+ inode = st.st_ino;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *backend = git_vector_get(&db->backends, i);
+ if (backend->disk_inode == inode)
+ return 0;
+ }
+#endif
+
/* add the loose object backend */
if (git_odb_backend_loose(&loose, objects_dir, -1, 0) < 0 ||
- add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates) < 0)
+ add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates, inode) < 0)
return -1;
/* add the packed file backend */
if (git_odb_backend_pack(&packed, objects_dir) < 0 ||
- add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0)
+ add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates, inode) < 0)
return -1;
return load_alternates(db, objects_dir, alternate_depth);
@@ -429,9 +505,8 @@ static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_
int result = 0;
/* Git reports an error, we just ignore anything deeper */
- if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) {
+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH)
return 0;
- }
if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
return -1;
@@ -464,7 +539,7 @@ static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_
alternate = git_buf_cstr(&alternates_path);
}
- if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0)
+ if ((result = add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0)
break;
}
@@ -476,7 +551,7 @@ static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_
int git_odb_add_disk_alternate(git_odb *odb, const char *path)
{
- return add_default_backends(odb, path, 1, 0);
+ return add_default_backends(odb, path, true, 0);
}
int git_odb_open(git_odb **out, const char *objects_dir)
@@ -514,7 +589,9 @@ static void odb_free(git_odb *db)
}
git_vector_free(&db->backends);
- git_cache_free(&db->cache);
+ git_cache_free(&db->own_cache);
+
+ git__memzero(db, sizeof(*db));
git__free(db);
}
@@ -535,7 +612,7 @@ int git_odb_exists(git_odb *db, const git_oid *id)
assert(db && id);
- if ((object = git_cache_get(&db->cache, id)) != NULL) {
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
git_odb_object_free(object);
return (int)true;
}
@@ -585,9 +662,9 @@ int git_odb__read_header_or_object(
assert(db && id && out && len_p && type_p);
- if ((object = git_cache_get(&db->cache, id)) != NULL) {
- *len_p = object->raw.len;
- *type_p = object->raw.type;
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
*out = object;
return 0;
}
@@ -612,8 +689,8 @@ int git_odb__read_header_or_object(
if ((error = git_odb_read(&object, db, id)) < 0)
return error; /* error already set - pass along */
- *len_p = object->raw.len;
- *type_p = object->raw.type;
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
*out = object;
return 0;
@@ -621,19 +698,15 @@ int git_odb__read_header_or_object(
int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
{
- size_t i;
+ size_t i, reads = 0;
int error;
bool refreshed = false;
git_rawobj raw;
+ git_odb_object *object;
assert(out && db && id);
- if (db->backends.length == 0) {
- giterr_set(GITERR_ODB, "Failed to lookup object: no backends loaded");
- return GIT_ENOTFOUND;
- }
-
- *out = git_cache_get(&db->cache, id);
+ *out = git_cache_get_raw(odb_cache(db), id);
if (*out != NULL)
return 0;
@@ -644,8 +717,10 @@ attempt_lookup:
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->read != NULL)
+ if (b->read != NULL) {
+ ++reads;
error = b->read(&raw.data, &raw.len, &raw.type, b, id);
+ }
}
if (error == GIT_ENOTFOUND && !refreshed) {
@@ -656,10 +731,16 @@ attempt_lookup:
goto attempt_lookup;
}
- if (error && error != GIT_PASSTHROUGH)
+ if (error && error != GIT_PASSTHROUGH) {
+ if (!reads)
+ return git_odb__error_notfound("no match for id", id);
return error;
+ }
+
+ if ((object = odb_object__alloc(id, &raw)) == NULL)
+ return -1;
- *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw));
+ *out = git_cache_store_raw(odb_cache(db), object);
return 0;
}
@@ -672,6 +753,7 @@ int git_odb_read_prefix(
git_rawobj raw;
void *data = NULL;
bool found = false, refreshed = false;
+ git_odb_object *object;
assert(out && db);
@@ -682,7 +764,7 @@ int git_odb_read_prefix(
len = GIT_OID_HEXSZ;
if (len == GIT_OID_HEXSZ) {
- *out = git_cache_get(&db->cache, short_id);
+ *out = git_cache_get_raw(odb_cache(db), short_id);
if (*out != NULL)
return 0;
}
@@ -704,7 +786,7 @@ attempt_lookup:
git__free(data);
data = raw.data;
- if (found && git_oid_cmp(&full_oid, &found_full_oid))
+ if (found && git_oid__cmp(&full_oid, &found_full_oid))
return git_odb__error_ambiguous("multiple matches for prefix");
found_full_oid = full_oid;
@@ -723,7 +805,10 @@ attempt_lookup:
if (!found)
return git_odb__error_notfound("no match for prefix", short_id);
- *out = git_cache_try_store(&db->cache, new_odb_object(&found_full_oid, &raw));
+ if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL)
+ return -1;
+
+ *out = git_cache_store_raw(odb_cache(db), object);
return 0;
}
@@ -770,10 +855,10 @@ int git_odb_write(
if (!error || error == GIT_PASSTHROUGH)
return 0;
- /* if no backends were able to write the object directly, we try a streaming
- * write to the backends; just write the whole object into the stream in one
- * push */
-
+ /* if no backends were able to write the object directly, we try a
+ * streaming write to the backends; just write the whole object into the
+ * stream in one push
+ */
if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0)
return error;
@@ -787,7 +872,7 @@ int git_odb_write(
int git_odb_open_wstream(
git_odb_stream **stream, git_odb *db, size_t size, git_otype type)
{
- size_t i;
+ size_t i, writes = 0;
int error = GIT_ERROR;
assert(stream && db);
@@ -800,21 +885,26 @@ int git_odb_open_wstream(
if (internal->is_alternate)
continue;
- if (b->writestream != NULL)
+ if (b->writestream != NULL) {
+ ++writes;
error = b->writestream(stream, b, size, type);
- else if (b->write != NULL)
+ } else if (b->write != NULL) {
+ ++writes;
error = init_fake_wstream(stream, b, size, type);
+ }
}
if (error == GIT_PASSTHROUGH)
error = 0;
+ if (error < 0 && !writes)
+ error = git_odb__error_unsupported_in_backend("write object");
return error;
}
int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid)
{
- size_t i;
+ size_t i, reads = 0;
int error = GIT_ERROR;
assert(stream && db);
@@ -823,19 +913,23 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->readstream != NULL)
+ if (b->readstream != NULL) {
+ ++reads;
error = b->readstream(stream, b, oid);
+ }
}
if (error == GIT_PASSTHROUGH)
error = 0;
+ if (error < 0 && !reads)
+ error = git_odb__error_unsupported_in_backend("read object streamed");
return error;
}
int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload)
{
- size_t i;
+ size_t i, writes = 0;
int error = GIT_ERROR;
assert(out && db);
@@ -848,12 +942,16 @@ int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer
if (internal->is_alternate)
continue;
- if (b->writepack != NULL)
+ if (b->writepack != NULL) {
+ ++writes;
error = b->writepack(out, b, progress_cb, progress_payload);
+ }
}
if (error == GIT_PASSTHROUGH)
error = 0;
+ if (error < 0 && !writes)
+ error = git_odb__error_unsupported_in_backend("write pack");
return error;
}
diff --git a/src/odb.h b/src/odb.h
index 7c018cc50..0d9f9e2ea 100644
--- a/src/odb.h
+++ b/src/odb.h
@@ -29,14 +29,14 @@ typedef struct {
/* EXPORT */
struct git_odb_object {
git_cached_obj cached;
- git_rawobj raw;
+ void *buffer;
};
/* EXPORT */
struct git_odb {
git_refcount rc;
git_vector backends;
- git_cache cache;
+ git_cache own_cache;
};
/*
@@ -96,4 +96,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);
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_odb_object__free(void *object);
+
#endif
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 68083f7fd..76ed8e232 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -8,7 +8,7 @@
#include "common.h"
#include <zlib.h>
#include "git2/object.h"
-#include "git2/oid.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
@@ -33,7 +33,9 @@ typedef struct loose_backend {
int object_zlib_level; /** loose object zlib compression level. */
int fsync_object_files; /** loose object file fsync flag. */
- char *objects_dir;
+
+ size_t objects_dirlen;
+ char objects_dir[GIT_FLEX_ARRAY];
} loose_backend;
/* State structure for exploring directories,
@@ -56,24 +58,30 @@ typedef struct {
*
***********************************************************/
-static int object_file_name(git_buf *name, const char *dir, const git_oid *id)
+static int object_file_name(
+ git_buf *name, const loose_backend *be, const git_oid *id)
{
- git_buf_sets(name, dir);
-
- /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */
- if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0)
+ /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */
+ if (git_buf_grow(name, be->objects_dirlen + GIT_OID_HEXSZ + 3) < 0)
return -1;
+ git_buf_set(name, be->objects_dir, be->objects_dirlen);
git_path_to_dir(name);
/* loose object filename: aa/aaa... (41 bytes) */
- git_oid_pathfmt(name->ptr + git_buf_len(name), id);
+ git_oid_pathfmt(name->ptr + name->size, id);
name->size += GIT_OID_HEXSZ + 1;
name->ptr[name->size] = '\0';
return 0;
}
+static int object_mkdir(const git_buf *name, const loose_backend *be)
+{
+ return git_futils_mkdir(
+ name->ptr + be->objects_dirlen, be->objects_dir, GIT_OBJECT_DIR_MODE,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
+}
static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
{
@@ -457,7 +465,7 @@ static int locate_object(
loose_backend *backend,
const git_oid *oid)
{
- int error = object_file_name(object_location, backend->objects_dir, oid);
+ int error = object_file_name(object_location, backend, oid);
if (!error && !git_path_exists(object_location->ptr))
return GIT_ENOTFOUND;
@@ -769,8 +777,8 @@ static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream)
int error = 0;
if (git_filebuf_hash(oid, &stream->fbuf) < 0 ||
- object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
- git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0)
+ object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0)
error = -1;
/*
* Don't try to add an existing object to the repository. This
@@ -880,8 +888,8 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v
git_filebuf_write(&fbuf, header, header_len);
git_filebuf_write(&fbuf, data, len);
- if (object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
- git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 ||
+ if (object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0 ||
git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0)
error = -1;
@@ -898,7 +906,6 @@ static void loose_backend__free(git_odb_backend *_backend)
assert(_backend);
backend = (loose_backend *)_backend;
- git__free(backend->objects_dir);
git__free(backend);
}
@@ -909,13 +916,20 @@ int git_odb_backend_loose(
int do_fsync)
{
loose_backend *backend;
+ size_t objects_dirlen;
+
+ assert(backend_out && objects_dir);
+
+ objects_dirlen = strlen(objects_dir);
- backend = git__calloc(1, sizeof(loose_backend));
+ backend = git__calloc(1, sizeof(loose_backend) + objects_dirlen + 2);
GITERR_CHECK_ALLOC(backend);
backend->parent.version = GIT_ODB_BACKEND_VERSION;
- backend->objects_dir = git__strdup(objects_dir);
- GITERR_CHECK_ALLOC(backend->objects_dir);
+ backend->objects_dirlen = objects_dirlen;
+ memcpy(backend->objects_dir, objects_dir, objects_dirlen);
+ if (backend->objects_dir[backend->objects_dirlen - 1] != '/')
+ backend->objects_dir[backend->objects_dirlen++] = '/';
if (compression_level < 0)
compression_level = Z_BEST_SPEED;
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 7240a4ac7..43880612a 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -8,7 +8,8 @@
#include "common.h"
#include <zlib.h>
#include "git2/repository.h"
-#include "git2/oid.h"
+#include "git2/indexer.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
@@ -206,7 +207,7 @@ static int packfile_load__cb(void *_data, git_buf *path)
return 0;
}
- error = git_packfile_check(&pack, path->ptr);
+ error = git_packfile_alloc(&pack, path->ptr);
if (error == GIT_ENOTFOUND)
/* ignore missing .pack file as git does */
return 0;
@@ -258,23 +259,26 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen
return git_odb__error_notfound("failed to find pack entry", oid);
}
-static unsigned pack_entry_find_prefix_inner(
- struct git_pack_entry *e,
- struct pack_backend *backend,
- const git_oid *short_oid,
- size_t len,
- struct git_pack_file *last_found)
+static int pack_entry_find_prefix(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
{
int error;
size_t i;
- unsigned found = 0;
+ git_oid found_full_oid = {{0}};
+ bool found = false;
+ struct git_pack_file *last_found = backend->last_found;
if (last_found) {
error = git_pack_entry_find(e, last_found, short_oid, len);
if (error == GIT_EAMBIGUOUS)
return error;
- if (!error)
- found = 1;
+ if (!error) {
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
+ }
}
for (i = 0; i < backend->packs.length; ++i) {
@@ -288,28 +292,16 @@ static unsigned pack_entry_find_prefix_inner(
if (error == GIT_EAMBIGUOUS)
return error;
if (!error) {
- if (++found > 1)
- break;
+ if (found && git_oid_cmp(&e->sha1, &found_full_oid))
+ return git_odb__error_ambiguous("found multiple pack entries");
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
backend->last_found = p;
}
}
- return found;
-}
-
-static int pack_entry_find_prefix(
- struct git_pack_entry *e,
- struct pack_backend *backend,
- const git_oid *short_oid,
- size_t len)
-{
- struct git_pack_file *last_found = backend->last_found;
- unsigned int found = pack_entry_find_prefix_inner(e, backend, short_oid, len, last_found);
-
if (!found)
return git_odb__error_notfound("no matching pack entry for prefix", short_oid);
- else if (found > 1)
- return git_odb__error_ambiguous("found multiple pack entries");
else
return 0;
}
@@ -526,80 +518,75 @@ static void pack_backend__free(git_odb_backend *_backend)
git__free(backend);
}
-int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
+static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
{
- struct pack_backend *backend = NULL;
- struct git_pack_file *packfile = NULL;
+ struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend));
+ GITERR_CHECK_ALLOC(backend);
- if (git_packfile_check(&packfile, idx) < 0)
+ if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) {
+ git__free(backend);
return -1;
+ }
- backend = git__calloc(1, sizeof(struct pack_backend));
- GITERR_CHECK_ALLOC(backend);
backend->parent.version = GIT_ODB_BACKEND_VERSION;
- if (git_vector_init(&backend->packs, 1, NULL) < 0)
- goto on_error;
-
- if (git_vector_insert(&backend->packs, packfile) < 0)
- goto on_error;
-
backend->parent.read = &pack_backend__read;
backend->parent.read_prefix = &pack_backend__read_prefix;
backend->parent.read_header = &pack_backend__read_header;
backend->parent.exists = &pack_backend__exists;
backend->parent.refresh = &pack_backend__refresh;
backend->parent.foreach = &pack_backend__foreach;
+ backend->parent.writepack = &pack_backend__writepack;
backend->parent.free = &pack_backend__free;
- *backend_out = (git_odb_backend *)backend;
-
+ *out = backend;
return 0;
-
-on_error:
- git_vector_free(&backend->packs);
- git__free(backend);
- git__free(packfile);
- return -1;
}
-int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
+int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
{
struct pack_backend *backend = NULL;
- git_buf path = GIT_BUF_INIT;
+ struct git_pack_file *packfile = NULL;
- backend = git__calloc(1, sizeof(struct pack_backend));
- GITERR_CHECK_ALLOC(backend);
- backend->parent.version = GIT_ODB_BACKEND_VERSION;
+ if (pack_backend__alloc(&backend, 1) < 0)
+ return -1;
- if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < 0 ||
- git_buf_joinpath(&path, objects_dir, "pack") < 0)
+ if (git_packfile_alloc(&packfile, idx) < 0 ||
+ git_vector_insert(&backend->packs, packfile) < 0)
{
- git__free(backend);
+ pack_backend__free((git_odb_backend *)backend);
return -1;
}
- if (git_path_isdir(git_buf_cstr(&path)) == true) {
- int error;
+ *backend_out = (git_odb_backend *)backend;
+ return 0;
+}
+
+int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
+{
+ int error = 0;
+ struct pack_backend *backend = NULL;
+ git_buf path = GIT_BUF_INIT;
+
+ if (pack_backend__alloc(&backend, 8) < 0)
+ return -1;
+ if (!(error = git_buf_joinpath(&path, objects_dir, "pack")) &&
+ git_path_isdir(git_buf_cstr(&path)))
+ {
backend->pack_folder = git_buf_detach(&path);
+
error = pack_backend__refresh((git_odb_backend *)backend);
- if (error < 0)
- return error;
}
- backend->parent.read = &pack_backend__read;
- backend->parent.read_prefix = &pack_backend__read_prefix;
- backend->parent.read_header = &pack_backend__read_header;
- backend->parent.exists = &pack_backend__exists;
- backend->parent.refresh = &pack_backend__refresh;
- backend->parent.foreach = &pack_backend__foreach;
- backend->parent.writepack = &pack_backend__writepack;
- backend->parent.free = &pack_backend__free;
+ if (error < 0) {
+ pack_backend__free((git_odb_backend *)backend);
+ backend = NULL;
+ }
*backend_out = (git_odb_backend *)backend;
git_buf_free(&path);
- return 0;
+ return error;
}
diff --git a/src/oid.c b/src/oid.c
index ab69eeb17..8300e46c1 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -68,12 +68,31 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
return str;
}
-void git_oid_fmt(char *str, const git_oid *oid)
+void git_oid_nfmt(char *str, size_t n, const git_oid *oid)
{
- size_t i;
+ size_t i, max_i;
+
+ if (!oid) {
+ memset(str, 0, n);
+ return;
+ }
+ if (n > GIT_OID_HEXSZ) {
+ memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
+ n = GIT_OID_HEXSZ;
+ }
+
+ max_i = n / 2;
- for (i = 0; i < sizeof(oid->id); i++)
+ for (i = 0; i < max_i; i++)
str = fmt_one(str, oid->id[i]);
+
+ if (n & 1)
+ *str++ = to_hex[oid->id[i] >> 4];
+}
+
+void git_oid_fmt(char *str, const git_oid *oid)
+{
+ git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
}
void git_oid_pathfmt(char *str, const git_oid *oid)
@@ -91,31 +110,20 @@ char *git_oid_allocfmt(const git_oid *oid)
char *str = git__malloc(GIT_OID_HEXSZ + 1);
if (!str)
return NULL;
- git_oid_fmt(str, oid);
- str[GIT_OID_HEXSZ] = '\0';
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
return str;
}
char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
{
- char str[GIT_OID_HEXSZ];
-
if (!out || n == 0)
return "";
- n--; /* allow room for terminating NUL */
-
- if (oid == NULL)
- n = 0;
-
- if (n > 0) {
- git_oid_fmt(str, oid);
- if (n > GIT_OID_HEXSZ)
- n = GIT_OID_HEXSZ;
- memcpy(out, str, n);
- }
+ if (n > GIT_OID_HEXSZ + 1)
+ n = GIT_OID_HEXSZ + 1;
- out[n] = '\0';
+ git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
+ out[n - 1] = '\0';
return out;
}
@@ -166,18 +174,26 @@ void git_oid_cpy(git_oid *out, const git_oid *src)
memcpy(out->id, src->id, sizeof(out->id));
}
+int git_oid_cmp(const git_oid *a, const git_oid *b)
+{
+ return git_oid__cmp(a, b);
+}
+
int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len)
{
const unsigned char *a = oid_a->id;
const unsigned char *b = oid_b->id;
- do {
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ while (len > 1) {
if (*a != *b)
return 1;
a++;
b++;
len -= 2;
- } while (len > 1);
+ };
if (len)
if ((*a ^ *b) & 0xf0)
@@ -186,14 +202,31 @@ int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len)
return 0;
}
-int git_oid_streq(const git_oid *a, const char *str)
+int git_oid_strcmp(const git_oid *oid_a, const char *str)
{
- git_oid id;
+ const unsigned char *a = oid_a->id;
+ unsigned char strval;
+ int hexval;
- if (git_oid_fromstr(&id, str) < 0)
- return -1;
+ for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval = hexval << 4;
+ if (*str) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval |= hexval;
+ }
+ if (*a != strval)
+ return (*a - strval);
+ }
- return git_oid_cmp(a, &id) == 0 ? 0 : -1;
+ return 0;
+}
+
+int git_oid_streq(const git_oid *oid_a, const char *str)
+{
+ return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1;
}
int git_oid_iszero(const git_oid *oid_a)
@@ -244,8 +277,10 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co
idx_leaf = (node_index)os->node_count++;
- if (os->node_count == SHRT_MAX)
+ if (os->node_count == SHRT_MAX) {
os->full = 1;
+ return NULL;
+ }
node = &os->nodes[idx];
node->children[push_at] = -idx_leaf;
diff --git a/src/oid.h b/src/oid.h
new file mode 100644
index 000000000..077d0a4c8
--- /dev/null
+++ b/src/oid.h
@@ -0,0 +1,33 @@
+/*
+ * 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_oid_h__
+#define INCLUDE_oid_h__
+
+#include "git2/oid.h"
+
+/*
+ * Compare two oid structures.
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return <0, 0, >0 if a < b, a == b, a > b.
+ */
+GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b)
+{
+ const unsigned char *sha1 = a->id;
+ const unsigned char *sha2 = b->id;
+ int i;
+
+ for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) {
+ if (*sha1 != *sha2)
+ return *sha1 - *sha2;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/src/oidmap.h b/src/oidmap.h
index 40274cd19..a29c7cd35 100644
--- a/src/oidmap.h
+++ b/src/oidmap.h
@@ -19,17 +19,15 @@
__KHASH_TYPE(oid, const git_oid *, void *);
typedef khash_t(oid) git_oidmap;
-GIT_INLINE(khint_t) hash_git_oid(const git_oid *oid)
+GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid)
{
- int i;
- khint_t h = 0;
- for (i = 0; i < 20; ++i)
- h = (h << 5) - h + oid->id[i];
+ khint_t h;
+ memcpy(&h, oid, sizeof(khint_t));
return h;
}
#define GIT__USE_OIDMAP \
- __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, hash_git_oid, git_oid_equal)
+ __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal)
#define git_oidmap_alloc() kh_init(oid)
#define git_oidmap_free(h) kh_destroy(oid,h), h = NULL
diff --git a/src/pack-objects.c b/src/pack-objects.c
index 459201f58..7f427e3bd 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -33,6 +33,11 @@ struct tree_walk_context {
git_buf buf;
};
+struct pack_write_context {
+ git_indexer_stream *indexer;
+ git_transfer_progress *stats;
+};
+
#ifdef GIT_THREADS
#define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) do { \
@@ -127,7 +132,10 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
if (git_mutex_init(&pb->cache_mutex) ||
git_mutex_init(&pb->progress_mutex) ||
git_cond_init(&pb->progress_cond))
+ {
+ giterr_set(GITERR_OS, "Failed to initialize packbuilder mutex");
goto on_error;
+ }
#endif
@@ -497,8 +505,10 @@ static git_pobject **compute_write_order(git_packbuilder *pb)
/*
* Mark objects that are at the tip of tags.
*/
- if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0)
+ if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) {
+ git__free(wo);
return NULL;
+ }
/*
* Give the objects in the original recency order until
@@ -620,26 +630,6 @@ static int write_pack_buf(void *buf, size_t size, void *data)
return git_buf_put(b, buf, size);
}
-static int write_pack_to_file(void *buf, size_t size, void *data)
-{
- git_filebuf *file = (git_filebuf *)data;
- return git_filebuf_write(file, buf, size);
-}
-
-static int write_pack_file(git_packbuilder *pb, const char *path)
-{
- git_filebuf file = GIT_FILEBUF_INIT;
-
- if (git_filebuf_open(&file, path, 0) < 0 ||
- write_pack(pb, &write_pack_to_file, &file) < 0 ||
- git_filebuf_commit(&file, GIT_PACK_FILE_MODE) < 0) {
- git_filebuf_cleanup(&file);
- return -1;
- }
-
- return 0;
-}
-
static int type_size_sort(const void *_a, const void *_b)
{
const git_pobject *a = (git_pobject *)_a;
@@ -1259,10 +1249,39 @@ int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
return write_pack(pb, &write_pack_buf, buf);
}
-int git_packbuilder_write(git_packbuilder *pb, const char *path)
+static int write_cb(void *buf, size_t len, void *payload)
+{
+ struct pack_write_context *ctx = payload;
+ return git_indexer_stream_add(ctx->indexer, buf, len, ctx->stats);
+}
+
+int git_packbuilder_write(
+ git_packbuilder *pb,
+ const char *path,
+ git_transfer_progress_callback progress_cb,
+ void *progress_cb_payload)
{
+ git_indexer_stream *indexer;
+ git_transfer_progress stats;
+ struct pack_write_context ctx;
+
PREPARE_PACK;
- return write_pack_file(pb, path);
+
+ if (git_indexer_stream_new(
+ &indexer, path, progress_cb, progress_cb_payload) < 0)
+ return -1;
+
+ ctx.indexer = indexer;
+ ctx.stats = &stats;
+
+ if (git_packbuilder_foreach(pb, write_cb, &ctx) < 0 ||
+ git_indexer_stream_finalize(indexer, &stats) < 0) {
+ git_indexer_stream_free(indexer);
+ return -1;
+ }
+
+ git_indexer_stream_free(indexer);
+ return 0;
}
#undef PREPARE_PACK
@@ -1284,6 +1303,21 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay
git_buf_cstr(&ctx->buf));
}
+int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid)
+{
+ git_commit *commit;
+
+ if (git_commit_lookup(&commit, pb->repo, oid) < 0 ||
+ git_packbuilder_insert(pb, oid, NULL) < 0)
+ return -1;
+
+ if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0)
+ return -1;
+
+ git_commit_free(commit);
+ return 0;
+}
+
int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
{
git_tree *tree;
diff --git a/src/pack.c b/src/pack.c
index 75ac98186..d7e6a1e94 100644
--- a/src/pack.c
+++ b/src/pack.c
@@ -12,8 +12,8 @@
#include "sha1_lookup.h"
#include "mwindow.h"
#include "fileops.h"
+#include "oid.h"
-#include "git2/oid.h"
#include <zlib.h>
static int packfile_open(struct git_pack_file *p);
@@ -85,15 +85,27 @@ static void cache_free(git_pack_cache *cache)
git_offmap_free(cache->entries);
git_mutex_free(&cache->lock);
}
+
+ memset(cache, 0, sizeof(*cache));
}
static int cache_init(git_pack_cache *cache)
{
- memset(cache, 0, sizeof(git_pack_cache));
+ memset(cache, 0, sizeof(*cache));
+
cache->entries = git_offmap_alloc();
GITERR_CHECK_ALLOC(cache->entries);
+
cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT;
- git_mutex_init(&cache->lock);
+
+ if (git_mutex_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize pack cache mutex");
+
+ git__free(cache->entries);
+ cache->entries = NULL;
+
+ return -1;
+ }
return 0;
}
@@ -205,13 +217,18 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
if (fd < 0)
return fd;
- if (p_fstat(fd, &st) < 0 ||
- !S_ISREG(st.st_mode) ||
+ if (p_fstat(fd, &st) < 0) {
+ p_close(fd);
+ giterr_set(GITERR_OS, "Unable to stat pack index '%s'", path);
+ return -1;
+ }
+
+ if (!S_ISREG(st.st_mode) ||
!git__is_sizet(st.st_size) ||
(idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20)
{
p_close(fd);
- giterr_set(GITERR_OS, "Failed to check pack index.");
+ giterr_set(GITERR_ODB, "Invalid pack index '%s'", path);
return -1;
}
@@ -288,32 +305,42 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
}
}
- p->index_version = version;
p->num_objects = nr;
+ p->index_version = version;
return 0;
}
static int pack_index_open(struct git_pack_file *p)
{
char *idx_name;
- int error;
- size_t name_len, offset;
+ int error = 0;
+ size_t name_len, base_len;
- if (p->index_map.data)
+ if (p->index_version > -1)
return 0;
- idx_name = git__strdup(p->pack_name);
- GITERR_CHECK_ALLOC(idx_name);
+ name_len = strlen(p->pack_name);
+ assert(name_len > strlen(".pack")); /* checked by git_pack_file alloc */
- name_len = strlen(idx_name);
- offset = name_len - strlen(".pack");
- assert(offset < name_len); /* make sure no underflow */
+ if ((idx_name = git__malloc(name_len)) == NULL)
+ return -1;
+
+ base_len = name_len - strlen(".pack");
+ memcpy(idx_name, p->pack_name, base_len);
+ memcpy(idx_name + base_len, ".idx", sizeof(".idx"));
+
+ if ((error = git_mutex_lock(&p->lock)) < 0) {
+ git__free(idx_name);
+ return error;
+ }
- strncpy(idx_name + offset, ".idx", name_len - offset);
+ if (p->index_version == -1)
+ error = pack_index_check(idx_name, p);
- error = pack_index_check(idx_name, p);
git__free(idx_name);
+ git_mutex_unlock(&p->lock);
+
return error;
}
@@ -389,12 +416,12 @@ int git_packfile_unpack_header(
* the maximum deflated object size is 2^137, which is just
* insane, so we know won't exceed what we have been given.
*/
-// base = pack_window_open(p, w_curs, *curpos, &left);
+/* base = pack_window_open(p, w_curs, *curpos, &left); */
base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left);
if (base == NULL)
return GIT_EBUFS;
- ret = packfile_unpack_header1(&used, size_p, type_p, base, left);
+ ret = packfile_unpack_header1(&used, size_p, type_p, base, left);
git_mwindow_close(w_curs);
if (ret == GIT_EBUFS)
return ret;
@@ -786,30 +813,23 @@ git_off_t get_delta_base(
*
***********************************************************/
-static struct git_pack_file *packfile_alloc(size_t extra)
-{
- struct git_pack_file *p = git__calloc(1, sizeof(*p) + extra);
- if (p != NULL)
- p->mwf.fd = -1;
- return p;
-}
-
-
void git_packfile_free(struct git_pack_file *p)
{
- assert(p);
+ if (!p)
+ return;
cache_free(&p->bases);
git_mwindow_free_all(&p->mwf);
- git_mwindow_file_deregister(&p->mwf);
- if (p->mwf.fd != -1)
+ if (p->mwf.fd >= 0)
p_close(p->mwf.fd);
pack_index_free(p);
git__free(p->bad_object_sha1);
+
+ git_mutex_free(&p->lock);
git__free(p);
}
@@ -820,17 +840,22 @@ static int packfile_open(struct git_pack_file *p)
git_oid sha1;
unsigned char *idx_sha1;
- assert(p->index_map.data);
-
- if (!p->index_map.data && pack_index_open(p) < 0)
+ if (p->index_version == -1 && pack_index_open(p) < 0)
return git_odb__error_notfound("failed to open packfile", NULL);
+ /* if mwf opened by another thread, return now */
+ if (git_mutex_lock(&p->lock) < 0)
+ return packfile_error("failed to get lock for open");
+
+ if (p->mwf.fd >= 0) {
+ git_mutex_unlock(&p->lock);
+ return 0;
+ }
+
/* TODO: open with noatime */
p->mwf.fd = git_futils_open_ro(p->pack_name);
- if (p->mwf.fd < 0) {
- p->mwf.fd = -1;
- return -1;
- }
+ if (p->mwf.fd < 0)
+ goto cleanup;
if (p_fstat(p->mwf.fd, &st) < 0 ||
git_mwindow_file_register(&p->mwf) < 0)
@@ -871,44 +896,55 @@ static int packfile_open(struct git_pack_file *p)
idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40;
- if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) == 0)
- return 0;
+ if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0)
+ goto cleanup;
+
+ git_mutex_unlock(&p->lock);
+ return 0;
cleanup:
giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name);
- p_close(p->mwf.fd);
+
+ if (p->mwf.fd >= 0)
+ p_close(p->mwf.fd);
p->mwf.fd = -1;
+
+ git_mutex_unlock(&p->lock);
+
return -1;
}
-int git_packfile_check(struct git_pack_file **pack_out, const char *path)
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path)
{
struct stat st;
struct git_pack_file *p;
- size_t path_len;
+ size_t path_len = path ? strlen(path) : 0;
*pack_out = NULL;
- path_len = strlen(path);
- p = packfile_alloc(path_len + 2);
+
+ if (path_len < strlen(".idx"))
+ return git_odb__error_notfound("invalid packfile path", NULL);
+
+ p = git__calloc(1, sizeof(*p) + path_len + 2);
GITERR_CHECK_ALLOC(p);
+ memcpy(p->pack_name, path, path_len + 1);
+
/*
* Make sure a corresponding .pack file exists and that
* the index looks sane.
*/
- path_len -= strlen(".idx");
- if (path_len < 1) {
- git__free(p);
- return git_odb__error_notfound("invalid packfile path", NULL);
- }
+ if (git__suffixcmp(path, ".idx") == 0) {
+ size_t root_len = path_len - strlen(".idx");
- memcpy(p->pack_name, path, path_len);
+ memcpy(p->pack_name + root_len, ".keep", sizeof(".keep"));
+ if (git_path_exists(p->pack_name) == true)
+ p->pack_keep = 1;
- strcpy(p->pack_name + path_len, ".keep");
- if (git_path_exists(p->pack_name) == true)
- p->pack_keep = 1;
+ memcpy(p->pack_name + root_len, ".pack", sizeof(".pack"));
+ path_len = path_len - strlen(".idx") + strlen(".pack");
+ }
- strcpy(p->pack_name + path_len, ".pack");
if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) {
git__free(p);
return git_odb__error_notfound("packfile not found", NULL);
@@ -917,9 +953,17 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path)
/* ok, it looks sane as far as we can check without
* actually mapping the pack file.
*/
+ p->mwf.fd = -1;
p->mwf.size = st.st_size;
p->pack_local = 1;
p->mtime = (git_time_t)st.st_mtime;
+ p->index_version = -1;
+
+ if (git_mutex_init(&p->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize packfile mutex");
+ git__free(p);
+ return -1;
+ }
/* see if we can parse the sha1 oid in the packfile name */
if (path_len < 40 ||
@@ -1034,12 +1078,11 @@ static int pack_entry_find_offset(
*offset_out = 0;
- if (index == NULL) {
+ if (p->index_version == -1) {
int error;
if ((error = pack_index_open(p)) < 0)
return error;
-
assert(p->index_map.data);
index = p->index_map.data;
@@ -1099,6 +1142,7 @@ static int pack_entry_find_offset(
return git_odb__error_notfound("failed to find offset for pack entry", short_oid);
if (found > 1)
return git_odb__error_ambiguous("found multiple offsets for pack entry");
+
*offset_out = nth_packed_object_offset(p, pos);
git_oid_fromraw(found_oid, current);
@@ -1110,6 +1154,7 @@ static int pack_entry_find_offset(
printf("found lo=%d %s\n", lo, hex_sha1);
}
#endif
+
return 0;
}
@@ -1128,7 +1173,7 @@ int git_pack_entry_find(
if (len == GIT_OID_HEXSZ && p->num_bad_objects) {
unsigned i;
for (i = 0; i < p->num_bad_objects; i++)
- if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0)
+ if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0)
return packfile_error("bad object found in packfile");
}
diff --git a/src/pack.h b/src/pack.h
index 8d7e33dfe..aeeac9ce1 100644
--- a/src/pack.h
+++ b/src/pack.h
@@ -79,6 +79,7 @@ typedef struct {
struct git_pack_file {
git_mwindow_file mwf;
git_map index_map;
+ git_mutex lock; /* protect updates to mwf and index_map */
uint32_t num_objects;
uint32_t num_bad_objects;
@@ -142,7 +143,8 @@ git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs,
git_off_t delta_obj_offset);
void git_packfile_free(struct git_pack_file *p);
-int git_packfile_check(struct git_pack_file **pack_out, const char *path);
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path);
+
int git_pack_entry_find(
struct git_pack_entry *e,
struct git_pack_file *p,
diff --git a/src/path.h b/src/path.h
index ead4fa338..b2899e97f 100644
--- a/src/path.h
+++ b/src/path.h
@@ -175,7 +175,6 @@ extern bool git_path_contains(git_buf *dir, const char *item);
*
* @param parent Directory path that might contain subdir
* @param subdir Subdirectory name to look for in parent
- * @param append_if_exists If true, then subdir will be appended to the parent path if it does exist
* @return true if subdirectory exists, false otherwise.
*/
extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
@@ -185,7 +184,6 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
*
* @param dir Directory path that might contain file
* @param file File name to look for in parent
- * @param append_if_exists If true, then file will be appended to the path if it does exist
* @return true if file exists, false otherwise.
*/
extern bool git_path_contains_file(git_buf *dir, const char *file);
diff --git a/src/pathspec.c b/src/pathspec.c
index 9dee55ea1..4266bb99e 100644
--- a/src/pathspec.c
+++ b/src/pathspec.c
@@ -5,9 +5,16 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
+#include "git2/pathspec.h"
+#include "git2/diff.h"
#include "pathspec.h"
#include "buf_text.h"
#include "attr_file.h"
+#include "iterator.h"
+#include "repository.h"
+#include "index.h"
+#include "bitvec.h"
+#include "diff.h"
/* what is the common non-wildcard prefix for all items in the pathspec */
char *git_pathspec_prefix(const git_strarray *pathspec)
@@ -56,7 +63,7 @@ bool git_pathspec_is_empty(const git_strarray *pathspec)
}
/* build a vector of fnmatch patterns to evaluate efficiently */
-int git_pathspec_init(
+int git_pathspec__vinit(
git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
{
size_t i;
@@ -93,7 +100,7 @@ int git_pathspec_init(
}
/* free data from the pathspec vector */
-void git_pathspec_free(git_vector *vspec)
+void git_pathspec__vfree(git_vector *vspec)
{
git_attr_fnmatch *match;
unsigned int i;
@@ -106,63 +113,602 @@ void git_pathspec_free(git_vector *vspec)
git_vector_free(vspec);
}
+struct pathspec_match_context {
+ int fnmatch_flags;
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+};
+
+static void pathspec_match_context_init(
+ struct pathspec_match_context *ctxt,
+ bool disable_fnmatch,
+ bool casefold)
+{
+ if (disable_fnmatch)
+ ctxt->fnmatch_flags = -1;
+ else if (casefold)
+ ctxt->fnmatch_flags = FNM_CASEFOLD;
+ else
+ ctxt->fnmatch_flags = 0;
+
+ if (casefold) {
+ ctxt->strcomp = git__strcasecmp;
+ ctxt->strncomp = git__strncasecmp;
+ } else {
+ ctxt->strcomp = git__strcmp;
+ ctxt->strncomp = git__strncmp;
+ }
+}
+
+static int pathspec_match_one(
+ const git_attr_fnmatch *match,
+ struct pathspec_match_context *ctxt,
+ const char *path)
+{
+ int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
+
+ if (result == FNM_NOMATCH)
+ result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ ctxt->strncomp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1;
+ return -1;
+}
+
+static int git_pathspec__match_at(
+ size_t *matched_at,
+ const git_vector *vspec,
+ struct pathspec_match_context *ctxt,
+ const char *path0,
+ const char *path1)
+{
+ int result = GIT_ENOTFOUND;
+ size_t i = 0;
+ const git_attr_fnmatch *match;
+
+ git_vector_foreach(vspec, i, match) {
+ if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0)
+ break;
+ if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0)
+ break;
+ }
+
+ *matched_at = i;
+ return result;
+}
+
/* match a path against the vectorized pathspec */
-bool git_pathspec_match_path(
- git_vector *vspec,
+bool git_pathspec__match(
+ const git_vector *vspec,
const char *path,
bool disable_fnmatch,
bool casefold,
- const char **matched_pathspec)
+ const char **matched_pathspec,
+ size_t *matched_at)
{
- size_t i;
- git_attr_fnmatch *match;
- int fnmatch_flags = 0;
- int (*use_strcmp)(const char *, const char *);
- int (*use_strncmp)(const char *, const char *, size_t);
+ int result;
+ size_t pos;
+ struct pathspec_match_context ctxt;
if (matched_pathspec)
*matched_pathspec = NULL;
+ if (matched_at)
+ *matched_at = GIT_PATHSPEC_NOMATCH;
if (!vspec || !vspec->length)
return true;
- if (disable_fnmatch)
- fnmatch_flags = -1;
- else if (casefold)
- fnmatch_flags = FNM_CASEFOLD;
+ pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
- if (casefold) {
- use_strcmp = git__strcasecmp;
- use_strncmp = git__strncasecmp;
- } else {
- use_strcmp = git__strcmp;
- use_strncmp = git__strncmp;
+ result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL);
+ if (result >= 0) {
+ if (matched_pathspec) {
+ const git_attr_fnmatch *match = git_vector_get(vspec, pos);
+ *matched_pathspec = match->pattern;
+ }
+
+ if (matched_at)
+ *matched_at = pos;
}
- git_vector_foreach(vspec, i, match) {
- int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
-
- if (result == FNM_NOMATCH)
- result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
-
- if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
- result = p_fnmatch(match->pattern, path, fnmatch_flags);
-
- /* if we didn't match, look for exact dirname prefix match */
- if (result == FNM_NOMATCH &&
- (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
- use_strncmp(path, match->pattern, match->length) == 0 &&
- path[match->length] == '/')
- result = 0;
-
- if (result == 0) {
- if (matched_pathspec)
- *matched_pathspec = match->pattern;
-
- return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
+ return (result > 0);
+}
+
+
+int git_pathspec__init(git_pathspec *ps, const git_strarray *paths)
+{
+ int error = 0;
+
+ memset(ps, 0, sizeof(*ps));
+
+ ps->prefix = git_pathspec_prefix(paths);
+
+ if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 ||
+ (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0)
+ git_pathspec__clear(ps);
+
+ return error;
+}
+
+void git_pathspec__clear(git_pathspec *ps)
+{
+ git__free(ps->prefix);
+ git_pathspec__vfree(&ps->pathspec);
+ git_pool_clear(&ps->pool);
+ memset(ps, 0, sizeof(*ps));
+}
+
+int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec)
+{
+ int error = 0;
+ git_pathspec *ps = git__malloc(sizeof(git_pathspec));
+ GITERR_CHECK_ALLOC(ps);
+
+ if ((error = git_pathspec__init(ps, pathspec)) < 0) {
+ git__free(ps);
+ return error;
+ }
+
+ GIT_REFCOUNT_INC(ps);
+ *out = ps;
+ return 0;
+}
+
+static void pathspec_free(git_pathspec *ps)
+{
+ git_pathspec__clear(ps);
+ git__free(ps);
+}
+
+void git_pathspec_free(git_pathspec *ps)
+{
+ if (!ps)
+ return;
+ GIT_REFCOUNT_DEC(ps, pathspec_free);
+}
+
+int git_pathspec_matches_path(
+ const git_pathspec *ps, uint32_t flags, const char *path)
+{
+ bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0;
+ bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0;
+
+ assert(ps && path);
+
+ return (0 != git_pathspec__match(
+ &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL));
+}
+
+static void pathspec_match_free(git_pathspec_match_list *m)
+{
+ git_pathspec_free(m->pathspec);
+ m->pathspec = NULL;
+
+ git_array_clear(m->matches);
+ git_array_clear(m->failures);
+ git_pool_clear(&m->pool);
+ git__free(m);
+}
+
+static git_pathspec_match_list *pathspec_match_alloc(
+ git_pathspec *ps, int datatype)
+{
+ git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list));
+
+ if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) {
+ pathspec_match_free(m);
+ m = NULL;
+ }
+
+ /* need to keep reference to pathspec and increment refcount because
+ * failures array stores pointers to the pattern strings of the
+ * pathspec that had no matches
+ */
+ GIT_REFCOUNT_INC(ps);
+ m->pathspec = ps;
+ m->datatype = datatype;
+
+ return m;
+}
+
+GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos)
+{
+ if (!git_bitvec_get(used, pos)) {
+ git_bitvec_set(used, pos, true);
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t pathspec_mark_remaining(
+ git_bitvec *used,
+ git_vector *patterns,
+ struct pathspec_match_context *ctxt,
+ size_t start,
+ const char *path0,
+ const char *path1)
+{
+ size_t count = 0;
+
+ if (path1 == path0)
+ path1 = NULL;
+
+ for (; start < patterns->length; ++start) {
+ const git_attr_fnmatch *pat = git_vector_get(patterns, start);
+
+ if (git_bitvec_get(used, start))
+ continue;
+
+ if (path0 && pathspec_match_one(pat, ctxt, path0) > 0)
+ count += pathspec_mark_pattern(used, start);
+ else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0)
+ count += pathspec_mark_pattern(used, start);
+ }
+
+ return count;
+}
+
+static int pathspec_build_failure_array(
+ git_pathspec_string_array_t *failures,
+ git_vector *patterns,
+ git_bitvec *used,
+ git_pool *pool)
+{
+ size_t pos;
+ char **failed;
+ const git_attr_fnmatch *pat;
+
+ for (pos = 0; pos < patterns->length; ++pos) {
+ if (git_bitvec_get(used, pos))
+ continue;
+
+ if ((failed = git_array_alloc(*failures)) == NULL)
+ return -1;
+
+ pat = git_vector_get(patterns, pos);
+
+ if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pathspec_match_from_iterator(
+ git_pathspec_match_list **out,
+ git_iterator *iter,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ const git_index_entry *entry = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t pos, used_ct = 0, found_files = 0;
+ git_index *index = NULL;
+ git_bitvec used_patterns;
+ char **file;
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0)
+ goto done;
+
+ if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
+ (error = git_repository_index__weakptr(
+ &index, git_iterator_owner(iter))) < 0)
+ goto done;
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_iterator_ignore_case(iter));
+
+ while (!(error = git_iterator_advance(&entry, iter))) {
+ /* search for match with entry->path */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, entry->path, NULL);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result) {
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ continue;
+ }
+
+ /* check if path is ignored and untracked */
+ if (index != NULL &&
+ git_iterator_current_is_ignored(iter) &&
+ git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ ++found_files;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
}
+
+ /* insert matched path into matches array */
+ if ((file = (char **)git_array_alloc(m->matches)) == NULL ||
+ (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ error = 0;
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) {
+ giterr_set(GITERR_INVALID, "No matching files were found");
+ error = GIT_ENOTFOUND;
}
- return false;
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags)
+{
+ git_iterator_flag_t f = 0;
+
+ if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0)
+ f |= GIT_ITERATOR_IGNORE_CASE;
+ else if ((flags & GIT_PATHSPEC_USE_CASE) != 0)
+ f |= GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ return f;
+}
+
+int git_pathspec_match_workdir(
+ git_pathspec_match_list **out,
+ git_repository *repo,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(repo);
+
+ if (!(error = git_iterator_for_workdir(
+ &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_index(
+ git_pathspec_match_list **out,
+ git_index *index,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(index);
+
+ if (!(error = git_iterator_for_index(
+ &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_tree(
+ git_pathspec_match_list **out,
+ git_tree *tree,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(tree);
+
+ if (!(error = git_iterator_for_tree(
+ &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_diff(
+ git_pathspec_match_list **out,
+ git_diff_list *diff,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t i, pos, used_ct = 0, found_deltas = 0;
+ const git_diff_delta *delta, **match;
+ git_bitvec used_patterns;
+
+ assert(diff);
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_diff_is_sorted_icase(diff));
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ /* search for match with delta */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result)
+ continue;
+
+ ++found_deltas;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1,
+ delta->old_file.path, delta->new_file.path);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
+ }
+
+ /* insert matched delta into matches array */
+ if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) {
+ error = -1;
+ goto done;
+ } else {
+ *match = delta;
+ }
+ }
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) {
+ giterr_set(GITERR_INVALID, "No matching deltas were found");
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+void git_pathspec_match_list_free(git_pathspec_match_list *m)
+{
+ if (m)
+ pathspec_match_free(m);
+}
+
+size_t git_pathspec_match_list_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->matches) : 0;
+}
+
+const char *git_pathspec_match_list_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const char **)git_array_get(m->matches, pos));
+}
+
+const git_diff_delta *git_pathspec_match_list_diff_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const git_diff_delta **)git_array_get(m->matches, pos));
+}
+
+size_t git_pathspec_match_list_failed_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->failures) : 0;
+}
+
+const char * git_pathspec_match_list_failed_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ char **entry = m ? git_array_get(m->failures, pos) : NULL;
+
+ return entry ? *entry : NULL;
}
diff --git a/src/pathspec.h b/src/pathspec.h
index 43a94baad..40cd21c3f 100644
--- a/src/pathspec.h
+++ b/src/pathspec.h
@@ -8,9 +8,35 @@
#define INCLUDE_pathspec_h__
#include "common.h"
+#include <git2/pathspec.h>
#include "buffer.h"
#include "vector.h"
#include "pool.h"
+#include "array.h"
+
+/* public compiled pathspec */
+struct git_pathspec {
+ git_refcount rc;
+ char *prefix;
+ git_vector pathspec;
+ git_pool pool;
+};
+
+enum {
+ PATHSPEC_DATATYPE_STRINGS = 0,
+ PATHSPEC_DATATYPE_DIFF = 1,
+};
+
+typedef git_array_t(char *) git_pathspec_string_array_t;
+
+/* public interface to pathspec matching */
+struct git_pathspec_match_list {
+ git_pathspec *pathspec;
+ git_array_t(void *) matches;
+ git_pathspec_string_array_t failures;
+ git_pool pool;
+ int datatype;
+};
/* what is the common non-wildcard prefix for all items in the pathspec */
extern char *git_pathspec_prefix(const git_strarray *pathspec);
@@ -19,22 +45,31 @@ extern char *git_pathspec_prefix(const git_strarray *pathspec);
extern bool git_pathspec_is_empty(const git_strarray *pathspec);
/* build a vector of fnmatch patterns to evaluate efficiently */
-extern int git_pathspec_init(
+extern int git_pathspec__vinit(
git_vector *vspec, const git_strarray *strspec, git_pool *strpool);
/* free data from the pathspec vector */
-extern void git_pathspec_free(git_vector *vspec);
+extern void git_pathspec__vfree(git_vector *vspec);
+
+#define GIT_PATHSPEC_NOMATCH ((size_t)-1)
/*
* Match a path against the vectorized pathspec.
* The matched pathspec is passed back into the `matched_pathspec` parameter,
* unless it is passed as NULL by the caller.
*/
-extern bool git_pathspec_match_path(
- git_vector *vspec,
+extern bool git_pathspec__match(
+ const git_vector *vspec,
const char *path,
bool disable_fnmatch,
bool casefold,
- const char **matched_pathspec);
+ const char **matched_pathspec,
+ size_t *matched_at);
+
+/* easy pathspec setup */
+
+extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths);
+
+extern void git_pathspec__clear(git_pathspec *ps);
#endif
diff --git a/src/pool.c b/src/pool.c
index b3cd49665..d484769e9 100644
--- a/src/pool.c
+++ b/src/pool.c
@@ -194,6 +194,11 @@ char *git_pool_strndup(git_pool *pool, const char *str, size_t n)
assert(pool && str && pool->item_size == sizeof(char));
+ if (n + 1 == 0) {
+ giterr_set_oom();
+ return NULL;
+ }
+
if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) {
memcpy(ptr, str, n);
*(((char *)ptr) + n) = '\0';
diff --git a/src/posix.c b/src/posix.c
index 5d526d33c..b75109b83 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -111,12 +111,12 @@ int p_open(const char *path, int flags, ...)
va_end(arg_list);
}
- return open(path, flags | O_BINARY, mode);
+ return open(path, flags | O_BINARY | O_CLOEXEC, mode);
}
int p_creat(const char *path, mode_t mode)
{
- return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode);
+ return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode);
}
int p_getcwd(char *buffer_out, size_t size)
diff --git a/src/posix.h b/src/posix.h
index 719c8a04c..40bcc1ab0 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -25,6 +25,9 @@
#if !defined(O_BINARY)
#define O_BINARY 0
#endif
+#if !defined(O_CLOEXEC)
+#define O_CLOEXEC 0
+#endif
typedef int git_file;
diff --git a/src/pqueue.h b/src/pqueue.h
index ed7139285..9061f8279 100644
--- a/src/pqueue.h
+++ b/src/pqueue.h
@@ -48,7 +48,7 @@ typedef struct {
* should be preallocated
* @param cmppri the callback function to compare two nodes of the queue
*
- * @Return the handle or NULL for insufficent memory
+ * @return the handle or NULL for insufficent memory
*/
int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri);
@@ -83,8 +83,7 @@ int git_pqueue_insert(git_pqueue *q, void *d);
/**
* pop the highest-ranking item from the queue.
- * @param p the queue
- * @param d where to copy the entry to
+ * @param q the queue
* @return NULL on error, otherwise the entry
*/
void *git_pqueue_pop(git_pqueue *q);
@@ -93,7 +92,6 @@ void *git_pqueue_pop(git_pqueue *q);
/**
* access highest-ranking item without removing it.
* @param q the queue
- * @param d the entry
* @return NULL on error, otherwise the entry
*/
void *git_pqueue_peek(git_pqueue *q);
diff --git a/src/push.c b/src/push.c
index f81a0aee9..452d71789 100644
--- a/src/push.c
+++ b/src/push.c
@@ -99,19 +99,17 @@ static int check_lref(git_push *push, char *ref)
/* lref must be resolvable to an existing object */
git_object *obj;
int error = git_revparse_single(&obj, push->repo, ref);
+ git_object_free(obj);
- if (error) {
- if (error == GIT_ENOTFOUND)
- giterr_set(GITERR_REFERENCE,
- "src refspec '%s' does not match any existing object", ref);
- else
- giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ if (!error)
+ return 0;
- return -1;
- } else
- git_object_free(obj);
-
- return 0;
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_REFERENCE,
+ "src refspec '%s' does not match any existing object", ref);
+ else
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
}
static int parse_refspec(git_push *push, push_spec **spec, const char *str)
@@ -179,10 +177,10 @@ int git_push_add_refspec(git_push *push, const char *refspec)
int git_push_update_tips(git_push *push)
{
- git_refspec *fetch_spec = &push->remote->fetch;
git_buf remote_ref_name = GIT_BUF_INIT;
size_t i, j;
- push_spec *push_spec;
+ git_refspec *fetch_spec;
+ push_spec *push_spec = NULL;
git_reference *remote_ref;
push_status *status;
int error = 0;
@@ -193,7 +191,8 @@ int git_push_update_tips(git_push *push)
continue;
/* Find the corresponding remote ref */
- if (!git_refspec_src_matches(fetch_spec, status->ref))
+ fetch_spec = git_remote__matching_refspec(push->remote, status->ref);
+ if (!fetch_spec)
continue;
if ((error = git_refspec_transform_r(&remote_ref_name, fetch_spec, status->ref)) < 0)
@@ -304,7 +303,7 @@ static int revwalk(git_vector *commits, git_push *push)
continue;
if (!git_odb_exists(push->repo->_odb, &spec->roid)) {
- giterr_clear();
+ giterr_set(GITERR_REFERENCE, "Cannot push missing reference");
error = GIT_ENONFASTFORWARD;
goto on_error;
}
@@ -314,7 +313,8 @@ static int revwalk(git_vector *commits, git_push *push)
if (error == GIT_ENOTFOUND ||
(!error && !git_oid_equal(&base, &spec->roid))) {
- giterr_clear();
+ giterr_set(GITERR_REFERENCE,
+ "Cannot push non-fastforwardable reference");
error = GIT_ENONFASTFORWARD;
goto on_error;
}
@@ -334,12 +334,13 @@ static int revwalk(git_vector *commits, git_push *push)
while ((error = git_revwalk_next(&oid, rw)) == 0) {
git_oid *o = git__malloc(GIT_OID_RAWSZ);
- GITERR_CHECK_ALLOC(o);
- git_oid_cpy(o, &oid);
- if (git_vector_insert(commits, o) < 0) {
+ if (!o) {
error = -1;
goto on_error;
}
+ git_oid_cpy(o, &oid);
+ if ((error = git_vector_insert(commits, o)) < 0)
+ goto on_error;
}
on_error:
@@ -377,7 +378,7 @@ static int queue_differences(
const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j);
int cmp = 0;
- if (!git_oid_cmp(&b_entry->oid, &d_entry->oid))
+ if (!git_oid__cmp(&b_entry->oid, &d_entry->oid))
goto loop;
cmp = strcmp(b_entry->filename, d_entry->filename);
@@ -520,7 +521,7 @@ static int calculate_work(git_push *push)
/* This is a create or update. Local ref must exist. */
if (git_reference_name_to_id(
&spec->loid, push->repo, spec->lref) < 0) {
- giterr_set(GIT_ENOTFOUND, "No such reference '%s'", spec->lref);
+ giterr_set(GITERR_REFERENCE, "No such reference '%s'", spec->lref);
return -1;
}
}
diff --git a/src/refdb.c b/src/refdb.c
index d9b73c6e7..4de7188b2 100644
--- a/src/refdb.c
+++ b/src/refdb.c
@@ -7,15 +7,16 @@
#include "common.h"
#include "posix.h"
+
#include "git2/object.h"
#include "git2/refs.h"
#include "git2/refdb.h"
+#include "git2/sys/refdb_backend.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;
@@ -45,7 +46,7 @@ int git_refdb_open(git_refdb **out, git_repository *repo)
return -1;
/* Add the default (filesystem) backend */
- if (git_refdb_backend_fs(&dir, repo, db) < 0) {
+ if (git_refdb_backend_fs(&dir, repo) < 0) {
git_refdb_free(db);
return -1;
}
@@ -57,15 +58,19 @@ int git_refdb_open(git_refdb **out, git_repository *repo)
return 0;
}
-int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+static void refdb_free_backend(git_refdb *db)
{
if (db->backend) {
- if(db->backend->free)
+ if (db->backend->free)
db->backend->free(db->backend);
else
git__free(db->backend);
}
+}
+int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+{
+ refdb_free_backend(db);
db->backend = backend;
return 0;
@@ -74,23 +79,17 @@ int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
int git_refdb_compress(git_refdb *db)
{
assert(db);
-
- if (db->backend->compress) {
+
+ if (db->backend->compress)
return db->backend->compress(db->backend);
- }
-
+
return 0;
}
-static void refdb_free(git_refdb *db)
+void git_refdb__free(git_refdb *db)
{
- if (db->backend) {
- if(db->backend->free)
- db->backend->free(db->backend);
- else
- git__free(db->backend);
- }
-
+ refdb_free_backend(db);
+ git__memzero(db, sizeof(*db));
git__free(db);
}
@@ -99,7 +98,7 @@ void git_refdb_free(git_refdb *db)
if (db == NULL)
return;
- GIT_REFCOUNT_DEC(db, refdb_free);
+ GIT_REFCOUNT_DEC(db, git_refdb__free);
}
int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
@@ -111,75 +110,96 @@ int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
{
- assert(db && db->backend && ref_name);
+ git_reference *ref;
+ int error;
- return db->backend->lookup(out, db->backend, ref_name);
-}
+ assert(db && db->backend && out && ref_name);
-int git_refdb_foreach(
- git_refdb *db,
- unsigned int list_flags,
- git_reference_foreach_cb callback,
- void *payload)
-{
- assert(db && db->backend);
+ error = db->backend->lookup(&ref, db->backend, ref_name);
+ if (error < 0)
+ return error;
- return db->backend->foreach(db->backend, list_flags, callback, payload);
-}
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
-struct glob_cb_data {
- const char *glob;
- git_reference_foreach_cb callback;
- void *payload;
-};
+ *out = ref;
+ return 0;
+}
-static int fromglob_cb(const char *reference_name, void *payload)
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
{
- struct glob_cb_data *data = (struct glob_cb_data *)payload;
+ if (!db->backend || !db->backend->iterator) {
+ giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators");
+ return -1;
+ }
- if (!p_fnmatch(data->glob, reference_name, 0))
- return data->callback(reference_name, data->payload);
+ if (db->backend->iterator(out, db->backend, glob) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
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 git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter)
{
int error;
- struct glob_cb_data data;
- assert(db && db->backend && glob && callback);
+ if ((error = iter->next(out, iter)) < 0)
+ return error;
- 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;
+ GIT_REFCOUNT_INC(iter->db);
+ (*out)->db = iter->db;
- error = db->backend->foreach(db->backend,
- list_flags, fromglob_cb, &data);
- }
+ return 0;
+}
+
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter)
+{
+ return iter->next_name(out, iter);
+}
- return error;
+void git_refdb_iterator_free(git_reference_iterator *iter)
+{
+ GIT_REFCOUNT_DEC(iter->db, git_refdb__free);
+ iter->free(iter);
}
-int git_refdb_write(git_refdb *db, const git_reference *ref)
+int git_refdb_write(git_refdb *db, git_reference *ref, int force)
{
assert(db && db->backend);
- return db->backend->write(db->backend, ref);
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
+
+ return db->backend->write(db->backend, ref, force);
}
-int git_refdb_delete(struct git_refdb *db, const git_reference *ref)
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force)
{
+ int error;
+
assert(db && db->backend);
+ error = db->backend->rename(out, db->backend, old_name, new_name, force);
+ if (error < 0)
+ return error;
+
+ if (out) {
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+ }
+
+ return 0;
+}
- return db->backend->delete(db->backend, ref);
+int git_refdb_delete(struct git_refdb *db, const char *ref_name)
+{
+ assert(db && db->backend);
+ return db->backend->delete(db->backend, ref_name);
}
diff --git a/src/refdb.h b/src/refdb.h
index 0969711b9..3aea37b62 100644
--- a/src/refdb.h
+++ b/src/refdb.h
@@ -16,6 +16,8 @@ struct git_refdb {
git_refdb_backend *backend;
};
+void git_refdb__free(git_refdb *db);
+
int git_refdb_exists(
int *exists,
git_refdb *refdb,
@@ -26,21 +28,19 @@ int git_refdb_lookup(
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);
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force);
+
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob);
+int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);
+void git_refdb_iterator_free(git_reference_iterator *iter);
+
+int git_refdb_write(git_refdb *refdb, git_reference *ref, int force);
+int git_refdb_delete(git_refdb *refdb, const char *ref_name);
#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index f00bd72a0..acd82594b 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -9,16 +9,18 @@
#include "hash.h"
#include "repository.h"
#include "fileops.h"
+#include "filebuf.h"
#include "pack.h"
#include "reflog.h"
-#include "config.h"
#include "refdb.h"
#include "refdb_fs.h"
+#include "iterator.h"
#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
-#include <git2/refdb_backend.h>
+#include <git2/sys/refdb_backend.h>
+#include <git2/sys/refs.h>
GIT__USE_STRMAP;
@@ -26,8 +28,16 @@ GIT__USE_STRMAP;
#define MAX_NESTING_LEVEL 10
enum {
- GIT_PACKREF_HAS_PEEL = 1,
- GIT_PACKREF_WAS_LOOSE = 2
+ PACKREF_HAS_PEEL = 1,
+ PACKREF_WAS_LOOSE = 2,
+ PACKREF_CANNOT_PEEL = 4,
+ PACKREF_SHADOWED = 8,
+};
+
+enum {
+ PEELING_NONE = 0,
+ PEELING_STANDARD,
+ PEELING_FULL
};
struct packref {
@@ -41,10 +51,10 @@ typedef struct refdb_fs_backend {
git_refdb_backend parent;
git_repository *repo;
- const char *path;
- git_refdb *refdb;
+ char *path;
git_refcache refcache;
+ int peeling_mode;
} refdb_fs_backend;
static int reference_read(
@@ -62,7 +72,7 @@ static int reference_read(
/* 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);
@@ -99,7 +109,7 @@ static int packed_parse_oid(
refname_len = refname_end - refname_begin;
- ref = git__malloc(sizeof(struct packref) + refname_len + 1);
+ ref = git__calloc(1, sizeof(struct packref) + refname_len + 1);
GITERR_CHECK_ALLOC(ref);
memcpy(ref->name, refname_begin, refname_len);
@@ -107,11 +117,8 @@ static int packed_parse_oid(
git_oid_cpy(&ref->oid, &id);
- ref->flags = 0;
-
*ref_out = ref;
*buffer_out = refname_end + 1;
-
return 0;
corrupt:
@@ -133,10 +140,6 @@ static int packed_parse_peel(
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;
@@ -155,6 +158,7 @@ static int packed_parse_peel(
goto corrupt;
}
+ tag_ref->flags |= PACKREF_HAS_PEEL;
*buffer_out = buffer;
return 0;
@@ -175,7 +179,10 @@ static int packed_load(refdb_fs_backend *backend)
ref_cache->packfile = git_strmap_alloc();
GITERR_CHECK_ALLOC(ref_cache->packfile);
}
-
+
+ if (backend->path == NULL)
+ return 0;
+
result = reference_read(&packfile, &ref_cache->packfile_time,
backend->path, GIT_PACKEDREFS_FILE, &updated);
@@ -193,7 +200,7 @@ static int packed_load(refdb_fs_backend *backend)
if (result < 0)
return -1;
-
+
if (!updated)
return 0;
@@ -206,6 +213,30 @@ static int packed_load(refdb_fs_backend *backend)
buffer_start = (const char *)packfile.ptr;
buffer_end = (const char *)(buffer_start) + packfile.size;
+ backend->peeling_mode = PEELING_NONE;
+
+ if (buffer_start[0] == '#') {
+ static const char *traits_header = "# pack-refs with: ";
+
+ if (git__prefixcmp(buffer_start, traits_header) == 0) {
+ char *traits = (char *)buffer_start + strlen(traits_header);
+ char *traits_end = strchr(traits, '\n');
+
+ if (traits_end == NULL)
+ goto parse_failed;
+
+ *traits_end = '\0';
+
+ if (strstr(traits, " fully-peeled ") != NULL) {
+ backend->peeling_mode = PEELING_FULL;
+ } else if (strstr(traits, " peeled ") != NULL) {
+ backend->peeling_mode = PEELING_STANDARD;
+ }
+
+ buffer_start = traits_end + 1;
+ }
+ }
+
while (buffer_start < buffer_end && buffer_start[0] == '#') {
buffer_start = strchr(buffer_start, '\n');
if (buffer_start == NULL)
@@ -224,6 +255,10 @@ static int packed_load(refdb_fs_backend *backend)
if (buffer_start[0] == '^') {
if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
goto parse_failed;
+ } else if (backend->peeling_mode == PEELING_FULL ||
+ (backend->peeling_mode == PEELING_STANDARD &&
+ git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) {
+ ref->flags |= PACKREF_CANNOT_PEEL;
}
git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
@@ -241,7 +276,7 @@ parse_failed:
return -1;
}
-static int loose_parse_oid(git_oid *oid, git_buf *file_content)
+static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content)
{
size_t len;
const char *str;
@@ -263,7 +298,7 @@ static int loose_parse_oid(git_oid *oid, git_buf *file_content)
return 0;
corrupted:
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
return -1;
}
@@ -284,19 +319,19 @@ static int loose_lookup_to_packfile(
git_buf_rtrim(&ref_file);
name_len = strlen(name);
- ref = git__malloc(sizeof(struct packref) + name_len + 1);
+ ref = git__calloc(1, 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) {
+ if (loose_parse_oid(&ref->oid, name, &ref_file) < 0) {
git_buf_free(&ref_file);
git__free(ref);
return -1;
}
- ref->flags = GIT_PACKREF_WAS_LOOSE;
+ ref->flags = PACKREF_WAS_LOOSE;
*ref_out = ref;
git_buf_free(&ref_file);
@@ -417,6 +452,9 @@ static int loose_lookup(
git_buf ref_file = GIT_BUF_INIT;
int error = 0;
+ if (out)
+ *out = NULL;
+
error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL);
if (error < 0)
@@ -430,15 +468,17 @@ static int loose_lookup(
goto done;
}
- *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);
+ if (out)
+ *out = git_reference__alloc_symbolic(ref_name, target);
} else {
- if ((error = loose_parse_oid(&oid, &ref_file)) < 0)
+ if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0)
goto done;
-
- *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL);
+
+ if (out)
+ *out = git_reference__alloc(ref_name, &oid, NULL);
}
- if (*out == NULL)
+ if (out && *out == NULL)
error = -1;
done:
@@ -456,19 +496,19 @@ static int packed_map_entry(
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;
}
@@ -480,13 +520,14 @@ static int packed_lookup(
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)
+ if ((*out = git_reference__alloc(ref_name,
+ &entry->oid, &entry->peel)) == NULL)
return -1;
-
+
return 0;
}
@@ -515,108 +556,255 @@ static int refdb_fs_backend__lookup(
return result;
}
-struct dirent_list_data {
- refdb_fs_backend *backend;
- size_t repo_path_len;
- unsigned int list_type:2;
+typedef struct {
+ git_reference_iterator parent;
- git_reference_foreach_cb callback;
- void *callback_payload;
- int callback_error;
-};
+ char *glob;
+
+ git_pool pool;
+ git_vector loose;
+
+ unsigned int loose_pos;
+ khiter_t packed_pos;
+} refdb_fs_iter;
-static git_ref_t loose_guess_rtype(const git_buf *full_path)
+static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
{
- git_buf ref_file = GIT_BUF_INIT;
- git_ref_t type;
+ refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
- type = GIT_REF_INVALID;
+ git_vector_free(&iter->loose);
+ git_pool_clear(&iter->pool);
+ git__free(iter);
+}
- 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;
+static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
+{
+ int error = 0;
+ git_strmap *packfile = backend->refcache.packfile;
+ git_buf path = GIT_BUF_INIT;
+ git_iterator *fsit = NULL;
+ const git_index_entry *entry = NULL;
+
+ if (!backend->path) /* do nothing if no path for loose refs */
+ return 0;
+
+ if (git_buf_printf(&path, "%s/refs", backend->path) < 0)
+ return -1;
+
+ if ((error = git_iterator_for_filesystem(
+ &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0 ||
+ (error = git_vector_init(&iter->loose, 8, NULL)) < 0 ||
+ (error = git_buf_sets(&path, GIT_REFS_DIR)) < 0)
+ goto cleanup;
+
+ while (!git_iterator_advance(&entry, fsit)) {
+ const char *ref_name;
+ khiter_t pos;
+ char *ref_dup;
+
+ git_buf_truncate(&path, strlen(GIT_REFS_DIR));
+ git_buf_puts(&path, entry->path);
+ ref_name = git_buf_cstr(&path);
+
+ if (git__suffixcmp(ref_name, ".lock") == 0 ||
+ (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
+ continue;
+
+ pos = git_strmap_lookup_index(packfile, ref_name);
+ if (git_strmap_valid_index(packfile, pos)) {
+ struct packref *ref = git_strmap_value_at(packfile, pos);
+ ref->flags |= PACKREF_SHADOWED;
+ }
+
+ if (!(ref_dup = git_pool_strdup(&iter->pool, ref_name))) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_vector_insert(&iter->loose, ref_dup)) < 0)
+ goto cleanup;
}
- git_buf_free(&ref_file);
- return type;
+cleanup:
+ git_iterator_free(fsit);
+ git_buf_free(&path);
+
+ return 0;
}
-static int _dirent_loose_listall(void *_data, git_buf *full_path)
+static int refdb_fs_backend__iterator_next(
+ git_reference **out, git_reference_iterator *_iter)
{
- struct dirent_list_data *data = (struct dirent_list_data *)_data;
- const char *file_path = full_path->ptr + data->repo_path_len;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ git_strmap *packfile = backend->refcache.packfile;
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_listall, _data);
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
- /* 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 (loose_lookup(out, backend, path) == 0)
+ 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 */
+ giterr_clear();
}
- /* Locked references aren't returned */
- if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
+ while (iter->packed_pos < kh_end(packfile)) {
+ struct packref *ref = NULL;
+
+ while (!kh_exist(packfile, iter->packed_pos)) {
+ iter->packed_pos++;
+ if (iter->packed_pos == kh_end(packfile))
+ return GIT_ITEROVER;
+ }
+
+ ref = kh_val(packfile, iter->packed_pos);
+ iter->packed_pos++;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
+ if (*out == NULL)
+ return -1;
+
return 0;
+ }
+
+ return GIT_ITEROVER;
+}
+
+static int refdb_fs_backend__iterator_next_name(
+ const char **out, git_reference_iterator *_iter)
+{
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ git_strmap *packfile = backend->refcache.packfile;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
- if (data->callback(file_path, data->callback_payload))
- data->callback_error = GIT_EUSER;
+ if (git_strmap_exists(packfile, path))
+ continue;
+
+ if (loose_lookup(NULL, backend, path) != 0) {
+ giterr_clear();
+ continue;
+ }
- return data->callback_error;
+ *out = path;
+ return 0;
+ }
+
+ while (iter->packed_pos < kh_end(packfile)) {
+ while (!kh_exist(packfile, iter->packed_pos)) {
+ iter->packed_pos++;
+ if (iter->packed_pos == kh_end(packfile))
+ return GIT_ITEROVER;
+ }
+
+ *out = kh_key(packfile, iter->packed_pos);
+ iter->packed_pos++;
+
+ if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0)
+ continue;
+
+ return 0;
+ }
+
+ return GIT_ITEROVER;
}
-static int refdb_fs_backend__foreach(
- git_refdb_backend *_backend,
- unsigned int list_type,
- git_reference_foreach_cb callback,
- void *payload)
+static int refdb_fs_backend__iterator(
+ git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
+ refdb_fs_iter *iter;
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;
- });
+
+ iter = git__calloc(1, sizeof(refdb_fs_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if (git_pool_init(&iter->pool, 1, 0) < 0)
+ goto fail;
+
+ if (glob != NULL &&
+ (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
+ goto fail;
+
+ iter->parent.next = refdb_fs_backend__iterator_next;
+ iter->parent.next_name = refdb_fs_backend__iterator_next_name;
+ iter->parent.free = refdb_fs_backend__iterator_free;
+
+ if (iter_load_loose_paths(backend, iter) < 0)
+ goto fail;
+
+ *out = (git_reference_iterator *)iter;
+ return 0;
+
+fail:
+ refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
+ return -1;
+}
+
+static bool ref_is_available(
+ const char *old_ref, const char *new_ref, const char *this_ref)
+{
+ if (old_ref == NULL || strcmp(old_ref, this_ref)) {
+ size_t reflen = strlen(this_ref);
+ size_t newlen = strlen(new_ref);
+ size_t cmplen = reflen < newlen ? reflen : newlen;
+ const char *lead = reflen < newlen ? new_ref : this_ref;
+
+ if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
+ return false;
+ }
}
- /* now list the loose references, trying not to
- * duplicate the ref names already in the packed-refs file */
+ return true;
+}
- 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;
+static int reference_path_available(
+ refdb_fs_backend *backend,
+ const char *new_ref,
+ const char* old_ref,
+ int force)
+{
+ struct packref *this_ref;
- if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ if (packed_load(backend) < 0)
return -1;
- result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
+ if (!force) {
+ int exists;
- git_buf_free(&refs_path);
+ if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0)
+ return -1;
+
+ if (exists) {
+ giterr_set(GITERR_REFERENCE,
+ "Failed to write reference '%s': a reference with "
+ " that name already exists.", new_ref);
+ return GIT_EEXISTS;
+ }
+ }
+
+ git_strmap_foreach_value(backend->refcache.packfile, this_ref, {
+ if (!ref_is_available(old_ref, new_ref, this_ref->name)) {
+ giterr_set(GITERR_REFERENCE,
+ "The path to reference '%s' collides with an existing one", new_ref);
+ return -1;
+ }
+ });
- return data.callback_error ? GIT_EUSER : result;
+ return 0;
}
static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
@@ -627,8 +815,7 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
/* 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)
+ 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)
@@ -678,14 +865,7 @@ 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)
+ if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
return 0;
/*
@@ -706,7 +886,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
* Find the object pointed at by this tag
*/
git_oid_cpy(&ref->peel, git_tag_target_id(tag));
- ref->flags |= GIT_PACKREF_HAS_PEEL;
+ ref->flags |= PACKREF_HAS_PEEL;
/*
* The reference has now cached the resolved OID, and is
@@ -739,7 +919,7 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file)
* This obviously only applies to tags.
* The required peels have already been loaded into `ref->peel_target`.
*/
- if (ref->flags & GIT_PACKREF_HAS_PEEL) {
+ if (ref->flags & PACKREF_HAS_PEEL) {
char peel[GIT_OID_HEXSZ + 1];
git_oid_fmt(peel, &ref->peel);
peel[GIT_OID_HEXSZ] = 0;
@@ -776,7 +956,7 @@ static int packed_remove_loose(
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)
+ if ((ref->flags & PACKREF_WAS_LOOSE) == 0)
continue;
if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
@@ -895,66 +1075,113 @@ cleanup_memory:
static int refdb_fs_backend__write(
git_refdb_backend *_backend,
- const git_reference *ref)
+ const git_reference *ref,
+ int force)
{
refdb_fs_backend *backend;
+ int error;
assert(_backend);
backend = (refdb_fs_backend *)_backend;
+ error = reference_path_available(backend, ref->name, NULL, force);
+ if (error < 0)
+ return error;
+
return loose_write(backend, ref);
}
static int refdb_fs_backend__delete(
git_refdb_backend *_backend,
- const git_reference *ref)
+ const char *ref_name)
{
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;
+ int error = 0;
+ bool loose_deleted = 0;
assert(_backend);
- assert(ref);
+ assert(ref_name);
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)
+ if (git_buf_joinpath(&loose_path, backend->path, 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 */
+ error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name);
- if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) {
+ if (error == GIT_ENOTFOUND)
+ return loose_deleted ? 0 : GIT_ENOTFOUND;
+
+ if (error == 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__rename(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *old_name,
+ const char *new_name,
+ int force)
+{
+ refdb_fs_backend *backend;
+ git_reference *old, *new;
+ int error;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ error = reference_path_available(backend, new_name, old_name, force);
+ if (error < 0)
+ return error;
+
+ error = refdb_fs_backend__lookup(&old, _backend, old_name);
+ if (error < 0)
+ return error;
+
+ error = refdb_fs_backend__delete(_backend, old_name);
+ if (error < 0) {
+ git_reference_free(old);
+ return error;
+ }
+
+ new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1);
+ memcpy(new->name, new_name, strlen(new_name) + 1);
+
+ error = loose_write(backend, new);
+ if (error < 0) {
+ git_reference_free(new);
+ return error;
+ }
+
+ if (out) {
+ *out = new;
+ } else {
+ git_reference_free(new);
+ }
+
+ return 0;
+}
+
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
{
refdb_fs_backend *backend;
@@ -993,28 +1220,76 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend)
backend = (refdb_fs_backend *)_backend;
refcache_free(&backend->refcache);
+ git__free(backend->path);
git__free(backend);
}
+static int setup_namespace(git_buf *path, git_repository *repo)
+{
+ char *parts, *start, *end;
+
+ /* Not all repositories have a path */
+ if (repo->path_repository == NULL)
+ return 0;
+
+ /* Load the path to the repo first */
+ git_buf_puts(path, repo->path_repository);
+
+ /* if the repo is not namespaced, nothing else to do */
+ if (repo->namespace == NULL)
+ return 0;
+
+ parts = end = git__strdup(repo->namespace);
+ if (parts == NULL)
+ return -1;
+
+ /**
+ * From `man gitnamespaces`:
+ * namespaces which include a / will expand to a hierarchy
+ * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
+ * refs under refs/namespaces/foo/refs/namespaces/bar/
+ */
+ while ((start = git__strsep(&end, "/")) != NULL) {
+ git_buf_printf(path, "refs/namespaces/%s/", start);
+ }
+
+ git_buf_printf(path, "refs/namespaces/%s/refs", end);
+ git__free(parts);
+
+ /* Make sure that the folder with the namespace exists */
+ if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0)
+ return -1;
+
+ /* Return the root of the namespaced path, i.e. without the trailing '/refs' */
+ git_buf_rtruncate_at_char(path, '/');
+ return 0;
+}
+
int git_refdb_backend_fs(
git_refdb_backend **backend_out,
- git_repository *repository,
- git_refdb *refdb)
+ git_repository *repository)
{
+ git_buf path = GIT_BUF_INIT;
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;
+
+ if (setup_namespace(&path, repository) < 0) {
+ git__free(backend);
+ return -1;
+ }
+
+ backend->path = git_buf_detach(&path);
backend->parent.exists = &refdb_fs_backend__exists;
backend->parent.lookup = &refdb_fs_backend__lookup;
- backend->parent.foreach = &refdb_fs_backend__foreach;
+ backend->parent.iterator = &refdb_fs_backend__iterator;
backend->parent.write = &refdb_fs_backend__write;
backend->parent.delete = &refdb_fs_backend__delete;
+ backend->parent.rename = &refdb_fs_backend__rename;
backend->parent.compress = &refdb_fs_backend__compress;
backend->parent.free = &refdb_fs_backend__free;
diff --git a/src/reflog.c b/src/reflog.c
index 8c133fe53..4cc20d2c7 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -483,8 +483,10 @@ int git_reflog_drop(
entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
- if (entry == NULL)
+ if (entry == NULL) {
+ giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx);
return GIT_ENOTFOUND;
+ }
reflog_entry_free(entry);
diff --git a/src/refs.c b/src/refs.c
index b1f679632..c0e460cc3 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -9,6 +9,7 @@
#include "hash.h"
#include "repository.h"
#include "fileops.h"
+#include "filebuf.h"
#include "pack.h"
#include "reflog.h"
#include "refdb.h"
@@ -19,7 +20,7 @@
#include <git2/branch.h>
#include <git2/refs.h>
#include <git2/refdb.h>
-#include <git2/refdb_backend.h>
+#include <git2/sys/refs.h>
GIT__USE_STRMAP;
@@ -31,171 +32,79 @@ enum {
GIT_PACKREF_WAS_LOOSE = 2
};
-
-git_reference *git_reference__alloc(
- git_refdb *refdb,
- const char *name,
- const git_oid *oid,
- const char *symbolic)
+static git_reference *alloc_ref(const char *name)
{
git_reference *ref;
- size_t namelen;
-
- assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic)));
-
- namelen = strlen(name);
+ size_t namelen = strlen(name);
if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
return NULL;
- if (oid) {
- ref->type = GIT_REF_OID;
- git_oid_cpy(&ref->target.oid, oid);
- } else {
- ref->type = GIT_REF_SYMBOLIC;
-
- if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) {
- git__free(ref);
- return NULL;
- }
- }
-
- ref->db = refdb;
memcpy(ref->name, name, namelen + 1);
return ref;
}
-void git_reference_free(git_reference *reference)
+git_reference *git_reference__alloc_symbolic(
+ const char *name, const char *target)
{
- if (reference == NULL)
- return;
-
- if (reference->type == GIT_REF_SYMBOLIC) {
- git__free(reference->target.symbolic);
- reference->target.symbolic = NULL;
- }
-
- reference->db = NULL;
- reference->type = GIT_REF_INVALID;
-
- git__free(reference);
-}
+ git_reference *ref;
-struct reference_available_t {
- const char *new_ref;
- const char *old_ref;
- int available;
-};
+ assert(name && target);
-static int _reference_available_cb(const char *ref, void *data)
-{
- struct reference_available_t *d;
-
- assert(ref && data);
- d = (struct reference_available_t *)data;
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
- if (!d->old_ref || strcmp(d->old_ref, ref)) {
- size_t reflen = strlen(ref);
- size_t newlen = strlen(d->new_ref);
- size_t cmplen = reflen < newlen ? reflen : newlen;
- const char *lead = reflen < newlen ? d->new_ref : ref;
+ ref->type = GIT_REF_SYMBOLIC;
- if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {
- d->available = 0;
- return -1;
- }
+ if ((ref->target.symbolic = git__strdup(target)) == NULL) {
+ git__free(ref);
+ return NULL;
}
- return 0;
+ return ref;
}
-static int reference_path_available(
- git_repository *repo,
- const char *ref,
- const char* old_ref)
+git_reference *git_reference__alloc(
+ const char *name,
+ const git_oid *oid,
+ const git_oid *peel)
{
- int error;
- struct reference_available_t data;
+ git_reference *ref;
- data.new_ref = ref;
- data.old_ref = old_ref;
- data.available = 1;
+ assert(name && oid);
- error = git_reference_foreach(
- repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data);
- if (error < 0)
- return error;
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
- if (!data.available) {
- giterr_set(GITERR_REFERENCE,
- "The path to reference '%s' collides with an existing one", ref);
- return -1;
- }
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
- return 0;
+ if (peel != NULL)
+ git_oid_cpy(&ref->peel, peel);
+
+ return ref;
}
-/*
- * Check if a reference could be written to disk, based on:
- *
- * - Whether a reference with the same name already exists,
- * and we are allowing or disallowing overwrites
- *
- * - Whether the name of the reference would collide with
- * an existing path
- */
-static int reference_can_write(
- git_repository *repo,
- const char *refname,
- const char *previous_name,
- int force)
+void git_reference_free(git_reference *reference)
{
- 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)
- return -1;
-
- /* check if the reference actually exists, but only if we are not forcing
- * the rename. If we are forcing, it's OK to overwrite */
- if (!force) {
- int exists;
-
- if (git_refdb_exists(&exists, refdb, refname) < 0)
- return -1;
+ if (reference == NULL)
+ return;
- /* We cannot proceed if the reference already exists and we're not forcing
- * the rename; the existing one would be overwritten */
- if (exists) {
- giterr_set(GITERR_REFERENCE,
- "A reference with that name (%s) already exists", refname);
- return GIT_EEXISTS;
- }
- }
+ if (reference->type == GIT_REF_SYMBOLIC)
+ git__free(reference->target.symbolic);
- /* FIXME: if the reference exists and we are forcing, do we really need to
- * remove the reference first?
- *
- * Two cases:
- *
- * - the reference already exists and is loose: not a problem, the file
- * gets overwritten on disk
- *
- * - the reference already exists and is packed: we write a new one as
- * loose, which by all means renders the packed one useless
- */
+ if (reference->db)
+ GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
- return 0;
+ git__free(reference);
}
int git_reference_delete(git_reference *ref)
{
- return git_refdb_delete(ref->db, ref);
+ return git_refdb_delete(ref->db, ref->name);
}
int git_reference_lookup(git_reference **ref_out,
@@ -238,10 +147,10 @@ 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;
@@ -259,7 +168,7 @@ int git_reference_lookup_resolved(
if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
return error;
-
+
scan_type = ref->type;
}
@@ -274,6 +183,67 @@ int git_reference_lookup_resolved(
return 0;
}
+int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
+{
+ int error = 0, i;
+ bool fallbackmode = true, foundvalid = false;
+ git_reference *ref;
+ git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
+
+ static const char* formatters[] = {
+ "%s",
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
+ NULL
+ };
+
+ if (*refname)
+ git_buf_puts(&name, refname);
+ else {
+ git_buf_puts(&name, GIT_HEAD_FILE);
+ fallbackmode = false;
+ }
+
+ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
+
+ git_buf_clear(&refnamebuf);
+
+ if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
+ goto cleanup;
+
+ if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
+ error = GIT_EINVALIDSPEC;
+ continue;
+ }
+ foundvalid = true;
+
+ error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
+
+ if (!error) {
+ *out = ref;
+ error = 0;
+ goto cleanup;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
+
+cleanup:
+ if (error && !foundvalid) {
+ /* never found a valid reference name */
+ giterr_set(GITERR_REFERENCE,
+ "Could not use '%s' as valid reference name", git_buf_cstr(&name));
+ }
+
+ git_buf_free(&name);
+ git_buf_free(&refnamebuf);
+ return error;
+}
+
/**
* Getters
*/
@@ -305,6 +275,16 @@ const git_oid *git_reference_target(const git_reference *ref)
return &ref->target.oid;
}
+const git_oid *git_reference_target_peel(const git_reference *ref)
+{
+ assert(ref);
+
+ if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel))
+ return NULL;
+
+ return &ref->peel;
+}
+
const char *git_reference_symbolic_target(const git_reference *ref)
{
assert(ref);
@@ -327,23 +307,32 @@ static int reference__create(
git_refdb *refdb;
git_reference *ref = NULL;
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)
+ error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name);
+ if (error < 0)
+ return error;
+
+ error = git_repository_refdb__weakptr(&refdb, repo);
+ if (error < 0)
return error;
-
- if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL)
- return -1;
- if ((error = git_refdb_write(refdb, ref)) < 0) {
+ if (oid != NULL) {
+ assert(symbolic == NULL);
+ ref = git_reference__alloc(normalized, oid, NULL);
+ } else {
+ ref = git_reference__alloc_symbolic(normalized, symbolic);
+ }
+
+ GITERR_CHECK_ALLOC(ref);
+
+ if ((error = git_refdb_write(refdb, ref, force)) < 0) {
git_reference_free(ref);
return error;
}
-
+
if (ref_out == NULL)
git_reference_free(ref);
else
@@ -363,17 +352,17 @@ int git_reference_create(
int error = 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 (!git_odb_exists(odb, oid)) {
giterr_set(GITERR_REFERENCE,
"Target OID for the reference doesn't exist on the repository");
return -1;
}
-
+
return reference__create(ref_out, repo, name, oid, NULL, force);
}
@@ -388,7 +377,7 @@ int git_reference_symbolic_create(
int error = 0;
assert(repo && name && target);
-
+
if ((error = git_reference__normalize_name_lax(
normalized, sizeof(normalized), target)) < 0)
return error;
@@ -402,7 +391,7 @@ int git_reference_set_target(
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;
@@ -417,13 +406,13 @@ int git_reference_symbolic_set_target(
const char *target)
{
assert(out && ref && target);
-
+
if (ref->type != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_REFERENCE,
"Cannot set symbolic target on a direct reference");
return -1;
}
-
+
return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);
}
@@ -436,96 +425,174 @@ int git_reference_rename(
unsigned int normalization_flags;
char normalized[GIT_REFNAME_MAX];
bool should_head_be_updated = false;
- 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)
+ if ((error = git_reference_normalize_name(
+ normalized, sizeof(normalized), new_name, normalization_flags)) < 0)
return error;
- /*
- * Create the new reference.
- */
- if (ref->type == GIT_REF_OID) {
- oid = &ref->target.oid;
- symbolic = NULL;
- } else {
- oid = NULL;
- symbolic = ref->target.symbolic;
- }
-
- if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL)
- return -1;
-
/* Check if we have to update HEAD. */
if ((error = git_branch_is_head(ref)) < 0)
- goto on_error;
+ return error;
should_head_be_updated = (error > 0);
- /* 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;
-
+ if ((error = git_refdb_rename(out, ref->db, ref->name, new_name, force)) < 0)
+ return error;
+
/* 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) {
+ 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;
+ return error;
}
/* 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 < 0)
+ return reference_has_log;
+
if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
- goto on_error;
+ return error;
- *out = result;
+ return 0;
+}
- return error;
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
+{
+ switch (git_reference_type(ref)) {
+ case GIT_REF_OID:
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
+
+ case GIT_REF_SYMBOLIC:
+ return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
-rollback:
- git_refdb_write(ref->db, ref);
+ default:
+ giterr_set(GITERR_REFERENCE, "Invalid reference");
+ return -1;
+ }
+}
-on_error:
- git_reference_free(result);
+int git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ git_reference *ref;
+ int error;
+ if (git_reference_iterator_new(&iter, repo) < 0)
+ return -1;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ if (callback(ref, payload)) {
+ error = GIT_EUSER;
+ goto out;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+out:
+ git_reference_iterator_free(iter);
return error;
}
-int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
+int git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload)
{
- 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->db->repo,
- ref->target.symbolic, -1);
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if (git_reference_iterator_new(&iter, repo) < 0)
+ return -1;
+
+ while ((error = git_reference_next_name(&refname, iter)) == 0) {
+ if (callback(refname, payload)) {
+ error = GIT_EUSER;
+ goto out;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+out:
+ git_reference_iterator_free(iter);
+ return error;
}
-int git_reference_foreach(
+int git_reference_foreach_glob(
git_repository *repo,
- unsigned int list_flags,
- git_reference_foreach_cb callback,
+ const char *glob,
+ git_reference_foreach_name_cb callback,
void *payload)
{
+ git_reference_iterator *iter;
+ const char *refname;
+ int error;
+
+ if (git_reference_iterator_glob_new(&iter, repo, glob) < 0)
+ return -1;
+
+ while ((error = git_reference_next_name(&refname, iter)) == 0) {
+ if (callback(refname, payload)) {
+ error = GIT_EUSER;
+ goto out;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+out:
+ git_reference_iterator_free(iter);
+ return error;
+}
+
+int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
+{
git_refdb *refdb;
- git_repository_refdb__weakptr(&refdb, repo);
- return git_refdb_foreach(refdb, list_flags, callback, payload);
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, NULL);
+}
+
+int git_reference_iterator_glob_new(
+ git_reference_iterator **out, git_repository *repo, const char *glob)
+{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
+ return git_refdb_iterator(out, refdb, glob);
+}
+
+int git_reference_next(git_reference **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next(out, iter);
+}
+
+int git_reference_next_name(const char **out, git_reference_iterator *iter)
+{
+ return git_refdb_iterator_next_name(out, iter);
+}
+
+void git_reference_iterator_free(git_reference_iterator *iter)
+{
+ git_refdb_iterator_free(iter);
}
static int cb__reflist_add(const char *ref, void *data)
@@ -535,8 +602,7 @@ static int cb__reflist_add(const char *ref, void *data)
int git_reference_list(
git_strarray *array,
- git_repository *repo,
- unsigned int list_flags)
+ git_repository *repo)
{
git_vector ref_list;
@@ -548,8 +614,8 @@ int git_reference_list(
if (git_vector_init(&ref_list, 8, NULL) < 0)
return -1;
- if (git_reference_foreach(
- repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {
+ if (git_reference_foreach_name(
+ repo, &cb__reflist_add, (void *)&ref_list) < 0) {
git_vector_free(&ref_list);
return -1;
}
@@ -712,6 +778,7 @@ int git_reference__normalize_name(
goto cleanup;
if ((segments_count == 1 ) &&
+ !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
goto cleanup;
@@ -778,16 +845,20 @@ int git_reference__normalize_name_lax(
int git_reference_cmp(git_reference *ref1, git_reference *ref2)
{
+ git_ref_t type1, type2;
assert(ref1 && ref2);
+ type1 = git_reference_type(ref1);
+ type2 = git_reference_type(ref2);
+
/* let's put symbolic refs before OIDs */
- if (ref1->type != ref2->type)
- return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1;
+ if (type1 != type2)
+ return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1;
- if (ref1->type == GIT_REF_SYMBOLIC)
+ if (type1 == GIT_REF_SYMBOLIC)
return strcmp(ref1->target.symbolic, ref2->target.symbolic);
- return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);
+ return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
}
static int reference__update_terminal(
@@ -799,9 +870,11 @@ static int reference__update_terminal(
git_reference *ref;
int error = 0;
- if (nesting > MAX_NESTING_LEVEL)
+ if (nesting > MAX_NESTING_LEVEL) {
+ giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
return GIT_ENOTFOUND;
-
+ }
+
error = git_reference_lookup(&ref, repo, ref_name);
/* If we haven't found the reference at all, create a new reference. */
@@ -809,10 +882,10 @@ static int reference__update_terminal(
giterr_clear();
return git_reference_create(NULL, repo, ref_name, oid, 0);
}
-
+
if (error < 0)
return error;
-
+
/* If the ref is a symbolic reference, follow its target. */
if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
@@ -822,7 +895,7 @@ static int reference__update_terminal(
git_reference_free(ref);
error = git_reference_create(NULL, repo, ref_name, oid, 1);
}
-
+
return error;
}
@@ -839,24 +912,6 @@ int git_reference__update_terminal(
return reference__update_terminal(repo, ref_name, oid, 0);
}
-int git_reference_foreach_glob(
- git_repository *repo,
- const char *glob,
- unsigned int list_flags,
- int (*callback)(
- const char *reference_name,
- void *payload),
- void *payload)
-{
- git_refdb *refdb;
-
- assert(repo && glob && callback);
-
- git_repository_refdb__weakptr(&refdb, repo);
-
- return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload);
-}
-
int git_reference_has_log(
git_reference *ref)
{
@@ -905,15 +960,6 @@ static int peel_error(int error, git_reference *ref, const char* msg)
return error;
}
-static int reference_target(git_object **object, git_reference *ref)
-{
- const git_oid *oid;
-
- oid = git_reference_target(ref);
-
- return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY);
-}
-
int git_reference_peel(
git_object **peeled,
git_reference *ref,
@@ -925,10 +971,22 @@ int git_reference_peel(
assert(ref);
- if ((error = git_reference_resolve(&resolved, ref)) < 0)
- return peel_error(error, ref, "Cannot resolve reference");
+ if (ref->type == GIT_REF_OID) {
+ resolved = ref;
+ } else {
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+ }
- if ((error = reference_target(&target, resolved)) < 0) {
+ if (!git_oid_iszero(&resolved->peel)) {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
+ } else {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
+ }
+
+ if (error < 0) {
peel_error(error, ref, "Cannot retrieve reference target");
goto cleanup;
}
@@ -940,7 +998,10 @@ int git_reference_peel(
cleanup:
git_object_free(target);
- git_reference_free(resolved);
+
+ if (resolved != ref)
+ git_reference_free(resolved);
+
return error;
}
@@ -963,3 +1024,20 @@ int git_reference_is_valid_name(
refname,
GIT_REF_FORMAT_ALLOW_ONELEVEL);
}
+
+const char *git_reference_shorthand(git_reference *ref)
+{
+ const char *name = ref->name;
+
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ return name + strlen(GIT_REFS_HEADS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return name + strlen(GIT_REFS_TAGS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
+ return name + strlen(GIT_REFS_REMOTES_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_DIR))
+ return name + strlen(GIT_REFS_DIR);
+
+ /* No shorthands are avaiable, so just return the name */
+ return name;
+}
diff --git a/src/refs.h b/src/refs.h
index 7d63c3fbd..f487ee3fc 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -13,6 +13,7 @@
#include "git2/refdb.h"
#include "strmap.h"
#include "buffer.h"
+#include "oid.h"
#define GIT_REFS_DIR "refs/"
#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
@@ -25,7 +26,7 @@
#define GIT_SYMREF "ref: "
#define GIT_PACKEDREFS_FILE "packed-refs"
-#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled "
+#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled "
#define GIT_PACKEDREFS_FILE_MODE 0666
#define GIT_HEAD_FILE "HEAD"
@@ -49,14 +50,14 @@
struct git_reference {
git_refdb *db;
-
git_ref_t type;
union {
git_oid oid;
char *symbolic;
} target;
-
+
+ git_oid peel;
char name[0];
};
diff --git a/src/refspec.c b/src/refspec.c
index a51b0cfab..492c6ed3f 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -25,6 +25,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
assert(refspec && input);
memset(refspec, 0x0, sizeof(git_refspec));
+ refspec->push = !is_fetch;
lhs = input;
if (*lhs == '+') {
@@ -59,7 +60,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
refspec->pattern = is_glob;
refspec->src = git__strndup(lhs, llen);
- flags = GIT_REF_FORMAT_ALLOW_ONELEVEL
+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL | GIT_REF_FORMAT_REFSPEC_SHORTHAND
| (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
if (is_fetch) {
@@ -119,6 +120,9 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
}
}
+ refspec->string = git__strdup(input);
+ GITERR_CHECK_ALLOC(refspec->string);
+
return 0;
invalid:
@@ -132,6 +136,7 @@ void git_refspec__free(git_refspec *refspec)
git__free(refspec->src);
git__free(refspec->dst);
+ git__free(refspec->string);
}
const char *git_refspec_src(const git_refspec *refspec)
@@ -144,6 +149,11 @@ const char *git_refspec_dst(const git_refspec *refspec)
return refspec == NULL ? NULL : refspec->dst;
}
+const char *git_refspec_string(const git_refspec *refspec)
+{
+ return refspec == NULL ? NULL : refspec->string;
+}
+
int git_refspec_force(const git_refspec *refspec)
{
assert(refspec);
@@ -215,25 +225,31 @@ int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, co
return refspec_transform_internal(out, outlen, spec->dst, spec->src, name);
}
-static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name)
+static int refspec_transform(
+ git_buf *out, const char *from, const char *to, const char *name)
{
- if (git_buf_sets(out, to) < 0)
- return -1;
+ size_t to_len = to ? strlen(to) : 0;
+ size_t from_len = from ? strlen(from) : 0;
+ size_t name_len = name ? strlen(name) : 0;
- /*
- * No '*' at the end means that it's mapped to one specific
- * branch, so no actual transformation is needed.
- */
- if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*')
- return 0;
+ if (git_buf_set(out, to, to_len) < 0)
+ return -1;
- git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */
- git_buf_puts(out, name + strlen(from) - 1);
+ if (to_len > 0) {
+ /* No '*' at the end of 'to' means that refspec is mapped to one
+ * specific branch, so no actual transformation is needed.
+ */
+ if (out->ptr[to_len - 1] != '*')
+ return 0;
+ git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */
+ }
- if (git_buf_oom(out))
- return -1;
+ if (from_len > 0) /* ignore trailing '*' from 'from' */
+ from_len--;
+ if (from_len > name_len)
+ from_len = name_len;
- return 0;
+ return git_buf_put(out, name + from_len, name_len - from_len);
}
int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)
@@ -264,3 +280,10 @@ int git_refspec_is_wildcard(const git_refspec *spec)
return (spec->src[strlen(spec->src) - 1] == '*');
}
+
+git_direction git_refspec_direction(const git_refspec *spec)
+{
+ assert(spec);
+
+ return spec->push;
+}
diff --git a/src/refspec.h b/src/refspec.h
index a7a4dd834..44d484c7b 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -11,11 +11,13 @@
#include "buffer.h"
struct git_refspec {
- struct git_refspec *next;
+ char *string;
char *src;
char *dst;
unsigned int force :1,
+ push : 1,
pattern :1,
+ dwim :1,
matching :1;
};
diff --git a/src/remote.c b/src/remote.c
index 896361e30..158f3e938 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -8,6 +8,7 @@
#include "git2/config.h"
#include "git2/types.h"
#include "git2/oid.h"
+#include "git2/net.h"
#include "config.h"
#include "repository.h"
@@ -19,15 +20,26 @@
#include <regex.h>
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch)
+static int add_refspec(git_remote *remote, const char *string, bool is_fetch)
{
- int error;
- const char *val;
+ git_refspec *spec;
- if ((error = git_config_get_string(&val, cfg, var)) < 0)
- return error;
+ spec = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(spec);
+
+ if (git_refspec__parse(spec, string, is_fetch) < 0) {
+ git__free(spec);
+ return -1;
+ }
- return git_refspec__parse(refspec, val, is_fetch);
+ spec->push = !is_fetch;
+ if (git_vector_insert(&remote->refspecs, spec) < 0) {
+ git_refspec__free(spec);
+ git__free(spec);
+ return -1;
+ }
+
+ return 0;
}
static int download_tags_value(git_remote *remote, git_config *cfg)
@@ -36,11 +48,7 @@ static int download_tags_value(git_remote *remote, git_config *cfg)
git_buf buf = GIT_BUF_INIT;
int error;
- if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET)
- return 0;
-
- /* This is the default, let's see if we need to change it */
- remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
+ /* The 0 value is the default (auto), let's see if we need to change it */
if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
return -1;
@@ -51,8 +59,10 @@ static int download_tags_value(git_remote *remote, git_config *cfg)
else if (!error && !strcmp(val, "--tags"))
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
error = 0;
+ }
return error;
}
@@ -99,14 +109,13 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
}
if (fetch != NULL) {
- if (git_refspec__parse(&remote->fetch, fetch, true) < 0)
+ if (add_refspec(remote, fetch, true) < 0)
goto on_error;
}
- /* A remote without a name doesn't download tags */
- if (!name) {
+ if (!name)
+ /* A remote without a name doesn't download tags */
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
- }
*out = remote;
git_buf_free(&fetchbuf);
@@ -186,6 +195,43 @@ int git_remote_create_inmemory(git_remote **out, git_repository *repo, const cha
return 0;
}
+struct refspec_cb_data {
+ git_remote *remote;
+ int fetch;
+};
+
+static int refspec_cb(const git_config_entry *entry, void *payload)
+{
+ const struct refspec_cb_data *data = (struct refspec_cb_data *)payload;
+
+ return add_refspec(data->remote, entry->value, data->fetch);
+}
+
+static int get_optional_config(
+ git_config *config, git_buf *buf, git_config_foreach_cb cb, void *payload)
+{
+ int error = 0;
+ const char *key = git_buf_cstr(buf);
+
+ if (git_buf_oom(buf))
+ return -1;
+
+ if (cb != NULL)
+ error = git_config_get_multivar(config, key, NULL, cb, payload);
+ else
+ error = git_config_get_string(payload, config, key);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ if (error < 0)
+ error = -1;
+
+ return error;
+}
+
int git_remote_load(git_remote **out, git_repository *repo, const char *name)
{
git_remote *remote;
@@ -193,6 +239,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
const char *val;
int error = 0;
git_config *config;
+ struct refspec_cb_data data;
assert(out && repo && name);
@@ -211,7 +258,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
- if (git_vector_init(&remote->refs, 32, NULL) < 0) {
+ if ((git_vector_init(&remote->refs, 32, NULL) < 0) ||
+ (git_vector_init(&remote->refspecs, 2, NULL))) {
error = -1;
goto cleanup;
}
@@ -223,7 +271,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)
goto cleanup;
-
+
if (strlen(val) == 0) {
giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name);
error = -1;
@@ -234,57 +282,32 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
remote->url = git__strdup(val);
GITERR_CHECK_ALLOC(remote->url);
+ val = NULL;
git_buf_clear(&buf);
- if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) {
- error = -1;
- goto cleanup;
- }
-
- error = git_config_get_string(&val, config, git_buf_cstr(&buf));
- if (error == GIT_ENOTFOUND) {
- val = NULL;
- error = 0;
- }
+ git_buf_printf(&buf, "remote.%s.pushurl", name);
- if (error < 0) {
- error = -1;
+ if ((error = get_optional_config(config, &buf, NULL, (void *)&val)) < 0)
goto cleanup;
- }
if (val) {
remote->pushurl = git__strdup(val);
GITERR_CHECK_ALLOC(remote->pushurl);
}
+ data.remote = remote;
+ data.fetch = true;
git_buf_clear(&buf);
- if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) {
- error = -1;
- goto cleanup;
- }
+ git_buf_printf(&buf, "remote.%s.fetch", name);
- error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);
- if (error == GIT_ENOTFOUND)
- error = 0;
-
- if (error < 0) {
- error = -1;
+ if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0)
goto cleanup;
- }
+ data.fetch = false;
git_buf_clear(&buf);
- if (git_buf_printf(&buf, "remote.%s.push", name) < 0) {
- error = -1;
- goto cleanup;
- }
+ git_buf_printf(&buf, "remote.%s.push", name);
- error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);
- if (error == GIT_ENOTFOUND)
- error = 0;
-
- if (error < 0) {
- error = -1;
+ if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0)
goto cleanup;
- }
if (download_tags_value(remote, config) < 0)
goto cleanup;
@@ -300,36 +323,44 @@ cleanup:
return error;
}
-static int update_config_refspec(
- git_config *config,
- const char *remote_name,
- const git_refspec *refspec,
- int git_direction)
+static int update_config_refspec(const git_remote *remote, git_config *config, int direction)
{
- git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT;
- int error = -1;
+ git_buf name = GIT_BUF_INIT;
+ int push;
+ const char *dir;
+ size_t i;
+ int error = 0;
- if (refspec->src == NULL || refspec->dst == NULL)
- return 0;
+ push = direction == GIT_DIRECTION_PUSH;
+ dir = push ? "push" : "fetch";
- if (git_buf_printf(
- &name,
- "remote.%s.%s",
- remote_name,
- git_direction == GIT_DIRECTION_FETCH ? "fetch" : "push") < 0)
- goto cleanup;
+ if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0)
+ return -1;
- if (git_refspec__serialize(&value, refspec) < 0)
- goto cleanup;
+ /* Clear out the existing config */
+ while (!error)
+ error = git_config_delete_entry(config, git_buf_cstr(&name));
- error = git_config_set_string(
- config,
- git_buf_cstr(&name),
- git_buf_cstr(&value));
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ for (i = 0; i < remote->refspecs.length; i++) {
+ git_refspec *spec = git_vector_get(&remote->refspecs, i);
+
+ if (spec->push != push)
+ continue;
+
+ if ((error = git_config_set_multivar(
+ config, git_buf_cstr(&name), "", spec->string)) < 0) {
+ goto cleanup;
+ }
+ }
+
+ giterr_clear();
+ error = 0;
cleanup:
git_buf_free(&name);
- git_buf_free(&value);
return error;
}
@@ -383,19 +414,11 @@ int git_remote_save(const git_remote *remote)
}
}
- if (update_config_refspec(
- config,
- remote->name,
- &remote->fetch,
- GIT_DIRECTION_FETCH) < 0)
- goto on_error;
+ if (update_config_refspec(remote, config, GIT_DIRECTION_FETCH) < 0)
+ goto on_error;
- if (update_config_refspec(
- config,
- remote->name,
- &remote->push,
- GIT_DIRECTION_PUSH) < 0)
- goto on_error;
+ if (update_config_refspec(remote, config, GIT_DIRECTION_PUSH) < 0)
+ goto on_error;
/*
* What action to take depends on the old and new values. This
@@ -444,6 +467,12 @@ const char *git_remote_name(const git_remote *remote)
return remote->name;
}
+git_repository *git_remote_owner(const git_remote *remote)
+{
+ assert(remote);
+ return remote->repo;
+}
+
const char *git_remote_url(const git_remote *remote)
{
assert(remote);
@@ -482,49 +511,6 @@ int git_remote_set_pushurl(git_remote *remote, const char* url)
return 0;
}
-int git_remote_set_fetchspec(git_remote *remote, const char *spec)
-{
- git_refspec refspec;
-
- assert(remote && spec);
-
- if (git_refspec__parse(&refspec, spec, true) < 0)
- return -1;
-
- git_refspec__free(&remote->fetch);
- memcpy(&remote->fetch, &refspec, sizeof(git_refspec));
-
- return 0;
-}
-
-const git_refspec *git_remote_fetchspec(const git_remote *remote)
-{
- assert(remote);
- return &remote->fetch;
-}
-
-int git_remote_set_pushspec(git_remote *remote, const char *spec)
-{
- git_refspec refspec;
-
- assert(remote && spec);
-
- if (git_refspec__parse(&refspec, spec, false) < 0)
- return -1;
-
- git_refspec__free(&remote->push);
- remote->push.src = refspec.src;
- remote->push.dst = refspec.dst;
-
- return 0;
-}
-
-const git_refspec *git_remote_pushspec(const git_remote *remote)
-{
- assert(remote);
- return &remote->push;
-}
-
const char* git_remote__urlfordirection(git_remote *remote, int direction)
{
assert(remote);
@@ -586,11 +572,6 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
assert(remote);
- if (!git_remote_connected(remote)) {
- giterr_set(GITERR_NET, "The remote is not connected");
- return -1;
- }
-
return remote->transport->ls(remote->transport, list_cb, payload);
}
@@ -651,28 +632,105 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur
return 0;
}
+static int store_refs(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *)payload;
+
+ return git_vector_insert(refs, head);
+}
+
+static int dwim_refspecs(git_vector *refspecs, git_vector *refs)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec *spec;
+ size_t i, j, pos;
+ git_remote_head key;
+
+ const char* formatters[] = {
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ NULL
+ };
+
+ git_vector_foreach(refspecs, i, spec) {
+ if (spec->dwim)
+ continue;
+
+ /* shorthand on the lhs */
+ if (git__prefixcmp(spec->src, GIT_REFS_DIR)) {
+ for (j = 0; formatters[j]; j++) {
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, formatters[j], spec->src) < 0)
+ return -1;
+
+ key.name = (char *) git_buf_cstr(&buf);
+ if (!git_vector_search(&pos, refs, &key)) {
+ /* we found something to match the shorthand, set src to that */
+ git__free(spec->src);
+ spec->src = git_buf_detach(&buf);
+ }
+ }
+ }
+
+ if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) {
+ /* if it starts with "remotes" then we just prepend "refs/" */
+ if (!git__prefixcmp(spec->dst, "remotes/")) {
+ git_buf_puts(&buf, GIT_REFS_DIR);
+ } else {
+ git_buf_puts(&buf, GIT_REFS_HEADS_DIR);
+ }
+
+ if (git_buf_puts(&buf, spec->dst) < 0)
+ return -1;
+
+ git__free(spec->dst);
+ spec->dst = git_buf_detach(&buf);
+ }
+
+ spec->dwim = 1;
+ }
+
+ git_buf_free(&buf);
+ return 0;
+}
+
+static int remote_head_cmp(const void *_a, const void *_b)
+{
+ const git_remote_head *a = (git_remote_head *) _a;
+ const git_remote_head *b = (git_remote_head *) _b;
+
+ return git__strcmp_cb(a->name, b->name);
+}
+
int git_remote_download(
git_remote *remote,
git_transfer_progress_callback progress_cb,
void *progress_payload)
{
int error;
+ git_vector refs;
assert(remote);
+ if (git_vector_init(&refs, 16, remote_head_cmp) < 0)
+ return -1;
+
+ if (git_remote_ls(remote, store_refs, &refs) < 0) {
+ return -1;
+ }
+
+ error = dwim_refspecs(&remote->refspecs, &refs);
+ git_vector_free(&refs);
+ if (error < 0)
+ return -1;
+
if ((error = git_fetch_negotiate(remote)) < 0)
return error;
return git_fetch_download_pack(remote, progress_cb, progress_payload);
}
-static int update_tips_callback(git_remote_head *head, void *payload)
-{
- git_vector *refs = (git_vector *)payload;
-
- return git_vector_insert(refs, head);
-}
-
static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
{
unsigned int i;
@@ -692,21 +750,21 @@ static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *upda
return 0;
}
-static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
+static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, git_vector *update_heads, git_reference *ref)
{
git_reference *resolved_ref = NULL;
git_reference *tracking_ref = NULL;
git_buf remote_name = GIT_BUF_INIT;
int error = 0;
- assert(out && remote && ref);
+ assert(out && spec && ref);
*out = NULL;
if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
(!git_reference_is_branch(resolved_ref)) ||
(error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 ||
- (error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {
+ (error = git_refspec_transform_l(&remote_name, spec, git_reference_name(tracking_ref))) < 0) {
/* Not an error if HEAD is orphaned or no tracking branch */
if (error == GIT_ENOTFOUND)
error = 0;
@@ -723,9 +781,8 @@ cleanup:
return error;
}
-static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads)
+static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads)
{
- struct git_refspec *spec;
git_reference *head_ref = NULL;
git_fetchhead_ref *fetchhead_ref;
git_remote_head *remote_ref, *merge_remote_ref;
@@ -736,7 +793,9 @@ static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_hea
assert(remote);
- spec = &remote->fetch;
+ /* no heads, nothing to do */
+ if (update_heads->length == 0)
+ return 0;
if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
return -1;
@@ -747,7 +806,7 @@ static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_hea
/* Determine what to merge: if refspec was a wildcard, just use HEAD */
if (git_refspec_is_wildcard(spec)) {
if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||
- (error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0)
+ (error = remote_head_for_ref(&merge_remote_ref, spec, update_heads, head_ref)) < 0)
goto cleanup;
} else {
/* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
@@ -787,7 +846,7 @@ cleanup:
return error;
}
-int git_remote_update_tips(git_remote *remote)
+static int update_tips_for_spec(git_remote *remote, git_refspec *spec, git_vector *refs)
{
int error = 0, autotag;
unsigned int i = 0;
@@ -796,14 +855,11 @@ int git_remote_update_tips(git_remote *remote)
git_odb *odb;
git_remote_head *head;
git_reference *ref;
- struct git_refspec *spec;
git_refspec tagspec;
- git_vector refs, update_heads;
+ git_vector update_heads;
assert(remote);
- spec = &remote->fetch;
-
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
return -1;
@@ -811,35 +867,18 @@ int git_remote_update_tips(git_remote *remote)
return -1;
/* Make a copy of the transport's refs */
- if (git_vector_init(&refs, 16, NULL) < 0 ||
- git_vector_init(&update_heads, 16, NULL) < 0)
+ if (git_vector_init(&update_heads, 16, NULL) < 0)
return -1;
- if (git_remote_ls(remote, update_tips_callback, &refs) < 0)
- goto on_error;
-
- /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
- if (refs.length > 0) {
- head = (git_remote_head *)refs.contents[0];
-
- if (!strcmp(head->name, GIT_HEAD_FILE)) {
- if (git_reference_create(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
- goto on_error;
-
- i = 1;
- git_reference_free(ref);
- }
- }
-
- for (; i < refs.length; ++i) {
- head = (git_remote_head *)refs.contents[i];
+ for (; i < refs->length; ++i) {
+ head = git_vector_get(refs, i);
autotag = 0;
/* Ignore malformed ref names (which also saves us from tag^{} */
if (!git_reference_is_valid_name(head->name))
continue;
- if (git_refspec_src_matches(spec, head->name)) {
+ if (git_refspec_src_matches(spec, head->name) && spec->dst) {
if (git_refspec_transform_r(&refname, spec, head->name) < 0)
goto on_error;
} else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
@@ -870,7 +909,7 @@ int git_remote_update_tips(git_remote *remote)
if (error == GIT_ENOTFOUND)
memset(&old, 0, GIT_OID_RAWSZ);
- if (!git_oid_cmp(&old, &head->oid))
+ if (!git_oid__cmp(&old, &head->oid))
continue;
/* In autotag mode, don't overwrite any locally-existing tags */
@@ -887,17 +926,15 @@ int git_remote_update_tips(git_remote *remote)
}
if (git_remote_update_fetchhead(remote) &&
- (error = git_remote_write_fetchhead(remote, &update_heads)) < 0)
+ (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0)
goto on_error;
- git_vector_free(&refs);
git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return 0;
on_error:
- git_vector_free(&refs);
git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
@@ -905,6 +942,42 @@ on_error:
}
+int git_remote_update_tips(git_remote *remote)
+{
+ git_refspec *spec, tagspec;
+ git_vector refs;
+ int error;
+ size_t i;
+
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+ if (git_vector_init(&refs, 16, NULL) < 0)
+ return -1;
+
+ if ((error = git_remote_ls(remote, store_refs, &refs)) < 0)
+ goto out;
+
+ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ error = update_tips_for_spec(remote, &tagspec, &refs);
+ goto out;
+ }
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if ((error = update_tips_for_spec(remote, spec, &refs)) < 0)
+ goto out;
+ }
+
+out:
+ git_refspec__free(&tagspec);
+ git_vector_free(&refs);
+ return error;
+}
+
int git_remote_connected(git_remote *remote)
{
assert(remote);
@@ -934,6 +1007,9 @@ void git_remote_disconnect(git_remote *remote)
void git_remote_free(git_remote *remote)
{
+ git_refspec *spec;
+ size_t i;
+
if (remote == NULL)
return;
@@ -946,8 +1022,12 @@ void git_remote_free(git_remote *remote)
git_vector_free(&remote->refs);
- git_refspec__free(&remote->fetch);
- git_refspec__free(&remote->push);
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+ git_vector_free(&remote->refspecs);
+
git__free(remote->url);
git__free(remote->pushurl);
git__free(remote->name);
@@ -1158,40 +1238,24 @@ static int update_branch_remote_config_entry(
update_config_entries_cb, &data);
}
-static int rename_cb(const char *ref, void *data)
-{
- if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR))
- return 0;
-
- return git_vector_insert((git_vector *)data, git__strdup(ref));
-}
-
static int rename_one_remote_reference(
- git_repository *repo,
- const char *reference_name,
+ git_reference *reference,
const char *old_remote_name,
const char *new_remote_name)
{
int error = -1;
git_buf new_name = GIT_BUF_INIT;
- git_reference *reference = NULL;
- git_reference *newref = NULL;
if (git_buf_printf(
&new_name,
GIT_REFS_REMOTES_DIR "%s%s",
new_remote_name,
- reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)
+ reference->name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)
return -1;
- if (git_reference_lookup(&reference, repo, reference_name) < 0)
- goto cleanup;
-
- error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0);
+ error = git_reference_rename(NULL, reference, git_buf_cstr(&new_name), 0);
git_reference_free(reference);
-cleanup:
- git_reference_free(newref);
git_buf_free(&new_name);
return error;
}
@@ -1201,33 +1265,30 @@ static int rename_remote_references(
const char *old_name,
const char *new_name)
{
- git_vector refnames;
int error = -1;
- unsigned int i;
- char *name;
+ git_reference *ref;
+ git_reference_iterator *iter;
- if (git_vector_init(&refnames, 8, NULL) < 0)
- goto cleanup;
+ if (git_reference_iterator_new(&iter, repo) < 0)
+ return -1;
- if (git_reference_foreach(
- repo,
- GIT_REF_LISTALL,
- rename_cb,
- &refnames) < 0)
- goto cleanup;
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ if (git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
+ git_reference_free(ref);
+ continue;
+ }
- git_vector_foreach(&refnames, i, name) {
- if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0)
- goto cleanup;
+ if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) {
+ git_reference_iterator_free(iter);
+ return error;
+ }
}
- error = 0;
-cleanup:
- git_vector_foreach(&refnames, i, name) {
- git__free(name);
- }
+ git_reference_iterator_free(iter);
+
+ if (error == GIT_ITEROVER)
+ return 0;
- git_vector_free(&refnames);
return error;
}
@@ -1238,58 +1299,58 @@ static int rename_fetch_refspecs(
void *payload)
{
git_config *config;
- const git_refspec *fetch_refspec;
- git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT;
- const char* pos;
+ git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT;
+ const git_refspec *spec;
+ size_t i;
int error = -1;
- fetch_refspec = git_remote_fetchspec(remote);
-
- /* Is there a refspec to deal with? */
- if (fetch_refspec->src == NULL &&
- fetch_refspec->dst == NULL)
- return 0;
-
- if (git_refspec__serialize(&serialized, fetch_refspec) < 0)
+ if (git_buf_printf(&base, "+refs/heads/*:refs/remotes/%s/*", remote->name) < 0)
goto cleanup;
- /* Is it an in-memory remote? */
- if (!remote->name) {
- error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
- goto cleanup;
- }
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
- if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0)
- goto cleanup;
+ /* Every refspec is a problem refspec for an in-memory remote */
+ if (!remote->name) {
+ if (callback(spec->string, payload) < 0) {
+ error = GIT_EUSER;
+ goto cleanup;
+ }
- pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix));
+ continue;
+ }
- /* Does the dst part of the refspec follow the extected standard format? */
- if (!pos) {
- error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
- goto cleanup;
- }
+ /* Does the dst part of the refspec follow the extected standard format? */
+ if (strcmp(git_buf_cstr(&base), spec->string)) {
+ if (callback(spec->string, payload) < 0) {
+ error = GIT_EUSER;
+ goto cleanup;
+ }
+
+ continue;
+ }
- if (git_buf_splice(
- &serialized,
- pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"),
- strlen(remote->name), new_name,
- strlen(new_name)) < 0)
+ /* If we do want to move it to the new section */
+ if (git_buf_printf(&val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0)
goto cleanup;
- git_refspec__free(&remote->fetch);
+ if (git_buf_printf(&var, "remote.%s.fetch", new_name) < 0)
+ goto cleanup;
- if (git_refspec__parse(&remote->fetch, git_buf_cstr(&serialized), true) < 0)
- goto cleanup;
+ if (git_repository_config__weakptr(&config, remote->repo) < 0)
+ goto cleanup;
- if (git_repository_config__weakptr(&config, remote->repo) < 0)
- goto cleanup;
+ if (git_config_set_string(config, git_buf_cstr(&var), git_buf_cstr(&val)) < 0)
+ goto cleanup;
+ }
- error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIRECTION_FETCH);
+ error = 0;
cleanup:
- git_buf_free(&serialized);
- git_buf_free(&dst_prefix);
+ git_buf_free(&base);
+ git_buf_free(&var);
+ git_buf_free(&val);
return error;
}
@@ -1390,3 +1451,128 @@ int git_remote_is_valid_name(
giterr_clear();
return error == 0;
}
+
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_src_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_dst_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+void git_remote_clear_refspecs(git_remote *remote)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+ git_vector_clear(&remote->refspecs);
+}
+
+int git_remote_add_fetch(git_remote *remote, const char *refspec)
+{
+ return add_refspec(remote, refspec, true);
+}
+
+int git_remote_add_push(git_remote *remote, const char *refspec)
+{
+ return add_refspec(remote, refspec, false);
+}
+
+static int copy_refspecs(git_strarray *array, git_remote *remote, int push)
+{
+ size_t i;
+ git_vector refspecs;
+ git_refspec *spec;
+ char *dup;
+
+ if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0)
+ return -1;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push != push)
+ continue;
+
+ if ((dup = git__strdup(spec->string)) == NULL)
+ goto on_error;
+
+ if (git_vector_insert(&refspecs, dup) < 0) {
+ git__free(dup);
+ goto on_error;
+ }
+ }
+
+ array->strings = (char **)refspecs.contents;
+ array->count = refspecs.length;
+
+ return 0;
+
+on_error:
+ git_vector_foreach(&refspecs, i, dup)
+ git__free(dup);
+ git_vector_free(&refspecs);
+
+ return -1;
+}
+
+int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote)
+{
+ return copy_refspecs(array, remote, false);
+}
+
+int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote)
+{
+ return copy_refspecs(array, remote, true);
+}
+
+size_t git_remote_refspec_count(git_remote *remote)
+{
+ return remote->refspecs.length;
+}
+
+const git_refspec *git_remote_get_refspec(git_remote *remote, size_t n)
+{
+ return git_vector_get(&remote->refspecs, n);
+}
+
+int git_remote_remove_refspec(git_remote *remote, size_t n)
+{
+ git_refspec *spec;
+
+ assert(remote);
+
+ spec = git_vector_get(&remote->refspecs, n);
+ if (spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+
+ return git_vector_remove(&remote->refspecs, n);
+}
diff --git a/src/remote.h b/src/remote.h
index 4c1a18aa7..dce4803ed 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -11,7 +11,7 @@
#include "git2/transport.h"
#include "refspec.h"
-#include "repository.h"
+#include "vector.h"
#define GIT_REMOTE_ORIGIN "origin"
@@ -20,8 +20,7 @@ struct git_remote {
char *url;
char *pushurl;
git_vector refs;
- struct git_refspec fetch;
- struct git_refspec push;
+ git_vector refspecs;
git_cred_acquire_cb cred_acquire_cb;
void *cred_acquire_payload;
git_transport *transport;
@@ -37,4 +36,7 @@ struct git_remote {
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url);
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname);
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname);
+
#endif
diff --git a/src/repository.c b/src/repository.c
index 0ad7449ba..99ac56ef9 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -9,6 +9,7 @@
#include "git2/object.h"
#include "git2/refdb.h"
+#include "git2/sys/repository.h"
#include "common.h"
#include "repository.h"
@@ -16,12 +17,15 @@
#include "tag.h"
#include "blob.h"
#include "fileops.h"
+#include "filebuf.h"
+#include "index.h"
#include "config.h"
#include "refs.h"
#include "filter.h"
#include "odb.h"
#include "remote.h"
#include "merge.h"
+#include "diff_driver.h"
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
@@ -31,61 +35,91 @@
#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
-static void drop_odb(git_repository *repo)
+static void set_odb(git_repository *repo, git_odb *odb)
{
- if (repo->_odb != NULL) {
- GIT_REFCOUNT_OWN(repo->_odb, NULL);
- git_odb_free(repo->_odb);
- repo->_odb = NULL;
+ if (odb) {
+ GIT_REFCOUNT_OWN(odb, repo);
+ GIT_REFCOUNT_INC(odb);
+ }
+
+ if ((odb = git__swap(repo->_odb, odb)) != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
}
}
-static void drop_refdb(git_repository *repo)
+static void set_refdb(git_repository *repo, git_refdb *refdb)
{
- if (repo->_refdb != NULL) {
- GIT_REFCOUNT_OWN(repo->_refdb, NULL);
- git_refdb_free(repo->_refdb);
- repo->_refdb = NULL;
+ if (refdb) {
+ GIT_REFCOUNT_OWN(refdb, repo);
+ GIT_REFCOUNT_INC(refdb);
+ }
+
+ if ((refdb = git__swap(repo->_refdb, refdb)) != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
}
}
-static void drop_config(git_repository *repo)
+static void set_config(git_repository *repo, git_config *config)
{
- if (repo->_config != NULL) {
- GIT_REFCOUNT_OWN(repo->_config, NULL);
- git_config_free(repo->_config);
- repo->_config = NULL;
+ if (config) {
+ GIT_REFCOUNT_OWN(config, repo);
+ GIT_REFCOUNT_INC(config);
+ }
+
+ if ((config = git__swap(repo->_config, config)) != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
}
git_repository__cvar_cache_clear(repo);
}
-static void drop_index(git_repository *repo)
+static void set_index(git_repository *repo, git_index *index)
{
- if (repo->_index != NULL) {
- GIT_REFCOUNT_OWN(repo->_index, NULL);
- git_index_free(repo->_index);
- repo->_index = NULL;
+ if (index) {
+ GIT_REFCOUNT_OWN(index, repo);
+ GIT_REFCOUNT_INC(index);
+ }
+
+ if ((index = git__swap(repo->_index, index)) != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
}
}
+void git_repository__cleanup(git_repository *repo)
+{
+ assert(repo);
+
+ git_cache_clear(&repo->objects);
+ git_attr_cache_flush(repo);
+
+ set_config(repo, NULL);
+ set_index(repo, NULL);
+ set_odb(repo, NULL);
+ set_refdb(repo, NULL);
+}
+
void git_repository_free(git_repository *repo)
{
if (repo == NULL)
return;
+ git_repository__cleanup(repo);
+
git_cache_free(&repo->objects);
- git_attr_cache_flush(repo);
git_submodule_config_free(repo);
+ git_diff_driver_registry_free(repo->diff_drivers);
+ repo->diff_drivers = NULL;
+
git__free(repo->path_repository);
git__free(repo->workdir);
+ git__free(repo->namespace);
- drop_config(repo);
- drop_index(repo);
- drop_odb(repo);
- drop_refdb(repo);
-
+ git__memzero(repo, sizeof(*repo));
git__free(repo);
}
@@ -112,13 +146,11 @@ static bool valid_repository_path(git_buf *repository_path)
static git_repository *repository_alloc(void)
{
- git_repository *repo = git__malloc(sizeof(git_repository));
+ git_repository *repo = git__calloc(1, sizeof(git_repository));
if (!repo)
return NULL;
- memset(repo, 0x0, sizeof(git_repository));
-
- if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) {
+ if (git_cache_init(&repo->objects) < 0) {
git__free(repo);
return NULL;
}
@@ -129,6 +161,12 @@ static git_repository *repository_alloc(void)
return repo;
}
+int git_repository_new(git_repository **out)
+{
+ *out = repository_alloc();
+ return 0;
+}
+
static int load_config_data(git_repository *repo)
{
int is_bare;
@@ -228,7 +266,7 @@ static int find_ceiling_dir_offset(
buf[--len] = '\0';
if (!strncmp(path, buf2, len) &&
- path[len] == '/' &&
+ (path[len] == '/' || !path[len]) &&
len > max_len)
{
max_len = len;
@@ -284,17 +322,18 @@ static int find_repo(
git_buf path = GIT_BUF_INIT;
struct stat st;
dev_t initial_device = 0;
- bool try_with_dot_git = false;
+ bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0);
int ceiling_offset;
git_buf_free(repo_path);
- if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0)
+ if ((error = git_path_prettify(&path, start_path, NULL)) < 0)
return error;
ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
- if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
+ if (!try_with_dot_git &&
+ (error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
return error;
while (!error && !git_buf_len(repo_path)) {
@@ -346,7 +385,7 @@ static int find_repo(
try_with_dot_git = !try_with_dot_git;
}
- if (!error && parent_path != NULL) {
+ if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
if (!git_buf_len(repo_path))
git_buf_clear(parent_path);
else {
@@ -368,6 +407,37 @@ static int find_repo(
return error;
}
+int git_repository_open_bare(
+ git_repository **repo_ptr,
+ const char *bare_path)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo = NULL;
+
+ if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0)
+ return error;
+
+ if (!valid_repository_path(&path)) {
+ git_buf_free(&path);
+ giterr_set(GITERR_REPOSITORY, "Path is not a repository: %s", bare_path);
+ return GIT_ENOTFOUND;
+ }
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ repo->path_repository = git_buf_detach(&path);
+ GITERR_CHECK_ALLOC(repo->path_repository);
+
+ /* of course we're bare! */
+ repo->is_bare = 1;
+ repo->workdir = NULL;
+
+ *repo_ptr = repo;
+ return 0;
+}
+
int git_repository_open_ext(
git_repository **repo_ptr,
const char *start_path,
@@ -391,7 +461,9 @@ int git_repository_open_ext(
repo->path_repository = git_buf_detach(&path);
GITERR_CHECK_ALLOC(repo->path_repository);
- if ((error = load_config_data(repo)) < 0 ||
+ if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
+ repo->is_bare = 1;
+ else if ((error = load_config_data(repo)) < 0 ||
(error = load_workdir(repo, &parent)) < 0)
{
git_repository_free(repo);
@@ -511,39 +583,51 @@ on_error:
return error;
}
-int git_repository_config__weakptr(git_config **out, git_repository *repo)
+static const char *path_unless_empty(git_buf *buf)
{
- if (repo->_config == NULL) {
- git_buf global_buf = GIT_BUF_INIT, xdg_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT;
- int res;
-
- const char *global_config_path = NULL;
- const char *xdg_config_path = NULL;
- const char *system_config_path = NULL;
-
- if (git_config_find_global_r(&global_buf) == 0)
- global_config_path = global_buf.ptr;
+ return git_buf_len(buf) > 0 ? git_buf_cstr(buf) : NULL;
+}
- if (git_config_find_xdg_r(&xdg_buf) == 0)
- xdg_config_path = xdg_buf.ptr;
+int git_repository_config__weakptr(git_config **out, git_repository *repo)
+{
+ int error = 0;
- if (git_config_find_system_r(&system_buf) == 0)
- system_config_path = system_buf.ptr;
+ if (repo->_config == NULL) {
+ git_buf global_buf = GIT_BUF_INIT;
+ git_buf xdg_buf = GIT_BUF_INIT;
+ git_buf system_buf = GIT_BUF_INIT;
+ git_config *config;
- res = load_config(&repo->_config, repo, global_config_path, xdg_config_path, system_config_path);
+ git_config_find_global_r(&global_buf);
+ git_config_find_xdg_r(&xdg_buf);
+ git_config_find_system_r(&system_buf);
+
+ /* If there is no global file, open a backend for it anyway */
+ if (git_buf_len(&global_buf) == 0)
+ git_config__global_location(&global_buf);
+
+ error = load_config(
+ &config, repo,
+ path_unless_empty(&global_buf),
+ path_unless_empty(&xdg_buf),
+ path_unless_empty(&system_buf));
+ if (!error) {
+ GIT_REFCOUNT_OWN(config, repo);
+
+ config = git__compare_and_swap(&repo->_config, NULL, config);
+ if (config != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
+ }
+ }
git_buf_free(&global_buf);
git_buf_free(&xdg_buf);
git_buf_free(&system_buf);
-
- if (res < 0)
- return -1;
-
- GIT_REFCOUNT_OWN(repo->_config, repo);
}
*out = repo->_config;
- return 0;
+ return error;
}
int git_repository_config(git_config **out, git_repository *repo)
@@ -558,36 +642,37 @@ int git_repository_config(git_config **out, git_repository *repo)
void git_repository_set_config(git_repository *repo, git_config *config)
{
assert(repo && config);
-
- drop_config(repo);
-
- repo->_config = config;
- GIT_REFCOUNT_OWN(repo->_config, repo);
- GIT_REFCOUNT_INC(repo->_config);
+ set_config(repo, config);
}
int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
{
+ int error = 0;
+
assert(repo && out);
if (repo->_odb == NULL) {
git_buf odb_path = GIT_BUF_INIT;
- int res;
+ git_odb *odb;
- if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0)
- return -1;
+ git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR);
- res = git_odb_open(&repo->_odb, odb_path.ptr);
- git_buf_free(&odb_path); /* done with path */
+ error = git_odb_open(&odb, odb_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(odb, repo);
- if (res < 0)
- return -1;
+ odb = git__compare_and_swap(&repo->_odb, NULL, odb);
+ if (odb != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
+ }
+ }
- GIT_REFCOUNT_OWN(repo->_odb, repo);
+ git_buf_free(&odb_path);
}
*out = repo->_odb;
- return 0;
+ return error;
}
int git_repository_odb(git_odb **out, git_repository *repo)
@@ -602,31 +687,32 @@ int git_repository_odb(git_odb **out, git_repository *repo)
void git_repository_set_odb(git_repository *repo, git_odb *odb)
{
assert(repo && odb);
-
- drop_odb(repo);
-
- repo->_odb = odb;
- GIT_REFCOUNT_OWN(repo->_odb, repo);
- GIT_REFCOUNT_INC(odb);
+ set_odb(repo, odb);
}
int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
{
+ int error = 0;
+
assert(out && repo);
if (repo->_refdb == NULL) {
- int res;
+ git_refdb *refdb;
- res = git_refdb_open(&repo->_refdb, repo);
+ error = git_refdb_open(&refdb, repo);
+ if (!error) {
+ GIT_REFCOUNT_OWN(refdb, repo);
- if (res < 0)
- return -1;
-
- GIT_REFCOUNT_OWN(repo->_refdb, repo);
+ refdb = git__compare_and_swap(&repo->_refdb, NULL, refdb);
+ if (refdb != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
+ }
+ }
}
*out = repo->_refdb;
- return 0;
+ return error;
}
int git_repository_refdb(git_refdb **out, git_repository *repo)
@@ -640,40 +726,40 @@ int git_repository_refdb(git_refdb **out, git_repository *repo)
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);
+ assert(repo && refdb);
+ set_refdb(repo, refdb);
}
int git_repository_index__weakptr(git_index **out, git_repository *repo)
{
+ int error = 0;
+
assert(out && repo);
if (repo->_index == NULL) {
- int res;
git_buf index_path = GIT_BUF_INIT;
+ git_index *index;
- if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0)
- return -1;
+ git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE);
- res = git_index_open(&repo->_index, index_path.ptr);
- git_buf_free(&index_path); /* done with path */
+ error = git_index_open(&index, index_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(index, repo);
- if (res < 0)
- return -1;
+ index = git__compare_and_swap(&repo->_index, NULL, index);
+ if (index != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
+ }
- GIT_REFCOUNT_OWN(repo->_index, repo);
+ error = git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER);
+ }
- if (git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER) < 0)
- return -1;
+ git_buf_free(&index_path);
}
*out = repo->_index;
- return 0;
+ return error;
}
int git_repository_index(git_index **out, git_repository *repo)
@@ -688,12 +774,24 @@ int git_repository_index(git_index **out, git_repository *repo)
void git_repository_set_index(git_repository *repo, git_index *index)
{
assert(repo && index);
+ set_index(repo, index);
+}
+
+int git_repository_set_namespace(git_repository *repo, const char *namespace)
+{
+ git__free(repo->namespace);
- drop_index(repo);
+ if (namespace == NULL) {
+ repo->namespace = NULL;
+ return 0;
+ }
- repo->_index = index;
- GIT_REFCOUNT_OWN(repo->_index, repo);
- GIT_REFCOUNT_INC(index);
+ return (repo->namespace = git__strdup(namespace)) ? 0 : -1;
+}
+
+const char *git_repository_get_namespace(git_repository *repo)
+{
+ return repo->namespace;
}
static int check_repositoryformatversion(git_config *config)
@@ -1383,14 +1481,15 @@ static int at_least_one_cb(const char *refname, void *payload)
static int repo_contains_no_reference(git_repository *repo)
{
- int error;
-
- error = git_reference_foreach(repo, GIT_REF_LISTALL, at_least_one_cb, NULL);
+ int error = git_reference_foreach_name(repo, &at_least_one_cb, NULL);
if (error == GIT_EUSER)
return 0;
- return error == 0 ? 1 : error;
+ if (!error)
+ return 1;
+
+ return error;
}
int git_repository_is_empty(git_repository *repo)
@@ -1401,12 +1500,12 @@ int git_repository_is_empty(git_repository *repo)
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
return -1;
- if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC))
+ if (!((error = git_reference_type(head)) == GIT_REF_SYMBOLIC))
goto cleanup;
- if (!(error = strcmp(
+ if (!(error = (strcmp(
git_reference_symbolic_target(head),
- GIT_REFS_HEADS_DIR "master") == 0))
+ GIT_REFS_HEADS_DIR "master") == 0)))
goto cleanup;
error = repo_contains_no_reference(repo);
@@ -1507,12 +1606,16 @@ int git_repository_message(char *buffer, size_t len, git_repository *repo)
struct stat st;
int error;
+ if (buffer != NULL)
+ *buffer = '\0';
+
if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
if (errno == ENOENT)
error = GIT_ENOTFOUND;
+ giterr_set(GITERR_OS, "Could not access message file");
}
else if (buffer != NULL) {
error = git_futils_readbuffer(&buf, git_buf_cstr(&path));
@@ -1543,11 +1646,11 @@ int git_repository_message_remove(git_repository *repo)
}
int git_repository_hashfile(
- git_oid *out,
- git_repository *repo,
- const char *path,
- git_otype type,
- const char *as_path)
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path)
{
int error;
git_vector filters = GIT_VECTOR_INIT;
@@ -1729,3 +1832,20 @@ int git_repository_state(git_repository *repo)
git_buf_free(&repo_path);
return state;
}
+
+int git_repository_is_shallow(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ git_buf_joinpath(&path, repo->path_repository, "shallow");
+ error = git_path_lstat(path.ptr, &st);
+ git_buf_free(&path);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+ if (error < 0)
+ return -1;
+ return st.st_size == 0 ? 0 : 1;
+}
diff --git a/src/repository.h b/src/repository.h
index cc2f8c2b8..12dc50d51 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -12,16 +12,15 @@
#include "git2/odb.h"
#include "git2/repository.h"
#include "git2/object.h"
+#include "git2/config.h"
-#include "index.h"
#include "cache.h"
#include "refs.h"
#include "buffer.h"
-#include "odb.h"
#include "object.h"
#include "attrcache.h"
#include "strmap.h"
-#include "refdb.h"
+#include "diff_driver.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
@@ -31,7 +30,13 @@
/** Cvar cache identifiers */
typedef enum {
GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */
- GIT_CVAR_EOL, /* core.eol */
+ GIT_CVAR_EOL, /* core.eol */
+ GIT_CVAR_SYMLINKS, /* core.symlinks */
+ GIT_CVAR_IGNORECASE, /* core.ignorecase */
+ GIT_CVAR_FILEMODE, /* core.filemode */
+ GIT_CVAR_IGNORESTAT, /* core.ignorestat */
+ GIT_CVAR_TRUSTCTIME, /* core.trustctime */
+ GIT_CVAR_ABBREV, /* core.abbrev */
GIT_CVAR_CACHE_MAX
} git_cvar_cached;
@@ -67,7 +72,21 @@ typedef enum {
#else
GIT_EOL_NATIVE = GIT_EOL_LF,
#endif
- GIT_EOL_DEFAULT = GIT_EOL_NATIVE
+ GIT_EOL_DEFAULT = GIT_EOL_NATIVE,
+
+ /* core.symlinks: bool */
+ GIT_SYMLINKS_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorecase */
+ GIT_IGNORECASE_DEFAULT = GIT_CVAR_FALSE,
+ /* core.filemode */
+ GIT_FILEMODE_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorestat */
+ GIT_IGNORESTAT_DEFAULT = GIT_CVAR_FALSE,
+ /* core.trustctime */
+ GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE,
+ /* core.abbrev */
+ GIT_ABBREV_DEFAULT = 7,
+
} git_cvar_value;
/* internal repository init flags */
@@ -87,9 +106,11 @@ struct git_repository {
git_cache objects;
git_attr_cache attrcache;
git_strmap *submodules;
+ git_diff_driver_registry *diff_drivers;
char *path_repository;
char *workdir;
+ char *namespace;
unsigned is_bare:1;
unsigned int lru_counter;
diff --git a/src/reset.c b/src/reset.c
index c1e1f865e..cea212a93 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -32,7 +32,7 @@ int git_reset_default(
int error;
git_index *index = NULL;
- assert(pathspecs != NULL && pathspecs->count > 0);
+ assert(pathspecs != NULL && pathspecs->count > 0);
memset(&entry, 0, sizeof(git_index_entry));
diff --git a/src/revparse.c b/src/revparse.c
index fd62a2fd2..d21f08b53 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -14,60 +14,6 @@
#include "git2.h"
-static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
-{
- int error, i;
- bool fallbackmode = true;
- git_reference *ref;
- git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
-
- static const char* formatters[] = {
- "%s",
- GIT_REFS_DIR "%s",
- GIT_REFS_TAGS_DIR "%s",
- GIT_REFS_HEADS_DIR "%s",
- GIT_REFS_REMOTES_DIR "%s",
- GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
- NULL
- };
-
- if (*refname)
- git_buf_puts(&name, refname);
- else {
- git_buf_puts(&name, GIT_HEAD_FILE);
- fallbackmode = false;
- }
-
- for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
-
- git_buf_clear(&refnamebuf);
-
- if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
- goto cleanup;
-
- if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
- error = GIT_EINVALIDSPEC;
- continue;
- }
-
- error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
-
- if (!error) {
- *out = ref;
- error = 0;
- goto cleanup;
- }
-
- if (error != GIT_ENOTFOUND)
- goto cleanup;
- }
-
-cleanup:
- git_buf_free(&name);
- git_buf_free(&refnamebuf);
- return error;
-}
-
static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)
{
git_oid oid;
@@ -107,7 +53,7 @@ static int build_regex(regex_t *regex, const char *pattern)
error = regcomp(regex, pattern, REG_EXTENDED);
if (!error)
return 0;
-
+
error = giterr_set_regex(regex, error);
regfree(regex);
@@ -125,7 +71,7 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe
if (substr == NULL)
return GIT_ENOTFOUND;
-
+
if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
return -1;
@@ -138,36 +84,45 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe
return maybe_abbrev(out, repo, substr+2);
}
-static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
+static int revparse_lookup_object(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
{
int error;
git_reference *ref;
- error = maybe_sha(out, repo, spec);
+ error = maybe_sha(object_out, repo, spec);
if (!error)
return 0;
if (error < 0 && error != GIT_ENOTFOUND)
return error;
- error = disambiguate_refname(&ref, repo, spec);
+ error = git_reference_dwim(&ref, repo, spec);
if (!error) {
- error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
- git_reference_free(ref);
+
+ error = git_object_lookup(
+ object_out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+
+ if (!error)
+ *reference_out = ref;
+
return error;
}
if (error < 0 && error != GIT_ENOTFOUND)
return error;
- error = maybe_abbrev(out, repo, spec);
+ error = maybe_abbrev(object_out, repo, spec);
if (!error)
return 0;
if (error < 0 && error != GIT_ENOTFOUND)
return error;
- error = maybe_describe(out, repo, spec);
+ error = maybe_describe(object_out, repo, spec);
if (!error)
return 0;
@@ -235,7 +190,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out,
git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
- if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)
+ if ((error = git_reference_dwim(base_ref, repo, git_buf_cstr(&buf))) == 0)
goto cleanup;
if (error < 0 && error != GIT_ENOTFOUND)
@@ -316,7 +271,7 @@ static int retrieve_revobject_from_reflog(git_object **out, git_reference **base
int error = -1;
if (*base_ref == NULL) {
- if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
return error;
} else {
ref = *base_ref;
@@ -344,7 +299,7 @@ static int retrieve_remote_tracking_reference(git_reference **base_ref, const ch
int error = -1;
if (*base_ref == NULL) {
- if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
return error;
} else {
ref = *base_ref;
@@ -358,7 +313,7 @@ static int retrieve_remote_tracking_reference(git_reference **base_ref, const ch
if ((error = git_branch_upstream(&tracking, ref)) < 0)
goto cleanup;
-
+
*base_ref = tracking;
cleanup:
@@ -508,7 +463,7 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
int error;
git_oid oid;
git_object *obj;
-
+
while (!(error = git_revwalk_next(&oid, walk))) {
error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
@@ -537,7 +492,7 @@ static int handle_grep_syntax(git_object **out, git_repository *repo, const git_
if ((error = build_regex(&preg, pattern)) < 0)
return error;
-
+
if ((error = git_revwalk_new(&walk, repo)) < 0)
goto cleanup;
@@ -551,7 +506,7 @@ static int handle_grep_syntax(git_object **out, git_repository *repo, const git_
goto cleanup;
error = walk_and_search(out, walk, &preg);
-
+
cleanup:
regfree(&preg);
git_revwalk_free(walk);
@@ -671,14 +626,8 @@ static int ensure_base_rev_loaded(git_object **object, git_reference **reference
if (*object != NULL)
return 0;
- if (*reference != NULL) {
- if ((error = object_from_reference(object, *reference)) < 0)
- return error;
-
- git_reference_free(*reference);
- *reference = NULL;
- return 0;
- }
+ if (*reference != NULL)
+ return object_from_reference(object, *reference);
if (!allow_empty_identifier && identifier_len == 0)
return GIT_EINVALIDSPEC;
@@ -686,7 +635,7 @@ static int ensure_base_rev_loaded(git_object **object, git_reference **reference
if (git_buf_put(&identifier, spec, identifier_len) < 0)
return -1;
- error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));
+ error = revparse_lookup_object(object, reference, repo, git_buf_cstr(&identifier));
git_buf_free(&identifier);
return error;
@@ -722,7 +671,12 @@ static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_
return GIT_EINVALIDSPEC;
}
-int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+int revparse__ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ size_t *identifier_len_out,
+ git_repository *repo,
+ const char *spec)
{
size_t pos = 0, identifier_len = 0;
int error = -1, n;
@@ -731,13 +685,18 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
git_reference *reference = NULL;
git_object *base_rev = NULL;
- assert(out && repo && spec);
+ bool should_return_reference = true;
- *out = NULL;
+ assert(object_out && reference_out && repo && spec);
+
+ *object_out = NULL;
+ *reference_out = NULL;
while (spec[pos]) {
switch (spec[pos]) {
case '^':
+ should_return_reference = false;
+
if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
goto cleanup;
@@ -770,6 +729,8 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
{
git_object *temp_object = NULL;
+ should_return_reference = false;
+
if ((error = extract_how_many(&n, spec, &pos)) < 0)
goto cleanup;
@@ -788,6 +749,8 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
{
git_object *temp_object = NULL;
+ should_return_reference = false;
+
if ((error = extract_path(&buf, spec, &pos)) < 0)
goto cleanup;
@@ -852,7 +815,14 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
goto cleanup;
- *out = base_rev;
+ if (!should_return_reference) {
+ git_reference_free(reference);
+ reference = NULL;
+ }
+
+ *object_out = base_rev;
+ *reference_out = reference;
+ *identifier_len_out = identifier_len;
error = 0;
cleanup:
@@ -862,33 +832,99 @@ cleanup:
"Failed to parse revision specifier - Invalid pattern '%s'", spec);
git_object_free(base_rev);
+ git_reference_free(reference);
}
- git_reference_free(reference);
+
git_buf_free(&buf);
return error;
}
-int git_revparse_rangelike(git_object **left, git_object **right, int *threedots, git_repository *repo, const char *rangelike)
+int git_revparse_ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
{
+ int error;
+ size_t identifier_len;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ if ((error = revparse__ext(&obj, &ref, &identifier_len, repo, spec)) < 0)
+ goto cleanup;
+
+ *object_out = obj;
+ *reference_out = ref;
+ GIT_UNUSED(identifier_len);
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+{
+ int error;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ *out = NULL;
+
+ if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0)
+ goto cleanup;
+
+ git_reference_free(ref);
+
+ *out = obj;
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec)
+{
+ const char *dotdot;
int error = 0;
- const char *p, *q;
- char *revspec;
- p = strstr(rangelike, "..");
- if (!p) {
- giterr_set(GITERR_INVALID, "Malformed range (or rangelike syntax): %s", rangelike);
- return GIT_EINVALIDSPEC;
- } else if (p[2] == '.') {
- *threedots = 1;
- q = p + 3;
+ assert(revspec && repo && spec);
+
+ memset(revspec, 0x0, sizeof(*revspec));
+
+ if ((dotdot = strstr(spec, "..")) != NULL) {
+ char *lstr;
+ const char *rstr;
+ revspec->flags = GIT_REVPARSE_RANGE;
+
+ lstr = git__substrdup(spec, dotdot - spec);
+ rstr = dotdot + 2;
+ if (dotdot[2] == '.') {
+ revspec->flags |= GIT_REVPARSE_MERGE_BASE;
+ rstr++;
+ }
+
+ if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) {
+ return error;
+ }
+
+ if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) {
+ return error;
+ }
+
+ git__free((void*)lstr);
} else {
- *threedots = 0;
- q = p + 2;
+ revspec->flags = GIT_REVPARSE_SINGLE;
+ error = git_revparse_single(&revspec->from, repo, spec);
}
- revspec = git__substrdup(rangelike, p - rangelike);
- error = (git_revparse_single(left, repo, revspec)
- || git_revparse_single(right, repo, q));
- git__free(revspec);
return error;
}
diff --git a/src/revwalk.c b/src/revwalk.c
index c1071843b..528d02b20 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -186,7 +186,7 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide)
data.hide = hide;
if (git_reference_foreach_glob(
- walk->repo, git_buf_cstr(&buf), GIT_REF_LISTALL, push_glob_cb, &data) < 0)
+ walk->repo, git_buf_cstr(&buf), push_glob_cb, &data) < 0)
goto on_error;
regfree(&preg);
@@ -231,25 +231,26 @@ int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
int git_revwalk_push_range(git_revwalk *walk, const char *range)
{
- git_object *left, *right;
- int threedots;
+ git_revspec revspec;
int error = 0;
- if ((error = git_revparse_rangelike(&left, &right, &threedots, walk->repo, range)))
+ if ((error = git_revparse(&revspec, walk->repo, range)))
return error;
- if (threedots) {
+
+ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
/* TODO: support "<commit>...<commit>" */
giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");
return GIT_EINVALIDSPEC;
}
- if ((error = push_commit(walk, git_object_id(left), 1)))
+ if ((error = push_commit(walk, git_object_id(revspec.from), 1)))
goto out;
- error = push_commit(walk, git_object_id(right), 0);
- out:
- git_object_free(left);
- git_object_free(right);
+ error = push_commit(walk, git_object_id(revspec.to), 0);
+
+out:
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
return error;
}
diff --git a/src/signature.c b/src/signature.c
index 164e8eb67..0a34ccfaa 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -9,6 +9,7 @@
#include "signature.h"
#include "repository.h"
#include "git2/common.h"
+#include "posix.h"
void git_signature_free(git_signature *sig)
{
@@ -35,11 +36,11 @@ static bool contains_angle_brackets(const char *input)
static char *extract_trimmed(const char *ptr, size_t len)
{
- while (len && ptr[0] == ' ') {
+ while (len && git__isspace(ptr[0])) {
ptr++; len--;
}
- while (len && ptr[len - 1] == ' ') {
+ while (len && git__isspace(ptr[len - 1])) {
len--;
}
@@ -66,10 +67,12 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema
p->name = extract_trimmed(name, strlen(name));
p->email = extract_trimmed(email, strlen(email));
- if (p->name == NULL || p->email == NULL ||
- p->name[0] == '\0' || p->email[0] == '\0') {
+ if (p->name == NULL || p->email == NULL)
+ return -1; /* oom */
+
+ if (p->name[0] == '\0') {
git_signature_free(p);
- return -1;
+ return signature_error("Signature cannot have an empty name");
}
p->when.time = time;
@@ -81,9 +84,16 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema
git_signature *git_signature_dup(const git_signature *sig)
{
- git_signature *new;
- if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < 0)
+ git_signature *new = git__calloc(1, sizeof(git_signature));
+
+ if (new == NULL)
return NULL;
+
+ new->name = git__strdup(sig->name);
+ new->email = git__strdup(sig->email);
+ new->when.time = sig->when.time;
+ new->when.offset = sig->when.offset;
+
return new;
}
@@ -164,9 +174,11 @@ int git_signature__parse(git_signature *sig, const char **buffer_out,
tz_start = time_end + 1;
- if ((tz_start[0] != '-' && tz_start[0] != '+') ||
- git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0)
- return signature_error("malformed timezone");
+ if ((tz_start[0] != '-' && tz_start[0] != '+') ||
+ git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) {
+ //malformed timezone, just assume it's zero
+ offset = 0;
+ }
hours = offset / 100;
mins = offset % 100;
diff --git a/src/stash.c b/src/stash.c
index 355c5dc9c..48f19144d 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -14,6 +14,7 @@
#include "git2/stash.h"
#include "git2/status.h"
#include "git2/checkout.h"
+#include "git2/index.h"
#include "signature.h"
static int create_error(int error, const char *msg)
@@ -116,7 +117,7 @@ static int build_tree_from_index(git_tree **out, git_index *index)
static int commit_index(
git_commit **i_commit,
git_index *index,
- git_signature *stasher,
+ const git_signature *stasher,
const char *message,
const git_commit *parent)
{
@@ -266,7 +267,7 @@ cleanup:
static int commit_untracked(
git_commit **u_commit,
git_index *index,
- git_signature *stasher,
+ const git_signature *stasher,
const char *message,
git_commit *i_commit,
uint32_t flags)
@@ -353,7 +354,7 @@ cleanup:
static int commit_worktree(
git_oid *w_commit_oid,
git_index *index,
- git_signature *stasher,
+ const git_signature *stasher,
const char *message,
git_commit *i_commit,
git_commit *b_commit,
@@ -430,7 +431,7 @@ cleanup:
static int update_reflog(
git_oid *w_commit_oid,
git_repository *repo,
- git_signature *stasher,
+ const git_signature *stasher,
const char *message)
{
git_reference *stash = NULL;
@@ -509,7 +510,7 @@ static int reset_index_and_workdir(
int git_stash_save(
git_oid *out,
git_repository *repo,
- git_signature *stasher,
+ const git_signature *stasher,
const char *message,
uint32_t flags)
{
@@ -587,8 +588,10 @@ int git_stash_foreach(
const git_reflog_entry *entry;
error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
return 0;
+ }
if (error < 0)
goto cleanup;
@@ -651,7 +654,7 @@ int git_stash_drop(
const git_reflog_entry *entry;
entry = git_reflog_entry_byindex(reflog, 0);
-
+
git_reference_free(stash);
error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1);
}
diff --git a/src/status.c b/src/status.c
index ac6b4379b..ccb8d37da 100644
--- a/src/status.c
+++ b/src/status.c
@@ -11,19 +11,20 @@
#include "hash.h"
#include "vector.h"
#include "tree.h"
+#include "status.h"
#include "git2/status.h"
#include "repository.h"
#include "ignore.h"
+#include "index.h"
#include "git2/diff.h"
#include "diff.h"
-#include "diff_output.h"
-static unsigned int index_delta2status(git_delta_t index_status)
+static unsigned int index_delta2status(const git_diff_delta *head2idx)
{
- unsigned int st = GIT_STATUS_CURRENT;
+ git_status_t st = GIT_STATUS_CURRENT;
- switch (index_status) {
+ switch (head2idx->status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_COPIED:
st = GIT_STATUS_INDEX_NEW;
@@ -36,6 +37,9 @@ static unsigned int index_delta2status(git_delta_t index_status)
break;
case GIT_DELTA_RENAMED:
st = GIT_STATUS_INDEX_RENAMED;
+
+ if (!git_oid_equal(&head2idx->old_file.oid, &head2idx->new_file.oid))
+ st |= GIT_STATUS_INDEX_MODIFIED;
break;
case GIT_DELTA_TYPECHANGE:
st = GIT_STATUS_INDEX_TYPECHANGE;
@@ -47,13 +51,13 @@ static unsigned int index_delta2status(git_delta_t index_status)
return st;
}
-static unsigned int workdir_delta2status(git_delta_t workdir_status)
+static unsigned int workdir_delta2status(
+ git_diff_list *diff, git_diff_delta *idx2wd)
{
- unsigned int st = GIT_STATUS_CURRENT;
+ git_status_t st = GIT_STATUS_CURRENT;
- switch (workdir_status) {
+ switch (idx2wd->status) {
case GIT_DELTA_ADDED:
- case GIT_DELTA_RENAMED:
case GIT_DELTA_COPIED:
case GIT_DELTA_UNTRACKED:
st = GIT_STATUS_WT_NEW;
@@ -67,6 +71,31 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
case GIT_DELTA_IGNORED:
st = GIT_STATUS_IGNORED;
break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_WT_RENAMED;
+
+ if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) {
+ /* if OIDs don't match, we might need to calculate them now to
+ * discern between RENAMED vs RENAMED+MODIFED
+ */
+ if (git_oid_iszero(&idx2wd->old_file.oid) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode,
+ idx2wd->old_file.size, &idx2wd->old_file.oid))
+ idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (git_oid_iszero(&idx2wd->new_file.oid) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode,
+ idx2wd->new_file.size, &idx2wd->new_file.oid))
+ idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid))
+ st |= GIT_STATUS_WT_MODIFIED;
+ }
+ break;
case GIT_DELTA_TYPECHANGE:
st = GIT_STATUS_WT_TYPECHANGE;
break;
@@ -77,142 +106,320 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
return st;
}
-typedef struct {
- git_status_cb cb;
- void *payload;
- const git_status_options *opts;
-} status_user_callback;
-
-static int status_invoke_cb(
- git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
+static bool status_is_included(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
{
- status_user_callback *usercb = payload;
- const char *path = NULL;
- unsigned int status = 0;
+ if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
+ return 1;
- if (i2w) {
- path = i2w->old_file.path;
- status |= workdir_delta2status(i2w->status);
+ /* if excluding submodules and this is a submodule everywhere */
+ if (head2idx) {
+ if (head2idx->status != GIT_DELTA_ADDED &&
+ head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (head2idx->status != GIT_DELTA_DELETED &&
+ head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
}
- if (h2i) {
- path = h2i->old_file.path;
- status |= index_delta2status(h2i->status);
+ if (idx2wd) {
+ if (idx2wd->status != GIT_DELTA_ADDED &&
+ idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (idx2wd->status != GIT_DELTA_DELETED &&
+ idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
}
- /* 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;
+ /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
+ return 0;
+}
+
+static git_status_t status_compute(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ if (head2idx)
+ st |= index_delta2status(head2idx);
+
+ if (idx2wd)
+ st |= workdir_delta2status(status->idx2wd, idx2wd);
+
+ return st;
+}
+
+static int status_collect(
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd,
+ void *payload)
+{
+ git_status_list *status = payload;
+ git_status_entry *status_entry;
+
+ if (!status_is_included(status, head2idx, idx2wd))
+ return 0;
+
+ status_entry = git__malloc(sizeof(git_status_entry));
+ GITERR_CHECK_ALLOC(status_entry);
+
+ status_entry->status = status_compute(status, head2idx, idx2wd);
+ status_entry->head_to_index = head2idx;
+ status_entry->index_to_workdir = idx2wd;
+
+ return git_vector_insert(&status->paired, status_entry);
+}
+
+GIT_INLINE(int) status_entry_cmp_base(
+ const void *a,
+ const void *b,
+ int (*strcomp)(const char *a, const char *b))
+{
+ const git_status_entry *entry_a = a;
+ const git_status_entry *entry_b = b;
+ const git_diff_delta *delta_a, *delta_b;
+
+ delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
+ entry_a->head_to_index;
+ delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
+ entry_b->head_to_index;
+
+ if (!delta_a && delta_b)
+ return -1;
+ if (delta_a && !delta_b)
+ return 1;
+ if (!delta_a && !delta_b)
+ return 0;
+
+ return strcomp(delta_a->new_file.path, delta_b->new_file.path);
+}
+
+static int status_entry_icmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcasecmp);
+}
+
+static int status_entry_cmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcmp);
+}
+
+static git_status_list *git_status_list_alloc(git_index *index)
+{
+ git_status_list *status = NULL;
+ int (*entrycmp)(const void *a, const void *b);
+
+ if (!(status = git__calloc(1, sizeof(git_status_list))))
+ return NULL;
+
+ entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
+
+ if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
+ git__free(status);
+ return NULL;
}
- return usercb->cb(path, status, usercb->payload);
+ return status;
}
-int git_status_foreach_ext(
+/*
+static int newfile_cmp(const void *a, const void *b)
+{
+ const git_diff_delta *delta_a = a;
+ const git_diff_delta *delta_b = b;
+
+ return git__strcmp(delta_a->new_file.path, delta_b->new_file.path);
+}
+
+static int newfile_casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *delta_a = a;
+ const git_diff_delta *delta_b = b;
+
+ return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path);
+}
+*/
+
+int git_status_list_new(
+ git_status_list **out,
git_repository *repo,
- const git_status_options *opts,
- git_status_cb cb,
- void *payload)
+ const git_status_options *opts)
{
- int err = 0;
+ git_index *index = NULL;
+ git_status_list *status = NULL;
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
- git_diff_list *head2idx = NULL, *idx2wd = NULL;
+ git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- status_user_callback usercb;
+ int error = 0;
+ unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
+
+ assert(show <= GIT_STATUS_SHOW_WORKDIR_ONLY);
- assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
+ *out = NULL;
GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
- if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_repository__ensure_not_bare(repo, "status")) < 0)
- return err;
+ if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0)
+ return error;
/* if there is no HEAD, that's okay - we'll make an empty iterator */
- if (((err = git_repository_head_tree(&head, repo)) < 0) &&
- !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))
- return err;
+ if (((error = git_repository_head_tree(&head, repo)) < 0) &&
+ error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) {
+ git_index_free(index); /* release index */
+ return error;
+ }
+
+ status = git_status_list_alloc(index);
+ GITERR_CHECK_ALLOC(status);
- memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ if (opts) {
+ memcpy(&status->opts, opts, sizeof(git_status_options));
+ memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ }
diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
- if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
+ if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
- if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
+ if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
- if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ if ((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)
+ if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+ findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED;
+
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
- err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
- if (err < 0)
- goto cleanup;
- }
+ if ((error = git_diff_tree_to_index(
+ &status->head2idx, repo, head, NULL, &diffopt)) < 0)
+ goto done;
- if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
- err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
- if (err < 0)
- goto cleanup;
+ if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
+ (error = git_diff_find_similar(status->head2idx, NULL)) < 0)
+ goto done;
}
- usercb.cb = cb;
- usercb.payload = payload;
- usercb.opts = opts;
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
+ if ((error = git_diff_index_to_workdir(
+ &status->idx2wd, repo, NULL, &diffopt)) < 0)
+ goto done;
- if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
- if ((err = git_diff__paired_foreach(
- head2idx, NULL, status_invoke_cb, &usercb)) < 0)
- goto cleanup;
+ if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
+ (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0)
+ goto done;
+ }
- git_diff_list_free(head2idx);
- head2idx = NULL;
+ if ((error = git_diff__paired_foreach(
+ status->head2idx, status->idx2wd, status_collect, status)) < 0)
+ goto done;
+
+ if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_cmp);
+ if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_icmp);
+
+ if ((flags &
+ (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
+ git_vector_sort(&status->paired);
+
+done:
+ if (error < 0) {
+ git_status_list_free(status);
+ status = NULL;
}
- err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
+ *out = status;
-cleanup:
git_tree_free(head);
- git_diff_list_free(head2idx);
- git_diff_list_free(idx2wd);
+ git_index_free(index);
- if (err == GIT_EUSER)
- giterr_clear();
+ return error;
+}
+
+size_t git_status_list_entrycount(git_status_list *status)
+{
+ assert(status);
- return err;
+ return status->paired.length;
}
-int git_status_foreach(
+const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
+{
+ assert(status);
+
+ return git_vector_get(&status->paired, i);
+}
+
+void git_status_list_free(git_status_list *status)
+{
+ git_status_entry *status_entry;
+ size_t i;
+
+ if (status == NULL)
+ return;
+
+ git_diff_list_free(status->head2idx);
+ git_diff_list_free(status->idx2wd);
+
+ git_vector_foreach(&status->paired, i, status_entry)
+ git__free(status_entry);
+
+ git_vector_free(&status->paired);
+
+ git__memzero(status, sizeof(*status));
+ git__free(status);
+}
+
+int git_status_foreach_ext(
git_repository *repo,
- git_status_cb callback,
+ const git_status_options *opts,
+ git_status_cb cb,
void *payload)
{
- git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *status;
+ const git_status_entry *status_entry;
+ size_t i;
+ int error = 0;
- opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
- GIT_STATUS_OPT_INCLUDE_UNTRACKED |
- GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+ if ((error = git_status_list_new(&status, repo, opts)) < 0)
+ return error;
- return git_status_foreach_ext(repo, &opts, callback, payload);
+ git_vector_foreach(&status->paired, i, status_entry) {
+ const char *path = status_entry->head_to_index ?
+ status_entry->head_to_index->old_file.path :
+ status_entry->index_to_workdir->old_file.path;
+
+ if (cb(path, status_entry->status, payload) != 0) {
+ error = GIT_EUSER;
+ giterr_clear();
+ break;
+ }
+ }
+
+ git_status_list_free(status);
+
+ return error;
+}
+
+int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
+{
+ return git_status_foreach_ext(repo, NULL, cb, payload);
}
struct status_file_info {
@@ -238,7 +445,7 @@ static int get_one_status(const char *path, unsigned int status, void *data)
p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
{
sfi->ambiguous = true;
- return GIT_EAMBIGUOUS;
+ return GIT_EAMBIGUOUS; /* giterr_set will be done by caller */
}
return 0;
@@ -264,8 +471,9 @@ int git_status_file(
if (index->ignore_case)
sfi.fnm_flags = FNM_CASEFOLD;
- opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
@@ -281,22 +489,9 @@ int git_status_file(
}
if (!error && !sfi.count) {
- git_buf full = GIT_BUF_INIT;
-
- /* if the file actually exists and we still did not get a callback
- * for it, then it must be contained inside an ignored directory, so
- * mark it as such instead of generating an error.
- */
- if (!git_buf_joinpath(&full, git_repository_workdir(repo), path) &&
- git_path_exists(full.ptr))
- sfi.status = GIT_STATUS_IGNORED;
- else {
- giterr_set(GITERR_INVALID,
- "Attempt to get status of nonexistent file '%s'", path);
- error = GIT_ENOTFOUND;
- }
-
- git_buf_free(&full);
+ giterr_set(GITERR_INVALID,
+ "Attempt to get status of nonexistent file '%s'", path);
+ error = GIT_ENOTFOUND;
}
*status_flags = sfi.status;
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 000000000..b58e0ebd6
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,23 @@
+/*
+ * 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_status_h__
+#define INCLUDE_status_h__
+
+#include "diff.h"
+#include "git2/status.h"
+#include "git2/diff.h"
+
+struct git_status_list {
+ git_status_options opts;
+
+ git_diff_list *head2idx;
+ git_diff_list *idx2wd;
+
+ git_vector paired;
+};
+
+#endif
diff --git a/src/submodule.c b/src/submodule.c
index 2fdaf2f77..40bda9a41 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -7,10 +7,9 @@
#include "common.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "git2/types.h"
-#include "git2/repository.h"
#include "git2/index.h"
-#include "git2/submodule.h"
#include "buffer.h"
#include "buf_text.h"
#include "vector.h"
@@ -21,6 +20,8 @@
#include "submodule.h"
#include "tree.h"
#include "iterator.h"
+#include "path.h"
+#include "index.h"
#define GIT_MODULES_FILE ".gitmodules"
@@ -29,6 +30,8 @@ static git_cvar_map _sm_update_map[] = {
{GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
{GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT},
};
static git_cvar_map _sm_ignore_map[] = {
@@ -36,6 +39,8 @@ static git_cvar_map _sm_ignore_map[] = {
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL},
};
static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
@@ -70,15 +75,11 @@ static int load_submodule_config(git_repository *repo);
static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *);
static int lookup_head_remote(git_buf *url, git_repository *repo);
static int submodule_get(git_submodule **, git_repository *, const char *, const char *);
-static void submodule_release(git_submodule *sm, int decr);
-static int submodule_load_from_index(git_repository *, const git_index_entry *);
-static int submodule_load_from_head(git_repository*, const char*, const git_oid*);
static int submodule_load_from_config(const git_config_entry *, void *);
static int submodule_load_from_wd_lite(git_submodule *, const char *, void *);
static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
-static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);
-static int submodule_index_status(unsigned int *status, git_submodule *sm);
-static int submodule_wd_status(unsigned int *status, git_submodule *sm);
+static void submodule_get_index_status(unsigned int *, git_submodule *);
+static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t);
static int submodule_cmp(const void *a, const void *b)
{
@@ -127,6 +128,10 @@ int git_submodule_lookup(
git_buf_free(&path);
}
+ giterr_set(GITERR_SUBMODULE, (error == GIT_ENOTFOUND) ?
+ "No submodule named '%s'" :
+ "Submodule '%s' has not been added yet", name);
+
return error;
}
@@ -144,7 +149,7 @@ int git_submodule_foreach(
int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
- seen._cmp = submodule_cmp;
+ git_vector_set_cmp(&seen, submodule_cmp);
assert(repo && callback);
@@ -156,7 +161,7 @@ int git_submodule_foreach(
* us from issuing a callback twice for a submodule where the name
* and path are not the same.
*/
- if (sm->refcount > 1) {
+ if (GIT_REFCOUNT_VAL(sm) > 1) {
if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
@@ -188,9 +193,7 @@ void git_submodule_config_free(git_repository *repo)
if (smcfg == NULL)
return;
- git_strmap_foreach_value(smcfg, sm, {
- submodule_release(sm,1);
- });
+ git_strmap_foreach_value(smcfg, sm, { git_submodule_free(sm); });
git_strmap_free(smcfg);
}
@@ -331,7 +334,7 @@ int git_submodule_add_finalize(git_submodule *sm)
assert(sm);
- if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
(error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
return error;
@@ -341,7 +344,7 @@ int git_submodule_add_finalize(git_submodule *sm)
int git_submodule_add_to_index(git_submodule *sm, int write_index)
{
int error;
- git_repository *repo, *sm_repo = NULL;
+ git_repository *sm_repo = NULL;
git_index *index;
git_buf path = GIT_BUF_INIT;
git_commit *head;
@@ -350,14 +353,12 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
assert(sm);
- repo = sm->owner;
-
/* force reload of wd OID by git_submodule_open */
sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
- if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
(error = git_buf_joinpath(
- &path, git_repository_workdir(repo), sm->path)) < 0 ||
+ &path, git_repository_workdir(sm->repo), sm->path)) < 0 ||
(error = git_submodule_open(&sm_repo, sm)) < 0)
goto cleanup;
@@ -409,15 +410,34 @@ cleanup:
return error;
}
+const char *git_submodule_ignore_to_str(git_submodule_ignore_t ignore)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_ignore_map); ++i)
+ if (_sm_ignore_map[i].map_value == ignore)
+ return _sm_ignore_map[i].str_match;
+ return NULL;
+}
+
+const char *git_submodule_update_to_str(git_submodule_update_t update)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i)
+ if (_sm_update_map[i].map_value == update)
+ return _sm_update_map[i].str_match;
+ return NULL;
+}
+
int git_submodule_save(git_submodule *submodule)
{
int error = 0;
git_config_backend *mods;
git_buf key = GIT_BUF_INIT;
+ const char *val;
assert(submodule);
- mods = open_gitmodules(submodule->owner, true, NULL);
+ mods = open_gitmodules(submodule->repo, true, NULL);
if (!mods) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
@@ -438,22 +458,14 @@ int git_submodule_save(git_submodule *submodule)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
- submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
- {
- const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
- NULL : _sm_update_map[submodule->update].str_match;
+ (val = git_submodule_update_to_str(submodule->update)) != NULL)
error = git_config_file_set_string(mods, key.ptr, val);
- }
if (error < 0)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
- submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT)
- {
- const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ?
- NULL : _sm_ignore_map[submodule->ignore].str_match;
+ (val = git_submodule_ignore_to_str(submodule->ignore)) != NULL)
error = git_config_file_set_string(mods, key.ptr, val);
- }
if (error < 0)
goto cleanup;
@@ -480,7 +492,7 @@ cleanup:
git_repository *git_submodule_owner(git_submodule *submodule)
{
assert(submodule);
- return submodule->owner;
+ return submodule->repo;
}
const char *git_submodule_name(git_submodule *submodule)
@@ -537,11 +549,12 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule)
{
assert(submodule);
+ /* load unless we think we have a valid oid */
if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
git_repository *subrepo;
/* calling submodule open grabs the HEAD OID if possible */
- if (!git_submodule_open(&subrepo, submodule))
+ if (!git_submodule_open_bare(&subrepo, submodule))
git_repository_free(subrepo);
else
giterr_clear();
@@ -556,7 +569,8 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule)
git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
{
assert(submodule);
- return submodule->ignore;
+ return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ?
+ GIT_SUBMODULE_IGNORE_NONE : submodule->ignore;
}
git_submodule_ignore_t git_submodule_set_ignore(
@@ -566,7 +580,7 @@ git_submodule_ignore_t git_submodule_set_ignore(
assert(submodule);
- if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)
+ if (ignore == GIT_SUBMODULE_IGNORE_RESET)
ignore = submodule->ignore_default;
old = submodule->ignore;
@@ -577,7 +591,8 @@ git_submodule_ignore_t git_submodule_set_ignore(
git_submodule_update_t git_submodule_update(git_submodule *submodule)
{
assert(submodule);
- return submodule->update;
+ return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update;
}
git_submodule_update_t git_submodule_set_update(
@@ -587,7 +602,7 @@ git_submodule_update_t git_submodule_set_update(
assert(submodule);
- if (update == GIT_SUBMODULE_UPDATE_DEFAULT)
+ if (update == GIT_SUBMODULE_UPDATE_RESET)
update = submodule->update_default;
old = submodule->update;
@@ -618,6 +633,7 @@ int git_submodule_set_fetch_recurse_submodules(
int git_submodule_init(git_submodule *submodule, int overwrite)
{
int error;
+ const char *val;
/* write "submodule.NAME.url" */
@@ -634,14 +650,10 @@ int git_submodule_init(git_submodule *submodule, int overwrite)
/* write "submodule.NAME.update" if not default */
- if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT)
- error = submodule_update_config(
- submodule, "update", NULL, (overwrite != 0), false);
- else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
- error = submodule_update_config(
- submodule, "update",
- _sm_update_map[submodule->update].str_match,
- (overwrite != 0), false);
+ val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ NULL : git_submodule_update_to_str(submodule->update);
+ error = submodule_update_config(
+ submodule, "update", val, (overwrite != 0), false);
return error;
}
@@ -660,51 +672,70 @@ int git_submodule_sync(git_submodule *submodule)
submodule, "url", submodule->url, true, true);
}
-int git_submodule_open(
- git_repository **subrepo,
- git_submodule *submodule)
+static int git_submodule__open(
+ git_repository **subrepo, git_submodule *sm, bool bare)
{
int error;
git_buf path = GIT_BUF_INIT;
- git_repository *repo;
- const char *workdir;
-
- assert(submodule && subrepo);
+ unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH;
+ const char *wd;
- repo = submodule->owner;
- workdir = git_repository_workdir(repo);
+ assert(sm && subrepo);
- if (!workdir) {
- giterr_set(GITERR_REPOSITORY,
- "Cannot open submodule repository in a bare repo");
- return GIT_ENOTFOUND;
- }
+ if (git_repository__ensure_not_bare(
+ sm->repo, "open submodule repository") < 0)
+ return GIT_EBAREREPO;
- if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) {
- giterr_set(GITERR_REPOSITORY,
- "Cannot open submodule repository that is not checked out");
- return GIT_ENOTFOUND;
- }
+ wd = git_repository_workdir(sm->repo);
- if (git_buf_joinpath(&path, workdir, submodule->path) < 0)
+ if (git_buf_joinpath(&path, wd, sm->path) < 0 ||
+ git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0)
return -1;
- error = git_repository_open(subrepo, path.ptr);
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_SCANNED);
- git_buf_free(&path);
+ if (bare)
+ flags |= GIT_REPOSITORY_OPEN_BARE;
- /* if we have opened the submodule successfully, let's grab the HEAD OID */
+ error = git_repository_open_ext(subrepo, path.ptr, flags, wd);
+
+ /* if we opened the submodule successfully, grab HEAD OID, etc. */
if (!error) {
- if (!git_reference_name_to_id(
- &submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
- submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
else
giterr_clear();
+ } else if (git_path_exists(path.ptr)) {
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED |
+ GIT_SUBMODULE_STATUS_IN_WD;
+ } else {
+ git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
}
+ git_buf_free(&path);
+
return error;
}
+int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, true);
+}
+
+int git_submodule_open(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, false);
+}
+
int git_submodule_reload_all(git_repository *repo)
{
assert(repo);
@@ -712,74 +743,100 @@ int git_submodule_reload_all(git_repository *repo)
return load_submodule_config(repo);
}
-int git_submodule_reload(git_submodule *submodule)
+static void submodule_update_from_index_entry(
+ git_submodule *sm, const git_index_entry *ie)
{
- git_repository *repo;
- git_index *index;
- int error;
- size_t pos;
- git_tree *head;
- git_config_backend *mods;
+ bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0;
- assert(submodule);
+ if (!S_ISGITLINK(ie->mode)) {
+ if (!already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else {
+ if (already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+ else
+ git_oid_cpy(&sm->index_oid, &ie->oid);
- /* refresh index data */
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
+ }
+}
+
+static int submodule_update_index(git_submodule *sm)
+{
+ git_index *index;
+ const git_index_entry *ie;
- repo = submodule->owner;
- if (git_repository_index__weakptr(&index, repo) < 0)
+ if (git_repository_index__weakptr(&index, sm->repo) < 0)
return -1;
- submodule->flags = submodule->flags &
+ sm->flags = sm->flags &
~(GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
- if (!git_index_find(&pos, index, submodule->path)) {
- const git_index_entry *entry = git_index_get_byindex(index, pos);
+ if (!(ie = git_index_get_bypath(index, sm->path, 0)))
+ return 0;
- if (S_ISGITLINK(entry->mode)) {
- if ((error = submodule_load_from_index(repo, entry)) < 0)
- return error;
- } else {
- submodule_mode_mismatch(
- repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
- }
+ submodule_update_from_index_entry(sm, ie);
+
+ return 0;
+}
+
+static void submodule_update_from_head_data(
+ git_submodule *sm, mode_t mode, const git_oid *id)
+{
+ if (!S_ISGITLINK(mode))
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ else {
+ git_oid_cpy(&sm->head_oid, id);
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
}
+}
- /* refresh HEAD tree data */
+static int submodule_update_head(git_submodule *submodule)
+{
+ git_tree *head = NULL;
+ git_tree_entry *te = NULL;
- if (!(error = git_repository_head_tree(&head, repo))) {
- git_tree_entry *te;
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
- submodule->flags = submodule->flags &
- ~(GIT_SUBMODULE_STATUS_IN_HEAD |
- GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
+ /* if we can't look up file in current head, then done */
+ if (git_repository_head_tree(&head, submodule->repo) < 0 ||
+ git_tree_entry_bypath(&te, head, submodule->path) < 0)
+ giterr_clear();
+ else
+ submodule_update_from_head_data(submodule, te->attr, &te->oid);
- if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) {
+ git_tree_entry_free(te);
+ git_tree_free(head);
+ return 0;
+}
- if (S_ISGITLINK(te->attr)) {
- error = submodule_load_from_head(repo, submodule->path, &te->oid);
- } else {
- submodule_mode_mismatch(
- repo, submodule->path,
- GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
- }
+int git_submodule_reload(git_submodule *submodule)
+{
+ int error = 0;
+ git_config_backend *mods;
- git_tree_entry_free(te);
- }
- else if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = 0;
- }
+ assert(submodule);
- git_tree_free(head);
- }
+ /* refresh index data */
- if (error < 0)
- return error;
+ if (submodule_update_index(submodule) < 0)
+ return -1;
+
+ /* refresh HEAD tree data */
+
+ if (submodule_update_head(submodule) < 0)
+ return -1;
/* refresh config data */
- if ((mods = open_gitmodules(repo, false, NULL)) != NULL) {
+ mods = open_gitmodules(submodule->repo, false, NULL);
+ if (mods != NULL) {
git_buf path = GIT_BUF_INIT;
git_buf_sets(&path, "submodule\\.");
@@ -790,7 +847,7 @@ int git_submodule_reload(git_submodule *submodule)
error = -1;
else
error = git_config_file_foreach_match(
- mods, path.ptr, submodule_load_from_config, repo);
+ mods, path.ptr, submodule_load_from_config, submodule->repo);
git_buf_free(&path);
git_config_file_free(mods);
@@ -809,38 +866,90 @@ int git_submodule_reload(git_submodule *submodule)
return error;
}
-int git_submodule_status(
- unsigned int *status,
- git_submodule *submodule)
+static void submodule_copy_oid_maybe(
+ git_oid *tgt, const git_oid *src, bool valid)
{
- int error = 0;
- unsigned int status_val;
+ if (tgt) {
+ if (valid)
+ memcpy(tgt, src, sizeof(*tgt));
+ else
+ memset(tgt, 0, sizeof(*tgt));
+ }
+}
- assert(status && submodule);
+int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign)
+{
+ unsigned int status;
+ git_repository *smrepo = NULL;
- status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);
+ if (ign < GIT_SUBMODULE_IGNORE_NONE)
+ ign = sm->ignore;
- if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {
- if (!(error = submodule_index_status(&status_val, submodule)))
- error = submodule_wd_status(&status_val, submodule);
+ /* only return location info if ignore == all */
+ if (ign == GIT_SUBMODULE_IGNORE_ALL) {
+ *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS);
+ return 0;
}
- *status = status_val;
+ /* refresh the index OID */
+ if (submodule_update_index(sm) < 0)
+ return -1;
- return error;
+ /* refresh the HEAD OID */
+ if (submodule_update_head(sm) < 0)
+ return -1;
+
+ /* for ignore == dirty, don't scan the working directory */
+ if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
+ /* git_submodule_open_bare will load WD OID data */
+ if (git_submodule_open_bare(&smrepo, sm) < 0)
+ giterr_clear();
+ else
+ git_repository_free(smrepo);
+ smrepo = NULL;
+ } else if (git_submodule_open(&smrepo, sm) < 0) {
+ giterr_clear();
+ smrepo = NULL;
+ }
+
+ status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags);
+
+ submodule_get_index_status(&status, sm);
+ submodule_get_wd_status(&status, sm, smrepo, ign);
+
+ git_repository_free(smrepo);
+
+ *out_status = status;
+
+ submodule_copy_oid_maybe(out_head_id, &sm->head_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_index_id, &sm->index_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0);
+
+ return 0;
}
-int git_submodule_location(
- unsigned int *location_status,
- git_submodule *submodule)
+int git_submodule_status(unsigned int *status, git_submodule *sm)
{
- assert(location_status && submodule);
+ assert(status && sm);
- *location_status = submodule->flags &
- (GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX |
- GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD);
+ return git_submodule__status(status, NULL, NULL, NULL, sm, 0);
+}
- return 0;
+int git_submodule_location(unsigned int *location, git_submodule *sm)
+{
+ assert(location && sm);
+
+ return git_submodule__status(
+ location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
}
@@ -850,54 +959,50 @@ int git_submodule_location(
static git_submodule *submodule_alloc(git_repository *repo, const char *name)
{
+ size_t namelen;
git_submodule *sm;
- if (!name || !strlen(name)) {
+ if (!name || !(namelen = strlen(name))) {
giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
return NULL;
}
sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
- goto fail;
+ return NULL;
- sm->path = sm->name = git__strdup(name);
- if (!sm->name)
- goto fail;
+ sm->name = sm->path = git__strdup(name);
+ if (!sm->name) {
+ git__free(sm);
+ return NULL;
+ }
- sm->owner = repo;
- sm->refcount = 1;
+ GIT_REFCOUNT_INC(sm);
+ sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE;
+ sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ sm->repo = repo;
return sm;
-
-fail:
- submodule_release(sm, 0);
- return NULL;
}
-static void submodule_release(git_submodule *sm, int decr)
+static void submodule_release(git_submodule *sm)
{
if (!sm)
return;
- sm->refcount -= decr;
-
- if (sm->refcount == 0) {
- if (sm->name != sm->path) {
- git__free(sm->path);
- sm->path = NULL;
- }
-
- git__free(sm->name);
- sm->name = NULL;
-
- git__free(sm->url);
- sm->url = NULL;
-
- sm->owner = NULL;
+ if (sm->path != sm->name)
+ git__free(sm->path);
+ git__free(sm->name);
+ git__free(sm->url);
+ git__memzero(sm, sizeof(*sm));
+ git__free(sm);
+}
- git__free(sm);
- }
+void git_submodule_free(git_submodule *sm)
+{
+ if (!sm)
+ return;
+ GIT_REFCOUNT_DEC(sm, submodule_release);
}
static int submodule_get(
@@ -920,6 +1025,7 @@ static int submodule_get(
if (!git_strmap_valid_index(smcfg, pos)) {
sm = submodule_alloc(repo, name);
+ GITERR_CHECK_ALLOC(sm);
/* insert value at name - if another thread beats us to it, then use
* their record and release our own.
@@ -927,10 +1033,10 @@ static int submodule_get(
pos = kh_put(str, smcfg, sm->name, &error);
if (error < 0) {
- submodule_release(sm, 1);
+ git_submodule_free(sm);
sm = NULL;
} else if (error == 0) {
- submodule_release(sm, 1);
+ git_submodule_free(sm);
sm = git_strmap_value_at(smcfg, pos);
} else {
git_strmap_set_value_at(smcfg, pos, sm);
@@ -944,50 +1050,41 @@ static int submodule_get(
return (sm != NULL) ? 0 : -1;
}
-static int submodule_load_from_index(
- git_repository *repo, const git_index_entry *entry)
+static int submodule_config_error(const char *property, const char *value)
{
- git_submodule *sm;
+ giterr_set(GITERR_INVALID,
+ "Invalid value for submodule '%s' property: '%s'", property, value);
+ return -1;
+}
- if (submodule_get(&sm, repo, entry->path, NULL) < 0)
- return -1;
+int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
+{
+ int val;
- if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
- sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
- return 0;
+ if (git_config_lookup_map_value(
+ &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) {
+ *out = GIT_SUBMODULE_IGNORE_NONE;
+ return submodule_config_error("ignore", value);
}
- sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;
-
- git_oid_cpy(&sm->index_oid, &entry->oid);
- sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
-
+ *out = (git_submodule_ignore_t)val;
return 0;
}
-static int submodule_load_from_head(
- git_repository *repo, const char *path, const git_oid *oid)
+int git_submodule_parse_update(git_submodule_update_t *out, const char *value)
{
- git_submodule *sm;
-
- if (submodule_get(&sm, repo, path, NULL) < 0)
- return -1;
-
- sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
+ int val;
- git_oid_cpy(&sm->head_oid, oid);
- sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+ if (git_config_lookup_map_value(
+ &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) {
+ *out = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ return submodule_config_error("update", value);
+ }
+ *out = (git_submodule_update_t)val;
return 0;
}
-static int submodule_config_error(const char *property, const char *value)
-{
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule '%s' property: '%s'", property, value);
- return -1;
-}
-
static int submodule_load_from_config(
const git_config_entry *entry, void *data)
{
@@ -1005,8 +1102,10 @@ static int submodule_load_from_config(
namestart = key + strlen("submodule.");
property = strrchr(namestart, '.');
- if (property == NULL)
+
+ if (!property || (property == namestart))
return 0;
+
property++;
is_path = (strcasecmp(property, "path") == 0);
@@ -1040,11 +1139,11 @@ static int submodule_load_from_config(
git_strmap_insert2(smcfg, alternate, sm, old_sm, error);
if (error >= 0)
- sm->refcount++; /* inserted under a new key */
+ GIT_REFCOUNT_INC(sm); /* inserted under a new key */
/* if we replaced an old module under this key, release the old one */
if (old_sm && ((git_submodule *)old_sm) != sm) {
- submodule_release(old_sm, 1);
+ git_submodule_free(old_sm);
/* TODO: log warning about multiple submodules with same path */
}
}
@@ -1069,22 +1168,18 @@ static int submodule_load_from_config(
return -1;
}
else if (strcasecmp(property, "update") == 0) {
- int val;
- if (git_config_lookup_map_value(
- &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0)
- return submodule_config_error("update", value);
- sm->update_default = sm->update = (git_submodule_update_t)val;
+ if (git_submodule_parse_update(&sm->update, value) < 0)
+ return -1;
+ sm->update_default = sm->update;
}
else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
if (git__parse_bool(&sm->fetch_recurse, value) < 0)
return submodule_config_error("fetchRecurseSubmodules", value);
}
else if (strcasecmp(property, "ignore") == 0) {
- int val;
- if (git_config_lookup_map_value(
- &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0)
- return submodule_config_error("ignore", value);
- sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
+ if (git_submodule_parse_ignore(&sm->ignore, value) < 0)
+ return -1;
+ sm->ignore_default = sm->ignore;
}
/* ignore other unknown submodule properties */
@@ -1094,13 +1189,15 @@ static int submodule_load_from_config(
static int submodule_load_from_wd_lite(
git_submodule *sm, const char *name, void *payload)
{
- git_repository *repo = git_submodule_owner(sm);
git_buf path = GIT_BUF_INIT;
GIT_UNUSED(name);
GIT_UNUSED(payload);
- if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0)
+ if (git_repository_is_bare(sm->repo))
+ return 0;
+
+ if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0)
return -1;
if (git_path_isdir(path.ptr))
@@ -1114,18 +1211,6 @@ static int submodule_load_from_wd_lite(
return 0;
}
-static void submodule_mode_mismatch(
- git_repository *repo, const char *path, unsigned int flag)
-{
- khiter_t pos = git_strmap_lookup_index(repo->submodules, path);
-
- if (git_strmap_valid_index(repo->submodules, pos)) {
- git_submodule *sm = git_strmap_value_at(repo->submodules, pos);
-
- sm->flags |= flag;
- }
-}
-
static int load_submodule_config_from_index(
git_repository *repo, git_oid *gitmodules_oid)
{
@@ -1138,25 +1223,27 @@ static int load_submodule_config_from_index(
(error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0)
return error;
- error = git_iterator_current(&entry, i);
-
- while (!error && entry != NULL) {
-
- if (S_ISGITLINK(entry->mode)) {
- error = submodule_load_from_index(repo, entry);
- if (error < 0)
- break;
- } else {
- submodule_mode_mismatch(
- repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
-
- if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
- git_oid_cpy(gitmodules_oid, &entry->oid);
- }
-
- error = git_iterator_advance(&entry, i);
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path);
+ git_submodule *sm;
+
+ if (git_strmap_valid_index(repo->submodules, pos)) {
+ sm = git_strmap_value_at(repo->submodules, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_index_entry(sm, entry);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get(&sm, repo, entry->path, NULL))
+ submodule_update_from_index_entry(sm, entry);
+ } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
+ git_oid_cpy(gitmodules_oid, &entry->oid);
}
+ if (error == GIT_ITEROVER)
+ error = 0;
+
git_iterator_free(i);
return error;
@@ -1170,34 +1257,42 @@ static int load_submodule_config_from_head(
git_iterator *i;
const git_index_entry *entry;
- if ((error = git_repository_head_tree(&head, repo)) < 0)
- return error;
+ /* if we can't look up current head, then there's no submodule in it */
+ if (git_repository_head_tree(&head, repo) < 0) {
+ giterr_clear();
+ return 0;
+ }
if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {
git_tree_free(head);
return error;
}
- error = git_iterator_current(&entry, i);
-
- while (!error && entry != NULL) {
-
- if (S_ISGITLINK(entry->mode)) {
- error = submodule_load_from_head(repo, entry->path, &entry->oid);
- if (error < 0)
- break;
- } else {
- submodule_mode_mismatch(
- repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
-
- if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
- git_oid_iszero(gitmodules_oid))
- git_oid_cpy(gitmodules_oid, &entry->oid);
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path);
+ git_submodule *sm;
+
+ if (git_strmap_valid_index(repo->submodules, pos)) {
+ sm = git_strmap_value_at(repo->submodules, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_head_data(
+ sm, entry->mode, &entry->oid);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get(&sm, repo, entry->path, NULL))
+ submodule_update_from_head_data(
+ sm, entry->mode, &entry->oid);
+ } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
+ git_oid_iszero(gitmodules_oid)) {
+ git_oid_cpy(gitmodules_oid, &entry->oid);
}
-
- error = git_iterator_advance(&entry, i);
}
+ if (error == GIT_ITEROVER)
+ error = 0;
+
git_iterator_free(i);
git_tree_free(head);
@@ -1376,7 +1471,7 @@ static int submodule_update_config(
assert(submodule);
- error = git_repository_config__weakptr(&config, submodule->owner);
+ error = git_repository_config__weakptr(&config, submodule->repo);
if (error < 0)
return error;
@@ -1404,11 +1499,13 @@ cleanup:
return error;
}
-static int submodule_index_status(unsigned int *status, git_submodule *sm)
+static void submodule_get_index_status(unsigned int *status, git_submodule *sm)
{
const git_oid *head_oid = git_submodule_head_id(sm);
const git_oid *index_oid = git_submodule_index_id(sm);
+ *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS;
+
if (!head_oid) {
if (index_oid)
*status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
@@ -1417,27 +1514,22 @@ static int submodule_index_status(unsigned int *status, git_submodule *sm)
*status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
else if (!git_oid_equal(head_oid, index_oid))
*status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
-
- return 0;
}
-static int submodule_wd_status(unsigned int *status, git_submodule *sm)
+static void submodule_get_wd_status(
+ unsigned int *status,
+ git_submodule *sm,
+ git_repository *sm_repo,
+ git_submodule_ignore_t ign)
{
- int error = 0;
- const git_oid *wd_oid, *index_oid;
- git_repository *sm_repo = NULL;
-
- /* open repo now if we need it (so wd_id() call won't reopen) */
- if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||
- sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&
- (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)
- {
- if ((error = git_submodule_open(&sm_repo, sm)) < 0)
- return error;
- }
+ const git_oid *index_oid = git_submodule_index_id(sm);
+ const git_oid *wd_oid =
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL;
+ git_tree *sm_head = NULL;
+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+ git_diff_list *diff;
- index_oid = git_submodule_index_id(sm);
- wd_oid = git_submodule_wd_id(sm);
+ *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS;
if (!index_oid) {
if (wd_oid)
@@ -1453,59 +1545,49 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
else if (!git_oid_equal(index_oid, wd_oid))
*status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
- if (sm_repo != NULL) {
- git_tree *sm_head;
- git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
- git_diff_list *diff;
-
- /* the diffs below could be optimized with an early termination
- * option to the git_diff functions, but for now this is sufficient
- * (and certainly no worse that what core git does).
- */
-
- /* perform head-to-index diff on submodule */
-
- if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)
- return error;
+ /* if we have no repo, then we're done */
+ if (!sm_repo)
+ return;
- if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
- opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+ /* the diffs below could be optimized with an early termination
+ * option to the git_diff functions, but for now this is sufficient
+ * (and certainly no worse that what core git does).
+ */
- error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt);
+ if (ign == GIT_SUBMODULE_IGNORE_NONE)
+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
- if (!error) {
+ /* if we don't have an orphaned head, check diff with index */
+ if (git_repository_head_tree(&sm_head, sm_repo) < 0)
+ giterr_clear();
+ else {
+ /* perform head to index diff on submodule */
+ if (git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt) < 0)
+ giterr_clear();
+ else {
if (git_diff_num_deltas(diff) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
-
git_diff_list_free(diff);
diff = NULL;
}
git_tree_free(sm_head);
+ }
- if (error < 0)
- return error;
-
- /* perform index-to-workdir diff on submodule */
-
- error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt);
-
- if (!error) {
- size_t untracked =
- git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
-
- if (untracked > 0)
- *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+ /* perform index-to-workdir diff on submodule */
+ if (git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt) < 0)
+ giterr_clear();
+ else {
+ size_t untracked =
+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
- if (git_diff_num_deltas(diff) != untracked)
- *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+ if (untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
- git_diff_list_free(diff);
- diff = NULL;
- }
+ if (git_diff_num_deltas(diff) != untracked)
+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
- git_repository_free(sm_repo);
+ git_diff_list_free(diff);
+ diff = NULL;
}
-
- return error;
}
diff --git a/src/submodule.h b/src/submodule.h
index ba8e2518e..b05937503 100644
--- a/src/submodule.h
+++ b/src/submodule.h
@@ -7,6 +7,10 @@
#ifndef INCLUDE_submodule_h__
#define INCLUDE_submodule_h__
+#include "git2/submodule.h"
+#include "git2/repository.h"
+#include "fileops.h"
+
/* Notes:
*
* Submodule information can be in four places: the index, the config files
@@ -44,44 +48,51 @@
* an entry for every submodule found in the HEAD and index, and for every
* submodule described in .gitmodules. The fields are as follows:
*
- * - `owner` is the git_repository containing this submodule
+ * - `rc` tracks the refcount of how many hash table entries in the
+ * git_submodule_cache there are for this submodule. It only comes into
+ * play if the name and path of the submodule differ.
+ *
* - `name` is the name of the submodule from .gitmodules.
* - `path` is the path to the submodule from the repo root. It is almost
* always the same as `name`.
* - `url` is the url for the submodule.
- * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD.
- * - `index_oid` is the SHA1 for the submodule recorded in the index.
- * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule.
* - `update` is a git_submodule_update_t value - see gitmodules(5) update.
+ * - `update_default` is the update value from the config
* - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.
+ * - `ignore_default` is the ignore value from the config
* - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.
- * - `refcount` tracks how many hashmap entries there are for this submodule.
- * It only comes into play if the name and path of the submodule differ.
- * - `flags` is for internal use, tracking where this submodule has been
- * found (head, index, config, workdir) and other misc info about it.
+ *
+ * - `repo` is the parent repository that contains this submodule.
+ * - `flags` after for internal use, tracking where this submodule has been
+ * found (head, index, config, workdir) and known status info, etc.
+ * - `head_oid` is the SHA1 for the submodule path in the repo HEAD.
+ * - `index_oid` is the SHA1 for the submodule recorded in the index.
+ * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule.
*
* If the submodule has been added to .gitmodules but not yet git added,
- * then the `index_oid` will be valid and zero. If the submodule has been
- * deleted, but the delete has not been committed yet, then the `index_oid`
- * will be set, but the `url` will be NULL.
+ * then the `index_oid` will be zero but still marked valid. If the
+ * submodule has been deleted, but the delete has not been committed yet,
+ * then the `index_oid` will be set, but the `url` will be NULL.
*/
struct git_submodule {
- git_repository *owner;
+ git_refcount rc;
+
+ /* information from config */
char *name;
- char *path; /* important: may point to same string data as "name" */
+ char *path; /* important: may just point to "name" string */
char *url;
- uint32_t flags;
- git_oid head_oid;
- git_oid index_oid;
- git_oid wd_oid;
- /* information from config */
git_submodule_update_t update;
git_submodule_update_t update_default;
git_submodule_ignore_t ignore;
git_submodule_ignore_t ignore_default;
int fetch_recurse;
+
/* internal information */
- int refcount;
+ git_repository *repo;
+ uint32_t flags;
+ git_oid head_oid;
+ git_oid index_oid;
+ git_oid wd_oid;
};
/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
@@ -99,4 +110,29 @@ enum {
#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
((S) & ~(0xFFFFFFFFu << 20))
+/* Internal status fn returns status and optionally the various OIDs */
+extern int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign);
+
+/* Open submodule repository as bare repo for quick HEAD check, etc. */
+extern int git_submodule_open_bare(
+ git_repository **repo,
+ git_submodule *submodule);
+
+/* Release reference to submodule object - not currently for external use */
+extern void git_submodule_free(git_submodule *sm);
+
+extern int git_submodule_parse_ignore(
+ git_submodule_ignore_t *out, const char *value);
+extern int git_submodule_parse_update(
+ git_submodule_update_t *out, const char *value);
+
+extern const char *git_submodule_ignore_to_str(git_submodule_ignore_t);
+extern const char *git_submodule_update_to_str(git_submodule_update_t);
+
#endif
diff --git a/src/tag.c b/src/tag.c
index 735ba7e1d..71f4c1eb1 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -13,20 +13,17 @@
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/signature.h"
+#include "git2/odb_backend.h"
-void git_tag__free(git_tag *tag)
+void git_tag__free(void *_tag)
{
+ git_tag *tag = _tag;
git_signature_free(tag->tagger);
git__free(tag->message);
git__free(tag->tag_name);
git__free(tag);
}
-const git_oid *git_tag_id(const git_tag *c)
-{
- return git_object_id((const git_object *)c);
-}
-
int git_tag_target(git_object **target, const git_tag *t)
{
assert(t);
@@ -68,7 +65,7 @@ static int tag_error(const char *str)
return -1;
}
-int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
+static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end)
{
static const char *tag_types[] = {
NULL, "commit\n", "tree\n", "blob\n", "tag\n"
@@ -78,8 +75,6 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
size_t text_len;
char *search;
- const char *buffer_end = buffer + length;
-
if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0)
return tag_error("Object field invalid");
@@ -156,6 +151,15 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
return 0;
}
+int git_tag__parse(void *_tag, git_odb_object *odb_obj)
+{
+ git_tag *tag = _tag;
+ const char *buffer = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
+
+ return tag_parse(tag, buffer, buffer_end);
+}
+
static int retrieve_tag_reference(
git_reference **tag_reference_out,
git_buf *ref_name_out,
@@ -276,23 +280,36 @@ cleanup:
}
int git_tag_create(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- const git_signature *tagger,
- const char *message,
- int allow_ref_overwrite)
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int allow_ref_overwrite)
{
return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1);
}
+int git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message)
+{
+ assert(oid && repo && tag_name && target && tagger && message);
+
+ return write_tag_annotation(oid, repo, tag_name, target, tagger, message);
+}
+
int git_tag_create_lightweight(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- int allow_ref_overwrite)
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ int allow_ref_overwrite)
{
return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0);
}
@@ -316,14 +333,14 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu
return -1;
/* validate the buffer */
- if (git_tag__parse_buffer(&tag, buffer, strlen(buffer)) < 0)
+ if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0)
return -1;
/* validate the target */
if (git_odb_read(&target_obj, odb, &tag.target) < 0)
goto on_error;
- if (tag.type != target_obj->raw.type) {
+ if (tag.type != target_obj->cached.type) {
giterr_set(GITERR_TAG, "The type for the given target is invalid");
goto on_error;
}
@@ -389,14 +406,8 @@ int git_tag_delete(git_repository *repo, const char *tag_name)
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)
-{
- assert(tag);
- return git_tag__parse_buffer(tag, obj->raw.data, obj->raw.len);
+ return error;
}
typedef struct {
@@ -429,7 +440,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, &tags_cb, &data);
+ return git_reference_foreach_name(repo, &tags_cb, &data);
}
typedef struct {
diff --git a/src/tag.h b/src/tag.h
index c8e421ee6..d0cd393c7 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -22,8 +22,7 @@ struct git_tag {
char *message;
};
-void git_tag__free(git_tag *tag);
-int git_tag__parse(git_tag *tag, git_odb_object *obj);
-int git_tag__parse_buffer(git_tag *tag, const char *data, size_t len);
+void git_tag__free(void *tag);
+int git_tag__parse(void *tag, git_odb_object *obj);
#endif
diff --git a/src/thread-utils.h b/src/thread-utils.h
index 2ca290adf..04e02959f 100644
--- a/src/thread-utils.h
+++ b/src/thread-utils.h
@@ -7,8 +7,6 @@
#ifndef INCLUDE_thread_utils_h__
#define INCLUDE_thread_utils_h__
-#include "common.h"
-
/* Common operations even if threading has been disabled */
typedef struct {
#if defined(GIT_WIN32)
@@ -18,10 +16,27 @@ typedef struct {
#endif
} git_atomic;
-GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
-{
- a->val = val;
-}
+#ifdef GIT_ARCH_64
+
+typedef struct {
+#if defined(GIT_WIN32)
+ __int64 val;
+#else
+ int64_t val;
+#endif
+} git_atomic64;
+
+typedef git_atomic64 git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic64_add
+
+#else
+
+typedef git_atomic git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic_add
+
+#endif
#ifdef GIT_THREADS
@@ -46,6 +61,17 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
#define git_cond_signal(c) pthread_cond_signal(c)
#define git_cond_broadcast(c) pthread_cond_broadcast(c)
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+#if defined(GIT_WIN32)
+ InterlockedExchange(&a->val, (LONG)val);
+#elif defined(__GNUC__)
+ __sync_lock_test_and_set(&a->val, val);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
GIT_INLINE(int) git_atomic_inc(git_atomic *a)
{
#if defined(GIT_WIN32)
@@ -57,6 +83,17 @@ GIT_INLINE(int) git_atomic_inc(git_atomic *a)
#endif
}
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
GIT_INLINE(int) git_atomic_dec(git_atomic *a)
{
#if defined(GIT_WIN32)
@@ -68,6 +105,45 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
#endif
}
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ volatile void *foundval;
+#if defined(GIT_WIN32)
+ foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
+#elif defined(__GNUC__)
+ foundval = __sync_val_compare_and_swap(ptr, oldval, newval);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+ return (foundval == oldval) ? oldval : newval;
+}
+
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangePointer(ptr, newval);
+#else
+ return __sync_lock_test_and_set(ptr, newval);
+#endif
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd64(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+#endif
+
#else
#define git_thread unsigned int
@@ -78,7 +154,7 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
/* Pthreads Mutex */
#define git_mutex unsigned int
-#define git_mutex_init(a) (void)0
+#define git_mutex_init(a) 0
#define git_mutex_lock(a) 0
#define git_mutex_unlock(a) (void)0
#define git_mutex_free(a) (void)0
@@ -91,18 +167,78 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
#define git_cond_signal(c) (void)0
#define git_cond_broadcast(c) (void)0
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+ a->val = val;
+}
+
GIT_INLINE(int) git_atomic_inc(git_atomic *a)
{
return ++a->val;
}
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
GIT_INLINE(int) git_atomic_dec(git_atomic *a)
{
return --a->val;
}
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ if (*ptr == oldval)
+ *ptr = newval;
+ else
+ oldval = newval;
+ return oldval;
+}
+
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+ volatile void *old = *ptr;
+ *ptr = newval;
+ return old;
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
#endif
+#endif
+
+GIT_INLINE(int) git_atomic_get(git_atomic *a)
+{
+ return (int)a->val;
+}
+
+/* Atomically replace oldval with newval
+ * @return oldval if it was replaced or newval if it was not
+ */
+#define git__compare_and_swap(P,O,N) \
+ git___compare_and_swap((void * volatile *)P, O, N)
+
+#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val)
+
extern int git_online_cpus(void);
+#if defined(GIT_THREADS) && defined(GIT_WIN32)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+#elif defined(GIT_THREADS)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+#else
+# define GIT_MEMORY_BARRIER /* noop */
+#endif
+
#endif /* INCLUDE_thread_utils_h__ */
diff --git a/src/trace.c b/src/trace.c
index 159ac91cc..ee5039f56 100644
--- a/src/trace.c
+++ b/src/trace.c
@@ -25,7 +25,7 @@ int git_trace_set(git_trace_level_t level, git_trace_callback callback)
git_trace__data.level = level;
git_trace__data.callback = callback;
GIT_MEMORY_BARRIER;
-
+
return 0;
#else
GIT_UNUSED(level);
@@ -36,4 +36,3 @@ int git_trace_set(git_trace_level_t level, git_trace_callback callback)
return -1;
#endif
}
-
diff --git a/src/trace.h b/src/trace.h
index f4bdff88a..77b1e03ef 100644
--- a/src/trace.h
+++ b/src/trace.h
@@ -25,14 +25,14 @@ GIT_INLINE(void) git_trace__write_fmt(
git_trace_level_t level,
const char *fmt, ...)
{
- git_trace_callback callback = git_trace__data.callback;
+ 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);
diff --git a/src/transport.c b/src/transport.c
index adb6d5355..354789db1 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -18,19 +18,27 @@ typedef struct transport_definition {
void *param;
} transport_definition;
-static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
-static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
-
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
+#ifdef GIT_SSH
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
+#endif
+
+static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
+#ifdef GIT_SSH
+static transport_definition ssh_transport_definition = { "ssh://", 1, git_transport_smart, &ssh_subtransport_definition };
+#else
+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
+#endif
static transport_definition transports[] = {
{"git://", 1, git_transport_smart, &git_subtransport_definition},
{"http://", 1, git_transport_smart, &http_subtransport_definition},
{"https://", 1, git_transport_smart, &http_subtransport_definition},
{"file://", 1, git_transport_local, NULL},
- {"git+ssh://", 1, git_transport_dummy, NULL},
- {"ssh+git://", 1, git_transport_dummy, NULL},
+#ifdef GIT_SSH
+ {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition},
+#endif
{NULL, 0, 0}
};
@@ -65,7 +73,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
/* It could be a SSH remote path. Check to see if there's a :
* SSH is an unsupported transport mechanism in this version of libgit2 */
if (!definition && strrchr(url, ':'))
- definition = &dummy_transport_definition;
+ definition = &dummy_transport_definition;
#else
/* For other systems, perform the SSH check first, to avoid going to the
* filesystem if it is not necessary */
@@ -73,7 +81,11 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
/* It could be a SSH remote path. Check to see if there's a :
* SSH is an unsupported transport mechanism in this version of libgit2 */
if (!definition && strrchr(url, ':'))
- definition = &dummy_transport_definition;
+#ifdef GIT_SSH
+ definition = &ssh_transport_definition;
+#else
+ definition = &dummy_transport_definition;
+#endif
/* Check to see if the path points to a file on the local file system */
if (!definition && git_path_exists(url) && git_path_isdir(url))
@@ -85,7 +97,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
*callback = definition->fn;
*param = definition->param;
-
+
return 0;
}
diff --git a/src/transports/cred.c b/src/transports/cred.c
index ecb026062..a6727e902 100644
--- a/src/transports/cred.c
+++ b/src/transports/cred.c
@@ -12,16 +12,17 @@
static void plaintext_free(struct git_cred *cred)
{
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
- size_t pass_len = strlen(c->password);
git__free(c->username);
/* Zero the memory which previously held the password */
- memset(c->password, 0x0, pass_len);
- git__free(c->password);
-
- memset(c, 0, sizeof(*c));
+ if (c->password) {
+ size_t pass_len = strlen(c->password);
+ git__memzero(c->password, pass_len);
+ git__free(c->password);
+ }
+ git__memzero(c, sizeof(*c));
git__free(c);
}
@@ -32,8 +33,7 @@ int git_cred_userpass_plaintext_new(
{
git_cred_userpass_plaintext *c;
- if (!cred)
- return -1;
+ assert(cred);
c = git__malloc(sizeof(git_cred_userpass_plaintext));
GITERR_CHECK_ALLOC(c);
@@ -58,3 +58,97 @@ int git_cred_userpass_plaintext_new(
*cred = &c->parent;
return 0;
}
+
+static void ssh_keyfile_passphrase_free(struct git_cred *cred)
+{
+ git_cred_ssh_keyfile_passphrase *c =
+ (git_cred_ssh_keyfile_passphrase *)cred;
+
+ git__free(c->publickey);
+ git__free(c->privatekey);
+
+ if (c->passphrase) {
+ /* Zero the memory which previously held the passphrase */
+ size_t pass_len = strlen(c->passphrase);
+ git__memzero(c->passphrase, pass_len);
+ git__free(c->passphrase);
+ }
+
+ git__memzero(c, sizeof(*c));
+ git__free(c);
+}
+
+static void ssh_publickey_free(struct git_cred *cred)
+{
+ git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred;
+
+ git__free(c->publickey);
+
+ git__memzero(c, sizeof(*c));
+ git__free(c);
+}
+
+int git_cred_ssh_keyfile_passphrase_new(
+ git_cred **cred,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+ git_cred_ssh_keyfile_passphrase *c;
+
+ assert(cred && privatekey);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_keyfile_passphrase));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE;
+ c->parent.free = ssh_keyfile_passphrase_free;
+
+ c->privatekey = git__strdup(privatekey);
+ GITERR_CHECK_ALLOC(c->privatekey);
+
+ if (publickey) {
+ c->publickey = git__strdup(publickey);
+ GITERR_CHECK_ALLOC(c->publickey);
+ }
+
+ if (passphrase) {
+ c->passphrase = git__strdup(passphrase);
+ GITERR_CHECK_ALLOC(c->passphrase);
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_publickey_new(
+ git_cred **cred,
+ const char *publickey,
+ size_t publickey_len,
+ git_cred_sign_callback sign_callback,
+ void *sign_data)
+{
+ git_cred_ssh_publickey *c;
+
+ assert(cred);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_publickey));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY;
+ c->parent.free = ssh_publickey_free;
+
+ if (publickey_len > 0) {
+ c->publickey = git__malloc(publickey_len);
+ GITERR_CHECK_ALLOC(c->publickey);
+
+ memcpy(c->publickey, publickey, publickey_len);
+ }
+
+ c->publickey_len = publickey_len;
+ c->sign_callback = sign_callback;
+ c->sign_data = sign_data;
+
+ *cred = &c->parent;
+ return 0;
+}
diff --git a/src/transports/local.c b/src/transports/local.c
index ce89bb213..a9da8146c 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -36,7 +36,8 @@ typedef struct {
git_atomic cancelled;
git_repository *repo;
git_vector refs;
- unsigned connected : 1;
+ unsigned connected : 1,
+ have_refs : 1;
} transport_local;
static int add_ref(transport_local *t, const char *name)
@@ -118,15 +119,24 @@ on_error:
static int store_refs(transport_local *t)
{
- unsigned int i;
+ size_t i;
+ git_remote_head *head;
git_strarray ref_names = {0};
assert(t);
- if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&t->refs, ref_names.count, NULL) < 0)
+ if (git_reference_list(&ref_names, t->repo) < 0)
goto on_error;
+ /* Clear all heads we might have fetched in a previous connect */
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+
+ /* Clear the vector so we can reuse it */
+ git_vector_clear(&t->refs);
+
/* Sort the references first */
git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
@@ -139,6 +149,7 @@ static int store_refs(transport_local *t)
goto on_error;
}
+ t->have_refs = 1;
git_strarray_free(&ref_names);
return 0;
@@ -208,8 +219,8 @@ static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *pay
unsigned int i;
git_remote_head *head = NULL;
- if (!t->connected) {
- giterr_set(GITERR_NET, "The transport is not connected");
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
return -1;
}
@@ -280,7 +291,7 @@ static int local_push_copy_object(
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) {
+ } 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.");
@@ -346,7 +357,7 @@ static int local_push(
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
+ /* 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) {
@@ -493,7 +504,7 @@ static int local_download_pack(
/* Tag or some other wanted object. Add it on its own */
error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
}
- git_object_free(obj);
+ git_object_free(obj);
}
/* Walk the objects, building a packfile */
@@ -569,8 +580,6 @@ 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;
@@ -579,13 +588,6 @@ static int local_close(git_transport *transport)
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;
@@ -597,6 +599,15 @@ static int local_close(git_transport *transport)
static void local_free(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
+ size_t i;
+ git_remote_head *head;
+
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+
+ git_vector_free(&t->refs);
/* Close the transport, if it's still open. */
local_close(transport);
@@ -630,6 +641,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param)
t->parent.read_flags = local_read_flags;
t->parent.cancel = local_cancel;
+ git_vector_init(&t->refs, 0, NULL);
t->owner = owner;
*out = (git_transport *) t;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index bfcce0c08..416eb221f 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -253,7 +253,6 @@ static int git_smart__read_flags(git_transport *transport, int *flags)
static int git_smart__close(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
- git_vector *refs = &t->refs;
git_vector *common = &t->common;
unsigned int i;
git_pkt *p;
@@ -261,11 +260,6 @@ static int git_smart__close(git_transport *transport)
ret = git_smart__reset_stream(t, true);
- git_vector_foreach(refs, i, p)
- git_pkt_free(p);
-
- git_vector_free(refs);
-
git_vector_foreach(common, i, p)
git_pkt_free(p);
@@ -284,6 +278,9 @@ static int git_smart__close(git_transport *transport)
static void git_smart__free(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+ git_pkt *p;
/* Make sure that the current stream is closed, if we have one. */
git_smart__close(transport);
@@ -291,6 +288,11 @@ static void git_smart__free(git_transport *transport)
/* Free the subtransport */
t->wrapped->free(t->wrapped);
+ git_vector_foreach(refs, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(refs);
+
git__free(t);
}
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 8acedeb49..0cd5e831d 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -5,6 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2.h"
+#include "git2/odb_backend.h"
#include "smart.h"
#include "refs.h"
@@ -20,12 +21,18 @@ int git_smart__store_refs(transport_smart *t, int flushes)
gitno_buffer *buf = &t->buffer;
git_vector *refs = &t->refs;
int error, flush = 0, recvd;
- const char *line_end;
- git_pkt *pkt;
+ const char *line_end = NULL;
+ git_pkt *pkt = NULL;
+ git_pkt_ref *ref;
+ size_t i;
/* Clear existing refs in case git_remote_connect() is called again
* after git_remote_disconnect().
*/
+ git_vector_foreach(refs, i, ref) {
+ git__free(ref->head.name);
+ git__free(ref);
+ }
git_vector_clear(refs);
do {
@@ -128,7 +135,7 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
static int recv_pkt(git_pkt **out, gitno_buffer *buf)
{
const char *ptr = buf->data, *line_end = ptr;
- git_pkt *pkt;
+ git_pkt *pkt = NULL;
int pkt_type, error = 0, ret;
do {
@@ -186,7 +193,7 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
unsigned int i;
git_reference *ref;
- if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
+ if (git_reference_list(&refs, repo) < 0)
return -1;
if (git_revwalk_new(&walk, repo) < 0)
@@ -365,7 +372,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
return error;
if (pkt->type == GIT_PKT_NAK ||
- (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
+ (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
git__free(pkt);
break;
}
@@ -569,7 +576,7 @@ static int add_push_report_pkt(git_push *push, git_pkt *pkt)
switch (pkt->type) {
case GIT_PKT_OK:
- status = git__malloc(sizeof(push_status));
+ status = git__calloc(1, sizeof(push_status));
GITERR_CHECK_ALLOC(status);
status->msg = NULL;
status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
@@ -633,8 +640,8 @@ static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)
static int parse_report(gitno_buffer *buf, git_push *push)
{
- git_pkt *pkt;
- const char *line_end;
+ git_pkt *pkt = NULL;
+ const char *line_end = NULL;
int error, recvd;
for (;;) {
@@ -806,13 +813,13 @@ int git_smart__push(git_transport *transport, git_push *push)
transport_smart *t = (transport_smart *)transport;
git_smart_subtransport_stream *s;
git_buf pktline = GIT_BUF_INIT;
- int error = -1;
+ int error = -1, need_pack = 0;
+ push_spec *spec;
+ unsigned int i;
#ifdef PUSH_DEBUG
{
git_remote_head *head;
- push_spec *spec;
- unsigned int i;
char hex[41]; hex[40] = '\0';
git_vector_foreach(&push->remote->refs, i, head) {
@@ -830,10 +837,23 @@ int git_smart__push(git_transport *transport, git_push *push)
}
#endif
+ /*
+ * Figure out if we need to send a packfile; which is in all
+ * cases except when we only send delete commands
+ */
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->lref) {
+ need_pack = 1;
+ break;
+ }
+ }
+
if (git_smart__get_push_stream(t, &s) < 0 ||
gen_pktline(&pktline, push) < 0 ||
- s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0 ||
- git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)
+ s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0)
+ goto on_error;
+
+ if (need_pack && git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)
goto on_error;
/* If we sent nothing or the server doesn't support report-status, then
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
new file mode 100644
index 000000000..7fb53bc3c
--- /dev/null
+++ b/src/transports/ssh.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+#include "smart.h"
+
+#ifdef GIT_SSH
+
+#include <libssh2.h>
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_ssh[] = "ssh://";
+static const char default_user[] = "git";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ ssh_stream *current_stream;
+ git_cred *cred;
+} ssh_subtransport;
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: git-upload-pack '/libgit2/libgit2'
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *repo;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ url = url + strlen(prefix_ssh);
+ repo = strchr(url, '/');
+ } else {
+ repo = strchr(url, ':');
+ }
+
+ if (!repo) {
+ giterr_set(GITERR_NET, "Malformed git protocol URL");
+ return -1;
+ }
+
+ int len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%s '%s'", cmd, repo);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = libssh2_channel_exec(s->channel, request.ptr);
+ if (error < 0) {
+ giterr_set(GITERR_NET, "SSH could not execute request");
+ goto cleanup;
+ }
+
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int rc;
+ ssh_stream *s = (ssh_stream *)stream;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < 0) {
+ giterr_set(GITERR_NET, "SSH could not read data");
+ return -1;
+ }
+
+ *bytes_read = rc;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if (libssh2_channel_write(s->channel, buffer, len) < 0) {
+ giterr_set(GITERR_NET, "SSH could not write data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->channel) {
+ libssh2_channel_close(s->channel);
+ libssh2_channel_free(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->session) {
+ libssh2_session_free(s->session);
+ s->session = NULL;
+ }
+
+ if (s->socket.socket) {
+ (void)gitno_close(&s->socket);
+ /* can't do anything here with error return value */
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ ssh_stream *s;
+
+ assert(stream);
+
+ s = git__calloc(sizeof(ssh_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = ssh_stream_read;
+ s->parent.write = ssh_stream_write;
+ s->parent.free = ssh_stream_free;
+
+ s->cmd = cmd;
+
+ s->url = git__strdup(url);
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int git_ssh_extract_url_parts(
+ char **host,
+ char **username,
+ const char *url)
+{
+ char *colon, *at;
+ const char *start;
+
+ colon = strchr(url, ':');
+
+ if (colon == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL: missing :");
+ return -1;
+ }
+
+ at = strchr(url, '@');
+ if (at) {
+ start = at+1;
+ *username = git__substrdup(url, at - url);
+ } else {
+ start = url;
+ *username = git__strdup(default_user);
+ }
+ GITERR_CHECK_ALLOC(*username);
+
+ *host = git__substrdup(start, colon - start);
+ GITERR_CHECK_ALLOC(*host);
+
+ return 0;
+}
+
+static int _git_ssh_authenticate_session(
+ LIBSSH2_SESSION* session,
+ const char *user,
+ git_cred* cred)
+{
+ int rc;
+
+ do {
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ rc = libssh2_userauth_password(session, c->username, c->password);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: {
+ git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred;
+ rc = libssh2_userauth_publickey_fromfile(
+ session, user, c->publickey, c->privatekey, c->passphrase);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_PUBLICKEY: {
+ git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred;
+ rc = libssh2_userauth_publickey(
+ session, user, (const unsigned char *)c->publickey,
+ c->publickey_len, c->sign_callback, &c->sign_data);
+ break;
+ }
+ default:
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ }
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc != 0) {
+ giterr_set(GITERR_NET, "Failed to authenticate SSH session");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _git_ssh_session_create(
+ LIBSSH2_SESSION** session,
+ gitno_socket socket)
+{
+ int rc = 0;
+ LIBSSH2_SESSION* s;
+
+ assert(session);
+
+ s = libssh2_session_init();
+ if (!s) {
+ giterr_set(GITERR_NET, "Failed to initialize SSH session");
+ return -1;
+ }
+
+ do {
+ rc = libssh2_session_startup(s, socket.socket);
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (0 != rc) {
+ libssh2_session_free(s);
+ giterr_set(GITERR_NET, "Failed to start SSH session");
+ return -1;
+ }
+
+ libssh2_session_set_blocking(s, 1);
+
+ *session = s;
+
+ return 0;
+}
+
+static int _git_ssh_setup_conn(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ char *host, *port=NULL, *user=NULL, *pass=NULL;
+ const char *default_port="22";
+ ssh_stream *s;
+ LIBSSH2_SESSION* session=NULL;
+ LIBSSH2_CHANNEL* channel=NULL;
+
+ *stream = NULL;
+ if (ssh_stream_alloc(t, url, cmd, stream) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ url = url + strlen(prefix_ssh);
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, default_port) < 0)
+ goto on_error;
+ } else {
+ if (git_ssh_extract_url_parts(&host, &user, url) < 0)
+ goto on_error;
+ port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(port);
+ }
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ if (user && pass) {
+ if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
+ goto on_error;
+ } else if (t->owner->cred_acquire_cb) {
+ if (t->owner->cred_acquire_cb(
+ &t->cred, t->owner->url, user,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT |
+ GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE,
+ t->owner->cred_acquire_payload) < 0)
+ goto on_error;
+
+ if (!t->cred) {
+ giterr_set(GITERR_NET, "Callback failed to initialize SSH credentials");
+ goto on_error;
+ }
+ } else {
+ giterr_set(GITERR_NET, "Cannot set up SSH connection without credentials");
+ goto on_error;
+ }
+ assert(t->cred);
+
+ if (!user) {
+ user = git__strdup(default_user);
+ GITERR_CHECK_ALLOC(user);
+ }
+
+ if (_git_ssh_session_create(&session, s->socket) < 0)
+ goto on_error;
+
+ if (_git_ssh_authenticate_session(session, user, t->cred) < 0)
+ goto on_error;
+
+ channel = libssh2_channel_open_session(session);
+ if (!channel) {
+ giterr_set(GITERR_NET, "Failed to open SSH channel");
+ goto on_error;
+ }
+
+ libssh2_channel_set_blocking(channel, 1);
+
+ s->session = session;
+ s->channel = channel;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+
+ return 0;
+
+on_error:
+ s->session = NULL;
+ s->channel = NULL;
+ t->current_stream = NULL;
+
+ if (*stream)
+ ssh_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+
+ if (session)
+ libssh2_session_free(session);
+
+ return -1;
+}
+
+static int ssh_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int ssh_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _ssh_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return ssh_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return ssh_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _ssh_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _ssh_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+#endif
+
+int git_smart_subtransport_ssh(
+ git_smart_subtransport **out, git_transport *owner)
+{
+#ifdef GIT_SSH
+ ssh_subtransport *t;
+
+ assert(out);
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = _ssh_action;
+ t->parent.close = _ssh_close;
+ t->parent.free = _ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index e502001cb..95e422dc0 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -19,6 +19,8 @@
#include <winhttp.h>
#pragma comment(lib, "winhttp")
+#include <strsafe.h>
+
/* For UuidCreate */
#pragma comment(lib, "rpcrt4")
@@ -893,7 +895,7 @@ static int winhttp_connect(
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
wchar_t host[GIT_WIN_PATH];
int32_t port;
- const char *default_port;
+ const char *default_port = "80";
int ret;
if (!git__prefixcmp(url, prefix_http)) {
diff --git a/src/tree.c b/src/tree.c
index 17b3c378d..65d01b4d5 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -10,6 +10,9 @@
#include "tree.h"
#include "git2/repository.h"
#include "git2/object.h"
+#include "path.h"
+#include "tree-cache.h"
+#include "index.h"
#define DEFAULT_TREE_SIZE 16
#define MAX_FILEMODE_BYTES 6
@@ -149,7 +152,7 @@ static int tree_key_search(
/* Initial homing search; find an entry on the tree with
* the same prefix as the filename we're looking for */
if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0)
- return GIT_ENOTFOUND;
+ return GIT_ENOTFOUND; /* just a signal error; not passed back to user */
/* We found a common prefix. Look forward as long as
* there are entries that share the common prefix */
@@ -219,8 +222,9 @@ git_tree_entry *git_tree_entry_dup(const git_tree_entry *entry)
return copy;
}
-void git_tree__free(git_tree *tree)
+void git_tree__free(void *_tree)
{
+ git_tree *tree = _tree;
size_t i;
git_tree_entry *e;
@@ -231,16 +235,6 @@ void git_tree__free(git_tree *tree)
git__free(tree);
}
-const git_oid *git_tree_id(const git_tree *t)
-{
- return git_object_id((const git_object *)t);
-}
-
-git_repository *git_tree_owner(const git_tree *t)
-{
- return git_object_owner((const git_object *)t);
-}
-
git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
{
return (git_filemode_t)entry->attr;
@@ -280,25 +274,27 @@ int git_tree_entry_to_object(
}
static const git_tree_entry *entry_fromname(
- git_tree *tree, const char *name, size_t name_len)
+ const git_tree *tree, const char *name, size_t name_len)
{
size_t idx;
- if (tree_key_search(&idx, &tree->entries, name, name_len) < 0)
+ assert(tree->entries.sorted); /* be safe when we cast away constness */
+
+ if (tree_key_search(&idx, (git_vector *)&tree->entries, name, name_len) < 0)
return NULL;
return git_vector_get(&tree->entries, idx);
}
const git_tree_entry *git_tree_entry_byname(
- git_tree *tree, const char *filename)
+ const git_tree *tree, const char *filename)
{
assert(tree && filename);
return entry_fromname(tree, filename, strlen(filename));
}
const git_tree_entry *git_tree_entry_byindex(
- git_tree *tree, size_t idx)
+ const git_tree *tree, size_t idx)
{
assert(tree);
return git_vector_get(&tree->entries, idx);
@@ -320,9 +316,9 @@ const git_tree_entry *git_tree_entry_byoid(
return NULL;
}
-int git_tree__prefix_position(git_tree *tree, const char *path)
+int git_tree__prefix_position(const git_tree *tree, const char *path)
{
- git_vector *entries = &tree->entries;
+ const git_vector *entries = &tree->entries;
struct tree_key_search ksearch;
size_t at_pos;
@@ -332,8 +328,11 @@ int git_tree__prefix_position(git_tree *tree, const char *path)
ksearch.filename = path;
ksearch.filename_len = strlen(path);
+ assert(tree->entries.sorted); /* be safe when we cast away constness */
+
/* Find tree entry with appropriate prefix */
- git_vector_bsearch2(&at_pos, entries, &homing_search_cmp, &ksearch);
+ git_vector_bsearch2(
+ &at_pos, (git_vector *)entries, &homing_search_cmp, &ksearch);
for (; at_pos < entries->length; ++at_pos) {
const git_tree_entry *entry = entries->contents[at_pos];
@@ -371,8 +370,12 @@ static int tree_error(const char *str, const char *path)
return -1;
}
-static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end)
+int git_tree__parse(void *_tree, git_odb_object *odb_obj)
{
+ git_tree *tree = _tree;
+ const char *buffer = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
+
if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0)
return -1;
@@ -413,13 +416,9 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf
buffer += GIT_OID_RAWSZ;
}
- return 0;
-}
+ git_vector_sort(&tree->entries);
-int git_tree__parse(git_tree *tree, git_odb_object *obj)
-{
- assert(tree);
- return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len);
+ return 0;
}
static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
@@ -525,7 +524,6 @@ static int write_tree(
/* Write out the subtree */
written = write_tree(&sub_oid, repo, index, subdir, i);
if (written < 0) {
- tree_error("Failed to write subtree", subdir);
git__free(subdir);
goto on_error;
} else {
@@ -808,7 +806,7 @@ static size_t subpath_len(const char *path)
int git_tree_entry_bypath(
git_tree_entry **entry_out,
- git_tree *root,
+ const git_tree *root,
const char *path)
{
int error = 0;
diff --git a/src/tree.h b/src/tree.h
index b77bfd961..f07039a07 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -37,8 +37,8 @@ GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e)
extern int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2);
-void git_tree__free(git_tree *tree);
-int git_tree__parse(git_tree *tree, git_odb_object *obj);
+void git_tree__free(void *tree);
+int git_tree__parse(void *tree, git_odb_object *obj);
/**
* Lookup the first position in the tree with a given prefix.
@@ -47,7 +47,7 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj);
* @param prefix the beginning of a path to find in the tree.
* @return index of the first item at or after the given prefix.
*/
-int git_tree__prefix_position(git_tree *tree, const char *prefix);
+int git_tree__prefix_position(const git_tree *tree, const char *prefix);
/**
diff --git a/src/unix/posix.h b/src/unix/posix.h
index f4886c5d1..9c9f837b9 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -21,6 +21,8 @@
/* The OpenBSD realpath function behaves differently */
#if !defined(__OpenBSD__)
# define p_realpath(p, po) realpath(p, po)
+#else
+char *p_realpath(const char *, char *);
#endif
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
diff --git a/src/unix/realpath.c b/src/unix/realpath.c
index f382c2b73..15601bd22 100644
--- a/src/unix/realpath.c
+++ b/src/unix/realpath.c
@@ -22,7 +22,7 @@ char *p_realpath(const char *pathname, char *resolved)
/* Figure out if the file exists */
if (!access(ret, F_OK))
- ret;
+ return ret;
return NULL;
}
diff --git a/src/util.c b/src/util.c
index 44ac1af73..ad7603829 100644
--- a/src/util.c
+++ b/src/util.c
@@ -11,6 +11,7 @@
#include <ctype.h>
#include "posix.h"
#include "fileops.h"
+#include "cache.h"
#ifdef _MSC_VER
# include <Shlwapi.h>
@@ -32,13 +33,15 @@ int git_libgit2_capabilities()
#if defined(GIT_SSL) || defined(GIT_WINHTTP)
| GIT_CAP_HTTPS
#endif
+#if defined(GIT_SSH)
+ | GIT_CAP_SSH
+#endif
;
}
/* 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;
static int config_level_to_futils_dir(int config_level)
{
@@ -94,12 +97,25 @@ int git_libgit2_opts(int key, ...)
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;
+ case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
+ {
+ git_otype type = (git_otype)va_arg(ap, int);
+ size_t size = va_arg(ap, size_t);
+ error = git_cache_set_max_object_size(type, size);
+ break;
+ }
+
+ case GIT_OPT_SET_CACHE_MAX_SIZE:
+ git_cache__max_storage = va_arg(ap, ssize_t);
+ break;
+
+ case GIT_OPT_ENABLE_CACHING:
+ git_cache__enabled = (va_arg(ap, int) != 0);
break;
- case GIT_OPT_SET_ODB_CACHE_SIZE:
- git_odb__cache_size = va_arg(ap, size_t);
+ case GIT_OPT_GET_CACHED_MEMORY:
+ *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
+ *(va_arg(ap, ssize_t *)) = git_cache__max_storage;
break;
}
@@ -266,6 +282,28 @@ int git__strcasecmp(const char *a, const char *b)
return (tolower(*a) - tolower(*b));
}
+int git__strcasesort_cmp(const char *a, const char *b)
+{
+ int cmp = 0;
+
+ while (*a && *b) {
+ if (*a != *b) {
+ if (tolower(*a) != tolower(*b))
+ break;
+ /* use case in sort order even if not in equivalence */
+ if (!cmp)
+ cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b);
+ }
+
+ ++a, ++b;
+ }
+
+ if (*a || *b)
+ return tolower(*a) - tolower(*b);
+
+ return cmp;
+}
+
int git__strncmp(const char *a, const char *b, size_t sz)
{
while (sz && *a && *b && *a == *b)
@@ -672,7 +710,9 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
void git__qsort_r(
void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
{
-#if defined(__MINGW32__)
+#if defined(__MINGW32__) || defined(__OpenBSD__) || defined(AMIGA) || \
+ defined(__gnu_hurd__) || \
+ (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8)
git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
#elif defined(GIT_WIN32)
git__qsort_r_glue glue = { cmp, payload };
diff --git a/src/util.h b/src/util.h
index c0f271997..ed9624770 100644
--- a/src/util.h
+++ b/src/util.h
@@ -7,6 +7,8 @@
#ifndef INCLUDE_util_h__
#define INCLUDE_util_h__
+#include "common.h"
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
#define bitsizeof(x) (CHAR_BIT * sizeof(x))
#define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits))))
@@ -107,6 +109,13 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
return p == (git_off_t)r;
}
+/** @return true if p fits into the range of a uint32_t */
+GIT_INLINE(int) git__is_uint32(size_t p)
+{
+ uint32_t r = (uint32_t)p;
+ return p == (size_t)r;
+}
+
/* 32-bit cross-platform rotl */
#ifdef _MSC_VER /* use built-in method in MSVC */
# define git__rotl(v, s) (uint32_t)_rotl(v, s)
@@ -185,21 +194,25 @@ extern int git__strcasecmp(const char *a, const char *b);
extern int git__strncmp(const char *a, const char *b, size_t sz);
extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+extern int git__strcasesort_cmp(const char *a, const char *b);
+
+#include "thread-utils.h"
+
typedef struct {
- short refcount;
+ git_atomic refcount;
void *owner;
} git_refcount;
typedef void (*git_refcount_freeptr)(void *r);
#define GIT_REFCOUNT_INC(r) { \
- ((git_refcount *)(r))->refcount++; \
+ git_atomic_inc(&((git_refcount *)(r))->refcount); \
}
#define GIT_REFCOUNT_DEC(_r, do_free) { \
git_refcount *r = (git_refcount *)(_r); \
- r->refcount--; \
- if (r->refcount <= 0 && r->owner == NULL) { do_free(_r); } \
+ int val = git_atomic_dec(&r->refcount); \
+ if (val <= 0 && r->owner == NULL) { do_free(_r); } \
}
#define GIT_REFCOUNT_OWN(r, o) { \
@@ -208,6 +221,9 @@ typedef void (*git_refcount_freeptr)(void *r);
#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner)
+#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount)
+
+
static signed char from_hex[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */
@@ -260,22 +276,27 @@ GIT_INLINE(size_t) git__size_t_powerof2(size_t v)
GIT_INLINE(bool) git__isupper(int c)
{
- return (c >= 'A' && c <= 'Z');
+ return (c >= 'A' && c <= 'Z');
}
GIT_INLINE(bool) git__isalpha(int c)
{
- return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
GIT_INLINE(bool) git__isdigit(int c)
{
- return (c >= '0' && c <= '9');
+ return (c >= '0' && c <= '9');
}
GIT_INLINE(bool) git__isspace(int c)
{
- return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
+}
+
+GIT_INLINE(bool) git__isspace_nonlf(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
}
GIT_INLINE(bool) git__iswildcard(int c)
@@ -284,8 +305,7 @@ GIT_INLINE(bool) git__iswildcard(int c)
}
/*
- * Parse a string value as a boolean, just like Core Git
- * does.
+ * Parse a string value as a boolean, just like Core Git does.
*
* Valid values for true are: 'true', 'yes', 'on'
* Valid values for false are: 'false', 'no', 'off'
@@ -300,15 +320,31 @@ extern int git__parse_bool(int *out, const char *value);
* - "July 17, 2003"
* - "2003-7-17 08:23"
*/
-int git__date_parse(git_time_t *out, const char *date);
+extern int git__date_parse(git_time_t *out, const char *date);
/*
* Unescapes a string in-place.
- *
+ *
* Edge cases behavior:
* - "jackie\" -> "jacky\"
* - "chan\\" -> "chan\"
*/
extern size_t git__unescape(char *str);
+/*
+ * Safely zero-out memory, making sure that the compiler
+ * doesn't optimize away the operation.
+ */
+GIT_INLINE(void) git__memzero(void *data, size_t size)
+{
+#ifdef _MSC_VER
+ SecureZeroMemory((PVOID)data, size);
+#else
+ volatile uint8_t *scan = (volatile uint8_t *)data;
+
+ while (size--)
+ *scan++ = 0x0;
+#endif
+}
+
#endif /* INCLUDE_util_h__ */
diff --git a/src/vector.c b/src/vector.c
index f4a818ed2..5ba2fab18 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -277,15 +277,13 @@ void git_vector_swap(git_vector *a, git_vector *b)
int git_vector_resize_to(git_vector *v, size_t new_length)
{
- if (new_length <= v->length)
- return 0;
-
if (new_length > v->_alloc_size &&
resize_vector(v, new_length) < 0)
return -1;
- memset(&v->contents[v->length], 0,
- sizeof(void *) * (new_length - v->length));
+ if (new_length > v->length)
+ memset(&v->contents[v->length], 0,
+ sizeof(void *) * (new_length - v->length));
v->length = new_length;
diff --git a/src/vector.h b/src/vector.h
index e2f729b83..1bda9c93d 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -78,4 +78,13 @@ void git_vector_remove_matching(
int git_vector_resize_to(git_vector *v, size_t new_length);
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
+/** Set the comparison function used for sorting the vector */
+GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp)
+{
+ if (cmp != v->_cmp) {
+ v->_cmp = cmp;
+ v->sorted = 0;
+ }
+}
+
#endif
diff --git a/src/win32/dir.c b/src/win32/dir.c
index 95ae5060e..8c51d8378 100644
--- a/src/win32/dir.c
+++ b/src/win32/dir.c
@@ -32,7 +32,7 @@ git__DIR *git__opendir(const char *dir)
if (!dir || !init_filter(filter, sizeof(filter), dir))
return NULL;
- new = git__malloc(sizeof(*new));
+ new = git__calloc(1, sizeof(*new));
if (!new)
return NULL;
diff --git a/src/win32/error.c b/src/win32/error.c
index 4a9a0631f..a62a07e82 100644
--- a/src/win32/error.c
+++ b/src/win32/error.c
@@ -12,7 +12,9 @@
# include <winhttp.h>
#endif
+#ifndef WC_ERR_INVALID_CHARS
#define WC_ERR_INVALID_CHARS 0x80
+#endif
char *git_win32_get_error_message(DWORD error_code)
{
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
index bc36b6b45..9d9051bff 100644
--- a/src/win32/findfile.c
+++ b/src/win32/findfile.c
@@ -156,7 +156,7 @@ static int win32_find_git_in_registry(
}
static int win32_find_existing_dirs(
- git_buf *out, const wchar_t *tmpl[], char *temp[])
+ git_buf *out, const wchar_t *tmpl[])
{
struct git_win32__path path16;
git_buf buf = GIT_BUF_INIT;
@@ -209,7 +209,6 @@ int git_win32__find_system_dirs(git_buf *out)
int git_win32__find_global_dirs(git_buf *out)
{
- char *temp[3];
static const wchar_t *global_tmpls[4] = {
L"%HOME%\\",
L"%HOMEDRIVE%%HOMEPATH%\\",
@@ -217,12 +216,11 @@ int git_win32__find_global_dirs(git_buf *out)
NULL,
};
- return win32_find_existing_dirs(out, global_tmpls, temp);
+ return win32_find_existing_dirs(out, global_tmpls);
}
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",
@@ -233,6 +231,5 @@ int git_win32__find_xdg_dirs(git_buf *out)
NULL,
};
- return win32_find_existing_dirs(out, global_tmpls, temp);
+ return win32_find_existing_dirs(out, global_tmpls);
}
-
diff --git a/src/win32/git2.rc b/src/win32/git2.rc
index 436913228..22c63f695 100644
--- a/src/win32/git2.rc
+++ b/src/win32/git2.rc
@@ -1,10 +1,8 @@
#include <winver.h>
#include "../../include/git2/version.h"
-#ifndef INCLUDE_LIB
-#define LIBGIT2_FILENAME "git2.dll"
-#else
-#define LIBGIT2_FILENAME "libgit2.dll"
+#ifndef LIBGIT2_FILENAME
+# define LIBGIT2_FILENAME "git2"
#endif
VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE
@@ -27,9 +25,9 @@ BEGIN
BEGIN
VALUE "FileDescription", "libgit2 - the Git linkable library\0"
VALUE "FileVersion", LIBGIT2_VERSION "\0"
- VALUE "InternalName", LIBGIT2_FILENAME "\0"
+ VALUE "InternalName", LIBGIT2_FILENAME ".dll\0"
VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0"
- VALUE "OriginalFilename", LIBGIT2_FILENAME "\0"
+ VALUE "OriginalFilename", LIBGIT2_FILENAME ".dll\0"
VALUE "ProductName", "libgit2\0"
VALUE "ProductVersion", LIBGIT2_VERSION "\0"
VALUE "Comments", "For more information visit http://libgit2.github.com/\0"
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 4d56299f7..036632e2a 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -5,6 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "../posix.h"
+#include "../fileops.h"
#include "path.h"
#include "utf-conv.h"
#include "repository.h"
@@ -89,6 +90,9 @@ static int do_lstat(
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
fMode |= S_IFLNK;
+ if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction
+ fMode ^= S_IFLNK;
+
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
@@ -295,7 +299,18 @@ int p_getcwd(char *buffer_out, size_t size)
int p_stat(const char* path, struct stat* buf)
{
- return do_lstat(path, buf, 0);
+ char target[GIT_WIN_PATH];
+ int error = 0;
+
+ error = do_lstat(path, buf, 0);
+
+ /* We need not do this in a loop to unwind chains of symlinks since
+ * p_readlink calls GetFinalPathNameByHandle which does it for us. */
+ if (error >= 0 && S_ISLNK(buf->st_mode) &&
+ (error = p_readlink(path, target, GIT_WIN_PATH)) >= 0)
+ error = do_lstat(target, buf, 0);
+
+ return error;
}
int p_chdir(const char* path)
@@ -314,9 +329,20 @@ int p_chmod(const char* path, mode_t mode)
int p_rmdir(const char* path)
{
+ int error;
wchar_t buf[GIT_WIN_PATH];
git__utf8_to_16(buf, GIT_WIN_PATH, path);
- return _wrmdir(buf);
+
+ error = _wrmdir(buf);
+
+ /* _wrmdir() is documented to return EACCES if "A program has an open
+ * handle to the directory." This sounds like what everybody else calls
+ * EBUSY. Let's convert appropriate error codes.
+ */
+ if (GetLastError() == ERROR_SHARING_VIOLATION)
+ errno = EBUSY;
+
+ return error;
}
int p_hide_directory__w32(const char *path)
@@ -457,29 +483,29 @@ int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
* Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
* On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
*/
-struct tm *
-p_localtime_r (const time_t *timer, struct tm *result)
-{
- struct tm *local_result;
- local_result = localtime (timer);
-
- if (local_result == NULL || result == NULL)
- return NULL;
-
- memcpy (result, local_result, sizeof (struct tm));
- return result;
-}
-struct tm *
-p_gmtime_r (const time_t *timer, struct tm *result)
-{
- struct tm *local_result;
- local_result = gmtime (timer);
-
- if (local_result == NULL || result == NULL)
- return NULL;
-
- memcpy (result, local_result, sizeof (struct tm));
- return result;
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
}
#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
@@ -492,44 +518,44 @@ p_gmtime_r (const time_t *timer, struct tm *result)
#define _TIMEZONE_DEFINED
struct timezone
{
- int tz_minuteswest; /* minutes W of Greenwich */
- int tz_dsttime; /* type of dst correction */
+ int tz_minuteswest; /* minutes W of Greenwich */
+ int tz_dsttime; /* type of dst correction */
};
#endif
-
+
int p_gettimeofday(struct timeval *tv, struct timezone *tz)
{
- FILETIME ft;
- unsigned __int64 tmpres = 0;
- static int tzflag;
-
- if (NULL != tv)
- {
- GetSystemTimeAsFileTime(&ft);
-
- tmpres |= ft.dwHighDateTime;
- tmpres <<= 32;
- tmpres |= ft.dwLowDateTime;
-
- /*converting file time to unix epoch*/
- tmpres /= 10; /*convert into microseconds*/
- tmpres -= DELTA_EPOCH_IN_MICROSECS;
- tv->tv_sec = (long)(tmpres / 1000000UL);
- tv->tv_usec = (long)(tmpres % 1000000UL);
- }
-
- if (NULL != tz)
- {
- if (!tzflag)
- {
- _tzset();
- tzflag++;
- }
- tz->tz_minuteswest = _timezone / 60;
- tz->tz_dsttime = _daylight;
- }
-
- return 0;
+ FILETIME ft;
+ unsigned __int64 tmpres = 0;
+ static int tzflag;
+
+ if (NULL != tv)
+ {
+ GetSystemTimeAsFileTime(&ft);
+
+ tmpres |= ft.dwHighDateTime;
+ tmpres <<= 32;
+ tmpres |= ft.dwLowDateTime;
+
+ /*converting file time to unix epoch*/
+ tmpres /= 10; /*convert into microseconds*/
+ tmpres -= DELTA_EPOCH_IN_MICROSECS;
+ tv->tv_sec = (long)(tmpres / 1000000UL);
+ tv->tv_usec = (long)(tmpres % 1000000UL);
+ }
+
+ if (NULL != tz)
+ {
+ if (!tzflag)
+ {
+ _tzset();
+ tzflag++;
+ }
+ tz->tz_minuteswest = _timezone / 60;
+ tz->tz_dsttime = _daylight;
+ }
+
+ return 0;
}
int p_inet_pton(int af, const char* src, void* dst)
diff --git a/src/win32/pthread.c b/src/win32/pthread.c
index 105f4b89e..2f263b3e0 100644
--- a/src/win32/pthread.c
+++ b/src/win32/pthread.c
@@ -14,22 +14,30 @@ int pthread_create(
void *GIT_RESTRICT arg)
{
GIT_UNUSED(attr);
- *thread = (pthread_t) CreateThread(
+ *thread = CreateThread(
NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL);
return *thread ? 0 : -1;
}
int pthread_join(pthread_t thread, void **value_ptr)
{
- int ret;
- ret = WaitForSingleObject(thread, INFINITE);
- if (ret && value_ptr)
- GetExitCodeThread(thread, (void*) value_ptr);
- return -(!!ret);
+ DWORD ret = WaitForSingleObject(thread, INFINITE);
+
+ if (ret == WAIT_OBJECT_0) {
+ if (value_ptr != NULL) {
+ *value_ptr = NULL;
+ GetExitCodeThread(thread, (void *)value_ptr);
+ }
+ CloseHandle(thread);
+ return 0;
+ }
+
+ return -1;
}
-int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex,
- const pthread_mutexattr_t *GIT_RESTRICT mutexattr)
+int pthread_mutex_init(
+ pthread_mutex_t *GIT_RESTRICT mutex,
+ const pthread_mutexattr_t *GIT_RESTRICT mutexattr)
{
GIT_UNUSED(mutexattr);
InitializeCriticalSection(mutex);
diff --git a/src/win32/pthread.h b/src/win32/pthread.h
index a219a0137..8277ecf6e 100644
--- a/src/win32/pthread.h
+++ b/src/win32/pthread.h
@@ -25,13 +25,16 @@ typedef HANDLE pthread_cond_t;
#define PTHREAD_MUTEX_INITIALIZER {(void*)-1};
-int pthread_create(pthread_t *GIT_RESTRICT,
- const pthread_attr_t *GIT_RESTRICT,
- void *(*start_routine)(void*), void *__restrict);
+int pthread_create(
+ pthread_t *GIT_RESTRICT,
+ const pthread_attr_t *GIT_RESTRICT,
+ void *(*start_routine)(void*),
+ void *__restrict);
int pthread_join(pthread_t, void **);
-int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT);
+int pthread_mutex_init(
+ pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT);
int pthread_mutex_destroy(pthread_mutex_t *);
int pthread_mutex_lock(pthread_mutex_t *);
int pthread_mutex_unlock(pthread_mutex_t *);