summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrad Morgan <brad@dmgctrl.com>2013-05-07 14:30:35 -0400
committerBrad Morgan <brad@dmgctrl.com>2013-05-07 14:30:35 -0400
commit00e43380a0beee3ac40935c45d4aa67fbfc27fbb (patch)
tree6b9c959fa74694fb0f1841d9669dfc6221ff4277 /src
parent7369b3c3bf396e466d065f9921415fe2b9d69a7a (diff)
parent42b2bcf038b4e45df33a1078dd05a95759addd35 (diff)
downloadlibgit2-00e43380a0beee3ac40935c45d4aa67fbfc27fbb.tar.gz
Merge remote-tracking branch 'origin/development' into ssh_transport
Diffstat (limited to 'src')
-rw-r--r--src/attr.c2
-rw-r--r--src/blob.c21
-rw-r--r--src/blob.h4
-rw-r--r--src/branch.c24
-rw-r--r--src/cache.c272
-rw-r--r--src/cache.h53
-rw-r--r--src/checkout.c52
-rw-r--r--src/clone.c24
-rw-r--r--src/commit.c104
-rw-r--r--src/commit.h5
-rw-r--r--src/commit_list.c20
-rw-r--r--src/config.c18
-rw-r--r--src/config_cache.c31
-rw-r--r--src/config_file.c5
-rw-r--r--src/diff.c670
-rw-r--r--src/diff.h12
-rw-r--r--src/diff_output.c69
-rw-r--r--src/diff_tform.c20
-rw-r--r--src/fetch.c3
-rw-r--r--src/global.c6
-rw-r--r--src/global.h8
-rw-r--r--src/ignore.c38
-rw-r--r--src/ignore.h2
-rw-r--r--src/index.c262
-rw-r--r--src/index.h1
-rw-r--r--src/indexer.c34
-rw-r--r--src/iterator.c521
-rw-r--r--src/iterator.h11
-rw-r--r--src/merge.c1340
-rw-r--r--src/merge.h115
-rw-r--r--src/merge_file.c175
-rw-r--r--src/merge_file.h71
-rw-r--r--src/mwindow.c2
-rw-r--r--src/object.c197
-rw-r--r--src/object.h1
-rw-r--r--src/object_api.c129
-rw-r--r--src/odb.c147
-rw-r--r--src/odb.h7
-rw-r--r--src/odb_loose.c2
-rw-r--r--src/odb_pack.c82
-rw-r--r--src/oid.c39
-rw-r--r--src/oid.h33
-rw-r--r--src/oidmap.h10
-rw-r--r--src/pack-objects.c15
-rw-r--r--src/pack.c134
-rw-r--r--src/pack.h4
-rw-r--r--src/push.c7
-rw-r--r--src/refdb.c44
-rw-r--r--src/refdb.h2
-rw-r--r--src/refdb_fs.c163
-rw-r--r--src/refs.c209
-rw-r--r--src/refs.h7
-rw-r--r--src/refspec.c19
-rw-r--r--src/refspec.h4
-rw-r--r--src/remote.c530
-rw-r--r--src/remote.h6
-rw-r--r--src/repository.c293
-rw-r--r--src/repository.h26
-rw-r--r--src/revparse.c2
-rw-r--r--src/signature.c2
-rw-r--r--src/submodule.c1
-rw-r--r--src/tag.c58
-rw-r--r--src/tag.h5
-rw-r--r--src/thread-utils.h104
-rw-r--r--src/transports/local.c2
-rw-r--r--src/transports/smart_protocol.c24
-rw-r--r--src/tree.c26
-rw-r--r--src/tree.h4
-rw-r--r--src/unix/posix.h2
-rw-r--r--src/util.c23
-rw-r--r--src/util.h12
-rw-r--r--src/vector.c8
-rw-r--r--src/win32/pthread.c22
-rw-r--r--src/win32/pthread.h11
74 files changed, 4831 insertions, 1580 deletions
diff --git a/src/attr.c b/src/attr.c
index 979fecc14..6dd2c7e2f 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -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)
diff --git a/src/blob.c b/src/blob.c
index c0514fc13..a68c4cc3e 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -8,6 +8,7 @@
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
+#include "git2/odb_backend.h"
#include "common.h"
#include "blob.h"
@@ -17,32 +18,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;
}
@@ -314,8 +317,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..22e37cc3a 100644
--- a/src/blob.h
+++ b/src/blob.h
@@ -17,8 +17,8 @@ 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);
#endif
diff --git a/src/branch.c b/src/branch.c
index e7088790e..88f052529 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"
@@ -283,12 +284,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 +332,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
@@ -377,7 +373,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;
@@ -522,9 +518,9 @@ int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
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/cache.c b/src/cache.c
index e7f333577..1360cc976 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -11,100 +11,262 @@
#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 (size < 8)
- size = 8;
- size = git__size_t_powerof2(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;
+}
- cache->size_mask = size - 1;
- cache->lru_count = 0;
- cache->free_obj = free_ptr;
+void git_cache_dump_stats(git_cache *cache)
+{
+ git_cached_obj *object;
- git_mutex_init(&cache->lock);
+ if (kh_size(cache->map) == 0)
+ return;
- cache->nodes = git__malloc(size * sizeof(git_cached_obj *));
- GITERR_CHECK_ALLOC(cache->nodes);
+ printf("Cache %p: %d items cached, %d bytes\n",
+ cache, kh_size(cache->map), (int)cache->used_memory);
- memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *));
+ 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
+ );
+ });
+}
+
+int git_cache_init(git_cache *cache)
+{
+ cache->used_memory = 0;
+ cache->map = git_oidmap_alloc();
+ git_mutex_init(&cache->lock);
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);
- for (i = 0; i < (cache->size_mask + 1); ++i) {
- if (cache->nodes[i] != NULL)
- git_cached_obj_decref(cache->nodes[i], cache->free_obj);
+ git_oidmap_free(cache->map);
+ git_mutex_free(&cache->lock);
+}
+
+/* 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;
+
+ /* 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 (!cache_should_store(entry->type, entry->size))
+ return entry;
- {
- git_cached_obj *node = cache->nodes[hash & cache->size_mask];
+ if (git_mutex_lock(&cache->lock) < 0)
+ return entry;
- /* increase the refcount on this object, because
- * the cache now owns it */
- git_cached_obj_incref(entry);
+ /* soften the load on the cache */
+ if (git_cache__current_storage.val > git_cache__max_storage)
+ cache_evict_entries(cache);
- 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;
+ 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..21f32d89a 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -16,6 +16,7 @@
#include "git2/config.h"
#include "git2/diff.h"
#include "git2/submodule.h"
+#include "git2/sys/index.h"
#include "refs.h"
#include "repository.h"
@@ -119,6 +120,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 +139,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 +164,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) \
@@ -454,6 +467,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,8 +491,11 @@ 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)
- goto fail;
+ if ((error = git_iterator_advance_into(&wd, workdir)) < 0) {
+ if (error != GIT_ENOTFOUND ||
+ git_iterator_advance(&wd, workdir) < 0)
+ goto fail;
+ }
*wditem_ptr = wd;
continue;
@@ -698,8 +715,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;
@@ -1107,7 +1124,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));
@@ -1120,9 +1136,6 @@ static int checkout_data_init(
if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
return error;
- if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
- return error;
-
data->repo = repo;
GITERR_CHECK_VERSION(
@@ -1135,7 +1148,10 @@ static int checkout_data_init(
/* 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 +1188,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) {
diff --git a/src/clone.c b/src/clone.c
index 0bbccd44b..aeb7bbf5c 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -132,14 +132,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 +187,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;
@@ -211,8 +212,13 @@ 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(
@@ -317,12 +323,14 @@ 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 &&
diff --git a/src/commit.c b/src/commit.c
index c7b83ed43..46c02c292 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"
@@ -30,8 +31,10 @@ static void clear_parents(git_commit *commit)
git_vector_clear(&commit->parent_ids);
}
-void git_commit__free(git_commit *commit)
+void git_commit__free(void *_commit)
{
+ git_commit *commit = _commit;
+
clear_parents(commit);
git_vector_free(&commit->parent_ids);
@@ -44,16 +47,16 @@ void git_commit__free(git_commit *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 +79,29 @@ 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(oid && repo && tree && parent_count >= 0);
assert(git_object_owner((const git_object *)tree) == repo);
- 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,10 +133,46 @@ 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);
+
+ 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 = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
git_oid parent_id;
if (git_vector_init(&commit->parent_ids, 4, NULL) < 0)
@@ -206,12 +244,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) \
{\
diff --git a/src/commit.h b/src/commit.h
index 1ab164c0b..d0981b125 100644
--- a/src/commit.h
+++ b/src/commit.h
@@ -27,8 +27,7 @@ struct git_commit {
char *message;
};
-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..bd5b5201a 100644
--- a/src/commit_list.c
+++ b/src/commit_list.c
@@ -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..2e1268ef3 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"
@@ -292,6 +293,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;
}
@@ -359,6 +363,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 +373,20 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
}
internal = git_vector_get(&cfg->files, 0);
+ if (!internal) {
+ /* Should we auto-vivify .git/config? Tricky from this location */
+ giterr_set(GITERR_CONFIG, "Cannot set value when no config files exist");
+ return GIT_ENOTFOUND;
+ }
+
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;
}
/***********
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..a9a40c366 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"
@@ -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);
diff --git a/src/diff.c b/src/diff.c
index 37c89f3f1..a154e67e8 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -14,6 +14,8 @@
#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,
@@ -194,21 +196,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:
@@ -267,67 +269,157 @@ 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;
+ }
+
+ 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_init(&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;
/* 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
- */
+ /* 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;
+ if (config_bool(cfg, "diff.ignoreSubmodules", 0))
+ diff->opts.flags |= GIT_DIFF_IGNORE_SUBMODULES;
}
- memcpy(&diff->opts, opts, sizeof(git_diff_options));
-
- if(opts->flags & GIT_DIFF_IGNORE_FILEMODE)
- diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS;
-
- /* 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 +427,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)
@@ -445,24 +529,30 @@ cleanup:
return result;
}
+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(
- 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_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(
&diff->pathspec, oitem->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
@@ -607,237 +697,311 @@ 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);
+ }
+
+ return error;
}
- return 0;
+ /* look for actual untracked file */
+ while (!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)) {
+ if ((error = git_iterator_advance_into(
+ &info->nitem, info->new_iter)) < 0)
+ break;
+ continue;
+ }
+
+ /* 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 (!diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
+ break;
+ }
+
+ git_buf_free(&base);
+
+ 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 (S_ISDIR(nitem->mode)) {
+ 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;
- /* 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;
+ /* it iteration changed delta type, the update the record */
+ if (delta_type == GIT_DELTA_IGNORED) {
+ last->status = GIT_DELTA_IGNORED;
+
+ /* 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)
+ /* try to advance into directory if necessary */
+ if (recurse_into_dir) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
+
+ /* if real error or no error, proceed with iteration */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+
+ /* 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;
+ }
+ }
+
+ /* 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);
+
+ else if (git_iterator_current_is_ignored(info->new_iter))
+ delta_type = GIT_DELTA_IGNORED;
+
+ else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ delta_type = GIT_DELTA_ADDED;
+
+ /* Actually create the record for this item if necessary */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
+ return error;
+
+ /* 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;
+ }
+ }
+
+ 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 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...
*/
- 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;
- }
+ 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);
+ }
- /* 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;
- }
+ return git_iterator_advance(&info->oitem, info->old_iter);
+}
- if (contains_oitem || recurse_into_dir) {
- /* advance into directory */
- error = git_iterator_advance_into(&nitem, new_iter);
+static int handle_matched_item(
+ git_diff_list *diff, diff_in_progress *info)
+{
+ int error = 0;
- /* if directory is empty, can't advance into it, so skip */
- if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = git_iterator_advance(&nitem, new_iter);
+ if (!(error = maybe_modified(diff, info)) &&
+ !(error = git_iterator_advance(&info->oitem, info->old_iter)))
+ error = git_iterator_advance(&info->nitem, info->new_iter);
- git_buf_clear(&ignore_prefix);
- }
+ return error;
+}
- if (error < 0)
- goto fail;
- continue;
- }
- }
+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;
- /* 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 */
- }
+ *diff_ptr = NULL;
- else if (git_iterator_current_is_ignored(new_iter))
- delta_type = GIT_DELTA_IGNORED;
+ diff = diff_list_alloc(repo, old_iter, new_iter);
+ GITERR_CHECK_ALLOC(diff);
- else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- delta_type = GIT_DELTA_ADDED;
+ info.repo = repo;
+ info.old_iter = old_iter;
+ info.new_iter = new_iter;
+ git_buf_init(&info.ignore_prefix, 0);
- if (diff_delta__from_one(diff, delta_type, nitem) < 0)
- goto fail;
+ /* 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)))
+ error = git_iterator_set_ignore_case(new_iter, true);
+ }
- /* 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;
- }
- }
+ /* finish initialization */
+ if (!error &&
+ !(error = diff_list_apply_options(diff, opts)) &&
+ !(error = git_iterator_current(&info.oitem, old_iter)))
+ error = git_iterator_current(&info.nitem, new_iter);
- if (git_iterator_advance(&nitem, new_iter) < 0)
- goto fail;
- }
+ /* 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);
-
- 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;
- }
+ else
+ error = handle_matched_item(diff, &info);
}
- *diff_ptr = diff;
-
-fail:
- if (!*diff_ptr) {
+ 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 +1023,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;
diff --git a/src/diff.h b/src/diff.h
index 8e3cbcd46..48e20d1e3 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -74,5 +74,17 @@ extern int git_diff__from_iterators(
git_iterator *new_iter,
const git_diff_options *opts);
+int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p);
+
+int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
+
+void git_diff_find_similar__hashsig_free(void *sig, void *payload);
+
+int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload);
+
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index 34a3e506c..64ff6b5be 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -101,8 +101,8 @@ static bool diff_delta_is_binary_forced(
/* 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)
- {
+ (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;
@@ -232,8 +232,7 @@ static int get_blob_content(
if (git_oid_iszero(&file->oid))
return 0;
- if (file->mode == GIT_FILEMODE_COMMIT)
- {
+ if (file->mode == GIT_FILEMODE_COMMIT) {
char oidstr[GIT_OID_HEXSZ+1];
git_buf content = GIT_BUF_INIT;
@@ -299,8 +298,8 @@ static int get_workdir_sm_content(
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)
- {
+ (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;
@@ -312,8 +311,8 @@ static int get_workdir_sm_content(
const git_oid* sm_head;
if ((sm_head = git_submodule_wd_id(sm)) != NULL ||
- (sm_head = git_submodule_head_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;
}
@@ -660,8 +659,8 @@ static int diff_patch_load(
*/
if (check_if_unmodified &&
delta->old_file.mode == delta->new_file.mode &&
- !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
- {
+ !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)
@@ -1049,6 +1048,12 @@ char git_diff_status_char(git_delta_t status)
return code;
}
+static int callback_error(void)
+{
+ giterr_clear();
+ return GIT_EUSER;
+}
+
static int print_compact(
const git_diff_delta *delta, float progress, void *data)
{
@@ -1083,10 +1088,7 @@ static int print_compact(
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 callback_error();
return 0;
}
@@ -1114,11 +1116,20 @@ int git_diff_print_compact(
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
{
- char start_oid[8], end_oid[8];
+ int abbrevlen;
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ if (git_repository__cvar(&abbrevlen, pi->diff->repo, GIT_CVAR_ABBREV) < 0)
+ return -1;
+
+ abbrevlen += 1; /* for NUL byte */
+ if (abbrevlen < 2)
+ abbrevlen = 2;
+ else if (abbrevlen > (int)sizeof(start_oid))
+ abbrevlen = (int)sizeof(start_oid);
- /* 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);
+ git_oid_tostr(start_oid, abbrevlen, &delta->old_file.oid);
+ git_oid_tostr(end_oid, abbrevlen, &delta->new_file.oid);
/* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
@@ -1191,10 +1202,7 @@ static int print_patch_file(
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 callback_error();
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
return 0;
@@ -1208,10 +1216,7 @@ static int print_patch_file(
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 callback_error();
return 0;
}
@@ -1234,10 +1239,7 @@ static int print_patch_hunk(
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 callback_error();
return 0;
}
@@ -1269,10 +1271,7 @@ static int print_patch_line(
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 callback_error();
return 0;
}
@@ -1379,7 +1378,7 @@ static int diff_single_apply(diff_single_data *data)
(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)
+ 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(
diff --git a/src/diff_tform.c b/src/diff_tform.c
index efcb19d95..201a0e896 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -170,7 +170,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;
@@ -187,12 +187,12 @@ 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);
@@ -204,13 +204,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);
@@ -291,10 +291,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;
@@ -429,7 +429,7 @@ static int similarity_measure(
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)
+ if (git_oid__cmp(&a_file->oid, &b_file->oid) == 0)
return 100;
/* update signature cache if needed */
diff --git a/src/fetch.c b/src/fetch.c
index b60a95232..8ae34bddf 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -34,7 +34,7 @@ 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 (git_remote__matching_refspec(p->remote, head->name))
match = 1;
else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&
git_refspec_src_matches(p->tagspec, head->name))
@@ -68,7 +68,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/global.c b/src/global.c
index b7fd8e257..a0571d127 100644
--- a/src/global.c
+++ b/src/global.c
@@ -135,6 +135,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/ignore.c b/src/ignore.c
index 17779522c..e150b9585 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)
diff --git a/src/ignore.h b/src/ignore.h
index 5af8e8e7d..e00e4a8c8 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);
diff --git a/src/index.c b/src/index.c
index 6290ec4e8..d4aa475a9 100644
--- a/src/index.c
+++ b/src/index.c
@@ -19,6 +19,7 @@
#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 +36,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)))
@@ -187,6 +189,51 @@ static int index_icmp(const void *a, const void *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;
@@ -278,6 +325,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;
@@ -330,6 +378,8 @@ void git_index_clear(git_index *index)
git_vector_clear(&index->entries);
git_index_reuc_clear(index);
+
+ git_index_name_clear(index);
git_futils_filestamp_set(&index->stamp, NULL);
@@ -352,19 +402,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 {
@@ -586,8 +635,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;
@@ -693,7 +743,7 @@ static int index_conflict_to_reuc(git_index *index, const char *path)
{
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],
@@ -947,7 +997,6 @@ int git_index_conflict_get(git_index_entry **ancestor_out,
return GIT_ENOTFOUND;
for (posmax = git_index_entrycount(index); pos < posmax; ++pos) {
-
conflict_entry = git_vector_get(&index->entries, pos);
if (index->entries_cmp_path(conflict_entry->path, path) != 0)
@@ -1043,13 +1092,82 @@ int git_index_has_conflicts(const git_index *index)
return 0;
}
+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 +1189,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;
@@ -1226,6 +1344,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;
@@ -1330,6 +1494,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 +1512,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) { \
@@ -1412,7 +1579,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 +1710,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 +1835,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;
diff --git a/src/index.h b/src/index.h
index 9498907b6..2ad401741 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;
diff --git a/src/indexer.c b/src/indexer.c
index 2cfbd3a5a..91b7ba5d9 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(
@@ -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;
diff --git a/src/iterator.c b/src/iterator.c
index 5b5ed9525..ff08c1ce0 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -26,8 +26,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); \
@@ -148,7 +146,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
@@ -581,6 +580,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)
@@ -810,7 +812,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 +836,221 @@ 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);
+};
-static workdir_iterator_frame *workdir_iterator__alloc_frame(
- workdir_iterator *wi)
+#define FS_MAX_DEPTH 100
+
+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 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);
+ if (error < 0 || ff->entries.length == 0) {
+ fs_iterator__free_frame(ff);
return GIT_ENOTFOUND;
}
- if (++(wi->depth) > WORKDIR_MAX_DEPTH) {
- giterr_set(GITERR_REPOSITORY,
- "Working directory is too deep (%d)", wi->depth);
- workdir_iterator__free_frame(wf);
- return -1;
- }
-
- workdir_iterator__seek_frame_start(wi, wf);
+ 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;
if (entry)
- *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
+ *entry = (fi->entry.path == NULL) ? NULL : &fi->entry;
return 0;
}
-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);
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;
+
+ /* 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,107 +1060,192 @@ 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;
+ 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;
- workdir_iterator__seek_frame_start(wi, wi->stack);
+ fs_iterator__seek_frame_start(fi, fi->stack);
- return workdir_iterator__update_entry(wi);
+ return fs_iterator__update_entry(fi);
}
-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_vector_get(&fi->stack->entries, fi->stack->index);
- git_buf_truncate(&wi->path, wi->root_len);
- memset(&wi->entry, 0, sizeof(wi->entry));
+ git_buf_truncate(&fi->path, fi->root_len);
+ memset(&fi->entry, 0, sizeof(fi->entry));
if (!ps)
return 0;
+ if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
+ return -1;
+ if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
+ return 0;
- /* skip over .git entries */
- if (path_is_dotgit(ps))
- return workdir_iterator__advance(NULL, (git_iterator *)wi);
+ 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))
+ return git_iterator_advance(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_put(&wi->path, ps->path, ps->path_len) < 0)
+ 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)) == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+ if (error) {
+ 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;
+
+ /* 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;
- /* 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;
+ /* 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,
+ git_iterator **out,
git_repository *repo,
git_iterator_flag_t flags,
const char *start,
@@ -1182,38 +1254,28 @@ int git_iterator_for_workdir(
int error;
workdir_iterator *wi;
- assert(iter && repo);
+ if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
+ return GIT_EBAREREPO;
- if ((error = git_repository__ensure_not_bare(
- repo, "scan working directory")) < 0)
- return error;
+ /* 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);
- ITERATOR_BASE_INIT(wi, workdir, WORKDIR, 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, git_repository_workdir(repo));
}
@@ -1315,7 +1377,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 +1403,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..7998f7c6b 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 {
@@ -88,6 +89,16 @@ extern int git_iterator_for_workdir(
const char *start,
const char *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);
/* Return a git_index_entry structure for the current value the iterator
diff --git a/src/merge.c b/src/merge.c
index e0010d6a4..56290bfad 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -5,15 +5,54 @@
* 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 "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"
+#include "git2/checkout.h"
+#include "git2/signature.h"
+#include "git2/config.h"
+#include "git2/tree.h"
+#include "git2/sys/index.h"
+
+#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
+
+typedef enum {
+ TREE_IDX_ANCESTOR = 0,
+ TREE_IDX_OURS = 1,
+ TREE_IDX_THEIRS = 2
+} merge_tree_index_t;
+
+/* Tracks D/F conflicts */
+struct merge_diff_df_data {
+ const char *df_path;
+ const char *prev_path;
+ git_merge_diff *prev_conflict;
+};
+
int git_repository_merge_cleanup(git_repository *repo)
{
@@ -48,6 +87,8 @@ cleanup:
return error;
}
+/* Merge base computation */
+
int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)
{
git_revwalk *walk;
@@ -177,7 +218,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 +335,1296 @@ 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++) {
+ if ((error = git_iterator_current(&items[i], iterators[i])) < 0)
+ 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);
+
+ /* Advance each iterator that participated */
+ for (i = 0; i < 3; i++) {
+ if (cur_items[i] != NULL &&
+ (error = git_iterator_advance(&items[i], iterators[i])) < 0)
+ goto done;
+ }
+ }
+
+done:
+ for (i = 0; i < 3; i++)
+ git_iterator_free(iterators[i]);
+
+ 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;
+}
+
+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;
+}
+
+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);
+}
+
diff --git a/src/merge.h b/src/merge.h
index 22c644270..6307d1569 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -7,16 +7,121 @@
#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;
+
+int git_merge__bases_many(
+ git_commit_list **out,
+ git_revwalk *walk,
+ git_commit_list_node *one,
+ git_vector *twos);
+
+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);
-int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos);
+void git_merge_diff_list__free(git_merge_diff_list *diff_list);
#endif
diff --git a/src/merge_file.c b/src/merge_file.c
new file mode 100644
index 000000000..4b3f3730b
--- /dev/null
+++ b/src/merge_file.c
@@ -0,0 +1,175 @@
+/*
+ * 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..1aa34893d
--- /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/object.c b/src/object.c
index 80fe51152..b87a07404 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;
+ /* Parse raw object data */
+ def = &git_objects_table[odb_obj->cached.type];
+ assert(def->free && def->parse);
- case GIT_OBJ_TREE:
- error = git_tree__parse((git_tree *)object, odb_obj);
- break;
-
- 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(
@@ -154,27 +133,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 +201,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 +218,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 +254,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)
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..07e1ea6eb 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)
@@ -353,9 +366,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 +377,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 +396,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 +410,74 @@ 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);
}
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth)
+size_t git_odb_num_backends(git_odb *odb)
{
+ assert(odb);
+ return odb->backends.length;
+}
+
+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;
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+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) {
+ 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);
@@ -464,7 +527,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 +539,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 +577,7 @@ static void odb_free(git_odb *db)
}
git_vector_free(&db->backends);
- git_cache_free(&db->cache);
+ git_cache_free(&db->own_cache);
git__free(db);
}
@@ -535,7 +598,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 +648,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 +675,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;
@@ -625,6 +688,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
int error;
bool refreshed = false;
git_rawobj raw;
+ git_odb_object *object;
assert(out && db && id);
@@ -633,7 +697,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
return GIT_ENOTFOUND;
}
- *out = git_cache_get(&db->cache, id);
+ *out = git_cache_get_raw(odb_cache(db), id);
if (*out != NULL)
return 0;
@@ -659,7 +723,10 @@ attempt_lookup:
if (error && error != GIT_PASSTHROUGH)
return error;
- *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw));
+ if ((object = odb_object__alloc(id, &raw)) == NULL)
+ return -1;
+
+ *out = git_cache_store_raw(odb_cache(db), object);
return 0;
}
@@ -672,6 +739,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 +750,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 +772,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 +791,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;
}
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..e78172cf6 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"
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 7240a4ac7..eec79259b 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;
@@ -526,80 +527,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..e74640c57 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -166,18 +166,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 +194,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)
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..1329a9bc1 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -1284,6 +1284,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..417d225f3 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);
@@ -205,13 +205,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 +293,40 @@ 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 */
+
+ 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"));
- name_len = strlen(idx_name);
- offset = name_len - strlen(".pack");
- assert(offset < name_len); /* make sure no underflow */
+ if ((error = git_mutex_lock(&p->lock)) < 0)
+ 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 +402,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,23 +799,14 @@ 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)
p_close(p->mwf.fd);
@@ -810,6 +814,8 @@ void git_packfile_free(struct git_pack_file *p)
pack_index_free(p);
git__free(p->bad_object_sha1);
+
+ git_mutex_free(&p->lock);
git__free(p);
}
@@ -820,17 +826,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 +882,54 @@ 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);
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 +938,13 @@ 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;
+
+ git_mutex_init(&p->lock);
/* see if we can parse the sha1 oid in the packfile name */
if (path_len < 40 ||
@@ -1034,12 +1059,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 +1123,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 +1135,7 @@ static int pack_entry_find_offset(
printf("found lo=%d %s\n", lo, hex_sha1);
}
#endif
+
return 0;
}
@@ -1128,7 +1154,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/push.c b/src/push.c
index cec4c64af..9b1e78c8e 100644
--- a/src/push.c
+++ b/src/push.c
@@ -177,9 +177,9 @@ 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;
+ git_refspec *fetch_spec;
push_spec *push_spec;
git_reference *remote_ref;
push_status *status;
@@ -191,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)
@@ -375,7 +376,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);
diff --git a/src/refdb.c b/src/refdb.c
index d9b73c6e7..33a1934d1 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,16 @@ 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)
{
- if (db->backend) {
- if(db->backend->free)
- db->backend->free(db->backend);
- else
- git__free(db->backend);
- }
-
+ refdb_free_backend(db);
git__free(db);
}
@@ -111,9 +109,19 @@ 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;
+
+ assert(db && db->backend && out && ref_name);
- return db->backend->lookup(out, db->backend, ref_name);
+ if (!(error = db->backend->lookup(&ref, db->backend, ref_name))) {
+ ref->db = db;
+ *out = ref;
+ } else {
+ *out = NULL;
+ }
+
+ return error;
}
int git_refdb_foreach(
diff --git a/src/refdb.h b/src/refdb.h
index 0969711b9..047113ac8 100644
--- a/src/refdb.h
+++ b/src/refdb.h
@@ -41,6 +41,6 @@ int git_refdb_foreach_glob(
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_delete(git_refdb *refdb, const git_reference *ref);
#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index f00bd72a0..c0a32bae7 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -11,14 +11,14 @@
#include "fileops.h"
#include "pack.h"
#include "reflog.h"
-#include "config.h"
#include "refdb.h"
#include "refdb_fs.h"
#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
-#include <git2/refdb_backend.h>
+#include <git2/sys/refdb_backend.h>
+#include <git2/sys/refs.h>
GIT__USE_STRMAP;
@@ -26,8 +26,15 @@ 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
+};
+
+enum {
+ PEELING_NONE = 0,
+ PEELING_STANDARD,
+ PEELING_FULL
};
struct packref {
@@ -41,10 +48,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 +69,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);
@@ -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,7 @@ static int packed_load(refdb_fs_backend *backend)
ref_cache->packfile = git_strmap_alloc();
GITERR_CHECK_ALLOC(ref_cache->packfile);
}
-
+
result = reference_read(&packfile, &ref_cache->packfile_time,
backend->path, GIT_PACKEDREFS_FILE, &updated);
@@ -193,7 +197,7 @@ static int packed_load(refdb_fs_backend *backend)
if (result < 0)
return -1;
-
+
if (!updated)
return 0;
@@ -206,6 +210,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 +252,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 +273,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 +295,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;
}
@@ -290,13 +322,13 @@ static int loose_lookup_to_packfile(
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);
@@ -430,12 +462,12 @@ static int loose_lookup(
goto done;
}
- *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);
+ *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);
+
+ *out = git_reference__alloc(ref_name, &oid, NULL);
}
if (*out == NULL)
@@ -456,19 +488,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 +512,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;
}
@@ -582,7 +615,7 @@ static int refdb_fs_backend__foreach(
git_buf refs_path = GIT_BUF_INIT;
const char *ref_name;
void *ref = NULL;
-
+
GIT_UNUSED(ref);
assert(_backend);
@@ -590,7 +623,7 @@ static int refdb_fs_backend__foreach(
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, {
@@ -678,14 +711,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 +732,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 +765,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 +802,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)
@@ -924,7 +950,7 @@ static int refdb_fs_backend__delete(
repo = backend->repo;
/* If a loose reference exists, remove it from the filesystem */
-
+
if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0)
return -1;
@@ -932,7 +958,7 @@ static int refdb_fs_backend__delete(
error = p_unlink(loose_path.ptr);
loose_deleted = 1;
}
-
+
git_buf_free(&loose_path);
if (error != 0)
@@ -946,7 +972,7 @@ static int refdb_fs_backend__delete(
error = packed_write(backend);
}
-
+
if (pack_error == GIT_ENOTFOUND)
error = loose_deleted ? 0 : GIT_ENOTFOUND;
else
@@ -993,22 +1019,65 @@ 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;
+
+ /* 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);
+ 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;
diff --git a/src/refs.c b/src/refs.c
index b1f679632..b85a2e828 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -19,7 +19,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,37 +31,58 @@ enum {
GIT_PACKREF_WAS_LOOSE = 2
};
+static git_reference *alloc_ref(const char *name)
+{
+ git_reference *ref;
+ size_t namelen = strlen(name);
+
+ if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
+ return NULL;
+
+ memcpy(ref->name, name, namelen + 1);
+
+ return ref;
+}
+
+git_reference *git_reference__alloc_symbolic(
+ const char *name, const char *target)
+{
+ git_reference *ref;
+
+ assert(name && target);
+
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
+
+ ref->type = GIT_REF_SYMBOLIC;
+
+ if ((ref->target.symbolic = git__strdup(target)) == NULL) {
+ git__free(ref);
+ return NULL;
+ }
+
+ return ref;
+}
git_reference *git_reference__alloc(
- git_refdb *refdb,
const char *name,
const git_oid *oid,
- const char *symbolic)
+ const git_oid *peel)
{
git_reference *ref;
- size_t namelen;
-
- assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic)));
- namelen = strlen(name);
+ assert(name && oid);
- if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
+ ref = alloc_ref(name);
+ if (!ref)
return NULL;
- if (oid) {
- ref->type = GIT_REF_OID;
- git_oid_cpy(&ref->target.oid, oid);
- } else {
- ref->type = GIT_REF_SYMBOLIC;
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
- if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) {
- git__free(ref);
- return NULL;
- }
- }
-
- ref->db = refdb;
- memcpy(ref->name, name, namelen + 1);
+ if (peel != NULL)
+ git_oid_cpy(&ref->peel, peel);
return ref;
}
@@ -71,13 +92,8 @@ void git_reference_free(git_reference *reference)
if (reference == NULL)
return;
- if (reference->type == GIT_REF_SYMBOLIC) {
+ 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);
}
@@ -238,10 +254,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 +275,7 @@ int git_reference_lookup_resolved(
if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
return error;
-
+
scan_type = ref->type;
}
@@ -305,6 +321,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,7 +353,7 @@ static int reference__create(
git_refdb *refdb;
git_reference *ref = NULL;
int error = 0;
-
+
if (ref_out)
*ref_out = NULL;
@@ -335,15 +361,22 @@ static int reference__create(
(error = reference_can_write(repo, normalized, NULL, force)) < 0 ||
(error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
return error;
-
- if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL)
- return -1;
+
+ if (oid != NULL) {
+ assert(symbolic == NULL);
+ ref = git_reference__alloc(name, oid, NULL);
+ } else {
+ ref = git_reference__alloc_symbolic(name, symbolic);
+ }
+
+ GITERR_CHECK_ALLOC(ref);
+ ref->db = refdb;
if ((error = git_refdb_write(refdb, ref)) < 0) {
git_reference_free(ref);
return error;
}
-
+
if (ref_out == NULL)
git_reference_free(ref);
else
@@ -363,17 +396,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 +421,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 +435,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 +450,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);
}
@@ -437,17 +470,16 @@ int git_reference_rename(
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 ||
+ if ((error = git_reference_normalize_name(
+ normalized, sizeof(normalized), new_name, normalization_flags)) < 0 ||
(error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0)
return error;
@@ -455,16 +487,18 @@ int git_reference_rename(
* Create the new reference.
*/
if (ref->type == GIT_REF_OID) {
- oid = &ref->target.oid;
- symbolic = NULL;
+ result = git_reference__alloc(new_name, &ref->target.oid, &ref->peel);
+ } else if (ref->type == GIT_REF_SYMBOLIC) {
+ result = git_reference__alloc_symbolic(new_name, ref->target.symbolic);
} else {
- oid = NULL;
- symbolic = ref->target.symbolic;
+ assert(0);
}
-
- if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL)
+
+ if (result == NULL)
return -1;
+ result->db = ref->db;
+
/* Check if we have to update HEAD. */
if ((error = git_branch_is_head(ref)) < 0)
goto on_error;
@@ -474,11 +508,11 @@ int git_reference_rename(
/* Now delete the old ref and save the new one. */
if ((error = git_refdb_delete(ref->db, ref)) < 0)
goto on_error;
-
+
/* Save the new reference. */
if ((error = git_refdb_write(ref->db, result)) < 0)
goto rollback;
-
+
/* Update HEAD it was poiting to the reference being renamed. */
if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) {
giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
@@ -509,11 +543,17 @@ on_error:
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
{
- if (ref->type == GIT_REF_OID)
+ switch (git_reference_type(ref)) {
+ case 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);
+
+ case GIT_REF_SYMBOLIC:
+ return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
+
+ default:
+ giterr_set(GITERR_REFERENCE, "Invalid reference");
+ return -1;
+ }
}
int git_reference_foreach(
@@ -712,6 +752,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 +819,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(
@@ -801,7 +846,7 @@ static int reference__update_terminal(
if (nesting > MAX_NESTING_LEVEL)
return GIT_ENOTFOUND;
-
+
error = git_reference_lookup(&ref, repo, ref_name);
/* If we haven't found the reference at all, create a new reference. */
@@ -809,10 +854,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 +867,7 @@ static int reference__update_terminal(
git_reference_free(ref);
error = git_reference_create(NULL, repo, ref_name, oid, 1);
}
-
+
return error;
}
@@ -905,15 +950,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 +961,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 (!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 = reference_target(&target, resolved)) < 0) {
+ if (error < 0) {
peel_error(error, ref, "Cannot retrieve reference target");
goto cleanup;
}
@@ -940,7 +988,10 @@ int git_reference_peel(
cleanup:
git_object_free(target);
- git_reference_free(resolved);
+
+ if (resolved != ref)
+ git_reference_free(resolved);
+
return error;
}
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..a907df84c 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);
@@ -264,3 +274,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 56853834b..6eaaf8b49 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)
@@ -99,7 +111,7 @@ 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;
}
@@ -186,6 +198,18 @@ 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);
+}
+
int git_remote_load(git_remote **out, git_repository *repo, const char *name)
{
git_remote *remote;
@@ -193,6 +217,8 @@ 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 +237,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;
}
@@ -262,7 +289,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
goto cleanup;
}
- error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);
+ data.remote = remote;
+ data.fetch = true;
+ error = git_config_get_multivar(config, git_buf_cstr(&buf), NULL, refspec_cb, &data);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -277,7 +306,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
goto cleanup;
}
- error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);
+ data.fetch = false;
+ error = git_config_get_multivar(config, git_buf_cstr(&buf), NULL, refspec_cb, &data);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -300,36 +330,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;
+ git_buf name = GIT_BUF_INIT;
+ int push;
+ const char *dir;
+ size_t i;
int error = -1;
- 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 */
+ do {
+ error = git_config_delete_entry(config, git_buf_cstr(&name));
+ } while (!error);
- 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 +421,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
@@ -482,49 +512,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);
@@ -646,28 +633,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;
@@ -687,21 +751,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;
@@ -718,9 +782,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;
@@ -735,8 +798,6 @@ static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_hea
if (update_heads->length == 0)
return 0;
- spec = &remote->fetch;
-
if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
return -1;
@@ -746,7 +807,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. */
@@ -786,7 +847,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;
@@ -795,14 +856,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;
@@ -810,16 +868,12 @@ 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 (refs->length > 0) {
+ head = git_vector_get(refs, 0);
if (!strcmp(head->name, GIT_HEAD_FILE)) {
if (git_reference_create(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
@@ -830,15 +884,15 @@ int git_remote_update_tips(git_remote *remote)
}
}
- 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) {
@@ -869,7 +923,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 */
@@ -886,17 +940,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);
@@ -904,6 +956,34 @@ on_error:
}
+int git_remote_update_tips(git_remote *remote)
+{
+ git_refspec *spec;
+ git_vector refs;
+ size_t i;
+
+ if (git_vector_init(&refs, 16, NULL) < 0)
+ return -1;
+
+ if (git_remote_ls(remote, store_refs, &refs) < 0)
+ goto on_error;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (update_tips_for_spec(remote, spec, &refs) < 0)
+ goto on_error;
+ }
+
+ git_vector_free(&refs);
+ return 0;
+
+on_error:
+ git_vector_free(&refs);
+ return -1;
+}
+
int git_remote_connected(git_remote *remote)
{
assert(remote);
@@ -933,6 +1013,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;
@@ -945,8 +1028,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);
@@ -1237,58 +1324,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;
+ }
- if (git_buf_splice(
- &serialized,
- pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"),
- strlen(remote->name), new_name,
- strlen(new_name)) < 0)
+ continue;
+ }
+
+ /* 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;
}
@@ -1389,3 +1476,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..c9c26b77d 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -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..e6eaf753c 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"
@@ -31,42 +32,71 @@
#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)
@@ -74,17 +104,14 @@ 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__free(repo->path_repository);
git__free(repo->workdir);
-
- drop_config(repo);
- drop_index(repo);
- drop_odb(repo);
- drop_refdb(repo);
+ git__free(repo->namespace);
git__free(repo);
}
@@ -118,7 +145,7 @@ static git_repository *repository_alloc(void)
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 +156,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;
@@ -368,6 +401,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,
@@ -511,39 +575,47 @@ 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);
+
+ 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 +630,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 +675,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;
-
- res = git_refdb_open(&repo->_refdb, repo);
+ git_refdb *refdb;
- if (res < 0)
- return -1;
+ error = git_refdb_open(&refdb, repo);
+ if (!error) {
+ GIT_REFCOUNT_OWN(refdb, repo);
- 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 +714,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 +762,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);
+}
- drop_index(repo);
+int git_repository_set_namespace(git_repository *repo, const char *namespace)
+{
+ git__free(repo->namespace);
- repo->_index = index;
- GIT_REFCOUNT_OWN(repo->_index, repo);
- GIT_REFCOUNT_INC(index);
+ if (namespace == NULL) {
+ repo->namespace = NULL;
+ return 0;
+ }
+
+ 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 +1469,13 @@ 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(repo, GIT_REF_LISTALL, 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)
diff --git a/src/repository.h b/src/repository.h
index cc2f8c2b8..bd5f63dac 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -12,6 +12,7 @@
#include "git2/odb.h"
#include "git2/repository.h"
#include "git2/object.h"
+#include "git2/config.h"
#include "index.h"
#include "cache.h"
@@ -31,7 +32,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 +74,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 */
@@ -90,6 +111,7 @@ struct git_repository {
char *path_repository;
char *workdir;
+ char *namespace;
unsigned is_bare:1;
unsigned int lru_counter;
diff --git a/src/revparse.c b/src/revparse.c
index 74635ed04..8a22a04f3 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -16,7 +16,7 @@
static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
{
- int error, i;
+ int error = 0, i;
bool fallbackmode = true;
git_reference *ref;
git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
diff --git a/src/signature.c b/src/signature.c
index 164e8eb67..649dbcd3d 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -69,7 +69,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema
if (p->name == NULL || p->email == NULL ||
p->name[0] == '\0' || p->email[0] == '\0') {
git_signature_free(p);
- return -1;
+ return signature_error("Empty name or email");
}
p->when.time = time;
diff --git a/src/submodule.c b/src/submodule.c
index 2fdaf2f77..0a22e3b13 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -7,6 +7,7 @@
#include "common.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "git2/types.h"
#include "git2/repository.h"
#include "git2/index.h"
diff --git a/src/tag.c b/src/tag.c
index 735ba7e1d..a4f2e2581 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,23 @@ 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_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 +320,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 +393,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 {
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..49b5f3b5e 100644
--- a/src/thread-utils.h
+++ b/src/thread-utils.h
@@ -18,6 +18,28 @@ typedef struct {
#endif
} git_atomic;
+#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
+
GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
{
a->val = val;
@@ -57,6 +79,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 +101,35 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
#endif
}
+GIT_INLINE(void *) git___compare_and_swap(
+ volatile void **ptr, void *oldval, void *newval)
+{
+ volatile void *foundval;
+#if defined(GIT_WIN32)
+ foundval = InterlockedCompareExchangePointer(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;
+}
+
+#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
@@ -96,13 +158,55 @@ 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(
+ volatile void **ptr, void *oldval, void *newval)
+{
+ if (*ptr == oldval)
+ *ptr = newval;
+ else
+ oldval = newval;
+ return oldval;
+}
+
+#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
+/* 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((volatile void **)P, O, N)
+
+#define git__swap(ptr, val) git__compare_and_swap(&ptr, 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/transports/local.c b/src/transports/local.c
index 8af970eac..8b4d50c14 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -282,7 +282,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.");
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 8acedeb49..a5ad1e422 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"
@@ -806,13 +807,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 +831,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/tree.c b/src/tree.c
index 17b3c378d..79cbcffcb 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -219,8 +219,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 +232,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;
@@ -371,8 +362,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;
@@ -416,12 +411,6 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf
return 0;
}
-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);
-}
-
static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
{
size_t dirlen, i, entries = git_index_entrycount(index);
@@ -525,7 +514,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 {
diff --git a/src/tree.h b/src/tree.h
index b77bfd961..7cb2dd36c 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.
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/util.c b/src/util.c
index 8e83d298e..8c8bc1a6c 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>
@@ -38,7 +39,6 @@ int git_libgit2_capabilities()
/* Declarations for tuneable settings */
extern size_t git_mwindow__window_size;
extern size_t git_mwindow__mapped_limit;
-extern size_t git_odb__cache_size;
static int config_level_to_futils_dir(int config_level)
{
@@ -94,12 +94,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;
}
diff --git a/src/util.h b/src/util.h
index c0f271997..687afe084 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))))
@@ -186,20 +188,20 @@ 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);
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) { \
@@ -304,7 +306,7 @@ int git__date_parse(git_time_t *out, const char *date);
/*
* Unescapes a string in-place.
- *
+ *
* Edge cases behavior:
* - "jackie\" -> "jacky\"
* - "chan\\" -> "chan\"
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/win32/pthread.c b/src/win32/pthread.c
index 105f4b89e..232709e54 100644
--- a/src/win32/pthread.c
+++ b/src/win32/pthread.c
@@ -14,22 +14,28 @@ 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)
+ 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 *);