summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Marti <tanoku@gmail.com>2013-04-16 17:46:41 +0200
committerVicent Marti <tanoku@gmail.com>2013-04-16 17:46:41 +0200
commita50086d174658914d4d6462afbc83b02825b1f5b (patch)
treee8daa1c7bf678222cf351445179837bed7db3a72 /src
parent5b9fac39d8a76b9139667c26a63e6b3f204b3977 (diff)
parentf124ebd457bfbf43de3516629aaba5a279636e04 (diff)
downloadlibgit2-a50086d174658914d4d6462afbc83b02825b1f5b.tar.gz
Merge branch 'development'v0.18.0
Diffstat (limited to 'src')
-rw-r--r--src/amiga/map.c48
-rw-r--r--src/attr.c156
-rw-r--r--src/attr.h21
-rw-r--r--src/attr_file.c108
-rw-r--r--src/attr_file.h32
-rw-r--r--src/attrcache.h24
-rw-r--r--src/blob.c136
-rw-r--r--src/blob.h2
-rw-r--r--src/branch.c569
-rw-r--r--src/branch.h12
-rw-r--r--src/bswap.h2
-rw-r--r--src/buf_text.c291
-rw-r--r--src/buf_text.h122
-rw-r--r--src/buffer.c122
-rw-r--r--src/buffer.h83
-rw-r--r--src/cache.c33
-rw-r--r--src/cache.h2
-rw-r--r--src/cc-compat.h4
-rw-r--r--src/checkout.c1383
-rw-r--r--src/checkout.h24
-rw-r--r--src/clone.c466
-rw-r--r--src/commit.c204
-rw-r--r--src/commit.h6
-rw-r--r--src/commit_list.c194
-rw-r--r--src/commit_list.h49
-rw-r--r--src/common.h73
-rw-r--r--src/compress.c53
-rw-r--r--src/compress.h16
-rw-r--r--src/config.c759
-rw-r--r--src/config.h28
-rw-r--r--src/config_cache.c4
-rw-r--r--src/config_file.c378
-rw-r--r--src/config_file.h43
-rw-r--r--src/crlf.c176
-rw-r--r--src/date.c876
-rw-r--r--src/delta-apply.c15
-rw-r--r--src/delta-apply.h19
-rw-r--r--src/delta.c424
-rw-r--r--src/delta.h114
-rw-r--r--src/diff.c914
-rw-r--r--src/diff.h44
-rw-r--r--src/diff_output.c1801
-rw-r--r--src/diff_output.h93
-rw-r--r--src/diff_tform.c687
-rw-r--r--src/errors.c74
-rw-r--r--src/fetch.c158
-rw-r--r--src/fetch.h17
-rw-r--r--src/fetchhead.c295
-rw-r--r--src/fetchhead.h34
-rw-r--r--src/filebuf.c81
-rw-r--r--src/filebuf.h7
-rw-r--r--src/fileops.c918
-rw-r--r--src/fileops.h258
-rw-r--r--src/filter.c85
-rw-r--r--src/filter.h31
-rw-r--r--src/fnmatch.c (renamed from src/compat/fnmatch.c)22
-rw-r--r--src/fnmatch.h (renamed from src/compat/fnmatch.h)9
-rw-r--r--src/global.c73
-rw-r--r--src/global.h19
-rw-r--r--src/graph.c178
-rw-r--r--src/hash.c77
-rw-r--r--src/hash.h30
-rw-r--r--src/hash/hash_generic.c (renamed from src/sha1.c)32
-rw-r--r--src/hash/hash_generic.h24
-rw-r--r--src/hash/hash_openssl.h45
-rw-r--r--src/hash/hash_win32.c291
-rw-r--r--src/hash/hash_win32.h140
-rw-r--r--src/hashsig.c368
-rw-r--r--src/hashsig.h72
-rw-r--r--src/ignore.c187
-rw-r--r--src/ignore.h8
-rw-r--r--src/index.c1056
-rw-r--r--src/index.h25
-rw-r--r--src/indexer.c733
-rw-r--r--src/iterator.c1213
-rw-r--r--src/iterator.h230
-rw-r--r--src/khash.h10
-rw-r--r--src/map.h6
-rw-r--r--src/merge.c296
-rw-r--r--src/merge.h22
-rw-r--r--src/message.c31
-rw-r--r--src/message.h5
-rw-r--r--src/mwindow.c109
-rw-r--r--src/mwindow.h4
-rw-r--r--src/netops.c516
-rw-r--r--src/netops.h65
-rw-r--r--src/notes.c687
-rw-r--r--src/notes.h3
-rw-r--r--src/object.c213
-rw-r--r--src/object.h32
-rw-r--r--src/odb.c292
-rw-r--r--src/odb.h31
-rw-r--r--src/odb_loose.c97
-rw-r--r--src/odb_pack.c306
-rw-r--r--src/offmap.h65
-rw-r--r--src/oid.c26
-rw-r--r--src/oidmap.h9
-rw-r--r--src/pack-objects.c1342
-rw-r--r--src/pack-objects.h87
-rw-r--r--src/pack.c396
-rw-r--r--src/pack.h58
-rw-r--r--src/path.c333
-rw-r--r--src/path.h90
-rw-r--r--src/pathspec.c168
-rw-r--r--src/pathspec.h40
-rw-r--r--src/pkt.h81
-rw-r--r--src/pool.c40
-rw-r--r--src/pool.h27
-rw-r--r--src/posix.c98
-rw-r--r--src/posix.h59
-rw-r--r--src/ppc/sha1.c70
-rw-r--r--src/ppc/sha1.h26
-rw-r--r--src/ppc/sha1ppc.S224
-rw-r--r--src/pqueue.c24
-rw-r--r--src/pqueue.h24
-rw-r--r--src/protocol.c58
-rw-r--r--src/protocol.h23
-rw-r--r--src/push.c653
-rw-r--r--src/push.h51
-rw-r--r--src/refdb.c185
-rw-r--r--src/refdb.h46
-rw-r--r--src/refdb_fs.c1023
-rw-r--r--src/refdb_fs.h15
-rw-r--r--src/reflog.c419
-rw-r--r--src/reflog.h3
-rw-r--r--src/refs.c1864
-rw-r--r--src/refs.h40
-rw-r--r--src/refspec.c196
-rw-r--r--src/refspec.h31
-rw-r--r--src/remote.c1107
-rw-r--r--src/remote.h21
-rw-r--r--src/repo_template.h58
-rw-r--r--src/repository.c1156
-rw-r--r--src/repository.h46
-rw-r--r--src/reset.c163
-rw-r--r--src/revparse.c912
-rw-r--r--src/revwalk.c515
-rw-r--r--src/revwalk.h44
-rw-r--r--src/sha1.h21
-rw-r--r--src/sha1_lookup.c2
-rw-r--r--src/sha1_lookup.h2
-rw-r--r--src/signature.c292
-rw-r--r--src/signature.h2
-rw-r--r--src/stash.c663
-rw-r--r--src/status.c218
-rw-r--r--src/strmap.h4
-rw-r--r--src/submodule.c1536
-rw-r--r--src/submodule.h102
-rw-r--r--src/tag.c135
-rw-r--r--src/tag.h2
-rw-r--r--src/thread-utils.c2
-rw-r--r--src/thread-utils.h18
-rw-r--r--src/trace.c39
-rw-r--r--src/trace.h56
-rw-r--r--src/transport.c115
-rw-r--r--src/transport.h108
-rw-r--r--src/transports/cred.c60
-rw-r--r--src/transports/cred_helpers.c49
-rw-r--r--src/transports/git.c544
-rw-r--r--src/transports/http.c1203
-rw-r--r--src/transports/local.c514
-rw-r--r--src/transports/smart.c345
-rw-r--r--src/transports/smart.h179
-rw-r--r--src/transports/smart_pkt.c (renamed from src/pkt.c)239
-rw-r--r--src/transports/smart_protocol.c856
-rw-r--r--src/transports/winhttp.c1136
-rw-r--r--src/tree-cache.c6
-rw-r--r--src/tree-cache.h2
-rw-r--r--src/tree.c647
-rw-r--r--src/tree.h23
-rw-r--r--src/tsort.c62
-rw-r--r--src/unix/map.c6
-rw-r--r--src/unix/posix.h22
-rw-r--r--src/unix/realpath.c30
-rw-r--r--src/util.c300
-rw-r--r--src/util.h118
-rw-r--r--src/vector.c193
-rw-r--r--src/vector.h51
-rw-r--r--src/win32/dir.c31
-rw-r--r--src/win32/dir.h2
-rw-r--r--src/win32/error.c77
-rw-r--r--src/win32/error.h13
-rw-r--r--src/win32/findfile.c238
-rw-r--r--src/win32/findfile.h27
-rw-r--r--src/win32/git2.rc8
-rw-r--r--src/win32/map.c2
-rw-r--r--src/win32/mingw-compat.h2
-rw-r--r--src/win32/msvc-compat.h12
-rw-r--r--src/win32/posix.h22
-rw-r--r--src/win32/posix_w32.c411
-rw-r--r--src/win32/precompiled.c1
-rw-r--r--src/win32/precompiled.h19
-rw-r--r--src/win32/pthread.c70
-rw-r--r--src/win32/pthread.h9
-rw-r--r--src/win32/utf-conv.c136
-rw-r--r--src/win32/utf-conv.h9
-rw-r--r--src/win32/version.h20
197 files changed, 34094 insertions, 8958 deletions
diff --git a/src/amiga/map.c b/src/amiga/map.c
new file mode 100644
index 000000000..0ba7995c6
--- /dev/null
+++ b/src/amiga/map.c
@@ -0,0 +1,48 @@
+/*
+ * 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/common.h>
+
+#ifndef GIT_WIN32
+
+#include "posix.h"
+#include "map.h"
+#include <errno.h>
+
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
+{
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
+
+ out->data = NULL;
+ out->len = 0;
+
+ if ((prot & GIT_PROT_WRITE) && ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)) {
+ giterr_set(GITERR_OS, "Trying to map shared-writeable");
+ return -1;
+ }
+
+ out->data = malloc(len);
+ GITERR_CHECK_ALLOC(out->data);
+
+ if ((p_lseek(fd, offset, SEEK_SET) < 0) || ((size_t)p_read(fd, out->data, len) != len)) {
+ giterr_set(GITERR_OS, "mmap emulation failed");
+ return -1;
+ }
+
+ out->len = len;
+ return 0;
+}
+
+int p_munmap(git_map *map)
+{
+ assert(map != NULL);
+ free(map->data);
+
+ return 0;
+}
+
+#endif
+
diff --git a/src/attr.c b/src/attr.c
index 093f64d5c..979fecc14 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -1,10 +1,32 @@
#include "repository.h"
#include "fileops.h"
#include "config.h"
+#include "attr.h"
+#include "ignore.h"
+#include "git2/oid.h"
#include <ctype.h>
GIT__USE_STRMAP;
+const char *git_attr__true = "[internal]__TRUE__";
+const char *git_attr__false = "[internal]__FALSE__";
+const char *git_attr__unset = "[internal]__UNSET__";
+
+git_attr_t git_attr_value(const char *attr)
+{
+ if (attr == NULL || attr == git_attr__unset)
+ return GIT_ATTR_UNSPECIFIED_T;
+
+ if (attr == git_attr__true)
+ return GIT_ATTR_TRUE_T;
+
+ if (attr == git_attr__false)
+ return GIT_ATTR_FALSE_T;
+
+ return GIT_ATTR_VALUE_T;
+}
+
+
static int collect_attr_files(
git_repository *repo,
uint32_t flags,
@@ -22,7 +44,7 @@ int git_attr_get(
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
- unsigned int i, j;
+ size_t i, j;
git_attr_file *file;
git_attr_name attr;
git_attr_rule *rule;
@@ -41,8 +63,9 @@ int git_attr_get(
git_vector_foreach(&files, i, file) {
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
- int pos = git_vector_bsearch(&rule->assigns, &attr);
- if (pos >= 0) {
+ size_t pos;
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
*value = ((git_attr_assignment *)git_vector_get(
&rule->assigns, pos))->value;
goto cleanup;
@@ -74,7 +97,7 @@ int git_attr_get_many(
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
- unsigned int i, j, k;
+ size_t i, j, k;
git_attr_file *file;
git_attr_rule *rule;
attr_get_many_info *info = NULL;
@@ -96,7 +119,7 @@ int git_attr_get_many(
git_attr_file__foreach_matching_rule(file, &path, j, rule) {
for (k = 0; k < num_attr; k++) {
- int pos;
+ size_t pos;
if (info[k].found != NULL) /* already found assignment */
continue;
@@ -106,8 +129,7 @@ int git_attr_get_many(
info[k].name.name_hash = git_attr_file__name_hash(names[k]);
}
- pos = git_vector_bsearch(&rule->assigns, &info[k].name);
- if (pos >= 0) {
+ if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
info[k].found = (git_attr_assignment *)
git_vector_get(&rule->assigns, pos);
values[k] = info[k].found->value;
@@ -138,7 +160,7 @@ int git_attr_foreach(
int error;
git_attr_path path;
git_vector files = GIT_VECTOR_INIT;
- unsigned int i, j, k;
+ size_t i, j, k;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
@@ -163,11 +185,15 @@ int git_attr_foreach(
continue;
git_strmap_insert(seen, assign->name, assign, error);
- if (error >= 0)
- error = callback(assign->name, assign->value, payload);
+ if (error < 0)
+ goto cleanup;
- if (error != 0)
+ error = callback(assign->name, assign->value, payload);
+ if (error) {
+ giterr_clear();
+ error = GIT_EUSER;
goto cleanup;
+ }
}
}
}
@@ -237,30 +263,30 @@ bool git_attr_cache__is_cached(
static int load_attr_file(
const char **data,
- git_attr_file_stat_sig *sig,
+ git_futils_filestamp *stamp,
const char *filename)
{
int error;
git_buf content = GIT_BUF_INIT;
- struct stat st;
- if (p_stat(filename, &st) < 0)
- return GIT_ENOTFOUND;
+ error = git_futils_filestamp_check(stamp, filename);
+ if (error < 0)
+ return error;
- if (sig != NULL &&
- (git_time_t)st.st_mtime == sig->seconds &&
- (git_off_t)st.st_size == sig->size &&
- (unsigned int)st.st_ino == sig->ino)
+ /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
+ * we tell the caller not to reparse this file...
+ */
+ if (!error)
return GIT_ENOTFOUND;
- error = git_futils_readbuffer_updated(&content, filename, NULL, NULL);
- if (error < 0)
- return error;
+ error = git_futils_readbuffer(&content, filename);
+ if (error < 0) {
+ /* convert error into ENOTFOUND so failed permissions / invalid
+ * file type don't actually stop the operation in progress.
+ */
+ return GIT_ENOTFOUND;
- if (sig != NULL) {
- sig->seconds = (git_time_t)st.st_mtime;
- sig->size = (git_off_t)st.st_size;
- sig->ino = (unsigned int)st.st_ino;
+ /* TODO: once warnings are available, issue a warning callback */
}
*data = git_buf_detach(&content);
@@ -276,14 +302,15 @@ static int load_attr_blob_from_index(
const char *relfile)
{
int error;
+ size_t pos;
git_index *index;
- git_index_entry *entry;
+ const git_index_entry *entry;
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
- (error = git_index_find(index, relfile)) < 0)
+ (error = git_index_find(&pos, index, relfile)) < 0)
return error;
- entry = git_index_get(index, error);
+ entry = git_index_get_byindex(index, pos);
if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
return GIT_ENOTFOUND;
@@ -352,6 +379,7 @@ int git_attr_cache__push_file(
const char *filename,
git_attr_file_source source,
git_attr_file_parser parse,
+ void* parsedata,
git_vector *stack)
{
int error = 0;
@@ -361,7 +389,7 @@ int git_attr_cache__push_file(
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file *file = NULL;
git_blob *blob = NULL;
- git_attr_file_stat_sig st;
+ git_futils_filestamp stamp;
assert(filename && stack);
@@ -383,12 +411,10 @@ int git_attr_cache__push_file(
/* if not in cache, load data, parse, and cache */
if (source == GIT_ATTR_FILE_FROM_FILE) {
- if (file)
- memcpy(&st, &file->cache_data.st, sizeof(st));
- else
- memset(&st, 0, sizeof(st));
+ git_futils_filestamp_set(
+ &stamp, file ? &file->cache_data.stamp : NULL);
- error = load_attr_file(&content, &st, filename);
+ error = load_attr_file(&content, &stamp, filename);
} else {
error = load_attr_blob_from_index(&content, &blob,
repo, file ? &file->cache_data.oid : NULL, relfile);
@@ -403,14 +429,19 @@ int git_attr_cache__push_file(
goto finish;
}
- if (!file &&
- (error = git_attr_file__new(&file, source, relfile, &cache->pool)) < 0)
- goto finish;
+ /* if we got here, we have to parse and/or reparse the file */
+ if (file)
+ git_attr_file__clear_rules(file);
+ else {
+ error = git_attr_file__new(&file, source, relfile, &cache->pool);
+ if (error < 0)
+ goto finish;
+ }
- if (parse && (error = parse(repo, content, file)) < 0)
+ if (parse && (error = parse(repo, parsedata, content, file)) < 0)
goto finish;
- git_strmap_insert(cache->files, file->key, file, error);
+ git_strmap_insert(cache->files, file->key, file, error); //-V595
if (error > 0)
error = 0;
@@ -418,7 +449,7 @@ int git_attr_cache__push_file(
if (blob)
git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
else
- memcpy(&file->cache_data.st, &st, sizeof(st));
+ git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
finish:
/* push file onto vector if we found one*/
@@ -439,7 +470,7 @@ finish:
}
#define push_attr_file(R,S,B,F) \
- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S))
+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
typedef struct {
git_repository *repo;
@@ -488,7 +519,7 @@ static int push_one_attr(void *ref, git_buf *path)
for (i = 0; !error && i < n_src; ++i)
error = git_attr_cache__push_file(
info->repo, path->ptr, GIT_ATTR_FILE, src[i],
- git_attr_file__parse_buffer, info->files);
+ git_attr_file__parse_buffer, NULL, info->files);
return error;
}
@@ -550,8 +581,10 @@ static int collect_attr_files(
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
if (!error)
error = push_attr_file(repo, files, NULL, dir.ptr);
- else if (error == GIT_ENOTFOUND)
+ else if (error == GIT_ENOTFOUND) {
+ giterr_clear();
error = 0;
+ }
}
cleanup:
@@ -562,6 +595,29 @@ static int collect_attr_files(
return error;
}
+static int attr_cache__lookup_path(
+ const char **out, git_config *cfg, const char *key, const char *fallback)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (!(error = git_config_get_string(out, cfg, key)))
+ return 0;
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+
+ if (!git_futils_find_xdg_file(&buf, fallback))
+ *out = git_buf_detach(&buf);
+ else
+ *out = NULL;
+
+ git_buf_free(&buf);
+ }
+
+ return error;
+}
int git_attr_cache__init(git_repository *repo)
{
@@ -576,16 +632,16 @@ int git_attr_cache__init(git_repository *repo)
if (git_repository_config__weakptr(&cfg, repo) < 0)
return -1;
- ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG);
- if (ret < 0 && ret != GIT_ENOTFOUND)
+ ret = attr_cache__lookup_path(
+ &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
+ if (ret < 0)
return ret;
- ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG);
- if (ret < 0 && ret != GIT_ENOTFOUND)
+ ret = attr_cache__lookup_path(
+ &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
+ if (ret < 0)
return ret;
- giterr_clear();
-
/* allocate hashtable for attribute and ignore file contents */
if (cache->files == NULL) {
cache->files = git_strmap_alloc();
diff --git a/src/attr.h b/src/attr.h
index a35b1160f..19c979bcd 100644
--- a/src/attr.h
+++ b/src/attr.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,24 +8,12 @@
#define INCLUDE_attr_h__
#include "attr_file.h"
-#include "strmap.h"
-#define GIT_ATTR_CONFIG "core.attributesfile"
-#define GIT_IGNORE_CONFIG "core.excludesfile"
-
-typedef struct {
- int initialized;
- git_pool pool;
- git_strmap *files; /* hash path to git_attr_file of rules */
- git_strmap *macros; /* hash name to vector<git_attr_assignment> */
- const char *cfg_attr_file; /* cached value of core.attributesfile */
- const char *cfg_excl_file; /* cached value of core.excludesfile */
-} git_attr_cache;
+#define GIT_ATTR_CONFIG "core.attributesfile"
+#define GIT_IGNORE_CONFIG "core.excludesfile"
typedef int (*git_attr_file_parser)(
- git_repository *, const char *, git_attr_file *);
-
-extern int git_attr_cache__init(git_repository *repo);
+ git_repository *, void *, const char *, git_attr_file *);
extern int git_attr_cache__insert_macro(
git_repository *repo, git_attr_rule *macro);
@@ -39,6 +27,7 @@ extern int git_attr_cache__push_file(
const char *filename,
git_attr_file_source source,
git_attr_file_parser parse,
+ void *parsedata, /* passed through to parse function */
git_vector *stack);
extern int git_attr_cache__internal_file(
diff --git a/src/attr_file.c b/src/attr_file.c
index 5030ad5de..85cd87624 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -1,16 +1,17 @@
#include "common.h"
#include "repository.h"
#include "filebuf.h"
+#include "attr.h"
#include "git2/blob.h"
#include "git2/tree.h"
#include <ctype.h>
-const char *git_attr__true = "[internal]__TRUE__";
-const char *git_attr__false = "[internal]__FALSE__";
-const char *git_attr__unset = "[internal]__UNSET__";
-
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
static void git_attr_rule__clear(git_attr_rule *rule);
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern);
int git_attr_file__new(
git_attr_file **attrs_ptr,
@@ -57,13 +58,15 @@ fail:
}
int git_attr_file__parse_buffer(
- git_repository *repo, const char *buffer, git_attr_file *attrs)
+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
{
int error = 0;
const char *scan = NULL;
char *context = NULL;
git_attr_rule *rule = NULL;
+ GIT_UNUSED(parsedata);
+
assert(buffer && attrs);
scan = buffer;
@@ -127,7 +130,7 @@ int git_attr_file__new_and_load(
if (!(error = git_futils_readbuffer(&content, path)))
error = git_attr_file__parse_buffer(
- NULL, git_buf_cstr(&content), *attrs_ptr);
+ NULL, NULL, git_buf_cstr(&content), *attrs_ptr);
git_buf_free(&content);
@@ -139,18 +142,23 @@ int git_attr_file__new_and_load(
return error;
}
-void git_attr_file__free(git_attr_file *file)
+void git_attr_file__clear_rules(git_attr_file *file)
{
unsigned int i;
git_attr_rule *rule;
- if (!file)
- return;
-
git_vector_foreach(&file->rules, i, rule)
git_attr_rule__free(rule);
git_vector_free(&file->rules);
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+ if (!file)
+ return;
+
+ git_attr_file__clear_rules(file);
if (file->pool_is_allocated) {
git_pool_clear(file->pool);
@@ -178,7 +186,7 @@ int git_attr_file__lookup_one(
const char *attr,
const char **value)
{
- unsigned int i;
+ size_t i;
git_attr_name name;
git_attr_rule *rule;
@@ -188,9 +196,9 @@ int git_attr_file__lookup_one(
name.name_hash = git_attr_file__name_hash(attr);
git_attr_file__foreach_matching_rule(file, path, i, rule) {
- int pos = git_vector_bsearch(&rule->assigns, &name);
+ size_t pos;
- if (pos >= 0) {
+ if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
*value = ((git_attr_assignment *)
git_vector_get(&rule->assigns, pos))->value;
break;
@@ -206,16 +214,17 @@ bool git_attr_fnmatch__match(
const git_attr_path *path)
{
int fnm;
+ int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0;
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
return false;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
- fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME);
+ fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags);
else if (path->is_dir)
- fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR);
+ fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags);
else
- fnm = p_fnmatch(match->pattern, path->basename, 0);
+ fnm = p_fnmatch(match->pattern, path->basename, icase_flags);
return (fnm == FNM_NOMATCH) ? false : true;
}
@@ -236,31 +245,29 @@ bool git_attr_rule__match(
git_attr_assignment *git_attr_rule__lookup_assignment(
git_attr_rule *rule, const char *name)
{
- int pos;
+ size_t pos;
git_attr_name key;
key.name = name;
key.name_hash = git_attr_file__name_hash(name);
- pos = git_vector_bsearch(&rule->assigns, &key);
+ if (git_vector_bsearch(&pos, &rule->assigns, &key))
+ return NULL;
- return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
+ return git_vector_get(&rule->assigns, pos);
}
int git_attr_path__init(
git_attr_path *info, const char *path, const char *base)
{
+ ssize_t root;
+
/* build full path as best we can */
git_buf_init(&info->full, 0);
- if (base != NULL && git_path_root(path) < 0) {
- if (git_buf_joinpath(&info->full, base, path) < 0)
- return -1;
- info->path = info->full.ptr + strlen(base);
- } else {
- if (git_buf_sets(&info->full, path) < 0)
- return -1;
- info->path = info->full.ptr;
- }
+ if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
+ return -1;
+
+ info->path = info->full.ptr + root;
/* remove trailing slashes */
while (info->full.size > 0) {
@@ -293,7 +300,6 @@ void git_attr_path__free(git_attr_path *info)
info->basename = NULL;
}
-
/*
* From gitattributes(5):
*
@@ -338,10 +344,16 @@ int git_attr_fnmatch__parse(
const char **base)
{
const char *pattern, *scan;
- int slash_count;
+ int slash_count, allow_space;
assert(spec && base && *base);
+ if (parse_optimized_patterns(spec, pool, *base))
+ return 0;
+
+ spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE);
+ allow_space = (spec->flags != 0);
+
pattern = *base;
while (git__isspace(*pattern)) pattern++;
@@ -350,8 +362,6 @@ int git_attr_fnmatch__parse(
return GIT_ENOTFOUND;
}
- spec->flags = 0;
-
if (*pattern == '[') {
if (strncmp(pattern, "[attr]", 6) == 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
@@ -368,8 +378,10 @@ int git_attr_fnmatch__parse(
slash_count = 0;
for (scan = pattern; *scan != '\0'; ++scan) {
/* scan until (non-escaped) white space */
- if (git__isspace(*scan) && *(scan - 1) != '\\')
- break;
+ if (git__isspace(*scan) && *(scan - 1) != '\\') {
+ if (!allow_space || (*scan != ' ' && *scan != '\t'))
+ break;
+ }
if (*scan == '/') {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
@@ -418,22 +430,28 @@ int git_attr_fnmatch__parse(
return -1;
} else {
/* strip '\' that might have be used for internal whitespace */
- char *to = spec->pattern;
- for (scan = spec->pattern; *scan; to++, scan++) {
- if (*scan == '\\')
- scan++; /* skip '\' but include next char */
- if (to != scan)
- *to = *scan;
- }
- if (to != scan) {
- *to = '\0';
- spec->length = (to - spec->pattern);
- }
+ spec->length = git__unescape(spec->pattern);
}
return 0;
}
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern)
+{
+ if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
+ spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
+ spec->pattern = git_pool_strndup(pool, pattern, 1);
+ spec->length = 1;
+
+ return true;
+ }
+
+ return false;
+}
+
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
{
const git_attr_name *a = a_raw;
diff --git a/src/attr_file.h b/src/attr_file.h
index 3718f4bda..d8abcda58 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,14 +7,17 @@
#ifndef INCLUDE_attr_file_h__
#define INCLUDE_attr_file_h__
+#include "git2/oid.h"
#include "git2/attr.h"
#include "vector.h"
#include "pool.h"
#include "buffer.h"
+#include "fileops.h"
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_INREPO "info/attributes"
#define GIT_ATTR_FILE_SYSTEM "gitattributes"
+#define GIT_ATTR_FILE_XDG "attributes"
#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
@@ -22,6 +25,13 @@
#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
#define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
+#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
+#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
+#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
+
+extern const char *git_attr__true;
+extern const char *git_attr__false;
+extern const char *git_attr__unset;
typedef struct {
char *pattern;
@@ -48,27 +58,21 @@ typedef struct {
} git_attr_assignment;
typedef struct {
- git_time_t seconds;
- git_off_t size;
- unsigned int ino;
-} git_attr_file_stat_sig;
-
-typedef struct {
char *key; /* cache "source#path" this was loaded from */
git_vector rules; /* vector of <rule*> or <fnmatch*> */
git_pool *pool;
bool pool_is_allocated;
union {
git_oid oid;
- git_attr_file_stat_sig st;
+ git_futils_filestamp stamp;
} cache_data;
} git_attr_file;
typedef struct {
- git_buf full;
- const char *path;
- const char *basename;
- int is_dir;
+ git_buf full;
+ char *path;
+ char *basename;
+ int is_dir;
} git_attr_path;
typedef enum {
@@ -88,8 +92,10 @@ extern int git_attr_file__new_and_load(
extern void git_attr_file__free(git_attr_file *file);
+extern void git_attr_file__clear_rules(git_attr_file *file);
+
extern int git_attr_file__parse_buffer(
- git_repository *repo, const char *buf, git_attr_file *file);
+ git_repository *repo, void *parsedata, const char *buf, git_attr_file *file);
extern int git_attr_file__lookup_one(
git_attr_file *file,
diff --git a/src/attrcache.h b/src/attrcache.h
new file mode 100644
index 000000000..12cec4bfb
--- /dev/null
+++ b/src/attrcache.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attrcache_h__
+#define INCLUDE_attrcache_h__
+
+#include "pool.h"
+#include "strmap.h"
+
+typedef struct {
+ int initialized;
+ git_pool pool;
+ git_strmap *files; /* hash path to git_attr_file of rules */
+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */
+ const char *cfg_attr_file; /* cached value of core.attributesfile */
+ const char *cfg_excl_file; /* cached value of core.excludesfile */
+} git_attr_cache;
+
+extern int git_attr_cache__init(git_repository *repo);
+
+#endif
diff --git a/src/blob.c b/src/blob.c
index e25944b91..c0514fc13 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,17 +12,18 @@
#include "common.h"
#include "blob.h"
#include "filter.h"
+#include "buf_text.h"
-const void *git_blob_rawcontent(git_blob *blob)
+const void *git_blob_rawcontent(const git_blob *blob)
{
assert(blob);
return blob->odb_object->raw.data;
}
-size_t git_blob_rawsize(git_blob *blob)
+git_off_t git_blob_rawsize(const git_blob *blob)
{
assert(blob);
- return blob->odb_object->raw.len;
+ return (git_off_t)blob->odb_object->raw.len;
}
int git_blob__getbuf(git_buf *buffer, git_blob *blob)
@@ -68,6 +69,7 @@ static int write_file_stream(
int fd, error;
char buffer[4096];
git_odb_stream *stream = NULL;
+ ssize_t read_len = -1, written = 0;
if ((error = git_odb_open_wstream(
&stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)
@@ -78,20 +80,18 @@ static int write_file_stream(
return -1;
}
- while (!error && file_size > 0) {
- ssize_t read_len = p_read(fd, buffer, sizeof(buffer));
-
- if (read_len < 0) {
- giterr_set(
- GITERR_OS, "Failed to create blob. Can't read whole file");
- error = -1;
- }
- else if (!(error = stream->write(stream, buffer, read_len)))
- file_size -= read_len;
+ while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ error = stream->write(stream, buffer, read_len);
+ written += read_len;
}
p_close(fd);
+ if (written != file_size || read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read file into stream");
+ error = -1;
+ }
+
if (!error)
error = stream->finalize_write(oid, stream);
@@ -148,27 +148,31 @@ static int write_symlink(
return error;
}
-static int blob_create_internal(git_oid *oid, git_repository *repo, const char *path)
+static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters)
{
int error;
struct stat st;
git_odb *odb = NULL;
git_off_t size;
- if ((error = git_path_lstat(path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ assert(hint_path || !try_load_filters);
+
+ if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0)
return error;
size = st.st_size;
if (S_ISLNK(st.st_mode)) {
- error = write_symlink(oid, odb, path, (size_t)size);
+ error = write_symlink(oid, odb, content_path, (size_t)size);
} else {
git_vector write_filters = GIT_VECTOR_INIT;
- int filter_count;
+ int filter_count = 0;
- /* Load the filters for writing this file to the ODB */
- filter_count = git_filters_load(
- &write_filters, repo, path, GIT_FILTER_TO_ODB);
+ if (try_load_filters) {
+ /* Load the filters for writing this file to the ODB */
+ filter_count = git_filters_load(
+ &write_filters, repo, hint_path, GIT_FILTER_TO_ODB);
+ }
if (filter_count < 0) {
/* Negative value means there was a critical error */
@@ -176,10 +180,10 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
} else if (filter_count == 0) {
/* No filters need to be applied to the document: we can stream
* directly from disk */
- error = write_file_stream(oid, odb, path, size);
+ error = write_file_stream(oid, odb, content_path, size);
} else {
/* We need to apply one or more filters */
- error = write_file_filtered(oid, odb, path, &write_filters);
+ error = write_file_filtered(oid, odb, content_path, &write_filters);
}
git_filters_free(&write_filters);
@@ -202,21 +206,25 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char *
return error;
}
-int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path)
+int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path)
{
git_buf full_path = GIT_BUF_INIT;
const char *workdir;
int error;
+ if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)
+ return error;
+
workdir = git_repository_workdir(repo);
- assert(workdir); /* error to call this on bare repo */
if (git_buf_joinpath(&full_path, workdir, path) < 0) {
git_buf_free(&full_path);
return -1;
}
- error = blob_create_internal(oid, repo, git_buf_cstr(&full_path));
+ error = blob_create_internal(
+ oid, repo, git_buf_cstr(&full_path),
+ git_buf_cstr(&full_path) + strlen(workdir), true);
git_buf_free(&full_path);
return error;
@@ -226,14 +234,88 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat
{
int error;
git_buf full_path = GIT_BUF_INIT;
+ const char *workdir, *hintpath;
if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
git_buf_free(&full_path);
return error;
}
- error = blob_create_internal(oid, repo, git_buf_cstr(&full_path));
+ hintpath = git_buf_cstr(&full_path);
+ workdir = git_repository_workdir(repo);
+
+ if (workdir && !git__prefixcmp(hintpath, workdir))
+ hintpath += strlen(workdir);
+
+ error = blob_create_internal(
+ oid, repo, git_buf_cstr(&full_path), hintpath, true);
git_buf_free(&full_path);
return error;
}
+
+#define BUFFER_SIZE 4096
+
+int git_blob_create_fromchunks(
+ git_oid *oid,
+ git_repository *repo,
+ const char *hintpath,
+ int (*source_cb)(char *content, size_t max_length, void *payload),
+ void *payload)
+{
+ int error = -1, read_bytes;
+ char *content = NULL;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf path = GIT_BUF_INIT;
+
+ if (git_buf_join_n(
+ &path, '/', 3,
+ git_repository_path(repo),
+ GIT_OBJECTS_DIR,
+ "streamed") < 0)
+ goto cleanup;
+
+ content = git__malloc(BUFFER_SIZE);
+ GITERR_CHECK_ALLOC(content);
+
+ if (git_filebuf_open(&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY) < 0)
+ goto cleanup;
+
+ while (1) {
+ read_bytes = source_cb(content, BUFFER_SIZE, payload);
+
+ assert(read_bytes <= BUFFER_SIZE);
+
+ if (read_bytes <= 0)
+ break;
+
+ if (git_filebuf_write(&file, content, read_bytes) < 0)
+ goto cleanup;
+ }
+
+ if (read_bytes < 0)
+ goto cleanup;
+
+ if (git_filebuf_flush(&file) < 0)
+ goto cleanup;
+
+ error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL);
+
+cleanup:
+ git_buf_free(&path);
+ git_filebuf_cleanup(&file);
+ git__free(content);
+ return error;
+}
+
+int git_blob_is_binary(git_blob *blob)
+{
+ git_buf content;
+
+ assert(blob);
+
+ content.ptr = blob->odb_object->raw.data;
+ content.size = min(blob->odb_object->raw.len, 4000);
+
+ return git_buf_text_is_binary(&content);
+}
diff --git a/src/blob.h b/src/blob.h
index 0305e9473..524734b1f 100644
--- a/src/blob.h
+++ b/src/blob.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/branch.c b/src/branch.c
index 5d5a24038..e7088790e 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,8 +7,12 @@
#include "common.h"
#include "commit.h"
-#include "branch.h"
#include "tag.h"
+#include "config.h"
+#include "refspec.h"
+#include "refs.h"
+
+#include "git2/branch.h"
static int retrieve_branch_reference(
git_reference **branch_reference_out,
@@ -41,168 +45,539 @@ cleanup:
return error;
}
-static int create_error_invalid(const char *msg)
+static int not_a_local_branch(const char *reference_name)
{
- giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg);
+ giterr_set(
+ GITERR_INVALID,
+ "Reference '%s' is not a local branch.", reference_name);
return -1;
}
int git_branch_create(
- git_oid *oid_out,
- git_repository *repo,
- const char *branch_name,
- const git_object *target,
- int force)
-{
- git_otype target_type = GIT_OBJ_BAD;
- git_object *commit = NULL;
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ int force)
+{
git_reference *branch = NULL;
git_buf canonical_branch_name = GIT_BUF_INIT;
int error = -1;
- assert(repo && branch_name && target && oid_out);
-
- if (git_object_owner(target) != repo)
- return create_error_invalid("The given target does not belong to this repository");
-
- target_type = git_object_type(target);
-
- switch (target_type)
- {
- case GIT_OBJ_TAG:
- if (git_tag_peel(&commit, (git_tag *)target) < 0)
- goto cleanup;
-
- if (git_object_type(commit) != GIT_OBJ_COMMIT) {
- create_error_invalid("The given target does not resolve to a commit");
- goto cleanup;
- }
- break;
-
- case GIT_OBJ_COMMIT:
- commit = (git_object *)target;
- break;
-
- default:
- return create_error_invalid("Only git_tag and git_commit objects are valid targets.");
- }
+ assert(branch_name && commit && ref_out);
+ assert(git_object_owner((const git_object *)commit) == repository);
if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
goto cleanup;
- if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0)
- goto cleanup;
+ error = git_reference_create(&branch, repository,
+ git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force);
- git_oid_cpy(oid_out, git_reference_oid(branch));
- error = 0;
+ if (!error)
+ *ref_out = branch;
cleanup:
- if (target_type == GIT_OBJ_TAG)
- git_object_free(commit);
-
- git_reference_free(branch);
git_buf_free(&canonical_branch_name);
return error;
}
-int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_t branch_type)
+int git_branch_delete(git_reference *branch)
{
- git_reference *branch = NULL;
- git_reference *head = NULL;
- int error;
+ int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(branch);
- assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE));
+ if (!git_reference_is_branch(branch) &&
+ !git_reference_is_remote(branch)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.", git_reference_name(branch));
+ return -1;
+ }
- if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0)
- return error;
+ if ((is_head = git_branch_is_head(branch)) < 0)
+ return is_head;
- if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
- giterr_set(GITERR_REFERENCE, "Cannot locate HEAD.");
- goto on_error;
+ if (is_head) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch));
+ return -1;
}
- if ((git_reference_type(head) == GIT_REF_SYMBOLIC)
- && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) {
- giterr_set(GITERR_REFERENCE,
- "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name);
+ if (git_buf_printf(&config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto on_error;
+
+ if (git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&config_section),
+ NULL) < 0)
goto on_error;
- }
if (git_reference_delete(branch) < 0)
goto on_error;
- git_reference_free(head);
- return 0;
+ error = 0;
on_error:
- git_reference_free(head);
- git_reference_free(branch);
- return -1;
+ git_buf_free(&config_section);
+ return error;
}
typedef struct {
- git_vector *branchlist;
+ git_branch_foreach_cb branch_cb;
+ void *callback_payload;
unsigned int branch_type;
-} branch_filter_data;
+} branch_foreach_filter;
-static int branch_list_cb(const char *branch_name, void *payload)
+static int branch_foreach_cb(const char *branch_name, void *payload)
{
- branch_filter_data *filter = (branch_filter_data *)payload;
+ branch_foreach_filter *filter = (branch_foreach_filter *)payload;
+
+ if (filter->branch_type & GIT_BRANCH_LOCAL &&
+ git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
+ return filter->branch_cb(branch_name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, filter->callback_payload);
- if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)
- || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0))
- return git_vector_insert(filter->branchlist, git__strdup(branch_name));
+ if (filter->branch_type & GIT_BRANCH_REMOTE &&
+ git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)
+ return filter->branch_cb(branch_name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, filter->callback_payload);
return 0;
}
-int git_branch_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags)
+int git_branch_foreach(
+ git_repository *repo,
+ unsigned int list_flags,
+ git_branch_foreach_cb branch_cb,
+ void *payload)
{
+ branch_foreach_filter filter;
+
+ filter.branch_cb = branch_cb;
+ filter.branch_type = list_flags;
+ filter.callback_payload = payload;
+
+ return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter);
+}
+
+int git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force)
+{
+ git_buf new_reference_name = GIT_BUF_INIT,
+ old_config_section = GIT_BUF_INIT,
+ new_config_section = GIT_BUF_INIT;
int error;
- branch_filter_data filter;
- git_vector branchlist;
- assert(branch_names && repo);
+ assert(branch && new_branch_name);
+
+ if (!git_reference_is_branch(branch))
+ return not_a_local_branch(git_reference_name(branch));
+
+ if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 ||
+ (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 ||
+ (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0)
+ goto done;
+
+ if ((error = git_config_rename_section(git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section))) < 0)
+ goto done;
+
+ if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)
+ goto done;
+
+done:
+ git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
- if (git_vector_init(&branchlist, 8, NULL) < 0)
+ return error;
+}
+
+int git_branch_lookup(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type)
+{
+ assert(ref_out && repo && branch_name);
+
+ return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
+}
+
+int git_branch_name(const char **out, git_reference *ref)
+{
+ const char *branch_name;
+
+ assert(out && ref);
+
+ branch_name = ref->name;
+
+ if (git_reference_is_branch(ref)) {
+ branch_name += strlen(GIT_REFS_HEADS_DIR);
+ } else if (git_reference_is_remote(ref)) {
+ branch_name += strlen(GIT_REFS_REMOTES_DIR);
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Reference '%s' is neither a local nor a remote branch.", ref->name);
return -1;
+ }
+ *out = branch_name;
+ return 0;
+}
- filter.branchlist = &branchlist;
- filter.branch_type = list_flags;
+static int retrieve_upstream_configuration(
+ const char **out,
+ git_repository *repo,
+ const char *canonical_branch_name,
+ const char *format)
+{
+ git_config *config;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
- error = git_reference_foreach(repo, GIT_REF_LISTALL, &branch_list_cb, (void *)&filter);
- if (error < 0) {
- git_vector_free(&branchlist);
+ if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
+
+ if (git_buf_printf(&buf, format,
+ canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ return -1;
+
+ error = git_config_get_string(out, config, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_branch_upstream__name(
+ git_buf *tracking_name,
+ git_repository *repo,
+ const char *canonical_branch_name)
+{
+ const char *remote_name, *merge_name;
+ git_buf buf = GIT_BUF_INIT;
+ int error = -1;
+ git_remote *remote = NULL;
+ const git_refspec *refspec;
+
+ assert(tracking_name && canonical_branch_name);
+
+ if (!git_reference__is_branch(canonical_branch_name))
+ return not_a_local_branch(canonical_branch_name);
+
+ if ((error = retrieve_upstream_configuration(
+ &remote_name, repo, canonical_branch_name, "branch.%s.remote")) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_upstream_configuration(
+ &merge_name, repo, canonical_branch_name, "branch.%s.merge")) < 0)
+ goto cleanup;
+
+ if (!*remote_name || !*merge_name) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
}
- branch_names->strings = (char **)branchlist.contents;
- branch_names->count = branchlist.length;
- return 0;
+ if (strcmp(".", remote_name) != 0) {
+ 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;
+ }
+
+ if (git_refspec_transform_r(&buf, refspec, merge_name) < 0)
+ goto cleanup;
+ } else
+ if (git_buf_sets(&buf, merge_name) < 0)
+ goto cleanup;
+
+ error = git_buf_set(tracking_name, git_buf_cstr(&buf), git_buf_len(&buf));
+
+cleanup:
+ git_remote_free(remote);
+ git_buf_free(&buf);
+ return error;
}
-int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force)
+static int remote_name(git_buf *buf, git_repository *repo, const char *canonical_branch_name)
{
- git_reference *reference = NULL;
- git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT;
+ git_strarray remote_list = {0};
+ size_t i;
+ git_remote *remote;
+ const git_refspec *fetchspec;
int error = 0;
+ char *remote_name = NULL;
+
+ assert(buf && repo && canonical_branch_name);
- if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0)
+ /* Verify that this is a remote branch */
+ if (!git_reference__is_remote(canonical_branch_name)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
+ canonical_branch_name);
+ error = GIT_ERROR;
goto cleanup;
+ }
- /* We need to be able to return GIT_ENOTFOUND */
- if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0)
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
goto cleanup;
- if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ 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)) {
+ /* If we have not already set out yet, then set
+ * it to the matching remote name. Otherwise
+ * multiple remotes match this reference, and it
+ * is ambiguous. */
+ if (!remote_name) {
+ remote_name = remote_list.strings[i];
+ } else {
+ git_remote_free(remote);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ git_buf_clear(buf);
+ error = git_buf_puts(buf, remote_name);
+ } else {
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ git_strarray_free(&remote_list);
+ return error;
+}
+
+int git_branch_remote_name(char *buffer, size_t buffer_len, git_repository *repo, const char *refname)
+{
+ int ret;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((ret = remote_name(&buf, repo, refname)) < 0)
+ return ret;
+
+ if (buffer)
+ git_buf_copy_cstr(buffer, buffer_len, &buf);
+
+ ret = git_buf_len(&buf) + 1;
+ git_buf_free(&buf);
+
+ return ret;
+}
+
+int git_branch_upstream_name(
+ char *tracking_branch_name_out,
+ size_t buffer_size,
+ git_repository *repo,
+ const char *canonical_branch_name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ assert(canonical_branch_name);
+
+ if (tracking_branch_name_out && buffer_size)
+ *tracking_branch_name_out = '\0';
+
+ if ((error = git_branch_upstream__name(
+ &buf, repo, canonical_branch_name)) < 0)
+ goto cleanup;
+
+ if (tracking_branch_name_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */
+ giterr_set(
+ GITERR_INVALID,
+ "Buffer too short to hold the tracked reference name.");
+ error = -1;
goto cleanup;
+ }
+
+ if (tracking_branch_name_out)
+ git_buf_copy_cstr(tracking_branch_name_out, buffer_size, &buf);
- error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force);
+ error = (int)buf.size + 1;
cleanup:
- git_reference_free(reference);
- git_buf_free(&old_reference_name);
- git_buf_free(&new_reference_name);
+ git_buf_free(&buf);
+ return (int)error;
+}
+
+int git_branch_upstream(
+ git_reference **tracking_out,
+ git_reference *branch)
+{
+ int error;
+ git_buf tracking_name = GIT_BUF_INIT;
+
+ if ((error = git_branch_upstream__name(&tracking_name,
+ git_reference_owner(branch), git_reference_name(branch))) < 0)
+ return error;
+ error = git_reference_lookup(
+ tracking_out,
+ git_reference_owner(branch),
+ git_buf_cstr(&tracking_name));
+
+ git_buf_free(&tracking_name);
return error;
}
+
+static int unset_upstream(git_config *config, const char *shortname)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
+ return -1;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ return -1;
+}
+
+int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
+{
+ git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ git_reference *upstream;
+ git_repository *repo;
+ git_remote *remote = NULL;
+ git_config *config;
+ const char *name, *shortname;
+ int local;
+ const git_refspec *fetchspec;
+
+ name = git_reference_name(branch);
+ if (!git_reference__is_branch(name))
+ return not_a_local_branch(name);
+
+ if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
+ return -1;
+
+ shortname = name + strlen(GIT_REFS_HEADS_DIR);
+
+ if (upstream_name == NULL)
+ return unset_upstream(config, shortname);
+
+ repo = git_reference_owner(branch);
+
+ /* First we need to figure out whether it's a branch or remote-tracking */
+ if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
+ local = 1;
+ else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
+ local = 0;
+ else
+ return GIT_ENOTFOUND;
+
+ /*
+ * If it's local, the remote is "." and the branch name is
+ * simply the refname. Otherwise we need to figure out what
+ * the remote-tracking branch's name on the remote is and use
+ * that.
+ */
+ if (local)
+ git_buf_puts(&value, ".");
+ else
+ remote_name(&value, repo, git_reference_name(upstream));
+
+ if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ if (local) {
+ if (git_buf_puts(&value, git_reference_name(branch)) < 0)
+ goto on_error;
+ } else {
+ /* Get the remoe-tracking branch's refname in its repo */
+ if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ fetchspec = git_remote_fetchspec(remote);
+ git_buf_clear(&value);
+ if (git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0)
+ goto on_error;
+
+ git_remote_free(remote);
+ remote = NULL;
+ }
+
+ git_buf_clear(&key);
+ if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+
+ return 0;
+
+on_error:
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+ git_remote_free(remote);
+
+ return -1;
+}
+
+int git_branch_is_head(
+ git_reference *branch)
+{
+ git_reference *head;
+ bool is_same = false;
+ int error;
+
+ assert(branch);
+
+ if (!git_reference_is_branch(branch))
+ return false;
+
+ error = git_repository_head(&head, git_reference_owner(branch));
+
+ if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
+ return false;
+
+ if (error < 0)
+ return -1;
+
+ is_same = strcmp(
+ git_reference_name(branch),
+ git_reference_name(head)) == 0;
+
+ git_reference_free(head);
+
+ return is_same;
+}
diff --git a/src/branch.h b/src/branch.h
index d0e5abc8b..d02f2af0d 100644
--- a/src/branch.h
+++ b/src/branch.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,11 +7,11 @@
#ifndef INCLUDE_branch_h__
#define INCLUDE_branch_h__
-#include "git2/branch.h"
+#include "buffer.h"
-struct git_branch {
- char *remote; /* TODO: Make this a git_remote */
- char *merge;
-};
+int git_branch_upstream__name(
+ git_buf *tracking_name,
+ git_repository *repo,
+ const char *canonical_branch_name);
#endif
diff --git a/src/bswap.h b/src/bswap.h
index 995767a14..486df82f4 100644
--- a/src/bswap.h
+++ b/src/bswap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/buf_text.c b/src/buf_text.c
new file mode 100644
index 000000000..443454b5f
--- /dev/null
+++ b/src/buf_text.c
@@ -0,0 +1,291 @@
+/*
+ * 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 "buf_text.h"
+
+int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with)
+{
+ const char *scan;
+ size_t total = 0, esc_len = strlen(esc_with), count;
+
+ if (!string)
+ return 0;
+
+ for (scan = string; *scan; ) {
+ /* count run of non-escaped characters */
+ count = strcspn(scan, esc_chars);
+ total += count;
+ scan += count;
+ /* count run of escaped characters */
+ count = strspn(scan, esc_chars);
+ total += count * (esc_len + 1);
+ scan += count;
+ }
+
+ if (git_buf_grow(buf, buf->size + total + 1) < 0)
+ return -1;
+
+ for (scan = string; *scan; ) {
+ count = strcspn(scan, esc_chars);
+
+ memmove(buf->ptr + buf->size, scan, count);
+ scan += count;
+ buf->size += count;
+
+ for (count = strspn(scan, esc_chars); count > 0; --count) {
+ /* copy escape sequence */
+ memmove(buf->ptr + buf->size, esc_with, esc_len);
+ buf->size += esc_len;
+ /* copy character to be escaped */
+ buf->ptr[buf->size] = *scan;
+ buf->size++;
+ scan++;
+ }
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_buf_text_unescape(git_buf *buf)
+{
+ buf->size = git__unescape(buf->ptr);
+}
+
+int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
+{
+ const char *scan = src->ptr;
+ const char *scan_end = src->ptr + src->size;
+ const char *next = memchr(scan, '\r', src->size);
+ char *out;
+
+ assert(tgt != src);
+
+ if (!next)
+ return GIT_ENOTFOUND;
+
+ /* reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size) < 0)
+ return -1;
+ out = tgt->ptr;
+ tgt->size = 0;
+
+ /* Find the next \r and copy whole chunk up to there to tgt */
+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+ if (next > scan) {
+ size_t copylen = next - scan;
+ memcpy(out, scan, copylen);
+ out += copylen;
+ }
+
+ /* Do not drop \r unless it is followed by \n */
+ if (next[1] != '\n')
+ *out++ = '\r';
+ }
+
+ /* Copy remaining input into dest */
+ memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */
+ out += (scan_end - scan);
+ tgt->size = out - tgt->ptr;
+
+ return 0;
+}
+
+int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
+{
+ const char *start = src->ptr;
+ const char *end = start + src->size;
+ const char *scan = start;
+ const char *next = memchr(scan, '\n', src->size);
+
+ assert(tgt != src);
+
+ if (!next)
+ return GIT_ENOTFOUND;
+
+ /* attempt to reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)
+ return -1;
+ tgt->size = 0;
+
+ for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
+ size_t copylen = next - scan;
+ /* don't convert existing \r\n to \r\r\n */
+ size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2;
+ size_t needsize = tgt->size + copylen + extralen + 1;
+
+ if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)
+ return -1;
+
+ if (next > scan) {
+ memcpy(tgt->ptr + tgt->size, scan, copylen);
+ tgt->size += copylen;
+ }
+ if (extralen == 2)
+ tgt->ptr[tgt->size++] = '\r';
+ tgt->ptr[tgt->size++] = '\n';
+ }
+
+ return git_buf_put(tgt, scan, end - scan);
+}
+
+int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
+{
+ size_t i;
+ const char *str, *pfx;
+
+ git_buf_clear(buf);
+
+ if (!strings || !strings->count)
+ return 0;
+
+ /* initialize common prefix to first string */
+ if (git_buf_sets(buf, strings->strings[0]) < 0)
+ return -1;
+
+ /* go through the rest of the strings, truncating to shared prefix */
+ for (i = 1; i < strings->count; ++i) {
+
+ for (str = strings->strings[i], pfx = buf->ptr;
+ *str && *str == *pfx; str++, pfx++)
+ /* scanning */;
+
+ git_buf_truncate(buf, pfx - buf->ptr);
+
+ if (!buf->size)
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_is_binary(const git_buf *buf)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ int printable = 0, nonprintable = 0;
+
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if (c > 0x1F && c < 0x7F)
+ printable++;
+ else if (c == '\0')
+ return true;
+ else if (!git__isspace(c))
+ nonprintable++;
+ }
+
+ return ((printable >> 7) < nonprintable);
+}
+
+bool git_buf_text_contains_nul(const git_buf *buf)
+{
+ return (memchr(buf->ptr, '\0', buf->size) != NULL);
+}
+
+int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf, size_t offset)
+{
+ const char *ptr;
+ size_t len;
+
+ *bom = GIT_BOM_NONE;
+ /* need at least 2 bytes after offset to look for any BOM */
+ if (buf->size < offset + 2)
+ return 0;
+
+ ptr = buf->ptr + offset;
+ len = buf->size - offset;
+
+ switch (*ptr++) {
+ case 0:
+ if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
+ *bom = GIT_BOM_UTF32_BE;
+ return 4;
+ }
+ break;
+ case '\xEF':
+ if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
+ *bom = GIT_BOM_UTF8;
+ return 3;
+ }
+ break;
+ case '\xFE':
+ if (*ptr == '\xFF') {
+ *bom = GIT_BOM_UTF16_BE;
+ return 2;
+ }
+ break;
+ case '\xFF':
+ if (*ptr != '\xFE')
+ break;
+ if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
+ *bom = GIT_BOM_UTF32_LE;
+ return 4;
+ } else {
+ *bom = GIT_BOM_UTF16_LE;
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ int skip;
+
+ memset(stats, 0, sizeof(*stats));
+
+ /* BOM detection */
+ skip = git_buf_text_detect_bom(&stats->bom, buf, 0);
+ if (skip_bom)
+ scan += skip;
+
+ /* Ignore EOF character */
+ if (buf->size > 0 && end[-1] == '\032')
+ end--;
+
+ /* Counting loop */
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if ((c > 0x1F && c < 0x7F) || c > 0x9f)
+ stats->printable++;
+ else switch (c) {
+ case '\0':
+ stats->nul++;
+ stats->nonprintable++;
+ break;
+ case '\n':
+ stats->lf++;
+ break;
+ case '\r':
+ stats->cr++;
+ if (scan < end && *scan == '\n')
+ stats->crlf++;
+ break;
+ case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
+ stats->printable++;
+ break;
+ default:
+ stats->nonprintable++;
+ break;
+ }
+ }
+
+ return (stats->nul > 0 ||
+ ((stats->printable >> 7) < stats->nonprintable));
+}
diff --git a/src/buf_text.h b/src/buf_text.h
new file mode 100644
index 000000000..58e4e26a7
--- /dev/null
+++ b/src/buf_text.h
@@ -0,0 +1,122 @@
+/*
+ * 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_buf_text_h__
+#define INCLUDE_buf_text_h__
+
+#include "buffer.h"
+
+typedef enum {
+ GIT_BOM_NONE = 0,
+ GIT_BOM_UTF8 = 1,
+ GIT_BOM_UTF16_LE = 2,
+ GIT_BOM_UTF16_BE = 3,
+ GIT_BOM_UTF32_LE = 4,
+ GIT_BOM_UTF32_BE = 5
+} git_bom_t;
+
+typedef struct {
+ git_bom_t bom; /* BOM found at head of text */
+ unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */
+ unsigned int printable, nonprintable; /* These are just approximations! */
+} git_buf_text_stats;
+
+/**
+ * Append string to buffer, prefixing each character from `esc_chars` with
+ * `esc_with` string.
+ *
+ * @param buf Buffer to append data to
+ * @param string String to escape and append
+ * @param esc_chars Characters to be escaped
+ * @param esc_with String to insert in from of each found character
+ * @return 0 on success, <0 on failure (probably allocation problem)
+ */
+extern int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with);
+
+/**
+ * Append string escaping characters that are regex special
+ */
+GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
+{
+ return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");
+}
+
+/**
+ * Unescape all characters in a buffer in place
+ *
+ * I.e. remove backslashes
+ */
+extern void git_buf_text_unescape(git_buf *buf);
+
+/**
+ * Replace all \r\n with \n (or do nothing if no \r\n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error
+ */
+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Replace all \n with \r\n (or do nothing if no \n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error
+ */
+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Fill buffer with the common prefix of a array of strings
+ *
+ * Buffer will be set to empty if there is no common prefix
+ */
+extern int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strs);
+
+/**
+ * Check quickly if buffer looks like it contains binary data
+ *
+ * @param buf Buffer to check
+ * @return true if buffer looks like non-text data
+ */
+extern bool git_buf_text_is_binary(const git_buf *buf);
+
+/**
+ * Check quickly if buffer contains a NUL byte
+ *
+ * @param buf Buffer to check
+ * @return true if buffer contains a NUL byte
+ */
+extern bool git_buf_text_contains_nul(const git_buf *buf);
+
+/**
+ * Check if a buffer begins with a UTF BOM
+ *
+ * @param bom Set to the type of BOM detected or GIT_BOM_NONE
+ * @param buf Buffer in which to check the first bytes for a BOM
+ * @param offset Offset into buffer to look for BOM
+ * @return Number of bytes of BOM data (or 0 if no BOM found)
+ */
+extern int git_buf_text_detect_bom(
+ git_bom_t *bom, const git_buf *buf, size_t offset);
+
+/**
+ * Gather stats for a piece of text
+ *
+ * Fill the `stats` structure with counts of unreadable characters, carriage
+ * returns, etc, so it can be used in heuristics. This automatically skips
+ * a trailing EOF (\032 character). Also it will look for a BOM at the
+ * start of the text and can be told to skip that as well.
+ *
+ * @param stats Structure to be filled in
+ * @param buf Text to process
+ * @param skip_bom Exclude leading BOM from stats if true
+ * @return Does the buffer heuristically look like binary data
+ */
+extern bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom);
+
+#endif
diff --git a/src/buffer.c b/src/buffer.c
index 783a36eb8..6e3ffe560 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -31,15 +31,7 @@ void git_buf_init(git_buf *buf, size_t initial_size)
git_buf_grow(buf, initial_size);
}
-int git_buf_grow(git_buf *buf, size_t target_size)
-{
- int error = git_buf_try_grow(buf, target_size);
- if (error != 0)
- buf->ptr = git_buf__oom;
- return error;
-}
-
-int git_buf_try_grow(git_buf *buf, size_t target_size)
+int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom)
{
char *new_ptr;
size_t new_size;
@@ -67,8 +59,12 @@ int git_buf_try_grow(git_buf *buf, size_t target_size)
new_size = (new_size + 7) & ~7;
new_ptr = git__realloc(new_ptr, new_size);
- if (!new_ptr)
+
+ if (!new_ptr) {
+ if (mark_oom)
+ buf->ptr = git_buf__oom;
return -1;
+ }
buf->asize = new_size;
buf->ptr = new_ptr;
@@ -141,11 +137,52 @@ int git_buf_puts(git_buf *buf, const char *string)
return git_buf_put(buf, string, strlen(string));
}
+static const char b64str[64] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
+{
+ size_t extra = len % 3;
+ uint8_t *write, a, b, c;
+ const uint8_t *read = (const uint8_t *)data;
+
+ ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
+ write = (uint8_t *)&buf->ptr[buf->size];
+
+ /* convert each run of 3 bytes into 4 output bytes */
+ for (len -= extra; len > 0; len -= 3) {
+ a = *read++;
+ b = *read++;
+ c = *read++;
+
+ *write++ = b64str[a >> 2];
+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];
+ *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
+ *write++ = b64str[c & 0x3f];
+ }
+
+ if (extra > 0) {
+ a = *read++;
+ b = (extra > 1) ? *read++ : 0;
+
+ *write++ = b64str[a >> 2];
+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];
+ *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
+ *write++ = '=';
+ }
+
+ buf->size = ((char *)write) - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
int len;
+ const size_t expected_size = buf->size + (strlen(format) * 2);
- ENSURE_SIZE(buf, buf->size + (strlen(format) * 2));
+ ENSURE_SIZE(buf, expected_size);
while (1) {
va_list args;
@@ -411,51 +448,30 @@ int git_buf_cmp(const git_buf *a, const git_buf *b)
(a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
}
-int git_buf_common_prefix(git_buf *buf, const git_strarray *strings)
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert)
{
- size_t i;
- const char *str, *pfx;
-
- git_buf_clear(buf);
-
- if (!strings || !strings->count)
- return 0;
-
- /* initialize common prefix to first string */
- if (git_buf_sets(buf, strings->strings[0]) < 0)
+ assert(buf &&
+ where <= git_buf_len(buf) &&
+ where + nb_to_remove <= git_buf_len(buf));
+
+ /* Ported from git.git
+ * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
+ */
+ if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
return -1;
- /* go through the rest of the strings, truncating to shared prefix */
- for (i = 1; i < strings->count; ++i) {
-
- for (str = strings->strings[i], pfx = buf->ptr;
- *str && *str == *pfx; str++, pfx++)
- /* scanning */;
+ memmove(buf->ptr + where + nb_to_insert,
+ buf->ptr + where + nb_to_remove,
+ buf->size - where - nb_to_remove);
- git_buf_truncate(buf, pfx - buf->ptr);
-
- if (!buf->size)
- break;
- }
+ memcpy(buf->ptr + where, data, nb_to_insert);
+ buf->size = buf->size + nb_to_insert - nb_to_remove;
+ buf->ptr[buf->size] = '\0';
return 0;
}
-
-bool git_buf_is_binary(const git_buf *buf)
-{
- size_t i;
- int printable = 0, nonprintable = 0;
-
- for (i = 0; i < buf->size; i++) {
- unsigned char c = buf->ptr[i];
- if (c > 0x1F && c < 0x7F)
- printable++;
- else if (c == '\0')
- return true;
- else if (!git__isspace(c))
- nonprintable++;
- }
-
- return ((printable >> 7) < nonprintable);
-}
-
diff --git a/src/buffer.h b/src/buffer.h
index 50c75f64e..5402f3827 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,6 +8,7 @@
#define INCLUDE_buffer_h__
#include "common.h"
+#include "git2/strarray.h"
#include <stdarg.h>
typedef struct {
@@ -26,30 +27,35 @@ extern char git_buf__oom[];
* For the cases where GIT_BUF_INIT cannot be used to do static
* initialization.
*/
-void git_buf_init(git_buf *buf, size_t initial_size);
+extern void git_buf_init(git_buf *buf, size_t initial_size);
/**
- * Grow the buffer to hold at least `target_size` bytes.
+ * Attempt to grow the buffer to hold at least `target_size` bytes.
*
- * If the allocation fails, this will return an error and the buffer
- * will be marked as invalid for future operations. The existing
- * contents of the buffer will be preserved however.
- * @return 0 on success or -1 on failure
+ * If the allocation fails, this will return an error. If mark_oom is true,
+ * this will mark the buffer as invalid for future operations; if false,
+ * existing buffer content will be preserved, but calling code must handle
+ * that buffer was not expanded.
*/
-int git_buf_grow(git_buf *buf, size_t target_size);
+extern int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom);
/**
- * Attempt to grow the buffer to hold at least `target_size` bytes.
+ * Grow the buffer to hold at least `target_size` bytes.
+ *
+ * If the allocation fails, this will return an error and the buffer will be
+ * marked as invalid for future operations, invaliding contents.
*
- * This is just like `git_buf_grow` except that even if the allocation
- * fails, the git_buf will still be left in a valid state.
+ * @return 0 on success or -1 on failure
*/
-int git_buf_try_grow(git_buf *buf, size_t target_size);
+GIT_INLINE(int) git_buf_grow(git_buf *buf, size_t target_size)
+{
+ return git_buf_try_grow(buf, target_size, true);
+}
-void git_buf_free(git_buf *buf);
-void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
-char *git_buf_detach(git_buf *buf);
-void git_buf_attach(git_buf *buf, char *ptr, size_t asize);
+extern void git_buf_free(git_buf *buf);
+extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
+extern char *git_buf_detach(git_buf *buf);
+extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize);
/**
* Test if there have been any reallocation failures with this git_buf.
@@ -113,7 +119,7 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf);
#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1)
-GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)
+GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch)
{
ssize_t idx = (ssize_t)buf->size - 1;
while (idx >= 0 && buf->ptr[idx] == ch) idx--;
@@ -121,15 +127,50 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)
return idx;
}
+GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)
+{
+ ssize_t idx = (ssize_t)buf->size - 1;
+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)
+{
+ void *found = memchr(buf->ptr, ch, buf->size);
+ return found ? (ssize_t)((const char *)found - buf->ptr) : -1;
+}
+
/* Remove whitespace from the end of the buffer */
void git_buf_rtrim(git_buf *buf);
int git_buf_cmp(const git_buf *a, const git_buf *b);
-/* Fill buf with the common prefix of a array of strings */
-int git_buf_common_prefix(git_buf *buf, const git_strarray *strings);
+/* Write data as base64 encoded in buffer */
+int git_buf_put_base64(git_buf *buf, const char *data, size_t len);
-/* Check if buffer looks like it contains binary data */
-bool git_buf_is_binary(const git_buf *buf);
+/*
+ * Insert, remove or replace a portion of the buffer.
+ *
+ * @param buf The buffer to work with
+ *
+ * @param where The location in the buffer where the transformation
+ * should be applied.
+ *
+ * @param nb_to_remove The number of chars to be removed. 0 to not
+ * remove any character in the buffer.
+ *
+ * @param data A pointer to the data which should be inserted.
+ *
+ * @param nb_to_insert The number of chars to be inserted. 0 to not
+ * insert any character from the buffer.
+ *
+ * @return 0 or an error code.
+ */
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert);
#endif
diff --git a/src/cache.c b/src/cache.c
index 31da3c36e..e7f333577 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,6 +11,7 @@
#include "thread-utils.h"
#include "util.h"
#include "cache.h"
+#include "git2/oid.h"
int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
{
@@ -40,6 +41,7 @@ void git_cache_free(git_cache *cache)
git_cached_obj_decref(cache->nodes[i], cache->free_obj);
}
+ git_mutex_free(&cache->lock);
git__free(cache->nodes);
}
@@ -50,7 +52,11 @@ void *git_cache_get(git_cache *cache, const git_oid *oid)
memcpy(&hash, oid->id, sizeof(hash));
- git_mutex_lock(&cache->lock);
+ if (git_mutex_lock(&cache->lock)) {
+ giterr_set(GITERR_THREAD, "unable to lock cache mutex");
+ return NULL;
+ }
+
{
node = cache->nodes[hash & cache->size_mask];
@@ -69,16 +75,20 @@ void *git_cache_try_store(git_cache *cache, void *_entry)
git_cached_obj *entry = _entry;
uint32_t hash;
- memcpy(&hash, &entry->oid, sizeof(hash));
+ memcpy(&hash, &entry->oid, sizeof(uint32_t));
- /* increase the refcount on this object, because
- * the cache now owns it */
- git_cached_obj_incref(entry);
+ if (git_mutex_lock(&cache->lock)) {
+ giterr_set(GITERR_THREAD, "unable to lock cache mutex");
+ return NULL;
+ }
- git_mutex_lock(&cache->lock);
{
git_cached_obj *node = cache->nodes[hash & cache->size_mask];
+ /* increase the refcount on this object, because
+ * the cache now owns it */
+ git_cached_obj_incref(entry);
+
if (node == NULL) {
cache->nodes[hash & cache->size_mask] = entry;
} else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {
@@ -88,12 +98,13 @@ void *git_cache_try_store(git_cache *cache, void *_entry)
git_cached_obj_decref(node, cache->free_obj);
cache->nodes[hash & cache->size_mask] = entry;
}
+
+ /* increase the refcount again, because we are
+ * returning it to the user */
+ git_cached_obj_incref(entry);
+
}
git_mutex_unlock(&cache->lock);
- /* increase the refcount again, because we are
- * returning it to the user */
- git_cached_obj_incref(entry);
-
return entry;
}
diff --git a/src/cache.h b/src/cache.h
index 6dc706897..7034ec268 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/cc-compat.h b/src/cc-compat.h
index 9f23dcae2..a5e4ce17e 100644
--- a/src/cc-compat.h
+++ b/src/cc-compat.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -38,8 +38,10 @@
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)
# define PRIuZ "Iu"
+# define PRIxZ "Ix"
#else
# define PRIuZ "zu"
+# define PRIxZ "zx"
#endif
/* Micosoft Visual C/C++ */
diff --git a/src/checkout.c b/src/checkout.c
new file mode 100644
index 000000000..24fa21024
--- /dev/null
+++ b/src/checkout.c
@@ -0,0 +1,1383 @@
+/*
+ * 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 <assert.h>
+
+#include "checkout.h"
+
+#include "git2/repository.h"
+#include "git2/refs.h"
+#include "git2/tree.h"
+#include "git2/blob.h"
+#include "git2/config.h"
+#include "git2/diff.h"
+#include "git2/submodule.h"
+
+#include "refs.h"
+#include "repository.h"
+#include "filter.h"
+#include "blob.h"
+#include "diff.h"
+#include "pathspec.h"
+#include "buf_text.h"
+
+/* See docs/checkout-internals.md for more information */
+
+enum {
+ CHECKOUT_ACTION__NONE = 0,
+ CHECKOUT_ACTION__REMOVE = 1,
+ CHECKOUT_ACTION__UPDATE_BLOB = 2,
+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
+ CHECKOUT_ACTION__CONFLICT = 8,
+ CHECKOUT_ACTION__MAX = 8,
+ CHECKOUT_ACTION__DEFER_REMOVE = 16,
+ CHECKOUT_ACTION__REMOVE_AND_UPDATE =
+ (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
+};
+
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_checkout_opts opts;
+ bool opts_free_baseline;
+ char *pfx;
+ git_index *index;
+ git_pool pool;
+ git_vector removes;
+ git_buf path;
+ size_t workdir_len;
+ unsigned int strategy;
+ int can_symlink;
+ bool reload_submodules;
+ size_t total_steps;
+ size_t completed_steps;
+} checkout_data;
+
+static int checkout_notify(
+ checkout_data *data,
+ git_checkout_notify_t why,
+ const git_diff_delta *delta,
+ const git_index_entry *wditem)
+{
+ git_diff_file wdfile;
+ const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;
+ const char *path = NULL;
+
+ if (!data->opts.notify_cb)
+ return 0;
+
+ if ((why & data->opts.notify_flags) == 0)
+ return 0;
+
+ if (wditem) {
+ memset(&wdfile, 0, sizeof(wdfile));
+
+ git_oid_cpy(&wdfile.oid, &wditem->oid);
+ wdfile.path = wditem->path;
+ wdfile.size = wditem->file_size;
+ wdfile.flags = GIT_DIFF_FLAG_VALID_OID;
+ wdfile.mode = wditem->mode;
+
+ workdir = &wdfile;
+
+ path = wditem->path;
+ }
+
+ if (delta) {
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ default:
+ baseline = &delta->old_file;
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_UNTRACKED:
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_DELETED:
+ baseline = &delta->old_file;
+ break;
+ }
+
+ path = delta->old_file.path;
+ }
+
+ return data->opts.notify_cb(
+ why, path, baseline, target, workdir, data->opts.notify_payload);
+}
+
+static bool checkout_is_workdir_modified(
+ checkout_data *data,
+ const git_diff_file *baseitem,
+ const git_index_entry *wditem)
+{
+ git_oid oid;
+
+ /* handle "modified" submodule */
+ if (wditem->mode == GIT_FILEMODE_COMMIT) {
+ git_submodule *sm;
+ unsigned int sm_status = 0;
+ const git_oid *sm_oid = NULL;
+
+ if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0 ||
+ git_submodule_status(&sm_status, sm) < 0)
+ return true;
+
+ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ return true;
+
+ sm_oid = git_submodule_wd_id(sm);
+ if (!sm_oid)
+ return false;
+
+ return (git_oid_cmp(&baseitem->oid, sm_oid) != 0);
+ }
+
+ /* depending on where base is coming from, we may or may not know
+ * the actual size of the data, so we can't rely on this shortcut.
+ */
+ if (baseitem->size && wditem->file_size != baseitem->size)
+ return true;
+
+ if (git_diff__oid_for_file(
+ data->repo, wditem->path, wditem->mode,
+ wditem->file_size, &oid) < 0)
+ return false;
+
+ return (git_oid_cmp(&baseitem->oid, &oid) != 0);
+}
+
+#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
+ ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)
+
+static int checkout_action_common(
+ checkout_data *data,
+ int action,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if (action <= 0)
+ return action;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ action = (action & ~CHECKOUT_ACTION__REMOVE);
+
+ if ((action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->new_file.mode))
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
+
+ notify = GIT_CHECKOUT_NOTIFY_UPDATED;
+ }
+
+ if ((action & CHECKOUT_ACTION__CONFLICT) != 0)
+ notify = GIT_CHECKOUT_NOTIFY_CONFLICT;
+
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE &&
+ checkout_notify(data, notify, delta, wd) != 0)
+ return GIT_EUSER;
+
+ return action;
+}
+
+static int checkout_action_no_wd(
+ checkout_data *data,
+ const git_diff_delta *delta)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 12 */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_DELETED: /* case 8 or 25 */
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(data, action, delta, NULL);
+}
+
+static int checkout_action_wd_only(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry *wd,
+ git_vector *pathspec)
+{
+ bool remove = false;
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if (!git_pathspec_match_path(
+ pathspec, wd->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL))
+ return 0;
+
+ /* check if item is tracked in the index but not in the checkout diff */
+ if (data->index != NULL) {
+ if (wd->mode != GIT_FILEMODE_TREE) {
+ int error;
+
+ if ((error = git_index_find(NULL, data->index, wd->path)) == 0) {
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ } else if (error != GIT_ENOTFOUND)
+ return error;
+ } else {
+ /* for tree entries, we have to see if there are any index
+ * entries that are contained inside that tree
+ */
+ size_t pos = git_index__prefix_position(data->index, wd->path);
+ const git_index_entry *e = git_index_get_byindex(data->index, pos);
+
+ if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) {
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ }
+ }
+ }
+
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE)
+ /* found in index */;
+ else if (git_iterator_current_is_ignored(workdir)) {
+ notify = GIT_CHECKOUT_NOTIFY_IGNORED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
+ }
+ else {
+ notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
+ }
+
+ if (checkout_notify(data, notify, NULL, wd))
+ return GIT_EUSER;
+
+ if (remove) {
+ char *path = git_pool_strdup(&data->pool, wd->path);
+ GITERR_CHECK_ALLOC(path);
+
+ if (git_vector_insert(&data->removes, path) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static bool submodule_is_config_only(
+ checkout_data *data,
+ const char *path)
+{
+ git_submodule *sm = NULL;
+ unsigned int sm_loc = 0;
+
+ if (git_submodule_lookup(&sm, data->repo, path) < 0 ||
+ git_submodule_location(&sm_loc, sm) < 0 ||
+ sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG)
+ return true;
+
+ return false;
+}
+
+static int checkout_action_with_wd(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd)) {
+ if (checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
+ }
+ break;
+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ if (wd->mode == GIT_FILEMODE_TREE)
+ /* either deleting items in old tree will delete the wd dir,
+ * or we'll get a conflict when we attempt blob update...
+ */
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else if (wd->mode == GIT_FILEMODE_COMMIT) {
+ /* workdir is possibly a "phantom" submodule - treat as a
+ * tree if the only submodule info came from the config
+ */
+ if (submodule_is_config_only(data, wd->path))
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ } else
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ }
+ else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
+
+ /* don't update if the typechange is to a tree */
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(data, action, delta, wd);
+}
+
+static int checkout_action_with_wd_blocker(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ /* should show delta as dirty / deleted */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED:
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ /* not 100% certain about this... */
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(data, action, delta, wd);
+}
+
+static int checkout_action_with_wd_dir(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||
+ checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
+ return GIT_EUSER;
+ break;
+ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
+ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ /* expected submodule (and maybe found one) */;
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
+ if (delta->old_file.mode != GIT_FILEMODE_TREE &&
+ checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
+ return GIT_EUSER;
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ /* For typechange from dir, remove dir and add blob, but it is
+ * not safe to remove dir if it contains modified files.
+ * However, safely removing child files will remove the parent
+ * directory if is it left empty, so we can defer removing the
+ * dir and it will succeed if no children are left.
+ */
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ if (action != CHECKOUT_ACTION__NONE)
+ action |= CHECKOUT_ACTION__DEFER_REMOVE;
+ }
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ /* For typechange to dir, dir is already created so no action */
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(data, action, delta, wd);
+}
+
+static int checkout_action(
+ checkout_data *data,
+ git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry **wditem_ptr,
+ git_vector *pathspec)
+{
+ const git_index_entry *wd = *wditem_ptr;
+ int cmp = -1, act;
+ int (*strcomp)(const char *, const char *) = data->diff->strcomp;
+ int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
+
+ /* move workdir iterator to follow along with deltas */
+
+ while (1) {
+ if (!wd)
+ return checkout_action_no_wd(data, delta);
+
+ cmp = strcomp(wd->path, delta->old_file.path);
+
+ /* 1. wd before delta ("a/a" before "a/b")
+ * 2. wd prefixes delta & should expand ("a/" before "a/b")
+ * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")
+ * 4. wd equals delta ("a/b" and "a/b")
+ * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")
+ * 6. wd after delta ("a/c" after "a/b")
+ */
+
+ if (cmp < 0) {
+ cmp = pfxcomp(delta->old_file.path, wd->path);
+
+ 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;
+
+ *wditem_ptr = wd;
+ continue;
+ }
+
+ /* case 3 maybe - wd contains non-dir where dir expected */
+ if (delta->old_file.path[strlen(wd->path)] == '/') {
+ act = checkout_action_with_wd_blocker(data, delta, wd);
+ *wditem_ptr =
+ git_iterator_advance(&wd, workdir) ? NULL : wd;
+ return act;
+ }
+ }
+
+ /* case 1 - handle wd item (if it matches pathspec) */
+ if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||
+ git_iterator_advance(&wd, workdir) < 0)
+ goto fail;
+
+ *wditem_ptr = wd;
+ continue;
+ }
+
+ if (cmp == 0) {
+ /* case 4 */
+ act = checkout_action_with_wd(data, delta, wd);
+ *wditem_ptr = git_iterator_advance(&wd, workdir) ? NULL : wd;
+ return act;
+ }
+
+ cmp = pfxcomp(wd->path, delta->old_file.path);
+
+ if (cmp == 0) { /* case 5 */
+ if (wd->path[strlen(delta->old_file.path)] != '/')
+ return checkout_action_no_wd(data, delta);
+
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ act = checkout_action_with_wd(data, delta, wd);
+ if (git_iterator_advance_into(&wd, workdir) < 0)
+ wd = NULL;
+ *wditem_ptr = wd;
+ return act;
+ }
+
+ if (delta->new_file.mode == GIT_FILEMODE_TREE ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ {
+ act = checkout_action_with_wd(data, delta, wd);
+ if (git_iterator_advance(&wd, workdir) < 0)
+ wd = NULL;
+ *wditem_ptr = wd;
+ return act;
+ }
+ }
+
+ return checkout_action_with_wd_dir(data, delta, wd);
+ }
+
+ /* case 6 - wd is after delta */
+ return checkout_action_no_wd(data, delta);
+ }
+
+fail:
+ *wditem_ptr = NULL;
+ return -1;
+}
+
+static int checkout_remaining_wd_items(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry *wd,
+ git_vector *spec)
+{
+ int error = 0;
+
+ while (wd && !error) {
+ if (!(error = checkout_action_wd_only(data, workdir, wd, spec)))
+ error = git_iterator_advance(&wd, workdir);
+ }
+
+ return error;
+}
+
+static int checkout_get_actions(
+ uint32_t **actions_ptr,
+ size_t **counts_ptr,
+ checkout_data *data,
+ git_iterator *workdir)
+{
+ int error = 0;
+ const git_index_entry *wditem;
+ git_vector pathspec = GIT_VECTOR_INIT, *deltas;
+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
+ git_diff_delta *delta;
+ size_t i, *counts = NULL;
+ uint32_t *actions = NULL;
+
+ if (data->opts.paths.count > 0 &&
+ git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)
+ return -1;
+
+ if ((error = git_iterator_current(&wditem, workdir)) < 0)
+ goto fail;
+
+ deltas = &data->diff->deltas;
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(
+ deltas->length ? deltas->length : 1, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
+
+ git_vector_foreach(deltas, i, delta) {
+ int act = checkout_action(data, delta, workdir, &wditem, &pathspec);
+
+ if (act < 0) {
+ error = act;
+ goto fail;
+ }
+
+ actions[i] = act;
+
+ if (act & CHECKOUT_ACTION__REMOVE)
+ counts[CHECKOUT_ACTION__REMOVE]++;
+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)
+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__CONFLICT)
+ counts[CHECKOUT_ACTION__CONFLICT]++;
+ }
+
+ error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec);
+ if (error < 0)
+ goto fail;
+
+ counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;
+
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
+ (int)counts[CHECKOUT_ACTION__CONFLICT]);
+ error = GIT_EMERGECONFLICT;
+ goto fail;
+ }
+
+ git_pathspec_free(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_pathspec_free(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return error;
+}
+
+static int buffer_to_file(
+ struct stat *st,
+ git_buf *buffer,
+ const char *path,
+ mode_t dir_mode,
+ int file_open_flags,
+ mode_t file_mode)
+{
+ int fd, error;
+
+ if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
+ return error;
+
+ if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ } else {
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ if ((error = p_stat(path, st)) < 0)
+ giterr_set(GITERR_OS, "Error while statting '%s'", path);
+ }
+
+ if (!error &&
+ (file_mode & 0100) != 0 &&
+ (error = p_chmod(path, file_mode)) < 0)
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+
+ return error;
+}
+
+static int blob_content_to_file(
+ struct stat *st,
+ git_blob *blob,
+ const char *path,
+ mode_t entry_filemode,
+ git_checkout_opts *opts)
+{
+ int error = -1, nb_filters = 0;
+ mode_t file_mode = opts->file_mode;
+ bool dont_free_filtered;
+ git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
+ 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;
+ /* ... and make sure it doesn't get unexpectedly freed */
+ dont_free_filtered = true;
+
+ if (!opts->disable_filters &&
+ !git_buf_text_is_binary(&filtered) &&
+ (nb_filters = git_filters_load(
+ &filters,
+ git_object_owner((git_object *)blob),
+ path,
+ GIT_FILTER_TO_WORKTREE)) > 0)
+ {
+ /* reset 'filtered' so it can be a filter target */
+ git_buf_init(&filtered, 0);
+ dont_free_filtered = false;
+ }
+
+ if (nb_filters < 0)
+ return nb_filters;
+
+ if (nb_filters > 0) {
+ if ((error = git_blob__getbuf(&unfiltered, blob)) < 0)
+ goto cleanup;
+
+ if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0)
+ goto cleanup;
+ }
+
+ /* Allow overriding of file mode */
+ if (!file_mode)
+ file_mode = entry_filemode;
+
+ error = buffer_to_file(
+ st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
+
+ if (!error)
+ st->st_mode = entry_filemode;
+
+cleanup:
+ git_filters_free(&filters);
+ git_buf_free(&unfiltered);
+ if (!dont_free_filtered)
+ git_buf_free(&filtered);
+
+ return error;
+}
+
+static int blob_content_to_link(
+ struct stat *st, git_blob *blob, const char *path, int can_symlink)
+{
+ git_buf linktarget = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
+ return error;
+
+ if (can_symlink) {
+ if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
+ giterr_set(GITERR_CHECKOUT, "Could not create symlink %s\n", path);
+ } else {
+ error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
+ }
+
+ if (!error) {
+ if ((error = p_lstat(path, st)) < 0)
+ giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
+
+ st->st_mode = GIT_FILEMODE_LINK;
+ }
+
+ git_buf_free(&linktarget);
+
+ return error;
+}
+
+static int checkout_update_index(
+ checkout_data *data,
+ const git_diff_file *file,
+ struct stat *st)
+{
+ git_index_entry entry;
+
+ if (!data->index)
+ return 0;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = (char *)file->path; /* cast to prevent warning */
+ git_index_entry__init_from_stat(&entry, st);
+ git_oid_cpy(&entry.oid, &file->oid);
+
+ return git_index_add(data->index, &entry);
+}
+
+static int checkout_submodule(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ int error = 0;
+ git_submodule *sm;
+
+ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
+ if ((error = git_futils_mkdir(
+ file->path, git_repository_workdir(data->repo),
+ data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
+ return error;
+
+ if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0)
+ return error;
+
+ /* TODO: Support checkout_strategy options. Two circumstances:
+ * 1 - submodule already checked out, but we need to move the HEAD
+ * to the new OID, or
+ * 2 - submodule not checked out and we should recursively check it out
+ *
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
+ */
+
+ /* update the index unless prevented */
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) {
+ struct stat st;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
+ return -1;
+
+ if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
+ return error;
+ }
+
+ st.st_mode = GIT_FILEMODE_COMMIT;
+
+ error = checkout_update_index(data, file, &st);
+ }
+
+ return error;
+}
+
+static void report_progress(
+ checkout_data *data,
+ const char *path)
+{
+ if (data->opts.progress_cb)
+ data->opts.progress_cb(
+ path, data->completed_steps, data->total_steps,
+ data->opts.progress_payload);
+}
+
+static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)
+{
+ struct stat st;
+
+ if (p_lstat(path, &st) < 0) {
+ /* if doesn't exist, then no error and no update */
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ /* otherwise, stat error and no update */
+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ return -1;
+ }
+
+ /* only safe for update if this is the same type of file */
+ if ((st.st_mode & ~0777) == (expected_mode & ~0777))
+ return 1;
+
+ return 0;
+}
+
+static int checkout_blob(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ int error = 0;
+ git_blob *blob;
+ struct stat st;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
+ return -1;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
+ int rval = checkout_safe_for_update_only(
+ git_buf_cstr(&data->path), file->mode);
+ if (rval <= 0)
+ return rval;
+ }
+
+ if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
+ return error;
+
+ if (S_ISLNK(file->mode))
+ error = blob_content_to_link(
+ &st, blob, git_buf_cstr(&data->path), data->can_symlink);
+ else
+ error = blob_content_to_file(
+ &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts);
+
+ git_blob_free(blob);
+
+ /* if we try to create the blob and an existing directory blocks it from
+ * being written, then there must have been a typechange conflict in a
+ * parent directory - suppress the error and try to continue.
+ */
+ if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&
+ (error == GIT_ENOTFOUND || error == GIT_EEXISTS))
+ {
+ giterr_clear();
+ error = 0;
+ }
+
+ /* update the index unless prevented */
+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = checkout_update_index(data, file, &st);
+
+ /* update the submodule data if this was a new .gitmodules file */
+ if (!error && strcmp(file->path, ".gitmodules") == 0)
+ data->reload_submodules = true;
+
+ return error;
+}
+
+static int checkout_remove_the_old(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ const char *str;
+ size_t i;
+ const char *workdir = git_buf_cstr(&data->path);
+ uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {
+ error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+
+ if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 &&
+ (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ (void)git_index_remove(data->index, delta->old_file.path, 0);
+ }
+ }
+ }
+
+ git_vector_foreach(&data->removes, i, str) {
+ error = git_futils_rmdir_r(str, workdir, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, str);
+
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ if (str[strlen(str) - 1] == '/')
+ (void)git_index_remove_directory(data->index, str, 0);
+ else
+ (void)git_index_remove(data->index, str, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_deferred_remove(git_repository *repo, const char *path)
+{
+#if 0
+ int error = git_futils_rmdir_r(
+ path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS);
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+#else
+ GIT_UNUSED(repo);
+ GIT_UNUSED(path);
+ assert(false);
+ return 0;
+#endif
+}
+
+static int checkout_create_the_new(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this had a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
+ error = checkout_blob(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_create_submodules(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ /* initial reload of submodules if .gitmodules was changed */
+ if (data->reload_submodules &&
+ (error = git_submodule_reload_all(data->repo)) < 0)
+ return error;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this has a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
+ int error = checkout_submodule(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ /* final reload once submodules have been updated */
+ return git_submodule_reload_all(data->repo);
+}
+
+static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
+{
+ int error = 0;
+ git_reference *ref = NULL;
+ git_object *head;
+
+ if (!(error = git_repository_head(&ref, repo)) &&
+ !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
+ *out = (git_tree *)head;
+
+ git_reference_free(ref);
+
+ return error;
+}
+
+static void checkout_data_clear(checkout_data *data)
+{
+ if (data->opts_free_baseline) {
+ git_tree_free(data->opts.baseline);
+ data->opts.baseline = NULL;
+ }
+
+ git_vector_free(&data->removes);
+ git_pool_clear(&data->pool);
+
+ git__free(data->pfx);
+ data->pfx = NULL;
+
+ git_buf_free(&data->path);
+
+ git_index_free(data->index);
+ data->index = NULL;
+}
+
+static int checkout_data_init(
+ checkout_data *data,
+ git_iterator *target,
+ git_checkout_opts *proposed)
+{
+ int error = 0;
+ git_config *cfg;
+ git_repository *repo = git_iterator_owner(target);
+
+ memset(data, 0, sizeof(*data));
+
+ if (!repo) {
+ giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
+ return -1;
+ }
+
+ 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(
+ proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
+
+ if (!proposed)
+ GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTS_VERSION);
+ else
+ memmove(&data->opts, proposed, sizeof(git_checkout_opts));
+
+ /* 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)
+ goto cleanup;
+
+ /* if we are checking out the index, don't reload,
+ * otherwise get index and force reload
+ */
+ if ((data->index = git_iterator_get_index(target)) != NULL) {
+ GIT_REFCOUNT_INC(data->index);
+ } else {
+ /* otherwise, grab and reload the index */
+ if ((error = git_repository_index(&data->index, data->repo)) < 0 ||
+ (error = git_index_read(data->index)) < 0)
+ goto cleanup;
+
+ /* clear the REUC when doing a tree or commit checkout */
+ git_index_reuc_clear(data->index);
+ }
+ }
+
+ /* if you are forcing, definitely allow safe updates */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ data->strategy = data->opts.checkout_strategy;
+
+ /* opts->disable_filters is false by default */
+
+ if (!data->opts.dir_mode)
+ data->opts.dir_mode = GIT_DIR_MODE;
+
+ if (!data->opts.file_open_flags)
+ data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+
+ 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 (!data->opts.baseline) {
+ data->opts_free_baseline = true;
+ error = checkout_lookup_head_tree(&data->opts.baseline, repo);
+
+ if (error == GIT_EORPHANEDHEAD) {
+ error = 0;
+ giterr_clear();
+ }
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
+ (error = git_pool_init(&data->pool, 1, 0)) < 0 ||
+ (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0)
+ goto cleanup;
+
+ data->workdir_len = git_buf_len(&data->path);
+
+cleanup:
+ if (error < 0)
+ checkout_data_clear(data);
+
+ return error;
+}
+
+int git_checkout_iterator(
+ git_iterator *target,
+ git_checkout_opts *opts)
+{
+ int error = 0;
+ git_iterator *baseline = NULL, *workdir = NULL;
+ checkout_data data = {0};
+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+ uint32_t *actions = NULL;
+ size_t *counts = NULL;
+ git_iterator_flag_t iterflags = 0;
+
+ /* initialize structures and options */
+ error = checkout_data_init(&data, target, opts);
+ if (error < 0)
+ return error;
+
+ diff_opts.flags =
+ GIT_DIFF_INCLUDE_UNMODIFIED |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
+ GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_TYPECHANGE |
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
+ GIT_DIFF_SKIP_BINARY_CHECK;
+ if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)
+ diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if (data.opts.paths.count > 0)
+ diff_opts.pathspec = data.opts.paths;
+
+ /* set up iterators */
+
+ iterflags = git_iterator_ignore_case(target) ?
+ GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_workdir(
+ &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
+ data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_tree(
+ &baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0)
+ goto cleanup;
+
+ /* Should not have case insensitivity mismatch */
+ assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));
+
+ /* Generate baseline-to-target diff which will include an entry for
+ * every possible update that might need to be made.
+ */
+ if ((error = git_diff__from_iterators(
+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)
+ goto cleanup;
+
+ /* Loop through diff (and working directory iterator) building a list of
+ * actions to be taken, plus look for conflicts and send notifications.
+ */
+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
+ goto cleanup;
+
+ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
+
+ report_progress(&data, NULL); /* establish 0 baseline */
+
+ /* To deal with some order dependencies, perform remaining checkout
+ * in three passes: removes, then update blobs, then update submodules.
+ */
+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
+ (error = checkout_remove_the_old(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
+ (error = checkout_create_the_new(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
+ (error = checkout_create_submodules(actions, &data)) < 0)
+ goto cleanup;
+
+ assert(data.completed_steps == data.total_steps);
+
+cleanup:
+ if (error == GIT_EUSER)
+ giterr_clear();
+
+ if (!error && data.index != NULL &&
+ (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = git_index_write(data.index);
+
+ git_diff_list_free(data.diff);
+ git_iterator_free(workdir);
+ git_iterator_free(baseline);
+ git__free(actions);
+ git__free(counts);
+ checkout_data_clear(&data);
+
+ return error;
+}
+
+int git_checkout_index(
+ git_repository *repo,
+ git_index *index,
+ git_checkout_opts *opts)
+{
+ int error;
+ git_iterator *index_i;
+
+ if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0)
+ return error;
+
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
+ GIT_REFCOUNT_INC(index);
+
+ if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL)))
+ error = git_checkout_iterator(index_i, opts);
+
+ git_iterator_free(index_i);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_checkout_tree(
+ git_repository *repo,
+ const git_object *treeish,
+ git_checkout_opts *opts)
+{
+ int error;
+ git_tree *tree = NULL;
+ git_iterator *tree_i = NULL;
+
+ if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0)
+ return error;
+
+ if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
+ }
+
+ if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL)))
+ error = git_checkout_iterator(tree_i, opts);
+
+ git_iterator_free(tree_i);
+ git_tree_free(tree);
+
+ return error;
+}
+
+int git_checkout_head(
+ git_repository *repo,
+ git_checkout_opts *opts)
+{
+ int error;
+ git_tree *head = NULL;
+ git_iterator *head_i = NULL;
+
+ if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0)
+ return error;
+
+ if (!(error = checkout_lookup_head_tree(&head, repo)) &&
+ !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL)))
+ error = git_checkout_iterator(head_i, opts);
+
+ git_iterator_free(head_i);
+ git_tree_free(head);
+
+ return error;
+}
diff --git a/src/checkout.h b/src/checkout.h
new file mode 100644
index 000000000..b1dc80c38
--- /dev/null
+++ b/src/checkout.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_checkout_h__
+#define INCLUDE_checkout_h__
+
+#include "git2/checkout.h"
+#include "iterator.h"
+
+#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
+
+/**
+ * Update the working directory to match the target iterator. The
+ * expected baseline value can be passed in via the checkout options
+ * or else will default to the HEAD commit.
+ */
+extern int git_checkout_iterator(
+ git_iterator *target,
+ git_checkout_opts *opts);
+
+#endif
diff --git a/src/clone.c b/src/clone.c
new file mode 100644
index 000000000..0bbccd44b
--- /dev/null
+++ b/src/clone.c
@@ -0,0 +1,466 @@
+/*
+ * 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 <assert.h>
+
+#include "git2/clone.h"
+#include "git2/remote.h"
+#include "git2/revparse.h"
+#include "git2/branch.h"
+#include "git2/config.h"
+#include "git2/checkout.h"
+#include "git2/commit.h"
+#include "git2/tree.h"
+
+#include "common.h"
+#include "remote.h"
+#include "fileops.h"
+#include "refs.h"
+#include "path.h"
+
+static int create_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *name)
+{
+ git_commit *head_obj = NULL;
+ git_reference *branch_ref = NULL;
+ int error;
+
+ /* Find the target commit */
+ if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)
+ return error;
+
+ /* Create the new branch */
+ error = git_branch_create(&branch_ref, repo, name, head_obj, 0);
+
+ git_commit_free(head_obj);
+
+ if (!error)
+ *branch = branch_ref;
+ else
+ git_reference_free(branch_ref);
+
+ return error;
+}
+
+static int setup_tracking_config(
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_name,
+ const char *merge_target)
+{
+ git_config *cfg;
+ git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_repository_config__weakptr(&cfg, repo) < 0)
+ return -1;
+
+ if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&remote_key);
+ git_buf_free(&merge_key);
+ return error;
+}
+
+static int create_tracking_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *branch_name)
+{
+ int error;
+
+ if ((error = create_branch(branch, repo, target, branch_name)) < 0)
+ return error;
+
+ return setup_tracking_config(
+ repo,
+ branch_name,
+ GIT_REMOTE_ORIGIN,
+ git_reference_name(*branch));
+}
+
+struct head_info {
+ git_repository *repo;
+ git_oid remote_head_oid;
+ git_buf branchname;
+ const git_refspec *refspec;
+ bool found;
+};
+
+static int reference_matches_remote_head(
+ const char *reference_name,
+ void *payload)
+{
+ struct head_info *head_info = (struct head_info *)payload;
+ git_oid oid;
+
+ /* TODO: Should we guard against references
+ * which name doesn't start with refs/heads/ ?
+ */
+
+ /* Stop looking if we've already found a match */
+ if (head_info->found)
+ return 0;
+
+ if (git_reference_name_to_id(
+ &oid,
+ head_info->repo,
+ reference_name) < 0) {
+ /* If the reference doesn't exists, it obviously cannot match the expected oid. */
+ giterr_clear();
+ return 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->refspec,
+ reference_name) < 0)
+ return -1;
+
+ if (git_buf_len(&head_info->branchname) > 0) {
+ if (git_buf_sets(
+ &head_info->branchname,
+ git_buf_cstr(&head_info->branchname) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ return -1;
+
+ head_info->found = 1;
+ }
+ }
+
+ return 0;
+}
+
+static int update_head_to_new_branch(
+ git_repository *repo,
+ const git_oid *target,
+ const char *name)
+{
+ git_reference *tracking_branch = NULL;
+ int error;
+
+ if ((error = create_tracking_branch(
+ &tracking_branch,
+ repo,
+ target,
+ name)) < 0)
+ return error;
+
+ error = git_repository_set_head(repo, git_reference_name(tracking_branch));
+
+ git_reference_free(tracking_branch);
+
+ return error;
+}
+
+static int get_head_callback(git_remote_head *head, void *payload)
+{
+ git_remote_head **destination = (git_remote_head **)payload;
+
+ /* Save the first entry, and terminate the enumeration */
+ *destination = head;
+ return 1;
+}
+
+static int update_head_to_remote(git_repository *repo, git_remote *remote)
+{
+ int retcode = -1;
+ git_remote_head *remote_head;
+ struct head_info head_info;
+ git_buf remote_master_name = GIT_BUF_INIT;
+
+ /* Did we just clone an empty repository? */
+ if (remote->refs.length == 0) {
+ return setup_tracking_config(
+ repo,
+ "master",
+ GIT_REMOTE_ORIGIN,
+ GIT_REFS_HEADS_MASTER_FILE);
+ }
+
+ /* Get the remote's HEAD. This is always the first ref in remote->refs. */
+ remote_head = NULL;
+
+ if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head))
+ return -1;
+
+ assert(remote_head);
+
+ 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.found = 0;
+
+ /* Determine the remote tracking reference name from the local master */
+ if (git_refspec_transform_r(
+ &remote_master_name,
+ head_info.refspec,
+ GIT_REFS_HEADS_MASTER_FILE) < 0)
+ return -1;
+
+ /* Check to see if the remote HEAD points to the remote master */
+ if (reference_matches_remote_head(git_buf_cstr(&remote_master_name), &head_info) < 0)
+ goto cleanup;
+
+ if (head_info.found) {
+ retcode = update_head_to_new_branch(
+ repo,
+ &head_info.remote_head_oid,
+ git_buf_cstr(&head_info.branchname));
+
+ goto cleanup;
+ }
+
+ /* Not master. Check all the other refs. */
+ if (git_reference_foreach(
+ repo,
+ GIT_REF_LISTALL,
+ reference_matches_remote_head,
+ &head_info) < 0)
+ goto cleanup;
+
+ if (head_info.found) {
+ retcode = update_head_to_new_branch(
+ repo,
+ &head_info.remote_head_oid,
+ git_buf_cstr(&head_info.branchname));
+
+ goto cleanup;
+ } else {
+ retcode = git_repository_set_head_detached(
+ repo,
+ &head_info.remote_head_oid);
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&remote_master_name);
+ git_buf_free(&head_info.branchname);
+ return retcode;
+}
+
+static int update_head_to_branch(
+ git_repository *repo,
+ const git_clone_options *options)
+{
+ int retcode;
+ git_buf remote_branch_name = GIT_BUF_INIT;
+ git_reference* remote_ref = NULL;
+
+ assert(options->checkout_branch);
+
+ if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
+ options->remote_name, options->checkout_branch)) < 0 )
+ goto cleanup;
+
+ if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
+ goto cleanup;
+
+ retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref),
+ options->checkout_branch);
+
+cleanup:
+ git_reference_free(remote_ref);
+ git_buf_free(&remote_branch_name);
+ return retcode;
+}
+
+/*
+ * submodules?
+ */
+
+static int create_and_configure_origin(
+ git_remote **out,
+ git_repository *repo,
+ const char *url,
+ const git_clone_options *options)
+{
+ int error;
+ git_remote *origin = NULL;
+
+ if ((error = git_remote_create(&origin, repo, options->remote_name, url)) < 0)
+ goto on_error;
+
+ git_remote_set_cred_acquire_cb(origin, options->cred_acquire_cb,
+ options->cred_acquire_payload);
+ git_remote_set_autotag(origin, options->remote_autotag);
+ /*
+ * Don't write FETCH_HEAD, we'll check out the remote tracking
+ * branch ourselves based on the server's default.
+ */
+ git_remote_set_update_fetchhead(origin, 0);
+
+ if (options->remote_callbacks &&
+ (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->push_spec &&
+ (error = git_remote_set_pushspec(origin, options->push_spec)) < 0)
+ goto on_error;
+
+ if (options->pushurl &&
+ (error = git_remote_set_pushurl(origin, options->pushurl)) < 0)
+ goto on_error;
+
+ if ((error = git_remote_save(origin)) < 0)
+ goto on_error;
+
+ *out = origin;
+ return 0;
+
+on_error:
+ git_remote_free(origin);
+ return error;
+}
+
+
+static int setup_remotes_and_fetch(
+ git_repository *repo,
+ const char *url,
+ const git_clone_options *options)
+{
+ int retcode = GIT_ERROR;
+ git_remote *origin;
+
+ /* Construct an origin remote */
+ if (!create_and_configure_origin(&origin, repo, url, options)) {
+ git_remote_set_update_fetchhead(origin, 0);
+
+ /* Connect and download everything */
+ if (!git_remote_connect(origin, GIT_DIRECTION_FETCH)) {
+ if (!(retcode = git_remote_download(origin, options->fetch_progress_cb,
+ options->fetch_progress_payload))) {
+ /* Create "origin/foo" branches for all remote branches */
+ if (!git_remote_update_tips(origin)) {
+ /* Point HEAD to the requested branch */
+ if (options->checkout_branch) {
+ if (!update_head_to_branch(repo, options))
+ retcode = 0;
+ }
+ /* Point HEAD to the same ref as the remote's head */
+ else if (!update_head_to_remote(repo, origin)) {
+ retcode = 0;
+ }
+ }
+ }
+ git_remote_disconnect(origin);
+ }
+ git_remote_free(origin);
+ }
+
+ return retcode;
+}
+
+
+static bool path_is_okay(const char *path)
+{
+ /* The path must either not exist, or be an empty directory */
+ if (!git_path_exists(path)) return true;
+ if (!git_path_is_empty_dir(path)) {
+ giterr_set(GITERR_INVALID,
+ "'%s' exists and is not an empty directory", path);
+ return false;
+ }
+ return true;
+}
+
+static bool should_checkout(
+ git_repository *repo,
+ bool is_bare,
+ git_checkout_opts *opts)
+{
+ if (is_bare)
+ return false;
+
+ if (!opts)
+ return false;
+
+ if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
+ return false;
+
+ return !git_repository_head_orphan(repo);
+}
+
+static void normalize_options(git_clone_options *dst, const git_clone_options *src)
+{
+ git_clone_options default_options = GIT_CLONE_OPTIONS_INIT;
+ if (!src) src = &default_options;
+
+ *dst = *src;
+
+ /* Provide defaults for null pointers */
+ if (!dst->remote_name) dst->remote_name = "origin";
+ if (!dst->remote_autotag) dst->remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
+}
+
+int git_clone(
+ git_repository **out,
+ const char *url,
+ const char *local_path,
+ const git_clone_options *options)
+{
+ int retcode = GIT_ERROR;
+ git_repository *repo = NULL;
+ git_clone_options normOptions;
+ int remove_directory_on_failure = 0;
+
+ assert(out && url && local_path);
+
+ normalize_options(&normOptions, options);
+ GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
+
+ if (!path_is_okay(local_path)) {
+ return GIT_ERROR;
+ }
+
+ /* Only remove the directory on failure if we create it */
+ remove_directory_on_failure = !git_path_exists(local_path);
+
+ if (!(retcode = git_repository_init(&repo, local_path, normOptions.bare))) {
+ if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) {
+ /* Failed to fetch; clean up */
+ git_repository_free(repo);
+
+ if (remove_directory_on_failure)
+ git_futils_rmdir_r(local_path, NULL, GIT_RMDIR_REMOVE_FILES);
+ else
+ git_futils_cleanupdir_r(local_path);
+
+ } else {
+ *out = repo;
+ retcode = 0;
+ }
+ }
+
+ if (!retcode && should_checkout(repo, normOptions.bare, &normOptions.checkout_opts))
+ retcode = git_checkout_head(*out, &normOptions.checkout_opts);
+
+ return retcode;
+}
diff --git a/src/commit.c b/src/commit.c
index 2bf12f3a5..c7b83ed43 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -18,31 +18,22 @@
#include <stdarg.h>
-#define COMMIT_BASIC_PARSE 0x0
-#define COMMIT_FULL_PARSE 0x1
-
-#define COMMIT_PRINT(commit) {\
- char oid[41]; oid[40] = 0;\
- git_oid_fmt(oid, &commit->object.id);\
- printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\
-}
-
static void clear_parents(git_commit *commit)
{
- unsigned int i;
+ size_t i;
- for (i = 0; i < commit->parent_oids.length; ++i) {
- git_oid *parent = git_vector_get(&commit->parent_oids, i);
+ for (i = 0; i < commit->parent_ids.length; ++i) {
+ git_oid *parent = git_vector_get(&commit->parent_ids, i);
git__free(parent);
}
- git_vector_clear(&commit->parent_oids);
+ git_vector_clear(&commit->parent_ids);
}
void git_commit__free(git_commit *commit)
{
clear_parents(commit);
- git_vector_free(&commit->parent_oids);
+ git_vector_free(&commit->parent_ids);
git_signature_free(commit->author);
git_signature_free(commit->committer);
@@ -52,11 +43,6 @@ void git_commit__free(git_commit *commit)
git__free(commit);
}
-const git_oid *git_commit_id(git_commit *c)
-{
- return git_object_id((git_object *)c);
-}
-
int git_commit_create_v(
git_oid *oid,
git_repository *repo,
@@ -90,66 +76,6 @@ int git_commit_create_v(
return res;
}
-/* Update the reference named `ref_name` so it points to `oid` */
-static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name)
-{
- git_reference *ref;
- int res;
-
- res = git_reference_lookup(&ref, repo, ref_name);
-
- /* If we haven't found the reference at all, we assume we need to create
- * a new reference and that's it */
- if (res == GIT_ENOTFOUND) {
- giterr_clear();
- return git_reference_create_oid(NULL, repo, ref_name, oid, 1);
- }
-
- if (res < 0)
- return -1;
-
- /* If we have found a reference, but it's symbolic, we need to update
- * the direct reference it points to */
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
- git_reference *aux;
- const char *sym_target;
-
- /* The target pointed at by this reference */
- sym_target = git_reference_target(ref);
-
- /* resolve the reference to the target it points to */
- res = git_reference_resolve(&aux, ref);
-
- /*
- * if the symbolic reference pointed to an inexisting ref,
- * this is means we're creating a new branch, for example.
- * We need to create a new direct reference with that name
- */
- if (res == GIT_ENOTFOUND) {
- giterr_clear();
- res = git_reference_create_oid(NULL, repo, sym_target, oid, 1);
- git_reference_free(ref);
- return res;
- }
-
- /* free the original symbolic reference now; not before because
- * we're using the `sym_target` pointer */
- git_reference_free(ref);
-
- if (res < 0)
- return -1;
-
- /* store the newly found direct reference in its place */
- ref = aux;
- }
-
- /* ref is made to point to `oid`: ref is either the original reference,
- * or the target of the symbolic reference we've looked up */
- res = git_reference_set_oid(ref, oid);
- git_reference_free(ref);
- return res;
-}
-
int git_commit_create(
git_oid *oid,
git_repository *repo,
@@ -162,7 +88,7 @@ int git_commit_create(
int parent_count,
const git_commit *parents[])
{
- git_buf commit = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT;
+ git_buf commit = GIT_BUF_INIT;
int i;
git_odb *odb;
@@ -183,15 +109,9 @@ int git_commit_create(
git_buf_putc(&commit, '\n');
- /* Remove comments by default */
- if (git_message_prettify(&cleaned_message, message, 1) < 0)
+ if (git_buf_puts(&commit, message) < 0)
goto on_error;
- if (git_buf_puts(&commit, git_buf_cstr(&cleaned_message)) < 0)
- goto on_error;
-
- git_buf_free(&cleaned_message);
-
if (git_repository_odb__weakptr(&odb, repo) < 0)
goto on_error;
@@ -201,13 +121,12 @@ int git_commit_create(
git_buf_free(&commit);
if (update_ref != NULL)
- return update_reference(repo, oid, update_ref);
+ return git_reference__update_terminal(repo, update_ref, oid);
return 0;
on_error:
git_buf_free(&commit);
- git_buf_free(&cleaned_message);
giterr_set(GITERR_OBJECT, "Failed to create commit.");
return -1;
}
@@ -216,27 +135,25 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
{
const char *buffer = data;
const char *buffer_end = (const char *)data + len;
+ git_oid parent_id;
- git_oid parent_oid;
-
- git_vector_init(&commit->parent_oids, 4, NULL);
+ if (git_vector_init(&commit->parent_ids, 4, NULL) < 0)
+ return -1;
- if (git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ") < 0)
+ if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
goto bad_buffer;
/*
* TODO: commit grafts!
*/
- while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == 0) {
- git_oid *new_oid;
-
- new_oid = git__malloc(sizeof(git_oid));
- GITERR_CHECK_ALLOC(new_oid);
+ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
+ git_oid *new_id = git__malloc(sizeof(git_oid));
+ GITERR_CHECK_ALLOC(new_id);
- git_oid_cpy(new_oid, &parent_oid);
+ git_oid_cpy(new_id, &parent_id);
- if (git_vector_insert(&commit->parent_oids, new_oid) < 0)
+ if (git_vector_insert(&commit->parent_ids, new_id) < 0)
return -1;
}
@@ -253,24 +170,30 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
return -1;
- if (git__prefixcmp(buffer, "encoding ") == 0) {
- const char *encoding_end;
- buffer += strlen("encoding ");
+ /* Parse add'l header entries until blank line found */
+ while (buffer < buffer_end && *buffer != '\n') {
+ const char *eoln = buffer;
+ while (eoln < buffer_end && *eoln != '\n')
+ ++eoln;
+
+ if (git__prefixcmp(buffer, "encoding ") == 0) {
+ buffer += strlen("encoding ");
- encoding_end = buffer;
- while (encoding_end < buffer_end && *encoding_end != '\n')
- encoding_end++;
+ commit->message_encoding = git__strndup(buffer, eoln - buffer);
+ GITERR_CHECK_ALLOC(commit->message_encoding);
+ }
- commit->message_encoding = git__strndup(buffer, encoding_end - buffer);
- GITERR_CHECK_ALLOC(commit->message_encoding);
+ if (eoln < buffer_end && *eoln == '\n')
+ ++eoln;
- buffer = encoding_end;
+ buffer = eoln;
}
- /* parse commit message */
- while (buffer < buffer_end - 1 && *buffer == '\n')
+ /* buffer is now at the end of the header, double-check and move forward into the message */
+ if (buffer < buffer_end && *buffer == '\n')
buffer++;
+ /* parse commit message */
if (buffer <= buffer_end) {
commit->message = git__strndup(buffer, buffer_end - buffer);
GITERR_CHECK_ALLOC(commit->message);
@@ -290,7 +213,7 @@ int git_commit__parse(git_commit *commit, git_odb_object *obj)
}
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
- _rvalue git_commit_##_name(git_commit *commit) \
+ _rvalue git_commit_##_name(const git_commit *commit) \
{\
assert(commit); \
return _return; \
@@ -302,33 +225,66 @@ GIT_COMMIT_GETTER(const char *, message, commit->message)
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
-GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
-GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
+GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length)
+GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
+int git_commit_tree(git_tree **tree_out, const git_commit *commit)
+{
+ assert(commit);
+ return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
+}
-int git_commit_tree(git_tree **tree_out, git_commit *commit)
+const git_oid *git_commit_parent_id(git_commit *commit, unsigned int n)
{
assert(commit);
- return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid);
+
+ return git_vector_get(&commit->parent_ids, n);
}
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
{
- git_oid *parent_oid;
+ const git_oid *parent_id;
assert(commit);
- parent_oid = git_vector_get(&commit->parent_oids, n);
- if (parent_oid == NULL) {
+ parent_id = git_commit_parent_id(commit, n);
+ if (parent_id == NULL) {
giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
return GIT_ENOTFOUND;
}
- return git_commit_lookup(parent, commit->object.repo, parent_oid);
+ return git_commit_lookup(parent, commit->object.repo, parent_id);
}
-const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
+int git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n)
{
- assert(commit);
+ git_commit *current, *parent;
+ int error;
+
+ assert(ancestor && commit);
+
+ current = (git_commit *)commit;
+
+ if (n == 0)
+ return git_commit_lookup(
+ ancestor,
+ commit->object.repo,
+ git_object_id((const git_object *)commit));
+
+ while (n--) {
+ error = git_commit_parent(&parent, (git_commit *)current, 0);
- return git_vector_get(&commit->parent_oids, n);
+ if (current != commit)
+ git_commit_free(current);
+
+ if (error < 0)
+ return error;
+
+ current = parent;
+ }
+
+ *ancestor = parent;
+ return 0;
}
diff --git a/src/commit.h b/src/commit.h
index d9f492862..1ab164c0b 100644
--- a/src/commit.h
+++ b/src/commit.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -17,8 +17,8 @@
struct git_commit {
git_object object;
- git_vector parent_oids;
- git_oid tree_oid;
+ git_vector parent_ids;
+ git_oid tree_id;
git_signature *author;
git_signature *committer;
diff --git a/src/commit_list.c b/src/commit_list.c
new file mode 100644
index 000000000..603dd754a
--- /dev/null
+++ b/src/commit_list.c
@@ -0,0 +1,194 @@
+/*
+ * 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 "commit_list.h"
+#include "common.h"
+#include "revwalk.h"
+#include "pool.h"
+#include "odb.h"
+
+int git_commit_list_time_cmp(void *a, void *b)
+{
+ git_commit_list_node *commit_a = (git_commit_list_node *)a;
+ git_commit_list_node *commit_b = (git_commit_list_node *)b;
+
+ return (commit_a->time < commit_b->time);
+}
+
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list *new_list = git__malloc(sizeof(git_commit_list));
+ if (new_list != NULL) {
+ new_list->item = item;
+ new_list->next = *list_p;
+ }
+ *list_p = new_list;
+ return new_list;
+}
+
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list **pp = list_p;
+ git_commit_list *p;
+
+ while ((p = *pp) != NULL) {
+ if (git_commit_list_time_cmp(p->item, item) < 0)
+ break;
+
+ pp = &p->next;
+ }
+
+ return git_commit_list_insert(item, pp);
+}
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)
+{
+ return (git_commit_list_node *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC);
+}
+
+static int commit_error(git_commit_list_node *commit, const char *msg)
+{
+ char commit_oid[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(commit_oid, &commit->oid);
+ commit_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_ODB, "Failed to parse commit %s - %s", commit_oid, msg);
+
+ return -1;
+}
+
+static git_commit_list_node **alloc_parents(
+ git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)
+{
+ if (n_parents <= PARENTS_PER_COMMIT)
+ return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));
+
+ return (git_commit_list_node **)git_pool_malloc(
+ &walk->commit_pool, (uint32_t)(n_parents * sizeof(git_commit_list_node *)));
+}
+
+
+void git_commit_list_free(git_commit_list **list_p)
+{
+ git_commit_list *list = *list_p;
+
+ if (list == NULL)
+ return;
+
+ while (list) {
+ git_commit_list *temp = list;
+ list = temp->next;
+ git__free(temp);
+ }
+
+ *list_p = NULL;
+}
+
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
+{
+ git_commit_list *top = *stack;
+ git_commit_list_node *item = top ? top->item : NULL;
+
+ if (top) {
+ *stack = top->next;
+ git__free(top);
+ }
+ return item;
+}
+
+static int commit_quick_parse(git_revwalk *walk, git_commit_list_node *commit, git_rawobj *raw)
+{
+ 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;
+ int i, parents = 0;
+ int commit_time;
+
+ buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;
+
+ parents_start = buffer;
+ while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {
+ parents++;
+ buffer += parent_len;
+ }
+
+ commit->parents = alloc_parents(walk, commit, parents);
+ GITERR_CHECK_ALLOC(commit->parents);
+
+ buffer = parents_start;
+ for (i = 0; i < parents; ++i) {
+ git_oid oid;
+
+ if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0)
+ return -1;
+
+ commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);
+ if (commit->parents[i] == NULL)
+ return -1;
+
+ buffer += parent_len;
+ }
+
+ commit->out_degree = (unsigned short)parents;
+
+ if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ buffer++;
+
+ if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ /* Skip trailing spaces */
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ /* Seek for the begining of the pack of digits */
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+
+ /* Skip potential timezone offset */
+ if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) {
+ buffer--;
+
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+ }
+
+ if ((buffer == committer_start) || (git__strtol32(&commit_time, (char *)(buffer + 1), NULL, 10) < 0))
+ return commit_error(commit, "cannot parse commit time");
+
+ commit->time = (time_t)commit_time;
+ commit->parsed = 1;
+ return 0;
+}
+
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
+{
+ git_odb_object *obj;
+ int error;
+
+ if (commit->parsed)
+ return 0;
+
+ if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
+ return error;
+
+ if (obj->raw.type != GIT_OBJ_COMMIT) {
+ giterr_set(GITERR_INVALID, "Object is no commit object");
+ error = -1;
+ } else
+ error = commit_quick_parse(walk, commit, &obj->raw);
+
+ git_odb_object_free(obj);
+ return error;
+}
+
diff --git a/src/commit_list.h b/src/commit_list.h
new file mode 100644
index 000000000..d2f54b3ca
--- /dev/null
+++ b/src/commit_list.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_commit_list_h__
+#define INCLUDE_commit_list_h__
+
+#include "git2/oid.h"
+
+#define PARENT1 (1 << 0)
+#define PARENT2 (1 << 1)
+#define RESULT (1 << 2)
+#define STALE (1 << 3)
+
+#define PARENTS_PER_COMMIT 2
+#define COMMIT_ALLOC \
+ (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *))
+
+typedef struct git_commit_list_node {
+ git_oid oid;
+ uint32_t time;
+ unsigned int seen:1,
+ uninteresting:1,
+ topo_delay:1,
+ parsed:1,
+ flags : 4;
+
+ unsigned short in_degree;
+ unsigned short out_degree;
+
+ struct git_commit_list_node **parents;
+} git_commit_list_node;
+
+typedef struct git_commit_list {
+ git_commit_list_node *item;
+ struct git_commit_list *next;
+} git_commit_list;
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk);
+int git_commit_list_time_cmp(void *a, void *b);
+void git_commit_list_free(git_commit_list **list_p);
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p);
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p);
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit);
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack);
+
+#endif
diff --git a/src/common.h b/src/common.h
index 30757de70..02d9ce9b6 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -24,21 +24,24 @@
# include <io.h>
# include <direct.h>
+# include <winsock2.h>
# include <windows.h>
# include "win32/msvc-compat.h"
# include "win32/mingw-compat.h"
+# include "win32/error.h"
+# include "win32/version.h"
# ifdef GIT_THREADS
# include "win32/pthread.h"
-#endif
-
-# define snprintf _snprintf
+# endif
#else
-# include <unistd.h>
+# include <unistd.h>
# ifdef GIT_THREADS
# include <pthread.h>
# endif
+#define GIT_STDLIB_CALL
+
#endif
#include "git2/types.h"
@@ -48,25 +51,59 @@
#include <regex.h>
-extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
-#define git__throw(error, ...) \
- (git___throw(__VA_ARGS__), error)
+/**
+ * Check a pointer allocation result, returning -1 if it failed.
+ */
+#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
-extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
-#define git__rethrow(error, ...) \
- (git___rethrow(__VA_ARGS__), error)
+/**
+ * Check a return value and propogate result if non-zero.
+ */
+#define GITERR_CHECK_ERROR(code) \
+ do { int _err = (code); if (_err < 0) return _err; } while (0)
+/**
+ * Set the error message for this thread, formatting as needed.
+ */
+void giterr_set(int error_class, const char *string, ...);
-#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
+/**
+ * Set the error message for a regex failure, using the internal regex
+ * error code lookup and return a libgit error code.
+ */
+int giterr_set_regex(const regex_t *regex, int error_code);
-void giterr_set_oom(void);
-void giterr_set(int error_class, const char *string, ...);
-void giterr_clear(void);
-void giterr_set_str(int error_class, const char *string);
-void giterr_set_regex(const regex_t *regex, int error_code);
+/**
+ * Check a versioned structure for validity
+ */
+GIT_INLINE(int) giterr__check_version(const void *structure, unsigned int expected_max, const char *name)
+{
+ unsigned int actual;
+ if (!structure)
+ return 0;
-#include "util.h"
+ actual = *(const unsigned int*)structure;
+ if (actual > 0 && actual <= expected_max)
+ return 0;
+ giterr_set(GITERR_INVALID, "Invalid version %d on %s", actual, name);
+ return -1;
+}
+#define GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) return -1
+
+/**
+ * Initialize a structure with a version.
+ */
+GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version)
+{
+ memset(structure, 0, len);
+ *((int*)structure) = version;
+}
+#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)
+
+/* NOTE: other giterr functions are in the public errors.h header file */
+
+#include "util.h"
#endif /* INCLUDE_common_h__ */
diff --git a/src/compress.c b/src/compress.c
new file mode 100644
index 000000000..14b79404c
--- /dev/null
+++ b/src/compress.c
@@ -0,0 +1,53 @@
+/*
+ * 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 "compress.h"
+
+#include <zlib.h>
+
+#define BUFFER_SIZE (1024 * 1024)
+
+int git__compress(git_buf *buf, const void *buff, size_t len)
+{
+ z_stream zs;
+ char *zb;
+ size_t have;
+
+ memset(&zs, 0, sizeof(zs));
+ if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
+ return -1;
+
+ zb = git__malloc(BUFFER_SIZE);
+ GITERR_CHECK_ALLOC(zb);
+
+ zs.next_in = (void *)buff;
+ zs.avail_in = (uInt)len;
+
+ do {
+ zs.next_out = (unsigned char *)zb;
+ zs.avail_out = BUFFER_SIZE;
+
+ if (deflate(&zs, Z_FINISH) == Z_STREAM_ERROR) {
+ git__free(zb);
+ return -1;
+ }
+
+ have = BUFFER_SIZE - (size_t)zs.avail_out;
+
+ if (git_buf_put(buf, zb, have) < 0) {
+ git__free(zb);
+ return -1;
+ }
+
+ } while (zs.avail_out == 0);
+
+ assert(zs.avail_in == 0);
+
+ deflateEnd(&zs);
+ git__free(zb);
+ return 0;
+}
diff --git a/src/compress.h b/src/compress.h
new file mode 100644
index 000000000..49e6f4749
--- /dev/null
+++ b/src/compress.h
@@ -0,0 +1,16 @@
+/*
+ * 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_compress_h__
+#define INCLUDE_compress_h__
+
+#include "common.h"
+
+#include "buffer.h"
+
+int git__compress(git_buf *buf, const void *buff, size_t len);
+
+#endif /* INCLUDE_compress_h__ */
diff --git a/src/config.c b/src/config.c
index 618202c34..5379b0ec5 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,6 +10,8 @@
#include "config.h"
#include "git2/config.h"
#include "vector.h"
+#include "buf_text.h"
+#include "config_file.h"
#if GIT_WIN32
# include <windows.h>
#endif
@@ -17,21 +19,29 @@
#include <ctype.h>
typedef struct {
- git_config_file *file;
- int priority;
+ git_refcount rc;
+
+ git_config_backend *file;
+ unsigned int level;
} file_internal;
+static void file_internal_free(file_internal *internal)
+{
+ git_config_backend *file;
+
+ file = internal->file;
+ file->free(file);
+ git__free(internal);
+}
+
static void config_free(git_config *cfg)
{
- unsigned int i;
- git_config_file *file;
+ size_t i;
file_internal *internal;
for(i = 0; i < cfg->files.length; ++i){
internal = git_vector_get(&cfg->files, i);
- file = internal->file;
- file->free(file);
- git__free(internal);
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
}
git_vector_free(&cfg->files);
@@ -51,7 +61,7 @@ static int config_backend_cmp(const void *a, const void *b)
const file_internal *bk_a = (const file_internal *)(a);
const file_internal *bk_b = (const file_internal *)(b);
- return bk_b->priority - bk_a->priority;
+ return bk_b->level - bk_a->level;
}
int git_config_new(git_config **out)
@@ -73,91 +83,252 @@ int git_config_new(git_config **out)
return 0;
}
-int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority)
+int git_config_add_file_ondisk(
+ git_config *cfg,
+ const char *path,
+ unsigned int level,
+ int force)
{
- git_config_file *file = NULL;
+ git_config_backend *file = NULL;
+ int res;
+
+ assert(cfg && path);
+
+ if (!git_path_isfile(path)) {
+ giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path);
+ return GIT_ENOTFOUND;
+ }
if (git_config_file__ondisk(&file, path) < 0)
return -1;
- if (git_config_add_file(cfg, file, priority) < 0) {
+ if ((res = git_config_add_backend(cfg, file, level, force)) < 0) {
/*
* free manually; the file is not owned by the config
* instance yet and will not be freed on cleanup
*/
file->free(file);
- return -1;
+ return res;
}
return 0;
}
-int git_config_open_ondisk(git_config **cfg, const char *path)
+int git_config_open_ondisk(git_config **out, const char *path)
{
- if (git_config_new(cfg) < 0)
- return -1;
+ int error;
+ git_config *config;
- if (git_config_add_file_ondisk(*cfg, path, 1) < 0) {
- git_config_free(*cfg);
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
return -1;
+
+ if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
+}
+
+static int find_internal_file_by_level(
+ file_internal **internal_out,
+ const git_config *cfg,
+ int level)
+{
+ int pos = -1;
+ file_internal *internal;
+ unsigned int i;
+
+ /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
+ * which has the highest level. As config files are stored in a vector
+ * sorted by decreasing order of level, getting the file at position 0
+ * will do the job.
+ */
+ if (level == GIT_CONFIG_HIGHEST_LEVEL) {
+ pos = 0;
+ } else {
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == (unsigned int)level)
+ pos = i;
+ }
+ }
+
+ if (pos == -1) {
+ giterr_set(GITERR_CONFIG,
+ "No config file exists for the given level '%i'", level);
+ return GIT_ENOTFOUND;
+ }
+
+ *internal_out = git_vector_get(&cfg->files, pos);
+
+ return 0;
+}
+
+static int duplicate_level(void **old_raw, void *new_raw)
+{
+ file_internal **old = (file_internal **)old_raw;
+
+ GIT_UNUSED(new_raw);
+
+ giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (*old)->level);
+ return GIT_EEXISTS;
+}
+
+static void try_remove_existing_file_internal(
+ git_config *cfg,
+ unsigned int level)
+{
+ int pos = -1;
+ file_internal *internal;
+ unsigned int i;
+
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == level)
+ pos = i;
+ }
+
+ if (pos == -1)
+ return;
+
+ internal = git_vector_get(&cfg->files, pos);
+
+ if (git_vector_remove(&cfg->files, pos) < 0)
+ return;
+
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
+}
+
+static int git_config__add_internal(
+ git_config *cfg,
+ file_internal *internal,
+ unsigned int level,
+ int force)
+{
+ int result;
+
+ /* delete existing config file for level if it exists */
+ if (force)
+ try_remove_existing_file_internal(cfg, level);
+
+ if ((result = git_vector_insert_sorted(&cfg->files,
+ internal, &duplicate_level)) < 0)
+ return result;
+
+ git_vector_sort(&cfg->files);
+ internal->file->cfg = cfg;
+
+ GIT_REFCOUNT_INC(internal);
+
+ return 0;
+}
+
+int git_config_open_level(
+ git_config **cfg_out,
+ const git_config *cfg_parent,
+ unsigned int level)
+{
+ git_config *cfg;
+ file_internal *internal;
+ int res;
+
+ if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0)
+ return res;
+
+ if ((res = git_config_new(&cfg)) < 0)
+ return res;
+
+ if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) {
+ git_config_free(cfg);
+ return res;
}
+ *cfg_out = cfg;
+
return 0;
}
-int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
+int git_config_add_backend(
+ git_config *cfg,
+ git_config_backend *file,
+ unsigned int level,
+ int force)
{
file_internal *internal;
int result;
assert(cfg && file);
- if ((result = file->open(file)) < 0)
+ GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
+
+ if ((result = file->open(file, level)) < 0)
return result;
internal = git__malloc(sizeof(file_internal));
GITERR_CHECK_ALLOC(internal);
+ memset(internal, 0x0, sizeof(file_internal));
+
internal->file = file;
- internal->priority = priority;
+ internal->level = level;
- if (git_vector_insert(&cfg->files, internal) < 0) {
+ if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) {
git__free(internal);
- return -1;
+ return result;
}
- git_vector_sort(&cfg->files);
- internal->file->cfg = cfg;
-
return 0;
}
+int git_config_refresh(git_config *cfg)
+{
+ int error = 0;
+ size_t i;
+
+ for (i = 0; i < cfg->files.length && !error; ++i) {
+ file_internal *internal = git_vector_get(&cfg->files, i);
+ git_config_backend *file = internal->file;
+ error = file->refresh(file);
+ }
+
+ return error;
+}
+
/*
* Loop over all the variables
*/
-int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data)
+int git_config_foreach(
+ const git_config *cfg, git_config_foreach_cb cb, void *payload)
+{
+ return git_config_foreach_match(cfg, NULL, cb, payload);
+}
+
+int git_config_foreach_match(
+ const git_config *cfg,
+ const char *regexp,
+ git_config_foreach_cb cb,
+ void *payload)
{
int ret = 0;
- unsigned int i;
+ size_t i;
file_internal *internal;
- git_config_file *file;
+ git_config_backend *file;
- for(i = 0; i < cfg->files.length && ret == 0; ++i) {
+ for (i = 0; i < cfg->files.length && ret == 0; ++i) {
internal = git_vector_get(&cfg->files, i);
file = internal->file;
- ret = file->foreach(file, fn, data);
+ ret = file->foreach(file, regexp, cb, payload);
}
return ret;
}
-int git_config_delete(git_config *cfg, const char *name)
+int git_config_delete_entry(git_config *cfg, const char *name)
{
+ git_config_backend *file;
file_internal *internal;
- git_config_file *file;
-
- assert(cfg->files.length);
internal = git_vector_get(&cfg->files, 0);
file = internal->file;
@@ -188,10 +359,13 @@ 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)
{
+ git_config_backend *file;
file_internal *internal;
- git_config_file *file;
- assert(cfg->files.length);
+ if (!value) {
+ giterr_set(GITERR_CONFIG, "The value to set cannot be NULL");
+ return -1;
+ }
internal = git_vector_get(&cfg->files, 0);
file = internal->file;
@@ -199,211 +373,121 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
return file->set(file, name, value);
}
-static int parse_int64(int64_t *out, const char *value)
-{
- const char *num_end;
- int64_t num;
-
- if (git__strtol64(&num, value, &num_end, 0) < 0)
- return -1;
-
- switch (*num_end) {
- case 'g':
- case 'G':
- num *= 1024;
- /* fallthrough */
-
- case 'm':
- case 'M':
- num *= 1024;
- /* fallthrough */
-
- case 'k':
- case 'K':
- num *= 1024;
-
- /* check that that there are no more characters after the
- * given modifier suffix */
- if (num_end[1] != '\0')
- return -1;
-
- /* fallthrough */
-
- case '\0':
- *out = num;
- return 0;
-
- default:
- return -1;
- }
-}
-
-static int parse_int32(int32_t *out, const char *value)
-{
- int64_t tmp;
- int32_t truncate;
-
- if (parse_int64(&tmp, value) < 0)
- return -1;
-
- truncate = tmp & 0xFFFFFFFF;
- if (truncate != tmp)
- return -1;
-
- *out = truncate;
- return 0;
-}
-
/***********
* Getters
***********/
-int git_config_lookup_map_value(
- git_cvar_map *maps, size_t map_n, const char *value, int *out)
-{
- size_t i;
-
- if (!value)
- return GIT_ENOTFOUND;
-
- for (i = 0; i < map_n; ++i) {
- git_cvar_map *m = maps + i;
-
- switch (m->cvar_type) {
- case GIT_CVAR_FALSE:
- case GIT_CVAR_TRUE: {
- int bool_val;
-
- if (git__parse_bool(&bool_val, value) == 0 &&
- bool_val == (int)m->cvar_type) {
- *out = m->map_value;
- return 0;
- }
- break;
- }
-
- case GIT_CVAR_INT32:
- if (parse_int32(out, value) == 0)
- return 0;
- break;
-
- case GIT_CVAR_STRING:
- if (strcasecmp(value, m->str_match) == 0) {
- *out = m->map_value;
- return 0;
- }
- break;
- }
- }
-
- return GIT_ENOTFOUND;
-}
-
int git_config_get_mapped(
int *out,
- git_config *cfg,
+ const git_config *cfg,
const char *name,
- git_cvar_map *maps,
+ const git_cvar_map *maps,
size_t map_n)
{
const char *value;
int ret;
- ret = git_config_get_string(&value, cfg, name);
- if (ret < 0)
+ if ((ret = git_config_get_string(&value, cfg, name)) < 0)
return ret;
- if (!git_config_lookup_map_value(maps, map_n, value, out))
- return 0;
-
- giterr_set(GITERR_CONFIG,
- "Failed to map the '%s' config variable with a valid value", name);
- return -1;
+ return git_config_lookup_map_value(out, maps, map_n, value);
}
-int git_config_get_int64(int64_t *out, git_config *cfg, const char *name)
+int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name)
{
const char *value;
int ret;
- ret = git_config_get_string(&value, cfg, name);
- if (ret < 0)
+ if ((ret = git_config_get_string(&value, cfg, name)) < 0)
return ret;
- if (parse_int64(out, value) < 0) {
- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
- return -1;
- }
-
- return 0;
+ return git_config_parse_int64(out, value);
}
-int git_config_get_int32(int32_t *out, git_config *cfg, const char *name)
+int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name)
{
const char *value;
int ret;
- ret = git_config_get_string(&value, cfg, name);
- if (ret < 0)
+ if ((ret = git_config_get_string(&value, cfg, name)) < 0)
return ret;
- if (parse_int32(out, value) < 0) {
- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
- return -1;
+ return git_config_parse_int32(out, value);
+}
+
+static int get_string_at_file(const char **out, const git_config_backend *file, const char *name)
+{
+ const git_config_entry *entry;
+ int res;
+
+ res = file->get(file, name, &entry);
+ if (!res)
+ *out = entry->value;
+
+ return res;
+}
+
+static int get_string(const char **out, const git_config *cfg, const char *name)
+{
+ file_internal *internal;
+ unsigned int i;
+
+ git_vector_foreach(&cfg->files, i, internal) {
+ int res = get_string_at_file(out, internal->file, name);
+
+ if (res != GIT_ENOTFOUND)
+ return res;
}
- return 0;
+ return GIT_ENOTFOUND;
}
-int git_config_get_bool(int *out, git_config *cfg, const char *name)
+int git_config_get_bool(int *out, const git_config *cfg, const char *name)
{
- const char *value;
+ const char *value = NULL;
int ret;
- ret = git_config_get_string(&value, cfg, name);
- if (ret < 0)
+ if ((ret = get_string(&value, cfg, name)) < 0)
return ret;
- if (git__parse_bool(out, value) == 0)
- return 0;
+ return git_config_parse_bool(out, value);
+}
- if (parse_int32(out, value) == 0) {
- *out = !!(*out);
- return 0;
- }
+int git_config_get_string(const char **out, const git_config *cfg, const char *name)
+{
+ int ret;
+ const char *str = NULL;
- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
- return -1;
+ if ((ret = get_string(&str, cfg, name)) < 0)
+ return ret;
+
+ *out = str == NULL ? "" : str;
+ return 0;
}
-int git_config_get_string(const char **out, git_config *cfg, const char *name)
+int git_config_get_entry(const git_config_entry **out, const git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
- assert(cfg->files.length);
-
*out = NULL;
git_vector_foreach(&cfg->files, i, internal) {
- git_config_file *file = internal->file;
+ git_config_backend *file = internal->file;
int ret = file->get(file, name, out);
if (ret != GIT_ENOTFOUND)
return ret;
}
- giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name);
return GIT_ENOTFOUND;
}
-int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp,
- int (*fn)(const char *value, void *data), void *data)
+int git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp,
+ git_config_foreach_cb cb, void *payload)
{
file_internal *internal;
- git_config_file *file;
+ git_config_backend *file;
int ret = GIT_ENOTFOUND;
- unsigned int i;
-
- assert(cfg->files.length);
+ size_t i;
/*
* This loop runs the "wrong" way 'round because we need to
@@ -412,7 +496,7 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex
for (i = cfg->files.length; i > 0; --i) {
internal = git_vector_get(&cfg->files, i - 1);
file = internal->file;
- ret = file->get_multivar(file, name, regexp, fn, data);
+ ret = file->get_multivar(file, name, regexp, cb, payload);
if (ret < 0 && ret != GIT_ENOTFOUND)
return ret;
}
@@ -422,47 +506,57 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex
int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
{
+ git_config_backend *file;
file_internal *internal;
- git_config_file *file;
- int ret = GIT_ENOTFOUND;
- unsigned int i;
- for (i = cfg->files.length; i > 0; --i) {
- internal = git_vector_get(&cfg->files, i - 1);
- file = internal->file;
- ret = file->set_multivar(file, name, regexp, value);
- if (ret < 0 && ret != GIT_ENOTFOUND)
- return ret;
+ internal = git_vector_get(&cfg->files, 0);
+ file = internal->file;
+
+ return file->set_multivar(file, name, regexp, value);
+}
+
+static int git_config__find_file_to_path(
+ char *out, size_t outlen, int (*find)(git_buf *buf))
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if ((error = find(&path)) < 0)
+ goto done;
+
+ if (path.size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ error = GIT_EBUFS;
+ goto done;
}
- return 0;
+ git_buf_copy_cstr(out, outlen, &path);
+
+done:
+ git_buf_free(&path);
+ return error;
}
int git_config_find_global_r(git_buf *path)
{
- return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
+ return git_futils_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL);
}
int git_config_find_global(char *global_config_path, size_t length)
{
- git_buf path = GIT_BUF_INIT;
- int ret = git_config_find_global_r(&path);
-
- if (ret < 0) {
- git_buf_free(&path);
- return ret;
- }
+ return git_config__find_file_to_path(
+ global_config_path, length, git_config_find_global_r);
+}
- if (path.size >= length) {
- git_buf_free(&path);
- giterr_set(GITERR_NOMEMORY,
- "Path is to long to fit on the given buffer");
- return -1;
- }
+int git_config_find_xdg_r(git_buf *path)
+{
+ return git_futils_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);
+}
- git_buf_copy_cstr(global_config_path, length, &path);
- git_buf_free(&path);
- return 0;
+int git_config_find_xdg(char *xdg_config_path, size_t length)
+{
+ return git_config__find_file_to_path(
+ xdg_config_path, length, git_config_find_xdg_r);
}
int git_config_find_system_r(git_buf *path)
@@ -472,37 +566,244 @@ int git_config_find_system_r(git_buf *path)
int git_config_find_system(char *system_config_path, size_t length)
{
- git_buf path = GIT_BUF_INIT;
- int ret = git_config_find_system_r(&path);
+ return git_config__find_file_to_path(
+ system_config_path, length, git_config_find_system_r);
+}
- if (ret < 0) {
- git_buf_free(&path);
- return ret;
+int git_config_open_default(git_config **out)
+{
+ int error;
+ git_config *cfg = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ error = git_config_new(&cfg);
+
+ if (!error && !git_config_find_global_r(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_GLOBAL, 0);
+
+ if (!error && !git_config_find_xdg_r(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_XDG, 0);
+
+ if (!error && !git_config_find_system_r(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_SYSTEM, 0);
+
+ git_buf_free(&buf);
+
+ if (error && cfg) {
+ git_config_free(cfg);
+ cfg = NULL;
}
- if (path.size >= length) {
- git_buf_free(&path);
- giterr_set(GITERR_NOMEMORY,
- "Path is to long to fit on the given buffer");
- return -1;
+ *out = cfg;
+
+ return error;
+}
+
+/***********
+ * Parsers
+ ***********/
+int git_config_lookup_map_value(
+ int *out,
+ const git_cvar_map *maps,
+ size_t map_n,
+ const char *value)
+{
+ size_t i;
+
+ if (!value)
+ goto fail_parse;
+
+ for (i = 0; i < map_n; ++i) {
+ const git_cvar_map *m = maps + i;
+
+ switch (m->cvar_type) {
+ case GIT_CVAR_FALSE:
+ case GIT_CVAR_TRUE: {
+ int bool_val;
+
+ if (git__parse_bool(&bool_val, value) == 0 &&
+ bool_val == (int)m->cvar_type) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
+
+ case GIT_CVAR_INT32:
+ if (git_config_parse_int32(out, value) == 0)
+ return 0;
+ break;
+
+ case GIT_CVAR_STRING:
+ if (strcasecmp(value, m->str_match) == 0) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
}
- git_buf_copy_cstr(system_config_path, length, &path);
- git_buf_free(&path);
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to map '%s'", value);
+ return -1;
+}
+
+int git_config_parse_bool(int *out, const char *value)
+{
+ if (git__parse_bool(out, value) == 0)
+ return 0;
+
+ if (git_config_parse_int32(out, value) == 0) {
+ *out = !!(*out);
+ return 0;
+ }
+
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
+ return -1;
+}
+
+int git_config_parse_int64(int64_t *out, const char *value)
+{
+ const char *num_end;
+ int64_t num;
+
+ if (git__strtol64(&num, value, &num_end, 0) < 0)
+ goto fail_parse;
+
+ switch (*num_end) {
+ case 'g':
+ case 'G':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'm':
+ case 'M':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'k':
+ case 'K':
+ num *= 1024;
+
+ /* check that that there are no more characters after the
+ * given modifier suffix */
+ if (num_end[1] != '\0')
+ return -1;
+
+ /* fallthrough */
+
+ case '\0':
+ *out = num;
+ return 0;
+
+ default:
+ goto fail_parse;
+ }
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
+ return -1;
+}
+
+int git_config_parse_int32(int32_t *out, const char *value)
+{
+ int64_t tmp;
+ int32_t truncate;
+
+ if (git_config_parse_int64(&tmp, value) < 0)
+ goto fail_parse;
+
+ truncate = tmp & 0xFFFFFFFF;
+ if (truncate != tmp)
+ goto fail_parse;
+
+ *out = truncate;
return 0;
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
+ return -1;
}
-int git_config_open_global(git_config **out)
+struct rename_data {
+ git_config *config;
+ git_buf *name;
+ size_t old_len;
+ int actual_error;
+};
+
+static int rename_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
{
- int error;
- git_buf path = GIT_BUF_INIT;
+ int error = 0;
+ struct rename_data *data = (struct rename_data *)payload;
+ size_t base_len = git_buf_len(data->name);
- if ((error = git_config_find_global_r(&path)) < 0)
- return error;
+ if (base_len > 0 &&
+ !(error = git_buf_puts(data->name, entry->name + data->old_len)))
+ {
+ error = git_config_set_string(
+ data->config, git_buf_cstr(data->name), entry->value);
- error = git_config_open_ondisk(out, git_buf_cstr(&path));
- git_buf_free(&path);
+ git_buf_truncate(data->name, base_len);
+ }
+
+ if (!error)
+ error = git_config_delete_entry(data->config, entry->name);
+
+ data->actual_error = error; /* preserve actual error code */
return error;
}
+int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name,
+ const char *new_section_name)
+{
+ git_config *config;
+ git_buf pattern = GIT_BUF_INIT, replace = GIT_BUF_INIT;
+ int error = 0;
+ struct rename_data data;
+
+ git_buf_text_puts_escape_regex(&pattern, old_section_name);
+
+ if ((error = git_buf_puts(&pattern, "\\..+")) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ goto cleanup;
+
+ data.config = config;
+ data.name = &replace;
+ data.old_len = strlen(old_section_name) + 1;
+ data.actual_error = 0;
+
+ if ((error = git_buf_join(&replace, '.', new_section_name, "")) < 0)
+ goto cleanup;
+
+ if (new_section_name != NULL &&
+ (error = git_config_file_normalize_section(
+ replace.ptr, strchr(replace.ptr, '.'))) < 0)
+ {
+ giterr_set(
+ GITERR_CONFIG, "Invalid config section '%s'", new_section_name);
+ goto cleanup;
+ }
+
+ error = git_config_foreach_match(
+ config, git_buf_cstr(&pattern), rename_config_entries_cb, &data);
+
+ if (error == GIT_EUSER)
+ error = data.actual_error;
+
+cleanup:
+ git_buf_free(&pattern);
+ git_buf_free(&replace);
+
+ return error;
+}
diff --git a/src/config.h b/src/config.h
index 82e98ce51..c43e47e82 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,9 +12,11 @@
#include "vector.h"
#include "repository.h"
-#define GIT_CONFIG_FILENAME ".gitconfig"
-#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
+#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"
+#define GIT_CONFIG_FILENAME_XDG "config"
+
+#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
@@ -23,11 +25,25 @@ struct git_config {
};
extern int git_config_find_global_r(git_buf *global_config_path);
+extern int git_config_find_xdg_r(git_buf *system_config_path);
extern int git_config_find_system_r(git_buf *system_config_path);
-extern int git_config_parse_bool(int *out, const char *bool_string);
+extern int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name, /* eg "branch.dummy" */
+ const char *new_section_name); /* NULL to drop the old section */
-extern int git_config_lookup_map_value(
- git_cvar_map *maps, size_t map_n, const char *value, int *out);
+/**
+ * Create a configuration file backend for ondisk files
+ *
+ * These are the normal `.gitconfig` files that Core Git
+ * processes. Note that you first have to add this file to a
+ * configuration object before you can query it for configuration
+ * variables.
+ *
+ * @param out the new backend
+ * @param path where the config file is located
+ */
+extern int git_config_file__ondisk(struct git_config_backend **out, const char *path);
#endif
diff --git a/src/config_cache.c b/src/config_cache.c
index ca9602e56..2f36df7d1 100644
--- a/src/config_cache.c
+++ b/src/config_cache.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -24,7 +24,7 @@ struct map_data {
* core.eol
* Sets the line ending type to use in the working directory for
* files that have the text property set. Alternatives are lf, crlf
- * and native, which uses the platform’s native line ending. The default
+ * 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.
*/
diff --git a/src/config_file.c b/src/config_file.c
index cbc48bcd9..8b51ab21b 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,6 +10,7 @@
#include "fileops.h"
#include "filebuf.h"
#include "buffer.h"
+#include "buf_text.h"
#include "git2/config.h"
#include "git2/types.h"
#include "strmap.h"
@@ -22,15 +23,9 @@ GIT__USE_STRMAP;
typedef struct cvar_t {
struct cvar_t *next;
- char *key; /* TODO: we might be able to get rid of this */
- char *value;
+ git_config_entry *entry;
} cvar_t;
-typedef struct {
- struct cvar_t *head;
- struct cvar_t *tail;
-} cvar_t_list;
-
#define CVAR_LIST_HEAD(list) ((list)->head)
#define CVAR_LIST_TAIL(list) ((list)->tail)
@@ -70,7 +65,7 @@ typedef struct {
(iter) = (tmp))
typedef struct {
- git_config_file parent;
+ git_config_backend parent;
git_strmap *values;
@@ -81,12 +76,17 @@ typedef struct {
int eof;
} reader;
- char *file_path;
+ char *file_path;
+ time_t file_mtime;
+ size_t file_size;
+
+ unsigned int level;
} diskfile_backend;
-static int config_parse(diskfile_backend *cfg_file);
+static int config_parse(diskfile_backend *cfg_file, unsigned int level);
static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
+static char *escape_value(const char *ptr);
static void set_parse_error(diskfile_backend *backend, int col, const char *error_str)
{
@@ -99,11 +99,35 @@ static void cvar_free(cvar_t *var)
if (var == NULL)
return;
- git__free(var->key);
- git__free(var->value);
+ git__free((char*)var->entry->name);
+ git__free((char *)var->entry->value);
+ git__free(var->entry);
git__free(var);
}
+int git_config_file_normalize_section(char *start, char *end)
+{
+ char *scan;
+
+ if (start == end)
+ return GIT_EINVALIDSPEC;
+
+ /* Validate and downcase range */
+ for (scan = start; *scan; ++scan) {
+ if (end && scan >= end)
+ break;
+ if (isalnum(*scan))
+ *scan = tolower(*scan);
+ else if (*scan != '-' || scan == start)
+ return GIT_EINVALIDSPEC;
+ }
+
+ if (scan == start)
+ return GIT_EINVALIDSPEC;
+
+ return 0;
+}
+
/* Take something the user gave us and make it nice for our hash function */
static int normalize_name(const char *in, char **out)
{
@@ -117,19 +141,26 @@ static int normalize_name(const char *in, char **out)
fdot = strchr(name, '.');
ldot = strrchr(name, '.');
- if (fdot == NULL || ldot == NULL) {
- git__free(name);
- giterr_set(GITERR_CONFIG,
- "Invalid variable name: '%s'", in);
- return -1;
- }
+ if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
+ goto invalid;
+
+ /* Validate and downcase up to first dot and after last dot */
+ if (git_config_file_normalize_section(name, fdot) < 0 ||
+ git_config_file_normalize_section(ldot + 1, NULL) < 0)
+ goto invalid;
- /* Downcase up to the first dot and after the last one */
- git__strntolower(name, fdot - name);
- git__strtolower(ldot);
+ /* If there is a middle range, make sure it doesn't have newlines */
+ while (fdot < ldot)
+ if (*fdot++ == '\n')
+ goto invalid;
*out = name;
return 0;
+
+invalid:
+ git__free(name);
+ giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
+ return GIT_EINVALIDSPEC;
}
static void free_vars(git_strmap *values)
@@ -149,33 +180,61 @@ static void free_vars(git_strmap *values)
git_strmap_free(values);
}
-static int config_open(git_config_file *cfg)
+static int config_open(git_config_backend *cfg, unsigned int level)
{
int res;
diskfile_backend *b = (diskfile_backend *)cfg;
+ b->level = level;
+
b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values);
git_buf_init(&b->reader.buffer, 0);
- res = git_futils_readbuffer(&b->reader.buffer, b->file_path);
+ res = git_futils_readbuffer_updated(
+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL);
/* It's fine if the file doesn't exist */
if (res == GIT_ENOTFOUND)
return 0;
- if (res < 0 || config_parse(b) < 0) {
+ if (res < 0 || (res = config_parse(b, level)) < 0) {
free_vars(b->values);
b->values = NULL;
- git_buf_free(&b->reader.buffer);
- return -1;
}
git_buf_free(&b->reader.buffer);
- return 0;
+ return res;
}
-static void backend_free(git_config_file *_backend)
+static int config_refresh(git_config_backend *cfg)
+{
+ int res, updated = 0;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ git_strmap *old_values;
+
+ res = git_futils_readbuffer_updated(
+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);
+ if (res < 0 || !updated)
+ return (res == GIT_ENOTFOUND) ? 0 : res;
+
+ /* need to reload - store old values and prep for reload */
+ old_values = b->values;
+ b->values = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(b->values);
+
+ if ((res = config_parse(b, b->level)) < 0) {
+ free_vars(b->values);
+ b->values = old_values;
+ } else {
+ free_vars(old_values);
+ }
+
+ git_buf_free(&b->reader.buffer);
+ return res;
+}
+
+static void backend_free(git_config_backend *_backend)
{
diskfile_backend *backend = (diskfile_backend *)_backend;
@@ -187,37 +246,63 @@ static void backend_free(git_config_file *_backend)
git__free(backend);
}
-static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)
+static int file_foreach(
+ git_config_backend *backend,
+ const char *regexp,
+ int (*fn)(const git_config_entry *, void *),
+ void *data)
{
diskfile_backend *b = (diskfile_backend *)backend;
- cvar_t *var;
+ cvar_t *var, *next_var;
const char *key;
+ regex_t regex;
+ int result = 0;
if (!b->values)
return 0;
+ if (regexp != NULL) {
+ if ((result = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
+ giterr_set_regex(&regex, result);
+ regfree(&regex);
+ return -1;
+ }
+ }
+
git_strmap_foreach(b->values, key, var,
- do {
- if (fn(key, var->value, data) < 0)
- break;
+ for (; var != NULL; var = next_var) {
+ next_var = CVAR_LIST_NEXT(var);
- var = CVAR_LIST_NEXT(var);
- } while (var != NULL);
+ /* skip non-matching keys if regexp was provided */
+ if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
+ continue;
+
+ /* abort iterator on non-zero return value */
+ if (fn(var->entry, data)) {
+ giterr_clear();
+ result = GIT_EUSER;
+ goto cleanup;
+ }
+ }
);
- return 0;
+cleanup:
+ if (regexp != NULL)
+ regfree(&regex);
+
+ return result;
}
-static int config_set(git_config_file *cfg, const char *name, const char *value)
+static int config_set(git_config_backend *cfg, const char *name, const char *value)
{
cvar_t *var = NULL, *old_var;
diskfile_backend *b = (diskfile_backend *)cfg;
- char *key;
+ char *key, *esc_value = NULL;
khiter_t pos;
- int rval;
+ int rval, ret;
- if (normalize_name(name, &key) < 0)
- return -1;
+ if ((rval = normalize_name(name, &key)) < 0)
+ return rval;
/*
* Try to find it in the existing values and update it if it
@@ -229,40 +314,57 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
char *tmp = NULL;
git__free(key);
+
if (existing->next != NULL) {
giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
return -1;
}
+ /* don't update if old and new values already match */
+ if ((!existing->entry->value && !value) ||
+ (existing->entry->value && value && !strcmp(existing->entry->value, value)))
+ return 0;
+
if (value) {
tmp = git__strdup(value);
GITERR_CHECK_ALLOC(tmp);
+ esc_value = escape_value(value);
+ GITERR_CHECK_ALLOC(esc_value);
}
- git__free(existing->value);
- existing->value = tmp;
+ git__free((void *)existing->entry->value);
+ existing->entry->value = tmp;
+
+ ret = config_write(b, existing->entry->name, NULL, esc_value);
- return config_write(b, existing->key, NULL, value);
+ git__free(esc_value);
+ return ret;
}
var = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(var);
-
memset(var, 0x0, sizeof(cvar_t));
+ var->entry = git__malloc(sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(var->entry);
+ memset(var->entry, 0x0, sizeof(git_config_entry));
- var->key = key;
- var->value = NULL;
+ var->entry->name = key;
+ var->entry->value = NULL;
if (value) {
- var->value = git__strdup(value);
- GITERR_CHECK_ALLOC(var->value);
+ var->entry->value = git__strdup(value);
+ GITERR_CHECK_ALLOC(var->entry->value);
+ esc_value = escape_value(value);
+ GITERR_CHECK_ALLOC(esc_value);
}
- if (config_write(b, key, NULL, value) < 0) {
+ if (config_write(b, key, NULL, esc_value) < 0) {
+ git__free(esc_value);
cvar_free(var);
return -1;
}
+ git__free(esc_value);
git_strmap_insert2(b->values, key, var, old_var, rval);
if (rval < 0)
return -1;
@@ -275,14 +377,15 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
/*
* Internal function that actually gets the value in string form
*/
-static int config_get(git_config_file *cfg, const char *name, const char **out)
+static int config_get(const git_config_backend *cfg, const char *name, const git_config_entry **out)
{
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
khiter_t pos;
+ int error;
- if (normalize_name(name, &key) < 0)
- return -1;
+ if ((error = normalize_name(name, &key)) < 0)
+ return error;
pos = git_strmap_lookup_index(b->values, key);
git__free(key);
@@ -291,25 +394,26 @@ static int config_get(git_config_file *cfg, const char *name, const char **out)
if (!git_strmap_valid_index(b->values, pos))
return GIT_ENOTFOUND;
- *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value;
+ *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry;
return 0;
}
static int config_get_multivar(
- git_config_file *cfg,
+ git_config_backend *cfg,
const char *name,
const char *regex_str,
- int (*fn)(const char *, void *),
+ int (*fn)(const git_config_entry *, void *),
void *data)
{
cvar_t *var;
diskfile_backend *b = (diskfile_backend *)cfg;
char *key;
khiter_t pos;
+ int error;
- if (normalize_name(name, &key) < 0)
- return -1;
+ if ((error = normalize_name(name, &key)) < 0)
+ return error;
pos = git_strmap_lookup_index(b->values, key);
git__free(key);
@@ -327,16 +431,17 @@ static int config_get_multivar(
result = regcomp(&regex, regex_str, REG_EXTENDED);
if (result < 0) {
giterr_set_regex(&regex, result);
+ regfree(&regex);
return -1;
}
/* and throw the callback only on the variables that
* match the regex */
do {
- if (regexec(&regex, var->value, 0, NULL, 0) == 0) {
+ if (regexec(&regex, var->entry->value, 0, NULL, 0) == 0) {
/* early termination by the user is not an error;
* just break and return successfully */
- if (fn(var->value, data) < 0)
+ if (fn(var->entry, data) < 0)
break;
}
@@ -348,7 +453,7 @@ static int config_get_multivar(
do {
/* early termination by the user is not an error;
* just break and return successfully */
- if (fn(var->value, data) < 0)
+ if (fn(var->entry, data) < 0)
break;
var = var->next;
@@ -359,7 +464,7 @@ static int config_get_multivar(
}
static int config_set_multivar(
- git_config_file *cfg, const char *name, const char *regexp, const char *value)
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
{
int replaced = 0;
cvar_t *var, *newvar;
@@ -371,8 +476,8 @@ static int config_set_multivar(
assert(regexp);
- if (normalize_name(name, &key) < 0)
- return -1;
+ if ((result = normalize_name(name, &key)) < 0)
+ return result;
pos = git_strmap_lookup_index(b->values, key);
if (!git_strmap_valid_index(b->values, pos)) {
@@ -386,16 +491,17 @@ static int config_set_multivar(
if (result < 0) {
git__free(key);
giterr_set_regex(&preg, result);
+ regfree(&preg);
return -1;
}
for (;;) {
- if (regexec(&preg, var->value, 0, NULL, 0) == 0) {
+ if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) {
char *tmp = git__strdup(value);
GITERR_CHECK_ALLOC(tmp);
- git__free(var->value);
- var->value = tmp;
+ git__free((void *)var->entry->value);
+ var->entry->value = tmp;
replaced = 1;
}
@@ -409,14 +515,18 @@ static int config_set_multivar(
if (!replaced) {
newvar = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(newvar);
-
memset(newvar, 0x0, sizeof(cvar_t));
+ newvar->entry = git__malloc(sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(newvar->entry);
+ memset(newvar->entry, 0x0, sizeof(git_config_entry));
+
+ newvar->entry->name = git__strdup(var->entry->name);
+ GITERR_CHECK_ALLOC(newvar->entry->name);
- newvar->key = git__strdup(var->key);
- GITERR_CHECK_ALLOC(newvar->key);
+ newvar->entry->value = git__strdup(value);
+ GITERR_CHECK_ALLOC(newvar->entry->value);
- newvar->value = git__strdup(value);
- GITERR_CHECK_ALLOC(newvar->value);
+ newvar->entry->level = var->entry->level;
var->next = newvar;
}
@@ -429,7 +539,7 @@ static int config_set_multivar(
return result;
}
-static int config_delete(git_config_file *cfg, const char *name)
+static int config_delete(git_config_backend *cfg, const char *name)
{
cvar_t *var;
diskfile_backend *b = (diskfile_backend *)cfg;
@@ -437,14 +547,16 @@ static int config_delete(git_config_file *cfg, const char *name)
int result;
khiter_t pos;
- if (normalize_name(name, &key) < 0)
- return -1;
+ if ((result = normalize_name(name, &key)) < 0)
+ return result;
pos = git_strmap_lookup_index(b->values, key);
git__free(key);
- if (!git_strmap_valid_index(b->values, pos))
+ if (!git_strmap_valid_index(b->values, pos)) {
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
return GIT_ENOTFOUND;
+ }
var = git_strmap_value_at(b->values, pos);
@@ -455,20 +567,20 @@ static int config_delete(git_config_file *cfg, const char *name)
git_strmap_delete_at(b->values, pos);
- result = config_write(b, var->key, NULL, NULL);
+ result = config_write(b, var->entry->name, NULL, NULL);
cvar_free(var);
return result;
}
-int git_config_file__ondisk(git_config_file **out, const char *path)
+int git_config_file__ondisk(git_config_backend **out, const char *path)
{
diskfile_backend *backend;
- backend = git__malloc(sizeof(diskfile_backend));
+ backend = git__calloc(1, sizeof(diskfile_backend));
GITERR_CHECK_ALLOC(backend);
- memset(backend, 0x0, sizeof(diskfile_backend));
+ backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
backend->file_path = git__strdup(path);
GITERR_CHECK_ALLOC(backend->file_path);
@@ -480,9 +592,10 @@ int git_config_file__ondisk(git_config_file **out, const char *path)
backend->parent.set_multivar = config_set_multivar;
backend->parent.del = config_delete;
backend->parent.foreach = file_foreach;
+ backend->parent.refresh = config_refresh;
backend->parent.free = backend_free;
- *out = (git_config_file *)backend;
+ *out = (git_config_backend *)backend;
return 0;
}
@@ -774,17 +887,14 @@ fail_parse:
static int skip_bom(diskfile_backend *cfg)
{
- static const char utf8_bom[] = "\xef\xbb\xbf";
-
- if (cfg->reader.buffer.size < sizeof(utf8_bom))
- return 0;
+ git_bom_t bom;
+ int bom_offset = git_buf_text_detect_bom(&bom,
+ &cfg->reader.buffer, cfg->reader.read_ptr - cfg->reader.buffer.ptr);
- if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
- cfg->reader.read_ptr += sizeof(utf8_bom);
+ if (bom == GIT_BOM_UTF8)
+ cfg->reader.read_ptr += bom_offset;
- /* TODO: the reference implementation does pretty stupid
- shit with the BoM
- */
+ /* TODO: reference implementation is pretty stupid with BoM */
return 0;
}
@@ -844,7 +954,7 @@ static int strip_comments(char *line, int in_quotes)
}
/* skip any space at the end */
- if (git__isspace(ptr[-1])) {
+ if (ptr > line && git__isspace(ptr[-1])) {
ptr--;
}
ptr[0] = '\0';
@@ -852,7 +962,7 @@ static int strip_comments(char *line, int in_quotes)
return quote_count;
}
-static int config_parse(diskfile_backend *cfg_file)
+static int config_parse(diskfile_backend *cfg_file, unsigned int level)
{
int c;
char *current_section = NULL;
@@ -900,8 +1010,10 @@ static int config_parse(diskfile_backend *cfg_file)
var = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(var);
-
memset(var, 0x0, sizeof(cvar_t));
+ var->entry = git__malloc(sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(var->entry);
+ memset(var->entry, 0x0, sizeof(git_config_entry));
git__strtolower(var_name);
git_buf_printf(&buf, "%s.%s", current_section, var_name);
@@ -910,13 +1022,14 @@ static int config_parse(diskfile_backend *cfg_file)
if (git_buf_oom(&buf))
return -1;
- var->key = git_buf_detach(&buf);
- var->value = var_value;
+ var->entry->name = git_buf_detach(&buf);
+ var->entry->value = var_value;
+ var->entry->level = level;
/* Add or append the new config option */
- pos = git_strmap_lookup_index(cfg_file->values, var->key);
+ pos = git_strmap_lookup_index(cfg_file->values, var->entry->name);
if (!git_strmap_valid_index(cfg_file->values, pos)) {
- git_strmap_insert(cfg_file->values, var->key, var, result);
+ git_strmap_insert(cfg_file->values, var->entry->name, var, result);
if (result < 0)
break;
result = 0;
@@ -948,9 +1061,12 @@ static int write_section(git_filebuf *file, const char *key)
if (dot == NULL) {
git_buf_puts(&buf, key);
} else {
+ char *escaped;
git_buf_put(&buf, key, dot - key);
- /* TODO: escape */
- git_buf_printf(&buf, " \"%s\"", dot + 1);
+ escaped = escape_value(dot + 1);
+ GITERR_CHECK_ALLOC(escaped);
+ git_buf_printf(&buf, " \"%s\"", escaped);
+ git__free(escaped);
}
git_buf_puts(&buf, "]\n");
@@ -1133,6 +1249,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
goto rewrite_fail;
}
+ /* If we are here, there is at least a section line */
+ if (cfg->reader.buffer.size > 0 && *(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n')
+ git_filebuf_write(&file, "\n", 1);
+
git_filebuf_printf(&file, "\t%s = %s\n", name, value);
}
}
@@ -1140,8 +1260,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git__free(section);
git__free(current_section);
+ /* refresh stats - if this errors, then commit will error too */
+ (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);
+
result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
git_buf_free(&cfg->reader.buffer);
+
return result;
rewrite_fail:
@@ -1153,13 +1277,44 @@ rewrite_fail:
return -1;
}
+static const char *escapes = "ntb\"\\";
+static const char *escaped = "\n\t\b\"\\";
+
+/* Escape the values to write them to the file */
+static char *escape_value(const char *ptr)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t len;
+ const char *esc;
+
+ assert(ptr);
+
+ len = strlen(ptr);
+ git_buf_grow(&buf, len);
+
+ while (*ptr != '\0') {
+ if ((esc = strchr(escaped, *ptr)) != NULL) {
+ git_buf_putc(&buf, '\\');
+ git_buf_putc(&buf, escapes[esc - escaped]);
+ } else {
+ git_buf_putc(&buf, *ptr);
+ }
+ ptr++;
+ }
+
+ if (git_buf_oom(&buf)) {
+ git_buf_free(&buf);
+ return NULL;
+ }
+
+ return git_buf_detach(&buf);
+}
+
/* '\"' -> '"' etc */
static char *fixup_line(const char *ptr, int quote_count)
{
char *str = git__malloc(strlen(ptr) + 1);
char *out = str, *esc;
- const char *escapes = "ntb\"\\";
- const char *escaped = "\n\t\b\"\\";
if (str == NULL)
return NULL;
@@ -1196,8 +1351,15 @@ out:
static int is_multiline_var(const char *str)
{
+ int count = 0;
const char *end = str + strlen(str);
- return (end > str) && (end[-1] == '\\');
+ while (end > str && end[-1] == '\\') {
+ count++;
+ end--;
+ }
+
+ /* An odd number means last backslash wasn't escaped, so it's multiline */
+ return (end > str) && (count & 1);
}
static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes)
@@ -1272,10 +1434,8 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
else
value_start = var_end + 1;
- if (git__isspace(var_end[-1])) {
- do var_end--;
- while (git__isspace(var_end[0]));
- }
+ do var_end--;
+ while (var_end>line && git__isspace(*var_end));
*var_name = git__strndup(line, var_end - line + 1);
GITERR_CHECK_ALLOC(*var_name);
@@ -1309,8 +1469,10 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
else if (value_start[0] != '\0') {
*var_value = fixup_line(value_start, 0);
GITERR_CHECK_ALLOC(*var_value);
+ } else { /* equals sign but missing rhs */
+ *var_value = git__strdup("");
+ GITERR_CHECK_ALLOC(*var_value);
}
-
}
git__free(line);
diff --git a/src/config_file.h b/src/config_file.h
index 0080b5713..7445859c4 100644
--- a/src/config_file.h
+++ b/src/config_file.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * 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.
@@ -9,23 +9,52 @@
#include "git2/config.h"
-GIT_INLINE(int) git_config_file_open(git_config_file *cfg)
+GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level)
{
- return cfg->open(cfg);
+ return cfg->open(cfg, level);
}
-GIT_INLINE(void) git_config_file_free(git_config_file *cfg)
+GIT_INLINE(void) git_config_file_free(git_config_backend *cfg)
{
cfg->free(cfg);
}
+GIT_INLINE(int) git_config_file_get_string(
+ const git_config_entry **out, git_config_backend *cfg, const char *name)
+{
+ return cfg->get(cfg, name, out);
+}
+
+GIT_INLINE(int) git_config_file_set_string(
+ git_config_backend *cfg, const char *name, const char *value)
+{
+ return cfg->set(cfg, name, value);
+}
+
+GIT_INLINE(int) git_config_file_delete(
+ git_config_backend *cfg, const char *name)
+{
+ return cfg->del(cfg, name);
+}
+
GIT_INLINE(int) git_config_file_foreach(
- git_config_file *cfg,
- int (*fn)(const char *key, const char *value, void *data),
+ git_config_backend *cfg,
+ int (*fn)(const git_config_entry *entry, void *data),
void *data)
{
- return cfg->foreach(cfg, fn, data);
+ return cfg->foreach(cfg, NULL, fn, data);
}
+GIT_INLINE(int) git_config_file_foreach_match(
+ git_config_backend *cfg,
+ const char *regexp,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return cfg->foreach(cfg, regexp, fn, data);
+}
+
+extern int git_config_file_normalize_section(char *start, char *end);
+
#endif
diff --git a/src/crlf.c b/src/crlf.c
index 303a46d3b..81268da83 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -9,9 +9,10 @@
#include "fileops.h"
#include "hash.h"
#include "filter.h"
+#include "buf_text.h"
#include "repository.h"
-
#include "git2/attr.h"
+#include "git2/blob.h"
struct crlf_attrs {
int crlf_action;
@@ -21,6 +22,8 @@ struct crlf_attrs {
struct crlf_filter {
git_filter f;
struct crlf_attrs attrs;
+ git_repository *repo;
+ char path[GIT_FLEX_ARRAY];
};
static int check_crlf(const char *value)
@@ -103,36 +106,46 @@ static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, con
return -1;
}
-static int drop_crlf(git_buf *dest, const git_buf *source)
+static int has_cr_in_index(git_filter *self)
{
- const char *scan = source->ptr, *next;
- const char *scan_end = git_buf_cstr(source) + git_buf_len(source);
+ struct crlf_filter *filter = (struct crlf_filter *)self;
+ git_index *index;
+ const git_index_entry *entry;
+ git_blob *blob;
+ const void *blobcontent;
+ git_off_t blobsize;
+ bool found_cr;
+
+ if (git_repository_index__weakptr(&index, filter->repo) < 0) {
+ giterr_clear();
+ return false;
+ }
- /* Main scan loop. Find the next carriage return and copy the
- * whole chunk up to that point to the destination buffer.
- */
- while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) {
- /* copy input up to \r */
- if (next > scan)
- git_buf_put(dest, scan, next - scan);
+ if (!(entry = git_index_get_bypath(index, filter->path, 0)) &&
+ !(entry = git_index_get_bypath(index, filter->path, 1)))
+ return false;
- /* Do not drop \r unless it is followed by \n */
- if (*(next + 1) != '\n')
- git_buf_putc(dest, '\r');
+ if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
+ return true;
- scan = next + 1;
- }
+ if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0)
+ return false;
- /* If there was no \r, then tell the library to skip this filter */
- if (scan == source->ptr)
- return -1;
+ blobcontent = git_blob_rawcontent(blob);
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize))
+ blobsize = (size_t)-1;
- /* Copy remaining input into dest */
- git_buf_put(dest, scan, scan_end - scan);
- return 0;
+ found_cr = (blobcontent != NULL &&
+ blobsize > 0 &&
+ memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
+
+ git_blob_free(blob);
+ return found_cr;
}
-static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source)
+static int crlf_apply_to_odb(
+ git_filter *self, git_buf *dest, const git_buf *source)
{
struct crlf_filter *filter = (struct crlf_filter *)self;
@@ -148,8 +161,11 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou
if (filter->attrs.crlf_action == GIT_CRLF_AUTO ||
filter->attrs.crlf_action == GIT_CRLF_GUESS) {
- git_text_stats stats;
- git_text_gather_stats(&stats, source);
+ git_buf_text_stats stats;
+
+ /* Check heuristics for binary vs text... */
+ if (git_buf_text_gather_stats(&stats, source, false))
+ return -1;
/*
* We're currently not going to even try to convert stuff
@@ -159,35 +175,94 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou
if (stats.cr != stats.crlf)
return -1;
- /*
- * And add some heuristics for binary vs text, of course...
- */
- if (git_text_is_binary(&stats))
- return -1;
-
-#if 0
- if (crlf_action == CRLF_GUESS) {
+ if (filter->attrs.crlf_action == GIT_CRLF_GUESS) {
/*
* If the file in the index has any CR in it, do not convert.
* This is the new safer autocrlf handling.
*/
- if (has_cr_in_index(path))
- return 0;
+ if (has_cr_in_index(self))
+ return -1;
}
-#endif
if (!stats.cr)
return -1;
}
/* Actually drop the carriage returns */
- return drop_crlf(dest, source);
+ return git_buf_text_crlf_to_lf(dest, source);
+}
+
+static const char *line_ending(struct crlf_filter *filter)
+{
+ switch (filter->attrs.crlf_action) {
+ case GIT_CRLF_BINARY:
+ case GIT_CRLF_INPUT:
+ return "\n";
+
+ case GIT_CRLF_CRLF:
+ return "\r\n";
+
+ case GIT_CRLF_AUTO:
+ case GIT_CRLF_TEXT:
+ case GIT_CRLF_GUESS:
+ break;
+
+ default:
+ goto line_ending_error;
+ }
+
+ switch (filter->attrs.eol) {
+ case GIT_EOL_UNSET:
+ return GIT_EOL_NATIVE == GIT_EOL_CRLF
+ ? "\r\n"
+ : "\n";
+
+ case GIT_EOL_CRLF:
+ return "\r\n";
+
+ case GIT_EOL_LF:
+ return "\n";
+
+ default:
+ goto line_ending_error;
+ }
+
+line_ending_error:
+ giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
+ return NULL;
+}
+
+static int crlf_apply_to_workdir(
+ git_filter *self, git_buf *dest, const git_buf *source)
+{
+ struct crlf_filter *filter = (struct crlf_filter *)self;
+ const char *workdir_ending = NULL;
+
+ assert(self && dest && source);
+
+ /* Empty file? Nothing to do. */
+ if (git_buf_len(source) == 0)
+ return -1;
+
+ /* Determine proper line ending */
+ workdir_ending = line_ending(filter);
+ if (!workdir_ending)
+ return -1;
+ if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */
+ return -1;
+
+ /* for now, only lf->crlf conversion is supported here */
+ assert(!strcmp("\r\n", workdir_ending));
+ return git_buf_text_lf_to_crlf(dest, source);
}
-int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
+static int find_and_add_filter(
+ git_vector *filters, git_repository *repo, const char *path,
+ int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source))
{
struct crlf_attrs ca;
struct crlf_filter *filter;
+ size_t pathlen;
int error;
/* Load gitattributes for the path */
@@ -196,7 +271,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const
/*
* Use the core Git logic to see if we should perform CRLF for this file
- * based on its attributes & the value of `core.auto_crlf`
+ * based on its attributes & the value of `core.autocrlf`
*/
ca.crlf_action = crlf_input_action(&ca);
@@ -206,8 +281,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const
if (ca.crlf_action == GIT_CRLF_GUESS) {
int auto_crlf;
- if ((error = git_repository__cvar(
- &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
+ if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
return error;
if (auto_crlf == GIT_AUTO_CRLF_FALSE)
@@ -216,13 +290,27 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const
/* If we're good, we create a new filter object and push it
* into the filters array */
- filter = git__malloc(sizeof(struct crlf_filter));
+ pathlen = strlen(path);
+ filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1);
GITERR_CHECK_ALLOC(filter);
- filter->f.apply = &crlf_apply_to_odb;
+ filter->f.apply = apply;
filter->f.do_free = NULL;
memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs));
+ filter->repo = repo;
+ memcpy(filter->path, path, pathlen + 1);
return git_vector_insert(filters, filter);
}
+int git_filter_add__crlf_to_odb(
+ git_vector *filters, git_repository *repo, const char *path)
+{
+ return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);
+}
+
+int git_filter_add__crlf_to_workdir(
+ git_vector *filters, git_repository *repo, const char *path)
+{
+ return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
+}
diff --git a/src/date.c b/src/date.c
new file mode 100644
index 000000000..ce1721a0b
--- /dev/null
+++ b/src/date.c
@@ -0,0 +1,876 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "common.h"
+
+#ifndef GIT_WIN32
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+#include "cache.h"
+#include "posix.h"
+
+#include <ctype.h>
+#include <time.h>
+
+typedef enum {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822,
+ DATE_RAW
+} date_mode;
+
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+static git_time_t tm_to_time_t(const struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+ return -1;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+ { "Z", 0, 0, }, /* Zulu, alias for UTC */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast */
+ { "JST", +9, 0, }, /* Japan Standard */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+static size_t match_string(const char *date, const char *str)
+{
+ size_t i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static size_t match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ unsigned int i;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+ size_t match = match_string(date, timezone_names[i].name);
+ if (match >= 3 || match == strlen(timezone_names[i].name)) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
+ return 2;
+ }
+
+ /* BAD */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ struct tm check = *tm;
+ struct tm *r = (now_tm ? &check : tm);
+ time_t specified;
+
+ r->tm_mon = month - 1;
+ r->tm_mday = day;
+ if (year == -1) {
+ if (!now_tm)
+ return 1;
+ r->tm_year = now_tm->tm_year;
+ }
+ else if (year >= 1970 && year < 2100)
+ r->tm_year = year - 1900;
+ else if (year > 70 && year < 100)
+ r->tm_year = year;
+ else if (year < 38)
+ r->tm_year = year + 100;
+ else
+ return 0;
+ if (!now_tm)
+ return 1;
+
+ specified = tm_to_time_t(r);
+
+ /* Be it commit time or author time, it does not make
+ * sense to specify timestamp way into the future. Make
+ * sure it is not later than ten days from now...
+ */
+ if (now + 10*24*3600 < specified)
+ return 0;
+ tm->tm_mon = r->tm_mon;
+ tm->tm_mday = r->tm_mday;
+ if (year != -1)
+ tm->tm_year = r->tm_year;
+ return 1;
+ }
+ return 0;
+}
+
+static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ time_t now;
+ struct tm now_tm;
+ struct tm *refuse_future;
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ case '.':
+ now = time(NULL);
+ refuse_future = NULL;
+ if (p_gmtime_r(&now, &now_tm))
+ refuse_future = &now_tm;
+
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, refuse_future, now, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, refuse_future, now, tm))
+ break;
+ }
+ /* Our eastern European friends say dd.mm.yy[yy]
+ * is the norm there, so giving precedence to
+ * mm/dd/yy[yy] form only when separator is not '.'
+ */
+ if (c != '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+ if (is_date(num3, num2, num, refuse_future, now, tm))
+ break;
+ /* Funny European mm.dd.yy */
+ if (c == '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
+static int nodate(struct tm *tm)
+{
+ return (tm->tm_year &
+ tm->tm_mon &
+ tm->tm_mday &
+ tm->tm_hour &
+ tm->tm_min &
+ tm->tm_sec) < 0;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ size_t n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for any numbers with
+ * more than 8 digits. This is because we don't want to rule out
+ * numbers like 20070606 as a YYYYMMDD date.
+ */
+ if (num >= 100000000 && nodate(tm)) {
+ time_t time = num;
+ if (p_gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[-.:/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1400 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numbers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 13 && tm->tm_mon < 0)
+ tm->tm_mon = num-1;
+
+ return n;
+}
+
+static size_t match_tz(const char *date, int *offp)
+{
+ char *end;
+ int hour = strtoul(date + 1, &end, 10);
+ size_t n = end - (date + 1);
+ int min = 0;
+
+ if (n == 4) {
+ /* hhmm */
+ min = hour % 100;
+ hour = hour / 100;
+ } else if (n != 2) {
+ min = 99; /* random stuff */
+ } else if (*end == ':') {
+ /* hh:mm? */
+ min = strtoul(end + 1, &end, 10);
+ if (end - (date + 1) != 5)
+ min = 99; /* random stuff */
+ } /* otherwise we parsed "hh" */
+
+ /*
+ * Don't accept any random stuff. Even though some places have
+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+ * UTC+14), there is something wrong if hour part is much
+ * larger than that. We might also want to check that the
+ * minutes are divisible by 15 or something too. (Offset of
+ * Kathmandu, Nepal is UTC+5:45)
+ */
+ if (min < 60 && hour < 24) {
+ int offset = hour * 60 + min;
+ if (*date == '-')
+ offset = -offset;
+ *offp = offset;
+ }
+ return end - date;
+}
+
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
+{
+ struct tm tm;
+ int tm_gmt;
+ git_time_t dummy_timestamp;
+ int dummy_offset;
+
+ if (!timestamp)
+ timestamp = &dummy_timestamp;
+ if (!offset)
+ offset = &dummy_offset;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ tm.tm_hour = -1;
+ tm.tm_min = -1;
+ tm.tm_sec = -1;
+ *offset = -1;
+ tm_gmt = 0;
+
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
+ for (;;) {
+ size_t match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, offset);
+
+ if (!match) {
+ /* BAD */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ *timestamp = tm_to_time_t(&tm);
+ if (*offset == -1)
+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
+
+ if (*timestamp == (git_time_t)-1)
+ return -1;
+
+ if (!tm_gmt)
+ *timestamp -= *offset * 60;
+ return 0; /* success */
+}
+
+
+/*
+ * Relative time update (eg "2 days ago"). If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
+{
+ time_t n;
+
+ if (tm->tm_mday < 0)
+ tm->tm_mday = now->tm_mday;
+ if (tm->tm_mon < 0)
+ tm->tm_mon = now->tm_mon;
+ if (tm->tm_year < 0) {
+ tm->tm_year = now->tm_year;
+ if (tm->tm_mon > now->tm_mon)
+ tm->tm_year--;
+ }
+
+ n = mktime(tm) - sec;
+ p_localtime_r(&n, tm);
+ return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 0);
+}
+
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 24*60*60);
+}
+
+static void date_time(struct tm *tm, struct tm *now, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, now, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 0);
+}
+
+static void date_noon(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 12);
+}
+
+static void date_tea(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 17);
+}
+
+static void date_pm(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, struct tm *now, int *num)
+{
+ time_t n = 0;
+ GIT_UNUSED(now);
+ GIT_UNUSED(num);
+ p_localtime_r(&n, tm);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
+ { "now", date_now },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int i;
+
+ while (isalpha(*++end))
+ /* scan to non-alpha */;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ *touched = 1;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ size_t len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, now, num);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ size_t len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ *touched = 1;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4) {
+ *num = 1;
+ *touched = 1;
+ }
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ size_t len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, now, tl->length * *num);
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, now, diff * 24 * 60 * 60);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n;
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ *touched = 1;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ tm->tm_year -= *num;
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+
+ return end;
+}
+
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
+ return end;
+}
+
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+ int number = *num;
+
+ if (number) {
+ *num = 0;
+ if (tm->tm_mday < 0 && number < 32)
+ tm->tm_mday = number;
+ else if (tm->tm_mon < 0 && number < 13)
+ tm->tm_mon = number-1;
+ else if (tm->tm_year < 0) {
+ if (number > 1969 && number < 2100)
+ tm->tm_year = number - 1900;
+ else if (number > 69 && number < 100)
+ tm->tm_year = number;
+ else if (number < 38)
+ tm->tm_year = 100 + number;
+ /* We mess up for number = 00 ? */
+ }
+ }
+}
+
+static git_time_t approxidate_str(const char *date,
+ const struct timeval *tv,
+ int *error_ret)
+{
+ int number = 0;
+ int touched = 0;
+ struct tm tm = {0}, now;
+ time_t time_sec;
+
+ time_sec = tv->tv_sec;
+ p_localtime_r(&time_sec, &tm);
+ now = tm;
+
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ pending_number(&tm, &number);
+ date = approxidate_digit(date-1, &tm, &number);
+ touched = 1;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
+ }
+ pending_number(&tm, &number);
+ if (!touched)
+ *error_ret = 1;
+ return update_tm(&tm, &now, 0);
+}
+
+int git__date_parse(git_time_t *out, const char *date)
+{
+ struct timeval tv;
+ git_time_t timestamp;
+ int offset, error_ret=0;
+
+ if (!parse_date_basic(date, &timestamp, &offset)) {
+ *out = timestamp;
+ return 0;
+ }
+
+ p_gettimeofday(&tv, NULL);
+ *out = approxidate_str(date, &tv, &error_ret);
+ return error_ret;
+}
diff --git a/src/delta-apply.c b/src/delta-apply.c
index 815ca8f16..a39c7af5c 100644
--- a/src/delta-apply.c
+++ b/src/delta-apply.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -36,6 +36,19 @@ static int hdr_sz(
return 0;
}
+int git__delta_read_header(
+ const unsigned char *delta,
+ size_t delta_len,
+ size_t *base_sz,
+ size_t *res_sz)
+{
+ const unsigned char *delta_end = delta + delta_len;
+ if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
+ (hdr_sz(res_sz, &delta, delta_end) < 0))
+ return -1;
+ return 0;
+}
+
int git__delta_apply(
git_rawobj *out,
const unsigned char *base,
diff --git a/src/delta-apply.h b/src/delta-apply.h
index 66fa76d43..d7d99d04c 100644
--- a/src/delta-apply.h
+++ b/src/delta-apply.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -30,4 +30,21 @@ extern int git__delta_apply(
const unsigned char *delta,
size_t delta_len);
+/**
+ * Read the header of a git binary delta.
+ *
+ * @param delta the delta to execute copy/insert instructions from.
+ * @param delta_len total number of bytes in the delta.
+ * @param base_sz pointer to store the base size field.
+ * @param res_sz pointer to store the result size field.
+ * @return
+ * - 0 on a successful decoding the header.
+ * - GIT_ERROR if the delta is corrupt.
+ */
+extern int git__delta_read_header(
+ const unsigned char *delta,
+ size_t delta_len,
+ size_t *base_sz,
+ size_t *res_sz);
+
#endif
diff --git a/src/delta.c b/src/delta.c
new file mode 100644
index 000000000..3252dbf14
--- /dev/null
+++ b/src/delta.c
@@ -0,0 +1,424 @@
+/*
+ * 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 "delta.h"
+
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+ 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+ 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+ 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+ 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+ 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+ 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+ 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+ 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+ 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+ 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+ 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+ 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+ 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+ 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+ 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+ 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+ 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+ 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+ 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+ 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+ 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+ 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+ 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+ 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+ 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+ 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+ 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+ 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+ 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+ 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+ 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+ 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+ 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+ 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+ 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+ 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+ 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+ 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+ 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+ 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+ 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+ 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+ 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
+
+static const unsigned int U[256] = {
+ 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+ 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+ 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+ 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+ 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+ 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+ 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+ 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+ 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+ 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+ 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+ 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+ 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+ 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+ 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+ 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+ 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+ 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+ 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+ 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+ 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+ 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+ 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+ 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+ 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+ 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+ 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+ 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+ 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+ 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+ 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+ 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+ 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+ 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+ 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+ 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+ 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+ 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+ 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+ 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+ 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+ 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+ 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
+
+struct index_entry {
+ const unsigned char *ptr;
+ unsigned int val;
+ struct index_entry *next;
+};
+
+struct git_delta_index {
+ unsigned long memsize;
+ const void *src_buf;
+ unsigned long src_size;
+ unsigned int hash_mask;
+ struct index_entry *hash[GIT_FLEX_ARRAY];
+};
+
+struct git_delta_index *
+git_delta_create_index(const void *buf, unsigned long bufsize)
+{
+ unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+ const unsigned char *data, *buffer = buf;
+ struct git_delta_index *index;
+ struct index_entry *entry, **hash;
+ void *mem;
+ unsigned long memsize;
+
+ if (!buf || !bufsize)
+ return NULL;
+
+ /* Determine index hash size. Note that indexing skips the
+ first byte to allow for optimizing the rabin polynomial
+ initialization in create_delta(). */
+ entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW;
+ if (bufsize >= 0xffffffffUL) {
+ /*
+ * Current delta format can't encode offsets into
+ * reference buffer with more than 32 bits.
+ */
+ entries = 0xfffffffeU / RABIN_WINDOW;
+ }
+ hsize = entries / 4;
+ for (i = 4; (1u << i) < hsize && i < 31; i++);
+ hsize = 1 << i;
+ hmask = hsize - 1;
+
+ /* allocate lookup index */
+ memsize = sizeof(*index) +
+ sizeof(*hash) * hsize +
+ sizeof(*entry) * entries;
+ mem = git__malloc(memsize);
+ if (!mem)
+ return NULL;
+ index = mem;
+ mem = index->hash;
+ hash = mem;
+ mem = hash + hsize;
+ entry = mem;
+
+ index->memsize = memsize;
+ index->src_buf = buf;
+ index->src_size = bufsize;
+ index->hash_mask = hmask;
+ memset(hash, 0, hsize * sizeof(*hash));
+
+ /* allocate an array to count hash entries */
+ hash_count = calloc(hsize, sizeof(*hash_count));
+ if (!hash_count) {
+ git__free(index);
+ return NULL;
+ }
+
+ /* then populate the index */
+ prev_val = ~0;
+ for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+ data >= buffer;
+ data -= RABIN_WINDOW) {
+ unsigned int val = 0;
+ for (i = 1; i <= RABIN_WINDOW; i++)
+ val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+ if (val == prev_val) {
+ /* keep the lowest of consecutive identical blocks */
+ entry[-1].ptr = data + RABIN_WINDOW;
+ } else {
+ prev_val = val;
+ i = val & hmask;
+ entry->ptr = data + RABIN_WINDOW;
+ entry->val = val;
+ entry->next = hash[i];
+ hash[i] = entry++;
+ hash_count[i]++;
+ }
+ }
+
+ /*
+ * Determine a limit on the number of entries in the same hash
+ * bucket. This guard us against patological data sets causing
+ * really bad hash distribution with most entries in the same hash
+ * bucket that would bring us to O(m*n) computing costs (m and n
+ * corresponding to reference and target buffer sizes).
+ *
+ * Make sure none of the hash buckets has more entries than
+ * we're willing to test. Otherwise we cull the entry list
+ * uniformly to still preserve a good repartition across
+ * the reference buffer.
+ */
+ for (i = 0; i < hsize; i++) {
+ if (hash_count[i] < HASH_LIMIT)
+ continue;
+
+ entry = hash[i];
+ do {
+ struct index_entry *keep = entry;
+ int skip = hash_count[i] / HASH_LIMIT / 2;
+ do {
+ entry = entry->next;
+ } while(--skip && entry);
+ keep->next = entry;
+ } while (entry);
+ }
+ git__free(hash_count);
+
+ return index;
+}
+
+void git_delta_free_index(struct git_delta_index *index)
+{
+ git__free(index);
+}
+
+unsigned long git_delta_sizeof_index(struct git_delta_index *index)
+{
+ if (index)
+ return index->memsize;
+ else
+ return 0;
+}
+
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
+
+void *
+git_delta_create(
+ const struct git_delta_index *index,
+ const void *trg_buf,
+ unsigned long trg_size,
+ unsigned long *delta_size,
+ unsigned long max_size)
+{
+ unsigned int i, outpos, outsize, moff, msize, val;
+ int inscnt;
+ const unsigned char *ref_data, *ref_top, *data, *top;
+ unsigned char *out;
+
+ if (!trg_buf || !trg_size)
+ return NULL;
+
+ outpos = 0;
+ outsize = 8192;
+ if (max_size && outsize >= max_size)
+ outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
+ out = git__malloc(outsize);
+ if (!out)
+ return NULL;
+
+ /* store reference buffer size */
+ i = index->src_size;
+ while (i >= 0x80) {
+ out[outpos++] = i | 0x80;
+ i >>= 7;
+ }
+ out[outpos++] = i;
+
+ /* store target buffer size */
+ i = trg_size;
+ while (i >= 0x80) {
+ out[outpos++] = i | 0x80;
+ i >>= 7;
+ }
+ out[outpos++] = i;
+
+ ref_data = index->src_buf;
+ ref_top = ref_data + index->src_size;
+ data = trg_buf;
+ top = (const unsigned char *) trg_buf + trg_size;
+
+ outpos++;
+ val = 0;
+ for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+ out[outpos++] = *data;
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ }
+ inscnt = i;
+
+ moff = 0;
+ msize = 0;
+ while (data < top) {
+ if (msize < 4096) {
+ struct index_entry *entry;
+ val ^= U[data[-RABIN_WINDOW]];
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ i = val & index->hash_mask;
+ for (entry = index->hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = (unsigned int)(ref_top - ref);
+ if (entry->val != val)
+ continue;
+ if (ref_size > (unsigned int)(top - src))
+ ref_size = (unsigned int)(top - src);
+ if (ref_size <= msize)
+ break;
+ while (ref_size-- && *src++ == *ref)
+ ref++;
+ if (msize < (unsigned int)(ref - entry->ptr)) {
+ /* this is our best match so far */
+ msize = (unsigned int)(ref - entry->ptr);
+ moff = (unsigned int)(entry->ptr - ref_data);
+ if (msize >= 4096) /* good enough */
+ break;
+ }
+ }
+ }
+
+ if (msize < 4) {
+ if (!inscnt)
+ outpos++;
+ out[outpos++] = *data++;
+ inscnt++;
+ if (inscnt == 0x7f) {
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+ msize = 0;
+ } else {
+ unsigned int left;
+ unsigned char *op;
+
+ if (inscnt) {
+ while (moff && ref_data[moff-1] == data[-1]) {
+ /* we can match one byte back */
+ msize++;
+ moff--;
+ data--;
+ outpos--;
+ if (--inscnt)
+ continue;
+ outpos--; /* remove count slot */
+ inscnt--; /* make it -1 */
+ break;
+ }
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+
+ /* A copy op is currently limited to 64KB (pack v2) */
+ left = (msize < 0x10000) ? 0 : (msize - 0x10000);
+ msize -= left;
+
+ op = out + outpos++;
+ i = 0x80;
+
+ if (moff & 0x000000ff)
+ out[outpos++] = moff >> 0, i |= 0x01;
+ if (moff & 0x0000ff00)
+ out[outpos++] = moff >> 8, i |= 0x02;
+ if (moff & 0x00ff0000)
+ out[outpos++] = moff >> 16, i |= 0x04;
+ if (moff & 0xff000000)
+ out[outpos++] = moff >> 24, i |= 0x08;
+
+ if (msize & 0x00ff)
+ out[outpos++] = msize >> 0, i |= 0x10;
+ if (msize & 0xff00)
+ out[outpos++] = msize >> 8, i |= 0x20;
+
+ *op = i;
+
+ data += msize;
+ moff += msize;
+ msize = left;
+
+ if (msize < 4096) {
+ int j;
+ val = 0;
+ for (j = -RABIN_WINDOW; j < 0; j++)
+ val = ((val << 8) | data[j])
+ ^ T[val >> RABIN_SHIFT];
+ }
+ }
+
+ if (outpos >= outsize - MAX_OP_SIZE) {
+ void *tmp = out;
+ outsize = outsize * 3 / 2;
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && outpos > max_size)
+ break;
+ out = git__realloc(out, outsize);
+ if (!out) {
+ git__free(tmp);
+ return NULL;
+ }
+ }
+ }
+
+ if (inscnt)
+ out[outpos - inscnt - 1] = inscnt;
+
+ if (max_size && outpos > max_size) {
+ git__free(out);
+ return NULL;
+ }
+
+ *delta_size = outpos;
+ return out;
+}
diff --git a/src/delta.h b/src/delta.h
new file mode 100644
index 000000000..4ca327992
--- /dev/null
+++ b/src/delta.h
@@ -0,0 +1,114 @@
+/*
+ * diff-delta code taken from git.git. See diff-delta.c for details.
+ *
+ */
+#ifndef INCLUDE_git_delta_h__
+#define INCLUDE_git_delta_h__
+
+#include "common.h"
+
+/* opaque object for delta index */
+struct git_delta_index;
+
+/*
+ * create_delta_index: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
+ * is returned on failure. The given buffer must not be freed nor altered
+ * before free_delta_index() is called. The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern struct git_delta_index *
+git_delta_create_index(const void *buf, unsigned long bufsize);
+
+/*
+ * free_delta_index: free the index created by create_delta_index()
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern void git_delta_free_index(struct git_delta_index *index);
+
+/*
+ * sizeof_delta_index: returns memory usage of delta index
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer. If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size. The returned buffer
+ * must be freed by the caller.
+ */
+extern void *git_delta_create(
+ const struct git_delta_index *index,
+ const void *buf,
+ unsigned long bufsize,
+ unsigned long *delta_size,
+ unsigned long max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then NULL is returned. On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size. The returned buffer must be freed by the caller.
+ */
+GIT_INLINE(void *) git_delta(
+ const void *src_buf, unsigned long src_bufsize,
+ const void *trg_buf, unsigned long trg_bufsize,
+ unsigned long *delta_size,
+ unsigned long max_delta_size)
+{
+ struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize);
+ if (index) {
+ void *delta = git_delta_create(
+ index, trg_buf, trg_bufsize, delta_size, max_delta_size);
+ git_delta_free_index(index);
+ return delta;
+ }
+ return NULL;
+}
+
+/*
+ * patch_delta: recreate target buffer given source buffer and delta data
+ *
+ * On success, a non-NULL pointer to the target buffer is returned and
+ * *trg_bufsize is updated with its size. On failure a NULL pointer is
+ * returned. The returned buffer must be freed by the caller.
+ */
+extern void *git_delta_patch(
+ const void *src_buf, unsigned long src_size,
+ const void *delta_buf, unsigned long delta_size,
+ unsigned long *dst_size);
+
+/* the smallest possible delta size is 4 bytes */
+#define GIT_DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected source buffer size, and again to get the target buffer size.
+ */
+GIT_INLINE(unsigned long) git_delta_get_hdr_size(
+ const unsigned char **datap, const unsigned char *top)
+{
+ const unsigned char *data = *datap;
+ unsigned long cmd, size = 0;
+ int i = 0;
+ do {
+ cmd = *data++;
+ size |= (cmd & 0x7f) << i;
+ i += 7;
+ } while (cmd & 0x80 && data < top);
+ *datap = data;
+ return size;
+}
+
+#endif
diff --git a/src/diff.c b/src/diff.c
index 0b2f8fb50..37c89f3f1 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1,74 +1,19 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/diff.h"
#include "diff.h"
#include "fileops.h"
#include "config.h"
#include "attr_file.h"
+#include "filter.h"
+#include "pathspec.h"
-static char *diff_prefix_from_pathspec(const git_strarray *pathspec)
-{
- git_buf prefix = GIT_BUF_INIT;
- const char *scan;
-
- if (git_buf_common_prefix(&prefix, pathspec) < 0)
- return NULL;
-
- /* diff prefix will only be leading non-wildcards */
- for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan);
- git_buf_truncate(&prefix, scan - prefix.ptr);
-
- if (prefix.size > 0)
- return git_buf_detach(&prefix);
-
- git_buf_free(&prefix);
- return NULL;
-}
-
-static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
-{
- const char *str;
-
- if (pathspec == NULL || pathspec->count == 0)
- return false;
- if (pathspec->count > 1)
- return true;
-
- str = pathspec->strings[0];
- if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
- return false;
- return true;
-}
-
-static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
-{
- unsigned int i;
- git_attr_fnmatch *match;
-
- if (!diff->pathspec.length)
- return true;
-
- git_vector_foreach(&diff->pathspec, i, match) {
- int result = p_fnmatch(match->pattern, path, 0);
-
- /* if we didn't match, look for exact dirname prefix match */
- if (result == FNM_NOMATCH &&
- (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
- strncmp(path, match->pattern, match->length) == 0 &&
- path[match->length] == '/')
- result = 0;
-
- if (result == 0)
- return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
- }
-
- return false;
-}
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
@@ -87,7 +32,7 @@ static git_diff_delta *diff_delta__alloc(
delta->new_file.path = delta->old_file.path;
- if (diff->opts.flags & GIT_DIFF_REVERSE) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
switch (status) {
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
@@ -99,70 +44,16 @@ static git_diff_delta *diff_delta__alloc(
return delta;
}
-static git_diff_delta *diff_delta__dup(
- const git_diff_delta *d, git_pool *pool)
-{
- git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
- if (!delta)
- return NULL;
-
- memcpy(delta, d, sizeof(git_diff_delta));
-
- delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
- if (delta->old_file.path == NULL)
- goto fail;
-
- if (d->new_file.path != d->old_file.path) {
- delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
- if (delta->new_file.path == NULL)
- goto fail;
- } else {
- delta->new_file.path = delta->old_file.path;
- }
-
- return delta;
-
-fail:
- git__free(delta);
- return NULL;
-}
-
-static git_diff_delta *diff_delta__merge_like_cgit(
- const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
+static int diff_notify(
+ const git_diff_list *diff,
+ const git_diff_delta *delta,
+ const char *matched_pathspec)
{
- git_diff_delta *dup = diff_delta__dup(a, pool);
- if (!dup)
- return NULL;
-
- if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)
- return dup;
-
- git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);
-
- dup->new_file.mode = b->new_file.mode;
- dup->new_file.size = b->new_file.size;
- dup->new_file.flags = b->new_file.flags;
-
- /* Emulate C git for merging two diffs (a la 'git diff <sha>').
- *
- * When C git does a diff between the work dir and a tree, it actually
- * diffs with the index but uses the workdir contents. This emulates
- * those choices so we can emulate the type of diff.
- */
- if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {
- if (dup->status == GIT_DELTA_DELETED)
- /* preserve pending delete info */;
- else if (b->status == GIT_DELTA_UNTRACKED ||
- b->status == GIT_DELTA_IGNORED)
- dup->status = b->status;
- else
- dup->status = GIT_DELTA_UNMODIFIED;
- }
- else if (dup->status == GIT_DELTA_UNMODIFIED ||
- b->status == GIT_DELTA_DELETED)
- dup->status = b->status;
+ if (!diff->opts.notify_cb)
+ return 0;
- return dup;
+ return diff->opts.notify_cb(
+ diff, delta, matched_pathspec, diff->opts.notify_payload);
}
static int diff_delta__from_one(
@@ -171,16 +62,26 @@ static int diff_delta__from_one(
const git_index_entry *entry)
{
git_diff_delta *delta;
+ const char *matched_pathspec;
+ int notify_res;
if (status == GIT_DELTA_IGNORED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
return 0;
if (status == GIT_DELTA_UNTRACKED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
+ return 0;
+
+ if (entry->mode == GIT_FILEMODE_COMMIT &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
return 0;
- if (!diff_path_matches_pathspec(diff, entry->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, entry->path,
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
+ &matched_pathspec))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
@@ -199,54 +100,122 @@ static int diff_delta__from_one(
git_oid_cpy(&delta->new_file.oid, &entry->oid);
}
- delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
- delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (delta->status == GIT_DELTA_DELETED ||
+ !git_oid_iszero(&delta->new_file.oid))
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
- if (git_vector_insert(&diff->deltas, delta) < 0) {
+ notify_res = diff_notify(diff, delta, matched_pathspec);
+
+ if (notify_res)
+ git__free(delta);
+ else if (git_vector_insert(&diff->deltas, delta) < 0) {
git__free(delta);
return -1;
}
- return 0;
+ return notify_res < 0 ? GIT_EUSER : 0;
}
static int diff_delta__from_two(
git_diff_list *diff,
git_delta_t status,
const git_index_entry *old_entry,
+ uint32_t old_mode,
const git_index_entry *new_entry,
- git_oid *new_oid)
+ uint32_t new_mode,
+ git_oid *new_oid,
+ const char *matched_pathspec)
{
git_diff_delta *delta;
+ int notify_res;
if (status == GIT_DELTA_UNMODIFIED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
+ return 0;
+
+ if (old_entry->mode == GIT_FILEMODE_COMMIT &&
+ new_entry->mode == GIT_FILEMODE_COMMIT &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
return 0;
- if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
- const git_index_entry *temp = old_entry;
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ uint32_t temp_mode = old_mode;
+ const git_index_entry *temp_entry = old_entry;
old_entry = new_entry;
- new_entry = temp;
+ new_entry = temp_entry;
+ old_mode = new_mode;
+ new_mode = temp_mode;
}
delta = diff_delta__alloc(diff, status, old_entry->path);
GITERR_CHECK_ALLOC(delta);
- delta->old_file.mode = old_entry->mode;
git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
- delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
+ delta->old_file.size = old_entry->file_size;
+ delta->old_file.mode = old_mode;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ git_oid_cpy(&delta->new_file.oid, &new_entry->oid);
+ delta->new_file.size = new_entry->file_size;
+ delta->new_file.mode = new_mode;
+
+ if (new_oid) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
+ git_oid_cpy(&delta->old_file.oid, new_oid);
+ else
+ git_oid_cpy(&delta->new_file.oid, new_oid);
+ }
- delta->new_file.mode = new_entry->mode;
- git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
if (new_oid || !git_oid_iszero(&new_entry->oid))
- delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ notify_res = diff_notify(diff, delta, matched_pathspec);
- if (git_vector_insert(&diff->deltas, delta) < 0) {
+ if (notify_res)
+ git__free(delta);
+ else if (git_vector_insert(&diff->deltas, delta) < 0) {
git__free(delta);
return -1;
}
- return 0;
+ return notify_res < 0 ? GIT_EUSER : 0;
+}
+
+static git_diff_delta *diff_delta__last_for_item(
+ git_diff_list *diff,
+ const git_index_entry *item)
+{
+ git_diff_delta *delta = git_vector_last(&diff->deltas);
+ if (!delta)
+ return NULL;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ 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)
+ 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)
+ 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)
+ return delta;
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
}
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
@@ -260,13 +229,34 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
return git_pool_strndup(pool, prefix, len + 1);
}
-static int diff_delta__cmp(const void *a, const void *b)
+int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(da->old_file.path, db->old_file.path);
return val ? val : ((int)da->status - (int)db->status);
}
+bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ if (delta->status == GIT_DELTA_UNMODIFIED &&
+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_IGNORED &&
+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_UNTRACKED &&
+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ return true;
+
+ return false;
+}
+
+
static int config_bool(git_config *cfg, const char *name, int defvalue)
{
int val = defvalue;
@@ -281,14 +271,14 @@ static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts)
{
git_config *cfg;
- size_t i;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL)
return NULL;
+ GIT_REFCOUNT_INC(diff);
diff->repo = repo;
- if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 ||
+ if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
git_pool_init(&diff->pool, 1, 0) < 0)
goto fail;
@@ -300,16 +290,36 @@ static git_diff_list *git_diff_list_alloc(
if (config_bool(cfg, "core.ignorestat", 0))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
if (config_bool(cfg, "core.filemode", 1))
- diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
if (config_bool(cfg, "core.trustctime", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
- if (opts == NULL)
+ /* 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 (opts == NULL) {
+ /* Make sure we default to 3 lines */
+ diff->opts.context_lines = 3;
return diff;
+ }
memcpy(&diff->opts, opts, sizeof(git_diff_options));
- memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));
+
+ 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;
+
+ /* TODO: handle config diff.mnemonicprefix, diff.noprefix */
diff->opts.old_prefix = diff_strdup_prefix(&diff->pool,
opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT);
@@ -319,39 +329,15 @@ static git_diff_list *git_diff_list_alloc(
if (!diff->opts.old_prefix || !diff->opts.new_prefix)
goto fail;
- if (diff->opts.flags & GIT_DIFF_REVERSE) {
- char *swap = diff->opts.old_prefix;
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ const char *swap = diff->opts.old_prefix;
diff->opts.old_prefix = diff->opts.new_prefix;
diff->opts.new_prefix = swap;
}
- /* only copy pathspec if it is "interesting" so we can test
- * diff->pathspec.length > 0 to know if it is worth calling
- * fnmatch as we iterate.
- */
- if (!diff_pathspec_is_interesting(&opts->pathspec))
- return diff;
-
- if (git_vector_init(
- &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0)
- goto fail;
-
- for (i = 0; i < opts->pathspec.count; ++i) {
- int ret;
- const char *pattern = opts->pathspec.strings[i];
- git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
- if (!match)
- goto fail;
- ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern);
- if (ret == GIT_ENOTFOUND) {
- git__free(match);
- continue;
- } else if (ret < 0)
- goto fail;
-
- if (git_vector_insert(&diff->pathspec, match) < 0)
- goto fail;
- }
+ /* 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;
@@ -360,65 +346,106 @@ fail:
return NULL;
}
-void git_diff_list_free(git_diff_list *diff)
+static void diff_list_free(git_diff_list *diff)
{
git_diff_delta *delta;
- git_attr_fnmatch *match;
unsigned int i;
- if (!diff)
- return;
-
git_vector_foreach(&diff->deltas, i, delta) {
git__free(delta);
diff->deltas.contents[i] = NULL;
}
git_vector_free(&diff->deltas);
- git_vector_foreach(&diff->pathspec, i, match) {
- git__free(match);
- diff->pathspec.contents[i] = NULL;
- }
- git_vector_free(&diff->pathspec);
-
+ git_pathspec_free(&diff->pathspec);
git_pool_clear(&diff->pool);
git__free(diff);
}
-static int oid_for_workdir_item(
+void git_diff_list_free(git_diff_list *diff)
+{
+ if (!diff)
+ return;
+
+ GIT_REFCOUNT_DEC(diff, diff_list_free);
+}
+
+void git_diff_list_addref(git_diff_list *diff)
+{
+ GIT_REFCOUNT_INC(diff);
+}
+
+int git_diff__oid_for_file(
git_repository *repo,
- const git_index_entry *item,
+ const char *path,
+ uint16_t mode,
+ git_off_t size,
git_oid *oid)
{
- int result;
+ int result = 0;
git_buf full_path = GIT_BUF_INIT;
- if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0)
+ if (git_buf_joinpath(
+ &full_path, git_repository_workdir(repo), path) < 0)
return -1;
- /* calculate OID for file if possible*/
- if (S_ISLNK(item->mode))
+ if (!mode) {
+ struct stat st;
+
+ if (p_stat(path, &st) < 0) {
+ giterr_set(GITERR_OS, "Could not stat '%s'", path);
+ result = -1;
+ goto cleanup;
+ }
+
+ mode = st.st_mode;
+ size = st.st_size;
+ }
+
+ /* calculate OID for file if possible */
+ if (S_ISGITLINK(mode)) {
+ git_submodule *sm;
+ const git_oid *sm_oid;
+
+ if (!git_submodule_lookup(&sm, repo, path) &&
+ (sm_oid = git_submodule_wd_id(sm)) != NULL)
+ git_oid_cpy(oid, sm_oid);
+ else {
+ /* if submodule lookup failed probably just in an intermediate
+ * state where some init hasn't happened, so ignore the error
+ */
+ giterr_clear();
+ memset(oid, 0, sizeof(*oid));
+ }
+ } else if (S_ISLNK(mode)) {
result = git_odb__hashlink(oid, full_path.ptr);
- else if (!git__is_sizet(item->file_size)) {
- giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ } else if (!git__is_sizet(size)) {
+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path);
result = -1;
} else {
- int fd = git_futils_open_ro(full_path.ptr);
- if (fd < 0)
- result = fd;
- else {
- result = git_odb__hashfd(
- oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB);
- p_close(fd);
+ git_vector filters = GIT_VECTOR_INIT;
+
+ result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB);
+ if (result >= 0) {
+ int fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0)
+ result = fd;
+ else {
+ result = git_odb__hashfd_filtered(
+ oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters);
+ p_close(fd);
+ }
}
+
+ git_filters_free(&filters);
}
+cleanup:
git_buf_free(&full_path);
-
return result;
}
-#define EXEC_BIT_MASK 0000111
+#define MODE_BITS_MASK 0000777
static int maybe_modified(
git_iterator *old_iter,
@@ -431,24 +458,30 @@ static int maybe_modified(
git_delta_t status = GIT_DELTA_MODIFIED;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
+ bool new_is_workdir = (new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
+ const char *matched_pathspec;
GIT_UNUSED(old_iter);
- if (!diff_path_matches_pathspec(diff, oitem->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, oitem->path,
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
+ &matched_pathspec))
return 0;
- /* on platforms with no symlinks, promote plain files to symlinks */
- if (S_ISLNK(omode) && S_ISREG(nmode) &&
+ /* on platforms with no symlinks, preserve mode of existing symlinks */
+ if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
- nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
+ nmode = omode;
- /* on platforms with no execmode, clear exec bit from comparisons */
- if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
- omode = omode & ~EXEC_BIT_MASK;
- nmode = nmode & ~EXEC_BIT_MASK;
- }
+ /* on platforms with no execmode, just preserve old mode */
+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
+ (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
+ new_is_workdir)
+ nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
- /* support "assume unchanged" (badly, b/c we still stat everything) */
+ /* support "assume unchanged" (poorly, b/c we still stat everything) */
if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
@@ -459,23 +492,29 @@ static int maybe_modified(
/* if basic type of file changed, then split into delete and add */
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
- diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
- return -1;
- return 0;
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
+ status = GIT_DELTA_TYPECHANGE;
+ else {
+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
+ diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
+ return -1;
+ return 0;
+ }
}
/* if oids and modes match, then file is unmodified */
- else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
- omode == nmode)
+ else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode)
status = GIT_DELTA_UNMODIFIED;
- /* if we have a workdir item with an unknown oid, check deeper */
- else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) {
+ /* if we have an unknown OID and a workdir iterator, then check some
+ * circumstances that can accelerate things or need special handling
+ */
+ else if (git_oid_iszero(&nitem->oid) && new_is_workdir) {
/* TODO: add check against index file st_mtime to avoid racy-git */
- /* if they files look exactly alike, then we'll assume the same */
- if (oitem->file_size == nitem->file_size &&
+ /* if the stat data looks exactly alike, then assume the same */
+ if (omode == nmode &&
+ oitem->file_size == nitem->file_size &&
(!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
(oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds &&
@@ -487,77 +526,193 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED;
else if (S_ISGITLINK(nmode)) {
+ int err;
git_submodule *sub;
- if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
status = GIT_DELTA_UNMODIFIED;
- else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
- return -1;
- else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)
+ else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {
+ if (err == GIT_EEXISTS)
+ status = GIT_DELTA_UNMODIFIED;
+ else
+ return err;
+ } else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
status = GIT_DELTA_UNMODIFIED;
else {
- /* TODO: support other GIT_SUBMODULE_IGNORE values */
- status = GIT_DELTA_UNMODIFIED;
+ unsigned int sm_status = 0;
+ if (git_submodule_status(&sm_status, sub) < 0)
+ return -1;
+
+ /* check IS_WD_UNMODIFIED because this case is only used
+ * when the new side of the diff is the working directory
+ */
+ status = GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)
+ ? GIT_DELTA_UNMODIFIED : GIT_DELTA_MODIFIED;
+
+ /* grab OID while we are here */
+ if (git_oid_iszero(&nitem->oid)) {
+ const git_oid *sm_oid = git_submodule_wd_id(sub);
+ if (sm_oid != NULL) {
+ git_oid_cpy(&noid, sm_oid);
+ use_noid = &noid;
+ }
+ }
}
}
+ }
- /* TODO: check git attributes so we will not have to read the file
- * in if it is marked binary.
- */
+ /* if mode is GITLINK and submodules are ignored, then skip */
+ else if (S_ISGITLINK(nmode) &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
+ status = GIT_DELTA_UNMODIFIED;
- else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
- return -1;
+ /* if we got here and decided that the files are modified, but we
+ * haven't calculated the OID of the new item, then calculate it now
+ */
+ if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
+ if (!use_noid) {
+ if (git_diff__oid_for_file(diff->repo,
+ nitem->path, nitem->mode, nitem->file_size, &noid) < 0)
+ return -1;
+ use_noid = &noid;
+ }
- else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
- omode == nmode)
+ /* if oid matches, then mark unmodified (except submodules, where
+ * the filesystem content may be modified even if the oid still
+ * matches between the index and the workdir HEAD)
+ */
+ if (omode == nmode && !S_ISGITLINK(omode) &&
+ git_oid_equal(&oitem->oid, use_noid))
status = GIT_DELTA_UNMODIFIED;
+ }
+
+ return diff_delta__from_two(
+ diff, status, oitem, omode, nitem, nmode, use_noid, matched_pathspec);
+}
- /* store calculated oid so we don't have to recalc later */
- use_noid = &noid;
+static bool entry_is_prefixed(
+ git_diff_list *diff,
+ const git_index_entry *item,
+ const git_index_entry *prefix_item)
+{
+ size_t pathlen;
+
+ if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
+ return false;
+
+ pathlen = strlen(prefix_item->path);
+
+ return (prefix_item->path[pathlen - 1] == '/' ||
+ item->path[pathlen] == '\0' ||
+ item->path[pathlen] == '/');
+}
+
+static int diff_list_init_from_iterators(
+ git_diff_list *diff,
+ git_iterator *old_iter,
+ git_iterator *new_iter)
+{
+ diff->old_src = old_iter->type;
+ diff->new_src = new_iter->type;
+
+ /* 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_delta__from_two(diff, status, oitem, nitem, use_noid);
+ return 0;
}
-static int diff_from_iterators(
+int git_diff__from_iterators(
+ git_diff_list **diff_ptr,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_iterator *old_iter,
git_iterator *new_iter,
- git_diff_list **diff_ptr)
+ const git_diff_options *opts)
{
+ 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);
- if (!diff)
+
+ *diff_ptr = NULL;
+
+ if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
goto fail;
- diff->old_src = old_iter->type;
- diff->new_src = new_iter->type;
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
+ if (git_iterator_set_ignore_case(old_iter, true) < 0 ||
+ git_iterator_set_ignore_case(new_iter, true) < 0)
+ goto fail;
+ }
- if (git_iterator_current(old_iter, &oitem) < 0 ||
- git_iterator_current(new_iter, &nitem) < 0)
+ if (git_iterator_current(&oitem, old_iter) < 0 ||
+ git_iterator_current(&nitem, new_iter) < 0)
goto fail;
/* run iterators building diffs */
while (oitem || nitem) {
+ int cmp = oitem ? (nitem ? diff->entrycomp(oitem, nitem) : -1) : 1;
/* create DELETED records for old items not matched in new */
- if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) {
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
- git_iterator_advance(old_iter, &oitem) < 0)
+ if (cmp < 0) {
+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
+ goto fail;
+
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating a DELETE record
+ */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ entry_is_prefixed(diff, 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;
+ }
+
+ /* 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;
+ }
+ }
+
+ if (git_iterator_advance(&oitem, old_iter) < 0)
goto fail;
}
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
- else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) {
+ 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) &&
- git__prefixcmp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
+ diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
delta_type = GIT_DELTA_IGNORED;
if (S_ISDIR(nitem->mode)) {
@@ -565,20 +720,48 @@ static int diff_from_iterators(
* it or if the user requested the contents of untracked
* directories and it is not under an ignored directory.
*/
- if ((oitem && git__prefixcmp(oitem->path, nitem->path) == 0) ||
+ bool recurse_into_dir =
(delta_type == GIT_DELTA_UNTRACKED &&
- (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0))
- {
- /* if this directory is ignored, remember it as the
- * "ignore_prefix" for processing contained items
- */
- if (delta_type == GIT_DELTA_UNTRACKED &&
- git_iterator_current_is_ignored(new_iter))
- git_buf_sets(&ignore_prefix, nitem->path);
-
- if (git_iterator_advance_into_directory(new_iter, &nitem) < 0)
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
+ (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
+
+ /* do not advance into directories that contain a .git file */
+ if (!contains_oitem && recurse_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 directory is ignored, remember ignore_prefix */
+ if ((contains_oitem || recurse_into_dir) &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ git_iterator_current_is_ignored(new_iter))
+ {
+ git_buf_sets(&ignore_prefix, nitem->path);
+ delta_type = GIT_DELTA_IGNORED;
+
+ /* skip recursion if we've just learned this is ignored */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
+ recurse_into_dir = false;
+ }
+
+ if (contains_oitem || recurse_into_dir) {
+ /* advance into directory */
+ error = git_iterator_advance_into(&nitem, new_iter);
+
+ /* if directory is empty, can't advance into it, so skip */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = git_iterator_advance(&nitem, new_iter);
+
+ git_buf_clear(&ignore_prefix);
+ }
+ if (error < 0)
+ goto fail;
continue;
}
}
@@ -598,8 +781,9 @@ static int diff_from_iterators(
* checked before container directory exclusions are used to
* skip the file.
*/
- else if (delta_type == GIT_DELTA_IGNORED) {
- if (git_iterator_advance(new_iter, &nitem) < 0)
+ else if (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {
+ if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
continue; /* ignored parent directory, so skip completely */
}
@@ -607,11 +791,28 @@ static int diff_from_iterators(
else if (git_iterator_current_is_ignored(new_iter))
delta_type = GIT_DELTA_IGNORED;
- else if (new_iter->type != GIT_ITERATOR_WORKDIR)
+ else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
delta_type = GIT_DELTA_ADDED;
- if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
- git_iterator_advance(new_iter, &nitem) < 0)
+ if (diff_delta__from_one(diff, delta_type, nitem) < 0)
+ goto fail;
+
+ /* 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;
+ }
+ }
+
+ if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
}
@@ -619,165 +820,116 @@ static int diff_from_iterators(
* (or ADDED and DELETED pair if type changed)
*/
else {
- assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0);
+ assert(oitem && nitem && cmp == 0);
if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
- git_iterator_advance(old_iter, &oitem) < 0 ||
- git_iterator_advance(new_iter, &nitem) < 0)
+ git_iterator_advance(&oitem, old_iter) < 0 ||
+ git_iterator_advance(&nitem, new_iter) < 0)
goto fail;
}
}
- git_iterator_free(old_iter);
- git_iterator_free(new_iter);
- git_buf_free(&ignore_prefix);
-
*diff_ptr = diff;
- return 0;
fail:
- git_iterator_free(old_iter);
- git_iterator_free(new_iter);
+ if (!*diff_ptr) {
+ git_diff_list_free(diff);
+ error = -1;
+ }
+
git_buf_free(&ignore_prefix);
- git_diff_list_free(diff);
- *diff_ptr = NULL;
- return -1;
+ return error;
}
+#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
+ git_iterator *a = NULL, *b = NULL; \
+ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ error = git_diff__from_iterators(diff, repo, a, b, opts); \
+ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
+} while (0)
int git_diff_tree_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old_tree,
git_tree *new_tree,
- git_diff_list **diff)
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && old_tree && new_tree && diff);
+ int error = 0;
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0)
- return -1;
+ assert(diff && repo);
- git__free(prefix);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_tree(&b, new_tree, 0, pfx, pfx)
+ );
- return diff_from_iterators(repo, opts, a, b, diff);
+ return error;
}
-int git_diff_index_to_tree(
+int git_diff_tree_to_index(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
git_tree *old_tree,
- git_diff_list **diff)
+ git_index *index,
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
+ int error = 0;
- assert(repo && diff);
+ assert(diff && repo);
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_index_range(&b, repo, prefix, prefix) < 0)
- return -1;
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- git__free(prefix);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_index(&b, index, 0, pfx, pfx)
+ );
- return diff_from_iterators(repo, opts, a, b, diff);
+ return error;
}
-int git_diff_workdir_to_index(
+int git_diff_index_to_workdir(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
- git_diff_list **diff)
+ git_index *index,
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
+ int error = 0;
- assert(repo && diff);
+ assert(diff && repo);
- if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 ||
- git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)
- return -1;
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- git__free(prefix);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index(&a, index, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
+ );
- return diff_from_iterators(repo, opts, a, b, diff);
+ return error;
}
-int git_diff_workdir_to_tree(
+int git_diff_tree_to_workdir(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
git_tree *old_tree,
- git_diff_list **diff)
-{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && old_tree && diff);
-
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)
- return -1;
-
- git__free(prefix);
-
- return diff_from_iterators(repo, opts, a, b, diff);
-}
-
-int git_diff_merge(
- git_diff_list *onto,
- const git_diff_list *from)
+ const git_diff_options *opts)
{
int error = 0;
- git_pool onto_pool;
- git_vector onto_new;
- git_diff_delta *delta;
- unsigned int i, j;
-
- assert(onto && from);
-
- if (!from->deltas.length)
- return 0;
-
- if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 ||
- git_pool_init(&onto_pool, 1, 0) < 0)
- return -1;
-
- for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
- git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
- const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
- int cmp = !f ? -1 : !o ? 1 : strcmp(o->old_file.path, f->old_file.path);
- if (cmp < 0) {
- delta = diff_delta__dup(o, &onto_pool);
- i++;
- } else if (cmp > 0) {
- delta = diff_delta__dup(f, &onto_pool);
- j++;
- } else {
- delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
- i++;
- j++;
- }
-
- if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
- break;
- }
+ assert(diff && repo);
- if (!error) {
- git_vector_swap(&onto->deltas, &onto_new);
- git_pool_swap(&onto->pool, &onto_pool);
- onto->new_src = from->new_src;
- }
-
- git_vector_foreach(&onto_new, i, delta)
- git__free(delta);
- git_vector_free(&onto_new);
- git_pool_clear(&onto_pool);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
+ );
return error;
}
-
diff --git a/src/diff.h b/src/diff.h
index ac2457956..8e3cbcd46 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,6 +7,9 @@
#ifndef INCLUDE_diff_h__
#define INCLUDE_diff_h__
+#include "git2/diff.h"
+#include "git2/oid.h"
+
#include <stdio.h>
#include "vector.h"
#include "buffer.h"
@@ -20,21 +23,56 @@
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
- GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */
+ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
+enum {
+ GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
+ GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
+ GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
+ GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
+ GIT_DIFF_FLAG__TO_DELETE = (1 << 11), /* delete entry during rename det. */
+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 12), /* split entry during rename det. */
+};
+
struct git_diff_list {
+ git_refcount rc;
git_repository *repo;
git_diff_options opts;
git_vector pathspec;
- git_vector deltas; /* vector of git_diff_file_delta */
+ git_vector deltas; /* vector of git_diff_delta */
git_pool pool;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
uint32_t diffcaps;
+
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+ int (*pfxcomp)(const char *str, const char *pfx);
+ int (*entrycomp)(const void *a, const void *b);
};
+extern void git_diff__cleanup_modes(
+ uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
+
+extern void git_diff_list_addref(git_diff_list *diff);
+
+extern int git_diff_delta__cmp(const void *a, const void *b);
+
+extern bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta);
+
+extern int git_diff__oid_for_file(
+ git_repository *, const char *, uint16_t, git_off_t, git_oid *);
+
+extern 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);
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index ba7ef8245..34a3e506c 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -1,29 +1,18 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/diff.h"
#include "git2/attr.h"
-#include "git2/blob.h"
-#include "xdiff/xdiff.h"
+#include "git2/oid.h"
+#include "git2/submodule.h"
+#include "diff_output.h"
#include <ctype.h>
-#include "diff.h"
-#include "map.h"
#include "fileops.h"
#include "filter.h"
-
-typedef struct {
- git_diff_list *diff;
- void *cb_data;
- git_diff_hunk_fn hunk_cb;
- git_diff_data_fn line_cb;
- unsigned int index;
- git_diff_delta *delta;
- git_diff_range range;
-} diff_output_info;
+#include "buf_text.h"
static int read_next_int(const char **str, int *value)
{
@@ -39,77 +28,50 @@ static int read_next_int(const char **str, int *value)
return (digits > 0) ? 0 : -1;
}
-static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
+static int parse_hunk_header(git_diff_range *range, const char *header)
{
- diff_output_info *info = priv;
-
- if (len == 1 && info->hunk_cb) {
- git_diff_range range = { -1, 0, -1, 0 };
- const char *scan = bufs[0].ptr;
-
- /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
- if (*scan != '@')
- return -1;
-
- if (read_next_int(&scan, &range.old_start) < 0)
- return -1;
- if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0)
- return -1;
-
- if (read_next_int(&scan, &range.new_start) < 0)
- return -1;
- if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0)
- return -1;
-
- if (range.old_start < 0 || range.new_start < 0)
+ /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
+ if (*header != '@')
+ return -1;
+ if (read_next_int(&header, &range->old_start) < 0)
+ return -1;
+ if (*header == ',') {
+ if (read_next_int(&header, &range->old_lines) < 0)
return -1;
-
- memcpy(&info->range, &range, sizeof(git_diff_range));
-
- return info->hunk_cb(
- info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size);
- }
-
- if ((len == 2 || len == 3) && info->line_cb) {
- int origin;
-
- /* expect " "/"-"/"+", then data, then maybe newline */
- origin =
- (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
- (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
- GIT_DIFF_LINE_CONTEXT;
-
- if (info->line_cb(
- info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)
+ } else
+ range->old_lines = 1;
+ if (read_next_int(&header, &range->new_start) < 0)
+ return -1;
+ if (*header == ',') {
+ if (read_next_int(&header, &range->new_lines) < 0)
return -1;
-
- /* deal with adding and removing newline at EOF */
- if (len == 3) {
- if (origin == GIT_DIFF_LINE_ADDITION)
- origin = GIT_DIFF_LINE_ADD_EOFNL;
- else
- origin = GIT_DIFF_LINE_DEL_EOFNL;
-
- return info->line_cb(
- info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);
- }
- }
+ } else
+ range->new_lines = 1;
+ if (range->old_start < 0 || range->new_start < 0)
+ return -1;
return 0;
}
-#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
+#define KNOWN_BINARY_FLAGS (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
+#define NOT_BINARY_FLAGS (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
-static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file)
+static int update_file_is_binary_by_attr(
+ git_repository *repo, git_diff_file *file)
{
const char *value;
+
+ /* because of blob diffs, cannot assume path is set */
+ if (!file->path || !strlen(file->path))
+ return 0;
+
if (git_attr_get(&value, repo, 0, file->path, "diff") < 0)
return -1;
if (GIT_ATTR_FALSE(value))
- file->flags |= GIT_DIFF_FILE_BINARY;
+ file->flags |= GIT_DIFF_FLAG_BINARY;
else if (GIT_ATTR_TRUE(value))
- file->flags |= GIT_DIFF_FILE_NOT_BINARY;
+ file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
/* otherwise leave file->flags alone */
return 0;
@@ -117,101 +79,133 @@ static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *fi
static void update_delta_is_binary(git_diff_delta *delta)
{
- if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 ||
- (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)
- delta->binary = 1;
- else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 ||
- (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0)
- delta->binary = 0;
- /* otherwise leave delta->binary value untouched */
+ if ((delta->old_file.flags & GIT_DIFF_FLAG_BINARY) != 0 ||
+ (delta->new_file.flags & GIT_DIFF_FLAG_BINARY) != 0)
+ delta->flags |= GIT_DIFF_FLAG_BINARY;
+
+ else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
+ (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
+ delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+
+ /* otherwise leave delta->flags binary value untouched */
}
-static int file_is_binary_by_attr(
- git_diff_list *diff,
+/* returns if we forced binary setting (and no further checks needed) */
+static bool diff_delta_is_binary_forced(
+ diff_context *ctxt,
git_diff_delta *delta)
{
- int error = 0, mirror_new;
-
- delta->binary = -1;
+ /* return true if binary-ness has already been settled */
+ if ((delta->flags & KNOWN_BINARY_FLAGS) != 0)
+ return true;
/* make sure files are conceivably mmap-able */
if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size ||
(git_off_t)((size_t)delta->new_file.size) != delta->new_file.size)
{
- delta->old_file.flags |= GIT_DIFF_FILE_BINARY;
- delta->new_file.flags |= GIT_DIFF_FILE_BINARY;
- delta->binary = 1;
- return 0;
+ delta->old_file.flags |= GIT_DIFF_FLAG_BINARY;
+ delta->new_file.flags |= GIT_DIFF_FLAG_BINARY;
+ delta->flags |= GIT_DIFF_FLAG_BINARY;
+ return true;
}
/* check if user is forcing us to text diff these files */
- if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) {
- delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
- delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
- delta->binary = 0;
- return 0;
+ if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {
+ delta->old_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ delta->new_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ return true;
}
+ return false;
+}
+
+static int diff_delta_is_binary_by_attr(
+ diff_context *ctxt, git_diff_patch *patch)
+{
+ int error = 0, mirror_new;
+ git_diff_delta *delta = patch->delta;
+
+ if (diff_delta_is_binary_forced(ctxt, delta))
+ return 0;
+
/* check diff attribute +, -, or 0 */
- if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0)
+ if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0)
return -1;
mirror_new = (delta->new_file.path == delta->old_file.path ||
- strcmp(delta->new_file.path, delta->old_file.path) == 0);
+ ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0);
if (mirror_new)
- delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS);
+ delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
else
- error = update_file_is_binary_by_attr(diff->repo, &delta->new_file);
+ error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);
update_delta_is_binary(delta);
return error;
}
-static int file_is_binary_by_content(
+static int diff_delta_is_binary_by_content(
+ diff_context *ctxt,
git_diff_delta *delta,
- git_map *old_data,
- git_map *new_data)
+ git_diff_file *file,
+ const git_map *map)
{
- git_buf search;
+ const git_buf search = { map->data, 0, min(map->len, 4000) };
- if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) {
- search.ptr = old_data->data;
- search.size = min(old_data->len, 4000);
+ if (diff_delta_is_binary_forced(ctxt, delta))
+ return 0;
- if (git_buf_is_binary(&search))
- delta->old_file.flags |= GIT_DIFF_FILE_BINARY;
- else
- delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
- }
+ /* TODO: provide encoding / binary detection callbacks that can
+ * be UTF-8 aware, etc. For now, instead of trying to be smart,
+ * let's just use the simple NUL-byte detection that core git uses.
+ */
- if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) {
- search.ptr = new_data->data;
- search.size = min(new_data->len, 4000);
+ /* previously was: if (git_buf_text_is_binary(&search)) */
+ if (git_buf_text_contains_nul(&search))
+ file->flags |= GIT_DIFF_FLAG_BINARY;
+ else
+ file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
- if (git_buf_is_binary(&search))
- delta->new_file.flags |= GIT_DIFF_FILE_BINARY;
- else
- delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
+ update_delta_is_binary(delta);
+
+ return 0;
+}
+
+static int diff_delta_is_binary_by_size(
+ diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)
+{
+ git_off_t threshold = MAX_DIFF_FILESIZE;
+
+ if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
+ return 0;
+
+ if (ctxt && ctxt->opts) {
+ if (ctxt->opts->max_size < 0)
+ return 0;
+
+ if (ctxt->opts->max_size > 0)
+ threshold = ctxt->opts->max_size;
}
- update_delta_is_binary(delta);
+ if (file->size > threshold)
+ file->flags |= GIT_DIFF_FLAG_BINARY;
- /* TODO: if value != NULL, implement diff drivers */
+ update_delta_is_binary(delta);
return 0;
}
static void setup_xdiff_options(
- git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
+ const git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
{
memset(cfg, 0, sizeof(xdemitconf_t));
memset(param, 0, sizeof(xpparam_t));
cfg->ctxlen =
- (!opts || !opts->context_lines) ? 3 : opts->context_lines;
+ (!opts) ? 3 : opts->context_lines;
cfg->interhunkctxlen =
- (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines;
+ (!opts) ? 0 : opts->interhunk_lines;
if (!opts)
return;
@@ -224,54 +218,239 @@ static void setup_xdiff_options(
param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
}
+
static int get_blob_content(
- git_repository *repo,
- const git_oid *oid,
+ diff_context *ctxt,
+ git_diff_delta *delta,
+ git_diff_file *file,
git_map *map,
git_blob **blob)
{
- if (git_oid_iszero(oid))
+ int error;
+ git_odb_object *odb_obj = NULL;
+
+ if (git_oid_iszero(&file->oid))
return 0;
- if (git_blob_lookup(blob, repo, oid) < 0)
- return -1;
+ if (file->mode == GIT_FILEMODE_COMMIT)
+ {
+ char oidstr[GIT_OID_HEXSZ+1];
+ git_buf content = GIT_BUF_INIT;
+
+ git_oid_fmt(oidstr, &file->oid);
+ oidstr[GIT_OID_HEXSZ] = 0;
+ git_buf_printf(&content, "Subproject commit %s\n", oidstr );
+
+ map->data = git_buf_detach(&content);
+ map->len = strlen(map->data);
+
+ file->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ return 0;
+ }
+
+ if (!file->size) {
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ /* peek at object header to avoid loading if too large */
+ if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 ||
+ (error = git_odb__read_header_or_object(
+ &odb_obj, &len, &type, odb, &file->oid)) < 0)
+ return error;
+
+ assert(type == GIT_OBJ_BLOB);
+
+ file->size = len;
+ }
+
+ /* if blob is too large to diff, mark as binary */
+ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)
+ return error;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ return 0;
+
+ if (odb_obj != NULL) {
+ error = git_object__from_odb_object(
+ (git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB);
+ git_odb_object_free(odb_obj);
+ } else
+ error = git_blob_lookup(blob, ctxt->repo, &file->oid);
+
+ if (error)
+ return error;
map->data = (void *)git_blob_rawcontent(*blob);
- map->len = git_blob_rawsize(*blob);
+ map->len = (size_t)git_blob_rawsize(*blob);
+
+ return diff_delta_is_binary_by_content(ctxt, delta, file, map);
+}
+
+static int get_workdir_sm_content(
+ diff_context *ctxt,
+ git_diff_file *file,
+ git_map *map)
+{
+ int error = 0;
+ git_buf content = GIT_BUF_INIT;
+ git_submodule* sm = NULL;
+ unsigned int sm_status = 0;
+ const char* sm_status_text = "";
+ char oidstr[GIT_OID_HEXSZ+1];
+
+ if ((error = git_submodule_lookup(&sm, ctxt->repo, file->path)) < 0 ||
+ (error = git_submodule_status(&sm_status, sm)) < 0)
+ {
+ /* GIT_EEXISTS means a "submodule" that has not been git added */
+ if (error == GIT_EEXISTS)
+ error = 0;
+ return error;
+ }
+
+ /* update OID if we didn't have it previously */
+ if ((file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
+ const git_oid* sm_head;
+
+ if ((sm_head = git_submodule_wd_id(sm)) != NULL ||
+ (sm_head = git_submodule_head_id(sm)) != NULL)
+ {
+ git_oid_cpy(&file->oid, sm_head);
+ file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+ }
+
+ git_oid_fmt(oidstr, &file->oid);
+ oidstr[GIT_OID_HEXSZ] = '\0';
+
+ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ sm_status_text = "-dirty";
+
+ git_buf_printf(&content, "Subproject commit %s%s\n",
+ oidstr, sm_status_text);
+
+ map->data = git_buf_detach(&content);
+ map->len = strlen(map->data);
+
+ file->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
return 0;
}
+static int get_filtered(
+ git_map *map, git_file fd, git_diff_file *file, git_vector *filters)
+{
+ int error;
+ git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
+
+ if ((error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) < 0)
+ return error;
+
+ if (!filters->length)
+ git_buf_swap(&filtered, &raw);
+ else
+ error = git_filters_apply(&filtered, &raw, filters);
+
+ if (!error) {
+ map->len = git_buf_len(&filtered);
+ map->data = git_buf_detach(&filtered);
+
+ file->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ }
+
+ git_buf_free(&raw);
+ git_buf_free(&filtered);
+
+ return error;
+}
+
static int get_workdir_content(
- git_repository *repo,
+ diff_context *ctxt,
+ git_diff_delta *delta,
git_diff_file *file,
git_map *map)
{
int error = 0;
git_buf path = GIT_BUF_INIT;
+ const char *wd = git_repository_workdir(ctxt->repo);
- if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0)
+ if (S_ISGITLINK(file->mode))
+ return get_workdir_sm_content(ctxt, file, map);
+
+ if (S_ISDIR(file->mode))
+ return 0;
+
+ if (git_buf_joinpath(&path, wd, file->path) < 0)
return -1;
if (S_ISLNK(file->mode)) {
- ssize_t read_len;
+ ssize_t alloc_len, read_len;
+
+ file->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ file->flags |= GIT_DIFF_FLAG_BINARY;
- file->flags |= GIT_DIFF_FILE_FREE_DATA;
- file->flags |= GIT_DIFF_FILE_BINARY;
+ /* link path on disk could be UTF-16, so prepare a buffer that is
+ * big enough to handle some UTF-8 data expansion
+ */
+ alloc_len = (ssize_t)(file->size * 2) + 1;
- map->data = git__malloc((size_t)file->size + 1);
+ map->data = git__malloc(alloc_len);
GITERR_CHECK_ALLOC(map->data);
- read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1);
- if (read_len != (ssize_t)file->size) {
+ read_len = p_readlink(path.ptr, map->data, alloc_len);
+ if (read_len < 0) {
giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path);
error = -1;
- } else
- map->len = read_len;
+ goto cleanup;
+ }
+
+ map->len = read_len;
}
else {
- error = git_futils_mmap_ro_file(map, path.ptr);
- file->flags |= GIT_DIFF_FILE_UNMAP_DATA;
+ git_file fd = git_futils_open_ro(path.ptr);
+ git_vector filters = GIT_VECTOR_INIT;
+
+ if (fd < 0) {
+ error = fd;
+ goto cleanup;
+ }
+
+ if (!file->size && !(file->size = git_futils_filesize(fd)))
+ goto close_and_cleanup;
+
+ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
+ (delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto close_and_cleanup;
+
+ error = git_filters_load(
+ &filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB);
+ if (error < 0)
+ goto close_and_cleanup;
+
+ if (error == 0) { /* note: git_filters_load returns filter count */
+ error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size);
+ if (!error)
+ file->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
+ }
+ if (error != 0)
+ error = get_filtered(map, fd, file, &filters);
+
+close_and_cleanup:
+ git_filters_free(&filters);
+ p_close(fd);
}
+
+ /* once data is loaded, update OID if we didn't have it previously */
+ if (!error && (file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {
+ error = git_odb_hash(
+ &file->oid, map->data, map->len, GIT_OBJ_BLOB);
+ if (!error)
+ file->flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ if (!error)
+ error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
+
+cleanup:
git_buf_free(&path);
return error;
}
@@ -281,185 +460,563 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
if (blob != NULL)
git_blob_free(blob);
- if (file->flags & GIT_DIFF_FILE_FREE_DATA) {
+ if (file->flags & GIT_DIFF_FLAG__FREE_DATA) {
git__free(map->data);
- map->data = NULL;
- file->flags &= ~GIT_DIFF_FILE_FREE_DATA;
+ map->data = "";
+ map->len = 0;
+ file->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
}
- else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) {
+ else if (file->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
git_futils_mmap_free(map);
- map->data = NULL;
- file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA;
+ map->data = "";
+ map->len = 0;
+ file->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
}
}
-static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) {
- assert(dst && src);
- dst->data = src->ptr;
- dst->len = src->size;
-#ifdef GIT_WIN32
- dst->fmh = NULL;
-#endif
+static int diff_context_init(
+ diff_context *ctxt,
+ git_diff_list *diff,
+ git_repository *repo,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ memset(ctxt, 0, sizeof(diff_context));
+
+ if (!repo && diff)
+ repo = diff->repo;
+
+ if (!opts && diff)
+ opts = &diff->opts;
+
+ ctxt->repo = repo;
+ ctxt->diff = diff;
+ ctxt->opts = opts;
+ ctxt->file_cb = file_cb;
+ ctxt->hunk_cb = hunk_cb;
+ ctxt->data_cb = data_cb;
+ ctxt->payload = payload;
+ ctxt->error = 0;
+
+ setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
+
+ return 0;
}
-int git_diff_foreach(
- git_diff_list *diff,
- void *data,
- git_diff_file_fn file_cb,
- git_diff_hunk_fn hunk_cb,
- git_diff_data_fn line_cb)
+static int diff_delta_file_callback(
+ diff_context *ctxt, git_diff_delta *delta, size_t idx)
+{
+ float progress;
+
+ if (!ctxt->file_cb)
+ return 0;
+
+ progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;
+
+ if (ctxt->file_cb(delta, progress, ctxt->payload) != 0)
+ ctxt->error = GIT_EUSER;
+
+ return ctxt->error;
+}
+
+static void diff_patch_init(
+ diff_context *ctxt, git_diff_patch *patch)
+{
+ memset(patch, 0, sizeof(git_diff_patch));
+
+ patch->diff = ctxt->diff;
+ patch->ctxt = ctxt;
+
+ if (patch->diff) {
+ patch->old_src = patch->diff->old_src;
+ patch->new_src = patch->diff->new_src;
+ } else {
+ patch->old_src = patch->new_src = GIT_ITERATOR_TYPE_TREE;
+ }
+}
+
+static git_diff_patch *diff_patch_alloc(
+ diff_context *ctxt, git_diff_delta *delta)
+{
+ git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));
+ if (!patch)
+ return NULL;
+
+ diff_patch_init(ctxt, patch);
+
+ git_diff_list_addref(patch->diff);
+
+ GIT_REFCOUNT_INC(patch);
+
+ patch->delta = delta;
+ patch->flags = GIT_DIFF_PATCH_ALLOCATED;
+
+ return patch;
+}
+
+static int diff_patch_load(
+ diff_context *ctxt, git_diff_patch *patch)
{
int error = 0;
- diff_output_info info;
- git_diff_delta *delta;
- xpparam_t xdiff_params;
- xdemitconf_t xdiff_config;
- xdemitcb_t xdiff_callback;
+ git_diff_delta *delta = patch->delta;
+ bool check_if_unmodified = false;
- info.diff = diff;
- info.cb_data = data;
- info.hunk_cb = hunk_cb;
- info.line_cb = line_cb;
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
+ return 0;
- setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params);
- memset(&xdiff_callback, 0, sizeof(xdiff_callback));
- xdiff_callback.outf = diff_output_cb;
- xdiff_callback.priv = &info;
+ error = diff_delta_is_binary_by_attr(ctxt, patch);
- git_vector_foreach(&diff->deltas, info.index, delta) {
- git_blob *old_blob = NULL, *new_blob = NULL;
- git_map old_data, new_data;
- mmfile_t old_xdiff_data, new_xdiff_data;
+ patch->old_data.data = "";
+ patch->old_data.len = 0;
+ patch->old_blob = NULL;
- if (delta->status == GIT_DELTA_UNMODIFIED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
- continue;
+ patch->new_data.data = "";
+ patch->new_data.len = 0;
+ patch->new_blob = NULL;
- if (delta->status == GIT_DELTA_IGNORED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
- continue;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
- if (delta->status == GIT_DELTA_UNTRACKED &&
- (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
- continue;
+ if (!ctxt->hunk_cb &&
+ !ctxt->data_cb &&
+ (ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)
+ goto cleanup;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ break;
+ case GIT_DELTA_DELETED:
+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ break;
+ case GIT_DELTA_MODIFIED:
+ break;
+ case GIT_DELTA_UNTRACKED:
+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)
+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ break;
+ default:
+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;
+ break;
+ }
+
+#define CHECK_UNMODIFIED (GIT_DIFF_FLAG__NO_DATA | GIT_DIFF_FLAG_VALID_OID)
+
+ check_if_unmodified =
+ (delta->old_file.flags & CHECK_UNMODIFIED) == 0 &&
+ (delta->new_file.flags & CHECK_UNMODIFIED) == 0;
+
+ /* Always try to load workdir content first, since it may need to be
+ * filtered (and hence use 2x memory) and we want to minimize the max
+ * memory footprint during diff.
+ */
- if ((error = file_is_binary_by_attr(diff, delta)) < 0)
+ if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
+ patch->old_src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = get_workdir_content(
+ ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
goto cleanup;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
- old_data.data = "";
- old_data.len = 0;
- new_data.data = "";
- new_data.len = 0;
+ if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
+ patch->new_src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = get_workdir_content(
+ ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
+ goto cleanup;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
- /* TODO: Partial blob reading to defer loading whole blob.
- * I.e. I want a blob with just the first 4kb loaded, then
- * later on I will read the rest of the blob if needed.
- */
+ if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
+ patch->old_src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = get_blob_content(
+ ctxt, delta, &delta->old_file,
+ &patch->old_data, &patch->old_blob)) < 0)
+ goto cleanup;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
- /* map files */
- if (delta->binary != 1 &&
- (hunk_cb || line_cb) &&
- (delta->status == GIT_DELTA_DELETED ||
- delta->status == GIT_DELTA_MODIFIED))
- {
- if (diff->old_src == GIT_ITERATOR_WORKDIR)
- error = get_workdir_content(diff->repo, &delta->old_file, &old_data);
- else
- error = get_blob_content(
- diff->repo, &delta->old_file.oid, &old_data, &old_blob);
-
- if (error < 0)
- goto cleanup;
- }
+ if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&
+ patch->new_src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = get_blob_content(
+ ctxt, delta, &delta->new_file,
+ &patch->new_data, &patch->new_blob)) < 0)
+ goto cleanup;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
- if (delta->binary != 1 &&
- (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) &&
- (delta->status == GIT_DELTA_ADDED ||
- delta->status == GIT_DELTA_MODIFIED))
- {
- if (diff->new_src == GIT_ITERATOR_WORKDIR)
- error = get_workdir_content(diff->repo, &delta->new_file, &new_data);
- else
- error = get_blob_content(
- diff->repo, &delta->new_file.oid, &new_data, &new_blob);
-
- if (error < 0)
- goto cleanup;
-
- if ((delta->new_file.flags | GIT_DIFF_FILE_VALID_OID) == 0) {
- error = git_odb_hash(
- &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB);
-
- if (error < 0)
- goto cleanup;
-
- /* since we did not have the definitive oid, we may have
- * incorrect status and need to skip this item.
- */
- if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) {
- delta->status = GIT_DELTA_UNMODIFIED;
- if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
- goto cleanup;
- }
- }
- }
+ /* if we did not previously have the definitive oid, we may have
+ * incorrect status and need to switch this to UNMODIFIED.
+ */
+ if (check_if_unmodified &&
+ delta->old_file.mode == delta->new_file.mode &&
+ !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
+ {
+ delta->status = GIT_DELTA_UNMODIFIED;
- /* if we have not already decided whether file is binary,
- * check the first 4K for nul bytes to decide...
+ if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ goto cleanup;
+ }
+
+cleanup:
+ if ((delta->flags & KNOWN_BINARY_FLAGS) == 0)
+ update_delta_is_binary(delta);
+
+ if (!error) {
+ patch->flags |= GIT_DIFF_PATCH_LOADED;
+
+ /* patch is diffable only for non-binary, modified files where at
+ * least one side has data and there is actual change in the data
*/
- if (delta->binary == -1) {
- error = file_is_binary_by_content(
- delta, &old_data, &new_data);
- if (error < 0)
- goto cleanup;
- }
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
+ delta->status != GIT_DELTA_UNMODIFIED &&
+ (patch->old_data.len || patch->new_data.len) &&
+ (patch->old_data.len != patch->new_data.len ||
+ !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid)))
+ patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
+ }
+
+ return error;
+}
+
+static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)
+{
+ git_diff_patch *patch = priv;
+ diff_context *ctxt = patch->ctxt;
+
+ if (len == 1) {
+ ctxt->error = parse_hunk_header(&ctxt->range, bufs[0].ptr);
+ if (ctxt->error < 0)
+ return ctxt->error;
+
+ if (ctxt->hunk_cb != NULL &&
+ ctxt->hunk_cb(patch->delta, &ctxt->range,
+ bufs[0].ptr, bufs[0].size, ctxt->payload))
+ ctxt->error = GIT_EUSER;
+ }
- /* TODO: if ignore_whitespace is set, then we *must* do text
- * diffs to tell if a file has really been changed.
+ if (len == 2 || len == 3) {
+ /* expect " "/"-"/"+", then data */
+ char origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
+ GIT_DIFF_LINE_CONTEXT;
+
+ if (ctxt->data_cb != NULL &&
+ ctxt->data_cb(patch->delta, &ctxt->range,
+ origin, bufs[1].ptr, bufs[1].size, ctxt->payload))
+ ctxt->error = GIT_EUSER;
+ }
+
+ if (len == 3 && !ctxt->error) {
+ /* If we have a '+' and a third buf, then we have added a line
+ * without a newline and the old code had one, so DEL_EOFNL.
+ * If we have a '-' and a third buf, then we have removed a line
+ * with out a newline but added a blank line, so ADD_EOFNL.
*/
+ char origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
+ GIT_DIFF_LINE_CONTEXT;
- if (file_cb != NULL) {
- error = file_cb(data, delta, (float)info.index / diff->deltas.length);
- if (error < 0)
- goto cleanup;
- }
+ if (ctxt->data_cb != NULL &&
+ ctxt->data_cb(patch->delta, &ctxt->range,
+ origin, bufs[2].ptr, bufs[2].size, ctxt->payload))
+ ctxt->error = GIT_EUSER;
+ }
- /* don't do hunk and line diffs if file is binary */
- if (delta->binary == 1)
- goto cleanup;
+ return ctxt->error;
+}
- /* nothing to do if we did not get data */
- if (!old_data.len && !new_data.len)
- goto cleanup;
+static int diff_patch_generate(
+ diff_context *ctxt, git_diff_patch *patch)
+{
+ int error = 0;
+ xdemitcb_t xdiff_callback;
+ mmfile_t old_xdiff_data, new_xdiff_data;
- assert(hunk_cb || line_cb);
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
+ return 0;
- info.delta = delta;
- old_xdiff_data.ptr = old_data.data;
- old_xdiff_data.size = old_data.len;
- new_xdiff_data.ptr = new_data.data;
- new_xdiff_data.size = new_data.len;
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
+ if ((error = diff_patch_load(ctxt, patch)) < 0)
+ return error;
- xdl_diff(&old_xdiff_data, &new_xdiff_data,
- &xdiff_params, &xdiff_config, &xdiff_callback);
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
+ return 0;
-cleanup:
- release_content(&delta->old_file, &old_data, old_blob);
- release_content(&delta->new_file, &new_data, new_blob);
+ if (!ctxt->file_cb && !ctxt->hunk_cb)
+ return 0;
+
+ patch->ctxt = ctxt;
+
+ memset(&xdiff_callback, 0, sizeof(xdiff_callback));
+ xdiff_callback.outf = diff_patch_cb;
+ xdiff_callback.priv = patch;
+
+ old_xdiff_data.ptr = patch->old_data.data;
+ old_xdiff_data.size = patch->old_data.len;
+ new_xdiff_data.ptr = patch->new_data.data;
+ new_xdiff_data.size = patch->new_data.len;
+
+ xdl_diff(&old_xdiff_data, &new_xdiff_data,
+ &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);
+
+ error = ctxt->error;
+
+ if (!error)
+ patch->flags |= GIT_DIFF_PATCH_DIFFED;
+
+ return error;
+}
+
+static void diff_patch_unload(git_diff_patch *patch)
+{
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {
+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);
+
+ patch->hunks_size = 0;
+ patch->lines_size = 0;
+ }
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) {
+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_LOADED);
+
+ release_content(
+ &patch->delta->old_file, &patch->old_data, patch->old_blob);
+ release_content(
+ &patch->delta->new_file, &patch->new_data, patch->new_blob);
+ }
+}
+
+static void diff_patch_free(git_diff_patch *patch)
+{
+ diff_patch_unload(patch);
+
+ git__free(patch->lines);
+ patch->lines = NULL;
+ patch->lines_asize = 0;
+
+ git__free(patch->hunks);
+ patch->hunks = NULL;
+ patch->hunks_asize = 0;
+
+ if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))
+ return;
+
+ patch->flags = 0;
+
+ git_diff_list_free(patch->diff); /* decrements refcount */
+
+ git__free(patch);
+}
+
+#define MAX_HUNK_STEP 128
+#define MIN_HUNK_STEP 8
+#define MAX_LINE_STEP 256
+#define MIN_LINE_STEP 8
+
+static int diff_patch_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ const char *header,
+ size_t header_len,
+ void *payload)
+{
+ git_diff_patch *patch = payload;
+ diff_patch_hunk *hunk;
+
+ GIT_UNUSED(delta);
+
+ if (patch->hunks_size >= patch->hunks_asize) {
+ size_t new_size;
+ diff_patch_hunk *new_hunks;
+
+ if (patch->hunks_asize > MAX_HUNK_STEP)
+ new_size = patch->hunks_asize + MAX_HUNK_STEP;
+ else
+ new_size = patch->hunks_asize * 2;
+ if (new_size < MIN_HUNK_STEP)
+ new_size = MIN_HUNK_STEP;
+
+ new_hunks = git__realloc(
+ patch->hunks, new_size * sizeof(diff_patch_hunk));
+ if (!new_hunks)
+ return -1;
+
+ patch->hunks = new_hunks;
+ patch->hunks_asize = new_size;
+ }
+
+ hunk = &patch->hunks[patch->hunks_size++];
+
+ memcpy(&hunk->range, range, sizeof(hunk->range));
+
+ assert(header_len + 1 < sizeof(hunk->header));
+ memcpy(&hunk->header, header, header_len);
+ hunk->header[header_len] = '\0';
+ hunk->header_len = header_len;
+
+ hunk->line_start = patch->lines_size;
+ hunk->line_count = 0;
+
+ patch->oldno = range->old_start;
+ patch->newno = range->new_start;
+
+ return 0;
+}
+
+static int diff_patch_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len,
+ void *payload)
+{
+ git_diff_patch *patch = payload;
+ diff_patch_hunk *hunk;
+ diff_patch_line *line;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(range);
+
+ assert(patch->hunks_size > 0);
+ assert(patch->hunks != NULL);
+
+ hunk = &patch->hunks[patch->hunks_size - 1];
+
+ if (patch->lines_size >= patch->lines_asize) {
+ size_t new_size;
+ diff_patch_line *new_lines;
+
+ if (patch->lines_asize > MAX_LINE_STEP)
+ new_size = patch->lines_asize + MAX_LINE_STEP;
+ else
+ new_size = patch->lines_asize * 2;
+ if (new_size < MIN_LINE_STEP)
+ new_size = MIN_LINE_STEP;
+
+ new_lines = git__realloc(
+ patch->lines, new_size * sizeof(diff_patch_line));
+ if (!new_lines)
+ return -1;
+
+ patch->lines = new_lines;
+ patch->lines_asize = new_size;
+ }
+
+ line = &patch->lines[patch->lines_size++];
+
+ line->ptr = content;
+ line->len = content_len;
+ line->origin = line_origin;
+
+ /* do some bookkeeping so we can provide old/new line numbers */
+
+ for (line->lines = 0; content_len > 0; --content_len) {
+ if (*content++ == '\n')
+ ++line->lines;
+ }
+
+ switch (line_origin) {
+ case GIT_DIFF_LINE_ADDITION:
+ line->oldno = -1;
+ line->newno = patch->newno;
+ patch->newno += line->lines;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ line->oldno = patch->oldno;
+ line->newno = -1;
+ patch->oldno += line->lines;
+ break;
+ default:
+ line->oldno = patch->oldno;
+ line->newno = patch->newno;
+ patch->oldno += line->lines;
+ patch->newno += line->lines;
+ break;
+ }
+
+ hunk->line_count++;
+
+ return 0;
+}
+
+static int diff_required(git_diff_list *diff, const char *action)
+{
+ if (!diff) {
+ giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_diff_foreach(
+ git_diff_list *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ diff_context ctxt;
+ size_t idx;
+ git_diff_patch patch;
+
+ if (diff_required(diff, "git_diff_foreach") < 0)
+ return -1;
+
+ if (diff_context_init(
+ &ctxt, diff, NULL, NULL, file_cb, hunk_cb, data_cb, payload) < 0)
+ return -1;
+
+ diff_patch_init(&ctxt, &patch);
+
+ git_vector_foreach(&diff->deltas, idx, patch.delta) {
+
+ /* check flags against patch status */
+ if (git_diff_delta__should_skip(ctxt.opts, patch.delta))
+ continue;
+
+ if (!(error = diff_patch_load(&ctxt, &patch))) {
+
+ /* invoke file callback */
+ error = diff_delta_file_callback(&ctxt, patch.delta, idx);
+
+ /* generate diffs and invoke hunk and line callbacks */
+ if (!error)
+ error = diff_patch_generate(&ctxt, &patch);
+
+ diff_patch_unload(&patch);
+ }
if (error < 0)
break;
}
+ if (error == GIT_EUSER)
+ giterr_clear(); /* don't let error message leak */
+
return error;
}
typedef struct {
git_diff_list *diff;
- git_diff_data_fn print_cb;
- void *cb_data;
+ git_diff_data_cb print_cb;
+ void *payload;
git_buf *buf;
} diff_print_info;
@@ -467,32 +1024,40 @@ static char pick_suffix(int mode)
{
if (S_ISDIR(mode))
return '/';
- else if (mode & 0100)
+ else if (mode & 0100) //-V536
/* in git, modes are very regular, so we must have 0100755 mode */
return '*';
else
return ' ';
}
-static int print_compact(void *data, git_diff_delta *delta, float progress)
+char git_diff_status_char(git_delta_t status)
+{
+ char code;
+
+ switch (status) {
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ default: code = ' '; break;
+ }
+
+ return code;
+}
+
+static int print_compact(
+ const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
- char code, old_suffix, new_suffix;
+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
GIT_UNUSED(progress);
- switch (delta->status) {
- case GIT_DELTA_ADDED: code = 'A'; break;
- case GIT_DELTA_DELETED: code = 'D'; break;
- case GIT_DELTA_MODIFIED: code = 'M'; break;
- case GIT_DELTA_RENAMED: code = 'R'; break;
- case GIT_DELTA_COPIED: code = 'C'; break;
- case GIT_DELTA_IGNORED: code = 'I'; break;
- case GIT_DELTA_UNTRACKED: code = '?'; break;
- default: code = 0;
- }
-
- if (!code)
+ if (code == ' ')
return 0;
old_suffix = pick_suffix(delta->old_file.mode);
@@ -501,7 +1066,7 @@ static int print_compact(void *data, git_diff_delta *delta, float progress)
git_buf_clear(pi->buf);
if (delta->old_file.path != delta->new_file.path &&
- strcmp(delta->old_file.path,delta->new_file.path) != 0)
+ pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)
git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode &&
@@ -516,13 +1081,20 @@ static int print_compact(void *data, git_diff_delta *delta, float progress)
if (git_buf_oom(pi->buf))
return -1;
- return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+
+ return 0;
}
int git_diff_print_compact(
git_diff_list *diff,
- void *cb_data,
- git_diff_data_fn print_cb)
+ git_diff_data_cb print_cb,
+ void *payload)
{
int error;
git_buf buf = GIT_BUF_INIT;
@@ -530,18 +1102,17 @@ int git_diff_print_compact(
pi.diff = diff;
pi.print_cb = print_cb;
- pi.cb_data = cb_data;
+ pi.payload = payload;
pi.buf = &buf;
- error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL);
+ error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
git_buf_free(&buf);
return error;
}
-
-static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
+static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
{
char start_oid[8], end_oid[8];
@@ -571,17 +1142,24 @@ static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
return 0;
}
-static int print_patch_file(void *data, git_diff_delta *delta, float progress)
+static int print_patch_file(
+ const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
const char *oldpfx = pi->diff->opts.old_prefix;
const char *oldpath = delta->old_file.path;
const char *newpfx = pi->diff->opts.new_prefix;
const char *newpath = delta->new_file.path;
- int result;
GIT_UNUSED(progress);
+ if (S_ISDIR(delta->new_file.mode) ||
+ delta->status == GIT_DELTA_UNMODIFIED ||
+ delta->status == GIT_DELTA_IGNORED ||
+ (delta->status == GIT_DELTA_UNTRACKED &&
+ (pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
+ return 0;
+
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
@@ -603,7 +1181,7 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress)
newpath = "/dev/null";
}
- if (delta->binary != 1) {
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) {
git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
}
@@ -611,12 +1189,15 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress)
if (git_buf_oom(pi->buf))
return -1;
- result = pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));
- if (result < 0)
- return result;
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ {
+ giterr_clear();
+ return GIT_EUSER;
+ }
- if (delta->binary != 1)
- return 0;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ return 0;
git_buf_clear(pi->buf);
git_buf_printf(
@@ -625,35 +1206,55 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress)
if (git_buf_oom(pi->buf))
return -1;
- return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf));
+ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+
+ return 0;
}
static int print_patch_hunk(
- void *data,
- git_diff_delta *d,
- git_diff_range *r,
+ const git_diff_delta *d,
+ const git_diff_range *r,
const char *header,
- size_t header_len)
+ size_t header_len,
+ void *data)
{
diff_print_info *pi = data;
+ if (S_ISDIR(d->new_file.mode))
+ return 0;
+
git_buf_clear(pi->buf);
if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
return -1;
- return pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));
+ if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+
+ return 0;
}
static int print_patch_line(
- void *data,
- git_diff_delta *delta,
- git_diff_range *range,
+ const git_diff_delta *delta,
+ const git_diff_range *range,
char line_origin, /* GIT_DIFF_LINE value from above */
const char *content,
- size_t content_len)
+ size_t content_len,
+ void *data)
{
diff_print_info *pi = data;
+ if (S_ISDIR(delta->new_file.mode))
+ return 0;
+
git_buf_clear(pi->buf);
if (line_origin == GIT_DIFF_LINE_ADDITION ||
@@ -666,13 +1267,20 @@ static int print_patch_line(
if (git_buf_oom(pi->buf))
return -1;
- return pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf));
+ if (pi->print_cb(delta, range, line_origin,
+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+
+ return 0;
}
int git_diff_print_patch(
git_diff_list *diff,
- void *cb_data,
- git_diff_data_fn print_cb)
+ git_diff_data_cb print_cb,
+ void *payload)
{
int error;
git_buf buf = GIT_BUF_INIT;
@@ -680,107 +1288,532 @@ int git_diff_print_patch(
pi.diff = diff;
pi.print_cb = print_cb;
- pi.cb_data = cb_data;
+ pi.payload = payload;
pi.buf = &buf;
error = git_diff_foreach(
- diff, &pi, print_patch_file, print_patch_hunk, print_patch_line);
+ diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
git_buf_free(&buf);
return error;
}
-int git_diff_blobs(
- git_blob *old_blob,
- git_blob *new_blob,
- git_diff_options *options,
- void *cb_data,
- git_diff_file_fn file_cb,
- git_diff_hunk_fn hunk_cb,
- git_diff_data_fn line_cb)
-{
- diff_output_info info;
+static void set_data_from_blob(
+ const git_blob *blob, git_map *map, git_diff_file *file)
+{
+ if (blob) {
+ file->size = git_blob_rawsize(blob);
+ git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
+ file->mode = 0644;
+
+ map->len = (size_t)file->size;
+ map->data = (char *)git_blob_rawcontent(blob);
+ } else {
+ file->size = 0;
+ file->flags |= GIT_DIFF_FLAG__NO_DATA;
+
+ map->len = 0;
+ map->data = "";
+ }
+}
+
+static void set_data_from_buffer(
+ const char *buffer, size_t buffer_len, git_map *map, git_diff_file *file)
+{
+ file->size = (git_off_t)buffer_len;
+ file->mode = 0644;
+ map->len = buffer_len;
+
+ if (!buffer) {
+ file->flags |= GIT_DIFF_FLAG__NO_DATA;
+ map->data = NULL;
+ } else {
+ map->data = (char *)buffer;
+ git_odb_hash(&file->oid, buffer, buffer_len, GIT_OBJ_BLOB);
+ }
+}
+
+typedef struct {
+ diff_context ctxt;
git_diff_delta delta;
- mmfile_t old_data, new_data;
- git_map old_map, new_map;
- xpparam_t xdiff_params;
- xdemitconf_t xdiff_config;
- xdemitcb_t xdiff_callback;
- git_blob *new, *old;
+ git_diff_patch patch;
+} diff_single_data;
+
+static int diff_single_init(
+ diff_single_data *data,
+ git_repository *repo,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
+
+ memset(data, 0, sizeof(*data));
+
+ if (diff_context_init(
+ &data->ctxt, NULL, repo, opts,
+ file_cb, hunk_cb, data_cb, payload) < 0)
+ return -1;
+
+ diff_patch_init(&data->ctxt, &data->patch);
+
+ return 0;
+}
+
+static int diff_single_apply(diff_single_data *data)
+{
+ int error;
+ git_diff_delta *delta = &data->delta;
+ bool has_old = ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+ bool has_new = ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+
+ /* finish setting up fake git_diff_delta record and loaded data */
- memset(&delta, 0, sizeof(delta));
+ data->patch.delta = delta;
+ delta->flags = delta->flags & ~KNOWN_BINARY_FLAGS;
- new = new_blob;
- old = old_blob;
+ delta->status = has_new ?
+ (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
+ (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
- if (options && (options->flags & GIT_DIFF_REVERSE)) {
- git_blob *swap = old;
- old = new;
- new = swap;
+ if (git_oid_cmp(&delta->new_file.oid, &delta->old_file.oid) == 0)
+ delta->status = GIT_DELTA_UNMODIFIED;
+
+ if ((error = diff_delta_is_binary_by_content(
+ &data->ctxt, delta, &delta->old_file, &data->patch.old_data)) < 0 ||
+ (error = diff_delta_is_binary_by_content(
+ &data->ctxt, delta, &delta->new_file, &data->patch.new_data)) < 0)
+ goto cleanup;
+
+ data->patch.flags |= GIT_DIFF_PATCH_LOADED;
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
+ delta->status != GIT_DELTA_UNMODIFIED)
+ data->patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
+
+ /* do diffs */
+
+ if (!(error = diff_delta_file_callback(&data->ctxt, delta, 1)))
+ error = diff_patch_generate(&data->ctxt, &data->patch);
+
+cleanup:
+ if (error == GIT_EUSER)
+ giterr_clear();
+
+ diff_patch_unload(&data->patch);
+
+ return error;
+}
+
+int git_diff_blobs(
+ const git_blob *old_blob,
+ const git_blob *new_blob,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error;
+ diff_single_data d;
+ git_repository *repo =
+ new_blob ? git_object_owner((const git_object *)new_blob) :
+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
+
+ if (!repo) /* Hmm, given two NULL blobs, silently do no callbacks? */
+ return 0;
+
+ if ((error = diff_single_init(
+ &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
+ return error;
+
+ if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
+ const git_blob *swap = old_blob;
+ old_blob = new_blob;
+ new_blob = swap;
}
- if (old) {
- old_data.ptr = (char *)git_blob_rawcontent(old);
- old_data.size = git_blob_rawsize(old);
- git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old));
+ set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
+ set_data_from_blob(new_blob, &d.patch.new_data, &d.delta.new_file);
+
+ return diff_single_apply(&d);
+}
+
+int git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *buf,
+ size_t buflen,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_data_cb data_cb,
+ void *payload)
+{
+ int error;
+ diff_single_data d;
+ git_repository *repo =
+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
+
+ if (!repo && !buf) /* Hmm, given NULLs, silently do no callbacks? */
+ return 0;
+
+ if ((error = diff_single_init(
+ &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)
+ return error;
+
+ if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {
+ set_data_from_buffer(buf, buflen, &d.patch.old_data, &d.delta.old_file);
+ set_data_from_blob(old_blob, &d.patch.new_data, &d.delta.new_file);
} else {
- old_data.ptr = "";
- old_data.size = 0;
+ set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);
+ set_data_from_buffer(buf, buflen, &d.patch.new_data, &d.delta.new_file);
}
- if (new) {
- new_data.ptr = (char *)git_blob_rawcontent(new);
- new_data.size = git_blob_rawsize(new);
- git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new));
- } else {
- new_data.ptr = "";
- new_data.size = 0;
+ return diff_single_apply(&d);
+}
+
+size_t git_diff_num_deltas(git_diff_list *diff)
+{
+ assert(diff);
+ return (size_t)diff->deltas.length;
+}
+
+size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
+{
+ size_t i, count = 0;
+ git_diff_delta *delta;
+
+ assert(diff);
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ count += (delta->status == type);
}
- /* populate a "fake" delta record */
- delta.status = new ?
- (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
- (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
+ return count;
+}
- if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0)
- delta.status = GIT_DELTA_UNMODIFIED;
+int git_diff_get_patch(
+ git_diff_patch **patch_ptr,
+ const git_diff_delta **delta_ptr,
+ git_diff_list *diff,
+ size_t idx)
+{
+ int error;
+ diff_context ctxt;
+ git_diff_delta *delta;
+ git_diff_patch *patch;
- delta.old_file.size = old_data.size;
- delta.new_file.size = new_data.size;
+ if (patch_ptr)
+ *patch_ptr = NULL;
+ if (delta_ptr)
+ *delta_ptr = NULL;
- fill_map_from_mmfile(&old_map, &old_data);
- fill_map_from_mmfile(&new_map, &new_data);
+ if (diff_required(diff, "git_diff_get_patch") < 0)
+ return -1;
- if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0)
+ if (diff_context_init(
+ &ctxt, diff, NULL, NULL,
+ NULL, diff_patch_hunk_cb, diff_patch_line_cb, NULL) < 0)
return -1;
- if (file_cb != NULL) {
- int error = file_cb(cb_data, &delta, 1);
- if (error < 0)
- return error;
+ delta = git_vector_get(&diff->deltas, idx);
+ if (!delta) {
+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
+ return GIT_ENOTFOUND;
}
- /* don't do hunk and line diffs if the two blobs are identical */
- if (delta.status == GIT_DELTA_UNMODIFIED)
+ if (delta_ptr)
+ *delta_ptr = delta;
+
+ if (!patch_ptr &&
+ ((delta->flags & KNOWN_BINARY_FLAGS) != 0 ||
+ (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
+ return 0;
+
+ if (git_diff_delta__should_skip(ctxt.opts, delta))
return 0;
- /* don't do hunk and line diffs if file is binary */
- if (delta.binary == 1)
+ /* Don't load the patch if the user doesn't want it */
+ if (!patch_ptr)
return 0;
- info.diff = NULL;
- info.delta = &delta;
- info.cb_data = cb_data;
- info.hunk_cb = hunk_cb;
- info.line_cb = line_cb;
+ patch = diff_patch_alloc(&ctxt, delta);
+ if (!patch)
+ return -1;
+
+ if (!(error = diff_patch_load(&ctxt, patch))) {
+ ctxt.payload = patch;
- setup_xdiff_options(options, &xdiff_config, &xdiff_params);
- memset(&xdiff_callback, 0, sizeof(xdiff_callback));
- xdiff_callback.outf = diff_output_cb;
- xdiff_callback.priv = &info;
+ error = diff_patch_generate(&ctxt, patch);
- xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback);
+ if (error == GIT_EUSER)
+ error = ctxt.error;
+ }
+
+ if (error)
+ git_diff_patch_free(patch);
+ else if (patch_ptr)
+ *patch_ptr = patch;
+
+ return error;
+}
+
+void git_diff_patch_free(git_diff_patch *patch)
+{
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, diff_patch_free);
+}
+
+const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
+{
+ assert(patch);
+ return patch->delta;
+}
+
+size_t git_diff_patch_num_hunks(git_diff_patch *patch)
+{
+ assert(patch);
+ return patch->hunks_size;
+}
+
+int git_diff_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_diff_patch *patch)
+{
+ size_t totals[3], idx;
+
+ memset(totals, 0, sizeof(totals));
+
+ for (idx = 0; idx < patch->lines_size; ++idx) {
+ switch (patch->lines[idx].origin) {
+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;
+ default:
+ /* diff --stat and --numstat don't count EOFNL marks because
+ * they will always be paired with a ADDITION or DELETION line.
+ */
+ break;
+ }
+ }
+
+ if (total_ctxt)
+ *total_ctxt = totals[0];
+ if (total_adds)
+ *total_adds = totals[1];
+ if (total_dels)
+ *total_dels = totals[2];
+
+ return 0;
+}
+
+int git_diff_patch_get_hunk(
+ const git_diff_range **range,
+ const char **header,
+ size_t *header_len,
+ size_t *lines_in_hunk,
+ git_diff_patch *patch,
+ size_t hunk_idx)
+{
+ diff_patch_hunk *hunk;
+
+ assert(patch);
+
+ if (hunk_idx >= patch->hunks_size) {
+ if (range) *range = NULL;
+ if (header) *header = NULL;
+ if (header_len) *header_len = 0;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return GIT_ENOTFOUND;
+ }
+
+ hunk = &patch->hunks[hunk_idx];
+
+ if (range) *range = &hunk->range;
+ if (header) *header = hunk->header;
+ if (header_len) *header_len = hunk->header_len;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
+
+ return 0;
+}
+
+int git_diff_patch_num_lines_in_hunk(
+ git_diff_patch *patch,
+ size_t hunk_idx)
+{
+ assert(patch);
+
+ if (hunk_idx >= patch->hunks_size)
+ return GIT_ENOTFOUND;
+ else
+ return (int)patch->hunks[hunk_idx].line_count;
+}
+
+int git_diff_patch_get_line_in_hunk(
+ char *line_origin,
+ const char **content,
+ size_t *content_len,
+ int *old_lineno,
+ int *new_lineno,
+ git_diff_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk)
+{
+ diff_patch_hunk *hunk;
+ diff_patch_line *line;
+
+ assert(patch);
+
+ if (hunk_idx >= patch->hunks_size)
+ goto notfound;
+ hunk = &patch->hunks[hunk_idx];
+
+ if (line_of_hunk >= hunk->line_count)
+ goto notfound;
+
+ line = &patch->lines[hunk->line_start + line_of_hunk];
+
+ if (line_origin) *line_origin = line->origin;
+ if (content) *content = line->ptr;
+ if (content_len) *content_len = line->len;
+ if (old_lineno) *old_lineno = (int)line->oldno;
+ if (new_lineno) *new_lineno = (int)line->newno;
+
+ return 0;
+
+notfound:
+ if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
+ if (content) *content = NULL;
+ if (content_len) *content_len = 0;
+ if (old_lineno) *old_lineno = -1;
+ if (new_lineno) *new_lineno = -1;
+
+ return GIT_ENOTFOUND;
+}
+
+static int print_to_buffer_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len,
+ void *payload)
+{
+ git_buf *output = payload;
+ GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
+ return git_buf_put(output, content, content_len);
+}
+
+int git_diff_patch_print(
+ git_diff_patch *patch,
+ git_diff_data_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+ size_t h, l;
+
+ assert(patch && print_cb);
+
+ pi.diff = patch->diff;
+ pi.print_cb = print_cb;
+ pi.payload = payload;
+ pi.buf = &temp;
+
+ error = print_patch_file(patch->delta, 0, &pi);
+
+ for (h = 0; h < patch->hunks_size && !error; ++h) {
+ diff_patch_hunk *hunk = &patch->hunks[h];
+
+ error = print_patch_hunk(
+ patch->delta, &hunk->range, hunk->header, hunk->header_len, &pi);
+
+ for (l = 0; l < hunk->line_count && !error; ++l) {
+ diff_patch_line *line = &patch->lines[hunk->line_start + l];
+
+ error = print_patch_line(
+ patch->delta, &hunk->range,
+ line->origin, line->ptr, line->len, &pi);
+ }
+ }
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
+int git_diff_patch_to_str(
+ char **string,
+ git_diff_patch *patch)
+{
+ int error;
+ git_buf output = GIT_BUF_INIT;
+
+ error = git_diff_patch_print(patch, print_to_buffer_cb, &output);
+
+ /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
+ * meaning a memory allocation failure, so just map to -1...
+ */
+ if (error == GIT_EUSER)
+ error = -1;
+
+ *string = git_buf_detach(&output);
+
+ return error;
+}
+
+int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ void *payload)
+{
+ int cmp;
+ git_diff_delta *i2h, *w2i;
+ size_t i, j, i_max, j_max;
+ int (*strcomp)(const char *, const char *);
+
+ i_max = idx2head ? idx2head->deltas.length : 0;
+ j_max = wd2idx ? wd2idx->deltas.length : 0;
+
+ /* Get appropriate strcmp function */
+ strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL;
+
+ /* Assert both iterators use matching ignore-case. If this function ever
+ * supports merging diffs that are not sorted by the same function, then
+ * it will need to spool and sort on one of the results before merging
+ */
+ if (idx2head && wd2idx) {
+ assert(idx2head->strcomp == wd2idx->strcomp);
+ }
+
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
+ i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
+ w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
+
+ cmp = !w2i ? -1 : !i2h ? 1 :
+ strcomp(i2h->old_file.path, w2i->old_file.path);
+
+ if (cmp < 0) {
+ if (cb(i2h, NULL, payload))
+ return GIT_EUSER;
+ i++;
+ } else if (cmp > 0) {
+ if (cb(NULL, w2i, payload))
+ return GIT_EUSER;
+ j++;
+ } else {
+ if (cb(i2h, w2i, payload))
+ return GIT_EUSER;
+ i++; j++;
+ }
+ }
return 0;
}
diff --git a/src/diff_output.h b/src/diff_output.h
new file mode 100644
index 000000000..083355676
--- /dev/null
+++ b/src/diff_output.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_output_h__
+#define INCLUDE_diff_output_h__
+
+#include "git2/blob.h"
+#include "diff.h"
+#include "map.h"
+#include "xdiff/xdiff.h"
+
+#define MAX_DIFF_FILESIZE 0x20000000
+
+enum {
+ GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
+ GIT_DIFF_PATCH_PREPPED = (1 << 1),
+ GIT_DIFF_PATCH_LOADED = (1 << 2),
+ GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
+ GIT_DIFF_PATCH_DIFFED = (1 << 4),
+};
+
+/* context for performing diffs */
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ const git_diff_options *opts;
+ git_diff_file_cb file_cb;
+ git_diff_hunk_cb hunk_cb;
+ git_diff_data_cb data_cb;
+ void *payload;
+ int error;
+ git_diff_range range;
+ xdemitconf_t xdiff_config;
+ xpparam_t xdiff_params;
+} diff_context;
+
+/* cached information about a single span in a diff */
+typedef struct diff_patch_line diff_patch_line;
+struct diff_patch_line {
+ const char *ptr;
+ size_t len;
+ size_t lines, oldno, newno;
+ char origin;
+};
+
+/* cached information about a hunk in a diff */
+typedef struct diff_patch_hunk diff_patch_hunk;
+struct diff_patch_hunk {
+ git_diff_range range;
+ char header[128];
+ size_t header_len;
+ size_t line_start;
+ size_t line_count;
+};
+
+struct git_diff_patch {
+ git_refcount rc;
+ git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ git_diff_delta *delta;
+ diff_context *ctxt; /* only valid while generating patch */
+ git_iterator_type_t old_src;
+ git_iterator_type_t new_src;
+ git_blob *old_blob;
+ git_blob *new_blob;
+ git_map old_data;
+ git_map new_data;
+ uint32_t flags;
+ diff_patch_hunk *hunks;
+ size_t hunks_asize, hunks_size;
+ diff_patch_line *lines;
+ size_t lines_asize, lines_size;
+ size_t oldno, newno;
+};
+
+/* context for performing diff on a single delta */
+typedef struct {
+ git_diff_patch *patch;
+ uint32_t prepped : 1;
+ uint32_t loaded : 1;
+ uint32_t diffable : 1;
+ uint32_t diffed : 1;
+} diff_delta_context;
+
+extern int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ void *payload);
+
+#endif
diff --git a/src/diff_tform.c b/src/diff_tform.c
new file mode 100644
index 000000000..efcb19d95
--- /dev/null
+++ b/src/diff_tform.c
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "diff.h"
+#include "git2/config.h"
+#include "git2/blob.h"
+#include "hashsig.h"
+
+static git_diff_delta *diff_delta__dup(
+ const git_diff_delta *d, git_pool *pool)
+{
+ git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
+ if (!delta)
+ return NULL;
+
+ memcpy(delta, d, sizeof(git_diff_delta));
+
+ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
+ if (delta->old_file.path == NULL)
+ goto fail;
+
+ if (d->new_file.path != d->old_file.path) {
+ delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
+ if (delta->new_file.path == NULL)
+ goto fail;
+ } else {
+ delta->new_file.path = delta->old_file.path;
+ }
+
+ return delta;
+
+fail:
+ git__free(delta);
+ return NULL;
+}
+
+static git_diff_delta *diff_delta__merge_like_cgit(
+ const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
+{
+ git_diff_delta *dup;
+
+ /* Emulate C git for merging two diffs (a la 'git diff <sha>').
+ *
+ * When C git does a diff between the work dir and a tree, it actually
+ * diffs with the index but uses the workdir contents. This emulates
+ * those choices so we can emulate the type of diff.
+ *
+ * We have three file descriptions here, let's call them:
+ * f1 = a->old_file
+ * f2 = a->new_file AND b->old_file
+ * f3 = b->new_file
+ */
+
+ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
+ if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
+ return diff_delta__dup(a, pool);
+
+ /* otherwise, base this diff on the 'b' diff */
+ if ((dup = diff_delta__dup(b, pool)) == NULL)
+ return NULL;
+
+ /* If 'a' status is uninteresting, then we're done */
+ if (a->status == GIT_DELTA_UNMODIFIED)
+ return dup;
+
+ assert(a->status != GIT_DELTA_UNMODIFIED);
+ assert(b->status != GIT_DELTA_UNMODIFIED);
+
+ /* A cgit exception is that the diff of a file that is only in the
+ * index (i.e. not in HEAD nor workdir) is given as empty.
+ */
+ if (dup->status == GIT_DELTA_DELETED) {
+ if (a->status == GIT_DELTA_ADDED)
+ dup->status = GIT_DELTA_UNMODIFIED;
+ /* else don't overwrite DELETE status */
+ } else {
+ dup->status = a->status;
+ }
+
+ git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
+ dup->old_file.mode = a->old_file.mode;
+ dup->old_file.size = a->old_file.size;
+ dup->old_file.flags = a->old_file.flags;
+
+ return dup;
+}
+
+int git_diff_merge(
+ git_diff_list *onto,
+ const git_diff_list *from)
+{
+ int error = 0;
+ git_pool onto_pool;
+ git_vector onto_new;
+ git_diff_delta *delta;
+ bool ignore_case = false;
+ unsigned int i, j;
+
+ assert(onto && from);
+
+ if (!from->deltas.length)
+ return 0;
+
+ if (git_vector_init(
+ &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 ||
+ git_pool_init(&onto_pool, 1, 0) < 0)
+ return -1;
+
+ if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
+ {
+ ignore_case = true;
+
+ /* This function currently only supports merging diff lists that
+ * are sorted identically. */
+ assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);
+ }
+
+ for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
+ git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
+ const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
+ int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
+
+ if (cmp < 0) {
+ delta = diff_delta__dup(o, &onto_pool);
+ i++;
+ } else if (cmp > 0) {
+ delta = diff_delta__dup(f, &onto_pool);
+ j++;
+ } else {
+ delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
+ i++;
+ j++;
+ }
+
+ /* the ignore rules for the target may not match the source
+ * or the result of a merged delta could be skippable...
+ */
+ if (git_diff_delta__should_skip(&onto->opts, delta)) {
+ git__free(delta);
+ continue;
+ }
+
+ if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
+ break;
+ }
+
+ if (!error) {
+ git_vector_swap(&onto->deltas, &onto_new);
+ git_pool_swap(&onto->pool, &onto_pool);
+ onto->new_src = from->new_src;
+
+ /* prefix strings also come from old pool, so recreate those.*/
+ onto->opts.old_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
+ onto->opts.new_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
+ }
+
+ git_vector_foreach(&onto_new, i, delta)
+ git__free(delta);
+ git_vector_free(&onto_new);
+ git_pool_clear(&onto_pool);
+
+ return error;
+}
+
+static int find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)p;
+ int error = 0;
+
+ GIT_UNUSED(f);
+ error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+}
+
+static int find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)p;
+ int error = 0;
+
+ GIT_UNUSED(f);
+ error = git_hashsig_create((git_hashsig **)out, buf, len, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+}
+
+static void find_similar__hashsig_free(void *sig, void *payload)
+{
+ GIT_UNUSED(payload);
+ git_hashsig_free(sig);
+}
+
+static int find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload)
+{
+ GIT_UNUSED(payload);
+ *score = git_hashsig_compare(siga, sigb);
+ return 0;
+}
+
+#define DEFAULT_THRESHOLD 50
+#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
+#define DEFAULT_TARGET_LIMIT 200
+
+static int normalize_find_opts(
+ git_diff_list *diff,
+ git_diff_find_options *opts,
+ git_diff_find_options *given)
+{
+ git_config *cfg = NULL;
+
+ if (diff->repo != NULL &&
+ git_repository_config__weakptr(&cfg, diff->repo) < 0)
+ return -1;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(*opts));
+ else {
+ const char *val = NULL;
+
+ GIT_INIT_STRUCTURE(opts, GIT_DIFF_FIND_OPTIONS_VERSION);
+
+ opts->flags = GIT_DIFF_FIND_RENAMES;
+
+ if (git_config_get_string(&val, cfg, "diff.renames") < 0)
+ giterr_clear();
+ else if (val &&
+ (!strcasecmp(val, "copies") || !strcasecmp(val, "copy")))
+ opts->flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ }
+
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options");
+
+ /* some flags imply others */
+
+ if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+
+ if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
+ opts->flags |= GIT_DIFF_FIND_COPIES;
+
+#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
+
+ if (USE_DEFAULT(opts->rename_threshold))
+ opts->rename_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->rename_from_rewrite_threshold))
+ opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->copy_threshold))
+ opts->copy_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->break_rewrite_threshold))
+ opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD;
+
+#undef USE_DEFAULT
+
+ if (!opts->target_limit) {
+ int32_t limit = 0;
+
+ opts->target_limit = DEFAULT_TARGET_LIMIT;
+
+ if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)
+ giterr_clear();
+ else 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 = 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;
+
+ 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 apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
+{
+ git_vector onto = GIT_VECTOR_INIT;
+ size_t i;
+ git_diff_delta *delta;
+
+ if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
+ return -1;
+
+ /* build new delta list without TO_DELETE and splitting TO_SPLIT */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ continue;
+
+ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {
+ git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ if (!deleted)
+ goto on_error;
+
+ deleted->status = GIT_DELTA_DELETED;
+ memset(&deleted->new_file, 0, sizeof(deleted->new_file));
+ deleted->new_file.path = deleted->old_file.path;
+ deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (git_vector_insert(&onto, deleted) < 0)
+ goto on_error;
+
+ delta->status = GIT_DELTA_ADDED;
+ memset(&delta->old_file, 0, sizeof(delta->old_file));
+ delta->old_file.path = delta->new_file.path;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+ }
+
+ if (git_vector_insert(&onto, delta) < 0)
+ goto on_error;
+ }
+
+ /* cannot return an error past this point */
+ git_vector_foreach(&diff->deltas, i, delta)
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ git__free(delta);
+
+ /* swap new delta list into place */
+ git_vector_sort(&onto);
+ git_vector_swap(&diff->deltas, &onto);
+ git_vector_free(&onto);
+
+ return 0;
+
+on_error:
+ git_vector_foreach(&onto, i, delta)
+ git__free(delta);
+
+ git_vector_free(&onto);
+
+ return -1;
+}
+
+GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx)
+{
+ git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2);
+ return (idx & 1) ? &delta->new_file : &delta->old_file;
+}
+
+static int similarity_calc(
+ git_diff_list *diff,
+ git_diff_find_options *opts,
+ size_t file_idx,
+ void **cache)
+{
+ int error = 0;
+ git_diff_file *file = similarity_get_file(diff, file_idx);
+ git_iterator_type_t src = (file_idx & 1) ? diff->old_src : diff->new_src;
+
+ if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */
+ git_buf path = GIT_BUF_INIT;
+
+ /* TODO: apply wd-to-odb filters to file data if necessary */
+
+ if (!(error = git_buf_joinpath(
+ &path, git_repository_workdir(diff->repo), file->path)))
+ error = opts->metric->file_signature(
+ &cache[file_idx], file, path.ptr, opts->metric->payload);
+
+ git_buf_free(&path);
+ } else { /* compute hashsig from blob buffer */
+ git_blob *blob = NULL;
+ git_off_t blobsize;
+
+ /* TODO: add max size threshold a la diff? */
+
+ if ((error = git_blob_lookup(&blob, diff->repo, &file->oid)) < 0)
+ return error;
+
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize)) /* ? what to do ? */
+ blobsize = (size_t)-1;
+
+ error = opts->metric->buffer_signature(
+ &cache[file_idx], file, git_blob_rawcontent(blob),
+ (size_t)blobsize, opts->metric->payload);
+
+ git_blob_free(blob);
+ }
+
+ return error;
+}
+
+static int similarity_measure(
+ git_diff_list *diff,
+ git_diff_find_options *opts,
+ void **cache,
+ size_t a_idx,
+ size_t b_idx)
+{
+ int score = 0;
+ git_diff_file *a_file = similarity_get_file(diff, a_idx);
+ git_diff_file *b_file = similarity_get_file(diff, b_idx);
+
+ if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
+ return 0;
+
+ if (git_oid_cmp(&a_file->oid, &b_file->oid) == 0)
+ return 100;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0)
+ return -1;
+ if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0)
+ return -1;
+
+ /* some metrics may not wish to process this file (too big / too small) */
+ if (!cache[a_idx] || !cache[b_idx])
+ 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;
+}
+
+#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0)
+
+int git_diff_find_similar(
+ git_diff_list *diff,
+ git_diff_find_options *given_opts)
+{
+ size_t i, j, cache_size, *matches;
+ int error = 0, similarity;
+ git_diff_delta *from, *to;
+ git_diff_find_options opts;
+ size_t tried_targets, num_rewrites = 0;
+ void **cache;
+
+ if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
+ return error;
+
+ /* TODO: maybe abort if deltas.length > target_limit ??? */
+
+ cache_size = diff->deltas.length * 2; /* must store b/c length may change */
+ cache = git__calloc(cache_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(cache);
+
+ matches = git__calloc(diff->deltas.length, sizeof(size_t));
+ GITERR_CHECK_ALLOC(matches);
+
+ /* first break MODIFIED records that are too different (if requested) */
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) {
+ git_vector_foreach(&diff->deltas, i, from) {
+ if (from->status != GIT_DELTA_MODIFIED)
+ continue;
+
+ similarity = similarity_measure(
+ diff, &opts, cache, 2 * i, 2 * i + 1);
+
+ if (similarity < 0) {
+ error = similarity;
+ goto cleanup;
+ }
+
+ if ((unsigned int)similarity < opts.break_rewrite_threshold) {
+ from->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites++;
+ }
+ }
+ }
+
+ /* next find the most similar delta for each rename / copy candidate */
+
+ git_vector_foreach(&diff->deltas, i, from) {
+ tried_targets = 0;
+
+ /* skip things that aren't blobs */
+ if (GIT_MODE_TYPE(from->old_file.mode) !=
+ GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+ continue;
+
+ /* don't check UNMODIFIED files as source unless given option */
+ if (from->status == GIT_DELTA_UNMODIFIED &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ continue;
+
+ /* skip all but DELETED files unless copy detection is on */
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES) &&
+ from->status != GIT_DELTA_DELETED &&
+ (from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
+ continue;
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ if (i == j)
+ continue;
+
+ /* skip things that aren't blobs */
+ if (GIT_MODE_TYPE(to->new_file.mode) !=
+ GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+ continue;
+
+ switch (to->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_RENAMED:
+ case GIT_DELTA_COPIED:
+ break;
+ case GIT_DELTA_MODIFIED:
+ if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)
+ continue;
+ break;
+ default:
+ /* only the above status values should be checked */
+ continue;
+ }
+
+ /* cap on maximum files we'll examine (per "from" file) */
+ if (++tried_targets > opts.target_limit)
+ break;
+
+ /* calculate similarity and see if this pair beats the
+ * similarity score of the current best pair.
+ */
+ similarity = similarity_measure(
+ diff, &opts, cache, 2 * i, 2 * j + 1);
+
+ if (similarity < 0) {
+ error = similarity;
+ goto cleanup;
+ }
+
+ if (to->similarity < (unsigned int)similarity) {
+ to->similarity = (unsigned int)similarity;
+ matches[j] = i + 1;
+ }
+ }
+ }
+
+ /* next rewrite the diffs with renames / copies */
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ if (!matches[j]) {
+ assert(to->similarity == 0);
+ continue;
+ }
+
+ i = matches[j] - 1;
+ from = GIT_VECTOR_GET(&diff->deltas, i);
+ assert(from);
+
+ /* four possible outcomes here:
+ * 1. old DELETED and if over rename threshold,
+ * new becomes RENAMED and old goes away
+ * 2. old SPLIT and if over rename threshold,
+ * new becomes RENAMED and old becomes ADDED (clear SPLIT)
+ * 3. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and
+ * old is more similar to new than it is to itself, in which
+ * case, new becomes RENAMED and old becomed ADDED
+ * 4. otherwise if over copy threshold, new becomes COPIED
+ */
+
+ if (from->status == GIT_DELTA_DELETED) {
+ if (to->similarity < opts.rename_threshold) {
+ to->similarity = 0;
+ continue;
+ }
+
+ to->status = GIT_DELTA_RENAMED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+
+ from->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ num_rewrites++;
+
+ continue;
+ }
+
+ if (from->status == GIT_DELTA_MODIFIED &&
+ (from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
+ {
+ if (to->similarity < opts.rename_threshold) {
+ to->similarity = 0;
+ continue;
+ }
+
+ to->status = GIT_DELTA_RENAMED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+
+ from->status = GIT_DELTA_ADDED;
+ from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ memset(&from->old_file, 0, sizeof(from->old_file));
+ num_rewrites--;
+
+ continue;
+ }
+
+ if (from->status == GIT_DELTA_MODIFIED &&
+ FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ to->similarity > opts.rename_threshold)
+ {
+ similarity = similarity_measure(
+ diff, &opts, cache, 2 * i, 2 * i + 1);
+
+ if (similarity < 0) {
+ error = similarity;
+ goto cleanup;
+ }
+
+ if ((unsigned int)similarity < opts.rename_from_rewrite_threshold) {
+ to->status = GIT_DELTA_RENAMED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+
+ from->status = GIT_DELTA_ADDED;
+ memset(&from->old_file, 0, sizeof(from->old_file));
+ from->old_file.path = to->old_file.path;
+ from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ continue;
+ }
+ }
+
+ if (to->similarity < opts.copy_threshold) {
+ to->similarity = 0;
+ continue;
+ }
+
+ /* convert "to" to a COPIED record */
+ to->status = GIT_DELTA_COPIED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ }
+
+ if (num_rewrites > 0) {
+ assert(num_rewrites < diff->deltas.length);
+
+ error = apply_splits_and_deletes(
+ diff, diff->deltas.length - num_rewrites);
+ }
+
+cleanup:
+ git__free(matches);
+
+ for (i = 0; i < cache_size; ++i) {
+ if (cache[i] != NULL)
+ opts.metric->free_signature(cache[i], opts.metric->payload);
+ }
+ git__free(cache);
+
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ return error;
+}
+
+#undef FLAG_SET
diff --git a/src/errors.c b/src/errors.c
index d43d7d9b5..e2629f69e 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -40,52 +40,34 @@ void giterr_set(int error_class, const char *string, ...)
{
git_buf buf = GIT_BUF_INIT;
va_list arglist;
-
- int unix_error_code = 0;
-
-#ifdef GIT_WIN32
- DWORD win32_error_code = 0;
-#endif
-
- if (error_class == GITERR_OS) {
- unix_error_code = errno;
- errno = 0;
-
#ifdef GIT_WIN32
- win32_error_code = GetLastError();
- SetLastError(0);
+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0;
#endif
- }
+ int error_code = (error_class == GITERR_OS) ? errno : 0;
va_start(arglist, string);
git_buf_vprintf(&buf, string, arglist);
va_end(arglist);
- /* automatically suffix strerror(errno) for GITERR_OS errors */
if (error_class == GITERR_OS) {
-
- if (unix_error_code != 0) {
+#ifdef GIT_WIN32
+ char * win32_error = git_win32_get_error_message(win32_error_code);
+ if (win32_error) {
git_buf_PUTS(&buf, ": ");
- git_buf_puts(&buf, strerror(unix_error_code));
- }
+ git_buf_puts(&buf, win32_error);
+ git__free(win32_error);
-#ifdef GIT_WIN32
- else if (win32_error_code != 0) {
- LPVOID lpMsgBuf = NULL;
-
- FormatMessage(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
-
- if (lpMsgBuf) {
- git_buf_PUTS(&buf, ": ");
- git_buf_puts(&buf, lpMsgBuf);
- LocalFree(lpMsgBuf);
- }
+ SetLastError(0);
}
+ else
#endif
+ if (error_code) {
+ git_buf_PUTS(&buf, ": ");
+ git_buf_puts(&buf, strerror(error_code));
+ }
+
+ if (error_code)
+ errno = 0;
}
if (!git_buf_oom(&buf))
@@ -94,22 +76,40 @@ void giterr_set(int error_class, const char *string, ...)
void giterr_set_str(int error_class, const char *string)
{
- char *message = git__strdup(string);
+ char *message;
+
+ assert(string);
+
+ message = git__strdup(string);
if (message)
set_error(error_class, message);
}
-void giterr_set_regex(const regex_t *regex, int error_code)
+int giterr_set_regex(const regex_t *regex, int error_code)
{
char error_buf[1024];
+
+ assert(error_code);
+
regerror(error_code, regex, error_buf, sizeof(error_buf));
giterr_set_str(GITERR_REGEX, error_buf);
+
+ if (error_code == REG_NOMATCH)
+ return GIT_ENOTFOUND;
+
+ return GIT_EINVALIDSPEC;
}
void giterr_clear(void)
{
+ set_error(0, NULL);
GIT_GLOBAL->last_error = NULL;
+
+ errno = 0;
+#ifdef GIT_WIN32
+ SetLastError(0);
+#endif
}
const git_error *giterr_last(void)
diff --git a/src/fetch.c b/src/fetch.c
index c92cf4ef5..b60a95232 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -1,18 +1,16 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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/remote.h"
#include "git2/oid.h"
#include "git2/refs.h"
#include "git2/revwalk.h"
-#include "git2/indexer.h"
+#include "git2/transport.h"
#include "common.h"
-#include "transport.h"
#include "remote.h"
#include "refspec.h"
#include "pack.h"
@@ -21,7 +19,7 @@
struct filter_payload {
git_remote *remote;
- const git_refspec *spec;
+ const git_refspec *spec, *tagspec;
git_odb *odb;
int found_head;
};
@@ -29,18 +27,21 @@ struct filter_payload {
static int filter_ref__cb(git_remote_head *head, void *payload)
{
struct filter_payload *p = payload;
+ int match = 0;
- if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) {
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0)
p->found_head = 1;
- } else {
- /* If it doesn't match the refpec, we don't want it */
- if (!git_refspec_src_matches(p->spec, head->name))
- return 0;
-
- /* Don't even try to ask for the annotation target */
- if (!git__suffixcmp(head->name, "^{}"))
- return 0;
- }
+ else if (git_refspec_src_matches(p->spec, head->name))
+ match = 1;
+ else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&
+ git_refspec_src_matches(p->tagspec, head->name))
+ match = 1;
+
+ if (!match)
+ return 0;
/* If we have the object, mark it so we don't ask for it */
if (git_odb_exists(p->odb, &head->oid))
@@ -54,8 +55,12 @@ static int filter_ref__cb(git_remote_head *head, void *payload)
static int filter_wants(git_remote *remote)
{
struct filter_payload p;
+ git_refspec tagspec;
+ int error = -1;
git_vector_clear(&remote->refs);
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return error;
/*
* The fetch refspec can be NULL, and what this means is that the
@@ -64,13 +69,19 @@ static int filter_wants(git_remote *remote)
* 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;
if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0)
- return -1;
+ goto cleanup;
- return remote->transport->ls(remote->transport, &filter_ref__cb, &p);
+ error = git_remote_ls(remote, filter_ref__cb, &p);
+
+cleanup:
+ git_refspec__free(&tagspec);
+
+ return error;
}
/*
@@ -81,7 +92,7 @@ static int filter_wants(git_remote *remote)
int git_fetch_negotiate(git_remote *remote)
{
git_transport *t = remote->transport;
-
+
if (filter_wants(remote) < 0) {
giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
return -1;
@@ -92,109 +103,24 @@ int git_fetch_negotiate(git_remote *remote)
return 0;
/*
- * Now we have everything set up so we can start tell the server
- * what we want and what we have.
+ * Now we have everything set up so we can start tell the
+ * server what we want and what we have.
*/
- return t->negotiate_fetch(t, remote->repo, &remote->refs);
+ return t->negotiate_fetch(t,
+ remote->repo,
+ (const git_remote_head * const *)remote->refs.contents,
+ remote->refs.length);
}
-int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+int git_fetch_download_pack(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
+ git_transport *t = remote->transport;
+
if(!remote->need_pack)
return 0;
- return remote->transport->download_pack(remote->transport, remote->repo, bytes, stats);
-}
-
-/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */
-int git_fetch__download_pack(
- const char *buffered,
- size_t buffered_size,
- GIT_SOCKET fd,
- git_repository *repo,
- git_off_t *bytes,
- git_indexer_stats *stats)
-{
- int recvd;
- char buff[1024];
- gitno_buffer buf;
- git_indexer_stream *idx;
-
- gitno_buffer_setup(&buf, buff, sizeof(buff), fd);
-
- if (memcmp(buffered, "PACK", strlen("PACK"))) {
- giterr_set(GITERR_NET, "The pack doesn't start with the signature");
- return -1;
- }
-
- if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0)
- return -1;
-
- memset(stats, 0, sizeof(git_indexer_stats));
- if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0)
- goto on_error;
-
- *bytes = buffered_size;
-
- do {
- if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 0)
- goto on_error;
-
- gitno_consume_n(&buf, buf.offset);
- if ((recvd = gitno_recv(&buf)) < 0)
- goto on_error;
-
- *bytes += recvd;
- } while(recvd > 0);
-
- if (git_indexer_stream_finalize(idx, stats))
- goto on_error;
-
- git_indexer_stream_free(idx);
- return 0;
-
-on_error:
- git_indexer_stream_free(idx);
- return -1;
-}
-
-int git_fetch_setup_walk(git_revwalk **out, git_repository *repo)
-{
- git_revwalk *walk;
- git_strarray refs;
- unsigned int i;
- git_reference *ref;
-
- if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
- return -1;
-
- if (git_revwalk_new(&walk, repo) < 0)
- return -1;
-
- git_revwalk_sorting(walk, GIT_SORT_TIME);
-
- for (i = 0; i < refs.count; ++i) {
- /* No tags */
- if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
- continue;
-
- if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
- goto on_error;
-
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
- continue;
- if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)
- goto on_error;
-
- git_reference_free(ref);
- }
-
- git_strarray_free(&refs);
- *out = walk;
- return 0;
-
-on_error:
- git_reference_free(ref);
- git_strarray_free(&refs);
- return -1;
+ return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload);
}
diff --git a/src/fetch.h b/src/fetch.h
index b3192a563..059251d04 100644
--- a/src/fetch.h
+++ b/src/fetch.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,10 +10,19 @@
#include "netops.h"
int git_fetch_negotiate(git_remote *remote);
-int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats);
-int git_fetch__download_pack(const char *buffered, size_t buffered_size, GIT_SOCKET fd,
- git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);
+int git_fetch_download_pack(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
+int git_fetch__download_pack(
+ git_transport *t,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
int git_fetch_setup_walk(git_revwalk **out, git_repository *repo);
#endif
diff --git a/src/fetchhead.c b/src/fetchhead.c
new file mode 100644
index 000000000..4dcebb857
--- /dev/null
+++ b/src/fetchhead.c
@@ -0,0 +1,295 @@
+/*
+ * 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/types.h"
+#include "git2/oid.h"
+
+#include "fetchhead.h"
+#include "common.h"
+#include "buffer.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "refs.h"
+#include "repository.h"
+
+int git_fetchhead_ref_cmp(const void *a, const void *b)
+{
+ const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
+ const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
+
+ if (one->is_merge && !two->is_merge)
+ return -1;
+ if (two->is_merge && !one->is_merge)
+ return 1;
+
+ if (one->ref_name && two->ref_name)
+ return strcmp(one->ref_name, two->ref_name);
+ else if (one->ref_name)
+ return -1;
+ else if (two->ref_name)
+ return 1;
+
+ return 0;
+}
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url)
+{
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
+ GITERR_CHECK_ALLOC(fetchhead_ref);
+
+ memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
+
+ git_oid_cpy(&fetchhead_ref->oid, oid);
+ fetchhead_ref->is_merge = is_merge;
+
+ if (ref_name)
+ fetchhead_ref->ref_name = git__strdup(ref_name);
+
+ if (remote_url)
+ fetchhead_ref->remote_url = git__strdup(remote_url);
+
+ *out = fetchhead_ref;
+
+ return 0;
+}
+
+static int fetchhead_ref_write(
+ git_filebuf *file,
+ git_fetchhead_ref *fetchhead_ref)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ const char *type, *name;
+
+ assert(file && fetchhead_ref);
+
+ git_oid_fmt(oid, &fetchhead_ref->oid);
+ oid[GIT_OID_HEXSZ] = '\0';
+
+ if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
+ type = "branch ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
+ } else if(git__prefixcmp(fetchhead_ref->ref_name,
+ GIT_REFS_TAGS_DIR) == 0) {
+ type = "tag ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
+ } else {
+ type = "";
+ name = fetchhead_ref->ref_name;
+ }
+
+ return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
+ oid,
+ (fetchhead_ref->is_merge) ? "" : "not-for-merge",
+ type,
+ name,
+ fetchhead_ref->remote_url);
+}
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf path = GIT_BUF_INIT;
+ unsigned int i;
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(repo && fetchhead_refs);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) {
+ git_buf_free(&path);
+ return -1;
+ }
+
+ git_buf_free(&path);
+
+ git_vector_sort(fetchhead_refs);
+
+ git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
+ fetchhead_ref_write(&file, fetchhead_ref);
+
+ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
+}
+
+static int fetchhead_ref_parse(
+ git_oid *oid,
+ unsigned int *is_merge,
+ git_buf *ref_name,
+ const char **remote_url,
+ char *line,
+ size_t line_num)
+{
+ char *oid_str, *is_merge_str, *desc, *name = NULL;
+ const char *type = NULL;
+ int error = 0;
+
+ *remote_url = NULL;
+
+ if (!*line) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Empty line in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
+ if ((oid_str = git__strsep(&line, "\t")) == NULL) {
+ oid_str = line;
+ line += strlen(line);
+
+ *is_merge = 1;
+ }
+
+ if (strlen(oid_str) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid object ID in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (git_oid_fromstr(oid, oid_str) < 0) {
+ const git_error *oid_err = giterr_last();
+ const char *err_msg = oid_err ? oid_err->message : "Invalid object ID";
+
+ giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d",
+ err_msg, line_num);
+ return -1;
+ }
+
+ /* Parse new data from newer git clients */
+ if (*line) {
+ if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description data in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (*is_merge_str == '\0')
+ *is_merge = 1;
+ else if (strcmp(is_merge_str, "not-for-merge") == 0)
+ *is_merge = 0;
+ else {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid for-merge entry in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if ((desc = line) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (git__prefixcmp(desc, "branch '") == 0) {
+ type = GIT_REFS_HEADS_DIR;
+ name = desc + 8;
+ } else if (git__prefixcmp(desc, "tag '") == 0) {
+ type = GIT_REFS_TAGS_DIR;
+ name = desc + 5;
+ } else if (git__prefixcmp(desc, "'") == 0)
+ name = desc + 1;
+
+ if (name) {
+ if ((desc = strchr(name, '\'')) == NULL ||
+ git__prefixcmp(desc, "' of ") != 0) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ *desc = '\0';
+ desc += 5;
+ }
+
+ *remote_url = desc;
+ }
+
+ git_buf_clear(ref_name);
+
+ if (type)
+ git_buf_join(ref_name, '/', type, name);
+ else if(name)
+ git_buf_puts(ref_name, name);
+
+ return error;
+}
+
+int git_repository_fetchhead_foreach(git_repository *repo,
+ git_repository_fetchhead_foreach_cb cb,
+ void *payload)
+{
+ git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;
+ const char *ref_name;
+ git_oid oid;
+ const char *remote_url;
+ unsigned int is_merge = 0;
+ char *buffer, *line;
+ size_t line_num = 0;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
+ goto done;
+
+ buffer = file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ ++line_num;
+
+ if ((error = fetchhead_ref_parse(&oid, &is_merge, &name, &remote_url,
+ line, line_num)) < 0)
+ goto done;
+
+ if (git_buf_len(&name) > 0)
+ ref_name = git_buf_cstr(&name);
+ else
+ ref_name = NULL;
+
+ if ((cb(ref_name, remote_url, &oid, is_merge, payload)) != 0) {
+ error = GIT_EUSER;
+ goto done;
+ }
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1);
+ error = -1;
+ goto done;
+ }
+
+done:
+ git_buf_free(&file);
+ git_buf_free(&path);
+ git_buf_free(&name);
+
+ return error;
+}
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
+{
+ if (fetchhead_ref == NULL)
+ return;
+
+ git__free(fetchhead_ref->remote_url);
+ git__free(fetchhead_ref->ref_name);
+ git__free(fetchhead_ref);
+}
+
diff --git a/src/fetchhead.h b/src/fetchhead.h
new file mode 100644
index 000000000..74fce049b
--- /dev/null
+++ b/src/fetchhead.h
@@ -0,0 +1,34 @@
+/*
+ * 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_fetchhead_h__
+#define INCLUDE_fetchhead_h__
+
+#include "vector.h"
+
+struct git_fetchhead_ref {
+ git_oid oid;
+ unsigned int is_merge;
+ char *ref_name;
+ char *remote_url;
+};
+
+typedef struct git_fetchhead_ref git_fetchhead_ref;
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **fetchhead_ref_out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url);
+
+int git_fetchhead_ref_cmp(const void *a, const void *b);
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs);
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
+
+#endif
diff --git a/src/filebuf.c b/src/filebuf.c
index 6538aea66..246ae34e7 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -50,6 +50,7 @@ static int lock_file(git_filebuf *file, int flags)
if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock);
else {
+ giterr_clear(); /* actual OS error code just confuses */
giterr_set(GITERR_OS,
"Failed to lock file '%s' for writing", file->path_lock);
return -1;
@@ -72,7 +73,7 @@ static int lock_file(git_filebuf *file, int flags)
if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) {
git_file source;
char buffer[2048];
- size_t read_bytes;
+ ssize_t read_bytes;
source = p_open(file->path_original, O_RDONLY);
if (source < 0) {
@@ -82,13 +83,18 @@ static int lock_file(git_filebuf *file, int flags)
return -1;
}
- while ((read_bytes = p_read(source, buffer, 2048)) > 0) {
+ while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
p_write(file->fd, buffer, read_bytes);
- if (file->digest)
- git_hash_update(file->digest, buffer, read_bytes);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, buffer, read_bytes);
}
p_close(source);
+
+ if (read_bytes < 0) {
+ giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original);
+ return -1;
+ }
}
return 0;
@@ -102,8 +108,10 @@ void git_filebuf_cleanup(git_filebuf *file)
if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock))
p_unlink(file->path_lock);
- if (file->digest)
- git_hash_free_ctx(file->digest);
+ if (file->compute_digest) {
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+ }
if (file->buffer)
git__free(file->buffer);
@@ -130,6 +138,11 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file)
return result;
}
+int git_filebuf_flush(git_filebuf *file)
+{
+ return flush_buffer(file);
+}
+
static int write_normal(git_filebuf *file, void *source, size_t len)
{
if (len > 0) {
@@ -138,8 +151,8 @@ static int write_normal(git_filebuf *file, void *source, size_t len)
return -1;
}
- if (file->digest)
- git_hash_update(file->digest, source, len);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
}
return 0;
@@ -175,8 +188,8 @@ static int write_deflate(git_filebuf *file, void *source, size_t len)
assert(zs->avail_in == 0);
- if (file->digest)
- git_hash_update(file->digest, source, len);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
}
return 0;
@@ -210,8 +223,10 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags)
/* If we are hashing on-write, allocate a new hash context */
if (flags & GIT_FILEBUF_HASH_CONTENTS) {
- file->digest = git_hash_new_ctx();
- GITERR_CHECK_ALLOC(file->digest);
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest) < 0)
+ goto cleanup;
}
compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
@@ -280,16 +295,16 @@ cleanup:
int git_filebuf_hash(git_oid *oid, git_filebuf *file)
{
- assert(oid && file && file->digest);
+ assert(oid && file && file->compute_digest);
flush_buffer(file);
if (verify_last_error(file) < 0)
return -1;
- git_hash_final(oid, file->digest);
- git_hash_free_ctx(file->digest);
- file->digest = NULL;
+ git_hash_final(oid, &file->digest);
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
return 0;
}
@@ -314,10 +329,15 @@ int git_filebuf_commit(git_filebuf *file, mode_t mode)
if (verify_last_error(file) < 0)
goto on_error;
- p_close(file->fd);
- file->fd = -1;
file->fd_is_open = false;
+ if (p_close(file->fd) < 0) {
+ giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock);
+ goto on_error;
+ }
+
+ file->fd = -1;
+
if (p_chmod(file->path_lock, mode)) {
giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock);
goto on_error;
@@ -450,3 +470,26 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...)
return res;
}
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)
+{
+ int res;
+ struct stat st;
+
+ if (file->fd_is_open)
+ res = p_fstat(file->fd, &st);
+ else
+ res = p_stat(file->path_original, &st);
+
+ if (res < 0) {
+ giterr_set(GITERR_OS, "Could not get stat info for '%s'",
+ file->path_original);
+ return res;
+ }
+
+ if (mtime)
+ *mtime = st.st_mtime;
+ if (size)
+ *size = (size_t)st.st_size;
+
+ return 0;
+}
diff --git a/src/filebuf.h b/src/filebuf.h
index 72563b57a..823af81bf 100644
--- a/src/filebuf.h
+++ b/src/filebuf.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -31,7 +31,8 @@ struct git_filebuf {
int (*write)(struct git_filebuf *file, void *source, size_t len);
- git_hash_ctx *digest;
+ bool compute_digest;
+ git_hash_ctx digest;
unsigned char *buffer;
unsigned char *z_buf;
@@ -81,5 +82,7 @@ int git_filebuf_commit(git_filebuf *lock, mode_t mode);
int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode);
void git_filebuf_cleanup(git_filebuf *lock);
int git_filebuf_hash(git_oid *oid, git_filebuf *file);
+int git_filebuf_flush(git_filebuf *file);
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file);
#endif
diff --git a/src/fileops.c b/src/fileops.c
index ee9d4212d..d6244711f 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,22 +7,15 @@
#include "common.h"
#include "fileops.h"
#include <ctype.h>
+#if GIT_WIN32
+#include "win32/findfile.h"
+#endif
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
- int result = 0;
- git_buf target_folder = GIT_BUF_INIT;
-
- if (git_path_dirname_r(&target_folder, file_path) < 0)
- return -1;
-
- /* Does the containing folder exist? */
- if (git_path_isdir(target_folder.ptr) == false)
- /* Let's create the tree structure */
- result = git_futils_mkdir_r(target_folder.ptr, NULL, mode);
-
- git_buf_free(&target_folder);
- return result;
+ return git_futils_mkdir(
+ file_path, NULL, mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
}
int git_futils_mktmp(git_buf *path_out, const char *filename)
@@ -65,11 +58,10 @@ int git_futils_creat_locked(const char *path, const mode_t mode)
int fd;
#ifdef GIT_WIN32
- wchar_t* buf;
+ wchar_t buf[GIT_WIN_PATH];
- buf = gitwin_to_utf16(path);
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
- git__free(buf);
#else
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
#endif
@@ -94,7 +86,7 @@ int git_futils_open_ro(const char *path)
{
int fd = p_open(path, O_RDONLY);
if (fd < 0) {
- if (errno == ENOENT)
+ if (errno == ENOENT || errno == ENOTDIR)
fd = GIT_ENOTFOUND;
giterr_set(GITERR_OS, "Failed to open '%s'", path);
}
@@ -127,11 +119,35 @@ mode_t git_futils_canonical_mode(mode_t raw_mode)
return 0;
}
-int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated)
+int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
+{
+ ssize_t read_size = 0;
+
+ git_buf_clear(buf);
+
+ if (git_buf_grow(buf, len + 1) < 0)
+ return -1;
+
+ /* p_read loops internally to read len bytes */
+ read_size = p_read(fd, buf->ptr, len);
+
+ if (read_size != (ssize_t)len) {
+ giterr_set(GITERR_OS, "Failed to read descriptor");
+ return -1;
+ }
+
+ buf->ptr[read_size] = '\0';
+ buf->size = read_size;
+
+ return 0;
+}
+
+int git_futils_readbuffer_updated(
+ git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{
git_file fd;
- size_t len;
struct stat st;
+ bool changed = false;
assert(buf && path && *path);
@@ -148,41 +164,31 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime,
}
/*
- * If we were given a time, we only want to read the file if it
- * has been modified.
+ * If we were given a time and/or a size, we only want to read the file
+ * if it has been modified.
*/
- if (mtime != NULL && *mtime >= st.st_mtime) {
+ if (size && *size != (size_t)st.st_size)
+ changed = true;
+ if (mtime && *mtime != st.st_mtime)
+ changed = true;
+ if (!size && !mtime)
+ changed = true;
+
+ if (!changed) {
p_close(fd);
return 0;
}
if (mtime != NULL)
*mtime = st.st_mtime;
+ if (size != NULL)
+ *size = (size_t)st.st_size;
- len = (size_t) st.st_size;
-
- git_buf_clear(buf);
-
- if (git_buf_grow(buf, len + 1) < 0) {
+ if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
p_close(fd);
return -1;
}
- buf->ptr[len] = '\0';
-
- while (len > 0) {
- ssize_t read_size = p_read(fd, buf->ptr, len);
-
- if (read_size < 0) {
- p_close(fd);
- giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path);
- return -1;
- }
-
- len -= read_size;
- buf->size += read_size;
- }
-
p_close(fd);
if (updated != NULL)
@@ -193,7 +199,7 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime,
int git_futils_readbuffer(git_buf *buf, const char *path)
{
- return git_futils_readbuffer_updated(buf, path, NULL, NULL);
+ return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
}
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
@@ -239,237 +245,771 @@ void git_futils_mmap_free(git_map *out)
p_munmap(out);
}
-int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
+int git_futils_mkdir(
+ const char *path,
+ const char *base,
+ mode_t mode,
+ uint32_t flags)
{
- int root_path_offset;
+ int error = -1;
git_buf make_path = GIT_BUF_INIT;
- size_t start;
- char *pp, *sp;
- bool failed = false;
-
- if (base != NULL) {
- start = strlen(base);
- if (git_buf_joinpath(&make_path, base, path) < 0)
- return -1;
- } else {
- start = 0;
- if (git_buf_puts(&make_path, path) < 0)
- return -1;
- }
+ ssize_t root = 0;
+ char lastch, *tail;
- pp = make_path.ptr + start;
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
+ return -1;
- root_path_offset = git_path_root(make_path.ptr);
- if (root_path_offset > 0)
- pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */
+ if (make_path.size == 0) {
+ giterr_set(GITERR_OS, "Attempt to create empty path");
+ goto fail;
+ }
- while (!failed && (sp = strchr(pp, '/')) != NULL) {
- if (sp != pp && git_path_isdir(make_path.ptr) == false) {
- *sp = 0;
+ /* remove trailing slashes on path */
+ while (make_path.ptr[make_path.size - 1] == '/') {
+ make_path.size--;
+ make_path.ptr[make_path.size] = '\0';
+ }
- /* Do not choke while trying to recreate an existing directory */
- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
- failed = true;
+ /* if we are not supposed to made the last element, truncate it */
+ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
+ git_buf_rtruncate_at_char(&make_path, '/');
+ flags |= GIT_MKDIR_SKIP_LAST;
+ }
+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
+ git_buf_rtruncate_at_char(&make_path, '/');
+
+ /* if we are not supposed to make the whole path, reset root */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ root = git_buf_rfind(&make_path, '/');
+
+ /* clip root to make_path length */
+ if (root >= (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size - 1;
+ if (root < 0)
+ root = 0;
+
+ tail = & make_path.ptr[root];
+
+ while (*tail) {
+ /* advance tail to include next path component */
+ while (*tail == '/')
+ tail++;
+ while (*tail && *tail != '/')
+ tail++;
+
+ /* truncate path at next component */
+ lastch = *tail;
+ *tail = '\0';
+
+ /* make directory */
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ int already_exists = 0;
+
+ switch (errno) {
+ case EEXIST:
+ if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ !git_path_isdir(make_path.ptr)) {
+ giterr_set(
+ GITERR_OS, "Existing path is not a directory '%s'",
+ make_path.ptr);
+ error = GIT_ENOTFOUND;
+ goto fail;
+ }
+
+ already_exists = 1;
+ break;
+ case ENOSYS:
+ /* Solaris can generate this error if you try to mkdir
+ * a path which is already a mount point. In that case,
+ * the path does already exist; but it's not implied by
+ * the definition of the error, so let's recheck */
+ if (git_path_isdir(make_path.ptr)) {
+ already_exists = 1;
+ break;
+ }
+
+ /* Fall through */
+ errno = ENOSYS;
+ default:
+ giterr_set(GITERR_OS, "Failed to make directory '%s'",
+ make_path.ptr);
+ goto fail;
+ }
+
+ if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'",
+ make_path.ptr);
+ error = GIT_EEXISTS;
+ goto fail;
+ }
+ }
- *sp = '/';
+ /* chmod if requested */
+ if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
+ ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
+ {
+ if (p_chmod(make_path.ptr, mode) < 0) {
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
+ make_path.ptr);
+ goto fail;
+ }
}
- pp = sp + 1;
+ *tail = lastch;
}
- if (*pp != '\0' && !failed) {
- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
- failed = true;
- }
+ git_buf_free(&make_path);
+ return 0;
+fail:
git_buf_free(&make_path);
+ return error;
+}
- if (failed) {
- giterr_set(GITERR_OS,
- "Failed to create directory structure at '%s'", path);
- return -1;
+int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
+{
+ return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
+}
+
+typedef struct {
+ const char *base;
+ size_t baselen;
+ uint32_t flags;
+ int error;
+} futils__rmdir_data;
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
+{
+ if (filemsg)
+ giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s",
+ path, filemsg);
+ else
+ giterr_set(GITERR_OS, "Could not remove directory '%s'", path);
+
+ return -1;
+}
+
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
+
+ while (error == GIT_ENOTFOUND) {
+ git_buf_rtruncate_at_char(path, '/');
+
+ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
+ error = 0;
+ else if (p_lstat_posixly(path->ptr, &st) == 0) {
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ error = p_unlink(path->ptr);
+ else if (!S_ISDIR(st.st_mode))
+ error = -1; /* fail to remove non-regular file */
+ } else if (errno != ENOTDIR)
+ error = -1;
}
- return 0;
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
+
+ return error;
}
-static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
+static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
- git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
+ struct stat st;
+ futils__rmdir_data *data = opaque;
- assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
- || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
- || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
+ if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ data->error = 0;
+ else if (errno == ENOTDIR) {
+ /* asked to remove a/b/c/d/e and a/b is a normal file */
+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
+ data->error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
+ }
+ else
+ futils__error_cannot_rmdir(path->ptr, "cannot access");
+ }
- if (git_path_isdir(path->ptr) == true) {
- if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
- return -1;
+ else if (S_ISDIR(st.st_mode)) {
+ int error = git_path_direach(path, futils__rmdir_recurs_foreach, data);
+ if (error < 0)
+ return (error == GIT_EUSER) ? data->error : error;
- if (p_rmdir(path->ptr) < 0) {
- if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
- return 0;
+ data->error = p_rmdir(path->ptr);
- giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
- return -1;
+ if (data->error < 0) {
+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
+ (errno == ENOTEMPTY || errno == EEXIST))
+ data->error = 0;
+ else
+ futils__error_cannot_rmdir(path->ptr, NULL);
}
+ }
- return 0;
+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
+ data->error = p_unlink(path->ptr);
+
+ if (data->error < 0)
+ futils__error_cannot_rmdir(path->ptr, "cannot be removed");
}
- if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
- if (p_unlink(path->ptr) < 0) {
- giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr);
- return -1;
- }
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ data->error = futils__error_cannot_rmdir(path->ptr, "still present");
- return 0;
+ return data->error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
+{
+ futils__rmdir_data *data = opaque;
+ int error;
+
+ if (git_buf_len(path) <= data->baselen)
+ return GIT_ITEROVER;
+
+ error = p_rmdir(git_buf_cstr(path));
+
+ if (error) {
+ int en = errno;
+
+ if (en == ENOENT || en == ENOTDIR) {
+ giterr_clear();
+ error = 0;
+ } else if (en == ENOTEMPTY || en == EEXIST) {
+ giterr_clear();
+ error = GIT_ITEROVER;
+ } else {
+ futils__error_cannot_rmdir(git_buf_cstr(path), NULL);
+ }
}
- if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
- giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
+ return error;
+}
+
+int git_futils_rmdir_r(
+ const char *path, const char *base, uint32_t flags)
+{
+ int error;
+ git_buf fullpath = GIT_BUF_INIT;
+ futils__rmdir_data data;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
return -1;
+
+ data.base = base ? base : "";
+ data.baselen = base ? strlen(base) : 0;
+ data.flags = flags;
+ data.error = 0;
+
+ error = futils__rmdir_recurs_foreach(&data, &fullpath);
+
+ /* remove now-empty parents if requested */
+ if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) {
+ error = git_path_walk_up(
+ &fullpath, base, futils__rmdir_empty_parent, &data);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
}
- return 0;
+ git_buf_free(&fullpath);
+
+ return error;
}
-int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
+int git_futils_cleanupdir_r(const char *path)
{
int error;
- git_buf p = GIT_BUF_INIT;
+ git_buf fullpath = GIT_BUF_INIT;
+ futils__rmdir_data data;
- error = git_buf_sets(&p, path);
- if (!error)
- error = _rmdir_recurs_foreach(&removal_type, &p);
- git_buf_free(&p);
+ if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0)
+ goto clean_up;
+
+ data.base = "";
+ data.baselen = 0;
+ data.flags = GIT_RMDIR_REMOVE_FILES;
+ data.error = 0;
+
+ if (!git_path_exists(path)) {
+ giterr_set(GITERR_OS, "Path does not exist: %s" , path);
+ error = GIT_ERROR;
+ goto clean_up;
+ }
+
+ if (!git_path_isdir(path)) {
+ giterr_set(GITERR_OS, "Path is not a directory: %s" , path);
+ error = GIT_ERROR;
+ goto clean_up;
+ }
+
+ error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data);
+ if (error == GIT_EUSER)
+ error = data.error;
+
+clean_up:
+ git_buf_free(&fullpath);
return error;
}
-int git_futils_find_global_file(git_buf *path, const char *filename)
+
+static int git_futils_guess_system_dirs(git_buf *out)
{
- const char *home = getenv("HOME");
+#ifdef GIT_WIN32
+ return git_win32__find_system_dirs(out);
+#else
+ return git_buf_sets(out, "/etc");
+#endif
+}
+static int git_futils_guess_global_dirs(git_buf *out)
+{
#ifdef GIT_WIN32
- if (home == NULL)
- home = getenv("USERPROFILE");
+ return git_win32__find_global_dirs(out);
+#else
+ return git_buf_sets(out, getenv("HOME"));
#endif
+}
- if (home == NULL) {
- giterr_set(GITERR_OS, "Global file lookup failed. "
- "Cannot locate the user's home directory");
- return -1;
- }
+static int git_futils_guess_xdg_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_xdg_dirs(out);
+#else
+ const char *env = NULL;
- if (git_buf_joinpath(path, home, filename) < 0)
- return -1;
+ if ((env = getenv("XDG_CONFIG_HOME")) != NULL)
+ return git_buf_joinpath(out, env, "git");
+ else if ((env = getenv("HOME")) != NULL)
+ return git_buf_joinpath(out, env, ".config/git");
- if (git_path_exists(path->ptr) == false) {
- git_buf_clear(path);
- return GIT_ENOTFOUND;
+ git_buf_clear(out);
+ return 0;
+#endif
+}
+
+typedef int (*git_futils_dirs_guess_cb)(git_buf *out);
+
+static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] =
+ { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };
+
+static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {
+ git_futils_guess_system_dirs,
+ git_futils_guess_global_dirs,
+ git_futils_guess_xdg_dirs,
+};
+
+static int git_futils_check_selector(git_futils_dir_t which)
+{
+ if (which < GIT_FUTILS_DIR__MAX)
+ return 0;
+ giterr_set(GITERR_INVALID, "config directory selector out of range");
+ return -1;
+}
+
+int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which)
+{
+ assert(out);
+
+ *out = NULL;
+
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
+
+ if (!git_buf_len(&git_futils__dirs[which]))
+ GITERR_CHECK_ERROR(
+ git_futils__dir_guess[which](&git_futils__dirs[which]));
+
+ *out = &git_futils__dirs[which];
+ return 0;
+}
+
+int git_futils_dirs_get_str(char *out, size_t outlen, git_futils_dir_t which)
+{
+ const git_buf *path = NULL;
+
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
+ GITERR_CHECK_ERROR(git_futils_dirs_get(&path, which));
+
+ if (!out || path->size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ return GIT_EBUFS;
}
+ git_buf_copy_cstr(out, outlen, path);
return 0;
}
-#ifdef GIT_WIN32
-typedef struct {
- wchar_t *path;
- DWORD len;
-} win32_path;
+#define PATH_MAGIC "$PATH"
-static const win32_path *win32_system_root(void)
+int git_futils_dirs_set(git_futils_dir_t which, const char *search_path)
{
- static win32_path s_root = { 0, 0 };
+ const char *expand_path = NULL;
+ git_buf merge = GIT_BUF_INIT;
- if (s_root.path == NULL) {
- const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\";
+ GITERR_CHECK_ERROR(git_futils_check_selector(which));
- s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0);
- if (s_root.len <= 0) {
- giterr_set(GITERR_OS, "Failed to expand environment strings");
- return NULL;
- }
+ if (search_path != NULL)
+ expand_path = strstr(search_path, PATH_MAGIC);
- s_root.path = git__calloc(s_root.len, sizeof(wchar_t));
- if (s_root.path == NULL)
- return NULL;
+ /* init with default if not yet done and needed (ignoring error) */
+ if ((!search_path || expand_path) &&
+ !git_buf_len(&git_futils__dirs[which]))
+ git_futils__dir_guess[which](&git_futils__dirs[which]);
- if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) {
- giterr_set(GITERR_OS, "Failed to expand environment strings");
- git__free(s_root.path);
- s_root.path = NULL;
- return NULL;
- }
- }
+ /* if $PATH is not referenced, then just set the path */
+ if (!expand_path)
+ return git_buf_sets(&git_futils__dirs[which], search_path);
+
+ /* otherwise set to join(before $PATH, old value, after $PATH) */
+ if (expand_path > search_path)
+ git_buf_set(&merge, search_path, expand_path - search_path);
+
+ if (git_buf_len(&git_futils__dirs[which]))
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR,
+ merge.ptr, git_futils__dirs[which].ptr);
+
+ expand_path += strlen(PATH_MAGIC);
+ if (*expand_path)
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path);
+
+ git_buf_swap(&git_futils__dirs[which], &merge);
+ git_buf_free(&merge);
- return &s_root;
+ return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0;
}
-static int win32_find_system_file(git_buf *path, const char *filename)
+void git_futils_dirs_free(void)
+{
+ int i;
+ for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i)
+ git_buf_free(&git_futils__dirs[i]);
+}
+
+static int git_futils_find_in_dirlist(
+ git_buf *path, const char *name, git_futils_dir_t which, const char *label)
{
- int error = 0;
- const win32_path *root = win32_system_root();
size_t len;
- wchar_t *file_utf16 = NULL, *scan;
- char *file_utf8 = NULL;
+ const char *scan, *next = NULL;
+ const git_buf *syspath;
- if (!root || !filename || (len = strlen(filename)) == 0)
- return GIT_ENOTFOUND;
+ GITERR_CHECK_ERROR(git_futils_dirs_get(&syspath, which));
- /* allocate space for wchar_t path to file */
- file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));
- GITERR_CHECK_ALLOC(file_utf16);
+ for (scan = git_buf_cstr(syspath); scan; scan = next) {
+ for (next = strchr(scan, GIT_PATH_LIST_SEPARATOR);
+ next && next > scan && next[-1] == '\\';
+ next = strchr(next + 1, GIT_PATH_LIST_SEPARATOR))
+ /* find unescaped separator or end of string */;
- /* append root + '\\' + filename as wchar_t */
- memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
+ len = next ? (size_t)(next++ - scan) : strlen(scan);
+ if (!len)
+ continue;
- if (*filename == '/' || *filename == '\\')
- filename++;
+ GITERR_CHECK_ERROR(git_buf_set(path, scan, len));
+ GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
- if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=
- (int)len + 1) {
- error = -1;
- goto cleanup;
+ if (git_path_exists(path->ptr))
+ return 0;
}
- for (scan = file_utf16; *scan; scan++)
- if (*scan == L'/')
- *scan = L'\\';
+ git_buf_clear(path);
+ giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);
+ return GIT_ENOTFOUND;
+}
+
+int git_futils_find_system_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_SYSTEM, "system");
+}
+
+int git_futils_find_global_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_GLOBAL, "global");
+}
- /* check access */
- if (_waccess(file_utf16, F_OK) < 0) {
- error = GIT_ENOTFOUND;
- goto cleanup;
+int git_futils_find_xdg_file(git_buf *path, const char *filename)
+{
+ return git_futils_find_in_dirlist(
+ path, filename, GIT_FUTILS_DIR_XDG, "global/xdg");
+}
+
+int git_futils_fake_symlink(const char *old, const char *new)
+{
+ int retcode = GIT_ERROR;
+ int fd = git_futils_creat_withpath(new, 0755, 0644);
+ if (fd >= 0) {
+ retcode = p_write(fd, old, strlen(old));
+ p_close(fd);
+ }
+ return retcode;
+}
+
+static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
+{
+ int error = 0;
+ char buffer[4096];
+ ssize_t len = 0;
+
+ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
+ /* p_write() does not have the same semantics as write(). It loops
+ * internally and will return 0 when it has completed writing.
+ */
+ error = p_write(ofd, buffer, len);
+
+ if (len < 0) {
+ giterr_set(GITERR_OS, "Read error while copying file");
+ error = (int)len;
+ }
+
+ if (close_fd_when_done) {
+ p_close(ifd);
+ p_close(ofd);
+ }
+
+ return error;
+}
+
+int git_futils_cp(const char *from, const char *to, mode_t filemode)
+{
+ int ifd, ofd;
+
+ if ((ifd = git_futils_open_ro(from)) < 0)
+ return ifd;
+
+ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ ofd = GIT_ENOTFOUND;
+ giterr_set(GITERR_OS, "Failed to open '%s' for writing", to);
+ p_close(ifd);
+ return ofd;
}
- /* convert to utf8 */
- if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)
+ return cp_by_fd(ifd, ofd, true);
+}
+
+static int cp_link(const char *from, const char *to, size_t link_size)
+{
+ int error = 0;
+ ssize_t read_len;
+ char *link_data = git__malloc(link_size + 1);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(from, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
error = -1;
+ }
else {
- git_path_mkposix(file_utf8);
- git_buf_attach(path, file_utf8, 0);
+ link_data[read_len] = '\0';
+
+ if (p_symlink(link_data, to) < 0) {
+ giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
+ link_data, to);
+ error = -1;
+ }
+ }
+
+ git__free(link_data);
+ return error;
+}
+
+typedef struct {
+ const char *to_root;
+ git_buf to;
+ ssize_t from_prefix;
+ uint32_t flags;
+ uint32_t mkdir_flags;
+ mode_t dirmode;
+} cp_r_info;
+
+#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
+
+static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
+{
+ int error = 0;
+
+ /* create root directory the first time we need to create a directory */
+ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
+ error = git_futils_mkdir(
+ info->to_root, NULL, info->dirmode,
+ (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
+
+ info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
}
-cleanup:
- git__free(file_utf16);
+ /* create directory with root as base to prevent excess chmods */
+ if (!error)
+ error = git_futils_mkdir(
+ from->ptr + info->from_prefix, info->to_root,
+ info->dirmode, info->mkdir_flags);
return error;
}
-#endif
-int git_futils_find_system_file(git_buf *path, const char *filename)
+static int _cp_r_callback(void *ref, git_buf *from)
{
- if (git_buf_joinpath(path, "/etc", filename) < 0)
+ int error = 0;
+ cp_r_info *info = ref;
+ struct stat from_st, to_st;
+ bool exists = false;
+
+ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
+ from->ptr[git_path_basename_offset(from)] == '.')
+ return 0;
+
+ if (git_buf_joinpath(
+ &info->to, info->to_root, from->ptr + info->from_prefix) < 0)
return -1;
- if (git_path_exists(path->ptr) == true)
+ if (p_lstat(info->to.ptr, &to_st) < 0) {
+ if (errno != ENOENT && errno != ENOTDIR) {
+ giterr_set(GITERR_OS,
+ "Could not access %s while copying files", info->to.ptr);
+ return -1;
+ }
+ } else
+ exists = true;
+
+ if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
+ return error;
+
+ if (S_ISDIR(from_st.st_mode)) {
+ mode_t oldmode = info->dirmode;
+
+ /* if we are not chmod'ing, then overwrite dirmode */
+ if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
+ info->dirmode = from_st.st_mode;
+
+ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
+ error = _cp_r_mkdir(info, from);
+
+ /* recurse onto target directory */
+ if (!error && (!exists || S_ISDIR(to_st.st_mode)))
+ error = git_path_direach(from, _cp_r_callback, info);
+
+ if (oldmode != 0)
+ info->dirmode = oldmode;
+
+ return error;
+ }
+
+ if (exists) {
+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
+ return 0;
+
+ if (p_unlink(info->to.ptr) < 0) {
+ giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
+ info->to.ptr);
+ return -1;
+ }
+ }
+
+ /* Done if this isn't a regular file or a symlink */
+ if (!S_ISREG(from_st.st_mode) &&
+ (!S_ISLNK(from_st.st_mode) ||
+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
return 0;
- git_buf_clear(path);
+ /* Make container directory on demand if needed */
+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
+ (error = _cp_r_mkdir(info, from)) < 0)
+ return error;
-#ifdef GIT_WIN32
- return win32_find_system_file(path, filename);
-#else
- return GIT_ENOTFOUND;
-#endif
+ /* make symlink or regular file */
+ if (S_ISLNK(from_st.st_mode))
+ error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
+ else {
+ mode_t usemode = from_st.st_mode;
+
+ if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
+ usemode = (usemode & 0111) ? 0777 : 0666;
+
+ error = git_futils_cp(from->ptr, info->to.ptr, usemode);
+ }
+
+ return error;
+}
+
+int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ cp_r_info info;
+
+ if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
+ return -1;
+
+ info.to_root = to;
+ info.flags = flags;
+ info.dirmode = dirmode;
+ info.from_prefix = path.size;
+ git_buf_init(&info.to, 0);
+
+ /* precalculate mkdir flags */
+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
+ /* if not creating empty dirs, then use mkdir to create the path on
+ * demand right before files are copied.
+ */
+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
+ if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
+ } else {
+ /* otherwise, we will do simple mkdir as directories are encountered */
+ info.mkdir_flags =
+ ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
+ }
+
+ error = _cp_r_callback(&info, &path);
+
+ git_buf_free(&path);
+ git_buf_free(&info.to);
+
+ return error;
+}
+
+int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path)
+{
+ struct stat st;
+
+ /* if the stamp is NULL, then always reload */
+ if (stamp == NULL)
+ return 1;
+
+ if (p_stat(path, &st) < 0)
+ return GIT_ENOTFOUND;
+
+ if (stamp->mtime == (git_time_t)st.st_mtime &&
+ stamp->size == (git_off_t)st.st_size &&
+ stamp->ino == (unsigned int)st.st_ino)
+ return 0;
+
+ stamp->mtime = (git_time_t)st.st_mtime;
+ stamp->size = (git_off_t)st.st_size;
+ stamp->ino = (unsigned int)st.st_ino;
+
+ return 1;
+}
+
+void git_futils_filestamp_set(
+ git_futils_filestamp *target, const git_futils_filestamp *source)
+{
+ assert(target);
+
+ if (source)
+ memcpy(target, source, sizeof(*target));
+ else
+ memset(target, 0, sizeof(*target));
}
diff --git a/src/fileops.h b/src/fileops.h
index be619d620..627a6923d 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -18,7 +18,9 @@
* Read whole files into an in-memory buffer for processing
*/
extern int git_futils_readbuffer(git_buf *obj, const char *path);
-extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated);
+extern int git_futils_readbuffer_updated(
+ git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);
+extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
/**
* File utils
@@ -49,34 +51,99 @@ extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmo
/**
* Create a path recursively
+ *
+ * If a base parameter is being passed, it's expected to be valued with a
+ * path pointing to an already existing directory.
*/
extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode);
/**
+ * Flags to pass to `git_futils_mkdir`.
+ *
+ * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
+ * * GIT_MKDIR_PATH says to make all components in the path.
+ * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
+ * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
+ * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
+ * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
+ *
+ * Note that the chmod options will be executed even if the directory already
+ * exists, unless GIT_MKDIR_EXCL is given.
+ */
+typedef enum {
+ GIT_MKDIR_EXCL = 1,
+ GIT_MKDIR_PATH = 2,
+ GIT_MKDIR_CHMOD = 4,
+ GIT_MKDIR_CHMOD_PATH = 8,
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_SKIP_LAST2 = 32,
+ GIT_MKDIR_VERIFY_DIR = 64,
+} git_futils_mkdir_flags;
+
+/**
+ * Create a directory or entire path.
+ *
+ * This makes a directory (and the entire path leading up to it if requested),
+ * and optionally chmods the directory immediately after (or each part of the
+ * path if requested).
+ *
+ * @param path The path to create.
+ * @param base Root for relative path. These directories will never be made.
+ * @param mode The mode to use for created directories.
+ * @param flags Combination of the mkdir flags above.
+ * @return 0 on success, else error code
+ */
+extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);
+
+/**
* Create all the folders required to contain
* the full path of a file
*/
extern int git_futils_mkpath2file(const char *path, const mode_t mode);
+/**
+ * Flags to pass to `git_futils_rmdir_r`.
+ *
+ * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty
+ * dirs and generate error if any files are found.
+ * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.
+ * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
+ * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
+ * if removing this item leaves them empty
+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
+ *
+ * The old values translate into the new as follows:
+ *
+ * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY
+ * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES
+ * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY
+ */
typedef enum {
- GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0,
- GIT_DIRREMOVAL_FILES_AND_DIRS = 1,
- GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2,
-} git_directory_removal_type;
+ GIT_RMDIR_EMPTY_HIERARCHY = 0,
+ GIT_RMDIR_REMOVE_FILES = (1 << 0),
+ GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
+ GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
+} git_futils_rmdir_flags;
/**
* Remove path and any files and directories beneath it.
*
- * @param path Path to to top level directory to process.
- *
- * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
- * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS
- * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove
- * empty directories (no failure on file encounter).
+ * @param path Path to the top level directory to process.
+ * @param base Root for relative path.
+ * @param flags Combination of git_futils_rmdir_flags values
+ * @return 0 on success; -1 on error.
+ */
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
+
+/**
+ * Remove all files and directories beneath the specified path.
*
+ * @param path Path to the top level directory to process.
* @return 0 on success; -1 on error.
*/
-extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type);
+extern int git_futils_cleanupdir_r(const char *path);
/**
* Create and open a temporary file with a `_git2_` suffix.
@@ -92,6 +159,58 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename);
extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
/**
+ * Copy a file
+ *
+ * The filemode will be used for the newly created file.
+ */
+extern int git_futils_cp(
+ const char *from,
+ const char *to,
+ mode_t filemode);
+
+/**
+ * Flags that can be passed to `git_futils_cp_r`.
+ *
+ * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no
+ * files under them (otherwise directories will only be created lazily
+ * when a file inside them is copied).
+ * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.
+ * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.
+ * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,
+ * otherwise they are silently skipped.
+ * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`
+ * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
+ * source file to the target; with this flag, always use 0666 (or 0777 if
+ * source has exec bits set) for target.
+ */
+typedef enum {
+ GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
+ GIT_CPDIR_COPY_SYMLINKS = (1u << 1),
+ GIT_CPDIR_COPY_DOTFILES = (1u << 2),
+ GIT_CPDIR_OVERWRITE = (1u << 3),
+ GIT_CPDIR_CHMOD_DIRS = (1u << 4),
+ GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),
+} git_futils_cpdir_flags;
+
+/**
+ * Copy a directory tree.
+ *
+ * This copies directories and files from one root to another. You can
+ * pass a combinationof GIT_CPDIR flags as defined above.
+ *
+ * If you pass the CHMOD flag, then the dirmode will be applied to all
+ * directories that are created during the copy, overiding the natural
+ * permissions. If you do not pass the CHMOD flag, then the dirmode
+ * will actually be copied from the source files and the `dirmode` arg
+ * will be ignored.
+ */
+extern int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode);
+
+/**
* Open a file readonly and set error if needed.
*/
extern int git_futils_open_ro(const char *path);
@@ -157,23 +276,120 @@ extern void git_futils_mmap_free(git_map *map);
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
- * @return
- * - 0 if found;
- * - GIT_ENOTFOUND if not found;
- * - -1 on an unspecified OS related error.
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/
extern int git_futils_find_global_file(git_buf *path, const char *filename);
/**
+ * Find an "XDG" file (i.e. one in user's XDG config path).
+ *
+ * @param pathbuf buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_futils_find_xdg_file(git_buf *path, const char *filename);
+
+/**
* Find a "system" file (i.e. one shared for all users of the system).
*
* @param pathbuf buffer to write the full path into
* @param filename name of file to find in the home directory
- * @return
- * - 0 if found;
- * - GIT_ENOTFOUND if not found;
- * - -1 on an unspecified OS related error.
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/
extern int git_futils_find_system_file(git_buf *path, const char *filename);
+typedef enum {
+ GIT_FUTILS_DIR_SYSTEM = 0,
+ GIT_FUTILS_DIR_GLOBAL = 1,
+ GIT_FUTILS_DIR_XDG = 2,
+ GIT_FUTILS_DIR__MAX = 3,
+} git_futils_dir_t;
+
+/**
+ * Get the search path for global/system/xdg files
+ *
+ * @param out pointer to git_buf containing search path
+ * @param which which list of paths to return
+ * @return 0 on success, <0 on failure
+ */
+extern int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which);
+
+/**
+ * Get search path into a preallocated buffer
+ *
+ * @param out String buffer to write into
+ * @param outlen Size of string buffer
+ * @param which Which search path to return
+ * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure
+ */
+
+extern int git_futils_dirs_get_str(
+ char *out, size_t outlen, git_futils_dir_t which);
+
+/**
+ * Set search paths for global/system/xdg files
+ *
+ * The first occurrence of the magic string "$PATH" in the new value will
+ * be replaced with the old value of the search path.
+ *
+ * @param which Which search path to modify
+ * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR)
+ * @return 0 on success, <0 on failure (allocation error)
+ */
+extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths);
+
+/**
+ * Release / reset all search paths
+ */
+extern void git_futils_dirs_free(void);
+
+/**
+ * Create a "fake" symlink (text file containing the target path).
+ *
+ * @param new symlink file to be created
+ * @param old original symlink target
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fake_symlink(const char *new, const char *old);
+
+/**
+ * A file stamp represents a snapshot of information about a file that can
+ * be used to test if the file changes. This portable implementation is
+ * based on stat data about that file, but it is possible that OS specific
+ * versions could be implemented in the future.
+ */
+typedef struct {
+ git_time_t mtime;
+ git_off_t size;
+ unsigned int ino;
+} git_futils_filestamp;
+
+/**
+ * Compare stat information for file with reference info.
+ *
+ * This function updates the file stamp to current data for the given path
+ * and returns 0 if the file is up-to-date relative to the prior setting or
+ * 1 if the file has been changed. (This also may return GIT_ENOTFOUND if
+ * the file doesn't exist.)
+ *
+ * @param stamp File stamp to be checked
+ * @param path Path to stat and check if changed
+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ */
+extern int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path);
+
+/**
+ * Set or reset file stamp data
+ *
+ * This writes the target file stamp. If the source is NULL, this will set
+ * the target stamp to values that will definitely be out of date. If the
+ * source is not NULL, this copies the source values to the target.
+ *
+ * @param tgt File stamp to write to
+ * @param src File stamp to copy from or NULL to clear the target
+ */
+extern void git_futils_filestamp_set(
+ git_futils_filestamp *tgt, const git_futils_filestamp *src);
+
#endif /* INCLUDE_fileops_h__ */
diff --git a/src/filter.c b/src/filter.c
index 8fa3eb684..9f749dcbd 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,79 +11,7 @@
#include "filter.h"
#include "repository.h"
#include "git2/config.h"
-
-/* Tweaked from Core Git. I wonder what we could use this for... */
-void git_text_gather_stats(git_text_stats *stats, const git_buf *text)
-{
- size_t i;
-
- memset(stats, 0, sizeof(*stats));
-
- for (i = 0; i < git_buf_len(text); i++) {
- unsigned char c = text->ptr[i];
-
- if (c == '\r') {
- stats->cr++;
-
- if (i + 1 < git_buf_len(text) && text->ptr[i + 1] == '\n')
- stats->crlf++;
- }
-
- else if (c == '\n')
- stats->lf++;
-
- else if (c == 0x85)
- /* Unicode CR+LF */
- stats->crlf++;
-
- else if (c == 127)
- /* DEL */
- stats->nonprintable++;
-
- else if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) {
- switch (c) {
- /* BS, HT, ESC and FF */
- case '\b': case '\t': case '\033': case '\014':
- stats->printable++;
- break;
- case 0:
- stats->nul++;
- /* fall through */
- default:
- stats->nonprintable++;
- }
- }
-
- else
- stats->printable++;
- }
-
- /* If file ends with EOF then don't count this EOF as non-printable. */
- if (git_buf_len(text) >= 1 && text->ptr[text->size - 1] == '\032')
- stats->nonprintable--;
-}
-
-/*
- * Fresh from Core Git
- */
-int git_text_is_binary(git_text_stats *stats)
-{
- if (stats->nul)
- return 1;
-
- if ((stats->printable >> 7) < stats->nonprintable)
- return 1;
- /*
- * Other heuristics? Average line length might be relevant,
- * as might LF vs CR vs CRLF counts..
- *
- * NOTE! It might be normal to have a low ratio of CRLF to LF
- * (somebody starts with a LF-only file and edits it with an editor
- * that adds CRLF only to lines that are added..). But do we
- * want to support CR-only? Probably not.
- */
- return 0;
-}
+#include "blob.h"
int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode)
{
@@ -95,8 +23,9 @@ int git_filters_load(git_vector *filters, git_repository *repo, const char *path
if (error < 0)
return error;
} else {
- giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet");
- return -1;
+ error = git_filter_add__crlf_to_workdir(filters, repo, path);
+ if (error < 0)
+ return error;
}
return (int)filters->length;
@@ -119,7 +48,8 @@ void git_filters_free(git_vector *filters)
int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
{
- unsigned int i, src;
+ size_t i;
+ unsigned int src;
git_buf *dbuffer[2];
dbuffer[0] = source;
@@ -162,4 +92,3 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
return 0;
}
-
diff --git a/src/filter.h b/src/filter.h
index 66e370aef..42a44ebdb 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -31,14 +31,6 @@ typedef enum {
GIT_CRLF_AUTO,
} git_crlf_t;
-typedef struct {
- /* NUL, CR, LF and CRLF counts */
- unsigned int nul, cr, lf, crlf;
-
- /* These are just approximations! */
- unsigned int printable, nonprintable;
-} git_text_stats;
-
/*
* FILTER API
*/
@@ -96,24 +88,7 @@ extern void git_filters_free(git_vector *filters);
/* Strip CRLF, from Worktree to ODB */
extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path);
-
-/*
- * PLAINTEXT API
- */
-
-/*
- * Gather stats for a piece of text
- *
- * Fill the `stats` structure with information on the number of
- * unreadable characters, carriage returns, etc, so it can be
- * used in heuristics.
- */
-extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text);
-
-/*
- * Process `git_text_stats` data generated by `git_text_stat` to see
- * if it qualifies as a binary file
- */
-extern int git_text_is_binary(git_text_stats *stats);
+/* Add CRLF, from ODB to worktree */
+extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path);
#endif
diff --git a/src/compat/fnmatch.c b/src/fnmatch.c
index 835d811bc..e3e47f37b 100644
--- a/src/compat/fnmatch.c
+++ b/src/fnmatch.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -24,13 +24,16 @@
static int rangematch(const char *, char, int, char **);
-int
-p_fnmatch(const char *pattern, const char *string, int flags)
+static int
+p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
{
const char *stringstart;
char *newp;
char c, test;
+ if (recurs-- == 0)
+ return FNM_NORES;
+
for (stringstart = string;;)
switch (c = *pattern++) {
case EOS:
@@ -75,8 +78,11 @@ p_fnmatch(const char *pattern, const char *string, int flags)
/* General case, use recursion. */
while ((test = *string) != EOS) {
- if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD))
- return (0);
+ int e;
+
+ e = p_fnmatchx(pattern, string, flags & ~FNM_PERIOD, recurs);
+ if (e != FNM_NOMATCH)
+ return e;
if (test == '/' && (flags & FNM_PATHNAME))
break;
++string;
@@ -178,3 +184,9 @@ rangematch(const char *pattern, char test, int flags, char **newp)
return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
}
+int
+p_fnmatch(const char *pattern, const char *string, int flags)
+{
+ return p_fnmatchx(pattern, string, flags, 64);
+}
+
diff --git a/src/compat/fnmatch.h b/src/fnmatch.h
index 7faef09b3..920e7de4d 100644
--- a/src/compat/fnmatch.h
+++ b/src/fnmatch.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,12 +11,13 @@
#define FNM_NOMATCH 1 /* Match failed. */
#define FNM_NOSYS 2 /* Function not supported (unused). */
+#define FNM_NORES 3 /* Out of resources */
-#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
-#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
+#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
+#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
#define FNM_PERIOD 0x04 /* Period must be matched by period. */
#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
-#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
+#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
#define FNM_IGNORECASE FNM_CASEFOLD
#define FNM_FILE_NAME FNM_PATHNAME
diff --git a/src/global.c b/src/global.c
index 368c6c664..b7fd8e257 100644
--- a/src/global.c
+++ b/src/global.c
@@ -1,14 +1,19 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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 "global.h"
-#include "git2/threads.h"
+#include "hash.h"
+#include "fileops.h"
+#include "git2/threads.h"
#include "thread-utils.h"
+
+git_mutex git__mwindow_mutex;
+
/**
* Handle the global state with TLS
*
@@ -35,30 +40,58 @@
* functions are not available in that case.
*/
+/*
+ * `git_threads_init()` allows subsystems to perform global setup,
+ * which may take place in the global scope. An explicit memory
+ * fence exists at the exit of `git_threads_init()`. Without this,
+ * CPU cores are free to reorder cache invalidation of `_tls_init`
+ * before cache invalidation of the subsystems' newly written global
+ * state.
+ */
#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _tls_index;
static int _tls_init = 0;
-void git_threads_init(void)
+int git_threads_init(void)
{
+ int error;
+
if (_tls_init)
- return;
+ return 0;
_tls_index = TlsAlloc();
- _tls_init = 1;
+ git_mutex_init(&git__mwindow_mutex);
+
+ /* Initialize any other subsystems that have global state */
+ if ((error = git_hash_global_init()) >= 0)
+ _tls_init = 1;
+
+ if (error == 0)
+ _tls_init = 1;
+
+ GIT_MEMORY_BARRIER;
+
+ return error;
}
void git_threads_shutdown(void)
{
TlsFree(_tls_index);
_tls_init = 0;
+ git_mutex_free(&git__mwindow_mutex);
+
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
{
void *ptr;
+ assert(_tls_init);
+
if ((ptr = TlsGetValue(_tls_index)) != NULL)
return ptr;
@@ -81,25 +114,42 @@ static void cb__free_status(void *st)
git__free(st);
}
-void git_threads_init(void)
+int git_threads_init(void)
{
+ int error = 0;
+
if (_tls_init)
- return;
+ return 0;
+ git_mutex_init(&git__mwindow_mutex);
pthread_key_create(&_tls_key, &cb__free_status);
- _tls_init = 1;
+
+ /* Initialize any other subsystems that have global state */
+ if ((error = git_hash_global_init()) >= 0)
+ _tls_init = 1;
+
+ GIT_MEMORY_BARRIER;
+
+ return error;
}
void git_threads_shutdown(void)
{
pthread_key_delete(_tls_key);
_tls_init = 0;
+ git_mutex_free(&git__mwindow_mutex);
+
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
{
void *ptr;
+ assert(_tls_init);
+
if ((ptr = pthread_getspecific(_tls_key)) != NULL)
return ptr;
@@ -116,14 +166,17 @@ git_global_st *git__global_state(void)
static git_global_st __state;
-void git_threads_init(void)
+int git_threads_init(void)
{
/* noop */
+ return 0;
}
void git_threads_shutdown(void)
{
- /* noop */
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
+ git_futils_dirs_free();
}
git_global_st *git__global_state(void)
diff --git a/src/global.h b/src/global.h
index 2b525ce07..f0ad1df29 100644
--- a/src/global.h
+++ b/src/global.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,20 +8,25 @@
#define INCLUDE_global_h__
#include "mwindow.h"
+#include "hash.h"
-typedef struct {
- struct {
- char last[1024];
- } error;
+#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;
-
- git_mwindow_ctl mem_ctl;
} git_global_st;
git_global_st *git__global_state(void);
+extern git_mutex git__mwindow_mutex;
+
#define GIT_GLOBAL (git__global_state())
#endif
diff --git a/src/graph.c b/src/graph.c
new file mode 100644
index 000000000..277f588ca
--- /dev/null
+++ b/src/graph.c
@@ -0,0 +1,178 @@
+
+/*
+ * 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 "revwalk.h"
+#include "merge.h"
+#include "git2/graph.h"
+
+static int interesting(git_pqueue *list, git_commit_list *roots)
+{
+ unsigned int i;
+ /* element 0 isn't used - we need to start at 1 */
+ for (i = 1; i < list->size; i++) {
+ git_commit_list_node *commit = list->d[i];
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ while(roots) {
+ if ((roots->item->flags & STALE) == 0)
+ return 1;
+ roots = roots->next;
+ }
+
+ return 0;
+}
+
+static int mark_parents(git_revwalk *walk, git_commit_list_node *one,
+ git_commit_list_node *two)
+{
+ unsigned int i;
+ git_commit_list *roots = NULL;
+ git_pqueue list;
+
+ /* if the commit is repeated, we have a our merge base already */
+ if (one == two) {
+ one->flags |= PARENT1 | PARENT2 | RESULT;
+ return 0;
+ }
+
+ if (git_pqueue_init(&list, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ goto on_error;
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ goto on_error;
+
+ if (git_commit_list_parse(walk, two) < 0)
+ goto on_error;
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ goto on_error;
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list, roots)) {
+ git_commit_list_node *commit;
+ int flags;
+
+ commit = git_pqueue_pop(&list);
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT))
+ commit->flags |= RESULT;
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if (git_commit_list_parse(walk, p) < 0)
+ goto on_error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ goto on_error;
+ }
+
+ /* Keep track of root commits, to make sure the path gets marked */
+ if (commit->out_degree == 0) {
+ if (git_commit_list_insert(commit, &roots) == NULL)
+ goto on_error;
+ }
+ }
+
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return 0;
+
+on_error:
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return -1;
+}
+
+
+static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
+ size_t *ahead, size_t *behind)
+{
+ git_commit_list_node *commit;
+ git_pqueue pq;
+ int i;
+ *ahead = 0;
+ *behind = 0;
+
+ if (git_pqueue_init(&pq, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+ if (git_pqueue_insert(&pq, one) < 0)
+ goto on_error;
+ if (git_pqueue_insert(&pq, two) < 0)
+ goto on_error;
+
+ while ((commit = git_pqueue_pop(&pq)) != NULL) {
+ if (commit->flags & RESULT ||
+ (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))
+ continue;
+ else if (commit->flags & PARENT1)
+ (*behind)++;
+ else if (commit->flags & PARENT2)
+ (*ahead)++;
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if (git_pqueue_insert(&pq, p) < 0)
+ return -1;
+ }
+ commit->flags |= RESULT;
+ }
+
+ git_pqueue_free(&pq);
+ return 0;
+
+on_error:
+ git_pqueue_free(&pq);
+ return -1;
+}
+
+int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
+ const git_oid *local, const git_oid *upstream)
+{
+ git_revwalk *walk;
+ git_commit_list_node *commit_u, *commit_l;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit_u = git_revwalk__commit_lookup(walk, upstream);
+ if (commit_u == NULL)
+ goto on_error;
+
+ commit_l = git_revwalk__commit_lookup(walk, local);
+ if (commit_l == NULL)
+ goto on_error;
+
+ if (mark_parents(walk, commit_l, commit_u) < 0)
+ goto on_error;
+ if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)
+ goto on_error;
+
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+}
diff --git a/src/hash.c b/src/hash.c
index 460756913..f3645a913 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,67 +8,40 @@
#include "common.h"
#include "hash.h"
-#if defined(PPC_SHA1)
-# include "ppc/sha1.h"
-#else
-# include "sha1.h"
-#endif
-
-struct git_hash_ctx {
- SHA_CTX c;
-};
-
-git_hash_ctx *git_hash_new_ctx(void)
+int git_hash_buf(git_oid *out, const void *data, size_t len)
{
- git_hash_ctx *ctx = git__malloc(sizeof(*ctx));
-
- if (!ctx)
- return NULL;
-
- SHA1_Init(&ctx->c);
+ git_hash_ctx ctx;
+ int error = 0;
- return ctx;
-}
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
-void git_hash_free_ctx(git_hash_ctx *ctx)
-{
- git__free(ctx);
-}
+ if ((error = git_hash_update(&ctx, data, len)) >= 0)
+ error = git_hash_final(out, &ctx);
-void git_hash_init(git_hash_ctx *ctx)
-{
- assert(ctx);
- SHA1_Init(&ctx->c);
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
}
-void git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
{
- assert(ctx);
- SHA1_Update(&ctx->c, data, len);
-}
+ git_hash_ctx ctx;
+ size_t i;
+ int error = 0;
-void git_hash_final(git_oid *out, git_hash_ctx *ctx)
-{
- assert(ctx);
- SHA1_Final(out->id, &ctx->c);
-}
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
-void git_hash_buf(git_oid *out, const void *data, size_t len)
-{
- SHA_CTX c;
+ for (i = 0; i < n; i++) {
+ if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)
+ goto done;
+ }
- SHA1_Init(&c);
- SHA1_Update(&c, data, len);
- SHA1_Final(out->id, &c);
-}
+ error = git_hash_final(out, &ctx);
-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
-{
- SHA_CTX c;
- size_t i;
+done:
+ git_hash_ctx_cleanup(&ctx);
- SHA1_Init(&c);
- for (i = 0; i < n; i++)
- SHA1_Update(&c, vec[i].data, vec[i].len);
- SHA1_Final(out->id, &c);
+ return error;
}
diff --git a/src/hash.h b/src/hash.h
index 33d7b20cd..5b848981f 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -9,21 +9,33 @@
#include "git2/oid.h"
+typedef struct git_hash_prov git_hash_prov;
typedef struct git_hash_ctx git_hash_ctx;
+int git_hash_global_init(void);
+void git_hash_global_shutdown(void);
+
+int git_hash_ctx_init(git_hash_ctx *ctx);
+void git_hash_ctx_cleanup(git_hash_ctx *ctx);
+
+#if defined(OPENSSL_SHA1)
+# include "hash/hash_openssl.h"
+#elif defined(WIN32_SHA1)
+# include "hash/hash_win32.h"
+#else
+# include "hash/hash_generic.h"
+#endif
+
typedef struct {
void *data;
size_t len;
} git_buf_vec;
-git_hash_ctx *git_hash_new_ctx(void);
-void git_hash_free_ctx(git_hash_ctx *ctx);
-
-void git_hash_init(git_hash_ctx *c);
-void git_hash_update(git_hash_ctx *c, const void *data, size_t len);
-void git_hash_final(git_oid *out, git_hash_ctx *c);
+int git_hash_init(git_hash_ctx *c);
+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
+int git_hash_final(git_oid *out, git_hash_ctx *c);
-void git_hash_buf(git_oid *out, const void *data, size_t len);
-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
+int git_hash_buf(git_oid *out, const void *data, size_t len);
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
#endif /* INCLUDE_hash_h__ */
diff --git a/src/sha1.c b/src/hash/hash_generic.c
index 8aaedeb8f..32fcd869c 100644
--- a/src/sha1.c
+++ b/src/hash/hash_generic.c
@@ -1,12 +1,13 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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 "sha1.h"
+#include "hash.h"
+#include "hash/hash_generic.h"
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
@@ -112,7 +113,7 @@
#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )
-static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
+static void hash__block(git_hash_ctx *ctx, const unsigned int *data)
{
unsigned int A,B,C,D,E;
unsigned int array[16];
@@ -220,7 +221,7 @@ static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
ctx->H[4] += E;
}
-void git__blk_SHA1_Init(blk_SHA_CTX *ctx)
+int git_hash_init(git_hash_ctx *ctx)
{
ctx->size = 0;
@@ -230,9 +231,11 @@ void git__blk_SHA1_Init(blk_SHA_CTX *ctx)
ctx->H[2] = 0x98badcfe;
ctx->H[3] = 0x10325476;
ctx->H[4] = 0xc3d2e1f0;
+
+ return 0;
}
-void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len)
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
{
unsigned int lenW = ctx->size & 63;
@@ -248,19 +251,21 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len)
len -= left;
data = ((const char *)data + left);
if (lenW)
- return;
- blk_SHA1_Block(ctx, ctx->W);
+ return 0;
+ hash__block(ctx, ctx->W);
}
while (len >= 64) {
- blk_SHA1_Block(ctx, data);
+ hash__block(ctx, data);
data = ((const char *)data + 64);
len -= 64;
}
if (len)
memcpy(ctx->W, data, len);
+
+ return 0;
}
-void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
{
static const unsigned char pad[64] = { 0x80 };
unsigned int padlen[2];
@@ -271,10 +276,13 @@ void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)
padlen[1] = htonl((uint32_t)(ctx->size << 3));
i = ctx->size & 63;
- git__blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i)));
- git__blk_SHA1_Update(ctx, padlen, 8);
+ git_hash_update(ctx, pad, 1+ (63 & (55 - i)));
+ git_hash_update(ctx, padlen, 8);
/* Output hash */
for (i = 0; i < 5; i++)
- put_be32(hashout + i*4, ctx->H[i]);
+ put_be32(out->id + i*4, ctx->H[i]);
+
+ return 0;
}
+
diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h
new file mode 100644
index 000000000..b731de8b3
--- /dev/null
+++ b/src/hash/hash_generic.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_hash_generic_h__
+#define INCLUDE_hash_generic_h__
+
+#include "hash.h"
+
+struct git_hash_ctx {
+ unsigned long long size;
+ unsigned int H[5];
+ unsigned int W[16];
+};
+
+#define git_hash_global_init() 0
+#define git_hash_global_shutdown() /* noop */
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+#endif /* INCLUDE_hash_generic_h__ */
diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h
new file mode 100644
index 000000000..f83279a5a
--- /dev/null
+++ b/src/hash/hash_openssl.h
@@ -0,0 +1,45 @@
+/*
+ * 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_hash_openssl_h__
+#define INCLUDE_hash_openssl_h__
+
+#include "hash.h"
+
+#include <openssl/sha.h>
+
+struct git_hash_ctx {
+ SHA_CTX c;
+};
+
+#define git_hash_global_init() 0
+#define git_hash_global_shutdown() /* noop */
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Init(&ctx->c);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx);
+ SHA1_Update(&ctx->c, data, len);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Final(out->id, &ctx->c);
+ return 0;
+}
+
+#endif /* INCLUDE_hash_openssl_h__ */
diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c
new file mode 100644
index 000000000..43d54ca6d
--- /dev/null
+++ b/src/hash/hash_win32.c
@@ -0,0 +1,291 @@
+/*
+ * 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 "global.h"
+#include "hash.h"
+#include "hash/hash_win32.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+static struct git_hash_prov hash_prov = {0};
+
+/* Hash initialization */
+
+/* Initialize CNG, if available */
+GIT_INLINE(int) hash_cng_prov_init(void)
+{
+ OSVERSIONINFOEX version_test = {0};
+ DWORD version_test_mask;
+ DWORDLONG version_condition_mask = 0;
+ char dll_path[MAX_PATH];
+ DWORD dll_path_len, size_len;
+
+ return -1;
+
+ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ version_test.dwMajorVersion = 6;
+ version_test.dwMinorVersion = 0;
+ version_test.wServicePackMajor = 1;
+ version_test.wServicePackMinor = 0;
+
+ version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);
+
+ VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
+
+ if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))
+ return -1;
+
+ /* Load bcrypt.dll explicitly from the system directory */
+ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH ||
+ StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
+ StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
+ (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL)
+ return -1;
+
+ /* Load the function addresses */
+ if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL ||
+ (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL ||
+ (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL ||
+ (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL ||
+ (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL ||
+ (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL ||
+ (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Load the SHA1 algorithm */
+ if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Get storage space for the hash object */
+ if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) {
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ hash_prov.type = CNG;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cng_prov_shutdown(void)
+{
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+
+ hash_prov.type = INVALID;
+}
+
+/* Initialize CryptoAPI */
+GIT_INLINE(int) hash_cryptoapi_prov_init()
+{
+ if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
+ return -1;
+
+ hash_prov.type = CRYPTOAPI;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
+{
+ CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);
+
+ hash_prov.type = INVALID;
+}
+
+int git_hash_global_init()
+{
+ int error = 0;
+
+ if (hash_prov.type != INVALID)
+ return 0;
+
+ if ((error = hash_cng_prov_init()) < 0)
+ error = hash_cryptoapi_prov_init();
+
+ return error;
+}
+
+void git_hash_global_shutdown()
+{
+ if (hash_prov.type == CNG)
+ hash_cng_prov_shutdown();
+ else if(hash_prov.type == CRYPTOAPI)
+ hash_cryptoapi_prov_shutdown();
+}
+
+/* CryptoAPI: available in Windows XP and newer */
+
+GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx)
+{
+ ctx->type = CRYPTOAPI;
+ ctx->prov = &hash_prov;
+
+ return git_hash_init(ctx);
+}
+
+GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+
+ if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
+ ctx->ctx.cryptoapi.valid = 0;
+ return -1;
+ }
+
+ ctx->ctx.cryptoapi.valid = 1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0))
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx)
+{
+ DWORD len = 20;
+ int error = 0;
+
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0))
+ error = -1;
+
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+ ctx->ctx.cryptoapi.valid = 0;
+
+ return error;
+}
+
+GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+}
+
+/* CNG: Available in Windows Server 2008 and newer */
+
+GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
+ return -1;
+
+ if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+ return -1;
+ }
+
+ ctx->type = CNG;
+ ctx->prov = &hash_prov;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx)
+{
+ BYTE hash[GIT_OID_RAWSZ];
+
+ if (!ctx->ctx.cng.updated)
+ return 0;
+
+ /* CNG needs to be finished to restart */
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx)
+{
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx)
+{
+ ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
+ git__free(ctx->ctx.cng.hash_object);
+}
+
+/* Indirection between CryptoAPI and CNG */
+
+int git_hash_ctx_init(git_hash_ctx *ctx)
+{
+ int error = 0;
+
+ assert(ctx);
+
+ /*
+ * When compiled with GIT_THREADS, the global hash_prov data is
+ * initialized with git_threads_init. Otherwise, it must be initialized
+ * at first use.
+ */
+ if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0)
+ return error;
+
+ memset(ctx, 0x0, sizeof(git_hash_ctx));
+
+ return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
+}
+
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
+}
+
+void git_hash_ctx_cleanup(git_hash_ctx *ctx)
+{
+ assert(ctx);
+
+ if (ctx->type == CNG)
+ hash_ctx_cng_cleanup(ctx);
+ else if(ctx->type == CRYPTOAPI)
+ hash_ctx_cryptoapi_cleanup(ctx);
+}
diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h
new file mode 100644
index 000000000..daa769b59
--- /dev/null
+++ b/src/hash/hash_win32.h
@@ -0,0 +1,140 @@
+/*
+ * 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_hash_win32_h__
+#define INCLUDE_hash_win32_h__
+
+#include "common.h"
+#include "hash.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+enum hash_win32_prov_type {
+ INVALID = 0,
+ CRYPTOAPI,
+ CNG
+};
+
+/*
+ * CryptoAPI is available for hashing on Windows XP and newer.
+ */
+
+struct hash_cryptoapi_prov {
+ HCRYPTPROV handle;
+};
+
+/*
+ * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
+ * preferred, however it is only available on Windows 2008 and newer and
+ * must therefore be dynamically loaded, and we must inline constants that
+ * would not exist when building in pre-Windows 2008 environments.
+ */
+
+#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll"
+
+/* BCRYPT_SHA1_ALGORITHM */
+#define GIT_HASH_CNG_HASH_TYPE L"SHA1"
+
+/* BCRYPT_OBJECT_LENGTH */
+#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
+
+/* BCRYPT_HASH_REUSEABLE_FLAGS */
+#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
+
+/* Function declarations for CNG */
+typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ DWORD dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)(
+ HANDLE /* BCRYPT_HANDLE */ hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG *pcbResult,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject,
+ PUCHAR pbSecret,
+ ULONG cbSecret,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ ULONG dwFlags);
+
+struct hash_cng_prov {
+ /* DLL for CNG */
+ HINSTANCE dll;
+
+ /* Function pointers for CNG */
+ hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider;
+ hash_win32_cng_get_property_fn get_property;
+ hash_win32_cng_create_hash_fn create_hash;
+ hash_win32_cng_finish_hash_fn finish_hash;
+ hash_win32_cng_hash_data_fn hash_data;
+ hash_win32_cng_destroy_hash_fn destroy_hash;
+ hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider;
+
+ HANDLE /* BCRYPT_ALG_HANDLE */ handle;
+ DWORD hash_object_size;
+};
+
+struct git_hash_prov {
+ enum hash_win32_prov_type type;
+
+ union {
+ struct hash_cryptoapi_prov cryptoapi;
+ struct hash_cng_prov cng;
+ } prov;
+};
+
+/* Hash contexts */
+
+struct hash_cryptoapi_ctx {
+ bool valid;
+ HCRYPTHASH hash_handle;
+};
+
+struct hash_cng_ctx {
+ bool updated;
+ HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle;
+ PBYTE hash_object;
+};
+
+struct git_hash_ctx {
+ enum hash_win32_prov_type type;
+ git_hash_prov *prov;
+
+ union {
+ struct hash_cryptoapi_ctx cryptoapi;
+ struct hash_cng_ctx cng;
+ } ctx;
+};
+
+#endif /* INCLUDE_hash_openssl_h__ */
diff --git a/src/hashsig.c b/src/hashsig.c
new file mode 100644
index 000000000..3a75aaaed
--- /dev/null
+++ b/src/hashsig.c
@@ -0,0 +1,368 @@
+/*
+ * 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 "hashsig.h"
+#include "fileops.h"
+#include "util.h"
+
+typedef uint32_t hashsig_t;
+typedef uint64_t hashsig_state;
+
+#define HASHSIG_SCALE 100
+
+#define HASHSIG_HASH_WINDOW 32
+#define HASHSIG_HASH_START 0
+#define HASHSIG_HASH_SHIFT 5
+#define HASHSIG_HASH_MASK 0x7FFFFFFF
+
+#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
+
+typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
+
+typedef struct {
+ int size, asize;
+ hashsig_cmp cmp;
+ hashsig_t values[HASHSIG_HEAP_SIZE];
+} hashsig_heap;
+
+typedef struct {
+ hashsig_state state, shift_n;
+ char window[HASHSIG_HASH_WINDOW];
+ int win_len, win_pos, saw_lf;
+} hashsig_in_progress;
+
+#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 }
+
+struct git_hashsig {
+ hashsig_heap mins;
+ hashsig_heap maxs;
+ git_hashsig_option_t opt;
+ int considered;
+};
+
+#define HEAP_LCHILD_OF(I) (((I)*2)+1)
+#define HEAP_RCHILD_OF(I) (((I)*2)+2)
+#define HEAP_PARENT_OF(I) (((I)-1)>>1)
+
+static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
+{
+ h->size = 0;
+ h->asize = HASHSIG_HEAP_SIZE;
+ h->cmp = cmp;
+}
+
+static int hashsig_cmp_max(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av < bv) ? -1 : (av > bv) ? 1 : 0;
+}
+
+static int hashsig_cmp_min(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av > bv) ? -1 : (av < bv) ? 1 : 0;
+}
+
+static void hashsig_heap_up(hashsig_heap *h, int el)
+{
+ int parent_el = HEAP_PARENT_OF(el);
+
+ while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {
+ hashsig_t t = h->values[el];
+ h->values[el] = h->values[parent_el];
+ h->values[parent_el] = t;
+
+ el = parent_el;
+ parent_el = HEAP_PARENT_OF(el);
+ }
+}
+
+static void hashsig_heap_down(hashsig_heap *h, int el)
+{
+ hashsig_t v, lv, rv;
+
+ /* 'el < h->size / 2' tests if el is bottom row of heap */
+
+ while (el < h->size / 2) {
+ int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel;
+
+ v = h->values[el];
+ lv = h->values[lel];
+ rv = h->values[rel];
+
+ if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)
+ break;
+
+ swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;
+
+ h->values[el] = h->values[swapel];
+ h->values[swapel] = v;
+
+ el = swapel;
+ }
+}
+
+static void hashsig_heap_sort(hashsig_heap *h)
+{
+ /* only need to do this at the end for signature comparison */
+ git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);
+}
+
+static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
+{
+ /* if heap is full, pop top if new element should replace it */
+ if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) {
+ h->size--;
+ h->values[0] = h->values[h->size];
+ hashsig_heap_down(h, 0);
+ }
+
+ /* if heap is not full, insert new element */
+ if (h->size < h->asize) {
+ h->values[h->size++] = val;
+ hashsig_heap_up(h, h->size - 1);
+ }
+}
+
+GIT_INLINE(bool) hashsig_include_char(
+ char ch, git_hashsig_option_t opt, int *saw_lf)
+{
+ if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch))
+ return false;
+
+ if (opt & GIT_HASHSIG_SMART_WHITESPACE) {
+ if (ch == '\r' || (*saw_lf && git__isspace(ch)))
+ return false;
+
+ *saw_lf = (ch == '\n');
+ }
+
+ return true;
+}
+
+static void hashsig_initial_window(
+ git_hashsig *sig,
+ const char **data,
+ size_t size,
+ hashsig_in_progress *prog)
+{
+ hashsig_state state, shift_n;
+ int win_len;
+ const char *scan, *end;
+
+ /* init until we have processed at least HASHSIG_HASH_WINDOW data */
+
+ if (prog->win_len >= HASHSIG_HASH_WINDOW)
+ return;
+
+ state = prog->state;
+ win_len = prog->win_len;
+ shift_n = prog->shift_n;
+
+ scan = *data;
+ end = scan + size;
+
+ while (scan < end && win_len < HASHSIG_HASH_WINDOW) {
+ char ch = *scan++;
+
+ if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))
+ continue;
+
+ state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK;
+
+ if (!win_len)
+ shift_n = 1;
+ else
+ shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;
+
+ prog->window[win_len++] = ch;
+ }
+
+ /* insert initial hash if we just finished */
+
+ if (win_len == HASHSIG_HASH_WINDOW) {
+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);
+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
+ sig->considered = 1;
+ }
+
+ prog->state = state;
+ prog->win_len = win_len;
+ prog->shift_n = shift_n;
+
+ *data = scan;
+}
+
+static int hashsig_add_hashes(
+ git_hashsig *sig,
+ const char *data,
+ size_t size,
+ hashsig_in_progress *prog)
+{
+ const char *scan = data, *end = data + size;
+ hashsig_state state, shift_n, rmv;
+
+ if (prog->win_len < HASHSIG_HASH_WINDOW)
+ hashsig_initial_window(sig, &scan, size, prog);
+
+ state = prog->state;
+ shift_n = prog->shift_n;
+
+ /* advance window, adding new chars and removing old */
+
+ for (; scan < end; ++scan) {
+ char ch = *scan;
+
+ if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))
+ continue;
+
+ rmv = shift_n * prog->window[prog->win_pos];
+
+ state = (state - rmv) & HASHSIG_HASH_MASK;
+ state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;
+ state = (state + ch) & HASHSIG_HASH_MASK;
+
+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);
+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
+ sig->considered++;
+
+ prog->window[prog->win_pos] = ch;
+ prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW;
+ }
+
+ prog->state = state;
+
+ return 0;
+}
+
+static int hashsig_finalize_hashes(git_hashsig *sig)
+{
+ if (sig->mins.size < HASHSIG_HEAP_SIZE) {
+ giterr_set(GITERR_INVALID,
+ "File too small for similarity signature calculation");
+ return GIT_EBUFS;
+ }
+
+ hashsig_heap_sort(&sig->mins);
+ hashsig_heap_sort(&sig->maxs);
+
+ return 0;
+}
+
+static git_hashsig *hashsig_alloc(git_hashsig_option_t opts)
+{
+ git_hashsig *sig = git__calloc(1, sizeof(git_hashsig));
+ if (!sig)
+ return NULL;
+
+ hashsig_heap_init(&sig->mins, hashsig_cmp_min);
+ hashsig_heap_init(&sig->maxs, hashsig_cmp_max);
+ sig->opt = opts;
+
+ return sig;
+}
+
+int git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts)
+{
+ int error;
+ hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ error = hashsig_add_hashes(sig, buf, buflen, &prog);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+int git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts)
+{
+ char buf[4096];
+ ssize_t buflen = 0;
+ int error = 0, fd;
+ hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ if ((fd = git_futils_open_ro(path)) < 0) {
+ git__free(sig);
+ return fd;
+ }
+
+ while (!error) {
+ if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
+ if ((error = (int)buflen) < 0)
+ giterr_set(GITERR_OS,
+ "Read error on '%s' calculating similarity hashes", path);
+ break;
+ }
+
+ error = hashsig_add_hashes(sig, buf, buflen, &prog);
+ }
+
+ p_close(fd);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+void git_hashsig_free(git_hashsig *sig)
+{
+ git__free(sig);
+}
+
+static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
+{
+ int matches = 0, i, j, cmp;
+
+ assert(a->cmp == b->cmp);
+
+ /* hash heaps are sorted - just look for overlap vs total */
+
+ for (i = 0, j = 0; i < a->size && j < b->size; ) {
+ cmp = a->cmp(&a->values[i], &b->values[j], NULL);
+
+ if (cmp < 0)
+ ++i;
+ else if (cmp > 0)
+ ++j;
+ else {
+ ++i; ++j; ++matches;
+ }
+ }
+
+ return HASHSIG_SCALE * (matches * 2) / (a->size + b->size);
+}
+
+int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
+{
+ return (hashsig_heap_compare(&a->mins, &b->mins) +
+ hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
+}
+
diff --git a/src/hashsig.h b/src/hashsig.h
new file mode 100644
index 000000000..8c920cbf1
--- /dev/null
+++ b/src/hashsig.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_hashsig_h__
+#define INCLUDE_hashsig_h__
+
+#include "common.h"
+
+/**
+ * Similarity signature of line hashes for a buffer
+ */
+typedef struct git_hashsig git_hashsig;
+
+typedef enum {
+ GIT_HASHSIG_NORMAL = 0, /* use all data */
+ GIT_HASHSIG_IGNORE_WHITESPACE = 1, /* ignore whitespace */
+ GIT_HASHSIG_SMART_WHITESPACE = 2, /* ignore \r and all space after \n */
+} git_hashsig_option_t;
+
+/**
+ * Build a similarity signature for a buffer
+ *
+ * If you have passed a whitespace-ignoring buffer, then the whitespace
+ * will be removed from the buffer while it is being processed, modifying
+ * the buffer in place. Sorry about that!
+ *
+ * This will return an error if the buffer doesn't contain enough data to
+ * compute a valid signature.
+ *
+ * @param out The array of hashed runs representing the file content
+ * @param buf The contents of the file to hash
+ * @param buflen The length of the data at `buf`
+ * @param generate_pairwise_hashes Should pairwise runs be hashed
+ */
+extern int git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts);
+
+/**
+ * Build a similarity signature from a file
+ *
+ * This walks through the file, only loading a maximum of 4K of file data at
+ * a time. Otherwise, it acts just like `git_hashsig_create`.
+ *
+ * This will return an error if the file doesn't contain enough data to
+ * compute a valid signature.
+ */
+extern int git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts);
+
+/**
+ * Release memory for a content similarity signature
+ */
+extern void git_hashsig_free(git_hashsig *sig);
+
+/**
+ * Measure similarity between two files
+ *
+ * @return <0 for error, [0 to 100] as similarity score
+ */
+extern int git_hashsig_compare(
+ const git_hashsig *a,
+ const git_hashsig *b);
+
+#endif
diff --git a/src/ignore.c b/src/ignore.c
index fc6194bb5..17779522c 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -1,19 +1,38 @@
+#include "git2/ignore.h"
#include "ignore.h"
+#include "attr.h"
#include "path.h"
+#include "config.h"
#define GIT_IGNORE_INTERNAL "[internal]exclude"
-#define GIT_IGNORE_FILE_INREPO "info/exclude"
-#define GIT_IGNORE_FILE ".gitignore"
+
+#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
static int parse_ignore_file(
- git_repository *repo, const char *buffer, git_attr_file *ignores)
+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores)
{
int error = 0;
git_attr_fnmatch *match = NULL;
const char *scan = NULL;
char *context = NULL;
-
- GIT_UNUSED(repo);
+ 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);
+
+ git_config_free(cfg);
+ }
if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) {
context = ignores->key + 2;
@@ -28,10 +47,16 @@ static int parse_ignore_file(
GITERR_CHECK_ALLOC(match);
}
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+
if (!(error = git_attr_fnmatch__parse(
match, ignores->pool, context, &scan)))
{
- match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE;
+ match->flags |= GIT_ATTR_FNMATCH_IGNORE;
+
+ if (ignore_case)
+ match->flags |= GIT_ATTR_FNMATCH_ICASE;
+
scan = git__next_line(scan);
error = git_vector_insert(&ignores->rules, match);
}
@@ -55,13 +80,26 @@ static int parse_ignore_file(
return error;
}
-#define push_ignore_file(R,S,B,F) \
- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(S))
+#define push_ignore_file(R,IGN,S,B,F) \
+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S))
static int push_one_ignore(void *ref, git_buf *path)
{
git_ignores *ign = (git_ignores *)ref;
- return push_ignore_file(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+ return push_ignore_file(ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+}
+
+static int get_internal_ignores(git_attr_file **ign, git_repository *repo)
+{
+ int error;
+
+ if (!(error = git_attr_cache__init(repo)))
+ error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign);
+
+ if (!error && !(*ign)->rules.length)
+ error = parse_ignore_file(repo, NULL, GIT_IGNORE_DEFAULT_RULES, *ign);
+
+ return error;
}
int git_ignore__for_path(
@@ -71,6 +109,8 @@ int git_ignore__for_path(
{
int error = 0;
const char *workdir = git_repository_workdir(repo);
+ git_config *cfg = NULL;
+ int val;
assert(ignores);
@@ -78,6 +118,17 @@ 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)
+ 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)
@@ -92,8 +143,7 @@ int git_ignore__for_path(
goto cleanup;
/* set up internals */
- error = git_attr_cache__internal_file(
- repo, GIT_IGNORE_INTERNAL, &ignores->ign_internal);
+ error = get_internal_ignores(&ignores->ign_internal, repo);
if (error < 0)
goto cleanup;
@@ -106,14 +156,14 @@ int git_ignore__for_path(
}
/* load .git/info/exclude */
- error = push_ignore_file(repo, &ignores->ign_global,
+ error = push_ignore_file(repo, ignores, &ignores->ign_global,
git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
if (error < 0)
goto cleanup;
/* load core.excludesfile */
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
- error = push_ignore_file(repo, &ignores->ign_global, NULL,
+ error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL,
git_repository_attr_cache(repo)->cfg_excl_file);
cleanup:
@@ -129,7 +179,7 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir)
return -1;
else
return push_ignore_file(
- ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
+ ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
}
int git_ignore__pop_dir(git_ignores *ign)
@@ -154,7 +204,7 @@ void git_ignore__free(git_ignores *ignores)
static bool ignore_lookup_in_rules(
git_vector *rules, git_attr_path *path, int *ignored)
{
- unsigned int j;
+ size_t j;
git_attr_fnmatch *match;
git_vector_rforeach(rules, j, match) {
@@ -201,3 +251,110 @@ cleanup:
git_attr_path__free(&path);
return 0;
}
+
+int git_ignore_add_rule(
+ git_repository *repo,
+ const char *rules)
+{
+ int error;
+ git_attr_file *ign_internal;
+
+ if (!(error = get_internal_ignores(&ign_internal, repo)))
+ error = parse_ignore_file(repo, NULL, rules, ign_internal);
+
+ return error;
+}
+
+int git_ignore_clear_internal_rules(
+ git_repository *repo)
+{
+ int error;
+ git_attr_file *ign_internal;
+
+ if (!(error = get_internal_ignores(&ign_internal, repo))) {
+ git_attr_file__clear_rules(ign_internal);
+
+ return parse_ignore_file(
+ repo, NULL, GIT_IGNORE_DEFAULT_RULES, ign_internal);
+ }
+
+ return error;
+}
+
+int git_ignore_path_is_ignored(
+ int *ignored,
+ git_repository *repo,
+ const char *pathname)
+{
+ int error;
+ const char *workdir;
+ git_attr_path path;
+ char *tail, *end;
+ bool full_is_dir;
+ git_ignores ignores;
+ unsigned int i;
+ git_attr_file *file;
+
+ assert(ignored && pathname);
+
+ workdir = repo ? git_repository_workdir(repo) : NULL;
+
+ if ((error = git_attr_path__init(&path, pathname, workdir)) < 0)
+ return error;
+
+ tail = path.path;
+ end = &path.full.ptr[path.full.size];
+ full_is_dir = path.is_dir;
+
+ while (1) {
+ /* advance to next component of path */
+ path.basename = tail;
+
+ while (tail < end && *tail != '/') tail++;
+ *tail = '\0';
+
+ path.full.size = (tail - path.full.ptr);
+ path.is_dir = (tail == end) ? full_is_dir : true;
+
+ /* update ignores for new path fragment */
+ if (path.basename == path.path)
+ error = git_ignore__for_path(repo, path.path, &ignores);
+ else
+ error = git_ignore__push_dir(&ignores, path.basename);
+ if (error < 0)
+ break;
+
+ /* first process builtins - success means path was found */
+ if (ignore_lookup_in_rules(
+ &ignores.ign_internal->rules, &path, ignored))
+ goto cleanup;
+
+ /* next process files in the path */
+ git_vector_foreach(&ignores.ign_path, i, file) {
+ if (ignore_lookup_in_rules(&file->rules, &path, ignored))
+ goto cleanup;
+ }
+
+ /* last process global ignores */
+ git_vector_foreach(&ignores.ign_global, i, file) {
+ if (ignore_lookup_in_rules(&file->rules, &path, ignored))
+ goto cleanup;
+ }
+
+ /* if we found no rules before reaching the end, we're done */
+ if (tail == end)
+ break;
+
+ /* reinstate divider in path */
+ *tail = '/';
+ while (*tail == '/') tail++;
+ }
+
+ *ignored = 0;
+
+cleanup:
+ git_attr_path__free(&path);
+ git_ignore__free(&ignores);
+ return error;
+}
+
diff --git a/src/ignore.h b/src/ignore.h
index 809d2edbd..5af8e8e7d 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -9,6 +9,11 @@
#include "repository.h"
#include "vector.h"
+#include "attr_file.h"
+
+#define GIT_IGNORE_FILE ".gitignore"
+#define GIT_IGNORE_FILE_INREPO "info/exclude"
+#define GIT_IGNORE_FILE_XDG "ignore"
/* The git_ignores structure maintains three sets of ignores:
* - internal ignores
@@ -23,6 +28,7 @@ typedef struct {
git_attr_file *ign_internal;
git_vector ign_path;
git_vector ign_global;
+ unsigned int ignore_case:1;
} 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 f1ae9a710..6290ec4e8 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -13,8 +13,12 @@
#include "tree.h"
#include "tree-cache.h"
#include "hash.h"
+#include "iterator.h"
+#include "pathspec.h"
#include "git2/odb.h"
+#include "git2/oid.h"
#include "git2/blob.h"
+#include "git2/config.h"
#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
#define short_entry_size(len) entry_size(struct entry_short, len)
@@ -79,94 +83,225 @@ struct entry_long {
char path[1]; /* arbitrary length */
};
+struct entry_srch_key {
+ const char *path;
+ int stage;
+};
+
/* local declarations */
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size);
static int read_header(struct index_header *dest, const void *buffer);
static int parse_index(git_index *index, const char *buffer, size_t buffer_size);
-static int is_index_extended(git_index *index);
+static bool is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file);
+static int index_find(size_t *at_pos, git_index *index, const char *path, int stage);
+
static void index_entry_free(git_index_entry *entry);
+static void index_entry_reuc_free(git_index_reuc_entry *reuc);
+
+GIT_INLINE(int) index_entry_stage(const git_index_entry *entry)
+{
+ return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+}
static int index_srch(const void *key, const void *array_member)
{
+ const struct entry_srch_key *srch_key = key;
+ const git_index_entry *entry = array_member;
+ int ret;
+
+ ret = strcmp(srch_key->path, entry->path);
+
+ if (ret == 0)
+ ret = srch_key->stage - index_entry_stage(entry);
+
+ return ret;
+}
+
+static int index_isrch(const void *key, const void *array_member)
+{
+ const struct entry_srch_key *srch_key = key;
const git_index_entry *entry = array_member;
+ int ret;
+
+ ret = strcasecmp(srch_key->path, entry->path);
- return strcmp(key, entry->path);
+ if (ret == 0)
+ ret = srch_key->stage - index_entry_stage(entry);
+
+ return ret;
+}
+
+static int index_cmp_path(const void *a, const void *b)
+{
+ return strcmp((const char *)a, (const char *)b);
+}
+
+static int index_icmp_path(const void *a, const void *b)
+{
+ return strcasecmp((const char *)a, (const char *)b);
+}
+
+static int index_srch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcmp((const char *)path, entry->path);
+}
+
+static int index_isrch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcasecmp((const char *)path, entry->path);
}
static int index_cmp(const void *a, const void *b)
{
+ int diff;
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
- return strcmp(entry_a->path, entry_b->path);
+ diff = strcmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+
+ return diff;
}
-static int unmerged_srch(const void *key, const void *array_member)
+static int index_icmp(const void *a, const void *b)
{
- const git_index_entry_unmerged *entry = array_member;
+ int diff;
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
- return strcmp(key, entry->path);
+ diff = strcasecmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+
+ return diff;
+}
+
+static int reuc_srch(const void *key, const void *array_member)
+{
+ const git_index_reuc_entry *reuc = array_member;
+
+ return strcmp(key, reuc->path);
}
-static int unmerged_cmp(const void *a, const void *b)
+static int reuc_isrch(const void *key, const void *array_member)
{
- const git_index_entry_unmerged *info_a = a;
- const git_index_entry_unmerged *info_b = b;
+ const git_index_reuc_entry *reuc = array_member;
+
+ return strcasecmp(key, reuc->path);
+}
+
+static int reuc_cmp(const void *a, const void *b)
+{
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
return strcmp(info_a->path, info_b->path);
}
+static int reuc_icmp(const void *a, const void *b)
+{
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
+
+ return strcasecmp(info_a->path, info_b->path);
+}
+
static unsigned int index_create_mode(unsigned int mode)
{
if (S_ISLNK(mode))
return S_IFLNK;
+
if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))
return (S_IFLNK | S_IFDIR);
+
return S_IFREG | ((mode & 0100) ? 0755 : 0644);
}
+static unsigned int index_merge_mode(
+ git_index *index, git_index_entry *existing, unsigned int mode)
+{
+ if (index->no_symlinks && S_ISREG(mode) &&
+ existing && S_ISLNK(existing->mode))
+ return existing->mode;
+
+ if (index->distrust_filemode && S_ISREG(mode))
+ return (existing && S_ISREG(existing->mode)) ?
+ existing->mode : index_create_mode(0666);
+
+ return index_create_mode(mode);
+}
+
+void git_index__set_ignore_case(git_index *index, bool ignore_case)
+{
+ index->ignore_case = ignore_case;
+
+ index->entries._cmp = ignore_case ? index_icmp : index_cmp;
+ index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
+ index->entries_search = ignore_case ? index_isrch : index_srch;
+ index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
+ index->entries.sorted = 0;
+ git_vector_sort(&index->entries);
+
+ index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;
+ index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
+ index->reuc.sorted = 0;
+ git_vector_sort(&index->reuc);
+}
+
int git_index_open(git_index **index_out, const char *index_path)
{
git_index *index;
- assert(index_out && index_path);
+ assert(index_out);
index = git__calloc(1, sizeof(git_index));
GITERR_CHECK_ALLOC(index);
- index->index_file_path = git__strdup(index_path);
- GITERR_CHECK_ALLOC(index->index_file_path);
+ if (index_path != NULL) {
+ index->index_file_path = git__strdup(index_path);
+ GITERR_CHECK_ALLOC(index->index_file_path);
+
+ /* Check if index file is stored on disk already */
+ if (git_path_exists(index->index_file_path) == true)
+ index->on_disk = 1;
+ }
- if (git_vector_init(&index->entries, 32, index_cmp) < 0)
+ if (git_vector_init(&index->entries, 32, index_cmp) < 0 ||
+ git_vector_init(&index->reuc, 32, reuc_cmp) < 0)
return -1;
- /* Check if index file is stored on disk already */
- if (git_path_exists(index->index_file_path) == true)
- index->on_disk = 1;
+ index->entries_cmp_path = index_cmp_path;
+ index->entries_search = index_srch;
+ index->entries_search_path = index_srch_path;
+ index->reuc_search = reuc_srch;
*index_out = index;
GIT_REFCOUNT_INC(index);
- return git_index_read(index);
+
+ return (index_path != NULL) ? git_index_read(index) : 0;
}
-static void index_free(git_index *index)
+int git_index_new(git_index **out)
{
- git_index_entry *e;
- unsigned int i;
+ return git_index_open(out, NULL);
+}
+static void index_free(git_index *index)
+{
git_index_clear(index);
- git_vector_foreach(&index->entries, i, e) {
- index_entry_free(e);
- }
git_vector_free(&index->entries);
- git_vector_foreach(&index->unmerged, i, e) {
- index_entry_free(e);
- }
- git_vector_free(&index->unmerged);
+ git_vector_free(&index->reuc);
git__free(index->index_file_path);
git__free(index);
@@ -182,7 +317,7 @@ void git_index_free(git_index *index)
void git_index_clear(git_index *index)
{
- unsigned int i;
+ size_t i;
assert(index);
@@ -192,29 +327,75 @@ void git_index_clear(git_index *index)
git__free(e->path);
git__free(e);
}
-
- for (i = 0; i < index->unmerged.length; ++i) {
- git_index_entry_unmerged *e;
- e = git_vector_get(&index->unmerged, i);
- git__free(e->path);
- git__free(e);
- }
-
git_vector_clear(&index->entries);
- git_vector_clear(&index->unmerged);
- index->last_modified = 0;
+
+ git_index_reuc_clear(index);
+
+ git_futils_filestamp_set(&index->stamp, NULL);
git_tree_cache_free(index->tree);
index->tree = NULL;
}
+static int create_index_error(int error, const char *msg)
+{
+ giterr_set(GITERR_INDEX, msg);
+ return error;
+}
+
+int git_index_set_caps(git_index *index, unsigned int caps)
+{
+ int old_ignore_case;
+
+ assert(index);
+
+ old_ignore_case = index->ignore_case;
+
+ if (caps == GIT_INDEXCAP_FROM_OWNER) {
+ git_config *cfg;
+ 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 (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
+ index->ignore_case = (val != 0);
+ if (git_config_get_bool(&val, cfg, "core.filemode") == 0)
+ index->distrust_filemode = (val == 0);
+ if (git_config_get_bool(&val, cfg, "core.symlinks") == 0)
+ index->no_symlinks = (val == 0);
+ }
+ else {
+ index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
+ index->distrust_filemode = ((caps & GIT_INDEXCAP_NO_FILEMODE) != 0);
+ index->no_symlinks = ((caps & GIT_INDEXCAP_NO_SYMLINKS) != 0);
+ }
+
+ if (old_ignore_case != index->ignore_case) {
+ git_index__set_ignore_case(index, index->ignore_case);
+ }
+
+ return 0;
+}
+
+unsigned int git_index_caps(const git_index *index)
+{
+ return ((index->ignore_case ? GIT_INDEXCAP_IGNORE_CASE : 0) |
+ (index->distrust_filemode ? GIT_INDEXCAP_NO_FILEMODE : 0) |
+ (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0));
+}
+
int git_index_read(git_index *index)
{
- int error, updated;
+ int error = 0, updated;
git_buf buffer = GIT_BUF_INIT;
- time_t mtime;
+ git_futils_filestamp stamp = {0};
- assert(index->index_file_path);
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
if (!index->on_disk || git_path_exists(index->index_file_path) == false) {
git_index_clear(index);
@@ -222,33 +403,35 @@ int git_index_read(git_index *index)
return 0;
}
- /* We don't want to update the mtime if we fail to parse the index */
- mtime = index->last_modified;
- error = git_futils_readbuffer_updated(
- &buffer, index->index_file_path, &mtime, &updated);
+ updated = git_futils_filestamp_check(&stamp, index->index_file_path);
+ if (updated <= 0)
+ return updated;
+
+ error = git_futils_readbuffer(&buffer, index->index_file_path);
if (error < 0)
return error;
- if (updated) {
- git_index_clear(index);
- error = parse_index(index, buffer.ptr, buffer.size);
-
- if (!error)
- index->last_modified = mtime;
+ git_index_clear(index);
+ error = parse_index(index, buffer.ptr, buffer.size);
- git_buf_free(&buffer);
- }
+ if (!error)
+ git_futils_filestamp_set(&index->stamp, &stamp);
+ git_buf_free(&buffer);
return error;
}
int git_index_write(git_index *index)
{
git_filebuf file = GIT_FILEBUF_INIT;
- struct stat indexst;
int error;
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
+
git_vector_sort(&index->entries);
+ git_vector_sort(&index->reuc);
if ((error = git_filebuf_open(
&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0)
@@ -262,33 +445,65 @@ int git_index_write(git_index *index)
if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0)
return error;
- if (p_stat(index->index_file_path, &indexst) == 0) {
- index->last_modified = indexst.st_mtime;
- index->on_disk = 1;
- }
+ error = git_futils_filestamp_check(&index->stamp, index->index_file_path);
+ if (error < 0)
+ return error;
+ index->on_disk = 1;
return 0;
}
-unsigned int git_index_entrycount(git_index *index)
+int git_index_write_tree(git_oid *oid, git_index *index)
+{
+ git_repository *repo;
+
+ assert(oid && index);
+
+ repo = INDEX_OWNER(index);
+
+ if (repo == NULL)
+ return create_index_error(-1, "Failed to write tree. "
+ "The index file is not backed up by an existing repository");
+
+ return git_tree__write_index(oid, index, repo);
+}
+
+int git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo)
+{
+ assert(oid && index && repo);
+ return git_tree__write_index(oid, index, repo);
+}
+
+size_t git_index_entrycount(const git_index *index)
{
assert(index);
return index->entries.length;
}
-unsigned int git_index_entrycount_unmerged(git_index *index)
+const git_index_entry *git_index_get_byindex(
+ git_index *index, size_t n)
{
assert(index);
- return index->unmerged.length;
+ git_vector_sort(&index->entries);
+ return git_vector_get(&index->entries, n);
}
-git_index_entry *git_index_get(git_index *index, unsigned int n)
+const git_index_entry *git_index_get_bypath(
+ git_index *index, const char *path, int stage)
{
+ size_t pos;
+
+ assert(index);
+
git_vector_sort(&index->entries);
- return git_vector_get(&index->entries, n);
+
+ if (index_find(&pos, index, path, stage) < 0)
+ return NULL;
+
+ return git_index_get_byindex(index, pos);
}
-void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry)
+void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st)
{
entry->ctime.seconds = (git_time_t)st->st_ctime;
entry->mtime.seconds = (git_time_t)st->st_mtime;
@@ -302,7 +517,23 @@ void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry)
entry->file_size = st->st_size;
}
-static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage)
+int git_index_entry__cmp(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcmp(entry_a->path, entry_b->path);
+}
+
+int git_index_entry__cmp_icase(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcasecmp(entry_a->path, entry_b->path);
+}
+
+static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path)
{
git_index_entry *entry = NULL;
struct stat st;
@@ -311,15 +542,16 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
git_buf full_path = GIT_BUF_INIT;
int error;
- assert(stage >= 0 && stage <= 3);
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not initialize index entry. "
+ "Index is not backed up by an existing repository.");
- if (INDEX_OWNER(index) == NULL ||
- (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL)
- {
- giterr_set(GITERR_INDEX,
+ workdir = git_repository_workdir(INDEX_OWNER(index));
+
+ if (!workdir)
+ return create_index_error(GIT_EBAREREPO,
"Could not initialize index entry. Repository is bare");
- return -1;
- }
if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)
return error;
@@ -336,16 +568,15 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
*/
/* write the blob to disk and get the oid */
- if ((error = git_blob_create_fromfile(&oid, INDEX_OWNER(index), rel_path)) < 0)
+ if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0)
return error;
entry = git__calloc(1, sizeof(git_index_entry));
GITERR_CHECK_ALLOC(entry);
- git_index__init_entry_from_stat(&st, entry);
+ git_index_entry__init_from_stat(entry, &st);
entry->oid = oid;
- entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT);
entry->path = git__strdup(rel_path);
GITERR_CHECK_ALLOC(entry->path);
@@ -353,6 +584,46 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
return 0;
}
+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)
+{
+ git_index_reuc_entry *reuc = NULL;
+
+ assert(reuc_out && path);
+
+ *reuc_out = NULL;
+
+ reuc = git__calloc(1, sizeof(git_index_reuc_entry));
+ GITERR_CHECK_ALLOC(reuc);
+
+ reuc->path = git__strdup(path);
+ if (reuc->path == NULL)
+ return -1;
+
+ if ((reuc->mode[0] = ancestor_mode) > 0)
+ git_oid_cpy(&reuc->oid[0], ancestor_oid);
+
+ if ((reuc->mode[1] = our_mode) > 0)
+ git_oid_cpy(&reuc->oid[1], our_oid);
+
+ if ((reuc->mode[2] = their_mode) > 0)
+ git_oid_cpy(&reuc->oid[2], their_oid);
+
+ *reuc_out = reuc;
+ return 0;
+}
+
+static void index_entry_reuc_free(git_index_reuc_entry *reuc)
+{
+ if (!reuc)
+ return;
+
+ git__free(reuc->path);
+ git__free(reuc);
+}
+
static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
{
git_index_entry *entry;
@@ -381,9 +652,8 @@ static void index_entry_free(git_index_entry *entry)
static int index_insert(git_index *index, git_index_entry *entry, int replace)
{
- size_t path_length;
- int position;
- git_index_entry **entry_array;
+ size_t path_length, position;
+ git_index_entry **existing = NULL;
assert(index && entry && entry->path != NULL);
@@ -395,62 +665,95 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
if (path_length < GIT_IDXENTRY_NAMEMASK)
entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;
else
- entry->flags |= GIT_IDXENTRY_NAMEMASK;;
-
- /*
- * replacing is not requested: just insert entry at the end;
- * the index is no longer sorted
- */
- if (!replace)
- return git_vector_insert(&index->entries, entry);
+ entry->flags |= GIT_IDXENTRY_NAMEMASK;
/* look if an entry with this path already exists */
- position = git_index_find(index, entry->path);
+ if (!index_find(&position, index, entry->path, index_entry_stage(entry))) {
+ existing = (git_index_entry **)&index->entries.contents[position];
+
+ /* update filemode to existing values if stat is not trusted */
+ entry->mode = index_merge_mode(index, *existing, entry->mode);
+ }
- /*
- * if no entry exists add the entry at the end;
- * the index is no longer sorted
+ /* if replacing is not requested or no existing entry exists, just
+ * insert entry at the end; the index is no longer sorted
*/
- if (position == GIT_ENOTFOUND)
+ if (!replace || !existing)
return git_vector_insert(&index->entries, entry);
/* exists, replace it */
- entry_array = (git_index_entry **) index->entries.contents;
- git__free(entry_array[position]->path);
- git__free(entry_array[position]);
- entry_array[position] = entry;
+ git__free((*existing)->path);
+ git__free(*existing);
+ *existing = entry;
return 0;
}
-static int index_add(git_index *index, const char *path, int stage, int replace)
+static int index_conflict_to_reuc(git_index *index, const char *path)
{
- git_index_entry *entry = NULL;
+ git_index_entry *conflict_entries[3];
+ int ancestor_mode, our_mode, their_mode;
+ git_oid *ancestor_oid, *our_oid, *their_oid;
int ret;
- if ((ret = index_entry_init(&entry, index, path, stage)) < 0 ||
- (ret = index_insert(index, entry, replace)) < 0)
- {
- index_entry_free(entry);
+ if ((ret = git_index_conflict_get(&conflict_entries[0],
+ &conflict_entries[1], &conflict_entries[2], index, path)) < 0)
return ret;
- }
- git_tree_cache_invalidate_path(index->tree, entry->path);
- return 0;
+ ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode;
+ our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode;
+ their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode;
+
+ ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->oid;
+ our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->oid;
+ their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->oid;
+
+ if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid,
+ our_mode, our_oid, their_mode, their_oid)) >= 0)
+ ret = git_index_conflict_remove(index, path);
+
+ return ret;
}
-int git_index_add(git_index *index, const char *path, int stage)
+int git_index_add_bypath(git_index *index, const char *path)
{
- return index_add(index, path, stage, 1);
+ git_index_entry *entry = NULL;
+ int ret;
+
+ assert(index && path);
+
+ if ((ret = index_entry_init(&entry, index, path)) < 0 ||
+ (ret = index_insert(index, entry, 1)) < 0)
+ goto on_error;
+
+ /* Adding implies conflict was resolved, move conflict entries to REUC */
+ if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
+ goto on_error;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
+
+on_error:
+ index_entry_free(entry);
+ return ret;
}
-int git_index_append(git_index *index, const char *path, int stage)
+int git_index_remove_bypath(git_index *index, const char *path)
{
- return index_add(index, path, stage, 0);
+ int ret;
+
+ assert(index && path);
+
+ if (((ret = git_index_remove(index, path, 0)) < 0 &&
+ ret != GIT_ENOTFOUND) ||
+ ((ret = index_conflict_to_reuc(index, path)) < 0 &&
+ ret != GIT_ENOTFOUND))
+ return ret;
+
+ return 0;
}
-static int index_add2(
- git_index *index, const git_index_entry *source_entry, int replace)
+int git_index_add(git_index *index, const git_index_entry *source_entry)
{
git_index_entry *entry = NULL;
int ret;
@@ -459,7 +762,7 @@ static int index_add2(
if (entry == NULL)
return -1;
- if ((ret = index_insert(index, entry, replace)) < 0) {
+ if ((ret = index_insert(index, entry, 1)) < 0) {
index_entry_free(entry);
return ret;
}
@@ -468,28 +771,22 @@ static int index_add2(
return 0;
}
-int git_index_add2(git_index *index, const git_index_entry *source_entry)
-{
- return index_add2(index, source_entry, 1);
-}
-
-int git_index_append2(git_index *index, const git_index_entry *source_entry)
-{
- return index_add2(index, source_entry, 1);
-}
-
-int git_index_remove(git_index *index, int position)
+int git_index_remove(git_index *index, const char *path, int stage)
{
+ size_t position;
int error;
git_index_entry *entry;
git_vector_sort(&index->entries);
+ if (index_find(&position, index, path, stage) < 0)
+ return GIT_ENOTFOUND;
+
entry = git_vector_get(&index->entries, position);
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
- error = git_vector_remove(&index->entries, (unsigned int)position);
+ error = git_vector_remove(&index->entries, position);
if (!error)
index_entry_free(entry);
@@ -497,45 +794,362 @@ int git_index_remove(git_index *index, int position)
return error;
}
-int git_index_find(git_index *index, const char *path)
+int git_index_remove_directory(git_index *index, const char *dir, int stage)
{
- return git_vector_bsearch2(&index->entries, index_srch, path);
+ git_buf pfx = GIT_BUF_INIT;
+ int error = 0;
+ size_t pos;
+ git_index_entry *entry;
+
+ if (git_buf_sets(&pfx, dir) < 0 || git_path_to_dir(&pfx) < 0)
+ return -1;
+
+ git_vector_sort(&index->entries);
+
+ pos = git_index__prefix_position(index, pfx.ptr);
+
+ while (1) {
+ entry = git_vector_get(&index->entries, pos);
+ if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)
+ break;
+
+ if (index_entry_stage(entry) != stage) {
+ ++pos;
+ continue;
+ }
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+
+ if ((error = git_vector_remove(&index->entries, pos)) < 0)
+ break;
+ index_entry_free(entry);
+
+ /* removed entry at 'pos' so we don't need to increment it */
+ }
+
+ git_buf_free(&pfx);
+
+ return error;
}
-unsigned int git_index__prefix_position(git_index *index, const char *path)
+static int index_find(size_t *at_pos, git_index *index, const char *path, int stage)
{
- unsigned int pos;
+ struct entry_srch_key srch_key;
- git_vector_bsearch3(&pos, &index->entries, index_srch, path);
+ assert(path);
+
+ srch_key.path = path;
+ srch_key.stage = stage;
+
+ return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key);
+}
+
+int git_index_find(size_t *at_pos, git_index *index, const char *path)
+{
+ size_t pos;
+
+ assert(index && path);
+
+ if (git_vector_bsearch2(&pos, &index->entries, index->entries_search_path, path) < 0) {
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
+ return GIT_ENOTFOUND;
+ }
+
+ /* Since our binary search only looked at path, we may be in the
+ * middle of a list of stages.
+ */
+ while (pos > 0) {
+ const git_index_entry *prev = git_vector_get(&index->entries, pos-1);
+
+ if (index->entries_cmp_path(prev->path, path) != 0)
+ break;
+
+ --pos;
+ }
+
+ if (at_pos)
+ *at_pos = pos;
+
+ return 0;
+}
+
+size_t git_index__prefix_position(git_index *index, const char *path)
+{
+ struct entry_srch_key srch_key;
+ size_t pos;
+
+ srch_key.path = path;
+ srch_key.stage = 0;
+
+ git_vector_sort(&index->entries);
+ git_vector_bsearch2(
+ &pos, &index->entries, index->entries_search, &srch_key);
return pos;
}
-void git_index_uniq(git_index *index)
+int git_index_conflict_add(git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry)
{
- git_vector_uniq(&index->entries);
+ git_index_entry *entries[3] = { 0 };
+ unsigned short i;
+ int ret = 0;
+
+ assert (index);
+
+ if ((ancestor_entry != NULL && (entries[0] = index_entry_dup(ancestor_entry)) == NULL) ||
+ (our_entry != NULL && (entries[1] = index_entry_dup(our_entry)) == NULL) ||
+ (their_entry != NULL && (entries[2] = index_entry_dup(their_entry)) == NULL))
+ return -1;
+
+ for (i = 0; i < 3; i++) {
+ if (entries[i] == NULL)
+ continue;
+
+ /* Make sure stage is correct */
+ entries[i]->flags = (entries[i]->flags & ~GIT_IDXENTRY_STAGEMASK) |
+ ((i+1) << GIT_IDXENTRY_STAGESHIFT);
+
+ if ((ret = index_insert(index, entries[i], 1)) < 0)
+ goto on_error;
+ }
+
+ return 0;
+
+on_error:
+ for (i = 0; i < 3; i++) {
+ if (entries[i] != NULL)
+ index_entry_free(entries[i]);
+ }
+
+ return ret;
}
-const git_index_entry_unmerged *git_index_get_unmerged_bypath(
+int git_index_conflict_get(git_index_entry **ancestor_out,
+ git_index_entry **our_out,
+ git_index_entry **their_out,
git_index *index, const char *path)
{
- int pos;
+ size_t pos, posmax;
+ int stage;
+ git_index_entry *conflict_entry;
+ int error = GIT_ENOTFOUND;
+
+ assert(ancestor_out && our_out && their_out && index && path);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ if (git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ 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)
+ break;
+
+ stage = index_entry_stage(conflict_entry);
+
+ switch (stage) {
+ case 3:
+ *their_out = conflict_entry;
+ error = 0;
+ break;
+ case 2:
+ *our_out = conflict_entry;
+ error = 0;
+ break;
+ case 1:
+ *ancestor_out = conflict_entry;
+ error = 0;
+ break;
+ default:
+ break;
+ };
+ }
+
+ return error;
+}
+
+int git_index_conflict_remove(git_index *index, const char *path)
+{
+ size_t pos, posmax;
+ git_index_entry *conflict_entry;
+ int error = 0;
+
assert(index && path);
- if (!index->unmerged.length)
+ if (git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ posmax = git_index_entrycount(index);
+
+ while (pos < posmax) {
+ conflict_entry = git_vector_get(&index->entries, pos);
+
+ if (index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ if (index_entry_stage(conflict_entry) == 0) {
+ pos++;
+ continue;
+ }
+
+ if ((error = git_vector_remove(&index->entries, pos)) < 0)
+ return error;
+
+ index_entry_free(conflict_entry);
+ posmax--;
+ }
+
+ return 0;
+}
+
+static int index_conflicts_match(const git_vector *v, size_t idx)
+{
+ git_index_entry *entry = git_vector_get(v, idx);
+
+ if (index_entry_stage(entry) > 0) {
+ index_entry_free(entry);
+ return 1;
+ }
+
+ return 0;
+}
+
+void git_index_conflict_cleanup(git_index *index)
+{
+ assert(index);
+ git_vector_remove_matching(&index->entries, index_conflicts_match);
+}
+
+int git_index_has_conflicts(const git_index *index)
+{
+ size_t i;
+ git_index_entry *entry;
+
+ assert(index);
+
+ git_vector_foreach(&index->entries, i, entry) {
+ if (index_entry_stage(entry) > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+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)
+{
+ git_index_reuc_entry **existing = NULL;
+ size_t position;
+
+ assert(index && reuc && reuc->path != NULL);
+
+ if (!git_index_reuc_find(&position, index, reuc->path))
+ existing = (git_index_reuc_entry **)&index->reuc.contents[position];
+
+ if (!replace || !existing)
+ return git_vector_insert(&index->reuc, reuc);
+
+ /* exists, replace it */
+ git__free((*existing)->path);
+ git__free(*existing);
+ *existing = reuc;
+
+ return 0;
+}
+
+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)
+{
+ git_index_reuc_entry *reuc = NULL;
+ int error = 0;
+
+ assert(index && path);
+
+ if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 ||
+ (error = index_reuc_insert(index, reuc, 1)) < 0)
+ {
+ index_entry_reuc_free(reuc);
+ return error;
+ }
+
+ return error;
+}
+
+int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path)
+{
+ return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_bypath(
+ git_index *index, const char *path)
+{
+ size_t pos;
+ assert(index && path);
+
+ if (!index->reuc.length)
return NULL;
- if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0)
+ git_vector_sort(&index->reuc);
+
+ if (git_index_reuc_find(&pos, index, path) < 0)
return NULL;
- return git_vector_get(&index->unmerged, pos);
+ return git_vector_get(&index->reuc, pos);
}
-const git_index_entry_unmerged *git_index_get_unmerged_byindex(
- git_index *index, unsigned int n)
+const git_index_reuc_entry *git_index_reuc_get_byindex(
+ git_index *index, size_t n)
{
assert(index);
- return git_vector_get(&index->unmerged, n);
+
+ git_vector_sort(&index->reuc);
+ return git_vector_get(&index->reuc, n);
+}
+
+int git_index_reuc_remove(git_index *index, size_t position)
+{
+ int error;
+ git_index_reuc_entry *reuc;
+
+ git_vector_sort(&index->reuc);
+
+ reuc = git_vector_get(&index->reuc, position);
+ error = git_vector_remove(&index->reuc, position);
+
+ if (!error)
+ index_entry_reuc_free(reuc);
+
+ return error;
+}
+
+void git_index_reuc_clear(git_index *index)
+{
+ size_t i;
+ git_index_reuc_entry *reuc;
+
+ assert(index);
+
+ git_vector_foreach(&index->reuc, i, reuc) {
+ git__free(reuc->path);
+ git__free(reuc);
+ }
+
+ git_vector_clear(&index->reuc);
}
static int index_error_invalid(const char *message)
@@ -544,26 +1158,27 @@ static int index_error_invalid(const char *message)
return -1;
}
-static int read_unmerged(git_index *index, const char *buffer, size_t size)
+static int read_reuc(git_index *index, const char *buffer, size_t size)
{
const char *endptr;
size_t len;
int i;
- if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0)
+ /* This gets called multiple times, the vector might already be initialized */
+ if (index->reuc._alloc_size == 0 && git_vector_init(&index->reuc, 16, reuc_cmp) < 0)
return -1;
while (size) {
- git_index_entry_unmerged *lost;
+ git_index_reuc_entry *lost;
len = strlen(buffer) + 1;
if (size <= len)
- return index_error_invalid("reading unmerged entries");
+ return index_error_invalid("reading reuc entries");
- lost = git__malloc(sizeof(git_index_entry_unmerged));
+ lost = git__malloc(sizeof(git_index_reuc_entry));
GITERR_CHECK_ALLOC(lost);
- if (git_vector_insert(&index->unmerged, lost) < 0)
+ if (git_vector_insert(&index->reuc, lost) < 0)
return -1;
/* read NUL-terminated pathname for entry */
@@ -580,13 +1195,13 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||
!endptr || endptr == buffer || *endptr ||
(unsigned)tmp > UINT_MAX)
- return index_error_invalid("reading unmerged entry stage");
+ return index_error_invalid("reading reuc entry stage");
lost->mode[i] = tmp;
len = (endptr + 1) - buffer;
if (size <= len)
- return index_error_invalid("reading unmerged entry stage");
+ return index_error_invalid("reading reuc entry stage");
size -= len;
buffer += len;
@@ -597,7 +1212,7 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
if (!lost->mode[i])
continue;
if (size < 20)
- return index_error_invalid("reading unmerged entry oid");
+ return index_error_invalid("reading reuc entry oid");
git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
size -= 20;
@@ -605,6 +1220,9 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
}
}
+ /* entries are guaranteed to be sorted on-disk */
+ index->reuc.sorted = 1;
+
return 0;
}
@@ -710,7 +1328,7 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0)
return 0;
} else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {
- if (read_unmerged(index, buffer + 8, dest.extension_size) < 0)
+ if (read_reuc(index, buffer + 8, dest.extension_size) < 0)
return 0;
}
/* else, unsupported extension. We cannot parse this, but we can skip
@@ -799,15 +1417,16 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
#undef seek_forward
- /* force sorting in the vector: the entries are
- * assured to be sorted on the index */
- index->entries.sorted = 1;
+ /* Entries are stored case-sensitively on disk. */
+ index->entries.sorted = !index->ignore_case;
+ git_vector_sort(&index->entries);
+
return 0;
}
-static int is_index_extended(git_index *index)
+static bool is_index_extended(git_index *index)
{
- unsigned int i, extended;
+ size_t i, extended;
git_index_entry *entry;
extended = 0;
@@ -820,7 +1439,7 @@ static int is_index_extended(git_index *index)
}
}
- return extended;
+ return (extended > 0);
}
static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
@@ -885,25 +1504,98 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
static int write_entries(git_index *index, git_filebuf *file)
{
- unsigned int i;
+ int error = 0;
+ size_t i;
+ git_vector case_sorted;
+ git_index_entry *entry;
+ git_vector *out = &index->entries;
+
+ /* If index->entries is sorted case-insensitively, then we need
+ * to re-sort it case-sensitively before writing */
+ if (index->ignore_case) {
+ git_vector_dup(&case_sorted, &index->entries, index_cmp);
+ git_vector_sort(&case_sorted);
+ out = &case_sorted;
+ }
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *entry;
- entry = git_vector_get(&index->entries, i);
- if (write_disk_entry(file, entry) < 0)
- return -1;
+ git_vector_foreach(out, i, entry)
+ if ((error = write_disk_entry(file, entry)) < 0)
+ break;
+
+ if (index->ignore_case)
+ git_vector_free(&case_sorted);
+
+ return error;
+}
+
+static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data)
+{
+ struct index_extension ondisk;
+ int error = 0;
+
+ memset(&ondisk, 0x0, sizeof(struct index_extension));
+ memcpy(&ondisk, header, 4);
+ ondisk.extension_size = htonl(header->extension_size);
+
+ if ((error = git_filebuf_write(file, &ondisk, sizeof(struct index_extension))) == 0)
+ error = git_filebuf_write(file, data->ptr, data->size);
+
+ return error;
+}
+
+static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)
+{
+ int i;
+ int error = 0;
+
+ if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0)
+ return error;
+
+ for (i = 0; i < 3; i++) {
+ if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 ||
+ (error = git_buf_put(reuc_buf, "\0", 1)) < 0)
+ return error;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0)
+ return error;
}
return 0;
}
+static int write_reuc_extension(git_index *index, git_filebuf *file)
+{
+ git_buf reuc_buf = GIT_BUF_INIT;
+ git_vector *out = &index->reuc;
+ git_index_reuc_entry *reuc;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, reuc) {
+ if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4);
+ extension.extension_size = (uint32_t)reuc_buf.size;
+
+ error = write_extension(file, &extension, &reuc_buf);
+
+ git_buf_free(&reuc_buf);
+
+done:
+ return error;
+}
+
static int write_index(git_index *index, git_filebuf *file)
{
git_oid hash_final;
-
struct index_header header;
-
- int is_extended;
+ bool is_extended;
assert(index && file);
@@ -911,7 +1603,7 @@ static int write_index(git_index *index, git_filebuf *file)
header.signature = htonl(INDEX_HEADER_SIG);
header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER);
- header.entry_count = htonl(index->entries.length);
+ header.entry_count = htonl((uint32_t)index->entries.length);
if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0)
return -1;
@@ -919,7 +1611,11 @@ static int write_index(git_index *index, git_filebuf *file)
if (write_entries(index, file) < 0)
return -1;
- /* TODO: write extensions (tree cache) */
+ /* TODO: write tree cache extension */
+
+ /* write the reuc extension */
+ if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)
+ return -1;
/* get out the hash for all the contents we've appended to the file */
git_filebuf_hash(&hash_final, file);
@@ -930,12 +1626,17 @@ static int write_index(git_index *index, git_filebuf *file)
int git_index_entry_stage(const git_index_entry *entry)
{
- return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+ return index_entry_stage(entry);
}
-static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data)
+typedef struct read_tree_data {
+ git_index *index;
+ git_transfer_progress *stats;
+} read_tree_data;
+
+static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)
{
- git_index *index = data;
+ git_index *index = (git_index *)data;
git_index_entry *entry = NULL;
git_buf path = GIT_BUF_INIT;
@@ -950,10 +1651,16 @@ static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data)
entry->mode = tentry->attr;
entry->oid = tentry->oid;
+
+ if (path.size < GIT_IDXENTRY_NAMEMASK)
+ entry->flags = path.size & GIT_IDXENTRY_NAMEMASK;
+ else
+ entry->flags = GIT_IDXENTRY_NAMEMASK;
+
entry->path = git_buf_detach(&path);
git_buf_free(&path);
- if (index_insert(index, entry, 0) < 0) {
+ if (git_vector_insert(&index->entries, entry) < 0) {
index_entry_free(entry);
return -1;
}
@@ -961,9 +1668,14 @@ static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data)
return 0;
}
-int git_index_read_tree(git_index *index, git_tree *tree)
+int git_index_read_tree(git_index *index, const git_tree *tree)
{
git_index_clear(index);
- return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index);
+ return git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, index);
+}
+
+git_repository *git_index_owner(const git_index *index)
+{
+ return INDEX_OWNER(index);
}
diff --git a/src/index.h b/src/index.h
index 8515f4fcb..9498907b6 100644
--- a/src/index.h
+++ b/src/index.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -22,17 +22,32 @@ struct git_index {
char *index_file_path;
- time_t last_modified;
+ git_futils_filestamp stamp;
git_vector entries;
unsigned int on_disk:1;
+
+ unsigned int ignore_case:1;
+ unsigned int distrust_filemode:1;
+ unsigned int no_symlinks:1;
+
git_tree_cache *tree;
- git_vector unmerged;
+ git_vector reuc;
+
+ git_vector_cmp entries_cmp_path;
+ git_vector_cmp entries_search;
+ git_vector_cmp entries_search_path;
+ git_vector_cmp reuc_search;
};
-extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry);
+extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st);
+
+extern size_t git_index__prefix_position(git_index *index, const char *path);
+
+extern int git_index_entry__cmp(const void *a, const void *b);
+extern int git_index_entry__cmp_icase(const void *a, const void *b);
-extern unsigned int git_index__prefix_position(git_index *index, const char *path);
+extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
#endif
diff --git a/src/indexer.c b/src/indexer.c
index 6f735e651..2cfbd3a5a 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -17,7 +17,7 @@
#include "posix.h"
#include "pack.h"
#include "filebuf.h"
-#include "sha1.h"
+#include "oidmap.h"
#define UINT31_MAX (0x7FFFFFFF)
@@ -28,39 +28,32 @@ struct entry {
uint64_t offset_long;
};
-struct git_indexer {
- struct git_pack_file *pack;
- size_t nr_objects;
- git_vector objects;
- git_filebuf file;
- unsigned int fanout[256];
- git_oid hash;
-};
-
struct git_indexer_stream {
unsigned int parsed_header :1,
- opened_pack;
+ opened_pack :1,
+ have_stream :1,
+ have_delta :1;
struct git_pack_file *pack;
git_filebuf pack_file;
- git_filebuf index_file;
git_off_t off;
+ git_off_t entry_start;
+ git_packfile_stream stream;
size_t nr_objects;
git_vector objects;
git_vector deltas;
unsigned int fanout[256];
+ git_hash_ctx hash_ctx;
git_oid hash;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
+ char objbuf[8*1024];
};
struct delta_info {
git_off_t delta_off;
};
-const git_oid *git_indexer_hash(git_indexer *idx)
-{
- return &idx->hash;
-}
-
-const git_oid *git_indexer_stream_hash(git_indexer_stream *idx)
+const git_oid *git_indexer_stream_hash(const git_indexer_stream *idx)
{
return &idx->hash;
}
@@ -130,23 +123,21 @@ static int objects_cmp(const void *a, const void *b)
return git_oid_cmp(&entrya->oid, &entryb->oid);
}
-static int cache_cmp(const void *a, const void *b)
-{
- const struct git_pack_entry *ea = a;
- const struct git_pack_entry *eb = b;
-
- return git_oid_cmp(&ea->sha1, &eb->sha1);
-}
-
-int git_indexer_stream_new(git_indexer_stream **out, const char *prefix)
+int git_indexer_stream_new(
+ git_indexer_stream **out,
+ const char *prefix,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
git_indexer_stream *idx;
git_buf path = GIT_BUF_INIT;
- static const char suff[] = "/objects/pack/pack-received";
+ static const char suff[] = "/pack";
int error;
idx = git__calloc(1, sizeof(git_indexer_stream));
GITERR_CHECK_ALLOC(idx);
+ idx->progress_cb = progress_cb;
+ idx->progress_payload = progress_payload;
error = git_buf_joinpath(&path, prefix, suff);
if (error < 0)
@@ -171,69 +162,171 @@ cleanup:
/* Try to store the delta so we can try to resolve it later */
static int store_delta(git_indexer_stream *idx)
{
- git_otype type;
- git_mwindow *w = NULL;
- git_mwindow_file *mwf = &idx->pack->mwf;
- git_off_t entry_start = idx->off;
struct delta_info *delta;
- size_t entry_size;
- git_rawobj obj;
- int error;
- /*
- * ref-delta objects can refer to object that we haven't
- * found yet, so give it another opportunity
- */
- if (git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off) < 0)
+ delta = git__calloc(1, sizeof(struct delta_info));
+ GITERR_CHECK_ALLOC(delta);
+ delta->delta_off = idx->entry_start;
+
+ if (git_vector_insert(&idx->deltas, delta) < 0)
return -1;
- git_mwindow_close(&w);
+ return 0;
+}
- /* If it's not a delta, mark it as failure, we can't do anything with it */
- if (type != GIT_OBJ_REF_DELTA && type != GIT_OBJ_OFS_DELTA)
- return -1;
+static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type)
+{
+ char buffer[64];
+ size_t hdrlen;
+
+ hdrlen = git_odb__format_object_header(buffer, sizeof(buffer), (size_t)len, type);
+ git_hash_update(ctx, buffer, hdrlen);
+}
+
+static int hash_object_stream(git_indexer_stream *idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(idx && stream);
+
+ do {
+ if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0)
+ break;
+
+ git_hash_update(&idx->hash_ctx, idx->objbuf, read);
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+/* In order to create the packfile stream, we need to skip over the delta base description */
+static int advance_delta_offset(git_indexer_stream *idx, git_otype type)
+{
+ git_mwindow *w = NULL;
+
+ assert(type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA);
if (type == GIT_OBJ_REF_DELTA) {
idx->off += GIT_OID_RAWSZ;
} else {
- git_off_t base_off;
-
- base_off = get_delta_base(idx->pack, &w, &idx->off, type, entry_start);
+ git_off_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start);
git_mwindow_close(&w);
if (base_off < 0)
return (int)base_off;
}
- error = packfile_unpack_compressed(&obj, idx->pack, &w, &idx->off, entry_size, type);
- if (error == GIT_EBUFS) {
- idx->off = entry_start;
- return GIT_EBUFS;
- } else if (error < 0){
- return -1;
+ return 0;
+}
+
+/* Read from the stream and discard any output */
+static int read_object_stream(git_indexer_stream *idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(stream);
+
+ do {
+ read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf));
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, git_off_t size)
+{
+ void *ptr;
+ uint32_t crc;
+ unsigned int left, len;
+ git_mwindow *w = NULL;
+
+ crc = crc32(0L, Z_NULL, 0);
+ while (size) {
+ ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left);
+ if (ptr == NULL)
+ return -1;
+
+ len = min(left, (unsigned int)size);
+ crc = crc32(crc, ptr, len);
+ size -= len;
+ start += len;
+ git_mwindow_close(&w);
}
- delta = git__calloc(1, sizeof(struct delta_info));
- GITERR_CHECK_ALLOC(delta);
- delta->delta_off = entry_start;
+ *crc_out = htonl(crc);
+ return 0;
+}
+
+static int store_object(git_indexer_stream *idx)
+{
+ int i, error;
+ khiter_t k;
+ git_oid oid;
+ struct entry *entry;
+ git_off_t entry_size;
+ struct git_pack_entry *pentry;
+ git_hash_ctx *ctx = &idx->hash_ctx;
+ git_off_t entry_start = idx->entry_start;
- git__free(obj.data);
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
- if (git_vector_insert(&idx->deltas, delta) < 0)
- return -1;
+ pentry = git__malloc(sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_hash_final(&oid, ctx);
+ entry_size = idx->off - entry_start;
+ if (entry_start > UINT31_MAX) {
+ entry->offset = UINT32_MAX;
+ entry->offset_long = entry_start;
+ } else {
+ entry->offset = (uint32_t)entry_start;
+ }
+
+ git_oid_cpy(&pentry->sha1, &oid);
+ pentry->offset = entry_start;
+
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+ if (!error) {
+ git__free(pentry);
+ goto on_error;
+ }
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
+
+ git_oid_cpy(&entry->oid, &oid);
+
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
+ goto on_error;
+
+ /* Add the object to the list */
+ if (git_vector_insert(&idx->objects, entry) < 0)
+ goto on_error;
+
+ for (i = oid.id[0]; i < 256; ++i) {
+ idx->fanout[i]++;
+ }
return 0;
+
+on_error:
+ git__free(entry);
+
+ return -1;
}
static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start)
{
- int i;
+ int i, error;
+ khiter_t k;
git_oid oid;
- void *packed;
size_t entry_size;
- unsigned int left;
struct entry *entry;
- git_mwindow *w = NULL;
- git_mwindow_file *mwf = &idx->pack->mwf;
struct git_pack_entry *pentry;
entry = git__calloc(1, sizeof(*entry));
@@ -257,20 +350,21 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
git_oid_cpy(&pentry->sha1, &oid);
pentry->offset = entry_start;
- if (git_vector_insert(&idx->pack->cache, pentry) < 0)
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+ if (!error) {
+ git__free(pentry);
goto on_error;
+ }
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
git_oid_cpy(&entry->oid, &oid);
entry->crc = crc32(0L, Z_NULL, 0);
entry_size = (size_t)(idx->off - entry_start);
- packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left);
- if (packed == NULL)
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
goto on_error;
- entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size));
- git_mwindow_close(&w);
-
/* Add the object to the list */
if (git_vector_insert(&idx->objects, entry) < 0)
goto on_error;
@@ -283,20 +377,27 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
on_error:
git__free(entry);
- git__free(pentry);
git__free(obj->data);
return -1;
}
-int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats)
+static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats)
{
- int error;
+ if (!idx->progress_cb) return 0;
+ return idx->progress_cb(stats, idx->progress_payload);
+}
+
+int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats)
+{
+ int error = -1;
struct git_pack_header hdr;
- size_t processed = stats->processed;
+ size_t processed;
git_mwindow_file *mwf = &idx->pack->mwf;
assert(idx && data && stats);
+ processed = stats->indexed_objects;
+
if (git_filebuf_write(&idx->pack_file, data, size) < 0)
return -1;
@@ -311,11 +412,11 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
mwf = &idx->pack->mwf;
if (git_mwindow_file_register(&idx->pack->mwf) < 0)
return -1;
-
- return 0;
}
if (!idx->parsed_header) {
+ unsigned int total_objects;
+
if ((unsigned)idx->pack->mwf.size < sizeof(hdr))
return 0;
@@ -328,19 +429,25 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
/* for now, limit to 2^32 objects */
assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));
+ if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects))
+ total_objects = (unsigned int)idx->nr_objects;
+ else
+ total_objects = UINT_MAX;
- if (git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp) < 0)
- return -1;
+ idx->pack->idx_cache = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(idx->pack->idx_cache);
idx->pack->has_cache = 1;
- if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0)
+ if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0)
return -1;
- if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0)
+ if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0)
return -1;
- stats->total = (unsigned int)idx->nr_objects;
- stats->processed = 0;
+ stats->received_objects = 0;
+ processed = stats->indexed_objects = 0;
+ stats->total_objects = total_objects;
+ do_progress_callback(idx, stats);
}
/* Now that we have data in the pack, let's try to parse it */
@@ -348,42 +455,91 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
/* As the file grows any windows we try to use will be out of date */
git_mwindow_free_all(mwf);
while (processed < idx->nr_objects) {
- git_rawobj obj;
+ git_packfile_stream *stream = &idx->stream;
git_off_t entry_start = idx->off;
+ size_t entry_size;
+ git_otype type;
+ git_mwindow *w = NULL;
if (idx->pack->mwf.size <= idx->off + 20)
return 0;
- error = git_packfile_unpack(&obj, idx->pack, &idx->off);
- if (error == GIT_EBUFS) {
- idx->off = entry_start;
- return 0;
- }
-
- if (error < 0) {
- idx->off = entry_start;
- error = store_delta(idx);
- if (error == GIT_EBUFS)
+ if (!idx->have_stream) {
+ error = git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
return 0;
+ }
if (error < 0)
- return error;
+ return -1;
+
+ git_mwindow_close(&w);
+ idx->entry_start = entry_start;
+ git_hash_ctx_init(&idx->hash_ctx);
+
+ if (type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA) {
+ error = advance_delta_offset(idx, type);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
+ return 0;
+ }
+ if (error < 0)
+ return -1;
+
+ idx->have_delta = 1;
+ } else {
+ idx->have_delta = 0;
+ hash_header(&idx->hash_ctx, entry_size, type);
+ }
+
+ idx->have_stream = 1;
+ if (git_packfile_stream_open(stream, idx->pack, idx->off) < 0)
+ goto on_error;
- continue;
}
- if (hash_and_save(idx, &obj, entry_start) < 0)
+ if (idx->have_delta) {
+ error = read_object_stream(idx, stream);
+ } else {
+ error = hash_object_stream(idx, stream);
+ }
+
+ idx->off = stream->curpos;
+ if (error == GIT_EBUFS)
+ return 0;
+
+ /* We want to free the stream reasorces no matter what here */
+ idx->have_stream = 0;
+ git_packfile_stream_free(stream);
+
+ if (error < 0)
+ goto on_error;
+
+ if (idx->have_delta) {
+ error = store_delta(idx);
+ } else {
+ error = store_object(idx);
+ }
+
+ if (error < 0)
goto on_error;
- git__free(obj.data);
+ if (!idx->have_delta) {
+ stats->indexed_objects = (unsigned int)++processed;
+ }
+ stats->received_objects++;
- stats->processed = (unsigned int)++processed;
+ if (do_progress_callback(idx, stats) != 0) {
+ error = GIT_EUSER;
+ goto on_error;
+ }
}
return 0;
on_error:
git_mwindow_free_all(mwf);
- return -1;
+ return error;
}
static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix)
@@ -408,7 +564,7 @@ static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char
return git_buf_oom(path) ? -1 : 0;
}
-static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats)
+static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats)
{
unsigned int i;
struct delta_info *delta;
@@ -424,13 +580,14 @@ static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats)
return -1;
git__free(obj.data);
- stats->processed++;
+ stats->indexed_objects++;
+ do_progress_callback(idx, stats);
}
return 0;
}
-int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats)
+int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats)
{
git_mwindow *w = NULL;
unsigned int i, long_offsets = 0, left;
@@ -439,11 +596,15 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
struct entry *entry;
void *packfile_hash;
git_oid file_hash;
- SHA_CTX ctx;
+ git_hash_ctx ctx;
+ git_filebuf index_file = {0};
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
/* Test for this before resolve_deltas(), as it plays with idx->off */
if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) {
- giterr_set(GITERR_INDEXER, "Indexing error: junk at the end of the pack");
+ giterr_set(GITERR_INDEXER, "Indexing error: unexpected data at the end of the pack");
return -1;
}
@@ -451,7 +612,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
if (resolve_deltas(idx, stats) < 0)
return -1;
- if (stats->processed != stats->total) {
+ if (stats->indexed_objects != stats->total_objects) {
giterr_set(GITERR_INDEXER, "Indexing error: early EOF");
return -1;
}
@@ -464,31 +625,30 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
if (git_buf_oom(&filename))
return -1;
- if (git_filebuf_open(&idx->index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0)
+ if (git_filebuf_open(&index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0)
goto on_error;
/* Write out the header */
hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
hdr.idx_version = htonl(2);
- git_filebuf_write(&idx->index_file, &hdr, sizeof(hdr));
+ git_filebuf_write(&index_file, &hdr, sizeof(hdr));
/* Write out the fanout table */
for (i = 0; i < 256; ++i) {
uint32_t n = htonl(idx->fanout[i]);
- git_filebuf_write(&idx->index_file, &n, sizeof(n));
+ git_filebuf_write(&index_file, &n, sizeof(n));
}
/* Write out the object names (SHA-1 hashes) */
- SHA1_Init(&ctx);
git_vector_foreach(&idx->objects, i, entry) {
- git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid));
- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);
+ git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid));
+ git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ);
}
- SHA1_Final(idx->hash.id, &ctx);
+ git_hash_final(&idx->hash, &ctx);
/* Write out the CRC32 values */
git_vector_foreach(&idx->objects, i, entry) {
- git_filebuf_write(&idx->index_file, &entry->crc, sizeof(uint32_t));
+ git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t));
}
/* Write out the offsets */
@@ -500,7 +660,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
else
n = htonl(entry->offset);
- git_filebuf_write(&idx->index_file, &n, sizeof(uint32_t));
+ git_filebuf_write(&index_file, &n, sizeof(uint32_t));
}
/* Write out the long offsets */
@@ -513,7 +673,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
split[0] = htonl(entry->offset_long >> 32);
split[1] = htonl(entry->offset_long & 0xffffffff);
- git_filebuf_write(&idx->index_file, &split, sizeof(uint32_t) * 2);
+ git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2);
}
/* Write out the packfile trailer */
@@ -526,24 +686,26 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ);
git_mwindow_close(&w);
- git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid));
+ git_filebuf_write(&index_file, &file_hash, sizeof(git_oid));
/* Write out the packfile trailer to the idx file as well */
- if (git_filebuf_hash(&file_hash, &idx->index_file) < 0)
+ if (git_filebuf_hash(&file_hash, &index_file) < 0)
goto on_error;
- git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid));
+ git_filebuf_write(&index_file, &file_hash, sizeof(git_oid));
/* Figure out what the final name should be */
if (index_path_stream(&filename, idx, ".idx") < 0)
goto on_error;
/* Commit file */
- if (git_filebuf_commit_at(&idx->index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0)
+ if (git_filebuf_commit_at(&index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0)
goto on_error;
git_mwindow_free_all(&idx->pack->mwf);
+ /* We need to close the descriptor here so Windows doesn't choke on commit_at */
p_close(idx->pack->mwf.fd);
+ idx->pack->mwf.fd = -1;
if (index_path_stream(&filename, idx, ".pack") < 0)
goto on_error;
@@ -556,17 +718,17 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
on_error:
git_mwindow_free_all(&idx->pack->mwf);
- p_close(idx->pack->mwf.fd);
- git_filebuf_cleanup(&idx->index_file);
+ git_filebuf_cleanup(&index_file);
git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
return -1;
}
void git_indexer_stream_free(git_indexer_stream *idx)
{
+ khiter_t k;
unsigned int i;
struct entry *e;
- struct git_pack_entry *pe;
struct delta_info *delta;
if (idx == NULL)
@@ -575,325 +737,20 @@ void git_indexer_stream_free(git_indexer_stream *idx)
git_vector_foreach(&idx->objects, i, e)
git__free(e);
git_vector_free(&idx->objects);
- git_vector_foreach(&idx->pack->cache, i, pe)
- git__free(pe);
- git_vector_free(&idx->pack->cache);
- git_vector_foreach(&idx->deltas, i, delta)
- git__free(delta);
- git_vector_free(&idx->deltas);
- git__free(idx->pack);
- git__free(idx);
-}
-
-int git_indexer_new(git_indexer **out, const char *packname)
-{
- git_indexer *idx;
- struct git_pack_header hdr;
- int error;
-
- assert(out && packname);
-
- if (git_path_root(packname) < 0) {
- giterr_set(GITERR_INDEXER, "Path is not absolute");
- return -1;
- }
-
- idx = git__calloc(1, sizeof(git_indexer));
- GITERR_CHECK_ALLOC(idx);
-
- open_pack(&idx->pack, packname);
-
- if ((error = parse_header(&hdr, idx->pack)) < 0)
- goto cleanup;
-
- idx->nr_objects = ntohl(hdr.hdr_entries);
-
- /* for now, limit to 2^32 objects */
- assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));
-
- error = git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp);
- if (error < 0)
- goto cleanup;
-
- idx->pack->has_cache = 1;
- error = git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp);
- if (error < 0)
- goto cleanup;
-
- *out = idx;
-
- return 0;
-cleanup:
- git_indexer_free(idx);
-
- return -1;
-}
-
-static int index_path(git_buf *path, git_indexer *idx)
-{
- const char prefix[] = "pack-", suffix[] = ".idx";
- size_t slash = (size_t)path->size;
-
- /* search backwards for '/' */
- while (slash > 0 && path->ptr[slash - 1] != '/')
- slash--;
-
- if (git_buf_grow(path, slash + 1 + strlen(prefix) +
- GIT_OID_HEXSZ + strlen(suffix) + 1) < 0)
- return -1;
-
- git_buf_truncate(path, slash);
- git_buf_puts(path, prefix);
- git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash);
- path->size += GIT_OID_HEXSZ;
- git_buf_puts(path, suffix);
-
- return git_buf_oom(path) ? -1 : 0;
-}
-
-int git_indexer_write(git_indexer *idx)
-{
- git_mwindow *w = NULL;
- int error;
- unsigned int i, long_offsets = 0, left;
- struct git_pack_idx_header hdr;
- git_buf filename = GIT_BUF_INIT;
- struct entry *entry;
- void *packfile_hash;
- git_oid file_hash;
- SHA_CTX ctx;
-
- git_vector_sort(&idx->objects);
-
- git_buf_sets(&filename, idx->pack->pack_name);
- git_buf_truncate(&filename, filename.size - strlen("pack"));
- git_buf_puts(&filename, "idx");
- if (git_buf_oom(&filename))
- return -1;
-
- error = git_filebuf_open(&idx->file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS);
- if (error < 0)
- goto cleanup;
-
- /* Write out the header */
- hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
- hdr.idx_version = htonl(2);
- error = git_filebuf_write(&idx->file, &hdr, sizeof(hdr));
- if (error < 0)
- goto cleanup;
-
- /* Write out the fanout table */
- for (i = 0; i < 256; ++i) {
- uint32_t n = htonl(idx->fanout[i]);
- error = git_filebuf_write(&idx->file, &n, sizeof(n));
- if (error < 0)
- goto cleanup;
- }
-
- /* Write out the object names (SHA-1 hashes) */
- SHA1_Init(&ctx);
- git_vector_foreach(&idx->objects, i, entry) {
- error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid));
- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);
- if (error < 0)
- goto cleanup;
- }
- SHA1_Final(idx->hash.id, &ctx);
-
- /* Write out the CRC32 values */
- git_vector_foreach(&idx->objects, i, entry) {
- error = git_filebuf_write(&idx->file, &entry->crc, sizeof(uint32_t));
- if (error < 0)
- goto cleanup;
- }
-
- /* Write out the offsets */
- git_vector_foreach(&idx->objects, i, entry) {
- uint32_t n;
-
- if (entry->offset == UINT32_MAX)
- n = htonl(0x80000000 | long_offsets++);
- else
- n = htonl(entry->offset);
-
- error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t));
- if (error < 0)
- goto cleanup;
- }
-
- /* Write out the long offsets */
- git_vector_foreach(&idx->objects, i, entry) {
- uint32_t split[2];
-
- if (entry->offset != UINT32_MAX)
- continue;
-
- split[0] = htonl(entry->offset_long >> 32);
- split[1] = htonl(entry->offset_long & 0xffffffff);
-
- error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2);
- if (error < 0)
- goto cleanup;
- }
-
- /* Write out the packfile trailer */
-
- packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
- git_mwindow_close(&w);
- if (packfile_hash == NULL) {
- error = -1;
- goto cleanup;
- }
-
- memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ);
-
- git_mwindow_close(&w);
-
- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));
- if (error < 0)
- goto cleanup;
-
- /* Write out the index sha */
- error = git_filebuf_hash(&file_hash, &idx->file);
- if (error < 0)
- goto cleanup;
-
- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));
- if (error < 0)
- goto cleanup;
-
- /* Figure out what the final name should be */
- error = index_path(&filename, idx);
- if (error < 0)
- goto cleanup;
-
- /* Commit file */
- error = git_filebuf_commit_at(&idx->file, filename.ptr, GIT_PACK_FILE_MODE);
-
-cleanup:
- git_mwindow_free_all(&idx->pack->mwf);
- if (error < 0)
- git_filebuf_cleanup(&idx->file);
- git_buf_free(&filename);
-
- return error;
-}
-
-int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
-{
- git_mwindow_file *mwf;
- git_off_t off = sizeof(struct git_pack_header);
- int error;
- struct entry *entry;
- unsigned int left, processed;
-
- assert(idx && stats);
-
- mwf = &idx->pack->mwf;
- error = git_mwindow_file_register(mwf);
- if (error < 0)
- return error;
-
- stats->total = (unsigned int)idx->nr_objects;
- stats->processed = processed = 0;
-
- while (processed < idx->nr_objects) {
- git_rawobj obj;
- git_oid oid;
- struct git_pack_entry *pentry;
- git_mwindow *w = NULL;
- int i;
- git_off_t entry_start = off;
- void *packed;
- size_t entry_size;
- char fmt[GIT_OID_HEXSZ] = {0};
-
- entry = git__calloc(1, sizeof(*entry));
- GITERR_CHECK_ALLOC(entry);
-
- if (off > UINT31_MAX) {
- entry->offset = UINT32_MAX;
- entry->offset_long = off;
- } else {
- entry->offset = (uint32_t)off;
- }
-
- error = git_packfile_unpack(&obj, idx->pack, &off);
- if (error < 0)
- goto cleanup;
-
- /* FIXME: Parse the object instead of hashing it */
- error = git_odb__hashobj(&oid, &obj);
- if (error < 0) {
- giterr_set(GITERR_INDEXER, "Failed to hash object");
- goto cleanup;
+ if (idx->pack) {
+ for (k = kh_begin(idx->pack->idx_cache); k != kh_end(idx->pack->idx_cache); k++) {
+ if (kh_exist(idx->pack->idx_cache, k))
+ git__free(kh_value(idx->pack->idx_cache, k));
}
- pentry = git__malloc(sizeof(struct git_pack_entry));
- if (pentry == NULL) {
- error = -1;
- goto cleanup;
- }
-
- git_oid_cpy(&pentry->sha1, &oid);
- pentry->offset = entry_start;
- git_oid_fmt(fmt, &oid);
- printf("adding %s to cache\n", fmt);
- error = git_vector_insert(&idx->pack->cache, pentry);
- if (error < 0)
- goto cleanup;
-
- git_oid_cpy(&entry->oid, &oid);
- entry->crc = crc32(0L, Z_NULL, 0);
-
- entry_size = (size_t)(off - entry_start);
- packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left);
- if (packed == NULL) {
- error = -1;
- goto cleanup;
- }
- entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size));
- git_mwindow_close(&w);
-
- /* Add the object to the list */
- error = git_vector_insert(&idx->objects, entry);
- if (error < 0)
- goto cleanup;
-
- for (i = oid.id[0]; i < 256; ++i) {
- idx->fanout[i]++;
- }
-
- git__free(obj.data);
-
- stats->processed = ++processed;
+ git_oidmap_free(idx->pack->idx_cache);
}
-cleanup:
- git_mwindow_free_all(mwf);
-
- return error;
-
-}
-
-void git_indexer_free(git_indexer *idx)
-{
- unsigned int i;
- struct entry *e;
- struct git_pack_entry *pe;
-
- if (idx == NULL)
- return;
-
- p_close(idx->pack->mwf.fd);
- git_vector_foreach(&idx->objects, i, e)
- git__free(e);
- git_vector_free(&idx->objects);
- git_vector_foreach(&idx->pack->cache, i, pe)
- git__free(pe);
- git_vector_free(&idx->pack->cache);
- git__free(idx->pack);
+ git_vector_foreach(&idx->deltas, i, delta)
+ git__free(delta);
+ git_vector_free(&idx->deltas);
+ git_packfile_free(idx->pack);
+ git_filebuf_cleanup(&idx->pack_file);
git__free(idx);
}
-
diff --git a/src/iterator.c b/src/iterator.c
index 819b0e22a..5b5ed9525 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,295 +10,565 @@
#include "ignore.h"
#include "buffer.h"
#include "git2/submodule.h"
+#include <ctype.h>
+
+#define ITERATOR_SET_CB(P,NAME_LC) do { \
+ (P)->cb.current = NAME_LC ## _iterator__current; \
+ (P)->cb.advance = NAME_LC ## _iterator__advance; \
+ (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \
+ (P)->cb.seek = NAME_LC ## _iterator__seek; \
+ (P)->cb.reset = NAME_LC ## _iterator__reset; \
+ (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
+ (P)->cb.free = NAME_LC ## _iterator__free; \
+ } while (0)
+
+#define ITERATOR_CASE_FLAGS \
+ (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)
-#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \
+#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_ ## NAME_UC; \
+ (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
+ (P)->base.cb = &(P)->cb; \
+ ITERATOR_SET_CB(P,NAME_LC); \
+ (P)->base.repo = (REPO); \
(P)->base.start = start ? git__strdup(start) : NULL; \
(P)->base.end = end ? git__strdup(end) : NULL; \
- (P)->base.current = NAME_LC ## _iterator__current; \
- (P)->base.at_end = NAME_LC ## _iterator__at_end; \
- (P)->base.advance = NAME_LC ## _iterator__advance; \
- (P)->base.seek = NAME_LC ## _iterator__seek; \
- (P)->base.reset = NAME_LC ## _iterator__reset; \
- (P)->base.free = NAME_LC ## _iterator__free; \
- if ((start && !(P)->base.start) || (end && !(P)->base.end)) \
- return -1; \
+ if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
+ git__free(P); return -1; } \
+ (P)->base.prefixcomp = git__prefixcmp; \
+ (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \
+ if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
+ (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
} while (0)
+#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+
+#define iterator__end(I) ((git_iterator *)(I))->end
+#define iterator__past_end(I,PATH) \
+ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
+
-static int empty_iterator__no_item(
- git_iterator *iter, const git_index_entry **entry)
+static int iterator__reset_range(
+ git_iterator *iter, const char *start, const char *end)
{
- GIT_UNUSED(iter);
- *entry = NULL;
+ if (start) {
+ if (iter->start)
+ git__free(iter->start);
+ iter->start = git__strdup(start);
+ GITERR_CHECK_ALLOC(iter->start);
+ }
+
+ if (end) {
+ if (iter->end)
+ git__free(iter->end);
+ iter->end = git__strdup(end);
+ GITERR_CHECK_ALLOC(iter->end);
+ }
+
return 0;
}
-static int empty_iterator__at_end(git_iterator *iter)
+static int iterator__update_ignore_case(
+ git_iterator *iter,
+ git_iterator_flag_t flags)
{
- GIT_UNUSED(iter);
- return 1;
+ int error = 0, ignore_case = -1;
+
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ ignore_case = true;
+ else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)
+ ignore_case = false;
+ else {
+ git_index *index;
+
+ if (!(error = git_repository_index__weakptr(&index, iter->repo)))
+ ignore_case = (index->ignore_case != false);
+ }
+
+ if (ignore_case > 0)
+ iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
+ else if (ignore_case == 0)
+ iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
+
+ iter->prefixcomp = iterator__ignore_case(iter) ?
+ git__prefixcmp_icase : git__prefixcmp;
+
+ return error;
+}
+
+GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
+{
+ if (entry) *entry = NULL;
}
-static int empty_iterator__noop(git_iterator *iter)
+
+static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
{
- GIT_UNUSED(iter);
+ GIT_UNUSED(i);
+ iterator__clear_entry(e);
return 0;
}
-static int empty_iterator__seek(git_iterator *iter, const char *prefix)
+static int empty_iterator__seek(git_iterator *i, const char *p)
{
- GIT_UNUSED(iter);
- GIT_UNUSED(prefix);
+ GIT_UNUSED(i); GIT_UNUSED(p);
return -1;
}
-static void empty_iterator__free(git_iterator *iter)
+static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)
{
- GIT_UNUSED(iter);
+ GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);
+ return 0;
}
-int git_iterator_for_nothing(git_iterator **iter)
+static int empty_iterator__at_end(git_iterator *i)
{
- git_iterator *i = git__calloc(1, sizeof(git_iterator));
- GITERR_CHECK_ALLOC(i);
+ GIT_UNUSED(i);
+ return 1;
+}
- i->type = GIT_ITERATOR_EMPTY;
- i->current = empty_iterator__no_item;
- i->at_end = empty_iterator__at_end;
- i->advance = empty_iterator__no_item;
- i->seek = empty_iterator__seek;
- i->reset = empty_iterator__noop;
- i->free = empty_iterator__free;
+static void empty_iterator__free(git_iterator *i)
+{
+ GIT_UNUSED(i);
+}
+
+typedef struct {
+ git_iterator base;
+ git_iterator_callbacks cb;
+} empty_iterator;
+
+int git_iterator_for_nothing(
+ git_iterator **iter,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ empty_iterator *i;
+
+#define empty_iterator__current empty_iterator__noop
+#define empty_iterator__advance empty_iterator__noop
+#define empty_iterator__advance_into empty_iterator__noop
+
+ ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
- *iter = i;
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+ *iter = (git_iterator *)i;
return 0;
}
+typedef struct tree_iterator_entry tree_iterator_entry;
+struct tree_iterator_entry {
+ tree_iterator_entry *parent;
+ const git_tree_entry *te;
+ git_tree *tree;
+};
+
typedef struct tree_iterator_frame tree_iterator_frame;
struct tree_iterator_frame {
- tree_iterator_frame *next;
- git_tree *tree;
- char *start;
- unsigned int index;
+ tree_iterator_frame *up, *down;
+
+ size_t n_entries; /* items in this frame */
+ size_t current; /* start of currently active range in frame */
+ size_t next; /* start of next range in frame */
+
+ const char *start;
+ size_t startlen;
+
+ tree_iterator_entry *entries[GIT_FLEX_ARRAY];
};
typedef struct {
git_iterator base;
- git_repository *repo;
- tree_iterator_frame *stack;
+ git_iterator_callbacks cb;
+ tree_iterator_frame *head, *root;
+ git_pool pool;
git_index_entry entry;
git_buf path;
+ int path_ambiguities;
bool path_has_filename;
+ int (*strncomp)(const char *a, const char *b, size_t sz);
} tree_iterator;
-static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti)
-{
- return (ti->stack == NULL) ? NULL :
- git_tree_entry_byindex(ti->stack->tree, ti->stack->index);
-}
-
static char *tree_iterator__current_filename(
tree_iterator *ti, const git_tree_entry *te)
{
if (!ti->path_has_filename) {
if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
return NULL;
+
+ if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)
+ return NULL;
+
ti->path_has_filename = true;
}
return ti->path.ptr;
}
-static void tree_iterator__pop_frame(tree_iterator *ti)
+static void tree_iterator__rewrite_filename(tree_iterator *ti)
{
- tree_iterator_frame *tf = ti->stack;
- ti->stack = tf->next;
- if (ti->stack != NULL) /* don't free the initial tree */
- git_tree_free(tf->tree);
- git__free(tf);
+ tree_iterator_entry *scan = ti->head->entries[ti->head->current];
+ ssize_t strpos = ti->path.size;
+ const git_tree_entry *te;
+
+ if (strpos && ti->path.ptr[strpos - 1] == '/')
+ strpos--;
+
+ for (; scan && (te = scan->te); scan = scan->parent) {
+ strpos -= te->filename_len;
+ memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);
+ strpos -= 1; /* separator */
+ }
+}
+
+static int tree_iterator__te_cmp(
+ const git_tree_entry *a,
+ const git_tree_entry *b,
+ int (*compare)(const char *, const char *, size_t))
+{
+ return git_path_cmp(
+ a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
+ b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
+ compare);
+}
+
+static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)
+{
+ const tree_iterator_entry *ae = a, *be = b;
+ int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);
+
+ if (!cmp) {
+ /* stabilize sort order among equivalent names */
+ if (!ae->parent->te || !be->parent->te)
+ cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);
+ else
+ cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);
+ }
+
+ return cmp;
}
-static int tree_iterator__to_end(tree_iterator *ti)
+static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
{
- while (ti->stack && ti->stack->next)
- tree_iterator__pop_frame(ti);
+ const tree_iterator_frame *tf = key;
+ const git_tree_entry *te = ((tree_iterator_entry *)val)->te;
+
+ return git_path_cmp(
+ tf->start, tf->startlen, false,
+ te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
+ ((tree_iterator *)p)->strncomp);
+}
+
+static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
+{
+ int error;
+ const git_tree_entry *te, *last = NULL;
+
+ tf->next = tf->current;
+
+ for (; tf->next < tf->n_entries; tf->next++, last = te) {
+ te = tf->entries[tf->next]->te;
+
+ if (last && tree_iterator__te_cmp(last, te, ti->strncomp))
+ break;
+
+ /* load trees for items in [current,next) range */
+ if (git_tree_entry__is_tree(te) &&
+ (error = git_tree_lookup(
+ &tf->entries[tf->next]->tree, ti->base.repo, &te->oid)) < 0)
+ return error;
+ }
- if (ti->stack)
- ti->stack->index = git_tree_entrycount(ti->stack->tree);
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities++;
+
+ if (last && !tree_iterator__current_filename(ti, last))
+ return -1;
return 0;
}
-static int tree_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)
{
- tree_iterator *ti = (tree_iterator *)self;
- const git_tree_entry *te = tree_iterator__tree_entry(ti);
+ return (ti->head->current < ti->head->n_entries &&
+ ti->head->entries[ti->head->current]->tree != NULL);
+}
- if (entry)
- *entry = NULL;
+static int tree_iterator__push_frame(tree_iterator *ti)
+{
+ int error = 0;
+ tree_iterator_frame *head = ti->head, *tf = NULL;
+ size_t i, n_entries = 0;
- if (te == NULL)
+ if (head->current >= head->n_entries || !head->entries[head->current]->tree)
return 0;
- ti->entry.mode = te->attr;
- git_oid_cpy(&ti->entry.oid, &te->oid);
+ for (i = head->current; i < head->next; ++i)
+ n_entries += git_tree_entrycount(head->entries[i]->tree);
- ti->entry.path = tree_iterator__current_filename(ti, te);
- if (ti->entry.path == NULL)
- return -1;
+ tf = git__calloc(sizeof(tree_iterator_frame) +
+ n_entries * sizeof(tree_iterator_entry *), 1);
+ GITERR_CHECK_ALLOC(tf);
- if (ti->base.end && git__prefixcmp(ti->entry.path, ti->base.end) > 0)
- return tree_iterator__to_end(ti);
+ tf->n_entries = n_entries;
- if (entry)
- *entry = &ti->entry;
+ tf->up = head;
+ head->down = tf;
+ ti->head = tf;
+
+ for (i = head->current, n_entries = 0; i < head->next; ++i) {
+ git_tree *tree = head->entries[i]->tree;
+ size_t j, max_j = git_tree_entrycount(tree);
+
+ for (j = 0; j < max_j; ++j) {
+ tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->parent = head->entries[i];
+ entry->te = git_tree_entry_byindex(tree, j);
+ entry->tree = NULL;
+
+ tf->entries[n_entries++] = entry;
+ }
+ }
+
+ /* if ignore_case, sort entries case insensitively */
+ if (iterator__ignore_case(ti))
+ git__tsort_r(
+ (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);
+
+ /* pick tf->current based on "start" (or start at zero) */
+ if (head->startlen > 0) {
+ git__bsearch_r((void **)tf->entries, tf->n_entries, head,
+ tree_iterator__search_cmp, ti, &tf->current);
+
+ while (tf->current &&
+ !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))
+ tf->current--;
+
+ if ((tf->start = strchr(head->start, '/')) != NULL) {
+ tf->start++;
+ tf->startlen = strlen(tf->start);
+ }
+ }
+
+ ti->path_has_filename = false;
+
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
+
+ /* autoexpand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__push_frame(ti);
return 0;
}
-static int tree_iterator__at_end(git_iterator *self)
+static bool tree_iterator__move_to_next(
+ tree_iterator *ti, tree_iterator_frame *tf)
{
- return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities--;
+
+ if (!tf->up) { /* at root */
+ tf->current = tf->next;
+ return false;
+ }
+
+ for (; tf->current < tf->next; tf->current++) {
+ git_tree_free(tf->entries[tf->current]->tree);
+ tf->entries[tf->current]->tree = NULL;
+ }
+
+ return (tf->current < tf->n_entries);
}
-static tree_iterator_frame *tree_iterator__alloc_frame(
- git_tree *tree, char *start)
+static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
{
- tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame));
- if (!tf)
- return NULL;
+ tree_iterator_frame *tf = ti->head;
- tf->tree = tree;
+ if (!tf->up)
+ return false;
- if (start && *start) {
- tf->start = start;
- tf->index = git_tree__prefix_position(tree, start);
+ ti->head = tf->up;
+ ti->head->down = NULL;
+
+ tree_iterator__move_to_next(ti, tf);
+
+ if (!final) { /* if final, don't bother to clean up */
+ git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries);
+ git_buf_rtruncate_at_char(&ti->path, '/');
}
- return tf;
+ git__free(tf);
+
+ return true;
}
-static int tree_iterator__expand_tree(tree_iterator *ti)
+static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
{
- int error;
- git_tree *subtree;
- const git_tree_entry *te = tree_iterator__tree_entry(ti);
- tree_iterator_frame *tf;
- char *relpath;
+ while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
- while (te != NULL && git_tree_entry__is_tree(te)) {
- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
- return -1;
+ if (!final) {
+ ti->head->current = to_end ? ti->head->n_entries : 0;
+ ti->path_ambiguities = 0;
+ git_buf_clear(&ti->path);
+ }
- /* check that we have not passed the range end */
- if (ti->base.end != NULL &&
- git__prefixcmp(ti->path.ptr, ti->base.end) > 0)
- return tree_iterator__to_end(ti);
+ return 0;
+}
- if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0)
- return error;
+static int tree_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
+ const git_tree_entry *te;
- relpath = NULL;
+ iterator__clear_entry(entry);
- /* apply range start to new frame if relevant */
- if (ti->stack->start &&
- git__prefixcmp(ti->stack->start, te->filename) == 0)
- {
- size_t namelen = strlen(te->filename);
- if (ti->stack->start[namelen] == '/')
- relpath = ti->stack->start + namelen + 1;
- }
+ if (tf->current >= tf->n_entries)
+ return 0;
+ te = tf->entries[tf->current]->te;
- if ((tf = tree_iterator__alloc_frame(subtree, relpath)) == NULL)
- return -1;
+ ti->entry.mode = te->attr;
+ git_oid_cpy(&ti->entry.oid, &te->oid);
- tf->next = ti->stack;
- ti->stack = tf;
+ ti->entry.path = tree_iterator__current_filename(ti, te);
+ GITERR_CHECK_ALLOC(ti->entry.path);
- te = tree_iterator__tree_entry(ti);
- }
+ if (ti->path_ambiguities > 0)
+ tree_iterator__rewrite_filename(ti);
+
+ if (iterator__past_end(ti, ti->entry.path))
+ return tree_iterator__pop_all(ti, true, false);
+
+ if (entry)
+ *entry = &ti->entry;
return 0;
}
-static int tree_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+static int tree_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
{
int error = 0;
tree_iterator *ti = (tree_iterator *)self;
- const git_tree_entry *te = NULL;
- if (entry != NULL)
- *entry = NULL;
+ iterator__clear_entry(entry);
- if (ti->path_has_filename) {
- git_buf_rtruncate_at_char(&ti->path, '/');
- ti->path_has_filename = false;
- }
+ if (tree_iterator__at_tree(ti) &&
+ !(error = tree_iterator__push_frame(ti)))
+ error = tree_iterator__current(entry, self);
- while (ti->stack != NULL) {
- te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index);
- if (te != NULL)
- break;
+ return error;
+}
+
+static int tree_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error;
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
- tree_iterator__pop_frame(ti);
+ iterator__clear_entry(entry);
+ if (tf->current > tf->n_entries)
+ return 0;
+
+ if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
+ tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+
+ if (ti->path_has_filename) {
git_buf_rtruncate_at_char(&ti->path, '/');
+ ti->path_has_filename = false;
}
- if (te && git_tree_entry__is_tree(te))
- error = tree_iterator__expand_tree(ti);
+ /* scan forward and up, advancing in frame or popping frame when done */
+ while (!tree_iterator__move_to_next(ti, tf) &&
+ tree_iterator__pop_frame(ti, false))
+ tf = ti->head;
- if (!error)
- error = tree_iterator__current(self, entry);
+ /* find next and load trees */
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
- return error;
+ /* deal with include_trees / auto_expand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+
+ return tree_iterator__current(entry, self);
}
static int tree_iterator__seek(git_iterator *self, const char *prefix)
{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
- /* pop stack until matches prefix */
- /* seek item in current frame matching prefix */
- /* push stack which matches prefix */
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
return -1;
}
-static void tree_iterator__free(git_iterator *self)
+static int tree_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
{
tree_iterator *ti = (tree_iterator *)self;
- while (ti->stack != NULL)
- tree_iterator__pop_frame(ti);
- git_buf_free(&ti->path);
+
+ tree_iterator__pop_all(ti, false, false);
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
+
+ return tree_iterator__push_frame(ti); /* re-expand root tree */
+}
+
+static int tree_iterator__at_end(git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+ return (ti->head->current >= ti->head->n_entries);
}
-static int tree_iterator__reset(git_iterator *self)
+static void tree_iterator__free(git_iterator *self)
{
tree_iterator *ti = (tree_iterator *)self;
- while (ti->stack && ti->stack->next)
- tree_iterator__pop_frame(ti);
+ tree_iterator__pop_all(ti, true, false);
+
+ git_tree_free(ti->head->entries[0]->tree);
+ git__free(ti->head);
+ git_pool_clear(&ti->pool);
+ git_buf_free(&ti->path);
+}
+
+static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
+{
+ size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);
+ tree_iterator_frame *root = git__calloc(sz, sizeof(char));
+ GITERR_CHECK_ALLOC(root);
- if (ti->stack)
- ti->stack->index =
- git_tree__prefix_position(ti->stack->tree, ti->base.start);
+ root->n_entries = 1;
+ root->next = 1;
+ root->start = ti->base.start;
+ root->startlen = root->start ? strlen(root->start) : 0;
+ root->entries[0] = git_pool_mallocz(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(root->entries[0]);
+ root->entries[0]->tree = tree;
- git_buf_clear(&ti->path);
+ ti->head = ti->root = root;
- return tree_iterator__expand_tree(ti);
+ return 0;
}
-int git_iterator_for_tree_range(
+int git_iterator_for_tree(
git_iterator **iter,
- git_repository *repo,
git_tree *tree,
+ git_iterator_flag_t flags,
const char *start,
const char *end)
{
@@ -306,42 +576,124 @@ int git_iterator_for_tree_range(
tree_iterator *ti;
if (tree == NULL)
- return git_iterator_for_nothing(iter);
+ return git_iterator_for_nothing(iter, flags, start, end);
- ITERATOR_BASE_INIT(ti, tree, TREE);
+ if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
+ return error;
- ti->repo = repo;
- ti->stack = tree_iterator__alloc_frame(tree, ti->base.start);
+ ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
- if ((error = tree_iterator__expand_tree(ti)) < 0)
- git_iterator_free((git_iterator *)ti);
- else
- *iter = (git_iterator *)ti;
+ if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)
+ goto fail;
+ ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp;
+
+ if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 ||
+ (error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
+ (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */
+ goto fail;
+ *iter = (git_iterator *)ti;
+ return 0;
+
+fail:
+ git_iterator_free((git_iterator *)ti);
return error;
}
typedef struct {
git_iterator base;
+ git_iterator_callbacks cb;
git_index *index;
- unsigned int current;
+ size_t current;
+ /* when not in autoexpand mode, use these to represent "tree" state */
+ git_buf partial;
+ size_t partial_pos;
+ char restore_terminator;
+ git_index_entry tree_entry;
} index_iterator;
-static int index_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
{
- index_iterator *ii = (index_iterator *)self;
- git_index_entry *ie = git_index_get(ii->index, ii->current);
+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
- if (ie != NULL &&
- ii->base.end != NULL &&
- git__prefixcmp(ie->path, ii->base.end) > 0)
- {
+ if (ie != NULL && iterator__past_end(ii, ie->path)) {
ii->current = git_index_entrycount(ii->index);
ie = NULL;
}
+ return ie;
+}
+
+static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii)
+{
+ const git_index_entry *ie;
+
+ while ((ie = index_iterator__index_entry(ii)) != NULL &&
+ git_index_entry_stage(ie) != 0)
+ ii->current++;
+
+ return ie;
+}
+
+static void index_iterator__next_prefix_tree(index_iterator *ii)
+{
+ const char *slash;
+
+ if (!iterator__include_trees(ii))
+ return;
+
+ slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');
+
+ if (slash != NULL) {
+ ii->partial_pos = (slash - ii->partial.ptr) + 1;
+ ii->restore_terminator = ii->partial.ptr[ii->partial_pos];
+ ii->partial.ptr[ii->partial_pos] = '\0';
+ } else {
+ ii->partial_pos = ii->partial.size;
+ }
+
+ if (index_iterator__index_entry(ii) == NULL)
+ ii->partial_pos = ii->partial.size;
+}
+
+static int index_iterator__first_prefix_tree(index_iterator *ii)
+{
+ const git_index_entry *ie = index_iterator__skip_conflicts(ii);
+ const char *scan, *prior, *slash;
+
+ if (!ie || !iterator__include_trees(ii))
+ return 0;
+
+ /* find longest common prefix with prior index entry */
+ for (scan = slash = ie->path, prior = ii->partial.ptr;
+ *scan && *scan == *prior; ++scan, ++prior)
+ if (*scan == '/')
+ slash = scan;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = (slash - ie->path) + 1;
+ index_iterator__next_prefix_tree(ii);
+
+ return 0;
+}
+
+#define index_iterator__at_tree(I) \
+ (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)
+
+static int index_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
+
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ ii->tree_entry.path = ii->partial.ptr;
+ ie = &ii->tree_entry;
+ }
+
if (entry)
*entry = ie;
@@ -355,28 +707,90 @@ static int index_iterator__at_end(git_iterator *self)
}
static int index_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
index_iterator *ii = (index_iterator *)self;
+ size_t entrycount = git_index_entrycount(ii->index);
+ const git_index_entry *ie;
+
+ if (index_iterator__at_tree(ii)) {
+ if (iterator__do_autoexpand(ii)) {
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ } else {
+ /* advance to sibling tree (i.e. find entry with new prefix) */
+ while (ii->current < entrycount) {
+ ii->current++;
+
+ if (!(ie = git_index_get_byindex(ii->index, ii->current)) ||
+ ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)
+ break;
+ }
+
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
+ }
+ } else {
+ if (ii->current < entrycount)
+ ii->current++;
- if (ii->current < git_index_entrycount(ii->index))
- ii->current++;
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
+ }
+
+ return index_iterator__current(entry, self);
+}
+
+static int index_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
+
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ if (ii->restore_terminator)
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ }
- return index_iterator__current(self, entry);
+ return index_iterator__current(entry, self);
}
static int index_iterator__seek(git_iterator *self, const char *prefix)
{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
- /* find last item before prefix */
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
return -1;
}
-static int index_iterator__reset(git_iterator *self)
+static int index_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
{
index_iterator *ii = (index_iterator *)self;
- ii->current = 0;
+ const git_index_entry *ie;
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
+
+ ii->current = ii->base.start ?
+ git_index__prefix_position(ii->index, ii->base.start) : 0;
+
+ if ((ie = index_iterator__skip_conflicts(ii)) == NULL)
+ return 0;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = 0;
+
+ if (ii->base.start) {
+ size_t startlen = strlen(ii->base.start);
+
+ ii->partial_pos = (startlen > ii->partial.size) ?
+ ii->partial.size : startlen;
+ }
+
+ index_iterator__next_prefix_tree(ii);
+
return 0;
}
@@ -385,58 +799,98 @@ static void index_iterator__free(git_iterator *self)
index_iterator *ii = (index_iterator *)self;
git_index_free(ii->index);
ii->index = NULL;
+
+ git_buf_free(&ii->partial);
}
-int git_iterator_for_index_range(
+int git_iterator_for_index(
git_iterator **iter,
- git_repository *repo,
+ git_index *index,
+ git_iterator_flag_t flags,
const char *start,
const char *end)
{
- int error;
index_iterator *ii;
- ITERATOR_BASE_INIT(ii, index, INDEX);
+ ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));
- if ((error = git_repository_index(&ii->index, repo)) < 0)
- git__free(ii);
- else {
- ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
- *iter = (git_iterator *)ii;
+ if (index->ignore_case) {
+ ii->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+ ii->base.prefixcomp = git__prefixcmp_icase;
}
- return error;
+ ii->index = index;
+ GIT_REFCOUNT_INC(index);
+
+ git_buf_init(&ii->partial, 0);
+ ii->tree_entry.mode = GIT_FILEMODE_TREE;
+
+ index_iterator__reset((git_iterator *)ii, NULL, NULL);
+
+ *iter = (git_iterator *)ii;
+
+ return 0;
}
+#define WORKDIR_MAX_DEPTH 100
+
typedef struct workdir_iterator_frame workdir_iterator_frame;
struct workdir_iterator_frame {
workdir_iterator_frame *next;
git_vector entries;
- unsigned int index;
- char *start;
+ size_t index;
};
typedef struct {
git_iterator base;
- git_repository *repo;
- size_t root_len;
+ git_iterator_callbacks cb;
workdir_iterator_frame *stack;
git_ignores ignores;
git_index_entry entry;
git_buf path;
+ size_t root_len;
int is_ignored;
+ int depth;
} workdir_iterator;
-static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
+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] == '/');
+ }
+}
+
+static workdir_iterator_frame *workdir_iterator__alloc_frame(
+ workdir_iterator *wi)
{
workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
+ git_vector_cmp entry_compare = CASESELECT(
+ iterator__ignore_case(wi),
+ git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
+
if (wf == NULL)
return NULL;
- if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) {
+
+ if (git_vector_init(&wf->entries, 0, entry_compare) != 0) {
git__free(wf);
return NULL;
}
+
return wf;
}
@@ -453,53 +907,73 @@ static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
static int workdir_iterator__update_entry(workdir_iterator *wi);
-static int workdir_iterator__entry_cmp(const void *prefix, const void *item)
+static int workdir_iterator__entry_cmp(const void *i, const void *item)
{
+ const workdir_iterator *wi = (const workdir_iterator *)i;
const git_path_with_stat *ps = item;
- return git__prefixcmp((const char *)prefix, ps->path);
+ return wi->base.prefixcomp(wi->base.start, ps->path);
+}
+
+static void workdir_iterator__seek_frame_start(
+ workdir_iterator *wi, workdir_iterator_frame *wf)
+{
+ if (!wf)
+ return;
+
+ if (wi->base.start)
+ git_vector_bsearch2(
+ &wf->index, &wf->entries, workdir_iterator__entry_cmp, wi);
+ else
+ wf->index = 0;
+
+ if (path_is_dotgit(git_vector_get(&wf->entries, wf->index)))
+ wf->index++;
}
static int workdir_iterator__expand_dir(workdir_iterator *wi)
{
int error;
- workdir_iterator_frame *wf = workdir_iterator__alloc_frame();
+ workdir_iterator_frame *wf;
+
+ wf = workdir_iterator__alloc_frame(wi);
GITERR_CHECK_ALLOC(wf);
- error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries);
+ error = git_path_dirload_with_stat(
+ wi->path.ptr, wi->root_len, iterator__ignore_case(wi),
+ wi->base.start, wi->base.end, &wf->entries);
+
if (error < 0 || wf->entries.length == 0) {
workdir_iterator__free_frame(wf);
return GIT_ENOTFOUND;
}
- git_vector_sort(&wf->entries);
-
- if (!wi->stack)
- wf->start = wi->base.start;
- else if (wi->stack->start &&
- git__prefixcmp(wi->stack->start, wi->path.ptr + wi->root_len) == 0)
- wf->start = wi->stack->start;
-
- if (wf->start)
- git_vector_bsearch3(
- &wf->index, &wf->entries, workdir_iterator__entry_cmp, wf->start);
+ 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;
+ }
- wf->next = wi->stack;
- wi->stack = wf;
+ workdir_iterator__seek_frame_start(wi, wf);
/* only push new ignores if this is not top level directory */
- if (wi->stack->next != NULL) {
+ 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]);
}
+ wf->next = wi->stack;
+ wi->stack = wf;
+
return workdir_iterator__update_entry(wi);
}
static int workdir_iterator__current(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
workdir_iterator *wi = (workdir_iterator *)self;
- *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
+ if (entry)
+ *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
return 0;
}
@@ -508,44 +982,83 @@ static int workdir_iterator__at_end(git_iterator *self)
return (((workdir_iterator *)self)->entry.path == NULL);
}
+static int workdir_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ iterator__clear_entry(entry);
+
+ /* workdir iterator will allow you to explicitly advance into a
+ * commit/submodule (as well as a tree) to avoid some cases where an
+ * entry is mislabeled as a submodule in the working directory
+ */
+ if (wi->entry.path != NULL &&
+ (wi->entry.mode == GIT_FILEMODE_TREE ||
+ wi->entry.mode == GIT_FILEMODE_COMMIT))
+ /* returns GIT_ENOTFOUND if the directory is empty */
+ error = workdir_iterator__expand_dir(wi);
+
+ if (!error && entry)
+ error = workdir_iterator__current(entry, iter);
+
+ return error;
+}
+
static int workdir_iterator__advance(
- git_iterator *self, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *self)
{
- int error;
+ int error = 0;
workdir_iterator *wi = (workdir_iterator *)self;
workdir_iterator_frame *wf;
git_path_with_stat *next;
+ /* given include_trees & autoexpand, we might have to go into a tree */
+ if (iterator__do_autoexpand(wi) &&
+ wi->entry.path != NULL &&
+ wi->entry.mode == GIT_FILEMODE_TREE)
+ {
+ error = workdir_iterator__advance_into(entry, self);
+
+ /* continue silently past empty directories if autoexpanding */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+ error = 0;
+ }
+
if (entry != NULL)
*entry = NULL;
- if (wi->entry.path == NULL)
- return 0;
-
- while ((wf = wi->stack) != NULL) {
+ while (wi->entry.path != NULL) {
+ wf = wi->stack;
next = git_vector_get(&wf->entries, ++wf->index);
+
if (next != NULL) {
- if (strcmp(next->path, DOT_GIT "/") == 0)
+ /* match git's behavior of ignoring anything named ".git" */
+ if (path_is_dotgit(next))
continue;
/* else found a good entry */
break;
}
- /* pop workdir directory stack */
- wi->stack = wf->next;
- workdir_iterator__free_frame(wf);
- git_ignore__pop_dir(&wi->ignores);
-
- if (wi->stack == NULL) {
+ /* 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);
}
error = workdir_iterator__update_entry(wi);
if (!error && entry != NULL)
- error = workdir_iterator__current(self, entry);
+ error = workdir_iterator__current(entry, self);
return error;
}
@@ -560,18 +1073,25 @@ static int workdir_iterator__seek(git_iterator *self, const char *prefix)
return 0;
}
-static int workdir_iterator__reset(git_iterator *self)
+static int workdir_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
{
workdir_iterator *wi = (workdir_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);
}
- if (wi->stack)
- wi->stack->index = 0;
- return 0;
+ wi->depth = 0;
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
+
+ workdir_iterator__seek_frame_start(wi, wi->stack);
+
+ return workdir_iterator__update_entry(wi);
}
static void workdir_iterator__free(git_iterator *self)
@@ -590,7 +1110,9 @@ static void workdir_iterator__free(git_iterator *self)
static int workdir_iterator__update_entry(workdir_iterator *wi)
{
- git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index);
+ int error = 0;
+ git_path_with_stat *ps =
+ git_vector_get(&wi->stack->entries, wi->stack->index);
git_buf_truncate(&wi->path, wi->root_len);
memset(&wi->entry, 0, sizeof(wi->entry));
@@ -598,62 +1120,62 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
if (!ps)
return 0;
+ /* skip over .git entries */
+ if (path_is_dotgit(ps))
+ return workdir_iterator__advance(NULL, (git_iterator *)wi);
+
if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)
return -1;
- if (wi->base.end &&
- git__prefixcmp(wi->path.ptr + wi->root_len, wi->base.end) > 0)
+ if (iterator__past_end(wi, wi->path.ptr + wi->root_len))
return 0;
wi->entry.path = ps->path;
- /* skip over .git directory */
- if (strcmp(ps->path, DOT_GIT "/") == 0)
- return workdir_iterator__advance((git_iterator *)wi, NULL);
+ wi->is_ignored = -1;
- /* if there is an error processing the entry, treat as ignored */
- wi->is_ignored = 1;
-
- git_index__init_entry_from_stat(&ps->st, &wi->entry);
+ git_index_entry__init_from_stat(&wi->entry, &ps->st);
/* need different mode here to keep directories during iteration */
wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
/* if this is a file type we don't handle, treat as ignored */
- if (wi->entry.mode == 0)
+ if (wi->entry.mode == 0) {
+ wi->is_ignored = 1;
return 0;
+ }
- /* okay, we are far enough along to look up real ignore rule */
- if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
- return 0; /* if error, ignore it and ignore file */
+ /* if this isn't a tree, then we're done */
+ if (wi->entry.mode != GIT_FILEMODE_TREE)
+ return 0;
/* detect submodules */
- if (S_ISDIR(wi->entry.mode)) {
- bool is_submodule = git_path_contains(&wi->path, DOT_GIT);
-
- /* if there is no .git, still check submodules data */
- if (!is_submodule) {
- int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
- is_submodule = (res == 0);
- if (res == GIT_ENOTFOUND)
- giterr_clear();
- }
-
- /* if submodule, mark as GITLINK and remove trailing slash */
- if (is_submodule) {
- size_t len = strlen(wi->entry.path);
- assert(wi->entry.path[len - 1] == '/');
- wi->entry.path[len - 1] = '\0';
- wi->entry.mode = S_IFGITLINK;
- }
+ error = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);
+ if (error == GIT_ENOTFOUND)
+ giterr_clear();
+
+ if (error == GIT_EEXISTS) /* if contains .git, treat as untracked submod */
+ error = 0;
+
+ /* if submodule, mark as GITLINK and remove trailing slash */
+ if (!error) {
+ size_t len = strlen(wi->entry.path);
+ assert(wi->entry.path[len - 1] == '/');
+ wi->entry.path[len - 1] = '\0';
+ wi->entry.mode = S_IFGITLINK;
+ return 0;
}
- return 0;
+ if (iterator__include_trees(wi))
+ return 0;
+
+ return workdir_iterator__advance(NULL, (git_iterator *)wi);
}
-int git_iterator_for_workdir_range(
+int git_iterator_for_workdir(
git_iterator **iter,
git_repository *repo,
+ git_iterator_flag_t flags,
const char *start,
const char *end)
{
@@ -662,15 +1184,14 @@ int git_iterator_for_workdir_range(
assert(iter && repo);
- if (git_repository_is_bare(repo)) {
- giterr_set(GITERR_INVALID,
- "Cannot scan working directory for bare repo");
- return -1;
- }
+ if ((error = git_repository__ensure_not_bare(
+ repo, "scan working directory")) < 0)
+ return error;
- ITERATOR_BASE_INIT(wi, workdir, WORKDIR);
+ ITERATOR_BASE_INIT(wi, workdir, WORKDIR, repo);
- wi->repo = repo;
+ if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0)
+ goto fail;
if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
git_path_to_dir(&wi->path) < 0 ||
@@ -679,70 +1200,150 @@ int git_iterator_for_workdir_range(
git__free(wi);
return -1;
}
-
wi->root_len = wi->path.size;
if ((error = workdir_iterator__expand_dir(wi)) < 0) {
- if (error == GIT_ENOTFOUND)
- error = 0;
- else {
- git_iterator_free((git_iterator *)wi);
- wi = NULL;
- }
+ if (error != GIT_ENOTFOUND)
+ goto fail;
+ giterr_clear();
}
*iter = (git_iterator *)wi;
+ return 0;
+fail:
+ git_iterator_free((git_iterator *)wi);
return error;
}
+void git_iterator_free(git_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ iter->cb->free(iter);
+
+ git__free(iter->start);
+ git__free(iter->end);
+
+ memset(iter, 0, sizeof(*iter));
+
+ git__free(iter);
+}
+
+int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
+{
+ bool desire_ignore_case = (ignore_case != 0);
+
+ if (iterator__ignore_case(iter) == desire_ignore_case)
+ return 0;
+
+ if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
+ if (desire_ignore_case)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Cannot currently set ignore case on non-empty iterators");
+ return -1;
+ }
+
+ return 0;
+}
+
+git_index *git_iterator_get_index(git_iterator *iter)
+{
+ if (iter->type == GIT_ITERATOR_TYPE_INDEX)
+ return ((index_iterator *)iter)->index;
+ return NULL;
+}
+
int git_iterator_current_tree_entry(
- git_iterator *iter, const git_tree_entry **tree_entry)
+ const git_tree_entry **tree_entry, git_iterator *iter)
{
- *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL :
- tree_iterator__tree_entry((tree_iterator *)iter);
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ *tree_entry = NULL;
+ else {
+ tree_iterator_frame *tf = ((tree_iterator *)iter)->head;
+ *tree_entry = (tf->current < tf->n_entries) ?
+ tf->entries[tf->current]->te : NULL;
+ }
+
return 0;
}
-int git_iterator_current_is_ignored(git_iterator *iter)
+int git_iterator_current_parent_tree(
+ const git_tree **tree_ptr,
+ git_iterator *iter,
+ const char *parent_path)
{
- return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
- ((workdir_iterator *)iter)->is_ignored;
+ tree_iterator *ti = (tree_iterator *)iter;
+ tree_iterator_frame *tf;
+ const char *scan = parent_path;
+ const git_tree_entry *te;
+
+ *tree_ptr = NULL;
+
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ return 0;
+
+ for (tf = ti->root; *scan; ) {
+ if (!(tf = tf->down) ||
+ tf->current >= tf->n_entries ||
+ !(te = tf->entries[tf->current]->te) ||
+ ti->strncomp(scan, te->filename, te->filename_len) != 0)
+ return 0;
+
+ scan += te->filename_len;
+ if (*scan == '/')
+ scan++;
+ }
+
+ *tree_ptr = tf->entries[tf->current]->tree;
+ return 0;
}
-int git_iterator_advance_into_directory(
- git_iterator *iter, const git_index_entry **entry)
+bool git_iterator_current_is_ignored(git_iterator *iter)
{
workdir_iterator *wi = (workdir_iterator *)iter;
- if (iter->type == GIT_ITERATOR_WORKDIR &&
- wi->entry.path &&
- S_ISDIR(wi->entry.mode) &&
- !S_ISGITLINK(wi->entry.mode))
- {
- if (workdir_iterator__expand_dir(wi) < 0)
- /* if error loading or if empty, skip the directory. */
- return workdir_iterator__advance(iter, entry);
- }
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ if (wi->is_ignored != -1)
+ return (bool)(wi->is_ignored != 0);
- return entry ? git_iterator_current(iter, entry) : 0;
+ if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
+ wi->is_ignored = true;
+
+ return (bool)wi->is_ignored;
}
-int git_iterator_cmp(
- git_iterator *iter, const char *path_prefix)
+int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
{
const git_index_entry *entry;
/* a "done" iterator is after every prefix */
- if (git_iterator_current(iter, &entry) < 0 ||
- entry == NULL)
+ if (git_iterator_current(&entry, iter) < 0 || entry == NULL)
return 1;
/* a NULL prefix is after any valid iterator */
if (!path_prefix)
return -1;
- return git__prefixcmp(entry->path, path_prefix);
+ return iter->prefixcomp(entry->path, path_prefix);
}
+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)
+ *path = NULL;
+ else
+ *path = &wi->path;
+
+ return 0;
+}
diff --git a/src/iterator.h b/src/iterator.h
index b916a9080..4a4e6a9d8 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -9,143 +9,197 @@
#include "common.h"
#include "git2/index.h"
+#include "vector.h"
+#include "buffer.h"
typedef struct git_iterator git_iterator;
typedef enum {
- GIT_ITERATOR_EMPTY = 0,
- GIT_ITERATOR_TREE = 1,
- GIT_ITERATOR_INDEX = 2,
- GIT_ITERATOR_WORKDIR = 3
+ GIT_ITERATOR_TYPE_EMPTY = 0,
+ GIT_ITERATOR_TYPE_TREE = 1,
+ GIT_ITERATOR_TYPE_INDEX = 2,
+ GIT_ITERATOR_TYPE_WORKDIR = 3,
} git_iterator_type_t;
+typedef enum {
+ /** ignore case for entry sort order */
+ GIT_ITERATOR_IGNORE_CASE = (1 << 0),
+ /** force case sensitivity for entry sort order */
+ GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1),
+ /** return tree items in addition to blob items */
+ GIT_ITERATOR_INCLUDE_TREES = (1 << 2),
+ /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
+ GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3),
+} git_iterator_flag_t;
+
+typedef struct {
+ int (*current)(const git_index_entry **, git_iterator *);
+ int (*advance)(const git_index_entry **, git_iterator *);
+ int (*advance_into)(const git_index_entry **, git_iterator *);
+ int (*seek)(git_iterator *, const char *prefix);
+ int (*reset)(git_iterator *, const char *start, const char *end);
+ int (*at_end)(git_iterator *);
+ void (*free)(git_iterator *);
+} git_iterator_callbacks;
+
struct git_iterator {
git_iterator_type_t type;
+ git_iterator_callbacks *cb;
+ git_repository *repo;
char *start;
char *end;
- int (*current)(git_iterator *, const git_index_entry **);
- int (*at_end)(git_iterator *);
- int (*advance)(git_iterator *, const git_index_entry **);
- int (*seek)(git_iterator *, const char *prefix);
- int (*reset)(git_iterator *);
- void (*free)(git_iterator *);
+ int (*prefixcomp)(const char *str, const char *prefix);
+ unsigned int flags;
};
-extern int git_iterator_for_nothing(git_iterator **iter);
-
-extern int git_iterator_for_tree_range(
- git_iterator **iter, git_repository *repo, git_tree *tree,
- const char *start, const char *end);
-
-GIT_INLINE(int) git_iterator_for_tree(
- git_iterator **iter, git_repository *repo, git_tree *tree)
-{
- return git_iterator_for_tree_range(iter, repo, tree, NULL, NULL);
-}
-
-extern int git_iterator_for_index_range(
- git_iterator **iter, git_repository *repo,
- const char *start, const char *end);
-
-GIT_INLINE(int) git_iterator_for_index(
- git_iterator **iter, git_repository *repo)
-{
- return git_iterator_for_index_range(iter, repo, NULL, NULL);
-}
+extern int git_iterator_for_nothing(
+ git_iterator **out,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
-extern int git_iterator_for_workdir_range(
- git_iterator **iter, git_repository *repo,
- const char *start, const char *end);
-
-GIT_INLINE(int) git_iterator_for_workdir(
- git_iterator **iter, git_repository *repo)
-{
- return git_iterator_for_workdir_range(iter, repo, NULL, NULL);
-}
+/* tree iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+extern int git_iterator_for_tree(
+ git_iterator **out,
+ git_tree *tree,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+/* index iterators will take the ignore_case value from the index; the
+ * ignore_case flags are not used
+ */
+extern int git_iterator_for_index(
+ git_iterator **out,
+ git_index *index,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+/* workdir iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+extern int git_iterator_for_workdir(
+ git_iterator **out,
+ git_repository *repo,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+extern void git_iterator_free(git_iterator *iter);
-/* Entry is not guaranteed to be fully populated. For a tree iterator,
- * we will only populate the mode, oid and path, for example. For a workdir
- * iterator, we will not populate the oid.
+/* Return a git_index_entry structure for the current value the iterator
+ * is looking at or NULL if the iterator is at the end.
+ *
+ * The entry may noy be fully populated. Tree iterators will only have a
+ * value mode, OID, and path. Workdir iterators will not have an OID (but
+ * you can use `git_iterator_current_oid()` to calculate it on demand).
*
* You do not need to free the entry. It is still "owned" by the iterator.
- * Once you call `git_iterator_advance`, then content of the old entry is
- * no longer guaranteed to be valid.
+ * Once you call `git_iterator_advance()` then the old entry is no longer
+ * guaranteed to be valid - it may be freed or just overwritten in place.
*/
GIT_INLINE(int) git_iterator_current(
- git_iterator *iter, const git_index_entry **entry)
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->current(iter, entry);
+ return iter->cb->current(entry, iter);
}
-GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
+/**
+ * Advance to the next item for the iterator.
+ *
+ * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If
+ * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree
+ * item will skip over all the items under that tree.
+ */
+GIT_INLINE(int) git_iterator_advance(
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->at_end(iter);
+ return iter->cb->advance(entry, iter);
}
-GIT_INLINE(int) git_iterator_advance(
- git_iterator *iter, const git_index_entry **entry)
+/**
+ * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).
+ *
+ * git_iterator_advance() steps through all items being iterated over
+ * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),
+ * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next
+ * sibling of a tree instead of going to the first child of the tree. In
+ * that case, use this function to advance to the first child of the tree.
+ *
+ * If the current item is not a tree, this is a no-op.
+ *
+ * For working directory iterators only, a tree (i.e. directory) can be
+ * empty. In that case, this function returns GIT_ENOTFOUND and does not
+ * advance. That can't happen for tree and index iterators.
+ */
+GIT_INLINE(int) git_iterator_advance_into(
+ const git_index_entry **entry, git_iterator *iter)
{
- return iter->advance(iter, entry);
+ return iter->cb->advance_into(entry, iter);
}
GIT_INLINE(int) git_iterator_seek(
git_iterator *iter, const char *prefix)
{
- return iter->seek(iter, prefix);
+ return iter->cb->seek(iter, prefix);
}
-GIT_INLINE(int) git_iterator_reset(git_iterator *iter)
+GIT_INLINE(int) git_iterator_reset(
+ git_iterator *iter, const char *start, const char *end)
{
- return iter->reset(iter);
+ return iter->cb->reset(iter, start, end);
}
-GIT_INLINE(void) git_iterator_free(git_iterator *iter)
+GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
{
- if (iter == NULL)
- return;
-
- iter->free(iter);
+ return iter->cb->at_end(iter);
+}
- git__free(iter->start);
- git__free(iter->end);
+GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
+{
+ return iter->type;
+}
- memset(iter, 0, sizeof(*iter));
+GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)
+{
+ return iter->repo;
+}
- git__free(iter);
+GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)
+{
+ return iter->flags;
}
-GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
+GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
{
- return iter->type;
+ return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
}
+extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);
+
extern int git_iterator_current_tree_entry(
- git_iterator *iter, const git_tree_entry **tree_entry);
+ const git_tree_entry **entry_out, git_iterator *iter);
-extern int git_iterator_current_is_ignored(git_iterator *iter);
+extern int git_iterator_current_parent_tree(
+ const git_tree **tree_out, git_iterator *iter, const char *parent_path);
-/**
- * Iterate into a workdir directory.
- *
- * Workdir iterators do not automatically descend into directories (so that
- * when comparing two iterator entries you can detect a newly created
- * directory in the workdir). As a result, you may get S_ISDIR items from
- * a workdir iterator. If you wish to iterate over the contents of the
- * directories you encounter, then call this function when you encounter
- * a directory.
- *
- * If there are no files in the directory, this will end up acting like a
- * regular advance and will skip past the directory, so you should be
- * prepared for that case.
- *
- * On non-workdir iterators or if not pointing at a directory, this is a
- * no-op and will not advance the iterator.
- */
-extern int git_iterator_advance_into_directory(
- git_iterator *iter, const git_index_entry **entry);
+extern bool git_iterator_current_is_ignored(git_iterator *iter);
extern int git_iterator_cmp(
git_iterator *iter, const char *path_prefix);
+/**
+ * Get full path of the current item from a workdir iterator. This will
+ * return NULL for a non-workdir iterator. The git_buf is still owned by
+ * the iterator; this is exposed just for efficiency.
+ */
+extern int git_iterator_current_workdir_path(
+ git_buf **path, git_iterator *iter);
+
+/* Return index pointer if index iterator, else NULL */
+extern git_index *git_iterator_get_index(git_iterator *iter);
+
#endif
diff --git a/src/khash.h b/src/khash.h
index bd67fe1f7..242204464 100644
--- a/src/khash.h
+++ b/src/khash.h
@@ -131,7 +131,9 @@ typedef unsigned long long khint64_t;
#endif
#ifdef _MSC_VER
-#define inline __inline
+#define kh_inline __inline
+#else
+#define kh_inline inline
#endif
typedef khint32_t khint_t;
@@ -345,7 +347,7 @@ static const double __ac_HASH_UPPER = 0.77;
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
/* --- BEGIN OF HASH FUNCTIONS --- */
@@ -374,7 +376,7 @@ static const double __ac_HASH_UPPER = 0.77;
@param s Pointer to a null terminated string
@return The hash value
*/
-static inline khint_t __ac_X31_hash_string(const char *s)
+static kh_inline khint_t __ac_X31_hash_string(const char *s)
{
khint_t h = (khint_t)*s;
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
@@ -391,7 +393,7 @@ static inline khint_t __ac_X31_hash_string(const char *s)
*/
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
-static inline khint_t __ac_Wang_hash(khint_t key)
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
{
key += ~(key << 15);
key ^= (key >> 10);
diff --git a/src/map.h b/src/map.h
index 96d879547..da3d1e19a 100644
--- a/src/map.h
+++ b/src/map.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -23,6 +23,10 @@
#define GIT_MAP_TYPE 0xf
#define GIT_MAP_FIXED 0x10
+#ifdef __amigaos4__
+#define MAP_FAILED 0
+#endif
+
typedef struct { /* memory mapped buffer */
void *data; /* data bytes */
size_t len; /* data length */
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 000000000..e0010d6a4
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,296 @@
+/*
+ * 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 "repository.h"
+#include "revwalk.h"
+#include "buffer.h"
+#include "merge.h"
+#include "refs.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/reset.h"
+#include "commit_list.h"
+
+int git_repository_merge_cleanup(git_repository *repo)
+{
+ int error = 0;
+ git_buf merge_head_path = GIT_BUF_INIT,
+ merge_mode_path = GIT_BUF_INIT,
+ merge_msg_path = GIT_BUF_INIT;
+
+ assert(repo);
+
+ if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
+ git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if (git_path_isfile(merge_head_path.ptr)) {
+ if ((error = p_unlink(merge_head_path.ptr)) < 0)
+ goto cleanup;
+ }
+
+ if (git_path_isfile(merge_mode_path.ptr))
+ (void)p_unlink(merge_mode_path.ptr);
+
+ if (git_path_isfile(merge_msg_path.ptr))
+ (void)p_unlink(merge_msg_path.ptr);
+
+cleanup:
+ git_buf_free(&merge_msg_path);
+ git_buf_free(&merge_mode_path);
+ git_buf_free(&merge_head_path);
+
+ return error;
+}
+
+int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)
+{
+ git_revwalk *walk;
+ git_vector list;
+ git_commit_list *result = NULL;
+ int error = -1;
+ unsigned int i;
+ git_commit_list_node *commit;
+
+ assert(out && repo && input_array);
+
+ if (length < 2) {
+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length);
+ return -1;
+ }
+
+ if (git_vector_init(&list, length - 1, NULL) < 0)
+ return -1;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ goto cleanup;
+
+ for (i = 1; i < length; i++) {
+ commit = git_revwalk__commit_lookup(walk, &input_array[i]);
+ if (commit == NULL)
+ goto cleanup;
+
+ git_vector_insert(&list, commit);
+ }
+
+ commit = git_revwalk__commit_lookup(walk, &input_array[0]);
+ if (commit == NULL)
+ goto cleanup;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto cleanup;
+
+ if (!result) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ git_oid_cpy(out, &result->item->oid);
+
+ error = 0;
+
+cleanup:
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+ git_vector_free(&list);
+ return error;
+}
+
+int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ git_revwalk *walk;
+ git_vector list;
+ git_commit_list *result = NULL;
+ git_commit_list_node *commit;
+ void *contents[1];
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit = git_revwalk__commit_lookup(walk, two);
+ if (commit == NULL)
+ goto on_error;
+
+ /* This is just one value, so we can do it on the stack */
+ memset(&list, 0x0, sizeof(git_vector));
+ contents[0] = commit;
+ list.length = 1;
+ list.contents = contents;
+
+ commit = git_revwalk__commit_lookup(walk, one);
+ if (commit == NULL)
+ goto on_error;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto on_error;
+
+ if (!result) {
+ git_revwalk_free(walk);
+ giterr_clear();
+ return GIT_ENOTFOUND;
+ }
+
+ git_oid_cpy(out, &result->item->oid);
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+}
+
+static int interesting(git_pqueue *list)
+{
+ unsigned int i;
+ /* element 0 isn't used - we need to start at 1 */
+ for (i = 1; i < list->size; i++) {
+ git_commit_list_node *commit = list->d[i];
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos)
+{
+ int error;
+ unsigned int i;
+ git_commit_list_node *two;
+ git_commit_list *result = NULL, *tmp = NULL;
+ git_pqueue list;
+
+ /* if the commit is repeated, we have a our merge base already */
+ git_vector_foreach(twos, i, two) {
+ if (one == two)
+ return git_commit_list_insert(one, out) ? 0 : -1;
+ }
+
+ if (git_pqueue_init(&list, twos->length * 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ return -1;
+
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ return -1;
+
+ git_vector_foreach(twos, i, two) {
+ git_commit_list_parse(walk, two);
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ return -1;
+ }
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list)) {
+ git_commit_list_node *commit;
+ int flags;
+
+ commit = git_pqueue_pop(&list);
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT)) {
+ commit->flags |= RESULT;
+ if (git_commit_list_insert(commit, &result) == NULL)
+ return -1;
+ }
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ return -1;
+ }
+ }
+
+ git_pqueue_free(&list);
+
+ /* filter out any stale commits in the results */
+ tmp = result;
+ result = NULL;
+
+ while (tmp) {
+ struct git_commit_list *next = tmp->next;
+ if (!(tmp->item->flags & STALE))
+ if (git_commit_list_insert_by_date(tmp->item, &result) == NULL)
+ return -1;
+
+ git__free(tmp);
+ tmp = next;
+ }
+
+ *out = result;
+ return 0;
+}
+
+int git_repository_mergehead_foreach(git_repository *repo,
+ git_repository_mergehead_foreach_cb cb,
+ void *payload)
+{
+ git_buf merge_head_path = GIT_BUF_INIT, merge_head_file = GIT_BUF_INIT;
+ char *buffer, *line;
+ size_t line_num = 1;
+ git_oid oid;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository,
+ GIT_MERGE_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_futils_readbuffer(&merge_head_file,
+ git_buf_cstr(&merge_head_path))) < 0)
+ goto cleanup;
+
+ buffer = merge_head_file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ if (strlen(line) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_INVALID, "Unable to parse OID - invalid length");
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_oid_fromstr(&oid, line)) < 0)
+ goto cleanup;
+
+ if (cb(&oid, payload) < 0) {
+ error = GIT_EUSER;
+ goto cleanup;
+ }
+
+ ++line_num;
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_MERGE, "No EOL at line %d", line_num);
+ error = -1;
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&merge_head_path);
+ git_buf_free(&merge_head_file);
+
+ return error;
+}
diff --git a/src/merge.h b/src/merge.h
new file mode 100644
index 000000000..22c644270
--- /dev/null
+++ b/src/merge.h
@@ -0,0 +1,22 @@
+/*
+ * 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_merge_h__
+#define INCLUDE_merge_h__
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "commit_list.h"
+#include "vector.h"
+
+#define GIT_MERGE_MSG_FILE "MERGE_MSG"
+#define GIT_MERGE_MODE_FILE "MERGE_MODE"
+
+#define MERGE_CONFIG_FILE_MODE 0666
+
+int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos);
+
+#endif
diff --git a/src/message.c b/src/message.c
index aa0220fd0..0eff426f2 100644
--- a/src/message.c
+++ b/src/message.c
@@ -1,12 +1,11 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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 "message.h"
-#include <ctype.h>
static size_t line_length_without_trailing_spaces(const char *line, size_t len)
{
@@ -22,7 +21,7 @@ static size_t line_length_without_trailing_spaces(const char *line, size_t len)
/* Greatly inspired from git.git "stripspace" */
/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */
-int git_message_prettify(git_buf *message_out, const char *message, int strip_comments)
+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments)
{
const size_t message_len = strlen(message);
@@ -59,3 +58,29 @@ int git_message_prettify(git_buf *message_out, const char *message, int strip_co
return git_buf_oom(message_out) ? -1 : 0;
}
+
+int git_message_prettify(char *message_out, size_t buffer_size, const char *message, int strip_comments)
+{
+ git_buf buf = GIT_BUF_INIT;
+ ssize_t out_size = -1;
+
+ if (message_out && buffer_size)
+ *message_out = '\0';
+
+ if (git_message__prettify(&buf, message, strip_comments) < 0)
+ goto done;
+
+ if (message_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */
+ giterr_set(GITERR_INVALID, "Buffer too short to hold the cleaned message");
+ goto done;
+ }
+
+ if (message_out)
+ git_buf_copy_cstr(message_out, buffer_size, &buf);
+
+ out_size = buf.size + 1;
+
+done:
+ git_buf_free(&buf);
+ return (int)out_size;
+}
diff --git a/src/message.h b/src/message.h
index ddfa13e18..3c4b8dc45 100644
--- a/src/message.h
+++ b/src/message.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,8 +7,9 @@
#ifndef INCLUDE_message_h__
#define INCLUDE_message_h__
+#include "git2/message.h"
#include "buffer.h"
-int git_message_prettify(git_buf *message_out, const char *message, int strip_comments);
+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments);
#endif /* INCLUDE_message_h__ */
diff --git a/src/mwindow.c b/src/mwindow.c
index b59c4d2f7..b35503d46 100644
--- a/src/mwindow.c
+++ b/src/mwindow.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -20,17 +20,11 @@
#define DEFAULT_MAPPED_LIMIT \
((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
-/*
- * These are the global options for mmmap limits.
- * TODO: allow the user to change these
- */
-static struct {
- size_t window_size;
- size_t mapped_limit;
-} _mw_options = {
- DEFAULT_WINDOW_SIZE,
- DEFAULT_MAPPED_LIMIT,
-};
+size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
+size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
+
+/* Whenever you want to read or modify this, grab git__mwindow_mutex */
+static git_mwindow_ctl mem_ctl;
/*
* Free all the windows in a sequence, typically because we're done
@@ -38,8 +32,14 @@ static struct {
*/
void git_mwindow_free_all(git_mwindow_file *mwf)
{
- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
- unsigned int i;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
/*
* Remove these windows from the global list
*/
@@ -67,6 +67,8 @@ void git_mwindow_free_all(git_mwindow_file *mwf)
mwf->windows = w->next;
git__free(w);
}
+
+ git_mutex_unlock(&git__mwindow_mutex);
}
/*
@@ -82,14 +84,13 @@ int git_mwindow_contains(git_mwindow *win, git_off_t offset)
/*
* Find the least-recently-used window in a file
*/
-void git_mwindow_scan_lru(
+static void git_mwindow_scan_lru(
git_mwindow_file *mwf,
git_mwindow **lru_w,
git_mwindow **lru_l)
{
git_mwindow *w, *w_l;
- puts("LRU");
for (w_l = NULL, w = mwf->windows; w; w = w->next) {
if (!w->inuse_cnt) {
/*
@@ -108,12 +109,13 @@ void git_mwindow_scan_lru(
/*
* Close the least recently used window. You should check to see if
- * the file descriptors need closing from time to time.
+ * the file descriptors need closing from time to time. Called under
+ * lock from new_window.
*/
static int git_mwindow_close_lru(git_mwindow_file *mwf)
{
- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
- unsigned int i;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
/* FIXME: Does this give us any advantage? */
@@ -147,18 +149,20 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf)
return 0;
}
+/* This gets called under lock from git_mwindow_open */
static git_mwindow *new_window(
git_mwindow_file *mwf,
git_file fd,
git_off_t size,
git_off_t offset)
{
- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
- size_t walign = _mw_options.window_size / 2;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t walign = git_mwindow__window_size / 2;
git_off_t len;
git_mwindow *w;
w = git__malloc(sizeof(*w));
+
if (w == NULL)
return NULL;
@@ -166,16 +170,16 @@ static git_mwindow *new_window(
w->offset = (offset / walign) * walign;
len = size - w->offset;
- if (len > (git_off_t)_mw_options.window_size)
- len = (git_off_t)_mw_options.window_size;
+ if (len > (git_off_t)git_mwindow__window_size)
+ len = (git_off_t)git_mwindow__window_size;
ctl->mapped += (size_t)len;
- while (_mw_options.mapped_limit < ctl->mapped &&
+ while (git_mwindow__mapped_limit < ctl->mapped &&
git_mwindow_close_lru(mwf) == 0) /* nop */;
/*
- * We treat _mw_options.mapped_limit as a soft limit. If we can't find a
+ * We treat `mapped_limit` as a soft limit. If we can't find a
* window to close and are above the limit, we still mmap the new
* window.
*/
@@ -208,9 +212,14 @@ unsigned char *git_mwindow_open(
size_t extra,
unsigned int *left)
{
- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
+ git_mwindow_ctl *ctl = &mem_ctl;
git_mwindow *w = *cursor;
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return NULL;
+ }
+
if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
if (w) {
w->inuse_cnt--;
@@ -228,8 +237,10 @@ unsigned char *git_mwindow_open(
*/
if (!w) {
w = new_window(mwf, mwf->fd, mwf->size, offset);
- if (w == NULL)
+ if (w == NULL) {
+ git_mutex_unlock(&git__mwindow_mutex);
return NULL;
+ }
w->next = mwf->windows;
mwf->windows = w;
}
@@ -247,26 +258,62 @@ unsigned char *git_mwindow_open(
if (left)
*left = (unsigned int)(w->window_map.len - offset);
- fflush(stdout);
+ git_mutex_unlock(&git__mwindow_mutex);
return (unsigned char *) w->window_map.data + offset;
}
int git_mwindow_file_register(git_mwindow_file *mwf)
{
- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ int ret;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return -1;
+ }
if (ctl->windowfiles.length == 0 &&
- git_vector_init(&ctl->windowfiles, 8, NULL) < 0)
+ git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
return -1;
+ }
- return git_vector_insert(&ctl->windowfiles, mwf);
+ ret = git_vector_insert(&ctl->windowfiles, mwf);
+ git_mutex_unlock(&git__mwindow_mutex);
+
+ return ret;
+}
+
+void git_mwindow_file_deregister(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ git_mwindow_file *cur;
+ size_t i;
+
+ if (git_mutex_lock(&git__mwindow_mutex))
+ return;
+
+ git_vector_foreach(&ctl->windowfiles, i, cur) {
+ if (cur == mwf) {
+ git_vector_remove(&ctl->windowfiles, i);
+ git_mutex_unlock(&git__mwindow_mutex);
+ return;
+ }
+ }
+ git_mutex_unlock(&git__mwindow_mutex);
}
void git_mwindow_close(git_mwindow **window)
{
git_mwindow *w = *window;
if (w) {
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
w->inuse_cnt--;
+ git_mutex_unlock(&git__mwindow_mutex);
*window = NULL;
}
}
diff --git a/src/mwindow.h b/src/mwindow.h
index 058027251..0018ebbf0 100644
--- a/src/mwindow.h
+++ b/src/mwindow.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -38,8 +38,8 @@ typedef struct git_mwindow_ctl {
int git_mwindow_contains(git_mwindow *win, git_off_t offset);
void git_mwindow_free_all(git_mwindow_file *mwf);
unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left);
-void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l);
int git_mwindow_file_register(git_mwindow_file *mwf);
+void git_mwindow_file_deregister(git_mwindow_file *mwf);
void git_mwindow_close(git_mwindow **w_cursor);
#endif
diff --git a/src/netops.c b/src/netops.c
index 4d461a049..69179dd1c 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,15 +10,26 @@
# include <sys/select.h>
# include <sys/time.h>
# include <netdb.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#else
-# include <winsock2.h>
-# include <Ws2tcpip.h>
+# include <ws2tcpip.h>
# ifdef _MSC_VER
-# pragma comment(lib, "Ws2_32.lib")
+# pragma comment(lib, "ws2_32")
# endif
#endif
+#ifdef __FreeBSD__
+# include <netinet/in.h>
+#endif
+
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
+#endif
+#include <ctype.h>
#include "git2/errors.h"
#include "common.h"
@@ -29,14 +40,15 @@
#ifdef GIT_WIN32
static void net_set_error(const char *str)
{
- int size, error = WSAGetLastError();
- LPSTR err_str = NULL;
+ int error = WSAGetLastError();
+ char * win32_error = git_win32_get_error_message(error);
- size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- 0, error, 0, (LPSTR)&err_str, 0, 0);
-
- giterr_set(GITERR_NET, "%s: %s", str, err_str);
- LocalFree(err_str);
+ if (win32_error) {
+ giterr_set(GITERR_NET, "%s: %s", str, win32_error);
+ git__free(win32_error);
+ } else {
+ giterr_set(GITERR_NET, str);
+ }
}
#else
static void net_set_error(const char *str)
@@ -45,21 +57,66 @@ static void net_set_error(const char *str)
}
#endif
-void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd)
+#ifdef GIT_SSL
+static int ssl_set_error(gitno_ssl *ssl, int error)
{
- memset(buf, 0x0, sizeof(gitno_buffer));
- memset(data, 0x0, len);
- buf->data = data;
- buf->len = len;
- buf->offset = 0;
- buf->fd = fd;
+ int err;
+ unsigned long e;
+
+ err = SSL_get_error(ssl->ssl, error);
+
+ assert(err != SSL_ERROR_WANT_READ);
+ assert(err != SSL_ERROR_WANT_WRITE);
+
+ switch (err) {
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ giterr_set(GITERR_NET, "SSL error: connection failure\n");
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ giterr_set(GITERR_NET, "SSL error: x509 error\n");
+ break;
+ case SSL_ERROR_SYSCALL:
+ e = ERR_get_error();
+ if (e > 0) {
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ } else if (error < 0) {
+ giterr_set(GITERR_OS, "SSL error: syscall failure");
+ break;
+ }
+ giterr_set(GITERR_NET, "SSL error: received early EOF");
+ break;
+ case SSL_ERROR_SSL:
+ e = ERR_get_error();
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ giterr_set(GITERR_NET, "SSL error: unknown error");
+ break;
+ }
+ return -1;
}
+#endif
int gitno_recv(gitno_buffer *buf)
{
+ return buf->recv(buf);
+}
+
+#ifdef GIT_SSL
+static int gitno__recv_ssl(gitno_buffer *buf)
+{
int ret;
- ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
+ do {
+ ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
+ } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
+
if (ret < 0) {
net_set_error("Error receiving socket data");
return -1;
@@ -68,6 +125,49 @@ int gitno_recv(gitno_buffer *buf)
buf->offset += ret;
return ret;
}
+#endif
+
+static int gitno__recv(gitno_buffer *buf)
+{
+ int ret;
+
+ ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
+ if (ret < 0) {
+ net_set_error("Error receiving socket data");
+ return -1;
+ }
+
+ buf->offset += ret;
+ return ret;
+}
+
+void gitno_buffer_setup_callback(
+ gitno_socket *socket,
+ gitno_buffer *buf,
+ char *data,
+ size_t len,
+ int (*recv)(gitno_buffer *buf), void *cb_data)
+{
+ memset(data, 0x0, len);
+ buf->data = data;
+ buf->len = len;
+ buf->offset = 0;
+ buf->socket = socket;
+ buf->recv = recv;
+ buf->cb_data = cb_data;
+}
+
+void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
+{
+#ifdef GIT_SSL
+ if (socket->ssl.ctx) {
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
+ return;
+ }
+#endif
+
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
+}
/* Consume up to ptr and move the rest of the buffer to the beginning */
void gitno_consume(gitno_buffer *buf, const char *ptr)
@@ -92,24 +192,284 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
buf->offset -= cons;
}
-int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port)
+#ifdef GIT_SSL
+
+static int gitno_ssl_teardown(gitno_ssl *ssl)
+{
+ int ret;
+
+ ret = SSL_shutdown(ssl->ssl);
+ if (ret < 0)
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
+
+ SSL_free(ssl->ssl);
+ SSL_CTX_free(ssl->ctx);
+ return ret;
+}
+
+/* Match host names according to RFC 2818 rules */
+static int match_host(const char *pattern, const char *host)
+{
+ for (;;) {
+ char c = tolower(*pattern++);
+
+ if (c == '\0')
+ return *host ? -1 : 0;
+
+ if (c == '*') {
+ c = *pattern;
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return 0;
+
+ /*
+ * We've found a pattern, so move towards the next matching
+ * char. The '.' is handled specially because wildcards aren't
+ * allowed to cross subdomains.
+ */
+
+ while(*host) {
+ char h = tolower(*host);
+ if (c == h)
+ return match_host(pattern, host++);
+ if (h == '.')
+ return match_host(pattern, host);
+ host++;
+ }
+ return -1;
+ }
+
+ if (c != tolower(*host++))
+ return -1;
+ }
+
+ return -1;
+}
+
+static int check_host_name(const char *name, const char *host)
+{
+ if (!strcasecmp(name, host))
+ return 0;
+
+ if (match_host(name, host) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int verify_server_cert(gitno_ssl *ssl, const char *host)
+{
+ X509 *cert;
+ X509_NAME *peer_name;
+ ASN1_STRING *str;
+ unsigned char *peer_cn = NULL;
+ int matched = -1, type = GEN_DNS;
+ GENERAL_NAMES *alts;
+ struct in6_addr addr6;
+ struct in_addr addr4;
+ void *addr;
+ int i = -1,j;
+
+ if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
+ giterr_set(GITERR_SSL, "The SSL certificate is invalid");
+ return -1;
+ }
+
+ /* Try to parse the host as an IP address to see if it is */
+ if (p_inet_pton(AF_INET, host, &addr4)) {
+ type = GEN_IPADD;
+ addr = &addr4;
+ } else {
+ if(p_inet_pton(AF_INET6, host, &addr6)) {
+ type = GEN_IPADD;
+ addr = &addr6;
+ }
+ }
+
+
+ cert = SSL_get_peer_certificate(ssl->ssl);
+
+ /* Check the alternative names */
+ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (alts) {
+ int num;
+
+ num = sk_GENERAL_NAME_num(alts);
+ for (i = 0; i < num && matched != 1; i++) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
+ const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
+ size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
+
+ /* Skip any names of a type we're not looking for */
+ if (gn->type != type)
+ continue;
+
+ if (type == GEN_DNS) {
+ /* If it contains embedded NULs, don't even try */
+ if (memchr(name, '\0', namelen))
+ continue;
+
+ if (check_host_name(name, host) < 0)
+ matched = 0;
+ else
+ matched = 1;
+ } else if (type == GEN_IPADD) {
+ /* Here name isn't so much a name but a binary representation of the IP */
+ matched = !!memcmp(name, addr, namelen);
+ }
+ }
+ }
+ GENERAL_NAMES_free(alts);
+
+ if (matched == 0)
+ goto cert_fail;
+
+ if (matched == 1)
+ return 0;
+
+ /* If no alternative names are available, check the common name */
+ peer_name = X509_get_subject_name(cert);
+ if (peer_name == NULL)
+ goto on_error;
+
+ if (peer_name) {
+ /* Get the index of the last CN entry */
+ while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
+ i = j;
+ }
+
+ if (i < 0)
+ goto on_error;
+
+ str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
+ if (str == NULL)
+ goto on_error;
+
+ /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
+ if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
+ int size = ASN1_STRING_length(str);
+
+ if (size > 0) {
+ peer_cn = OPENSSL_malloc(size + 1);
+ GITERR_CHECK_ALLOC(peer_cn);
+ memcpy(peer_cn, ASN1_STRING_data(str), size);
+ peer_cn[size] = '\0';
+ }
+ } else {
+ int size = ASN1_STRING_to_UTF8(&peer_cn, str);
+ GITERR_CHECK_ALLOC(peer_cn);
+ if (memchr(peer_cn, '\0', size))
+ goto cert_fail;
+ }
+
+ if (check_host_name((char *)peer_cn, host) < 0)
+ goto cert_fail;
+
+ OPENSSL_free(peer_cn);
+
+ return 0;
+
+on_error:
+ OPENSSL_free(peer_cn);
+ return ssl_set_error(ssl, 0);
+
+cert_fail:
+ OPENSSL_free(peer_cn);
+ giterr_set(GITERR_SSL, "Certificate host name check failed");
+ return -1;
+}
+
+static int ssl_setup(gitno_socket *socket, const char *host, int flags)
+{
+ int ret;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
+ if (socket->ssl.ctx == NULL)
+ return ssl_set_error(&socket->ssl, 0);
+
+ SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
+ return ssl_set_error(&socket->ssl, 0);
+
+ socket->ssl.ssl = SSL_new(socket->ssl.ctx);
+ if (socket->ssl.ssl == NULL)
+ return ssl_set_error(&socket->ssl, 0);
+
+ if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
+ return ssl_set_error(&socket->ssl, ret);
+
+ if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
+ return ssl_set_error(&socket->ssl, ret);
+
+ if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
+ return 0;
+
+ return verify_server_cert(&socket->ssl, host);
+}
+#endif
+
+static int gitno__close(GIT_SOCKET s)
+{
+#ifdef GIT_WIN32
+ if (SOCKET_ERROR == closesocket(s))
+ return -1;
+
+ if (0 != WSACleanup()) {
+ giterr_set(GITERR_OS, "Winsock cleanup failed");
+ return -1;
+ }
+
+ return 0;
+#else
+ return close(s);
+#endif
+}
+
+int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
{
struct addrinfo *info = NULL, *p;
struct addrinfo hints;
- int ret;
GIT_SOCKET s = INVALID_SOCKET;
+ int ret;
+
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+
+ if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
+ WSACleanup();
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+#endif
+
+ /* Zero the socket structure provided */
+ memset(s_out, 0x0, sizeof(gitno_socket));
memset(&hints, 0x0, sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
- if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) {
- giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret));
+ if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
+ giterr_set(GITERR_NET,
+ "Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
return -1;
}
for (p = info; p != NULL; p = p->ai_next) {
s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+
if (s == INVALID_SOCKET) {
net_set_error("error creating socket");
break;
@@ -119,30 +479,67 @@ int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port)
break;
/* If we can't connect, try the next one */
- gitno_close(s);
+ gitno__close(s);
s = INVALID_SOCKET;
}
/* Oops, we couldn't connect to any address */
if (s == INVALID_SOCKET && p == NULL) {
giterr_set(GITERR_OS, "Failed to connect to %s", host);
+ p_freeaddrinfo(info);
+ return -1;
+ }
+
+ s_out->socket = s;
+ p_freeaddrinfo(info);
+
+#ifdef GIT_SSL
+ if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
+ return -1;
+#else
+ /* SSL is not supported */
+ if (flags & GITNO_CONNECT_SSL) {
+ giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
return -1;
}
+#endif
- freeaddrinfo(info);
- *sock = s;
return 0;
}
-int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags)
+#ifdef GIT_SSL
+static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
{
int ret;
size_t off = 0;
+ GIT_UNUSED(flags);
+
while (off < len) {
- errno = 0;
+ ret = SSL_write(ssl->ssl, msg + off, len - off);
+ if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
+ return ssl_set_error(ssl, ret);
+
+ off += ret;
+ }
+
+ return off;
+}
+#endif
+
+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
+{
+ int ret;
+ size_t off = 0;
+
+#ifdef GIT_SSL
+ if (socket->ssl.ctx)
+ return gitno_send_ssl(&socket->ssl, msg, len, flags);
+#endif
- ret = p_send(s, msg + off, len - off, flags);
+ while (off < len) {
+ errno = 0;
+ ret = p_send(socket->socket, msg + off, len - off, flags);
if (ret < 0) {
net_set_error("Error sending data");
return -1;
@@ -154,19 +551,17 @@ int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags)
return (int)off;
}
-
-#ifdef GIT_WIN32
-int gitno_close(GIT_SOCKET s)
-{
- return closesocket(s) == SOCKET_ERROR ? -1 : 0;
-}
-#else
-int gitno_close(GIT_SOCKET s)
+int gitno_close(gitno_socket *s)
{
- return close(s);
-}
+#ifdef GIT_SSL
+ if (s->ssl.ctx &&
+ gitno_ssl_teardown(&s->ssl) < 0)
+ return -1;
#endif
+ return gitno__close(s->socket);
+}
+
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
{
fd_set fds;
@@ -176,33 +571,60 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
tv.tv_usec = usec;
FD_ZERO(&fds);
- FD_SET(buf->fd, &fds);
+ FD_SET(buf->socket->socket, &fds);
/* The select(2) interface is silly */
- return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);
+ return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
}
-int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port)
{
- char *colon, *slash, *delim;
+ char *colon, *slash, *at, *end;
+ const char *start;
+
+ /*
+ *
+ * ==> [user[:pass]@]hostname.tld[:port]/resource
+ */
colon = strchr(url, ':');
slash = strchr(url, '/');
+ at = strchr(url, '@');
if (slash == NULL) {
giterr_set(GITERR_NET, "Malformed URL: missing /");
return -1;
}
+ start = url;
+ if (at && at < slash) {
+ start = at+1;
+ *username = git__substrdup(url, at - url);
+ }
+
+ if (colon && colon < at) {
+ git__free(*username);
+ *username = git__substrdup(url, colon-url);
+ *password = git__substrdup(colon+1, at-colon-1);
+ colon = strchr(at, ':');
+ }
+
if (colon == NULL) {
*port = git__strdup(default_port);
} else {
- *port = git__strndup(colon + 1, slash - colon - 1);
+ *port = git__substrdup(colon + 1, slash - colon - 1);
}
GITERR_CHECK_ALLOC(*port);
- delim = colon == NULL ? slash : colon;
- *host = git__strndup(url, delim - url);
+ end = colon == NULL ? slash : colon;
+
+ *host = git__substrdup(start, end - start);
GITERR_CHECK_ALLOC(*host);
return 0;
diff --git a/src/netops.h b/src/netops.h
index 9d13f3891..d352bf3b6 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,25 +8,70 @@
#define INCLUDE_netops_h__
#include "posix.h"
+#include "common.h"
-typedef struct gitno_buffer {
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+#endif
+
+struct gitno_ssl {
+#ifdef GIT_SSL
+ SSL_CTX *ctx;
+ SSL *ssl;
+#else
+ size_t dummy;
+#endif
+};
+
+typedef struct gitno_ssl gitno_ssl;
+
+/* Represents a socket that may or may not be using SSL */
+struct gitno_socket {
+ GIT_SOCKET socket;
+ gitno_ssl ssl;
+};
+
+typedef struct gitno_socket gitno_socket;
+
+struct gitno_buffer {
char *data;
size_t len;
size_t offset;
- GIT_SOCKET fd;
-} gitno_buffer;
+ gitno_socket *socket;
+ int (*recv)(struct gitno_buffer *buffer);
+ void *cb_data;
+};
+
+typedef struct gitno_buffer gitno_buffer;
+
+/* Flags to gitno_connect */
+enum {
+ /* Attempt to create an SSL connection. */
+ GITNO_CONNECT_SSL = 1,
-void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd);
+ /* Valid only when GITNO_CONNECT_SSL is also specified.
+ * Indicates that the server certificate should not be validated. */
+ GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
+};
+
+void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);
+void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
int gitno_recv(gitno_buffer *buf);
+
void gitno_consume(gitno_buffer *buf, const char *ptr);
void gitno_consume_n(gitno_buffer *buf, size_t cons);
-int gitno_connect(GIT_SOCKET *s, const char *host, const char *port);
-int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags);
-int gitno_close(GIT_SOCKET s);
-int gitno_send_chunk_size(int s, size_t len);
+int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags);
+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);
+int gitno_close(gitno_socket *s);
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec);
-int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port);
#endif
diff --git a/src/notes.c b/src/notes.c
index 84ad94087..ef48ac88e 100644
--- a/src/notes.c
+++ b/src/notes.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,56 +11,68 @@
#include "refs.h"
#include "config.h"
#include "iterator.h"
+#include "signature.h"
-static int find_subtree(git_tree **subtree, const git_oid *root,
- git_repository *repo, const char *target, int *fanout)
+static int find_subtree_in_current_level(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ const char *annotated_object_sha,
+ int fanout)
{
- int error;
- unsigned int i;
- git_tree *tree;
+ size_t i;
const git_tree_entry *entry;
- *subtree = NULL;
+ *out = NULL;
- error = git_tree_lookup(&tree, repo, root);
- if (error < 0)
- return error;
+ if (parent == NULL)
+ return GIT_ENOTFOUND;
- for (i=0; i<git_tree_entrycount(tree); i++) {
- entry = git_tree_entry_byindex(tree, i);
+ for (i = 0; i < git_tree_entrycount(parent); i++) {
+ entry = git_tree_entry_byindex(parent, i);
if (!git__ishex(git_tree_entry_name(entry)))
continue;
- /*
- * A notes tree follows a strict byte-based progressive fanout
- * (i.e. using 2/38, 2/2/36, etc. fanouts, not e.g. 4/36 fanout)
- */
-
- if (S_ISDIR(git_tree_entry_attributes(entry))
+ if (S_ISDIR(git_tree_entry_filemode(entry))
&& strlen(git_tree_entry_name(entry)) == 2
- && !strncmp(git_tree_entry_name(entry), target + *fanout, 2)) {
+ && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2))
+ return git_tree_lookup(out, repo, git_tree_entry_id(entry));
- /* found matching subtree - unpack and resume lookup */
+ /* Not a DIR, so do we have an already existing blob? */
+ if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout))
+ return GIT_EEXISTS;
+ }
- git_oid subtree_sha;
- git_oid_cpy(&subtree_sha, git_tree_entry_id(entry));
- git_tree_free(tree);
+ return GIT_ENOTFOUND;
+}
- *fanout += 2;
+static int find_subtree_r(git_tree **out, git_tree *root,
+ git_repository *repo, const char *target, int *fanout)
+{
+ int error;
+ git_tree *subtree = NULL;
- return find_subtree(subtree, &subtree_sha, repo,
- target, fanout);
- }
+ *out = NULL;
+
+ error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
+ if (error == GIT_EEXISTS) {
+ return git_tree_lookup(out, repo, git_tree_id(root));
}
- *subtree = tree;
- return 0;
+ if (error < 0)
+ return error;
+
+ *fanout += 2;
+ error = find_subtree_r(out, subtree, repo, target, fanout);
+ git_tree_free(subtree);
+
+ return error;
}
static int find_blob(git_oid *blob, git_tree *tree, const char *target)
{
- unsigned int i;
+ size_t i;
const git_tree_entry *entry;
for (i=0; i<git_tree_entrycount(tree); i++) {
@@ -76,191 +88,285 @@ static int find_blob(git_oid *blob, git_tree *tree, const char *target)
return GIT_ENOTFOUND;
}
-static int note_write(git_oid *out, git_repository *repo,
- git_signature *author, git_signature *committer,
- const char *notes_ref, const char *note,
- const git_oid *tree_sha, const char *target,
- int nparents, git_commit **parents)
+static int tree_write(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *source_tree,
+ const git_oid *object_oid,
+ const char *treeentry_name,
+ unsigned int attributes)
{
- int error, fanout = 0;
- git_oid oid;
- git_tree *tree = NULL;
- git_tree_entry *entry;
- git_treebuilder *tb;
-
- /* check for existing notes tree */
-
- if (tree_sha) {
- error = find_subtree(&tree, tree_sha, repo, target, &fanout);
- if (error < 0)
- return error;
-
- error = find_blob(&oid, tree, target + fanout);
- if (error != GIT_ENOTFOUND) {
- git_tree_free(tree);
- if (!error) {
- giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", target);
- error = GIT_EEXISTS;
- }
- return error;
- }
- }
+ int error;
+ git_treebuilder *tb = NULL;
+ const git_tree_entry *entry;
+ git_oid tree_oid;
- /* no matching tree entry - add note object to target tree */
+ if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
+ goto cleanup;
- error = git_treebuilder_create(&tb, tree);
- git_tree_free(tree);
+ if (object_oid) {
+ if ((error = git_treebuilder_insert(
+ &entry, tb, treeentry_name, object_oid, attributes)) < 0)
+ goto cleanup;
+ } else {
+ if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
+ goto cleanup;
+ }
- if (error < 0)
- return error;
+ if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
+ goto cleanup;
- if (!tree_sha)
- /* no notes tree yet - create fanout */
- fanout += 2;
+ error = git_tree_lookup(out, repo, &tree_oid);
- /* create note object */
- error = git_blob_create_frombuffer(&oid, repo, note, strlen(note));
- if (error < 0) {
- git_treebuilder_free(tb);
- return error;
- }
+cleanup:
+ git_treebuilder_free(tb);
+ return error;
+}
- error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644);
- if (error < 0) {
- /* libgit2 doesn't support object removal (gc) yet */
- /* we leave an orphaned blob object behind - TODO */
+static int manipulate_note_in_tree_r(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int (*note_exists_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error),
+ int (*note_notfound_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error))
+{
+ int error;
+ git_tree *subtree = NULL, *new = NULL;
+ char subtree_name[3];
- git_treebuilder_free(tb);
- return error;
- }
+ error = find_subtree_in_current_level(
+ &subtree, repo, parent, annotated_object_sha, fanout);
- if (out)
- git_oid_cpy(out, git_tree_entry_id(entry));
+ if (error == GIT_EEXISTS) {
+ error = note_exists_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
- error = git_treebuilder_write(&oid, repo, tb);
- git_treebuilder_free(tb);
+ if (error == GIT_ENOTFOUND) {
+ error = note_notfound_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
if (error < 0)
- return 0;
-
- if (!tree_sha) {
- /* create fanout subtree */
+ goto cleanup;
- char subtree[3];
- strncpy(subtree, target, 2);
- subtree[2] = '\0';
+ /* An existing fanout has been found, let's dig deeper */
+ error = manipulate_note_in_tree_r(
+ &new, repo, subtree, note_oid, annotated_object_sha,
+ fanout + 2, note_exists_cb, note_notfound_cb);
- error = git_treebuilder_create(&tb, NULL);
- if (error < 0)
- return error;
+ if (error < 0)
+ goto cleanup;
- error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000);
- if (error < 0) {
- git_treebuilder_free(tb);
- return error;
- }
+ strncpy(subtree_name, annotated_object_sha + fanout, 2);
+ subtree_name[2] = '\0';
- error = git_treebuilder_write(&oid, repo, tb);
+ error = tree_write(out, repo, parent, git_tree_id(new),
+ subtree_name, GIT_FILEMODE_TREE);
- git_treebuilder_free(tb);
- if (error < 0)
- return error;
- }
+cleanup:
+ git_tree_free(new);
+ git_tree_free(subtree);
+ return error;
+}
- /* create new notes commit */
+static int remove_note_in_tree_eexists_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(current_error);
- error = git_tree_lookup(&tree, repo, &oid);
- if (error < 0)
- return error;
+ return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
+}
- error = git_commit_create(&oid, repo, notes_ref, author, committer,
- NULL, GIT_NOTES_DEFAULT_MSG_ADD,
- tree, nparents, (const git_commit **) parents);
+static int remove_note_in_tree_enotfound_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
+ return current_error;
+}
- git_tree_free(tree);
+static int insert_note_in_tree_eexists_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
+ return current_error;
+}
- return error;
+static int insert_note_in_tree_enotfound_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(current_error);
+
+ /* No existing fanout at this level, insert in place */
+ return tree_write(
+ out,
+ repo,
+ parent,
+ note_oid,
+ annotated_object_sha + fanout,
+ GIT_FILEMODE_BLOB);
}
-static int note_lookup(git_note **out, git_repository *repo,
- const git_oid *tree_sha, const char *target)
+static int note_write(git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const char *note,
+ git_tree *commit_tree,
+ const char *target,
+ git_commit **parents,
+ int allow_note_overwrite)
{
- int error, fanout = 0;
+ int error;
git_oid oid;
- git_blob *blob;
- git_tree *tree;
- git_note *note;
+ git_tree *tree = NULL;
- error = find_subtree(&tree, tree_sha, repo, target, &fanout);
- if (error < 0)
- return error;
+ // TODO: should we apply filters?
+ /* create note object */
+ if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)
+ goto cleanup;
- error = find_blob(&oid, tree, target + fanout);
+ if ((error = manipulate_note_in_tree_r(
+ &tree, repo, commit_tree, &oid, target, 0,
+ allow_note_overwrite ? insert_note_in_tree_enotfound_cb : insert_note_in_tree_eexists_cb,
+ insert_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
+
+ if (out)
+ git_oid_cpy(out, &oid);
+
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_ADD,
+ tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
+cleanup:
git_tree_free(tree);
- if (error < 0)
- return error;
+ return error;
+}
- error = git_blob_lookup(&blob, repo, &oid);
- if (error < 0)
- return error;
+static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
+{
+ git_note *note = NULL;
- note = git__malloc(sizeof(git_note));
+ note = (git_note *)git__malloc(sizeof(git_note));
GITERR_CHECK_ALLOC(note);
- git_oid_cpy(&note->oid, &oid);
- note->message = git__strdup(git_blob_rawcontent(blob));
+ git_oid_cpy(&note->oid, note_oid);
+ note->message = git__strdup((char *)git_blob_rawcontent(blob));
GITERR_CHECK_ALLOC(note->message);
*out = note;
- git_blob_free(blob);
- return error;
+ return 0;
}
-static int note_remove(git_repository *repo,
- git_signature *author, git_signature *committer,
- const char *notes_ref, const git_oid *tree_sha,
- const char *target, int nparents, git_commit **parents)
+static int note_lookup(git_note **out, git_repository *repo,
+ git_tree *tree, const char *target)
{
int error, fanout = 0;
git_oid oid;
- git_tree *tree;
- git_treebuilder *tb;
+ git_blob *blob = NULL;
+ git_note *note = NULL;
+ git_tree *subtree = NULL;
- error = find_subtree(&tree, tree_sha, repo, target, &fanout);
- if (error < 0)
- return error;
+ if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)
+ goto cleanup;
- error = find_blob(&oid, tree, target + fanout);
- if (!error)
- error = git_treebuilder_create(&tb, tree);
+ if ((error = find_blob(&oid, subtree, target + fanout)) < 0)
+ goto cleanup;
- git_tree_free(tree);
- if (error < 0)
- return error;
+ if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)
+ goto cleanup;
- error = git_treebuilder_remove(tb, target + fanout);
- if (!error)
- error = git_treebuilder_write(&oid, repo, tb);
+ if ((error = note_new(&note, &oid, blob)) < 0)
+ goto cleanup;
- git_treebuilder_free(tb);
- if (error < 0)
- return error;
+ *out = note;
- /* create new notes commit */
+cleanup:
+ git_tree_free(subtree);
+ git_blob_free(blob);
+ return error;
+}
- error = git_tree_lookup(&tree, repo, &oid);
- if (error < 0)
- return error;
+static int note_remove(git_repository *repo,
+ const git_signature *author, const git_signature *committer,
+ const char *notes_ref, git_tree *tree,
+ const char *target, git_commit **parents)
+{
+ int error;
+ git_tree *tree_after_removal = NULL;
+ git_oid oid;
- error = git_commit_create(&oid, repo, notes_ref, author, committer,
- NULL, GIT_NOTES_DEFAULT_MSG_RM,
- tree, nparents, (const git_commit **) parents);
+ if ((error = manipulate_note_in_tree_r(
+ &tree_after_removal, repo, tree, NULL, target, 0,
+ remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
- git_tree_free(tree);
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_RM,
+ tree_after_removal,
+ *parents == NULL ? 0 : 1,
+ (const git_commit **) parents);
+cleanup:
+ git_tree_free(tree_after_removal);
return error;
}
@@ -291,134 +397,108 @@ static int normalize_namespace(const char **notes_ref, git_repository *repo)
return note_get_default_ref(notes_ref, repo);
}
-static int retrieve_note_tree_oid(git_oid *tree_oid_out, git_repository *repo, const char *notes_ref)
+static int retrieve_note_tree_and_commit(
+ git_tree **tree_out,
+ git_commit **commit_out,
+ git_repository *repo,
+ const char **notes_ref)
{
- int error = -1;
- git_commit *commit = NULL;
+ int error;
git_oid oid;
- if ((error = git_reference_name_to_oid(&oid, repo, notes_ref)) < 0)
- goto cleanup;
+ if ((error = normalize_namespace(notes_ref, repo)) < 0)
+ return error;
- if (git_commit_lookup(&commit, repo, &oid) < 0)
- goto cleanup;
+ if ((error = git_reference_name_to_id(&oid, repo, *notes_ref)) < 0)
+ return error;
- git_oid_cpy(tree_oid_out, git_commit_tree_oid(commit));
+ if (git_commit_lookup(commit_out, repo, &oid) < 0)
+ return error;
- error = 0;
+ if ((error = git_commit_tree(tree_out, *commit_out)) < 0)
+ return error;
-cleanup:
- git_commit_free(commit);
- return error;
+ return 0;
}
int git_note_read(git_note **out, git_repository *repo,
const char *notes_ref, const git_oid *oid)
{
int error;
- char *target;
- git_oid sha;
-
- *out = NULL;
-
- if (normalize_namespace(&notes_ref, repo) < 0)
- return -1;
-
- if ((error = retrieve_note_tree_oid(&sha, repo, notes_ref)) < 0)
- return error;
+ char *target = NULL;
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
target = git_oid_allocfmt(oid);
GITERR_CHECK_ALLOC(target);
- error = note_lookup(out, repo, &sha, target);
+ if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
+ goto cleanup;
+
+ error = note_lookup(out, repo, tree, target);
+cleanup:
git__free(target);
+ git_tree_free(tree);
+ git_commit_free(commit);
return error;
}
int git_note_create(
- git_oid *out, git_repository *repo,
- git_signature *author, git_signature *committer,
- const char *notes_ref, const git_oid *oid,
- const char *note)
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const git_oid *oid,
+ const char *note,
+ int allow_note_overwrite)
{
- int error, nparents = 0;
- char *target;
- git_oid sha;
+ int error;
+ char *target = NULL;
git_commit *commit = NULL;
- git_reference *ref;
-
- if (normalize_namespace(&notes_ref, repo) < 0)
- return -1;
-
- error = git_reference_lookup(&ref, repo, notes_ref);
- if (error < 0 && error != GIT_ENOTFOUND)
- return error;
-
- if (!error) {
- assert(git_reference_type(ref) == GIT_REF_OID);
-
- /* lookup existing notes tree oid */
-
- git_oid_cpy(&sha, git_reference_oid(ref));
- git_reference_free(ref);
-
- error = git_commit_lookup(&commit, repo, &sha);
- if (error < 0)
- return error;
-
- git_oid_cpy(&sha, git_commit_tree_oid(commit));
- nparents++;
- }
+ git_tree *tree = NULL;
target = git_oid_allocfmt(oid);
GITERR_CHECK_ALLOC(target);
+ error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
error = note_write(out, repo, author, committer, notes_ref,
- note, nparents ? &sha : NULL, target,
- nparents, &commit);
+ note, tree, target, &commit, allow_note_overwrite);
+cleanup:
git__free(target);
git_commit_free(commit);
+ git_tree_free(tree);
return error;
}
int git_note_remove(git_repository *repo, const char *notes_ref,
- git_signature *author, git_signature *committer,
- const git_oid *oid)
+ const git_signature *author, const git_signature *committer,
+ const git_oid *oid)
{
int error;
- char *target;
- git_oid sha;
- git_commit *commit;
- git_reference *ref;
-
- if (normalize_namespace(&notes_ref, repo) < 0)
- return -1;
-
- error = git_reference_lookup(&ref, repo, notes_ref);
- if (error < 0)
- return error;
-
- assert(git_reference_type(ref) == GIT_REF_OID);
-
- git_oid_cpy(&sha, git_reference_oid(ref));
- git_reference_free(ref);
-
- error = git_commit_lookup(&commit, repo, &sha);
- if (error < 0)
- return error;
-
- git_oid_cpy(&sha, git_commit_tree_oid(commit));
+ char *target = NULL;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
target = git_oid_allocfmt(oid);
GITERR_CHECK_ALLOC(target);
+ if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref)) < 0)
+ goto cleanup;
+
error = note_remove(repo, author, committer, notes_ref,
- &sha, target, 1, &commit);
+ tree, target, &commit);
+cleanup:
git__free(target);
git_commit_free(commit);
+ git_tree_free(tree);
return error;
}
@@ -428,13 +508,13 @@ int git_note_default_ref(const char **out, git_repository *repo)
return note_get_default_ref(out, repo);
}
-const char * git_note_message(git_note *note)
+const char * git_note_message(const git_note *note)
{
assert(note);
return note->message;
}
-const git_oid * git_note_oid(git_note *note)
+const git_oid * git_note_oid(const git_note *note)
{
assert(note);
return &note->oid;
@@ -451,17 +531,15 @@ void git_note_free(git_note *note)
static int process_entry_path(
const char* entry_path,
- const git_oid *note_oid,
- int (*note_cb)(git_note_data *note_data, void *payload),
- void *payload)
+ git_oid *annotated_object_id)
{
- int i = 0, j = 0, error = -1, len;
+ int error = -1;
+ size_t i = 0, j = 0, len;
git_buf buf = GIT_BUF_INIT;
- git_note_data note_data;
- if (git_buf_puts(&buf, entry_path) < 0)
+ if ((error = git_buf_puts(&buf, entry_path)) < 0)
goto cleanup;
-
+
len = git_buf_len(&buf);
while (i < len) {
@@ -469,10 +547,9 @@ static int process_entry_path(
i++;
continue;
}
-
+
if (git__fromhex(buf.ptr[i]) < 0) {
/* This is not a note entry */
- error = 0;
goto cleanup;
}
@@ -488,16 +565,10 @@ static int process_entry_path(
if (j != GIT_OID_HEXSZ) {
/* This is not a note entry */
- error = 0;
goto cleanup;
}
- if (git_oid_fromstr(&note_data.annotated_object_oid, buf.ptr) < 0)
- return -1;
-
- git_oid_cpy(&note_data.blob_oid, note_oid);
-
- error = note_cb(&note_data, payload);
+ error = git_oid_fromstr(annotated_object_id, buf.ptr);
cleanup:
git_buf_free(&buf);
@@ -505,44 +576,86 @@ cleanup:
}
int git_note_foreach(
- git_repository *repo,
- const char *notes_ref,
- int (*note_cb)(git_note_data *note_data, void *payload),
- void *payload)
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload)
{
- int error = -1;
- git_oid tree_oid;
- git_iterator *iter = NULL;
- git_tree *tree = NULL;
- const git_index_entry *item;
+ int error;
+ git_note_iterator *iter = NULL;
+ git_oid note_id, annotated_id;
- if (normalize_namespace(&notes_ref, repo) < 0)
- return -1;
+ if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)
+ return error;
- if ((error = retrieve_note_tree_oid(&tree_oid, repo, notes_ref)) < 0)
- goto cleanup;
+ while (!(error = git_note_next(&note_id, &annotated_id, iter))) {
+ if (note_cb(&note_id, &annotated_id, payload)) {
+ error = GIT_EUSER;
+ break;
+ }
+ }
- if (git_tree_lookup(&tree, repo, &tree_oid) < 0)
- goto cleanup;
+ if (error == GIT_ITEROVER)
+ error = 0;
- if (git_iterator_for_tree(&iter, repo, tree) < 0)
- goto cleanup;
+ git_note_iterator_free(iter);
+ return error;
+}
- if (git_iterator_current(iter, &item) < 0)
- goto cleanup;
- while (item) {
- if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0)
- goto cleanup;
+void git_note_iterator_free(git_note_iterator *it)
+{
+ if (it == NULL)
+ return;
- if (git_iterator_advance(iter, &item) < 0)
- goto cleanup;
- }
+ git_iterator_free(it);
+}
- error = 0;
+
+int git_note_iterator_new(
+ git_note_iterator **it,
+ git_repository *repo,
+ const char *notes_ref)
+{
+ int error;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0)
+ git_iterator_free(*it);
cleanup:
- git_iterator_free(iter);
git_tree_free(tree);
+ git_commit_free(commit);
+
+ return error;
+}
+
+int git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it)
+{
+ int error;
+ const git_index_entry *item;
+
+ if ((error = git_iterator_current(&item, it)) < 0)
+ goto exit;
+
+ if (item != NULL) {
+ git_oid_cpy(note_id, &item->oid);
+ error = process_entry_path(item->path, annotated_id);
+
+ if (error >= 0)
+ error = git_iterator_advance(NULL, it);
+ } else {
+ error = GIT_ITEROVER;
+ }
+
+exit:
return error;
}
diff --git a/src/notes.h b/src/notes.h
index 219db1ab0..39e18b621 100644
--- a/src/notes.h
+++ b/src/notes.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,6 +10,7 @@
#include "common.h"
#include "git2/oid.h"
+#include "git2/types.h"
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
diff --git a/src/object.c b/src/object.c
index d3673eda0..80fe51152 100644
--- a/src/object.c
+++ b/src/object.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -77,11 +77,63 @@ static int create_object(git_object **object_out, git_otype type)
return 0;
}
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type)
+{
+ int error;
+ 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");
+ return GIT_ENOTFOUND;
+ }
+
+ type = odb_obj->raw.type;
+
+ if ((error = create_object(&object, type)) < 0)
+ return error;
+
+ /* Initialize parent object */
+ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
+ object->repo = repo;
+
+ switch (type) {
+ case GIT_OBJ_COMMIT:
+ error = git_commit__parse((git_commit *)object, odb_obj);
+ break;
+
+ case GIT_OBJ_TREE:
+ error = git_tree__parse((git_tree *)object, odb_obj);
+ break;
+
+ case GIT_OBJ_TAG:
+ error = git_tag__parse((git_tag *)object, odb_obj);
+ break;
+
+ case GIT_OBJ_BLOB:
+ error = git_blob__parse((git_blob *)object, odb_obj);
+ break;
+
+ default:
+ break;
+ }
+
+ if (error < 0)
+ git_object__free(object);
+ else
+ *object_out = git_cache_try_store(&repo->objects, object);
+
+ return error;
+}
+
int git_object_lookup_prefix(
git_object **object_out,
git_repository *repo,
const git_oid *id,
- unsigned int len,
+ size_t len,
git_otype type)
{
git_object *object = NULL;
@@ -109,7 +161,7 @@ int git_object_lookup_prefix(
if (object != NULL) {
if (type != GIT_OBJ_ANY && type != object->type) {
git_object_free(object);
- giterr_set(GITERR_ODB, "The given type does not match the type in ODB");
+ giterr_set(GITERR_INVALID, "The requested type does not match the type in ODB");
return GIT_ENOTFOUND;
}
@@ -148,51 +200,11 @@ int git_object_lookup_prefix(
if (error < 0)
return error;
- if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {
- git_odb_object_free(odb_obj);
- giterr_set(GITERR_ODB, "The given type does not match the type on the ODB");
- return GIT_ENOTFOUND;
- }
-
- type = odb_obj->raw.type;
-
- if (create_object(&object, type) < 0)
- return -1;
-
- /* Initialize parent object */
- git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
- object->repo = repo;
-
- switch (type) {
- case GIT_OBJ_COMMIT:
- error = git_commit__parse((git_commit *)object, odb_obj);
- break;
-
- case GIT_OBJ_TREE:
- error = git_tree__parse((git_tree *)object, odb_obj);
- break;
-
- case GIT_OBJ_TAG:
- error = git_tag__parse((git_tag *)object, odb_obj);
- break;
-
- case GIT_OBJ_BLOB:
- error = git_blob__parse((git_blob *)object, odb_obj);
- break;
-
- default:
- break;
- }
+ error = git_object__from_odb_object(object_out, repo, odb_obj, type);
git_odb_object_free(odb_obj);
- if (error < 0) {
- git_object__free(object);
- return -1;
- }
-
- *object_out = git_cache_try_store(&repo->objects, object);
- return 0;
+ return error;
}
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
@@ -292,42 +304,101 @@ size_t git_object__size(git_otype type)
return git_objects_table[type].size;
}
-int git_object__resolve_to_type(git_object **obj, git_otype type)
+static int dereference_object(git_object **dereferenced, git_object *obj)
{
- int error = 0;
- git_object *scan, *next;
+ git_otype type = git_object_type(obj);
- if (type == GIT_OBJ_ANY)
- return 0;
+ switch (type) {
+ case GIT_OBJ_COMMIT:
+ return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);
- scan = *obj;
+ case GIT_OBJ_TAG:
+ return git_tag_target(dereferenced, (git_tag*)obj);
- while (!error && scan && git_object_type(scan) != type) {
+ case GIT_OBJ_BLOB:
+ return GIT_ENOTFOUND;
- switch (git_object_type(scan)) {
- case GIT_OBJ_COMMIT:
- {
- git_tree *tree = NULL;
- error = git_commit_tree(&tree, (git_commit *)scan);
- next = (git_object *)tree;
- break;
- }
+ case GIT_OBJ_TREE:
+ return GIT_EAMBIGUOUS;
- case GIT_OBJ_TAG:
- error = git_tag_target(&next, (git_tag *)scan);
- break;
+ default:
+ return GIT_EINVALIDSPEC;
+ }
+}
+
+static int peel_error(int error, const git_oid *oid, git_otype type)
+{
+ const char *type_name;
+ char hex_oid[GIT_OID_HEXSZ + 1];
+
+ type_name = git_object_type2string(type);
+
+ git_oid_fmt(hex_oid, oid);
+ hex_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "
+ "successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);
+
+ return error;
+}
+
+int git_object_peel(
+ git_object **peeled,
+ const git_object *object,
+ git_otype target_type)
+{
+ git_object *source, *deref = NULL;
+ int error;
+
+ if (target_type != GIT_OBJ_TAG &&
+ target_type != GIT_OBJ_COMMIT &&
+ target_type != GIT_OBJ_TREE &&
+ target_type != GIT_OBJ_BLOB &&
+ target_type != GIT_OBJ_ANY)
+ return GIT_EINVALIDSPEC;
+
+ assert(object && peeled);
+
+ if (git_object_type(object) == target_type)
+ return git_object_dup(peeled, (git_object *)object);
- default:
- giterr_set(GITERR_REFERENCE, "Object does not resolve to type");
- error = -1;
- next = NULL;
- break;
+ source = (git_object *)object;
+
+ while (!(error = dereference_object(&deref, source))) {
+
+ if (source != object)
+ git_object_free(source);
+
+ if (git_object_type(deref) == target_type) {
+ *peeled = deref;
+ return 0;
+ }
+
+ if (target_type == GIT_OBJ_ANY &&
+ git_object_type(deref) != git_object_type(object))
+ {
+ *peeled = deref;
+ return 0;
}
- git_object_free(scan);
- scan = next;
+ source = deref;
+ deref = NULL;
}
- *obj = scan;
+ if (source != object)
+ git_object_free(source);
+
+ git_object_free(deref);
+
+ if (error)
+ error = peel_error(error, git_object_id(object), target_type);
+
return error;
}
+
+int git_object_dup(git_object **dest, git_object *source)
+{
+ git_cached_obj_incref(source);
+ *dest = source;
+ return 0;
+}
diff --git a/src/object.h b/src/object.h
new file mode 100644
index 000000000..c1e50593c
--- /dev/null
+++ b/src/object.h
@@ -0,0 +1,32 @@
+/*
+ * 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_object_h__
+#define INCLUDE_object_h__
+
+/** Base git object for inheritance */
+struct git_object {
+ git_cached_obj cached;
+ git_repository *repo;
+ git_otype type;
+};
+
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_object__free(void *object);
+
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type);
+
+int git_object__resolve_to_type(git_object **obj, git_otype type);
+
+int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
+
+void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
+
+#endif
diff --git a/src/odb.c b/src/odb.c
index a6a18f831..c98df247c 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,8 +12,10 @@
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
+#include "filter.h"
#include "git2/odb_backend.h"
+#include "git2/oid.h"
#define GIT_ALTERNATES_FILE "info/alternates"
@@ -21,6 +23,8 @@
#define GIT_LOOSE_PRIORITY 2
#define GIT_PACKED_PRIORITY 1
+#define GIT_ALTERNATES_MAX_DEPTH 5
+
typedef struct
{
git_odb_backend *backend;
@@ -28,7 +32,11 @@ typedef struct
int is_alternate;
} backend_internal;
-static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
+size_t git_odb__cache_size = GIT_DEFAULT_CACHE_SIZE;
+
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+
+int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
{
const char *type_str = git_object_type2string(obj_type);
int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);
@@ -49,7 +57,7 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj)
if (!obj->data && obj->len != 0)
return -1;
- hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type);
+ hdrlen = git_odb__format_object_header(header, sizeof(header), obj->len, obj->type);
vec[0].data = header;
vec[0].len = hdrlen;
@@ -105,6 +113,9 @@ git_otype git_odb_object_type(git_odb_object *object)
void git_odb_object_free(git_odb_object *object)
{
+ if (object == NULL)
+ return;
+
git_cached_obj_decref((git_cached_obj *)object, &free_odb_object);
}
@@ -112,31 +123,73 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
{
int hdr_len;
char hdr[64], buffer[2048];
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
+ ssize_t read_len = 0;
+ int error = 0;
- hdr_len = format_object_header(hdr, sizeof(hdr), size, type);
+ if (!git_object_typeisloose(type)) {
+ giterr_set(GITERR_INVALID, "Invalid object type for hash");
+ return -1;
+ }
- ctx = git_hash_new_ctx();
+ if ((error = git_hash_ctx_init(&ctx)) < 0)
+ return -1;
- git_hash_update(ctx, hdr, hdr_len);
+ hdr_len = git_odb__format_object_header(hdr, sizeof(hdr), size, type);
- while (size > 0) {
- ssize_t read_len = read(fd, buffer, sizeof(buffer));
+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)
+ goto done;
- if (read_len < 0) {
- git_hash_free_ctx(ctx);
- giterr_set(GITERR_OS, "Error reading file");
- return -1;
- }
+ while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)
+ goto done;
- git_hash_update(ctx, buffer, read_len);
size -= read_len;
}
- git_hash_final(out, ctx);
- git_hash_free_ctx(ctx);
+ /* If p_read returned an error code, the read obviously failed.
+ * If size is not zero, the file was truncated after we originally
+ * stat'd it, so we consider this a read failure too */
+ if (read_len < 0 || size > 0) {
+ giterr_set(GITERR_OS, "Error reading file for hashing");
+ error = -1;
- return 0;
+ goto done;
+ return -1;
+ }
+
+ error = git_hash_final(out, &ctx);
+
+done:
+ git_hash_ctx_cleanup(&ctx);
+ return error;
+}
+
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters)
+{
+ int error;
+ git_buf raw = GIT_BUF_INIT;
+ git_buf filtered = GIT_BUF_INIT;
+
+ if (!filters || !filters->length)
+ return git_odb__hashfd(out, fd, size, type);
+
+ /* size of data is used in header, so we have to read the whole file
+ * into memory to apply filters before beginning to calculate the hash
+ */
+
+ if (!(error = git_futils_readbuffer_fd(&raw, fd, size)))
+ error = git_filters_apply(&filtered, &raw, filters);
+
+ git_buf_free(&raw);
+
+ if (!error)
+ error = git_odb_hash(out, filtered.ptr, filtered.size, type);
+
+ git_buf_free(&filtered);
+
+ return error;
}
int git_odb__hashlink(git_oid *out, const char *path)
@@ -159,10 +212,11 @@ int git_odb__hashlink(git_oid *out, const char *path)
char *link_data;
ssize_t read_len;
- link_data = git__malloc((size_t)size);
+ link_data = git__malloc((size_t)(size + 1));
GITERR_CHECK_ALLOC(link_data);
- read_len = p_readlink(path, link_data, (size_t)(size + 1));
+ read_len = p_readlink(path, link_data, (size_t)size);
+ link_data[size] = '\0';
if (read_len != (ssize_t)size) {
giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path);
return -1;
@@ -170,7 +224,7 @@ int git_odb__hashlink(git_oid *out, const char *path)
result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB);
git__free(link_data);
- } else {
+ } else {
int fd = git_futils_open_ro(path);
if (fd < 0)
return -1;
@@ -299,7 +353,7 @@ int git_odb_new(git_odb **out)
git_odb *db = git__calloc(1, sizeof(*db));
GITERR_CHECK_ALLOC(db);
- if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 ||
+ if (git_cache_init(&db->cache, git_odb__cache_size, &free_odb_object) < 0 ||
git_vector_init(&db->backends, 4, backend_sort_cmp) < 0)
{
git__free(db);
@@ -317,6 +371,8 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio
assert(odb && backend);
+ GITERR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend");
+
/* Check if the backend is already owned by another ODB */
assert(!backend->odb || backend->odb == odb);
@@ -347,7 +403,7 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
return add_backend_internal(odb, backend, priority, 1);
}
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates)
+static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth)
{
git_odb_backend *loose, *packed;
@@ -361,10 +417,10 @@ static int add_default_backends(git_odb *db, const char *objects_dir, int as_alt
add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0)
return -1;
- return 0;
+ return load_alternates(db, objects_dir, alternate_depth);
}
-static int load_alternates(git_odb *odb, const char *objects_dir)
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth)
{
git_buf alternates_path = GIT_BUF_INIT;
git_buf alternates_buf = GIT_BUF_INIT;
@@ -372,6 +428,11 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
const char *alternate;
int result = 0;
+ /* Git reports an error, we just ignore anything deeper */
+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) {
+ return 0;
+ }
+
if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
return -1;
@@ -392,14 +453,18 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
if (*alternate == '\0' || *alternate == '#')
continue;
- /* relative path: build based on the current `objects` folder */
- if (*alternate == '.') {
+ /*
+ * Relative path: build based on the current `objects`
+ * folder. However, relative paths are only allowed in
+ * the current repository.
+ */
+ if (*alternate == '.' && !alternate_depth) {
if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0)
break;
alternate = git_buf_cstr(&alternates_path);
}
- if ((result = add_default_backends(odb, alternate, 1)) < 0)
+ if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0)
break;
}
@@ -409,6 +474,11 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
return result;
}
+int git_odb_add_disk_alternate(git_odb *odb, const char *path)
+{
+ return add_default_backends(odb, path, 1, 0);
+}
+
int git_odb_open(git_odb **out, const char *objects_dir)
{
git_odb *db;
@@ -420,9 +490,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
if (git_odb_new(&db) < 0)
return -1;
- if (add_default_backends(db, objects_dir, 0) < 0 ||
- load_alternates(db, objects_dir) < 0)
- {
+ if (add_default_backends(db, objects_dir, 0, 0) < 0) {
git_odb_free(db);
return -1;
}
@@ -433,7 +501,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
static void odb_free(git_odb *db)
{
- unsigned int i;
+ size_t i;
for (i = 0; i < db->backends.length; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
@@ -461,8 +529,9 @@ void git_odb_free(git_odb *db)
int git_odb_exists(git_odb *db, const git_oid *id)
{
git_odb_object *object;
- unsigned int i;
+ size_t i;
bool found = false;
+ bool refreshed = false;
assert(db && id);
@@ -471,6 +540,7 @@ int git_odb_exists(git_odb *db, const git_oid *id)
return (int)true;
}
+attempt_lookup:
for (i = 0; i < db->backends.length && !found; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -479,24 +549,51 @@ int git_odb_exists(git_odb *db, const git_oid *id)
found = b->exists(b, id);
}
+ if (!found && !refreshed) {
+ if (git_odb_refresh(db) < 0) {
+ giterr_clear();
+ return (int)false;
+ }
+
+ refreshed = true;
+ goto attempt_lookup;
+ }
+
return (int)found;
}
int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)
{
- unsigned int i;
+ int error;
+ git_odb_object *object;
+
+ error = git_odb__read_header_or_object(&object, len_p, type_p, db, id);
+
+ if (object)
+ git_odb_object_free(object);
+
+ return error;
+}
+
+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)
+{
+ size_t i;
int error = GIT_ENOTFOUND;
git_odb_object *object;
- assert(db && id);
+ 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;
- git_odb_object_free(object);
+ *out = object;
return 0;
}
+ *out = NULL;
+
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -517,22 +614,32 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git
*len_p = object->raw.len;
*type_p = object->raw.type;
- git_odb_object_free(object);
+ *out = object;
+
return 0;
}
int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
{
- unsigned int i;
- int error = GIT_ENOTFOUND;
+ size_t i;
+ int error;
+ bool refreshed = false;
git_rawobj raw;
assert(out && db && id);
+ if (db->backends.length == 0) {
+ giterr_set(GITERR_ODB, "Failed to lookup object: no backends loaded");
+ return GIT_ENOTFOUND;
+ }
+
*out = git_cache_get(&db->cache, id);
if (*out != NULL)
return 0;
+attempt_lookup:
+ error = GIT_ENOTFOUND;
+
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -541,9 +648,13 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
error = b->read(&raw.data, &raw.len, &raw.type, b, id);
}
- /* TODO: If no backends are configured, this returns GIT_ENOTFOUND but
- * will never have called giterr_set().
- */
+ if (error == GIT_ENOTFOUND && !refreshed) {
+ if ((error = git_odb_refresh(db)) < 0)
+ return error;
+
+ refreshed = true;
+ goto attempt_lookup;
+ }
if (error && error != GIT_PASSTHROUGH)
return error;
@@ -553,13 +664,14 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
}
int git_odb_read_prefix(
- git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len)
+ git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len)
{
- unsigned int i;
+ size_t i;
int error = GIT_ENOTFOUND;
git_oid found_full_oid = {{0}};
git_rawobj raw;
- bool found = false;
+ void *data = NULL;
+ bool found = false, refreshed = false;
assert(out && db);
@@ -575,11 +687,12 @@ int git_odb_read_prefix(
return 0;
}
+attempt_lookup:
for (i = 0; i < db->backends.length; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->read != NULL) {
+ if (b->read_prefix != NULL) {
git_oid full_oid;
error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len);
if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
@@ -588,13 +701,25 @@ int git_odb_read_prefix(
if (error)
return error;
+ git__free(data);
+ data = raw.data;
+
if (found && git_oid_cmp(&full_oid, &found_full_oid))
return git_odb__error_ambiguous("multiple matches for prefix");
+
found_full_oid = full_oid;
found = true;
}
}
+ if (!found && !refreshed) {
+ if ((error = git_odb_refresh(db)) < 0)
+ return error;
+
+ refreshed = true;
+ goto attempt_lookup;
+ }
+
if (!found)
return git_odb__error_notfound("no match for prefix", short_id);
@@ -602,15 +727,34 @@ int git_odb_read_prefix(
return 0;
}
+int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload)
+{
+ unsigned int i;
+ backend_internal *internal;
+
+ git_vector_foreach(&db->backends, i, internal) {
+ git_odb_backend *b = internal->backend;
+ int error = b->foreach(b, cb, payload);
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
int git_odb_write(
git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
git_odb_stream *stream;
assert(oid && db);
+ git_odb_hash(oid, data, len, type);
+ if (git_odb_exists(db, oid))
+ return 0;
+
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -643,7 +787,7 @@ int git_odb_write(
int git_odb_open_wstream(
git_odb_stream **stream, git_odb *db, size_t size, git_otype type)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
assert(stream && db);
@@ -670,7 +814,7 @@ int git_odb_open_wstream(
int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid)
{
- unsigned int i;
+ size_t i;
int error = GIT_ERROR;
assert(stream && db);
@@ -689,6 +833,56 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi
return error;
}
+int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload)
+{
+ size_t i;
+ int error = GIT_ERROR;
+
+ assert(out && db);
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ /* we don't write in alternates! */
+ if (internal->is_alternate)
+ continue;
+
+ if (b->writepack != NULL)
+ error = b->writepack(out, b, progress_cb, progress_payload);
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+
+ return error;
+}
+
+void *git_odb_backend_malloc(git_odb_backend *backend, size_t len)
+{
+ GIT_UNUSED(backend);
+ return git__malloc(len);
+}
+
+int git_odb_refresh(struct git_odb *db)
+{
+ size_t i;
+ assert(db);
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (b->refresh != NULL) {
+ int error = b->refresh(b);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return 0;
+}
+
int git_odb__error_notfound(const char *message, const git_oid *oid)
{
if (oid != NULL) {
diff --git a/src/odb.h b/src/odb.h
index 263e4c30b..7c018cc50 100644
--- a/src/odb.h
+++ b/src/odb.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -46,6 +46,10 @@ struct git_odb {
int git_odb__hashobj(git_oid *id, git_rawobj *obj);
/*
+ * Format the object header such as it would appear in the on-disk object
+ */
+int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type);
+/*
* Hash an open file descriptor.
* This is a performance call when the contents of a fd need to be hashed,
* but the fd is already open and we have the size of the contents.
@@ -58,12 +62,19 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj);
int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type);
/*
- * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink,
- * then the raw contents of the symlink will be hashed. Otherwise, this will
- * fallback to `git_odb__hashfd`.
+ * Hash an open file descriptor applying an array of filters
+ * Acts just like git_odb__hashfd with the addition of filters...
+ */
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters);
+
+/*
+ * Hash a `path`, assuming it could be a POSIX symlink: if the path is a
+ * symlink, then the raw contents of the symlink will be hashed. Otherwise,
+ * this will fallback to `git_odb__hashfd`.
*
- * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only
- * point to blobs.
+ * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may
+ * only point to blobs.
*/
int git_odb__hashlink(git_oid *out, const char *path);
@@ -77,4 +88,12 @@ int git_odb__error_notfound(const char *message, const git_oid *oid);
*/
int git_odb__error_ambiguous(const char *message);
+/*
+ * Attempt to read object header or just return whole object if it could
+ * not be read.
+ */
+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);
+
#endif
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 989b03ab2..68083f7fd 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -42,7 +42,7 @@ typedef struct loose_backend {
typedef struct {
size_t dir_len;
unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */
- unsigned int short_oid_len;
+ size_t short_oid_len;
int found; /* number of matching
* objects already found */
unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of
@@ -502,7 +502,7 @@ static int locate_object_short_oid(
git_oid *res_oid,
loose_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
char *objects_dir = backend->objects_dir;
size_t dir_len = strlen(objects_dir);
@@ -629,7 +629,7 @@ static int loose_backend__read_prefix(
git_otype *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
int error = 0;
@@ -676,6 +676,91 @@ static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
return !error;
}
+struct foreach_state {
+ size_t dir_len;
+ git_odb_foreach_cb cb;
+ void *data;
+ int cb_error;
+};
+
+GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
+{
+ int v, i = 0;
+ if (strlen(ptr) != 41)
+ return -1;
+
+ if (ptr[2] != '/') {
+ return -1;
+ }
+
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[0] = (unsigned char) v;
+
+ ptr += 3;
+ for (i = 0; i < 38; i += 2) {
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[1 + i/2] = (unsigned char) v;
+ }
+
+ return 0;
+}
+
+static int foreach_object_dir_cb(void *_state, git_buf *path)
+{
+ git_oid oid;
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)
+ return 0;
+
+ if (state->cb(&oid, state->data)) {
+ state->cb_error = GIT_EUSER;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int foreach_cb(void *_state, git_buf *path)
+{
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ return git_path_direach(path, foreach_object_dir_cb, state);
+}
+
+static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
+{
+ char *objects_dir;
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ struct foreach_state state;
+ loose_backend *backend = (loose_backend *) _backend;
+
+ assert(backend && cb);
+
+ objects_dir = backend->objects_dir;
+
+ git_buf_sets(&buf, objects_dir);
+ git_path_to_dir(&buf);
+
+ memset(&state, 0, sizeof(state));
+ state.cb = cb;
+ state.data = data;
+ state.dir_len = git_buf_len(&buf);
+
+ error = git_path_direach(&buf, foreach_cb, &state);
+
+ git_buf_free(&buf);
+
+ return state.cb_error ? state.cb_error : error;
+}
+
static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream)
{
loose_writestream *stream = (loose_writestream *)_stream;
@@ -785,7 +870,6 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v
if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
git_filebuf_open(&fbuf, final_path.ptr,
- GIT_FILEBUF_HASH_CONTENTS |
GIT_FILEBUF_TEMPORARY |
(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0)
{
@@ -795,7 +879,6 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v
git_filebuf_write(&fbuf, header, header_len);
git_filebuf_write(&fbuf, data, len);
- git_filebuf_hash(oid, &fbuf);
if (object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 ||
@@ -830,6 +913,7 @@ int git_odb_backend_loose(
backend = git__calloc(1, sizeof(loose_backend));
GITERR_CHECK_ALLOC(backend);
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
backend->objects_dir = git__strdup(objects_dir);
GITERR_CHECK_ALLOC(backend->objects_dir);
@@ -845,6 +929,7 @@ int git_odb_backend_loose(
backend->parent.read_header = &loose_backend__read_header;
backend->parent.writestream = &loose_backend__stream;
backend->parent.exists = &loose_backend__exists;
+ backend->parent.foreach = &loose_backend__foreach;
backend->parent.free = &loose_backend__free;
*backend_out = (git_odb_backend *)backend;
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 458f288d9..7240a4ac7 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -24,7 +24,11 @@ struct pack_backend {
git_vector packs;
struct git_pack_file *last_found;
char *pack_folder;
- time_t pack_folder_mtime;
+};
+
+struct pack_writepack {
+ struct git_odb_writepack parent;
+ git_indexer_stream *indexer_stream;
};
/**
@@ -128,13 +132,9 @@ struct pack_backend {
*
***********************************************************/
-static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p);
-static int pack_window_contains(git_mwindow *win, off_t offset);
-
static int packfile_sort__cb(const void *a_, const void *b_);
static int packfile_load__cb(void *_data, git_buf *path);
-static int packfile_refresh_all(struct pack_backend *backend);
static int pack_entry_find(struct git_pack_entry *e,
struct pack_backend *backend, const git_oid *oid);
@@ -149,7 +149,7 @@ static int pack_entry_find_prefix(
struct git_pack_entry *e,
struct pack_backend *backend,
const git_oid *short_oid,
- unsigned int len);
+ size_t len);
@@ -159,23 +159,6 @@ static int pack_entry_find_prefix(
*
***********************************************************/
-GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p)
-{
- GIT_UNUSED(backend);
- git_mwindow_free_all(&p->mwf);
-}
-
-GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset)
-{
- /* We must promise at least 20 bytes (one hash) after the
- * offset is available from this window, otherwise the offset
- * is not actually in this window and a different window (which
- * has that one hash excess) must be used. This is to support
- * the object header and delta base parsing routines below.
- */
- return git_mwindow_contains(win, offset + 20);
-}
-
static int packfile_sort__cb(const void *a_, const void *b_)
{
const struct git_pack_file *a = a_;
@@ -212,7 +195,7 @@ static int packfile_load__cb(void *_data, git_buf *path)
struct pack_backend *backend = (struct pack_backend *)_data;
struct git_pack_file *pack;
int error;
- unsigned int i;
+ size_t i;
if (git__suffixcmp(path->ptr, ".idx") != 0)
return 0; /* not an index */
@@ -233,79 +216,61 @@ static int packfile_load__cb(void *_data, git_buf *path)
return git_vector_insert(&backend->packs, pack);
}
-static int packfile_refresh_all(struct pack_backend *backend)
+static int pack_entry_find_inner(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *oid,
+ struct git_pack_file *last_found)
{
- int error;
- struct stat st;
+ size_t i;
- if (backend->pack_folder == NULL)
+ if (last_found &&
+ git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0)
return 0;
- if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
- return git_odb__error_notfound("failed to refresh packfiles", NULL);
-
- if (st.st_mtime != backend->pack_folder_mtime) {
- git_buf path = GIT_BUF_INIT;
- git_buf_sets(&path, backend->pack_folder);
-
- /* reload all packs */
- error = git_path_direach(&path, packfile_load__cb, (void *)backend);
-
- git_buf_free(&path);
+ for (i = 0; i < backend->packs.length; ++i) {
+ struct git_pack_file *p;
- if (error < 0)
- return error;
+ p = git_vector_get(&backend->packs, i);
+ if (p == last_found)
+ continue;
- git_vector_sort(&backend->packs);
- backend->pack_folder_mtime = st.st_mtime;
+ if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {
+ backend->last_found = p;
+ return 0;
+ }
}
- return 0;
+ return -1;
}
static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid)
{
- int error;
- unsigned int i;
-
- if ((error = packfile_refresh_all(backend)) < 0)
- return error;
+ struct git_pack_file *last_found = backend->last_found;
if (backend->last_found &&
git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0)
return 0;
- for (i = 0; i < backend->packs.length; ++i) {
- struct git_pack_file *p;
-
- p = git_vector_get(&backend->packs, i);
- if (p == backend->last_found)
- continue;
-
- if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {
- backend->last_found = p;
- return 0;
- }
- }
+ if (!pack_entry_find_inner(e, backend, oid, last_found))
+ return 0;
return git_odb__error_notfound("failed to find pack entry", oid);
}
-static int pack_entry_find_prefix(
- struct git_pack_entry *e,
- struct pack_backend *backend,
- const git_oid *short_oid,
- unsigned int len)
+static unsigned pack_entry_find_prefix_inner(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len,
+ struct git_pack_file *last_found)
{
int error;
- unsigned int i;
+ size_t i;
unsigned found = 0;
- if ((error = packfile_refresh_all(backend)) < 0)
- return error;
-
- if (backend->last_found) {
- error = git_pack_entry_find(e, backend->last_found, short_oid, len);
+ if (last_found) {
+ error = git_pack_entry_find(e, last_found, short_oid, len);
if (error == GIT_EAMBIGUOUS)
return error;
if (!error)
@@ -316,7 +281,7 @@ static int pack_entry_find_prefix(
struct git_pack_file *p;
p = git_vector_get(&backend->packs, i);
- if (p == backend->last_found)
+ if (p == last_found)
continue;
error = git_pack_entry_find(e, p, short_oid, len);
@@ -329,6 +294,18 @@ static int pack_entry_find_prefix(
}
}
+ return found;
+}
+
+static int pack_entry_find_prefix(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ struct git_pack_file *last_found = backend->last_found;
+ unsigned int found = pack_entry_find_prefix_inner(e, backend, short_oid, len, last_found);
+
if (!found)
return git_odb__error_notfound("no matching pack entry for prefix", short_oid);
else if (found > 1)
@@ -345,20 +322,47 @@ static int pack_entry_find_prefix(
* Implement the git_odb_backend API calls
*
***********************************************************/
+static int pack_backend__refresh(git_odb_backend *_backend)
+{
+ struct pack_backend *backend = (struct pack_backend *)_backend;
-/*
-int pack_backend__read_header(git_rawobj *obj, git_odb_backend *backend, const git_oid *oid)
+ int error;
+ struct stat st;
+ git_buf path = GIT_BUF_INIT;
+
+ if (backend->pack_folder == NULL)
+ return 0;
+
+ if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
+ return git_odb__error_notfound("failed to refresh packfiles", NULL);
+
+ git_buf_sets(&path, backend->pack_folder);
+
+ /* reload all packs */
+ error = git_path_direach(&path, packfile_load__cb, (void *)backend);
+
+ git_buf_free(&path);
+
+ if (error < 0)
+ return error;
+
+ git_vector_sort(&backend->packs);
+ return 0;
+}
+
+
+static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid)
{
- pack_location location;
+ struct git_pack_entry e;
+ int error;
- assert(obj && backend && oid);
+ assert(len_p && type_p && backend && oid);
- if (locate_packfile(&location, (struct pack_backend *)backend, oid) < 0)
- return GIT_ENOTFOUND;
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
+ return error;
- return read_header_packed(obj, &location);
+ return git_packfile_resolve_header(len_p, type_p, e.p, e.offset);
}
-*/
static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
{
@@ -384,7 +388,7 @@ static int pack_backend__read_prefix(
git_otype *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
int error = 0;
@@ -420,18 +424,101 @@ static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0;
}
-static void pack_backend__free(git_odb_backend *_backend)
+static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
{
+ int error;
+ struct git_pack_file *p;
struct pack_backend *backend;
unsigned int i;
+ assert(_backend && cb);
+ backend = (struct pack_backend *)_backend;
+
+ /* Make sure we know about the packfiles */
+ if ((error = pack_backend__refresh(_backend)) < 0)
+ return error;
+
+ git_vector_foreach(&backend->packs, i, p) {
+ if ((error = git_pack_foreach_entry(p, cb, data)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int pack_backend__writepack_add(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_stream_add(writepack->indexer_stream, data, size, stats);
+}
+
+static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_stream_finalize(writepack->indexer_stream, stats);
+}
+
+static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ git_indexer_stream_free(writepack->indexer_stream);
+ git__free(writepack);
+}
+
+static int pack_backend__writepack(struct git_odb_writepack **out,
+ git_odb_backend *_backend,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ struct pack_backend *backend;
+ struct pack_writepack *writepack;
+
+ assert(out && _backend);
+
+ *out = NULL;
+
+ backend = (struct pack_backend *)_backend;
+
+ writepack = git__calloc(1, sizeof(struct pack_writepack));
+ GITERR_CHECK_ALLOC(writepack);
+
+ if (git_indexer_stream_new(&writepack->indexer_stream,
+ backend->pack_folder, progress_cb, progress_payload) < 0) {
+ git__free(writepack);
+ return -1;
+ }
+
+ writepack->parent.backend = _backend;
+ writepack->parent.add = pack_backend__writepack_add;
+ writepack->parent.commit = pack_backend__writepack_commit;
+ writepack->parent.free = pack_backend__writepack_free;
+
+ *out = (git_odb_writepack *)writepack;
+
+ return 0;
+}
+
+static void pack_backend__free(git_odb_backend *_backend)
+{
+ struct pack_backend *backend;
+ size_t i;
+
assert(_backend);
backend = (struct pack_backend *)_backend;
for (i = 0; i < backend->packs.length; ++i) {
struct git_pack_file *p = git_vector_get(&backend->packs, i);
- packfile_free(p);
+ git_packfile_free(p);
}
git_vector_free(&backend->packs);
@@ -439,6 +526,43 @@ 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)
+{
+ struct pack_backend *backend = NULL;
+ struct git_pack_file *packfile = NULL;
+
+ if (git_packfile_check(&packfile, idx) < 0)
+ 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.free = &pack_backend__free;
+
+ *backend_out = (git_odb_backend *)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)
{
struct pack_backend *backend = NULL;
@@ -446,6 +570,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
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, 8, packfile_sort__cb) < 0 ||
git_buf_joinpath(&path, objects_dir, "pack") < 0)
@@ -455,14 +580,21 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
}
if (git_path_isdir(git_buf_cstr(&path)) == true) {
+ int error;
+
backend->pack_folder = git_buf_detach(&path);
- backend->pack_folder_mtime = 0;
+ 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 = NULL;
+ 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;
diff --git a/src/offmap.h b/src/offmap.h
new file mode 100644
index 000000000..cd46fd687
--- /dev/null
+++ b/src/offmap.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 the libgit2 contributors
+ *
+ * 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_offmap_h__
+#define INCLUDE_offmap_h__
+
+#include "common.h"
+#include "git2/types.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(off, git_off_t, void *);
+typedef khash_t(off) git_offmap;
+
+#define GIT__USE_OFFMAP \
+ __KHASH_IMPL(off, static kh_inline, git_off_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal);
+
+#define git_offmap_alloc() kh_init(off)
+#define git_offmap_free(h) kh_destroy(off, h), h = NULL
+#define git_offmap_clear(h) kh_clear(off, h)
+
+#define git_offmap_num_entries(h) kh_size(h)
+
+#define git_offmap_lookup_index(h, k) kh_get(off, h, k)
+#define git_offmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_offmap_exists(h, k) (kh_get(off, h, k) != kh_end(h))
+
+#define git_offmap_value_at(h, idx) kh_val(h, idx)
+#define git_offmap_set_value_at(h, idx, v) kh_val(h, idx) = v
+#define git_offmap_delete_at(h, idx) kh_del(off, h, idx)
+
+#define git_offmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_insert2(h, key, val, oldv, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) { \
+ oldv = kh_val(h, __pos); \
+ kh_key(h, __pos) = key; \
+ } else { oldv = NULL; } \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_delete(h, key) do { \
+ khiter_t __pos = git_offmap_lookup_index(h, key); \
+ if (git_offmap_valid_index(h, __pos)) \
+ git_offmap_delete_at(h, __pos); } while (0)
+
+#define git_offmap_foreach kh_foreach
+#define git_offmap_foreach_value kh_foreach_value
+
+#endif
diff --git a/src/oid.c b/src/oid.c
index 87756010b..ab69eeb17 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -24,11 +24,8 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
size_t p;
int v;
- if (length < 4)
- return oid_error_invalid("input too short");
-
if (length > GIT_OID_HEXSZ)
- length = GIT_OID_HEXSZ;
+ return oid_error_invalid("too long");
for (p = 0; p < length - 1; p += 2) {
v = (git__fromhex(str[p + 0]) << 4)
@@ -54,6 +51,11 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
return 0;
}
+int git_oid_fromstrp(git_oid *out, const char *str)
+{
+ return git_oid_fromstrn(out, str, strlen(str));
+}
+
int git_oid_fromstr(git_oid *out, const char *str)
{
return git_oid_fromstrn(out, str, GIT_OID_HEXSZ);
@@ -98,11 +100,14 @@ char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
{
char str[GIT_OID_HEXSZ];
- if (!out || n == 0 || !oid)
+ if (!out || n == 0)
return "";
n--; /* allow room for terminating NUL */
+ if (oid == NULL)
+ n = 0;
+
if (n > 0) {
git_oid_fmt(str, oid);
if (n > GIT_OID_HEXSZ)
@@ -161,12 +166,7 @@ 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 memcmp(a->id, b->id, sizeof(a->id));
-}
-
-int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len)
+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;
@@ -301,7 +301,7 @@ void git_oid_shorten_free(git_oid_shorten *os)
* - Each normal node points to 16 children (one for each possible
* character in the oid). This is *not* stored in an array of
* pointers, because in a 64-bit arch this would be sucking
- * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking
+ * 16*sizeof(void*) = 128 bytes of memory per node, which is
* insane. What we do is store Node Indexes, and use these indexes
* to look up each node in the om->index array. These indexes are
* signed shorts, so this limits the amount of unique OIDs that
diff --git a/src/oidmap.h b/src/oidmap.h
index 858268c92..40274cd19 100644
--- a/src/oidmap.h
+++ b/src/oidmap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * 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.
@@ -28,13 +28,8 @@ GIT_INLINE(khint_t) hash_git_oid(const git_oid *oid)
return h;
}
-GIT_INLINE(int) hash_git_oid_equal(const git_oid *a, const git_oid *b)
-{
- return (memcmp(a->id, b->id, sizeof(a->id)) == 0);
-}
-
#define GIT__USE_OIDMAP \
- __KHASH_IMPL(oid, static inline, const git_oid *, void *, 1, hash_git_oid, hash_git_oid_equal)
+ __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, hash_git_oid, 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
new file mode 100644
index 000000000..459201f58
--- /dev/null
+++ b/src/pack-objects.c
@@ -0,0 +1,1342 @@
+/*
+ * 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 "pack-objects.h"
+
+#include "compress.h"
+#include "delta.h"
+#include "iterator.h"
+#include "netops.h"
+#include "pack.h"
+#include "thread-utils.h"
+#include "tree.h"
+
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/tag.h"
+#include "git2/indexer.h"
+#include "git2/config.h"
+
+struct unpacked {
+ git_pobject *object;
+ void *data;
+ struct git_delta_index *index;
+ unsigned int depth;
+};
+
+struct tree_walk_context {
+ git_packbuilder *pb;
+ git_buf buf;
+};
+
+#ifdef GIT_THREADS
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) do { \
+ int result = git_mutex_##op(&(pb)->mtx); \
+ assert(!result); \
+ GIT_UNUSED(result); \
+ } while (0)
+
+#else
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb,mtx,op) GIT_UNUSED(pb)
+
+#endif /* GIT_THREADS */
+
+#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock)
+#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock)
+#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock)
+#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock)
+
+static unsigned name_hash(const char *name)
+{
+ unsigned c, hash = 0;
+
+ if (!name)
+ return 0;
+
+ /*
+ * This effectively just creates a sortable number from the
+ * last sixteen non-whitespace characters. Last characters
+ * count "most", so things that end in ".c" sort together.
+ */
+ while ((c = *name++) != 0) {
+ if (git__isspace(c))
+ continue;
+ hash = (hash >> 2) + (c << 24);
+ }
+ return hash;
+}
+
+static int packbuilder_config(git_packbuilder *pb)
+{
+ git_config *config;
+ int ret;
+ int64_t val;
+
+ if (git_repository_config__weakptr(&config, pb->repo) < 0)
+ return -1;
+
+#define config_get(KEY,DST,DFLT) do { \
+ ret = git_config_get_int64(&val, config, KEY); \
+ if (!ret) (DST) = val; \
+ else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \
+ else if (ret < 0) return -1; } while (0)
+
+ config_get("pack.deltaCacheSize", pb->max_delta_cache_size,
+ GIT_PACK_DELTA_CACHE_SIZE);
+ config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size,
+ GIT_PACK_DELTA_CACHE_LIMIT);
+ config_get("pack.deltaCacheSize", pb->big_file_threshold,
+ GIT_PACK_BIG_FILE_THRESHOLD);
+ config_get("pack.windowMemory", pb->window_memory_limit, 0);
+
+#undef config_get
+
+ return 0;
+}
+
+int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
+{
+ git_packbuilder *pb;
+
+ *out = NULL;
+
+ pb = git__calloc(1, sizeof(*pb));
+ GITERR_CHECK_ALLOC(pb);
+
+ pb->object_ix = git_oidmap_alloc();
+
+ if (!pb->object_ix)
+ goto on_error;
+
+ pb->repo = repo;
+ pb->nr_threads = 1; /* do not spawn any thread by default */
+
+ if (git_hash_ctx_init(&pb->ctx) < 0 ||
+ git_repository_odb(&pb->odb, repo) < 0 ||
+ packbuilder_config(pb) < 0)
+ goto on_error;
+
+#ifdef GIT_THREADS
+
+ if (git_mutex_init(&pb->cache_mutex) ||
+ git_mutex_init(&pb->progress_mutex) ||
+ git_cond_init(&pb->progress_cond))
+ goto on_error;
+
+#endif
+
+ *out = pb;
+ return 0;
+
+on_error:
+ git_packbuilder_free(pb);
+ return -1;
+}
+
+unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n)
+{
+ assert(pb);
+
+#ifdef GIT_THREADS
+ pb->nr_threads = n;
+#else
+ GIT_UNUSED(n);
+ assert(1 == pb->nr_threads);
+#endif
+
+ return pb->nr_threads;
+}
+
+static void rehash(git_packbuilder *pb)
+{
+ git_pobject *po;
+ khiter_t pos;
+ unsigned int i;
+ int ret;
+
+ kh_clear(oid, pb->object_ix);
+ for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) {
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ kh_value(pb->object_ix, pos) = po;
+ }
+}
+
+int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid,
+ const char *name)
+{
+ git_pobject *po;
+ khiter_t pos;
+ int ret;
+
+ assert(pb && oid);
+
+ /* If the object already exists in the hash table, then we don't
+ * have any work to do */
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos != kh_end(pb->object_ix))
+ return 0;
+
+ if (pb->nr_objects >= pb->nr_alloc) {
+ pb->nr_alloc = (pb->nr_alloc + 1024) * 3 / 2;
+ pb->object_list = git__realloc(pb->object_list,
+ pb->nr_alloc * sizeof(*po));
+ GITERR_CHECK_ALLOC(pb->object_list);
+ rehash(pb);
+ }
+
+ po = pb->object_list + pb->nr_objects;
+ memset(po, 0x0, sizeof(*po));
+
+ if (git_odb_read_header(&po->size, &po->type, pb->odb, oid) < 0)
+ return -1;
+
+ pb->nr_objects++;
+ git_oid_cpy(&po->id, oid);
+ po->hash = name_hash(name);
+
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ assert(ret != 0);
+ kh_value(pb->object_ix, pos) = po;
+
+ pb->done = false;
+ return 0;
+}
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ * - first byte: low four bits are "size",
+ * then three bits of "type",
+ * with the high bit being "size continues".
+ * - each byte afterwards: low seven bits are size continuation,
+ * with the high bit being "size continues"
+ */
+static int gen_pack_object_header(
+ unsigned char *hdr,
+ unsigned long size,
+ git_otype type)
+{
+ unsigned char *hdr_base;
+ unsigned char c;
+
+ assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA);
+
+ /* TODO: add support for chunked objects; see git.git 6c0d19b1 */
+
+ c = (unsigned char)((type << 4) | (size & 15));
+ size >>= 4;
+ hdr_base = hdr;
+
+ while (size) {
+ *hdr++ = c | 0x80;
+ c = size & 0x7f;
+ size >>= 7;
+ }
+ *hdr++ = c;
+
+ return (int)(hdr - hdr_base);
+}
+
+static int get_delta(void **out, git_odb *odb, git_pobject *po)
+{
+ git_odb_object *src = NULL, *trg = NULL;
+ unsigned long delta_size;
+ void *delta_buf;
+
+ *out = NULL;
+
+ if (git_odb_read(&src, odb, &po->delta->id) < 0 ||
+ git_odb_read(&trg, odb, &po->id) < 0)
+ goto on_error;
+
+ delta_buf = git_delta(
+ git_odb_object_data(src), (unsigned long)git_odb_object_size(src),
+ git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg),
+ &delta_size, 0);
+
+ if (!delta_buf || delta_size != po->delta_size) {
+ giterr_set(GITERR_INVALID, "Delta size changed");
+ goto on_error;
+ }
+
+ *out = delta_buf;
+
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return 0;
+
+on_error:
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return -1;
+}
+
+static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)
+{
+ git_odb_object *obj = NULL;
+ git_buf zbuf = GIT_BUF_INIT;
+ git_otype type;
+ unsigned char hdr[10];
+ unsigned int hdr_len;
+ unsigned long size;
+ void *data;
+
+ if (po->delta) {
+ if (po->delta_data)
+ data = po->delta_data;
+ else if (get_delta(&data, pb->odb, po) < 0)
+ goto on_error;
+ size = po->delta_size;
+ type = GIT_OBJ_REF_DELTA;
+ } else {
+ if (git_odb_read(&obj, pb->odb, &po->id))
+ goto on_error;
+
+ data = (void *)git_odb_object_data(obj);
+ size = (unsigned long)git_odb_object_size(obj);
+ type = git_odb_object_type(obj);
+ }
+
+ /* Write header */
+ hdr_len = gen_pack_object_header(hdr, size, type);
+
+ if (git_buf_put(buf, (char *)hdr, hdr_len) < 0)
+ goto on_error;
+
+ if (git_hash_update(&pb->ctx, hdr, hdr_len) < 0)
+ goto on_error;
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ if (git_buf_put(buf, (char *)po->delta->id.id, GIT_OID_RAWSZ) < 0 ||
+ git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ) < 0)
+ goto on_error;
+ }
+
+ /* Write data */
+ if (po->z_delta_size)
+ size = po->z_delta_size;
+ else if (git__compress(&zbuf, data, size) < 0)
+ goto on_error;
+ else {
+ if (po->delta)
+ git__free(data);
+ data = zbuf.ptr;
+ size = (unsigned long)zbuf.size;
+ }
+
+ if (git_buf_put(buf, data, size) < 0 ||
+ git_hash_update(&pb->ctx, data, size) < 0)
+ goto on_error;
+
+ if (po->delta_data)
+ git__free(po->delta_data);
+
+ git_odb_object_free(obj);
+ git_buf_free(&zbuf);
+
+ pb->nr_written++;
+ return 0;
+
+on_error:
+ git_odb_object_free(obj);
+ git_buf_free(&zbuf);
+ return -1;
+}
+
+enum write_one_status {
+ WRITE_ONE_SKIP = -1, /* already written */
+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+ WRITE_ONE_WRITTEN = 1, /* normal */
+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static int write_one(git_buf *buf, git_packbuilder *pb, git_pobject *po,
+ enum write_one_status *status)
+{
+ if (po->recursing) {
+ *status = WRITE_ONE_RECURSIVE;
+ return 0;
+ } else if (po->written) {
+ *status = WRITE_ONE_SKIP;
+ return 0;
+ }
+
+ if (po->delta) {
+ po->recursing = 1;
+ if (write_one(buf, pb, po->delta, status) < 0)
+ return -1;
+ switch (*status) {
+ case WRITE_ONE_RECURSIVE:
+ /* we cannot depend on this one */
+ po->delta = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ po->written = 1;
+ po->recursing = 0;
+ return write_object(buf, pb, po);
+}
+
+GIT_INLINE(void) add_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ if (po->filled)
+ return;
+ wo[(*endp)++] = po;
+ po->filled = 1;
+}
+
+static void add_descendants_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ int add_to_order = 1;
+ while (po) {
+ if (add_to_order) {
+ git_pobject *s;
+ /* add this node... */
+ add_to_write_order(wo, endp, po);
+ /* all its siblings... */
+ for (s = po->delta_sibling; s; s = s->delta_sibling) {
+ add_to_write_order(wo, endp, s);
+ }
+ }
+ /* drop down a level to add left subtree nodes if possible */
+ if (po->delta_child) {
+ add_to_order = 1;
+ po = po->delta_child;
+ } else {
+ add_to_order = 0;
+ /* our sibling might have some children, it is next */
+ if (po->delta_sibling) {
+ po = po->delta_sibling;
+ continue;
+ }
+ /* go back to our parent node */
+ po = po->delta;
+ while (po && !po->delta_sibling) {
+ /* we're on the right side of a subtree, keep
+ * going up until we can go right again */
+ po = po->delta;
+ }
+ if (!po) {
+ /* done- we hit our original root node */
+ return;
+ }
+ /* pass it off to sibling at this level */
+ po = po->delta_sibling;
+ }
+ };
+}
+
+static void add_family_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ git_pobject *root;
+
+ for (root = po; root->delta; root = root->delta)
+ ; /* nothing */
+ add_descendants_to_write_order(wo, endp, root);
+}
+
+static int cb_tag_foreach(const char *name, git_oid *oid, void *data)
+{
+ git_packbuilder *pb = data;
+ git_pobject *po;
+ khiter_t pos;
+
+ GIT_UNUSED(name);
+
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos == kh_end(pb->object_ix))
+ return 0;
+
+ po = kh_value(pb->object_ix, pos);
+ po->tagged = 1;
+
+ /* TODO: peel objects */
+
+ return 0;
+}
+
+static git_pobject **compute_write_order(git_packbuilder *pb)
+{
+ unsigned int i, wo_end, last_untagged;
+
+ git_pobject **wo = git__malloc(sizeof(*wo) * pb->nr_objects);
+
+ for (i = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ po->tagged = 0;
+ po->filled = 0;
+ po->delta_child = NULL;
+ po->delta_sibling = NULL;
+ }
+
+ /*
+ * Fully connect delta_child/delta_sibling network.
+ * Make sure delta_sibling is sorted in the original
+ * recency order.
+ */
+ for (i = pb->nr_objects; i > 0;) {
+ git_pobject *po = &pb->object_list[--i];
+ if (!po->delta)
+ continue;
+ /* Mark me as the first child */
+ po->delta_sibling = po->delta->delta_child;
+ po->delta->delta_child = po;
+ }
+
+ /*
+ * Mark objects that are at the tip of tags.
+ */
+ if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0)
+ return NULL;
+
+ /*
+ * Give the objects in the original recency order until
+ * we see a tagged tip.
+ */
+ for (i = wo_end = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ break;
+ add_to_write_order(wo, &wo_end, po);
+ }
+ last_untagged = i;
+
+ /*
+ * Then fill all the tagged tips.
+ */
+ for (; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all remaining commits and tags.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_COMMIT &&
+ po->type != GIT_OBJ_TAG)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all the trees.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_TREE)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * Finally all the rest in really tight order
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (!po->filled)
+ add_family_to_write_order(wo, &wo_end, po);
+ }
+
+ if (wo_end != pb->nr_objects) {
+ giterr_set(GITERR_INVALID, "invalid write order");
+ return NULL;
+ }
+
+ return wo;
+}
+
+static int write_pack(git_packbuilder *pb,
+ int (*cb)(void *buf, size_t size, void *data),
+ void *data)
+{
+ git_pobject **write_order;
+ git_pobject *po;
+ git_buf buf = GIT_BUF_INIT;
+ enum write_one_status status;
+ struct git_pack_header ph;
+ unsigned int i = 0;
+
+ write_order = compute_write_order(pb);
+ if (write_order == NULL)
+ goto on_error;
+
+ /* Write pack header */
+ ph.hdr_signature = htonl(PACK_SIGNATURE);
+ ph.hdr_version = htonl(PACK_VERSION);
+ ph.hdr_entries = htonl(pb->nr_objects);
+
+ if (cb(&ph, sizeof(ph), data) < 0)
+ goto on_error;
+
+ if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0)
+ goto on_error;
+
+ pb->nr_remaining = pb->nr_objects;
+ do {
+ pb->nr_written = 0;
+ for ( ; i < pb->nr_objects; ++i) {
+ po = write_order[i];
+ if (write_one(&buf, pb, po, &status) < 0)
+ goto on_error;
+ if (cb(buf.ptr, buf.size, data) < 0)
+ goto on_error;
+ git_buf_clear(&buf);
+ }
+
+ pb->nr_remaining -= pb->nr_written;
+ } while (pb->nr_remaining && i < pb->nr_objects);
+
+ git__free(write_order);
+ git_buf_free(&buf);
+
+ if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0)
+ goto on_error;
+
+ return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data);
+
+on_error:
+ git__free(write_order);
+ git_buf_free(&buf);
+ return -1;
+}
+
+static int write_pack_buf(void *buf, size_t size, void *data)
+{
+ git_buf *b = (git_buf *)data;
+ return git_buf_put(b, buf, size);
+}
+
+static int write_pack_to_file(void *buf, size_t size, void *data)
+{
+ git_filebuf *file = (git_filebuf *)data;
+ return git_filebuf_write(file, buf, size);
+}
+
+static int write_pack_file(git_packbuilder *pb, const char *path)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+
+ if (git_filebuf_open(&file, path, 0) < 0 ||
+ write_pack(pb, &write_pack_to_file, &file) < 0 ||
+ git_filebuf_commit(&file, GIT_PACK_FILE_MODE) < 0) {
+ git_filebuf_cleanup(&file);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int type_size_sort(const void *_a, const void *_b)
+{
+ const git_pobject *a = (git_pobject *)_a;
+ const git_pobject *b = (git_pobject *)_b;
+
+ if (a->type > b->type)
+ return -1;
+ if (a->type < b->type)
+ return 1;
+ if (a->hash > b->hash)
+ return -1;
+ if (a->hash < b->hash)
+ return 1;
+ /*
+ * TODO
+ *
+ if (a->preferred_base > b->preferred_base)
+ return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
+ */
+ if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
+ return 1;
+ return a < b ? -1 : (a > b); /* newest first */
+}
+
+static int delta_cacheable(git_packbuilder *pb, unsigned long src_size,
+ unsigned long trg_size, unsigned long delta_size)
+{
+ if (pb->max_delta_cache_size &&
+ pb->delta_cache_size + delta_size > pb->max_delta_cache_size)
+ return 0;
+
+ if (delta_size < pb->cache_max_small_delta_size)
+ return 1;
+
+ /* cache delta, if objects are large enough compared to delta size */
+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+ return 1;
+
+ return 0;
+}
+
+static int try_delta(git_packbuilder *pb, struct unpacked *trg,
+ struct unpacked *src, unsigned int max_depth,
+ unsigned long *mem_usage, int *ret)
+{
+ git_pobject *trg_object = trg->object;
+ git_pobject *src_object = src->object;
+ git_odb_object *obj;
+ unsigned long trg_size, src_size, delta_size,
+ sizediff, max_size, sz;
+ unsigned int ref_depth;
+ void *delta_buf;
+
+ /* Don't bother doing diffs between different types */
+ if (trg_object->type != src_object->type) {
+ *ret = -1;
+ return 0;
+ }
+
+ *ret = 0;
+
+ /* TODO: support reuse-delta */
+
+ /* Let's not bust the allowed depth. */
+ if (src->depth >= max_depth)
+ return 0;
+
+ /* Now some size filtering heuristics. */
+ trg_size = (unsigned long)trg_object->size;
+ if (!trg_object->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_object->delta_size;
+ ref_depth = trg->depth;
+ }
+
+ max_size = (uint64_t)max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
+ if (max_size == 0)
+ return 0;
+
+ src_size = (unsigned long)src_object->size;
+ sizediff = src_size < trg_size ? trg_size - src_size : 0;
+ if (sizediff >= max_size)
+ return 0;
+ if (trg_size < src_size / 32)
+ return 0;
+
+ /* Load data if not already done */
+ if (!trg->data) {
+ if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0)
+ return -1;
+
+ sz = (unsigned long)git_odb_object_size(obj);
+ trg->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(trg->data);
+ memcpy(trg->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != trg_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent target object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->data) {
+ if (git_odb_read(&obj, pb->odb, &src_object->id) < 0)
+ return -1;
+
+ sz = (unsigned long)git_odb_object_size(obj);
+ src->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(src->data);
+ memcpy(src->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != src_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent source object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->index) {
+ src->index = git_delta_create_index(src->data, src_size);
+ if (!src->index)
+ return 0; /* suboptimal pack - out of memory */
+
+ *mem_usage += git_delta_sizeof_index(src->index);
+ }
+
+ delta_buf = git_delta_create(src->index, trg->data, trg_size,
+ &delta_size, max_size);
+ if (!delta_buf)
+ return 0;
+
+ if (trg_object->delta) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_object->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ git__free(delta_buf);
+ return 0;
+ }
+ }
+
+ git_packbuilder__cache_lock(pb);
+ if (trg_object->delta_data) {
+ git__free(trg_object->delta_data);
+ pb->delta_cache_size -= trg_object->delta_size;
+ trg_object->delta_data = NULL;
+ }
+ if (delta_cacheable(pb, src_size, trg_size, delta_size)) {
+ pb->delta_cache_size += delta_size;
+ git_packbuilder__cache_unlock(pb);
+
+ trg_object->delta_data = git__realloc(delta_buf, delta_size);
+ GITERR_CHECK_ALLOC(trg_object->delta_data);
+ } else {
+ /* create delta when writing the pack */
+ git_packbuilder__cache_unlock(pb);
+ git__free(delta_buf);
+ }
+
+ trg_object->delta = src_object;
+ trg_object->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
+ *ret = 1;
+ return 0;
+}
+
+static unsigned int check_delta_limit(git_pobject *me, unsigned int n)
+{
+ git_pobject *child = me->delta_child;
+ unsigned int m = n;
+
+ while (child) {
+ unsigned int c = check_delta_limit(child, n + 1);
+ if (m < c)
+ m = c;
+ child = child->delta_sibling;
+ }
+ return m;
+}
+
+static unsigned long free_unpacked(struct unpacked *n)
+{
+ unsigned long freed_mem = git_delta_sizeof_index(n->index);
+ git_delta_free_index(n->index);
+ n->index = NULL;
+ if (n->data) {
+ freed_mem += (unsigned long)n->object->size;
+ git__free(n->data);
+ n->data = NULL;
+ }
+ n->object = NULL;
+ n->depth = 0;
+ return freed_mem;
+}
+
+static int find_deltas(git_packbuilder *pb, git_pobject **list,
+ unsigned int *list_size, unsigned int window,
+ unsigned int depth)
+{
+ git_pobject *po;
+ git_buf zbuf = GIT_BUF_INIT;
+ struct unpacked *array;
+ uint32_t idx = 0, count = 0;
+ unsigned long mem_usage = 0;
+ unsigned int i;
+ int error = -1;
+
+ array = git__calloc(window, sizeof(struct unpacked));
+ GITERR_CHECK_ALLOC(array);
+
+ for (;;) {
+ struct unpacked *n = array + idx;
+ unsigned int max_depth;
+ int j, best_base = -1;
+
+ git_packbuilder__progress_lock(pb);
+ if (!*list_size) {
+ git_packbuilder__progress_unlock(pb);
+ break;
+ }
+
+ po = *list++;
+ (*list_size)--;
+ git_packbuilder__progress_unlock(pb);
+
+ mem_usage -= free_unpacked(n);
+ n->object = po;
+
+ while (pb->window_memory_limit &&
+ mem_usage > pb->window_memory_limit &&
+ count > 1) {
+ uint32_t tail = (idx + window - count) % window;
+ mem_usage -= free_unpacked(array + tail);
+ count--;
+ }
+
+ /*
+ * If the current object is at pack edge, take the depth the
+ * objects that depend on the current object into account
+ * otherwise they would become too deep.
+ */
+ max_depth = depth;
+ if (po->delta_child) {
+ max_depth -= check_delta_limit(po, 0);
+ if (max_depth <= 0)
+ goto next;
+ }
+
+ j = window;
+ while (--j > 0) {
+ int ret;
+ uint32_t other_idx = idx + j;
+ struct unpacked *m;
+
+ if (other_idx >= window)
+ other_idx -= window;
+
+ m = array + other_idx;
+ if (!m->object)
+ break;
+
+ if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0)
+ goto on_error;
+ if (ret < 0)
+ break;
+ else if (ret > 0)
+ best_base = other_idx;
+ }
+
+ /*
+ * If we decided to cache the delta data, then it is best
+ * to compress it right away. First because we have to do
+ * it anyway, and doing it here while we're threaded will
+ * save a lot of time in the non threaded write phase,
+ * as well as allow for caching more deltas within
+ * the same cache size limit.
+ * ...
+ * But only if not writing to stdout, since in that case
+ * the network is most likely throttling writes anyway,
+ * and therefore it is best to go to the write phase ASAP
+ * instead, as we can afford spending more time compressing
+ * between writes at that moment.
+ */
+ if (po->delta_data) {
+ if (git__compress(&zbuf, po->delta_data, po->delta_size) < 0)
+ goto on_error;
+
+ git__free(po->delta_data);
+ po->delta_data = git__malloc(zbuf.size);
+ GITERR_CHECK_ALLOC(po->delta_data);
+
+ memcpy(po->delta_data, zbuf.ptr, zbuf.size);
+ po->z_delta_size = (unsigned long)zbuf.size;
+ git_buf_clear(&zbuf);
+
+ git_packbuilder__cache_lock(pb);
+ pb->delta_cache_size -= po->delta_size;
+ pb->delta_cache_size += po->z_delta_size;
+ git_packbuilder__cache_unlock(pb);
+ }
+
+ /*
+ * If we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ */
+ if (po->delta && max_depth <= n->depth)
+ continue;
+
+ /*
+ * Move the best delta base up in the window, after the
+ * currently deltified object, to keep it longer. It will
+ * be the first base object to be attempted next.
+ */
+ if (po->delta) {
+ struct unpacked swap = array[best_base];
+ int dist = (window + idx - best_base) % window;
+ int dst = best_base;
+ while (dist--) {
+ int src = (dst + 1) % window;
+ array[dst] = array[src];
+ dst = src;
+ }
+ array[dst] = swap;
+ }
+
+ next:
+ idx++;
+ if (count + 1 < window)
+ count++;
+ if (idx >= window)
+ idx = 0;
+ }
+ error = 0;
+
+on_error:
+ for (i = 0; i < window; ++i) {
+ git__free(array[i].index);
+ git__free(array[i].data);
+ }
+ git__free(array);
+ git_buf_free(&zbuf);
+
+ return error;
+}
+
+#ifdef GIT_THREADS
+
+struct thread_params {
+ git_thread thread;
+ git_packbuilder *pb;
+
+ git_pobject **list;
+
+ git_cond cond;
+ git_mutex mutex;
+
+ unsigned int list_size;
+ unsigned int remaining;
+
+ int window;
+ int depth;
+ int working;
+ int data_ready;
+};
+
+static void *threaded_find_deltas(void *arg)
+{
+ struct thread_params *me = arg;
+
+ while (me->remaining) {
+ if (find_deltas(me->pb, me->list, &me->remaining,
+ me->window, me->depth) < 0) {
+ ; /* TODO */
+ }
+
+ git_packbuilder__progress_lock(me->pb);
+ me->working = 0;
+ git_cond_signal(&me->pb->progress_cond);
+ git_packbuilder__progress_unlock(me->pb);
+
+ if (git_mutex_lock(&me->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ return NULL;
+ }
+
+ while (!me->data_ready)
+ git_cond_wait(&me->cond, &me->mutex);
+
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ me->data_ready = 0;
+ git_mutex_unlock(&me->mutex);
+ }
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
+}
+
+static int ll_find_deltas(git_packbuilder *pb, git_pobject **list,
+ unsigned int list_size, unsigned int window,
+ unsigned int depth)
+{
+ struct thread_params *p;
+ int i, ret, active_threads = 0;
+
+ if (!pb->nr_threads)
+ pb->nr_threads = git_online_cpus();
+
+ if (pb->nr_threads <= 1) {
+ find_deltas(pb, list, &list_size, window, depth);
+ return 0;
+ }
+
+ p = git__malloc(pb->nr_threads * sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ /* Partition the work among the threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ unsigned sub_size = list_size / (pb->nr_threads - i);
+
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < pb->nr_threads)
+ sub_size = 0;
+
+ p[i].pb = pb;
+ p[i].window = window;
+ p[i].depth = depth;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ if (!p[i].list_size)
+ continue;
+
+ git_mutex_init(&p[i].mutex);
+ git_cond_init(&p[i].cond);
+
+ ret = git_thread_create(&p[i].thread, NULL,
+ threaded_find_deltas, &p[i]);
+ if (ret) {
+ giterr_set(GITERR_THREAD, "unable to create thread");
+ return -1;
+ }
+ active_threads++;
+ }
+
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
+
+ /* Start by locating a thread that has transitioned its
+ * 'working' flag from 1 -> 0. This indicates that it is
+ * ready to receive more work using our work-stealing
+ * algorithm. */
+ git_packbuilder__progress_lock(pb);
+ for (;;) {
+ for (i = 0; !target && i < pb->nr_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ git_cond_wait(&pb->progress_cond, &pb->progress_mutex);
+ }
+
+ /* At this point we hold the progress lock and have located
+ * a thread to receive more work. We still need to locate a
+ * thread from which to steal work (the victim). */
+ for (i = 0; i < pb->nr_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ git_packbuilder__progress_unlock(pb);
+
+ if (git_mutex_lock(&target->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ git__free(p);
+ return -1;
+ }
+
+ target->data_ready = 1;
+ git_cond_signal(&target->cond);
+ git_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
+ git_thread_join(target->thread, NULL);
+ git_cond_free(&target->cond);
+ git_mutex_free(&target->mutex);
+ active_threads--;
+ }
+ }
+
+ git__free(p);
+ return 0;
+}
+
+#else
+#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d)
+#endif
+
+static int prepare_pack(git_packbuilder *pb)
+{
+ git_pobject **delta_list;
+ unsigned int i, n = 0;
+
+ if (pb->nr_objects == 0 || pb->done)
+ return 0; /* nothing to do */
+
+ delta_list = git__malloc(pb->nr_objects * sizeof(*delta_list));
+ GITERR_CHECK_ALLOC(delta_list);
+
+ for (i = 0; i < pb->nr_objects; ++i) {
+ git_pobject *po = pb->object_list + i;
+
+ /* Make sure the item is within our size limits */
+ if (po->size < 50 || po->size > pb->big_file_threshold)
+ continue;
+
+ delta_list[n++] = po;
+ }
+
+ if (n > 1) {
+ git__tsort((void **)delta_list, n, type_size_sort);
+ if (ll_find_deltas(pb, delta_list, n,
+ GIT_PACK_WINDOW + 1,
+ GIT_PACK_DEPTH) < 0) {
+ git__free(delta_list);
+ return -1;
+ }
+ }
+
+ pb->done = true;
+ git__free(delta_list);
+ return 0;
+}
+
+#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
+
+int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
+{
+ PREPARE_PACK;
+ return write_pack(pb, cb, payload);
+}
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
+{
+ PREPARE_PACK;
+ return write_pack(pb, &write_pack_buf, buf);
+}
+
+int git_packbuilder_write(git_packbuilder *pb, const char *path)
+{
+ PREPARE_PACK;
+ return write_pack_file(pb, path);
+}
+
+#undef PREPARE_PACK
+
+static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *payload)
+{
+ struct tree_walk_context *ctx = payload;
+
+ /* A commit inside a tree represents a submodule commit and should be skipped. */
+ if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT)
+ return 0;
+
+ if (git_buf_sets(&ctx->buf, root) < 0 ||
+ git_buf_puts(&ctx->buf, git_tree_entry_name(entry)) < 0)
+ return -1;
+
+ return git_packbuilder_insert(ctx->pb,
+ git_tree_entry_id(entry),
+ git_buf_cstr(&ctx->buf));
+}
+
+int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
+{
+ git_tree *tree;
+ struct tree_walk_context context = { pb, GIT_BUF_INIT };
+
+ if (git_tree_lookup(&tree, pb->repo, oid) < 0 ||
+ git_packbuilder_insert(pb, oid, NULL) < 0)
+ return -1;
+
+ if (git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context) < 0) {
+ git_tree_free(tree);
+ git_buf_free(&context.buf);
+ return -1;
+ }
+
+ git_tree_free(tree);
+ git_buf_free(&context.buf);
+ return 0;
+}
+
+uint32_t git_packbuilder_object_count(git_packbuilder *pb)
+{
+ return pb->nr_objects;
+}
+
+uint32_t git_packbuilder_written(git_packbuilder *pb)
+{
+ return pb->nr_written;
+}
+
+void git_packbuilder_free(git_packbuilder *pb)
+{
+ if (pb == NULL)
+ return;
+
+#ifdef GIT_THREADS
+
+ git_mutex_free(&pb->cache_mutex);
+ git_mutex_free(&pb->progress_mutex);
+ git_cond_free(&pb->progress_cond);
+
+#endif
+
+ if (pb->odb)
+ git_odb_free(pb->odb);
+
+ if (pb->object_ix)
+ git_oidmap_free(pb->object_ix);
+
+ if (pb->object_list)
+ git__free(pb->object_list);
+
+ git_hash_ctx_cleanup(&pb->ctx);
+
+ git__free(pb);
+}
diff --git a/src/pack-objects.h b/src/pack-objects.h
new file mode 100644
index 000000000..8e7ba7f78
--- /dev/null
+++ b/src/pack-objects.h
@@ -0,0 +1,87 @@
+/*
+ * 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_pack_objects_h__
+#define INCLUDE_pack_objects_h__
+
+#include "common.h"
+
+#include "buffer.h"
+#include "hash.h"
+#include "oidmap.h"
+#include "netops.h"
+
+#include "git2/oid.h"
+
+#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */
+#define GIT_PACK_DEPTH 50 /* max delta depth */
+#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024)
+#define GIT_PACK_DELTA_CACHE_LIMIT 1000
+#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024)
+
+typedef struct git_pobject {
+ git_oid id;
+ git_otype type;
+ git_off_t offset;
+
+ size_t size;
+
+ unsigned int hash; /* name hint hash */
+
+ struct git_pobject *delta; /* delta base object */
+ struct git_pobject *delta_child; /* deltified objects who bases me */
+ struct git_pobject *delta_sibling; /* other deltified objects
+ * who uses the same base as
+ * me */
+
+ void *delta_data;
+ unsigned long delta_size;
+ unsigned long z_delta_size;
+
+ int written:1,
+ recursing:1,
+ tagged:1,
+ filled:1;
+} git_pobject;
+
+struct git_packbuilder {
+ git_repository *repo; /* associated repository */
+ git_odb *odb; /* associated object database */
+
+ git_hash_ctx ctx;
+
+ uint32_t nr_objects,
+ nr_alloc,
+ nr_written,
+ nr_remaining;
+
+ git_pobject *object_list;
+
+ git_oidmap *object_ix;
+
+ git_oid pack_oid; /* hash of written pack */
+
+ /* synchronization objects */
+ git_mutex cache_mutex;
+ git_mutex progress_mutex;
+ git_cond progress_cond;
+
+ /* configs */
+ uint64_t delta_cache_size;
+ uint64_t max_delta_cache_size;
+ uint64_t cache_max_small_delta_size;
+ uint64_t big_file_threshold;
+ uint64_t window_memory_limit;
+
+ int nr_threads; /* nr of threads to use */
+
+ bool done;
+};
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
+
+#endif /* INCLUDE_pack_objects_h__ */
diff --git a/src/pack.c b/src/pack.c
index 0db1069de..75ac98186 100644
--- a/src/pack.c
+++ b/src/pack.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -38,7 +38,7 @@ static int pack_entry_find_offset(
git_oid *found_oid,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len);
+ size_t len);
static int packfile_error(const char *message)
{
@@ -46,6 +46,134 @@ static int packfile_error(const char *message)
return -1;
}
+/********************
+ * Delta base cache
+ ********************/
+
+static git_pack_cache_entry *new_cache_object(git_rawobj *source)
+{
+ git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry));
+ if (!e)
+ return NULL;
+
+ memcpy(&e->raw, source, sizeof(git_rawobj));
+
+ return e;
+}
+
+static void free_cache_object(void *o)
+{
+ git_pack_cache_entry *e = (git_pack_cache_entry *)o;
+
+ if (e != NULL) {
+ assert(e->refcount.val == 0);
+ git__free(e->raw.data);
+ git__free(e);
+ }
+}
+
+static void cache_free(git_pack_cache *cache)
+{
+ khiter_t k;
+
+ if (cache->entries) {
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (kh_exist(cache->entries, k))
+ free_cache_object(kh_value(cache->entries, k));
+ }
+
+ git_offmap_free(cache->entries);
+ git_mutex_free(&cache->lock);
+ }
+}
+
+static int cache_init(git_pack_cache *cache)
+{
+ memset(cache, 0, sizeof(git_pack_cache));
+ cache->entries = git_offmap_alloc();
+ GITERR_CHECK_ALLOC(cache->entries);
+ cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT;
+ git_mutex_init(&cache->lock);
+
+ return 0;
+}
+
+static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset)
+{
+ khiter_t k;
+ git_pack_cache_entry *entry = NULL;
+
+ if (git_mutex_lock(&cache->lock) < 0)
+ return NULL;
+
+ k = kh_get(off, cache->entries, offset);
+ if (k != kh_end(cache->entries)) { /* found it */
+ entry = kh_value(cache->entries, k);
+ git_atomic_inc(&entry->refcount);
+ entry->last_usage = cache->use_ctr++;
+ }
+ git_mutex_unlock(&cache->lock);
+
+ return entry;
+}
+
+/* Run with the cache lock held */
+static void free_lowest_entry(git_pack_cache *cache)
+{
+ git_pack_cache_entry *entry;
+ khiter_t k;
+
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (!kh_exist(cache->entries, k))
+ continue;
+
+ entry = kh_value(cache->entries, k);
+
+ if (entry && entry->refcount.val == 0) {
+ cache->memory_used -= entry->raw.len;
+ kh_del(off, cache->entries, k);
+ free_cache_object(entry);
+ }
+ }
+}
+
+static int cache_add(git_pack_cache *cache, git_rawobj *base, git_off_t offset)
+{
+ git_pack_cache_entry *entry;
+ int error, exists = 0;
+ khiter_t k;
+
+ if (base->len > GIT_PACK_CACHE_SIZE_LIMIT)
+ return -1;
+
+ entry = new_cache_object(base);
+ if (entry) {
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock cache");
+ return -1;
+ }
+ /* Add it to the cache if nobody else has */
+ exists = kh_get(off, cache->entries, offset) != kh_end(cache->entries);
+ if (!exists) {
+ while (cache->memory_used + base->len > cache->memory_limit)
+ free_lowest_entry(cache);
+
+ k = kh_put(off, cache->entries, offset, &error);
+ assert(error != 0);
+ kh_value(cache->entries, k) = entry;
+ cache->memory_used += entry->raw.len;
+ }
+ git_mutex_unlock(&cache->lock);
+ /* Somebody beat us to adding it into the cache */
+ if (exists) {
+ git__free(entry);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
/***********************************************************
*
* PACK INDEX METHODS
@@ -54,6 +182,10 @@ static int packfile_error(const char *message)
static void pack_index_free(struct git_pack_file *p)
{
+ if (p->oids) {
+ git__free(p->oids);
+ p->oids = NULL;
+ }
if (p->index_map.data) {
git_futils_mmap_free(&p->index_map);
p->index_map.data = NULL;
@@ -262,7 +394,7 @@ int git_packfile_unpack_header(
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;
@@ -273,6 +405,56 @@ int git_packfile_unpack_header(
return 0;
}
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
+ struct git_pack_file *p,
+ git_off_t offset)
+{
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = offset;
+ size_t size;
+ git_otype type;
+ git_off_t base_offset;
+ int error;
+
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+
+ if (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ size_t base_size;
+ git_rawobj delta;
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, offset);
+ git_mwindow_close(&w_curs);
+ error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, size, type);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+ error = git__delta_read_header(delta.data, delta.len, &base_size, size_p);
+ git__free(delta.data);
+ if (error < 0)
+ return error;
+ } else
+ *size_p = size;
+
+ while (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ curpos = base_offset;
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)
+ break;
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, base_offset);
+ git_mwindow_close(&w_curs);
+ }
+ *type_p = type;
+
+ return error;
+}
+
static int packfile_unpack_delta(
git_rawobj *obj,
struct git_pack_file *p,
@@ -282,9 +464,10 @@ static int packfile_unpack_delta(
git_otype delta_type,
git_off_t obj_offset)
{
- git_off_t base_offset;
+ git_off_t base_offset, base_key;
git_rawobj base, delta;
- int error;
+ git_pack_cache_entry *cached = NULL;
+ int error, found_base = 0;
base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset);
git_mwindow_close(w_curs);
@@ -293,32 +476,49 @@ static int packfile_unpack_delta(
if (base_offset < 0) /* must actually be an error code */
return (int)base_offset;
- error = git_packfile_unpack(&base, p, &base_offset);
+ if (!p->bases.entries && (cache_init(&p->bases) < 0))
+ return -1;
- /*
- * TODO: git.git tries to load the base from other packfiles
- * or loose objects.
- *
- * We'll need to do this in order to support thin packs.
- */
- if (error < 0)
- return error;
+ base_key = base_offset; /* git_packfile_unpack modifies base_offset */
+ if ((cached = cache_get(&p->bases, base_offset)) != NULL) {
+ memcpy(&base, &cached->raw, sizeof(git_rawobj));
+ found_base = 1;
+ }
+
+ if (!cached) { /* have to inflate it */
+ error = git_packfile_unpack(&base, p, &base_offset);
+
+ /*
+ * TODO: git.git tries to load the base from other packfiles
+ * or loose objects.
+ *
+ * We'll need to do this in order to support thin packs.
+ */
+ if (error < 0)
+ return error;
+ }
error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type);
git_mwindow_close(w_curs);
+
if (error < 0) {
- git__free(base.data);
+ if (!found_base)
+ git__free(base.data);
return error;
}
obj->type = base.type;
error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len);
+ if (error < 0)
+ goto on_error;
- git__free(base.data);
- git__free(delta.data);
+ if (found_base)
+ git_atomic_dec(&cached->refcount);
+ else if (cache_add(&p->bases, &base, base_key) < 0)
+ git__free(base.data);
- /* TODO: we might want to cache this shit. eventually */
- //add_delta_base_cache(p, base_offset, base, base_size, *type);
+on_error:
+ git__free(delta.data);
return error; /* error set by git__delta_apply */
}
@@ -387,6 +587,72 @@ static void use_git_free(void *opaq, void *ptr)
git__free(ptr);
}
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos)
+{
+ int st;
+
+ memset(obj, 0, sizeof(git_packfile_stream));
+ obj->curpos = curpos;
+ obj->p = p;
+ obj->zstream.zalloc = use_git_alloc;
+ obj->zstream.zfree = use_git_free;
+ obj->zstream.next_in = Z_NULL;
+ obj->zstream.next_out = Z_NULL;
+ st = inflateInit(&obj->zstream);
+ if (st != Z_OK) {
+ git__free(obj);
+ giterr_set(GITERR_ZLIB, "Failed to inflate packfile");
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len)
+{
+ unsigned char *in;
+ size_t written;
+ int st;
+
+ if (obj->done)
+ return 0;
+
+ in = pack_window_open(obj->p, &obj->mw, obj->curpos, &obj->zstream.avail_in);
+ if (in == NULL)
+ return GIT_EBUFS;
+
+ obj->zstream.next_out = buffer;
+ obj->zstream.avail_out = (unsigned int)len;
+ obj->zstream.next_in = in;
+
+ st = inflate(&obj->zstream, Z_SYNC_FLUSH);
+ git_mwindow_close(&obj->mw);
+
+ obj->curpos += obj->zstream.next_in - in;
+ written = len - obj->zstream.avail_out;
+
+ if (st != Z_OK && st != Z_STREAM_END) {
+ giterr_set(GITERR_ZLIB, "Failed to inflate packfile");
+ return -1;
+ }
+
+ if (st == Z_STREAM_END)
+ obj->done = 1;
+
+
+ /* If we didn't write anything out but we're not done, we need more data */
+ if (!written && st != Z_STREAM_END)
+ return GIT_EBUFS;
+
+ return written;
+
+}
+
+void git_packfile_stream_free(git_packfile_stream *obj)
+{
+ inflateEnd(&obj->zstream);
+}
+
int packfile_unpack_compressed(
git_rawobj *obj,
struct git_pack_file *p,
@@ -494,14 +760,14 @@ git_off_t get_delta_base(
} else if (type == GIT_OBJ_REF_DELTA) {
/* If we have the cooperative cache, search in it first */
if (p->has_cache) {
- int pos;
- struct git_pack_entry key;
+ khiter_t k;
+ git_oid oid;
- git_oid_fromraw(&key.sha1, base_info);
- pos = git_vector_bsearch(&p->cache, &key);
- if (pos >= 0) {
+ git_oid_fromraw(&oid, base_info);
+ k = kh_get(oid, p->idx_cache, &oid);
+ if (k != kh_end(p->idx_cache)) {
*curpos += 20;
- return ((struct git_pack_entry *)git_vector_get(&p->cache, pos))->offset;
+ return ((struct git_pack_entry *)kh_value(p->idx_cache, k))->offset;
}
}
/* The base entry _must_ be in the same pack */
@@ -529,12 +795,14 @@ static struct git_pack_file *packfile_alloc(size_t extra)
}
-void packfile_free(struct git_pack_file *p)
+void git_packfile_free(struct git_pack_file *p)
{
assert(p);
- /* clear_delta_base_cache(); */
+ 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);
@@ -559,8 +827,10 @@ static int packfile_open(struct git_pack_file *p)
/* TODO: open with noatime */
p->mwf.fd = git_futils_open_ro(p->pack_name);
- if (p->mwf.fd < 0)
- return p->mwf.fd;
+ if (p->mwf.fd < 0) {
+ p->mwf.fd = -1;
+ return -1;
+ }
if (p_fstat(p->mwf.fd, &st) < 0 ||
git_mwindow_file_register(&p->mwf) < 0)
@@ -685,12 +955,76 @@ static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_
}
}
+static int git__memcmp4(const void *a, const void *b) {
+ return memcmp(a, b, 4);
+}
+
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data)
+{
+ const unsigned char *index = p->index_map.data, *current;
+ uint32_t i;
+
+ if (index == NULL) {
+ int error;
+
+ if ((error = pack_index_open(p)) < 0)
+ return error;
+
+ assert(p->index_map.data);
+
+ index = p->index_map.data;
+ }
+
+ if (p->index_version > 1) {
+ index += 8;
+ }
+
+ index += 4 * 256;
+
+ if (p->oids == NULL) {
+ git_vector offsets, oids;
+ int error;
+
+ if ((error = git_vector_init(&oids, p->num_objects, NULL)))
+ return error;
+
+ if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4)))
+ return error;
+
+ if (p->index_version > 1) {
+ const unsigned char *off = index + 24 * p->num_objects;
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&off[4 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)&index[5 * (current - off)]);
+ } else {
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&index[24 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)&current[4]);
+ }
+ git_vector_free(&offsets);
+ p->oids = (git_oid **)oids.contents;
+ }
+
+ for (i = 0; i < p->num_objects; i++)
+ if (cb(p->oids[i], data))
+ return GIT_EUSER;
+
+ return 0;
+}
+
static int pack_entry_find_offset(
git_off_t *offset_out,
git_oid *found_oid,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
const uint32_t *level1_ofs = p->index_map.data;
const unsigned char *index = p->index_map.data;
@@ -783,7 +1117,7 @@ int git_pack_entry_find(
struct git_pack_entry *e,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
git_off_t offset;
git_oid found_oid;
diff --git a/src/pack.h b/src/pack.h
index cd7a4d2e1..8d7e33dfe 100644
--- a/src/pack.h
+++ b/src/pack.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,12 +8,15 @@
#ifndef INCLUDE_pack_h__
#define INCLUDE_pack_h__
+#include <zlib.h>
+
#include "git2/oid.h"
#include "common.h"
#include "map.h"
#include "mwindow.h"
#include "odb.h"
+#include "oidmap.h"
#define GIT_PACK_FILE_MODE 0444
@@ -51,6 +54,28 @@ struct git_pack_idx_header {
uint32_t idx_version;
};
+typedef struct git_pack_cache_entry {
+ size_t last_usage; /* enough? */
+ git_atomic refcount;
+ git_rawobj raw;
+} git_pack_cache_entry;
+
+#include "offmap.h"
+
+GIT__USE_OFFMAP;
+GIT__USE_OIDMAP;
+
+#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024
+#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */
+
+typedef struct {
+ size_t memory_used;
+ size_t memory_limit;
+ size_t use_ctr;
+ git_mutex lock;
+ git_offmap *entries;
+} git_pack_cache;
+
struct git_pack_file {
git_mwindow_file mwf;
git_map index_map;
@@ -63,7 +88,10 @@ struct git_pack_file {
git_time_t mtime;
unsigned pack_local:1, pack_keep:1, has_cache:1;
git_oid sha1;
- git_vector cache;
+ git_oidmap *idx_cache;
+ git_oid **oids;
+
+ git_pack_cache bases; /* delta base cache */
/* something like ".git/objects/pack/xxxxx.pack" */
char pack_name[GIT_FLEX_ARRAY]; /* more */
@@ -75,6 +103,14 @@ struct git_pack_entry {
struct git_pack_file *p;
};
+typedef struct git_packfile_stream {
+ git_off_t curpos;
+ int done;
+ z_stream zstream;
+ struct git_pack_file *p;
+ git_mwindow *mw;
+} git_packfile_stream;
+
int git_packfile_unpack_header(
size_t *size_p,
git_otype *type_p,
@@ -82,6 +118,12 @@ int git_packfile_unpack_header(
git_mwindow **w_curs,
git_off_t *curpos);
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
+ struct git_pack_file *p,
+ git_off_t offset);
+
int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset);
int packfile_unpack_compressed(
git_rawobj *obj,
@@ -91,16 +133,24 @@ int packfile_unpack_compressed(
size_t size,
git_otype type);
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos);
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len);
+void git_packfile_stream_free(git_packfile_stream *obj);
+
git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs,
git_off_t *curpos, git_otype type,
git_off_t delta_obj_offset);
-void packfile_free(struct git_pack_file *p);
+void git_packfile_free(struct git_pack_file *p);
int git_packfile_check(struct git_pack_file **pack_out, const char *path);
int git_pack_entry_find(
struct git_pack_entry *e,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len);
+ size_t len);
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data);
#endif
diff --git a/src/path.c b/src/path.c
index 84edf6d89..6437979d5 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -17,9 +17,55 @@
#include <stdio.h>
#include <ctype.h>
+#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+
+#ifdef GIT_WIN32
+static bool looks_like_network_computer_name(const char *path, int pos)
+{
+ if (pos < 3)
+ return false;
+
+ if (path[0] != '/' || path[1] != '/')
+ return false;
+
+ while (pos-- > 2) {
+ if (path[pos] == '/')
+ return false;
+ }
+
+ return true;
+}
+#endif
+
/*
* Based on the Android implementation, BSD licensed.
- * Check http://android.git.kernel.org/
+ * http://android.git.kernel.org/
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
*/
int git_path_basename_r(git_buf *buffer, const char *path)
{
@@ -105,10 +151,19 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
'C:/' here */
- if (len == 2 && isalpha(path[0]) && path[1] == ':') {
+ if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
len = 3;
goto Exit;
}
+
+ /* Similarly checks if we're dealing with a network computer name
+ '//computername/.git' will return '//computername/' */
+
+ if (looks_like_network_computer_name(path, len)) {
+ len++;
+ goto Exit;
+ }
+
#endif
Exit:
@@ -145,6 +200,20 @@ char *git_path_basename(const char *path)
return basename;
}
+size_t git_path_basename_offset(git_buf *buffer)
+{
+ ssize_t slash;
+
+ if (!buffer || buffer->size <= 0)
+ return 0;
+
+ slash = git_buf_rfind_next(buffer, '/');
+
+ if (slash >= 0 && buffer->ptr[slash] == '/')
+ return (size_t)(slash + 1);
+
+ return 0;
+}
const char *git_path_topdir(const char *path)
{
@@ -168,11 +237,11 @@ int git_path_root(const char *path)
{
int offset = 0;
-#ifdef GIT_WIN32
/* Does the root of the path look like a windows drive ? */
- if (isalpha(path[0]) && (path[1] == ':'))
+ if (LOOKS_LIKE_DRIVE_PREFIX(path))
offset += 2;
+#ifdef GIT_WIN32
/* Are we dealing with a windows network path? */
else if ((path[0] == '/' && path[1] == '/') ||
(path[0] == '\\' && path[1] == '\\'))
@@ -191,6 +260,31 @@ int git_path_root(const char *path)
return -1; /* Not a real error - signals that path is not rooted */
}
+int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
+{
+ int error, root;
+
+ assert(path && path_out);
+
+ root = git_path_root(path);
+
+ if (base != NULL && root < 0) {
+ error = git_buf_joinpath(path_out, base, path);
+
+ if (root_at)
+ *root_at = (ssize_t)strlen(base);
+ }
+ else {
+ error = git_buf_sets(path_out, path);
+
+ if (root_at)
+ *root_at = (root < 0) ? 0 : (ssize_t)root;
+ }
+
+ return error;
+}
+
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{
char buf[GIT_PATH_MAX];
@@ -210,7 +304,7 @@ int git_path_prettify(git_buf *path_out, const char *path, const char *base)
giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
git_buf_clear(path_out);
-
+
return error;
}
@@ -306,7 +400,7 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url)
if (offset >= len || file_url[offset] == '/')
return error_invalid_local_file_uri(file_url);
-#ifndef _MSC_VER
+#ifndef GIT_WIN32
offset--; /* A *nix absolute path starts with a forward slash */
#endif
@@ -341,9 +435,10 @@ int git_path_walk_up(
iter.asize = path->asize;
while (scan >= stop) {
- if ((error = cb(data, &iter)) < 0)
- break;
+ error = cb(data, &iter);
iter.ptr[scan] = oldc;
+ if (error < 0)
+ break;
scan = git_buf_rfind_next(&iter, '/');
if (scan >= 0) {
scan++;
@@ -385,6 +480,68 @@ bool git_path_isfile(const char *path)
return S_ISREG(st.st_mode) != 0;
}
+#ifdef GIT_WIN32
+
+bool git_path_is_empty_dir(const char *path)
+{
+ git_buf pathbuf = GIT_BUF_INIT;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ wchar_t wbuf[GIT_WIN_PATH];
+ WIN32_FIND_DATAW ffd;
+ bool retval = true;
+
+ if (!git_path_isdir(path)) return false;
+
+ git_buf_printf(&pathbuf, "%s\\*", path);
+ git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf));
+
+ hFind = FindFirstFileW(wbuf, &ffd);
+ if (INVALID_HANDLE_VALUE == hFind) {
+ giterr_set(GITERR_OS, "Couldn't open '%s'", path);
+ return false;
+ }
+
+ do {
+ if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
+ retval = false;
+ }
+ } while (FindNextFileW(hFind, &ffd) != 0);
+
+ FindClose(hFind);
+ git_buf_free(&pathbuf);
+ return retval;
+}
+
+#else
+
+bool git_path_is_empty_dir(const char *path)
+{
+ DIR *dir = NULL;
+ struct dirent *e;
+ bool retval = true;
+
+ if (!git_path_isdir(path)) return false;
+
+ dir = opendir(path);
+ if (!dir) {
+ giterr_set(GITERR_OS, "Couldn't open '%s'", path);
+ return false;
+ }
+
+ while ((e = readdir(dir)) != NULL) {
+ if (!git_path_is_dot_or_dotdot(e->d_name)) {
+ giterr_set(GITERR_INVALID,
+ "'%s' exists and is not an empty directory", path);
+ retval = false;
+ break;
+ }
+ }
+ closedir(dir);
+
+ return retval;
+}
+#endif
+
int git_path_lstat(const char *path, struct stat *st)
{
int err = 0;
@@ -407,7 +564,7 @@ static bool _check_dir_contents(
size_t sub_size = strlen(sub);
/* leave base valid even if we could not make space for subdir */
- if (git_buf_try_grow(dir, dir_size + sub_size + 2) < 0)
+ if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0)
return false;
/* save excursion */
@@ -437,12 +594,7 @@ bool git_path_contains_file(git_buf *base, const char *file)
int git_path_find_dir(git_buf *dir, const char *path, const char *base)
{
- int error;
-
- if (base != NULL && git_path_root(path) < 0)
- error = git_buf_joinpath(dir, base, path);
- else
- error = git_buf_sets(dir, path);
+ int error = git_path_join_unrooted(dir, path, base, NULL);
if (!error) {
char buf[GIT_PATH_MAX];
@@ -460,31 +612,94 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base)
return error;
}
+int git_path_resolve_relative(git_buf *path, size_t ceiling)
+{
+ char *base, *to, *from, *next;
+ size_t len;
+
+ if (!path || git_buf_oom(path))
+ return -1;
+
+ if (ceiling > path->size)
+ ceiling = path->size;
+
+ /* recognize drive prefixes, etc. that should not be backed over */
+ if (ceiling == 0)
+ ceiling = git_path_root(path->ptr) + 1;
+
+ /* recognize URL prefixes that should not be backed over */
+ if (ceiling == 0) {
+ for (next = path->ptr; *next && git__isalpha(*next); ++next);
+ if (next[0] == ':' && next[1] == '/' && next[2] == '/')
+ ceiling = (next + 3) - path->ptr;
+ }
+
+ base = to = from = path->ptr + ceiling;
+
+ while (*from) {
+ for (next = from; *next && *next != '/'; ++next);
+
+ len = next - from;
+
+ if (len == 1 && from[0] == '.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == '.' && from[1] == '.') {
+ while (to > base && to[-1] == '/') to--;
+ while (to > base && to[-1] != '/') to--;
+ }
+
+ else {
+ if (*next == '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == '/') from++;
+ }
+
+ *to = '\0';
+
+ path->size = to - path->ptr;
+
+ return 0;
+}
+
+int git_path_apply_relative(git_buf *target, const char *relpath)
+{
+ git_buf_joinpath(target, git_buf_cstr(target), relpath);
+ return git_path_resolve_relative(target, 0);
+}
+
int git_path_cmp(
const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2)
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t))
{
+ unsigned char c1, c2;
size_t len = len1 < len2 ? len1 : len2;
int cmp;
- cmp = memcmp(name1, name2, len);
+ cmp = compare(name1, name2, len);
if (cmp)
return cmp;
- if (len1 < len2)
- return (!isdir1 && !isdir2) ? -1 :
- (isdir1 ? '/' - name2[len1] : name2[len1] - '/');
- if (len1 > len2)
- return (!isdir1 && !isdir2) ? 1 :
- (isdir2 ? name1[len2] - '/' : '/' - name1[len2]);
- return 0;
-}
-/* Taken from git.git */
-GIT_INLINE(int) is_dot_or_dotdot(const char *name)
-{
- return (name[0] == '.' &&
- (name[1] == '\0' ||
- (name[1] == '.' && name[2] == '\0')));
+ c1 = name1[len];
+ c2 = name2[len];
+
+ if (c1 == '\0' && isdir1)
+ c1 = '/';
+
+ if (c2 == '\0' && isdir2)
+ c2 = '/';
+
+ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
int git_path_direach(
@@ -506,7 +721,7 @@ int git_path_direach(
return -1;
}
-#ifdef __sun
+#if defined(__sun) || defined(__GNU__)
de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
de_buf = git__malloc(sizeof(struct dirent));
@@ -515,7 +730,7 @@ int git_path_direach(
while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
int result;
- if (is_dot_or_dotdot(de->d_name))
+ if (git_path_is_dot_or_dotdot(de->d_name))
continue;
if (git_buf_puts(path, de->d_name) < 0) {
@@ -560,7 +775,7 @@ int git_path_dirload(
return -1;
}
-#ifdef __sun
+#if defined(__sun) || defined(__GNU__)
de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
de_buf = git__malloc(sizeof(struct dirent));
@@ -574,7 +789,7 @@ int git_path_dirload(
char *entry_path;
size_t entry_len;
- if (is_dot_or_dotdot(de->d_name))
+ if (git_path_is_dot_or_dotdot(de->d_name))
continue;
entry_len = strlen(de->d_name);
@@ -609,18 +824,30 @@ int git_path_dirload(
int git_path_with_stat_cmp(const void *a, const void *b)
{
const git_path_with_stat *psa = a, *psb = b;
- return git__strcmp_cb(psa->path, psb->path);
+ return strcmp(psa->path, psb->path);
+}
+
+int git_path_with_stat_cmp_icase(const void *a, const void *b)
+{
+ const git_path_with_stat *psa = a, *psb = b;
+ return strcasecmp(psa->path, psb->path);
}
int git_path_dirload_with_stat(
const char *path,
size_t prefix_len,
+ bool ignore_case,
+ const char *start_stat,
+ const char *end_stat,
git_vector *contents)
{
int error;
unsigned int i;
git_path_with_stat *ps;
git_buf full = GIT_BUF_INIT;
+ int (*strncomp)(const char *a, const char *b, size_t sz);
+ size_t start_len = start_stat ? strlen(start_stat) : 0;
+ size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
if (git_buf_set(&full, path, prefix_len) < 0)
return -1;
@@ -632,24 +859,46 @@ int git_path_dirload_with_stat(
return error;
}
+ strncomp = ignore_case ? git__strncasecmp : git__strncmp;
+
+ /* stat struct at start of git_path_with_stat, so shift path text */
git_vector_foreach(contents, i, ps) {
size_t path_len = strlen((char *)ps);
-
memmove(ps->path, ps, path_len + 1);
ps->path_len = path_len;
+ }
+
+ git_vector_foreach(contents, i, ps) {
+ /* skip if before start_stat or after end_stat */
+ cmp_len = min(start_len, ps->path_len);
+ if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
+ continue;
+ cmp_len = min(end_len, ps->path_len);
+ if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
+ continue;
+
+ git_buf_truncate(&full, prefix_len);
if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
(error = git_path_lstat(full.ptr, &ps->st)) < 0)
break;
- git_buf_truncate(&full, prefix_len);
-
if (S_ISDIR(ps->st.st_mode)) {
- ps->path[path_len] = '/';
- ps->path[path_len + 1] = '\0';
+ if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0)
+ break;
+
+ if (p_access(full.ptr, F_OK) == 0) {
+ ps->st.st_mode = GIT_FILEMODE_COMMIT;
+ } else {
+ ps->path[ps->path_len++] = '/';
+ ps->path[ps->path_len] = '\0';
+ }
}
}
+ /* sort now that directory suffix is added */
+ git_vector_sort(contents);
+
git_buf_free(&full);
return error;
diff --git a/src/path.h b/src/path.h
index fd76805e5..ead4fa338 100644
--- a/src/path.h
+++ b/src/path.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -58,6 +58,11 @@ extern int git_path_dirname_r(git_buf *buffer, const char *path);
extern char *git_path_basename(const char *path);
extern int git_path_basename_r(git_buf *buffer, const char *path);
+/* Return the offset of the start of the basename. Unlike the other
+ * basename functions, this returns 0 if the path is empty.
+ */
+extern size_t git_path_basename_offset(git_buf *buffer);
+
extern const char *git_path_topdir(const char *path);
/**
@@ -80,7 +85,24 @@ extern int git_path_to_dir(git_buf *path);
*/
extern void git_path_string_to_dir(char* path, size_t size);
+/**
+ * Taken from git.git; returns nonzero if the given path is "." or "..".
+ */
+GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name)
+{
+ return (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')));
+}
+
#ifdef GIT_WIN32
+GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name)
+{
+ return (name[0] == L'.' &&
+ (name[1] == L'\0' ||
+ (name[1] == L'.' && name[2] == L'\0')));
+}
+
/**
* Convert backslashes in path to forward slashes.
*/
@@ -130,6 +152,11 @@ extern bool git_path_isdir(const char *path);
extern bool git_path_isfile(const char *path);
/**
+ * Check if the given path is a directory, and is empty.
+ */
+extern bool git_path_is_empty_dir(const char *path);
+
+/**
* Stat a file and/or link and set error if needed.
*/
extern int git_path_lstat(const char *path, struct stat *st);
@@ -164,6 +191,15 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
extern bool git_path_contains_file(git_buf *dir, const char *file);
/**
+ * Prepend base to unrooted path or just copy path over.
+ *
+ * This will optionally return the index into the path where the "root"
+ * is, either the end of the base directory prefix or the path root.
+ */
+extern int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
+
+/**
* Clean up path, prepending base if it is not already rooted.
*/
extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
@@ -186,6 +222,29 @@ extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char
extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
/**
+ * Resolve relative references within a path.
+ *
+ * This eliminates "./" and "../" relative references inside a path,
+ * as well as condensing multiple slashes into single ones. It will
+ * not touch the path before the "ceiling" length.
+ *
+ * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL
+ * prefix and not touch that part of the path.
+ */
+extern int git_path_resolve_relative(git_buf *path, size_t ceiling);
+
+/**
+ * Apply a relative path to base path.
+ *
+ * Note that the base path could be a filename or a URL and this
+ * should still work. The relative path is walked segment by segment
+ * with three rules: series of slashes will be condensed to a single
+ * slash, "." will be eaten with no change, and ".." will remove a
+ * segment from the base path.
+ */
+extern int git_path_apply_relative(git_buf *target, const char *relpath);
+
+/**
* Walk each directory entry, except '.' and '..', calling fn(state).
*
* @param pathbuf buffer the function reads the initial directory
@@ -194,6 +253,7 @@ extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
* the input state and the second arg is pathbuf. The function
* may modify the pathbuf, but only by appending new text.
* @param state to pass to fn as the first arg.
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
extern int git_path_direach(
git_buf *pathbuf,
@@ -201,11 +261,12 @@ extern int git_path_direach(
void *state);
/**
- * Sort function to order two paths.
+ * Sort function to order two paths
*/
extern int git_path_cmp(
const char *name1, size_t len1, int isdir1,
- const char *name2, size_t len2, int isdir2);
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t));
/**
* Invoke callback up path directory by directory until the ceiling is
@@ -261,18 +322,33 @@ typedef struct {
} git_path_with_stat;
extern int git_path_with_stat_cmp(const void *a, const void *b);
+extern int git_path_with_stat_cmp_icase(const void *a, const void *b);
/**
* Load all directory entries along with stat info into a vector.
*
- * This is just like git_path_dirload except that each entry in the
- * vector is a git_path_with_stat structure that contains both the
- * path and the stat info, plus directories will have a / suffixed
- * to their path name.
+ * This adds four things on top of plain `git_path_dirload`:
+ *
+ * 1. Each entry in the vector is a `git_path_with_stat` struct that
+ * contains both the path and the stat info
+ * 2. The entries will be sorted alphabetically
+ * 3. Entries that are directories will be suffixed with a '/'
+ * 4. Optionally, you can be a start and end prefix and only elements
+ * after the start and before the end (inclusively) will be stat'ed.
+ *
+ * @param path The directory to read from
+ * @param prefix_len The trailing part of path to prefix to entry paths
+ * @param ignore_case How to sort and compare paths with start/end limits
+ * @param start_stat As optimization, only stat values after this prefix
+ * @param end_stat As optimization, only stat values before this prefix
+ * @param contents Vector to fill with git_path_with_stat structures
*/
extern int git_path_dirload_with_stat(
const char *path,
size_t prefix_len,
+ bool ignore_case,
+ const char *start_stat,
+ const char *end_stat,
git_vector *contents);
#endif
diff --git a/src/pathspec.c b/src/pathspec.c
new file mode 100644
index 000000000..d4eb12582
--- /dev/null
+++ b/src/pathspec.c
@@ -0,0 +1,168 @@
+/*
+ * 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 "pathspec.h"
+#include "buf_text.h"
+#include "attr_file.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+char *git_pathspec_prefix(const git_strarray *pathspec)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ const char *scan;
+
+ if (!pathspec || !pathspec->count ||
+ git_buf_text_common_prefix(&prefix, pathspec) < 0)
+ return NULL;
+
+ /* diff prefix will only be leading non-wildcards */
+ for (scan = prefix.ptr; *scan; ++scan) {
+ if (git__iswildcard(*scan) &&
+ (scan == prefix.ptr || (*(scan - 1) != '\\')))
+ break;
+ }
+ git_buf_truncate(&prefix, scan - prefix.ptr);
+
+ if (prefix.size <= 0) {
+ git_buf_free(&prefix);
+ return NULL;
+ }
+
+ git_buf_text_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_pathspec_is_empty(const git_strarray *pathspec)
+{
+ size_t i;
+
+ if (pathspec == NULL)
+ return true;
+
+ for (i = 0; i < pathspec->count; ++i) {
+ const char *str = pathspec->strings[i];
+
+ if (str && str[0])
+ return false;
+ }
+
+ return true;
+}
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+int git_pathspec_init(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
+{
+ size_t i;
+
+ memset(vspec, 0, sizeof(*vspec));
+
+ if (git_pathspec_is_empty(strspec))
+ return 0;
+
+ if (git_vector_init(vspec, strspec->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < strspec->count; ++i) {
+ int ret;
+ const char *pattern = strspec->strings[i];
+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ return -1;
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+
+ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0)
+ return ret;
+
+ if (git_vector_insert(vspec, match) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* free data from the pathspec vector */
+void git_pathspec_free(git_vector *vspec)
+{
+ git_attr_fnmatch *match;
+ unsigned int i;
+
+ git_vector_foreach(vspec, i, match) {
+ git__free(match);
+ vspec->contents[i] = NULL;
+ }
+
+ git_vector_free(vspec);
+}
+
+/* match a path against the vectorized pathspec */
+bool git_pathspec_match_path(
+ git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec)
+{
+ size_t i;
+ git_attr_fnmatch *match;
+ int fnmatch_flags = 0;
+ int (*use_strcmp)(const char *, const char *);
+ int (*use_strncmp)(const char *, const char *, size_t);
+
+ if (matched_pathspec)
+ *matched_pathspec = NULL;
+
+ if (!vspec || !vspec->length)
+ return true;
+
+ if (disable_fnmatch)
+ fnmatch_flags = -1;
+ else if (casefold)
+ fnmatch_flags = FNM_CASEFOLD;
+
+ if (casefold) {
+ use_strcmp = git__strcasecmp;
+ use_strncmp = git__strncasecmp;
+ } else {
+ use_strcmp = git__strcmp;
+ use_strncmp = git__strncmp;
+ }
+
+ git_vector_foreach(vspec, i, match) {
+ int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
+
+ if (result == FNM_NOMATCH)
+ result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, fnmatch_flags);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ use_strncmp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ if (result == 0) {
+ if (matched_pathspec)
+ *matched_pathspec = match->pattern;
+
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/src/pathspec.h b/src/pathspec.h
new file mode 100644
index 000000000..43a94baad
--- /dev/null
+++ b/src/pathspec.h
@@ -0,0 +1,40 @@
+/*
+ * 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_pathspec_h__
+#define INCLUDE_pathspec_h__
+
+#include "common.h"
+#include "buffer.h"
+#include "vector.h"
+#include "pool.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+extern char *git_pathspec_prefix(const git_strarray *pathspec);
+
+/* is there anything in the spec that needs to be filtered on */
+extern bool git_pathspec_is_empty(const git_strarray *pathspec);
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+extern int git_pathspec_init(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool);
+
+/* free data from the pathspec vector */
+extern void git_pathspec_free(git_vector *vspec);
+
+/*
+ * Match a path against the vectorized pathspec.
+ * The matched pathspec is passed back into the `matched_pathspec` parameter,
+ * unless it is passed as NULL by the caller.
+ */
+extern bool git_pathspec_match_path(
+ git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec);
+
+#endif
diff --git a/src/pkt.h b/src/pkt.h
deleted file mode 100644
index 75442c833..000000000
--- a/src/pkt.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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_pkt_h__
-#define INCLUDE_pkt_h__
-
-#include "common.h"
-#include "transport.h"
-#include "buffer.h"
-#include "posix.h"
-#include "git2/net.h"
-
-enum git_pkt_type {
- GIT_PKT_CMD,
- GIT_PKT_FLUSH,
- GIT_PKT_REF,
- GIT_PKT_HAVE,
- GIT_PKT_ACK,
- GIT_PKT_NAK,
- GIT_PKT_PACK,
- GIT_PKT_COMMENT,
- GIT_PKT_ERR,
-};
-
-/* Used for multi-ack */
-enum git_ack_status {
- GIT_ACK_NONE,
- GIT_ACK_CONTINUE,
- GIT_ACK_COMMON,
- GIT_ACK_READY
-};
-
-/* This would be a flush pkt */
-typedef struct {
- enum git_pkt_type type;
-} git_pkt;
-
-struct git_pkt_cmd {
- enum git_pkt_type type;
- char *cmd;
- char *path;
- char *host;
-};
-
-/* This is a pkt-line with some info in it */
-typedef struct {
- enum git_pkt_type type;
- git_remote_head head;
- char *capabilities;
-} git_pkt_ref;
-
-/* Useful later */
-typedef struct {
- enum git_pkt_type type;
- git_oid oid;
- enum git_ack_status status;
-} git_pkt_ack;
-
-typedef struct {
- enum git_pkt_type type;
- char comment[GIT_FLEX_ARRAY];
-} git_pkt_comment;
-
-typedef struct {
- enum git_pkt_type type;
- char error[GIT_FLEX_ARRAY];
-} git_pkt_err;
-
-int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
-int git_pkt_buffer_flush(git_buf *buf);
-int git_pkt_send_flush(GIT_SOCKET s);
-int git_pkt_buffer_done(git_buf *buf);
-int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf);
-int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
-void git_pkt_free(git_pkt *pkt);
-
-#endif
diff --git a/src/pool.c b/src/pool.c
index 641292d06..b3cd49665 100644
--- a/src/pool.c
+++ b/src/pool.c
@@ -10,6 +10,10 @@ struct git_pool_page {
char data[GIT_FLEX_ARRAY];
};
+struct pool_freelist {
+ struct pool_freelist *next;
+};
+
#define GIT_POOL_MIN_USABLE 4
#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*)
@@ -150,7 +154,7 @@ void *git_pool_malloc(git_pool *pool, uint32_t items)
pool->has_multi_item_alloc = 1;
else if (pool->free_list != NULL) {
ptr = pool->free_list;
- pool->free_list = *((void **)pool->free_list);
+ pool->free_list = ((struct pool_freelist *)pool->free_list)->next;
return ptr;
}
@@ -206,6 +210,11 @@ char *git_pool_strdup(git_pool *pool, const char *str)
return git_pool_strndup(pool, str, strlen(str));
}
+char *git_pool_strdup_safe(git_pool *pool, const char *str)
+{
+ return str ? git_pool_strdup(pool, str) : NULL;
+}
+
char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
{
void *ptr;
@@ -230,10 +239,31 @@ char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
void git_pool_free(git_pool *pool, void *ptr)
{
- assert(pool && ptr && pool->item_size >= sizeof(void*));
+ struct pool_freelist *item = ptr;
+
+ assert(pool && pool->item_size >= sizeof(void*));
+
+ if (item) {
+ item->next = pool->free_list;
+ pool->free_list = item;
+ }
+}
+
+void git_pool_free_array(git_pool *pool, size_t count, void **ptrs)
+{
+ struct pool_freelist **items = (struct pool_freelist **)ptrs;
+ size_t i;
+
+ assert(pool && ptrs && pool->item_size >= sizeof(void*));
+
+ if (!count)
+ return;
+
+ for (i = count - 1; i > 0; --i)
+ items[i]->next = items[i - 1];
- *((void **)ptr) = pool->free_list;
- pool->free_list = ptr;
+ items[i]->next = pool->free_list;
+ pool->free_list = items[count - 1];
}
uint32_t git_pool__open_pages(git_pool *pool)
@@ -275,6 +305,8 @@ uint32_t git_pool__system_page_size(void)
SYSTEM_INFO info;
GetSystemInfo(&info);
size = (uint32_t)info.dwPageSize;
+#elif defined(__amigaos4__)
+ size = (uint32_t)4096; /* 4K as there is no global value we can query */
#else
size = (uint32_t)sysconf(_SC_PAGE_SIZE);
#endif
diff --git a/src/pool.h b/src/pool.h
index 54a2861ed..5ac9b764f 100644
--- a/src/pool.h
+++ b/src/pool.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * 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.
@@ -76,6 +76,17 @@ extern void git_pool_swap(git_pool *a, git_pool *b);
extern void *git_pool_malloc(git_pool *pool, uint32_t items);
/**
+ * Allocate space and zero it out.
+ */
+GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items)
+{
+ void *ptr = git_pool_malloc(pool, items);
+ if (ptr)
+ memset(ptr, 0, (size_t)items * (size_t)pool->item_size);
+ return ptr;
+}
+
+/**
* Allocate space and duplicate string data into it.
*
* This is allowed only for pools with item_size == sizeof(char)
@@ -90,6 +101,13 @@ extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n);
extern char *git_pool_strdup(git_pool *pool, const char *str);
/**
+ * Allocate space and duplicate a string into it, NULL is no error.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup_safe(git_pool *pool, const char *str);
+
+/**
* Allocate space for the concatenation of two strings.
*
* This is allowed only for pools with item_size == sizeof(char)
@@ -108,6 +126,13 @@ extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b);
*/
extern void git_pool_free(git_pool *pool, void *ptr);
+/**
+ * Push an array of pool allocated blocks efficiently onto the free list.
+ *
+ * This has the same constraints as `git_pool_free()` above.
+ */
+extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs);
+
/*
* Misc utilities
*/
diff --git a/src/posix.c b/src/posix.c
index a9a6af984..5d526d33c 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,12 +12,98 @@
#ifndef GIT_WIN32
+#ifdef NO_ADDRINFO
+
+int p_getaddrinfo(
+ const char *host,
+ const char *port,
+ struct addrinfo *hints,
+ struct addrinfo **info)
+{
+ struct addrinfo *ainfo, *ai;
+ int p = 0;
+
+ GIT_UNUSED(hints);
+
+ if ((ainfo = malloc(sizeof(struct addrinfo))) == NULL)
+ return -1;
+
+ if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) {
+ free(ainfo);
+ return -2;
+ }
+
+ ainfo->ai_servent = getservbyname(port, 0);
+
+ if (ainfo->ai_servent)
+ ainfo->ai_port = ainfo->ai_servent->s_port;
+ else
+ ainfo->ai_port = atol(port);
+
+ memcpy(&ainfo->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[0],
+ ainfo->ai_hostent->h_length);
+
+ ainfo->ai_protocol = 0;
+ ainfo->ai_socktype = hints->ai_socktype;
+ ainfo->ai_family = ainfo->ai_hostent->h_addrtype;
+ ainfo->ai_addr_in.sin_family = ainfo->ai_family;
+ ainfo->ai_addr_in.sin_port = ainfo->ai_port;
+ ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in;
+ ainfo->ai_addrlen = sizeof(struct sockaddr_in);
+
+ *info = ainfo;
+
+ if (ainfo->ai_hostent->h_addr_list[1] == NULL) {
+ ainfo->ai_next = NULL;
+ return 0;
+ }
+
+ ai = ainfo;
+
+ for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) {
+ ai->ai_next = malloc(sizeof(struct addrinfo));
+ memcpy(&ai->ai_next, ainfo, sizeof(struct addrinfo));
+ memcpy(&ai->ai_next->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[p],
+ ainfo->ai_hostent->h_length);
+ ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in;
+ ai = ai->ai_next;
+ }
+
+ ai->ai_next = NULL;
+ return 0;
+}
+
+void p_freeaddrinfo(struct addrinfo *info)
+{
+ struct addrinfo *p, *next;
+
+ p = info;
+
+ while(p != NULL) {
+ next = p->ai_next;
+ free(p);
+ p = next;
+ }
+}
+
+const char *p_gai_strerror(int ret)
+{
+ switch(ret) {
+ case -1: return "Out of memory"; break;
+ case -2: return "Address lookup failed"; break;
+ default: return "Unknown error"; break;
+ }
+}
+
+#endif /* NO_ADDRINFO */
+
int p_open(const char *path, int flags, ...)
{
mode_t mode = 0;
- if (flags & O_CREAT)
- {
+ if (flags & O_CREAT) {
va_list arg_list;
va_start(arg_list, flags);
@@ -63,11 +149,12 @@ int p_rename(const char *from, const char *to)
return -1;
}
-#endif
+#endif /* GIT_WIN32 */
int p_read(git_file fd, void *buf, size_t cnt)
{
char *b = buf;
+
while (cnt) {
ssize_t r;
#ifdef GIT_WIN32
@@ -92,6 +179,7 @@ int p_read(git_file fd, void *buf, size_t cnt)
int p_write(git_file fd, const void *buf, size_t cnt)
{
const char *b = buf;
+
while (cnt) {
ssize_t r;
#ifdef GIT_WIN32
@@ -114,3 +202,5 @@ int p_write(git_file fd, const void *buf, size_t cnt)
}
return 0;
}
+
+
diff --git a/src/posix.h b/src/posix.h
index d020d94ac..719c8a04c 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,9 +10,17 @@
#include "common.h"
#include <fcntl.h>
#include <time.h>
+#include "fnmatch.h"
+#ifndef S_IFGITLINK
#define S_IFGITLINK 0160000
#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
+#endif
+
+/* if S_ISGID is not defined, then don't try to set it */
+#ifndef S_ISGID
+#define S_ISGID 0
+#endif
#if !defined(O_BINARY)
#define O_BINARY 0
@@ -24,14 +32,13 @@ typedef int git_file;
* Standard POSIX Methods
*
* All the methods starting with the `p_` prefix are
- * direct ports of the standard POSIX methods.
+ * direct ports of the standard POSIX methods.
*
* Some of the methods are slightly wrapped to provide
* saner defaults. Some of these methods are emulated
* in Windows platforns.
*
* Use your manpages to check the docs on these.
- * Straightforward
*/
extern int p_read(git_file fd, void *buf, size_t cnt);
@@ -59,9 +66,18 @@ extern int p_rename(const char *from, const char *to);
typedef int GIT_SOCKET;
#define INVALID_SOCKET -1
+#define p_localtime_r localtime_r
+#define p_gmtime_r gmtime_r
+#define p_gettimeofday gettimeofday
+
#else
typedef SOCKET GIT_SOCKET;
+struct timezone;
+extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
+extern int p_gettimeofday(struct timeval *tv, struct timezone *tz);
+
#endif
@@ -74,6 +90,41 @@ typedef SOCKET GIT_SOCKET;
# include "unix/posix.h"
#endif
-#define p_readdir_r(d,e,r) readdir_r(d,e,r)
+#ifdef NO_READDIR_R
+# include <dirent.h>
+GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+ GIT_UNUSED(entry);
+ *result = readdir(dirp);
+ return 0;
+}
+#else /* NO_READDIR_R */
+# define p_readdir_r(d,e,r) readdir_r(d,e,r)
+#endif
+
+#ifdef NO_ADDRINFO
+# include <netdb.h>
+struct addrinfo {
+ struct hostent *ai_hostent;
+ struct servent *ai_servent;
+ struct sockaddr_in ai_addr_in;
+ struct sockaddr *ai_addr;
+ size_t ai_addrlen;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ long ai_port;
+ struct addrinfo *ai_next;
+};
+
+extern int p_getaddrinfo(const char *host, const char *port,
+ struct addrinfo *hints, struct addrinfo **info);
+extern void p_freeaddrinfo(struct addrinfo *info);
+extern const char *p_gai_strerror(int ret);
+#else
+# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d)
+# define p_freeaddrinfo(a) freeaddrinfo(a)
+# define p_gai_strerror(c) gai_strerror(c)
+#endif /* NO_ADDRINFO */
#endif
diff --git a/src/ppc/sha1.c b/src/ppc/sha1.c
deleted file mode 100644
index 803b81d0a..000000000
--- a/src/ppc/sha1.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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 <stdio.h>
-#include <string.h>
-#include "sha1.h"
-
-extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p,
- unsigned int nblocks);
-
-int ppc_SHA1_Init(ppc_SHA_CTX *c)
-{
- c->hash[0] = 0x67452301;
- c->hash[1] = 0xEFCDAB89;
- c->hash[2] = 0x98BADCFE;
- c->hash[3] = 0x10325476;
- c->hash[4] = 0xC3D2E1F0;
- c->len = 0;
- c->cnt = 0;
- return 0;
-}
-
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
-{
- unsigned long nb;
- const unsigned char *p = ptr;
-
- c->len += (uint64_t) n << 3;
- while (n != 0) {
- if (c->cnt || n < 64) {
- nb = 64 - c->cnt;
- if (nb > n)
- nb = n;
- memcpy(&c->buf.b[c->cnt], p, nb);
- if ((c->cnt += nb) == 64) {
- ppc_sha1_core(c->hash, c->buf.b, 1);
- c->cnt = 0;
- }
- } else {
- nb = n >> 6;
- ppc_sha1_core(c->hash, p, nb);
- nb <<= 6;
- }
- n -= nb;
- p += nb;
- }
- return 0;
-}
-
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
-{
- unsigned int cnt = c->cnt;
-
- c->buf.b[cnt++] = 0x80;
- if (cnt > 56) {
- if (cnt < 64)
- memset(&c->buf.b[cnt], 0, 64 - cnt);
- ppc_sha1_core(c->hash, c->buf.b, 1);
- cnt = 0;
- }
- if (cnt < 56)
- memset(&c->buf.b[cnt], 0, 56 - cnt);
- c->buf.l[7] = c->len;
- ppc_sha1_core(c->hash, c->buf.b, 1);
- memcpy(hash, c->hash, 20);
- return 0;
-}
diff --git a/src/ppc/sha1.h b/src/ppc/sha1.h
deleted file mode 100644
index aca4e5dda..000000000
--- a/src/ppc/sha1.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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 <stdint.h>
-
-typedef struct {
- uint32_t hash[5];
- uint32_t cnt;
- uint64_t len;
- union {
- unsigned char b[64];
- uint64_t l[8];
- } buf;
-} ppc_SHA_CTX;
-
-int ppc_SHA1_Init(ppc_SHA_CTX *c);
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *p, unsigned long n);
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c);
-
-#define SHA_CTX ppc_SHA_CTX
-#define SHA1_Init ppc_SHA1_Init
-#define SHA1_Update ppc_SHA1_Update
-#define SHA1_Final ppc_SHA1_Final
diff --git a/src/ppc/sha1ppc.S b/src/ppc/sha1ppc.S
deleted file mode 100644
index 1711eef6e..000000000
--- a/src/ppc/sha1ppc.S
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * SHA-1 implementation for PowerPC.
- *
- * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
- */
-
-/*
- * PowerPC calling convention:
- * %r0 - volatile temp
- * %r1 - stack pointer.
- * %r2 - reserved
- * %r3-%r12 - Incoming arguments & return values; volatile.
- * %r13-%r31 - Callee-save registers
- * %lr - Return address, volatile
- * %ctr - volatile
- *
- * Register usage in this routine:
- * %r0 - temp
- * %r3 - argument (pointer to 5 words of SHA state)
- * %r4 - argument (pointer to data to hash)
- * %r5 - Constant K in SHA round (initially number of blocks to hash)
- * %r6-%r10 - Working copies of SHA variables A..E (actually E..A order)
- * %r11-%r26 - Data being hashed W[].
- * %r27-%r31 - Previous copies of A..E, for final add back.
- * %ctr - loop count
- */
-
-
-/*
- * We roll the registers for A, B, C, D, E around on each
- * iteration; E on iteration t is D on iteration t+1, and so on.
- * We use registers 6 - 10 for this. (Registers 27 - 31 hold
- * the previous values.)
- */
-#define RA(t) (((t)+4)%5+6)
-#define RB(t) (((t)+3)%5+6)
-#define RC(t) (((t)+2)%5+6)
-#define RD(t) (((t)+1)%5+6)
-#define RE(t) (((t)+0)%5+6)
-
-/* We use registers 11 - 26 for the W values */
-#define W(t) ((t)%16+11)
-
-/* Register 5 is used for the constant k */
-
-/*
- * The basic SHA-1 round function is:
- * E += ROTL(A,5) + F(B,C,D) + W[i] + K; B = ROTL(B,30)
- * Then the variables are renamed: (A,B,C,D,E) = (E,A,B,C,D).
- *
- * Every 20 rounds, the function F() and the constant K changes:
- * - 20 rounds of f0(b,c,d) = "bit wise b ? c : d" = (^b & d) + (b & c)
- * - 20 rounds of f1(b,c,d) = b^c^d = (b^d)^c
- * - 20 rounds of f2(b,c,d) = majority(b,c,d) = (b&d) + ((b^d)&c)
- * - 20 more rounds of f1(b,c,d)
- *
- * These are all scheduled for near-optimal performance on a G4.
- * The G4 is a 3-issue out-of-order machine with 3 ALUs, but it can only
- * *consider* starting the oldest 3 instructions per cycle. So to get
- * maximum performance out of it, you have to treat it as an in-order
- * machine. Which means interleaving the computation round t with the
- * computation of W[t+4].
- *
- * The first 16 rounds use W values loaded directly from memory, while the
- * remaining 64 use values computed from those first 16. We preload
- * 4 values before starting, so there are three kinds of rounds:
- * - The first 12 (all f0) also load the W values from memory.
- * - The next 64 compute W(i+4) in parallel. 8*f0, 20*f1, 20*f2, 16*f1.
- * - The last 4 (all f1) do not do anything with W.
- *
- * Therefore, we have 6 different round functions:
- * STEPD0_LOAD(t,s) - Perform round t and load W(s). s < 16
- * STEPD0_UPDATE(t,s) - Perform round t and compute W(s). s >= 16.
- * STEPD1_UPDATE(t,s)
- * STEPD2_UPDATE(t,s)
- * STEPD1(t) - Perform round t with no load or update.
- *
- * The G5 is more fully out-of-order, and can find the parallelism
- * by itself. The big limit is that it has a 2-cycle ALU latency, so
- * even though it's 2-way, the code has to be scheduled as if it's
- * 4-way, which can be a limit. To help it, we try to schedule the
- * read of RA(t) as late as possible so it doesn't stall waiting for
- * the previous round's RE(t-1), and we try to rotate RB(t) as early
- * as possible while reading RC(t) (= RB(t-1)) as late as possible.
- */
-
-/* the initial loads. */
-#define LOADW(s) \
- lwz W(s),(s)*4(%r4)
-
-/*
- * Perform a step with F0, and load W(s). Uses W(s) as a temporary
- * before loading it.
- * This is actually 10 instructions, which is an awkward fit.
- * It can execute grouped as listed, or delayed one instruction.
- * (If delayed two instructions, there is a stall before the start of the
- * second line.) Thus, two iterations take 7 cycles, 3.5 cycles per round.
- */
-#define STEPD0_LOAD(t,s) \
-add RE(t),RE(t),W(t); andc %r0,RD(t),RB(t); and W(s),RC(t),RB(t); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; rotlwi RB(t),RB(t),30; \
-add RE(t),RE(t),W(s); add %r0,%r0,%r5; lwz W(s),(s)*4(%r4); \
-add RE(t),RE(t),%r0
-
-/*
- * This is likewise awkward, 13 instructions. However, it can also
- * execute starting with 2 out of 3 possible moduli, so it does 2 rounds
- * in 9 cycles, 4.5 cycles/round.
- */
-#define STEPD0_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); andc %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r0; and %r0,RC(t),RB(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r5; loadk; rotlwi RB(t),RB(t),30; rotlwi W(s),W(s),1; \
-add RE(t),RE(t),%r0
-
-/* Nicely optimal. Conveniently, also the most common. */
-#define STEPD1_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); xor %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r5; loadk; xor %r0,%r0,RC(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30; rotlwi W(s),W(s),1
-
-/*
- * The naked version, no UPDATE, for the last 4 rounds. 3 cycles per.
- * We could use W(s) as a temp register, but we don't need it.
- */
-#define STEPD1(t) \
- add RE(t),RE(t),W(t); xor %r0,RD(t),RB(t); \
-rotlwi RB(t),RB(t),30; add RE(t),RE(t),%r5; xor %r0,%r0,RC(t); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; /* spare slot */ \
-add RE(t),RE(t),%r0
-
-/*
- * 14 instructions, 5 cycles per. The majority function is a bit
- * awkward to compute. This can execute with a 1-instruction delay,
- * but it causes a 2-instruction delay, which triggers a stall.
- */
-#define STEPD2_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); and %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r0; xor %r0,RD(t),RB(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r5; loadk; and %r0,%r0,RC(t); xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; rotlwi W(s),W(s),1; \
-add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30
-
-#define STEP0_LOAD4(t,s) \
- STEPD0_LOAD(t,s); \
- STEPD0_LOAD((t+1),(s)+1); \
- STEPD0_LOAD((t)+2,(s)+2); \
- STEPD0_LOAD((t)+3,(s)+3)
-
-#define STEPUP4(fn, t, s, loadk...) \
- STEP##fn##_UPDATE(t,s,); \
- STEP##fn##_UPDATE((t)+1,(s)+1,); \
- STEP##fn##_UPDATE((t)+2,(s)+2,); \
- STEP##fn##_UPDATE((t)+3,(s)+3,loadk)
-
-#define STEPUP20(fn, t, s, loadk...) \
- STEPUP4(fn, t, s,); \
- STEPUP4(fn, (t)+4, (s)+4,); \
- STEPUP4(fn, (t)+8, (s)+8,); \
- STEPUP4(fn, (t)+12, (s)+12,); \
- STEPUP4(fn, (t)+16, (s)+16, loadk)
-
- .globl ppc_sha1_core
-ppc_sha1_core:
- stwu %r1,-80(%r1)
- stmw %r13,4(%r1)
-
- /* Load up A - E */
- lmw %r27,0(%r3)
-
- mtctr %r5
-
-1:
- LOADW(0)
- lis %r5,0x5a82
- mr RE(0),%r31
- LOADW(1)
- mr RD(0),%r30
- mr RC(0),%r29
- LOADW(2)
- ori %r5,%r5,0x7999 /* K0-19 */
- mr RB(0),%r28
- LOADW(3)
- mr RA(0),%r27
-
- STEP0_LOAD4(0, 4)
- STEP0_LOAD4(4, 8)
- STEP0_LOAD4(8, 12)
- STEPUP4(D0, 12, 16,)
- STEPUP4(D0, 16, 20, lis %r5,0x6ed9)
-
- ori %r5,%r5,0xeba1 /* K20-39 */
- STEPUP20(D1, 20, 24, lis %r5,0x8f1b)
-
- ori %r5,%r5,0xbcdc /* K40-59 */
- STEPUP20(D2, 40, 44, lis %r5,0xca62)
-
- ori %r5,%r5,0xc1d6 /* K60-79 */
- STEPUP4(D1, 60, 64,)
- STEPUP4(D1, 64, 68,)
- STEPUP4(D1, 68, 72,)
- STEPUP4(D1, 72, 76,)
- addi %r4,%r4,64
- STEPD1(76)
- STEPD1(77)
- STEPD1(78)
- STEPD1(79)
-
- /* Add results to original values */
- add %r31,%r31,RE(0)
- add %r30,%r30,RD(0)
- add %r29,%r29,RC(0)
- add %r28,%r28,RB(0)
- add %r27,%r27,RA(0)
-
- bdnz 1b
-
- /* Save final hash, restore registers, and return */
- stmw %r27,0(%r3)
- lmw %r13,4(%r1)
- addi %r1,%r1,80
- blr
diff --git a/src/pqueue.c b/src/pqueue.c
index cb59c13ec..7819ed41e 100644
--- a/src/pqueue.c
+++ b/src/pqueue.c
@@ -1,8 +1,30 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
+ *
+ * This file is based on a modified version of the priority queue found
+ * in the Apache project and libpqueue library.
+ *
+ * https://github.com/vy/libpqueue
+ *
+ * Original file notice:
+ *
+ * Copyright 2010 Volkan Yazici <volkan.yazici@gmail.com>
+ * Copyright 2006-2010 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
*/
#include "common.h"
diff --git a/src/pqueue.h b/src/pqueue.h
index a3e1edd1d..ed7139285 100644
--- a/src/pqueue.h
+++ b/src/pqueue.h
@@ -1,8 +1,30 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
+ *
+ * This file is based on a modified version of the priority queue found
+ * in the Apache project and libpqueue library.
+ *
+ * https://github.com/vy/libpqueue
+ *
+ * Original file notice:
+ *
+ * Copyright 2010 Volkan Yazici <volkan.yazici@gmail.com>
+ * Copyright 2006-2010 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
*/
#ifndef INCLUDE_pqueue_h__
diff --git a/src/protocol.c b/src/protocol.c
deleted file mode 100644
index 6b3861796..000000000
--- a/src/protocol.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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 "protocol.h"
-#include "pkt.h"
-#include "buffer.h"
-
-int git_protocol_store_refs(git_protocol *p, const char *data, size_t len)
-{
- git_buf *buf = &p->buf;
- git_vector *refs = p->refs;
- int error;
- const char *line_end, *ptr;
-
- if (len == 0) { /* EOF */
- if (git_buf_len(buf) != 0) {
- giterr_set(GITERR_NET, "Unexpected EOF");
- return p->error = -1;
- } else {
- return 0;
- }
- }
-
- git_buf_put(buf, data, len);
- ptr = buf->ptr;
- while (1) {
- git_pkt *pkt;
-
- if (git_buf_len(buf) == 0)
- return 0;
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf));
- if (error == GIT_EBUFS)
- return 0; /* Ask for more */
- if (error < 0)
- return p->error = -1;
-
- git_buf_consume(buf, line_end);
-
- if (pkt->type == GIT_PKT_ERR) {
- giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
- git__free(pkt);
- return -1;
- }
-
- if (git_vector_insert(refs, pkt) < 0)
- return p->error = -1;
-
- if (pkt->type == GIT_PKT_FLUSH)
- p->flush = 1;
- }
-
- return 0;
-}
diff --git a/src/protocol.h b/src/protocol.h
deleted file mode 100644
index a6c3e0735..000000000
--- a/src/protocol.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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_protocol_h__
-#define INCLUDE_protocol_h__
-
-#include "transport.h"
-#include "buffer.h"
-
-typedef struct {
- git_transport *transport;
- git_vector *refs;
- git_buf buf;
- int error;
- unsigned int flush :1;
-} git_protocol;
-
-int git_protocol_store_refs(git_protocol *p, const char *data, size_t len);
-
-#endif
diff --git a/src/push.c b/src/push.c
new file mode 100644
index 000000000..cec4c64af
--- /dev/null
+++ b/src/push.c
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+
+#include "common.h"
+#include "pack.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "vector.h"
+#include "push.h"
+#include "tree.h"
+
+static int push_spec_rref_cmp(const void *a, const void *b)
+{
+ const push_spec *push_spec_a = a, *push_spec_b = b;
+
+ return strcmp(push_spec_a->rref, push_spec_b->rref);
+}
+
+static int push_status_ref_cmp(const void *a, const void *b)
+{
+ const push_status *push_status_a = a, *push_status_b = b;
+
+ return strcmp(push_status_a->ref, push_status_b->ref);
+}
+
+int git_push_new(git_push **out, git_remote *remote)
+{
+ git_push *p;
+
+ *out = NULL;
+
+ p = git__calloc(1, sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ p->repo = remote->repo;
+ p->remote = remote;
+ p->report_status = 1;
+ p->pb_parallelism = 1;
+
+ if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) {
+ git__free(p);
+ return -1;
+ }
+
+ if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) {
+ git_vector_free(&p->specs);
+ git__free(p);
+ return -1;
+ }
+
+ *out = p;
+ return 0;
+}
+
+int git_push_set_options(git_push *push, const git_push_options *opts)
+{
+ if (!push || !opts)
+ return -1;
+
+ GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options");
+
+ push->pb_parallelism = opts->pb_parallelism;
+
+ return 0;
+}
+
+static void free_refspec(push_spec *spec)
+{
+ if (spec == NULL)
+ return;
+
+ if (spec->lref)
+ git__free(spec->lref);
+
+ if (spec->rref)
+ git__free(spec->rref);
+
+ git__free(spec);
+}
+
+static int check_rref(char *ref)
+{
+ if (git__prefixcmp(ref, "refs/")) {
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_lref(git_push *push, char *ref)
+{
+ /* lref must be resolvable to an existing object */
+ git_object *obj;
+ int error = git_revparse_single(&obj, push->repo, ref);
+ git_object_free(obj);
+
+ if (!error)
+ return 0;
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_REFERENCE,
+ "src refspec '%s' does not match any existing object", ref);
+ else
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+}
+
+static int parse_refspec(git_push *push, push_spec **spec, const char *str)
+{
+ push_spec *s;
+ char *delim;
+
+ *spec = NULL;
+
+ s = git__calloc(1, sizeof(*s));
+ GITERR_CHECK_ALLOC(s);
+
+ if (str[0] == '+') {
+ s->force = true;
+ str++;
+ }
+
+ delim = strchr(str, ':');
+ if (delim == NULL) {
+ s->lref = git__strdup(str);
+ if (!s->lref || check_lref(push, s->lref) < 0)
+ goto on_error;
+ } else {
+ if (delim - str) {
+ s->lref = git__strndup(str, delim - str);
+ if (!s->lref || check_lref(push, s->lref) < 0)
+ goto on_error;
+ }
+
+ if (strlen(delim + 1)) {
+ s->rref = git__strdup(delim + 1);
+ if (!s->rref || check_rref(s->rref) < 0)
+ goto on_error;
+ }
+ }
+
+ if (!s->lref && !s->rref)
+ goto on_error;
+
+ /* If rref is ommitted, use the same ref name as lref */
+ if (!s->rref) {
+ s->rref = git__strdup(s->lref);
+ if (!s->rref || check_rref(s->rref) < 0)
+ goto on_error;
+ }
+
+ *spec = s;
+ return 0;
+
+on_error:
+ free_refspec(s);
+ return -1;
+}
+
+int git_push_add_refspec(git_push *push, const char *refspec)
+{
+ push_spec *spec;
+
+ if (parse_refspec(push, &spec, refspec) < 0 ||
+ git_vector_insert(&push->specs, spec) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_push_update_tips(git_push *push)
+{
+ git_refspec *fetch_spec = &push->remote->fetch;
+ git_buf remote_ref_name = GIT_BUF_INIT;
+ size_t i, j;
+ push_spec *push_spec;
+ git_reference *remote_ref;
+ push_status *status;
+ int error = 0;
+
+ git_vector_foreach(&push->status, i, status) {
+ /* If this ref update was successful (ok, not ng), it will have an empty message */
+ if (status->msg)
+ continue;
+
+ /* Find the corresponding remote ref */
+ if (!git_refspec_src_matches(fetch_spec, status->ref))
+ continue;
+
+ if ((error = git_refspec_transform_r(&remote_ref_name, fetch_spec, status->ref)) < 0)
+ goto on_error;
+
+ /* Find matching push ref spec */
+ git_vector_foreach(&push->specs, j, push_spec) {
+ if (!strcmp(push_spec->rref, status->ref))
+ break;
+ }
+
+ /* Could not find the corresponding push ref spec for this push update */
+ if (j == push->specs.length)
+ continue;
+
+ /* Update the remote ref */
+ if (git_oid_iszero(&push_spec->loid)) {
+ error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));
+
+ if (!error) {
+ if ((error = git_reference_delete(remote_ref)) < 0) {
+ git_reference_free(remote_ref);
+ goto on_error;
+ }
+ git_reference_free(remote_ref);
+ } else if (error == GIT_ENOTFOUND)
+ giterr_clear();
+ else
+ goto on_error;
+ } else if ((error = git_reference_create(NULL, push->remote->repo, git_buf_cstr(&remote_ref_name), &push_spec->loid, 1)) < 0)
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_buf_free(&remote_ref_name);
+ return error;
+}
+
+static int revwalk(git_vector *commits, git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ git_revwalk *rw;
+ git_oid oid;
+ unsigned int i;
+ int error = -1;
+
+ if (git_revwalk_new(&rw, push->repo) < 0)
+ return -1;
+
+ git_revwalk_sorting(rw, GIT_SORT_TIME);
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_otype type;
+ size_t size;
+
+ if (git_oid_iszero(&spec->loid))
+ /*
+ * Delete reference on remote side;
+ * nothing to do here.
+ */
+ continue;
+
+ if (git_oid_equal(&spec->loid, &spec->roid))
+ continue; /* up-to-date */
+
+ if (git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid) < 0)
+ goto on_error;
+
+ if (type == GIT_OBJ_TAG) {
+ git_tag *tag;
+ git_object *target;
+
+ if (git_packbuilder_insert(push->pb, &spec->loid, NULL) < 0)
+ goto on_error;
+
+ if (git_tag_lookup(&tag, push->repo, &spec->loid) < 0)
+ goto on_error;
+
+ if (git_tag_peel(&target, tag) < 0) {
+ git_tag_free(tag);
+ goto on_error;
+ }
+ git_tag_free(tag);
+
+ if (git_object_type(target) == GIT_OBJ_COMMIT) {
+ if (git_revwalk_push(rw, git_object_id(target)) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ } else {
+ if (git_packbuilder_insert(
+ push->pb, git_object_id(target), NULL) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ }
+ git_object_free(target);
+ } else if (git_revwalk_push(rw, &spec->loid) < 0)
+ goto on_error;
+
+ if (!spec->force) {
+ git_oid base;
+
+ if (git_oid_iszero(&spec->roid))
+ continue;
+
+ if (!git_odb_exists(push->repo->_odb, &spec->roid)) {
+ giterr_clear();
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ error = git_merge_base(&base, push->repo,
+ &spec->loid, &spec->roid);
+
+ if (error == GIT_ENOTFOUND ||
+ (!error && !git_oid_equal(&base, &spec->roid))) {
+ giterr_clear();
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ if (git_oid_iszero(&head->oid))
+ continue;
+
+ /* TODO */
+ git_revwalk_hide(rw, &head->oid);
+ }
+
+ while ((error = git_revwalk_next(&oid, rw)) == 0) {
+ git_oid *o = git__malloc(GIT_OID_RAWSZ);
+ GITERR_CHECK_ALLOC(o);
+ git_oid_cpy(o, &oid);
+ if (git_vector_insert(commits, o) < 0) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+on_error:
+ git_revwalk_free(rw);
+ return error == GIT_ITEROVER ? 0 : error;
+}
+
+static int enqueue_object(
+ const git_tree_entry *entry,
+ git_packbuilder *pb)
+{
+ switch (git_tree_entry_type(entry)) {
+ case GIT_OBJ_COMMIT:
+ return 0;
+ case GIT_OBJ_TREE:
+ return git_packbuilder_insert_tree(pb, &entry->oid);
+ default:
+ return git_packbuilder_insert(pb, &entry->oid, entry->filename);
+ }
+}
+
+static int queue_differences(
+ git_tree *base,
+ git_tree *delta,
+ git_packbuilder *pb)
+{
+ git_tree *b_child = NULL, *d_child = NULL;
+ size_t b_length = git_tree_entrycount(base);
+ size_t d_length = git_tree_entrycount(delta);
+ size_t i = 0, j = 0;
+ int error;
+
+ while (i < b_length && j < d_length) {
+ const git_tree_entry *b_entry = git_tree_entry_byindex(base, i);
+ 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))
+ goto loop;
+
+ cmp = strcmp(b_entry->filename, d_entry->filename);
+
+ /* If the entries are both trees and they have the same name but are
+ * different, then we'll recurse after adding the right-hand entry */
+ if (!cmp &&
+ git_tree_entry__is_tree(b_entry) &&
+ git_tree_entry__is_tree(d_entry)) {
+ /* Add the right-hand entry */
+ if ((error = git_packbuilder_insert(pb, &d_entry->oid,
+ d_entry->filename)) < 0)
+ goto on_error;
+
+ /* Acquire the subtrees and recurse */
+ if ((error = git_tree_lookup(&b_child,
+ git_tree_owner(base), &b_entry->oid)) < 0 ||
+ (error = git_tree_lookup(&d_child,
+ git_tree_owner(delta), &d_entry->oid)) < 0 ||
+ (error = queue_differences(b_child, d_child, pb)) < 0)
+ goto on_error;
+
+ git_tree_free(b_child); b_child = NULL;
+ git_tree_free(d_child); d_child = NULL;
+ }
+ /* If the object is new or different in the right-hand tree,
+ * then enumerate it */
+ else if (cmp >= 0 &&
+ (error = enqueue_object(d_entry, pb)) < 0)
+ goto on_error;
+
+ loop:
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+ }
+
+ /* Drain the right-hand tree of entries */
+ for (; j < d_length; j++)
+ if ((error = enqueue_object(git_tree_entry_byindex(delta, j), pb)) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (b_child)
+ git_tree_free(b_child);
+
+ if (d_child)
+ git_tree_free(d_child);
+
+ return error;
+}
+
+static int queue_objects(git_push *push)
+{
+ git_vector commits = GIT_VECTOR_INIT;
+ git_oid *oid;
+ size_t i;
+ unsigned j;
+ int error;
+
+ if ((error = revwalk(&commits, push)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&commits, i, oid) {
+ git_commit *parent = NULL, *commit;
+ git_tree *tree = NULL, *ptree = NULL;
+ size_t parentcount;
+
+ if ((error = git_commit_lookup(&commit, push->repo, oid)) < 0)
+ goto on_error;
+
+ /* Insert the commit */
+ if ((error = git_packbuilder_insert(push->pb, oid, NULL)) < 0)
+ goto loop_error;
+
+ parentcount = git_commit_parentcount(commit);
+
+ if (!parentcount) {
+ if ((error = git_packbuilder_insert_tree(push->pb,
+ git_commit_tree_id(commit))) < 0)
+ goto loop_error;
+ } else {
+ if ((error = git_tree_lookup(&tree, push->repo,
+ git_commit_tree_id(commit))) < 0 ||
+ (error = git_packbuilder_insert(push->pb,
+ git_commit_tree_id(commit), NULL)) < 0)
+ goto loop_error;
+
+ /* For each parent, add the items which are different */
+ for (j = 0; j < parentcount; j++) {
+ if ((error = git_commit_parent(&parent, commit, j)) < 0 ||
+ (error = git_commit_tree(&ptree, parent)) < 0 ||
+ (error = queue_differences(ptree, tree, push->pb)) < 0)
+ goto loop_error;
+
+ git_tree_free(ptree); ptree = NULL;
+ git_commit_free(parent); parent = NULL;
+ }
+ }
+
+ error = 0;
+
+ loop_error:
+ if (tree)
+ git_tree_free(tree);
+
+ if (ptree)
+ git_tree_free(ptree);
+
+ if (parent)
+ git_commit_free(parent);
+
+ git_commit_free(commit);
+
+ if (error < 0)
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_vector_foreach(&commits, i, oid)
+ git__free(oid);
+
+ git_vector_free(&commits);
+ return error;
+}
+
+static int calculate_work(git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j;
+
+ /* Update local and remote oids*/
+
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->lref) {
+ /* This is a create or update. Local ref must exist. */
+ if (git_reference_name_to_id(
+ &spec->loid, push->repo, spec->lref) < 0) {
+ giterr_set(GIT_ENOTFOUND, "No such reference '%s'", spec->lref);
+ return -1;
+ }
+ }
+
+ if (spec->rref) {
+ /* Remote ref may or may not (e.g. during create) already exist. */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->rref, head->name)) {
+ git_oid_cpy(&spec->roid, &head->oid);
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int do_push(git_push *push)
+{
+ int error;
+ git_transport *transport = push->remote->transport;
+
+ if (!transport->push) {
+ giterr_set(GITERR_NET, "Remote transport doesn't support push");
+ error = -1;
+ goto on_error;
+ }
+
+ /*
+ * A pack-file MUST be sent if either create or update command
+ * is used, even if the server already has all the necessary
+ * objects. In this case the client MUST send an empty pack-file.
+ */
+
+ if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0)
+ goto on_error;
+
+ git_packbuilder_set_threads(push->pb, push->pb_parallelism);
+
+ if ((error = calculate_work(push)) < 0 ||
+ (error = queue_objects(push)) < 0 ||
+ (error = transport->push(transport, push)) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ git_packbuilder_free(push->pb);
+ return error;
+}
+
+static int cb_filter_refs(git_remote_head *ref, void *data)
+{
+ git_remote *remote = (git_remote *) data;
+ return git_vector_insert(&remote->refs, ref);
+}
+
+static int filter_refs(git_remote *remote)
+{
+ git_vector_clear(&remote->refs);
+ return git_remote_ls(remote, cb_filter_refs, remote);
+}
+
+int git_push_finish(git_push *push)
+{
+ int error;
+
+ if (!git_remote_connected(push->remote) &&
+ (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH)) < 0)
+ return error;
+
+ if ((error = filter_refs(push->remote)) < 0 ||
+ (error = do_push(push)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_push_unpack_ok(git_push *push)
+{
+ return push->unpack_ok;
+}
+
+int git_push_status_foreach(git_push *push,
+ int (*cb)(const char *ref, const char *msg, void *data),
+ void *data)
+{
+ push_status *status;
+ unsigned int i;
+
+ git_vector_foreach(&push->status, i, status) {
+ if (cb(status->ref, status->msg, data) < 0)
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+void git_push_status_free(push_status *status)
+{
+ if (status == NULL)
+ return;
+
+ if (status->msg)
+ git__free(status->msg);
+
+ git__free(status->ref);
+ git__free(status);
+}
+
+void git_push_free(git_push *push)
+{
+ push_spec *spec;
+ push_status *status;
+ unsigned int i;
+
+ if (push == NULL)
+ return;
+
+ git_vector_foreach(&push->specs, i, spec) {
+ free_refspec(spec);
+ }
+ git_vector_free(&push->specs);
+
+ git_vector_foreach(&push->status, i, status) {
+ git_push_status_free(status);
+ }
+ git_vector_free(&push->status);
+
+ git__free(push);
+}
diff --git a/src/push.h b/src/push.h
new file mode 100644
index 000000000..e982b8385
--- /dev/null
+++ b/src/push.h
@@ -0,0 +1,51 @@
+/*
+ * 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_push_h__
+#define INCLUDE_push_h__
+
+#include "git2.h"
+
+typedef struct push_spec {
+ char *lref;
+ char *rref;
+
+ git_oid loid;
+ git_oid roid;
+
+ bool force;
+} push_spec;
+
+typedef struct push_status {
+ bool ok;
+
+ char *ref;
+ char *msg;
+} push_status;
+
+struct git_push {
+ git_repository *repo;
+ git_packbuilder *pb;
+ git_remote *remote;
+ git_vector specs;
+ bool report_status;
+
+ /* report-status */
+ bool unpack_ok;
+ git_vector status;
+
+ /* options */
+ unsigned pb_parallelism;
+};
+
+/**
+ * Free the given push status object
+ *
+ * @param status The push status object
+ */
+void git_push_status_free(push_status *status);
+
+#endif
diff --git a/src/refdb.c b/src/refdb.c
new file mode 100644
index 000000000..d9b73c6e7
--- /dev/null
+++ b/src/refdb.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+#include "git2/object.h"
+#include "git2/refs.h"
+#include "git2/refdb.h"
+#include "hash.h"
+#include "refdb.h"
+#include "refs.h"
+
+#include "git2/refdb_backend.h"
+
+int git_refdb_new(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+
+ assert(out && repo);
+
+ db = git__calloc(1, sizeof(*db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->repo = repo;
+
+ *out = db;
+ GIT_REFCOUNT_INC(db);
+ return 0;
+}
+
+int git_refdb_open(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+ git_refdb_backend *dir;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (git_refdb_new(&db, repo) < 0)
+ return -1;
+
+ /* Add the default (filesystem) backend */
+ if (git_refdb_backend_fs(&dir, repo, db) < 0) {
+ git_refdb_free(db);
+ return -1;
+ }
+
+ db->repo = repo;
+ db->backend = dir;
+
+ *out = db;
+ return 0;
+}
+
+int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+{
+ if (db->backend) {
+ if(db->backend->free)
+ db->backend->free(db->backend);
+ else
+ git__free(db->backend);
+ }
+
+ db->backend = backend;
+
+ return 0;
+}
+
+int git_refdb_compress(git_refdb *db)
+{
+ assert(db);
+
+ if (db->backend->compress) {
+ return db->backend->compress(db->backend);
+ }
+
+ return 0;
+}
+
+static void refdb_free(git_refdb *db)
+{
+ if (db->backend) {
+ if(db->backend->free)
+ db->backend->free(db->backend);
+ else
+ git__free(db->backend);
+ }
+
+ git__free(db);
+}
+
+void git_refdb_free(git_refdb *db)
+{
+ if (db == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(db, refdb_free);
+}
+
+int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
+{
+ assert(exists && refdb && refdb->backend);
+
+ return refdb->backend->exists(exists, refdb->backend, ref_name);
+}
+
+int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
+{
+ assert(db && db->backend && ref_name);
+
+ return db->backend->lookup(out, db->backend, ref_name);
+}
+
+int git_refdb_foreach(
+ git_refdb *db,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ assert(db && db->backend);
+
+ return db->backend->foreach(db->backend, list_flags, callback, payload);
+}
+
+struct glob_cb_data {
+ const char *glob;
+ git_reference_foreach_cb callback;
+ void *payload;
+};
+
+static int fromglob_cb(const char *reference_name, void *payload)
+{
+ struct glob_cb_data *data = (struct glob_cb_data *)payload;
+
+ if (!p_fnmatch(data->glob, reference_name, 0))
+ return data->callback(reference_name, data->payload);
+
+ return 0;
+}
+
+int git_refdb_foreach_glob(
+ git_refdb *db,
+ const char *glob,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ int error;
+ struct glob_cb_data data;
+
+ assert(db && db->backend && glob && callback);
+
+ if(db->backend->foreach_glob != NULL)
+ error = db->backend->foreach_glob(db->backend,
+ glob, list_flags, callback, payload);
+ else {
+ data.glob = glob;
+ data.callback = callback;
+ data.payload = payload;
+
+ error = db->backend->foreach(db->backend,
+ list_flags, fromglob_cb, &data);
+ }
+
+ return error;
+}
+
+int git_refdb_write(git_refdb *db, const git_reference *ref)
+{
+ assert(db && db->backend);
+
+ return db->backend->write(db->backend, ref);
+}
+
+int git_refdb_delete(struct git_refdb *db, const git_reference *ref)
+{
+ assert(db && db->backend);
+
+ return db->backend->delete(db->backend, ref);
+}
diff --git a/src/refdb.h b/src/refdb.h
new file mode 100644
index 000000000..0969711b9
--- /dev/null
+++ b/src/refdb.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_h__
+#define INCLUDE_refdb_h__
+
+#include "git2/refdb.h"
+#include "repository.h"
+
+struct git_refdb {
+ git_refcount rc;
+ git_repository *repo;
+ git_refdb_backend *backend;
+};
+
+int git_refdb_exists(
+ int *exists,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_lookup(
+ git_reference **out,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_foreach(
+ git_refdb *refdb,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+int git_refdb_foreach_glob(
+ git_refdb *refdb,
+ const char *glob,
+ unsigned int list_flags,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+int git_refdb_write(git_refdb *refdb, const git_reference *ref);
+
+int git_refdb_delete(struct git_refdb *refdb, const git_reference *ref);
+
+#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
new file mode 100644
index 000000000..f00bd72a0
--- /dev/null
+++ b/src/refdb_fs.c
@@ -0,0 +1,1023 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "refs.h"
+#include "hash.h"
+#include "repository.h"
+#include "fileops.h"
+#include "pack.h"
+#include "reflog.h"
+#include "config.h"
+#include "refdb.h"
+#include "refdb_fs.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/refdb.h>
+#include <git2/refdb_backend.h>
+
+GIT__USE_STRMAP;
+
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
+
+enum {
+ GIT_PACKREF_HAS_PEEL = 1,
+ GIT_PACKREF_WAS_LOOSE = 2
+};
+
+struct packref {
+ git_oid oid;
+ git_oid peel;
+ char flags;
+ char name[GIT_FLEX_ARRAY];
+};
+
+typedef struct refdb_fs_backend {
+ git_refdb_backend parent;
+
+ git_repository *repo;
+ const char *path;
+ git_refdb *refdb;
+
+ git_refcache refcache;
+} refdb_fs_backend;
+
+static int reference_read(
+ git_buf *file_content,
+ time_t *mtime,
+ const char *repo_path,
+ const char *ref_name,
+ int *updated)
+{
+ git_buf path = GIT_BUF_INIT;
+ int result;
+
+ assert(file_content && repo_path && ref_name);
+
+ /* Determine the full path of the file */
+ if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
+ return -1;
+
+ result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated);
+ git_buf_free(&path);
+
+ return result;
+}
+
+static int packed_parse_oid(
+ struct packref **ref_out,
+ const char **buffer_out,
+ const char *buffer_end)
+{
+ struct packref *ref = NULL;
+
+ const char *buffer = *buffer_out;
+ const char *refname_begin, *refname_end;
+
+ size_t refname_len;
+ git_oid id;
+
+ refname_begin = (buffer + GIT_OID_HEXSZ + 1);
+ if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
+ goto corrupt;
+
+ /* Is this a valid object id? */
+ if (git_oid_fromstr(&id, buffer) < 0)
+ goto corrupt;
+
+ refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
+ if (refname_end == NULL)
+ refname_end = buffer_end;
+
+ if (refname_end[-1] == '\r')
+ refname_end--;
+
+ refname_len = refname_end - refname_begin;
+
+ ref = git__malloc(sizeof(struct packref) + refname_len + 1);
+ GITERR_CHECK_ALLOC(ref);
+
+ memcpy(ref->name, refname_begin, refname_len);
+ ref->name[refname_len] = 0;
+
+ git_oid_cpy(&ref->oid, &id);
+
+ ref->flags = 0;
+
+ *ref_out = ref;
+ *buffer_out = refname_end + 1;
+
+ return 0;
+
+corrupt:
+ git__free(ref);
+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
+ return -1;
+}
+
+static int packed_parse_peel(
+ struct packref *tag_ref,
+ const char **buffer_out,
+ const char *buffer_end)
+{
+ const char *buffer = *buffer_out + 1;
+
+ assert(buffer[-1] == '^');
+
+ /* Ensure it's not the first entry of the file */
+ if (tag_ref == NULL)
+ goto corrupt;
+
+ /* Ensure reference is a tag */
+ if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
+ goto corrupt;
+
+ if (buffer + GIT_OID_HEXSZ > buffer_end)
+ goto corrupt;
+
+ /* Is this a valid object id? */
+ if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
+ goto corrupt;
+
+ buffer = buffer + GIT_OID_HEXSZ;
+ if (*buffer == '\r')
+ buffer++;
+
+ if (buffer != buffer_end) {
+ if (*buffer == '\n')
+ buffer++;
+ else
+ goto corrupt;
+ }
+
+ *buffer_out = buffer;
+ return 0;
+
+corrupt:
+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
+ return -1;
+}
+
+static int packed_load(refdb_fs_backend *backend)
+{
+ int result, updated;
+ git_buf packfile = GIT_BUF_INIT;
+ const char *buffer_start, *buffer_end;
+ git_refcache *ref_cache = &backend->refcache;
+
+ /* First we make sure we have allocated the hash table */
+ if (ref_cache->packfile == NULL) {
+ ref_cache->packfile = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(ref_cache->packfile);
+ }
+
+ result = reference_read(&packfile, &ref_cache->packfile_time,
+ backend->path, GIT_PACKEDREFS_FILE, &updated);
+
+ /*
+ * If we couldn't find the file, we need to clear the table and
+ * return. On any other error, we return that error. If everything
+ * went fine and the file wasn't updated, then there's nothing new
+ * for us here, so just return. Anything else means we need to
+ * refresh the packed refs.
+ */
+ if (result == GIT_ENOTFOUND) {
+ git_strmap_clear(ref_cache->packfile);
+ return 0;
+ }
+
+ if (result < 0)
+ return -1;
+
+ if (!updated)
+ return 0;
+
+ /*
+ * At this point, we want to refresh the packed refs. We already
+ * have the contents in our buffer.
+ */
+ git_strmap_clear(ref_cache->packfile);
+
+ buffer_start = (const char *)packfile.ptr;
+ buffer_end = (const char *)(buffer_start) + packfile.size;
+
+ while (buffer_start < buffer_end && buffer_start[0] == '#') {
+ buffer_start = strchr(buffer_start, '\n');
+ if (buffer_start == NULL)
+ goto parse_failed;
+
+ buffer_start++;
+ }
+
+ while (buffer_start < buffer_end) {
+ int err;
+ struct packref *ref = NULL;
+
+ if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
+ goto parse_failed;
+
+ if (buffer_start[0] == '^') {
+ if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
+ goto parse_failed;
+ }
+
+ git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
+ if (err < 0)
+ goto parse_failed;
+ }
+
+ git_buf_free(&packfile);
+ return 0;
+
+parse_failed:
+ git_strmap_free(ref_cache->packfile);
+ ref_cache->packfile = NULL;
+ git_buf_free(&packfile);
+ return -1;
+}
+
+static int loose_parse_oid(git_oid *oid, git_buf *file_content)
+{
+ size_t len;
+ const char *str;
+
+ len = git_buf_len(file_content);
+ if (len < GIT_OID_HEXSZ)
+ goto corrupted;
+
+ /* str is guranteed to be zero-terminated */
+ str = git_buf_cstr(file_content);
+
+ /* we need to get 40 OID characters from the file */
+ if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
+ goto corrupted;
+
+ /* If the file is longer than 40 chars, the 41st must be a space */
+ str += GIT_OID_HEXSZ;
+ if (*str == '\0' || git__isspace(*str))
+ return 0;
+
+corrupted:
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return -1;
+}
+
+static int loose_lookup_to_packfile(
+ struct packref **ref_out,
+ refdb_fs_backend *backend,
+ const char *name)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ struct packref *ref = NULL;
+ size_t name_len;
+
+ *ref_out = NULL;
+
+ if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0)
+ return -1;
+
+ git_buf_rtrim(&ref_file);
+
+ name_len = strlen(name);
+ ref = git__malloc(sizeof(struct packref) + name_len + 1);
+ GITERR_CHECK_ALLOC(ref);
+
+ memcpy(ref->name, name, name_len);
+ ref->name[name_len] = 0;
+
+ if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
+ git_buf_free(&ref_file);
+ git__free(ref);
+ return -1;
+ }
+
+ ref->flags = GIT_PACKREF_WAS_LOOSE;
+
+ *ref_out = ref;
+ git_buf_free(&ref_file);
+ return 0;
+}
+
+
+static int _dirent_loose_load(void *data, git_buf *full_path)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)data;
+ void *old_ref = NULL;
+ struct packref *ref;
+ const char *file_path;
+ int err;
+
+ if (git_path_isdir(full_path->ptr) == true)
+ return git_path_direach(full_path, _dirent_loose_load, backend);
+
+ file_path = full_path->ptr + strlen(backend->path);
+
+ if (loose_lookup_to_packfile(&ref, backend, file_path) < 0)
+ return -1;
+
+ git_strmap_insert2(
+ backend->refcache.packfile, ref->name, ref, old_ref, err);
+ if (err < 0) {
+ git__free(ref);
+ return -1;
+ }
+
+ git__free(old_ref);
+ return 0;
+}
+
+/*
+ * Load all the loose references from the repository
+ * into the in-memory Packfile, and build a vector with
+ * all the references so it can be written back to
+ * disk.
+ */
+static int packed_loadloose(refdb_fs_backend *backend)
+{
+ git_buf refs_path = GIT_BUF_INIT;
+ int result;
+
+ /* the packfile must have been previously loaded! */
+ assert(backend->refcache.packfile);
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ /*
+ * Load all the loose files from disk into the Packfile table.
+ * This will overwrite any old packed entries with their
+ * updated loose versions
+ */
+ result = git_path_direach(&refs_path, _dirent_loose_load, backend);
+ git_buf_free(&refs_path);
+
+ return result;
+}
+
+static int refdb_fs_backend__exists(
+ int *exists,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
+ return -1;
+
+ if (git_path_isfile(ref_path.ptr) == true ||
+ git_strmap_exists(backend->refcache.packfile, ref_path.ptr))
+ *exists = 1;
+ else
+ *exists = 0;
+
+ git_buf_free(&ref_path);
+ return 0;
+}
+
+static const char *loose_parse_symbolic(git_buf *file_content)
+{
+ const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
+ const char *refname_start;
+
+ refname_start = (const char *)file_content->ptr;
+
+ if (git_buf_len(file_content) < header_len + 1) {
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return NULL;
+ }
+
+ /*
+ * Assume we have already checked for the header
+ * before calling this function
+ */
+ refname_start += header_len;
+
+ return refname_start;
+}
+
+static int loose_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ const char *target;
+ git_oid oid;
+ git_buf ref_file = GIT_BUF_INIT;
+ int error = 0;
+
+ error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL);
+
+ if (error < 0)
+ goto done;
+
+ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
+ git_buf_rtrim(&ref_file);
+
+ if ((target = loose_parse_symbolic(&ref_file)) == NULL) {
+ error = -1;
+ goto done;
+ }
+
+ *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);
+ } else {
+ if ((error = loose_parse_oid(&oid, &ref_file)) < 0)
+ goto done;
+
+ *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL);
+ }
+
+ if (*out == NULL)
+ error = -1;
+
+done:
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int packed_map_entry(
+ struct packref **entry,
+ khiter_t *pos,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ git_strmap *packfile_refs;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ /* Look up on the packfile */
+ packfile_refs = backend->refcache.packfile;
+
+ *pos = git_strmap_lookup_index(packfile_refs, ref_name);
+
+ if (!git_strmap_valid_index(packfile_refs, *pos)) {
+ giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name);
+ return GIT_ENOTFOUND;
+ }
+
+ *entry = git_strmap_value_at(packfile_refs, *pos);
+
+ return 0;
+}
+
+static int packed_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ struct packref *entry;
+ khiter_t pos;
+ int error = 0;
+
+ if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0)
+ return error;
+
+ if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL)
+ return -1;
+
+ return 0;
+}
+
+static int refdb_fs_backend__lookup(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend;
+ int result;
+
+ assert(_backend);
+
+ backend = (refdb_fs_backend *)_backend;
+
+ if ((result = loose_lookup(out, backend, ref_name)) == 0)
+ return 0;
+
+ /* only try to lookup this reference on the packfile if it
+ * wasn't found on the loose refs; not if there was a critical error */
+ if (result == GIT_ENOTFOUND) {
+ giterr_clear();
+ result = packed_lookup(out, backend, ref_name);
+ }
+
+ return result;
+}
+
+struct dirent_list_data {
+ refdb_fs_backend *backend;
+ size_t repo_path_len;
+ unsigned int list_type:2;
+
+ git_reference_foreach_cb callback;
+ void *callback_payload;
+ int callback_error;
+};
+
+static git_ref_t loose_guess_rtype(const git_buf *full_path)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ git_ref_t type;
+
+ type = GIT_REF_INVALID;
+
+ if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
+ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
+ type = GIT_REF_SYMBOLIC;
+ else
+ type = GIT_REF_OID;
+ }
+
+ git_buf_free(&ref_file);
+ return type;
+}
+
+static int _dirent_loose_listall(void *_data, git_buf *full_path)
+{
+ struct dirent_list_data *data = (struct dirent_list_data *)_data;
+ const char *file_path = full_path->ptr + data->repo_path_len;
+
+ if (git_path_isdir(full_path->ptr) == true)
+ return git_path_direach(full_path, _dirent_loose_listall, _data);
+
+ /* do not add twice a reference that exists already in the packfile */
+ if (git_strmap_exists(data->backend->refcache.packfile, file_path))
+ return 0;
+
+ if (data->list_type != GIT_REF_LISTALL) {
+ if ((data->list_type & loose_guess_rtype(full_path)) == 0)
+ return 0; /* we are filtering out this reference */
+ }
+
+ /* Locked references aren't returned */
+ if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
+ return 0;
+
+ if (data->callback(file_path, data->callback_payload))
+ data->callback_error = GIT_EUSER;
+
+ return data->callback_error;
+}
+
+static int refdb_fs_backend__foreach(
+ git_refdb_backend *_backend,
+ unsigned int list_type,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ refdb_fs_backend *backend;
+ int result;
+ struct dirent_list_data data;
+ git_buf refs_path = GIT_BUF_INIT;
+ const char *ref_name;
+ void *ref = NULL;
+
+ GIT_UNUSED(ref);
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0)
+ return -1;
+
+ /* list all the packed references first */
+ if (list_type & GIT_REF_OID) {
+ git_strmap_foreach(backend->refcache.packfile, ref_name, ref, {
+ if (callback(ref_name, payload))
+ return GIT_EUSER;
+ });
+ }
+
+ /* now list the loose references, trying not to
+ * duplicate the ref names already in the packed-refs file */
+
+ data.repo_path_len = strlen(backend->path);
+ data.list_type = list_type;
+ data.backend = backend;
+ data.callback = callback;
+ data.callback_payload = payload;
+ data.callback_error = 0;
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
+
+ git_buf_free(&refs_path);
+
+ return data.callback_error ? GIT_EUSER : result;
+}
+
+static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ /* Remove a possibly existing empty directory hierarchy
+ * which name would collide with the reference name
+ */
+ if (git_futils_rmdir_r(ref->name, backend->path,
+ GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)
+ return -1;
+
+ if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
+ git_buf_free(&ref_path);
+ return -1;
+ }
+
+ git_buf_free(&ref_path);
+
+ if (ref->type == GIT_REF_OID) {
+ char oid[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(oid, &ref->target.oid);
+ oid[GIT_OID_HEXSZ] = '\0';
+
+ git_filebuf_printf(&file, "%s\n", oid);
+
+ } else if (ref->type == GIT_REF_SYMBOLIC) {
+ git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
+ } else {
+ assert(0); /* don't let this happen */
+ }
+
+ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
+}
+
+static int packed_sort(const void *a, const void *b)
+{
+ const struct packref *ref_a = (const struct packref *)a;
+ const struct packref *ref_b = (const struct packref *)b;
+
+ return strcmp(ref_a->name, ref_b->name);
+}
+
+/*
+ * Find out what object this reference resolves to.
+ *
+ * For references that point to a 'big' tag (e.g. an
+ * actual tag object on the repository), we need to
+ * cache on the packfile the OID of the object to
+ * which that 'big tag' is pointing to.
+ */
+static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
+{
+ git_object *object;
+
+ if (ref->flags & GIT_PACKREF_HAS_PEEL)
+ return 0;
+
+ /*
+ * Only applies to tags, i.e. references
+ * in the /refs/tags folder
+ */
+ if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
+ return 0;
+
+ /*
+ * Find the tagged object in the repository
+ */
+ if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ /*
+ * If the tagged object is a Tag object, we need to resolve it;
+ * if the ref is actually a 'weak' ref, we don't need to resolve
+ * anything.
+ */
+ if (git_object_type(object) == GIT_OBJ_TAG) {
+ git_tag *tag = (git_tag *)object;
+
+ /*
+ * Find the object pointed at by this tag
+ */
+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));
+ ref->flags |= GIT_PACKREF_HAS_PEEL;
+
+ /*
+ * The reference has now cached the resolved OID, and is
+ * marked at such. When written to the packfile, it'll be
+ * accompanied by this resolved oid
+ */
+ }
+
+ git_object_free(object);
+ return 0;
+}
+
+/*
+ * Write a single reference into a packfile
+ */
+static int packed_write_ref(struct packref *ref, git_filebuf *file)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(oid, &ref->oid);
+ oid[GIT_OID_HEXSZ] = 0;
+
+ /*
+ * For references that peel to an object in the repo, we must
+ * write the resulting peel on a separate line, e.g.
+ *
+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
+ *
+ * This obviously only applies to tags.
+ * The required peels have already been loaded into `ref->peel_target`.
+ */
+ if (ref->flags & GIT_PACKREF_HAS_PEEL) {
+ char peel[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(peel, &ref->peel);
+ peel[GIT_OID_HEXSZ] = 0;
+
+ if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
+ return -1;
+ } else {
+ if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Remove all loose references
+ *
+ * Once we have successfully written a packfile,
+ * all the loose references that were packed must be
+ * removed from disk.
+ *
+ * This is a dangerous method; make sure the packfile
+ * is well-written, because we are destructing references
+ * here otherwise.
+ */
+static int packed_remove_loose(
+ refdb_fs_backend *backend,
+ git_vector *packing_list)
+{
+ size_t i;
+ git_buf full_path = GIT_BUF_INIT;
+ int failed = 0;
+
+ for (i = 0; i < packing_list->length; ++i) {
+ struct packref *ref = git_vector_get(packing_list, i);
+
+ if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
+ continue;
+
+ if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
+ return -1; /* critical; do not try to recover on oom */
+
+ if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
+ if (failed)
+ continue;
+
+ giterr_set(GITERR_REFERENCE,
+ "Failed to remove loose reference '%s' after packing: %s",
+ full_path.ptr, strerror(errno));
+
+ failed = 1;
+ }
+
+ /*
+ * if we fail to remove a single file, this is *not* good,
+ * but we should keep going and remove as many as possible.
+ * After we've removed as many files as possible, we return
+ * the error code anyway.
+ */
+ }
+
+ git_buf_free(&full_path);
+ return failed ? -1 : 0;
+}
+
+/*
+ * Write all the contents in the in-memory packfile to disk.
+ */
+static int packed_write(refdb_fs_backend *backend)
+{
+ git_filebuf pack_file = GIT_FILEBUF_INIT;
+ size_t i;
+ git_buf pack_file_path = GIT_BUF_INIT;
+ git_vector packing_list;
+ unsigned int total_refs;
+
+ assert(backend && backend->refcache.packfile);
+
+ total_refs =
+ (unsigned int)git_strmap_num_entries(backend->refcache.packfile);
+
+ if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
+ return -1;
+
+ /* Load all the packfile into a vector */
+ {
+ struct packref *reference;
+
+ /* cannot fail: vector already has the right size */
+ git_strmap_foreach_value(backend->refcache.packfile, reference, {
+ git_vector_insert(&packing_list, reference);
+ });
+ }
+
+ /* sort the vector so the entries appear sorted on the packfile */
+ git_vector_sort(&packing_list);
+
+ /* Now we can open the file! */
+ if (git_buf_joinpath(&pack_file_path,
+ backend->path, GIT_PACKEDREFS_FILE) < 0)
+ goto cleanup_memory;
+
+ if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
+ goto cleanup_packfile;
+
+ /* Packfiles have a header... apparently
+ * This is in fact not required, but we might as well print it
+ * just for kicks */
+ if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
+ goto cleanup_packfile;
+
+ for (i = 0; i < packing_list.length; ++i) {
+ struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
+
+ if (packed_find_peel(backend, ref) < 0)
+ goto cleanup_packfile;
+
+ if (packed_write_ref(ref, &pack_file) < 0)
+ goto cleanup_packfile;
+ }
+
+ /* if we've written all the references properly, we can commit
+ * the packfile to make the changes effective */
+ if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
+ goto cleanup_memory;
+
+ /* when and only when the packfile has been properly written,
+ * we can go ahead and remove the loose refs */
+ if (packed_remove_loose(backend, &packing_list) < 0)
+ goto cleanup_memory;
+
+ {
+ struct stat st;
+ if (p_stat(pack_file_path.ptr, &st) == 0)
+ backend->refcache.packfile_time = st.st_mtime;
+ }
+
+ git_vector_free(&packing_list);
+ git_buf_free(&pack_file_path);
+
+ /* we're good now */
+ return 0;
+
+cleanup_packfile:
+ git_filebuf_cleanup(&pack_file);
+
+cleanup_memory:
+ git_vector_free(&packing_list);
+ git_buf_free(&pack_file_path);
+
+ return -1;
+}
+
+static int refdb_fs_backend__write(
+ git_refdb_backend *_backend,
+ const git_reference *ref)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ return loose_write(backend, ref);
+}
+
+static int refdb_fs_backend__delete(
+ git_refdb_backend *_backend,
+ const git_reference *ref)
+{
+ refdb_fs_backend *backend;
+ git_repository *repo;
+ git_buf loose_path = GIT_BUF_INIT;
+ struct packref *pack_ref;
+ khiter_t pack_ref_pos;
+ int error = 0, pack_error;
+ bool loose_deleted;
+
+ assert(_backend);
+ assert(ref);
+
+ backend = (refdb_fs_backend *)_backend;
+ repo = backend->repo;
+
+ /* If a loose reference exists, remove it from the filesystem */
+
+ if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0)
+ return -1;
+
+ if (git_path_isfile(loose_path.ptr)) {
+ error = p_unlink(loose_path.ptr);
+ loose_deleted = 1;
+ }
+
+ git_buf_free(&loose_path);
+
+ if (error != 0)
+ return error;
+
+ /* If a packed reference exists, remove it from the packfile and repack */
+
+ if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) {
+ git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos);
+ git__free(pack_ref);
+
+ error = packed_write(backend);
+ }
+
+ if (pack_error == GIT_ENOTFOUND)
+ error = loose_deleted ? 0 : GIT_ENOTFOUND;
+ else
+ error = pack_error;
+
+ return error;
+}
+
+static int refdb_fs_backend__compress(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ if (packed_load(backend) < 0 || /* load the existing packfile */
+ packed_loadloose(backend) < 0 || /* add all the loose refs */
+ packed_write(backend) < 0) /* write back to disk */
+ return -1;
+
+ return 0;
+}
+
+static void refcache_free(git_refcache *refs)
+{
+ assert(refs);
+
+ if (refs->packfile) {
+ struct packref *reference;
+
+ git_strmap_foreach_value(refs->packfile, reference, {
+ git__free(reference);
+ });
+
+ git_strmap_free(refs->packfile);
+ }
+}
+
+static void refdb_fs_backend__free(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend);
+ backend = (refdb_fs_backend *)_backend;
+
+ refcache_free(&backend->refcache);
+ git__free(backend);
+}
+
+int git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repository,
+ git_refdb *refdb)
+{
+ refdb_fs_backend *backend;
+
+ backend = git__calloc(1, sizeof(refdb_fs_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->repo = repository;
+ backend->path = repository->path_repository;
+ backend->refdb = refdb;
+
+ backend->parent.exists = &refdb_fs_backend__exists;
+ backend->parent.lookup = &refdb_fs_backend__lookup;
+ backend->parent.foreach = &refdb_fs_backend__foreach;
+ backend->parent.write = &refdb_fs_backend__write;
+ backend->parent.delete = &refdb_fs_backend__delete;
+ backend->parent.compress = &refdb_fs_backend__compress;
+ backend->parent.free = &refdb_fs_backend__free;
+
+ *backend_out = (git_refdb_backend *)backend;
+ return 0;
+}
diff --git a/src/refdb_fs.h b/src/refdb_fs.h
new file mode 100644
index 000000000..79e296833
--- /dev/null
+++ b/src/refdb_fs.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_fs_h__
+#define INCLUDE_refdb_fs_h__
+
+typedef struct {
+ git_strmap *packfile;
+ time_t packfile_time;
+} git_refcache;
+
+#endif
diff --git a/src/reflog.c b/src/reflog.c
index 3ea073e65..8c133fe53 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,7 +10,7 @@
#include "filebuf.h"
#include "signature.h"
-static int reflog_init(git_reflog **reflog, git_reference *ref)
+static int reflog_init(git_reflog **reflog, const git_reference *ref)
{
git_reflog *log;
@@ -28,66 +28,68 @@ static int reflog_init(git_reflog **reflog, git_reference *ref)
return -1;
}
+ log->owner = git_reference_owner(ref);
*reflog = log;
return 0;
}
-static int reflog_write(const char *log_path, const char *oid_old,
- const char *oid_new, const git_signature *committer,
- const char *msg)
+static int serialize_reflog_entry(
+ git_buf *buf,
+ const git_oid *oid_old,
+ const git_oid *oid_new,
+ const git_signature *committer,
+ const char *msg)
{
- int error;
- git_buf log = GIT_BUF_INIT;
- git_filebuf fbuf = GIT_FILEBUF_INIT;
- bool trailing_newline = false;
+ char raw_old[GIT_OID_HEXSZ+1];
+ char raw_new[GIT_OID_HEXSZ+1];
- assert(log_path && oid_old && oid_new && committer);
+ git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
+ git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
- if (msg) {
- const char *newline = strchr(msg, '\n');
- if (newline) {
- if (*(newline + 1) == '\0')
- trailing_newline = true;
- else {
- giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
- return -1;
- }
- }
- }
+ git_buf_clear(buf);
- git_buf_puts(&log, oid_old);
- git_buf_putc(&log, ' ');
+ git_buf_puts(buf, raw_old);
+ git_buf_putc(buf, ' ');
+ git_buf_puts(buf, raw_new);
- git_buf_puts(&log, oid_new);
+ git_signature__writebuf(buf, " ", committer);
- git_signature__writebuf(&log, " ", committer);
- git_buf_truncate(&log, log.size - 1); /* drop LF */
+ /* drop trailing LF */
+ git_buf_rtrim(buf);
if (msg) {
- git_buf_putc(&log, '\t');
- git_buf_puts(&log, msg);
+ git_buf_putc(buf, '\t');
+ git_buf_puts(buf, msg);
}
- if (!trailing_newline)
- git_buf_putc(&log, '\n');
+ git_buf_putc(buf, '\n');
- if (git_buf_oom(&log)) {
- git_buf_free(&log);
- return -1;
- }
+ return git_buf_oom(buf);
+}
- error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND);
- if (!error) {
- if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
- git_filebuf_cleanup(&fbuf);
- else
- error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
- }
+static int reflog_entry_new(git_reflog_entry **entry)
+{
+ git_reflog_entry *e;
- git_buf_free(&log);
+ assert(entry);
- return error;
+ e = git__malloc(sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(e);
+
+ memset(e, 0, sizeof(git_reflog_entry));
+
+ *entry = e;
+
+ return 0;
+}
+
+static void reflog_entry_free(git_reflog_entry *entry)
+{
+ git_signature_free(entry->committer);
+
+ git__free(entry->msg);
+ git__free(entry);
}
static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
@@ -105,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
} while (0)
while (buf_size > GIT_REFLOG_SIZE_MIN) {
- entry = git__malloc(sizeof(git_reflog_entry));
- GITERR_CHECK_ALLOC(entry);
+ if (reflog_entry_new(&entry) < 0)
+ return -1;
entry->committer = git__malloc(sizeof(git_signature));
GITERR_CHECK_ALLOC(entry->committer);
@@ -153,25 +155,24 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
#undef seek_forward
fail:
- if (entry) {
- git__free(entry->committer);
- git__free(entry);
- }
+ if (entry)
+ reflog_entry_free(entry);
+
return -1;
}
void git_reflog_free(git_reflog *reflog)
{
- unsigned int i;
+ size_t i;
git_reflog_entry *entry;
+ if (reflog == NULL)
+ return;
+
for (i=0; i < reflog->entries.length; i++) {
entry = git_vector_get(&reflog->entries, i);
- git_signature_free(entry->committer);
-
- git__free(entry->msg);
- git__free(entry);
+ reflog_entry_free(entry);
}
git_vector_free(&reflog->entries);
@@ -179,110 +180,230 @@ void git_reflog_free(git_reflog *reflog)
git__free(reflog);
}
-int git_reflog_read(git_reflog **reflog, git_reference *ref)
+static int retrieve_reflog_path(git_buf *path, const git_reference *ref)
{
- int error;
+ return git_buf_join_n(path, '/', 3,
+ git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name);
+}
+
+static int create_new_reflog_file(const char *filepath)
+{
+ int fd, error;
+
+ if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
+ return error;
+
+ if ((fd = p_open(filepath,
+ O_WRONLY | O_CREAT | O_TRUNC,
+ GIT_REFLOG_FILE_MODE)) < 0)
+ return -1;
+
+ return p_close(fd);
+}
+
+int git_reflog_read(git_reflog **reflog, const git_reference *ref)
+{
+ int error = -1;
git_buf log_path = GIT_BUF_INIT;
git_buf log_file = GIT_BUF_INIT;
git_reflog *log = NULL;
+ assert(reflog && ref);
+
*reflog = NULL;
if (reflog_init(&log, ref) < 0)
return -1;
- error = git_buf_join_n(&log_path, '/', 3,
- ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
+ if (retrieve_reflog_path(&log_path, ref) < 0)
+ goto cleanup;
+
+ error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
- if (!error)
- error = git_futils_readbuffer(&log_file, log_path.ptr);
+ if ((error == GIT_ENOTFOUND) &&
+ ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
+ goto cleanup;
- if (!error)
- error = reflog_parse(log, log_file.ptr, log_file.size);
+ if ((error = reflog_parse(log,
+ git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
+ goto cleanup;
- if (!error)
- *reflog = log;
- else
- git_reflog_free(log);
+ *reflog = log;
+ goto success;
+
+cleanup:
+ git_reflog_free(log);
+success:
git_buf_free(&log_file);
git_buf_free(&log_path);
return error;
}
-int git_reflog_write(git_reference *ref, const git_oid *oid_old,
- const git_signature *committer, const char *msg)
+int git_reflog_write(git_reflog *reflog)
{
- int error;
- char old[GIT_OID_HEXSZ+1];
- char new[GIT_OID_HEXSZ+1];
+ int error = -1;
+ unsigned int i;
+ git_reflog_entry *entry;
git_buf log_path = GIT_BUF_INIT;
- git_reference *r;
- const git_oid *oid;
+ git_buf log = GIT_BUF_INIT;
+ git_filebuf fbuf = GIT_FILEBUF_INIT;
- if ((error = git_reference_resolve(&r, ref)) < 0)
- return error;
+ assert(reflog);
- oid = git_reference_oid(r);
- if (oid == NULL) {
- giterr_set(GITERR_REFERENCE,
- "Failed to write reflog. Cannot resolve reference `%s`", r->name);
- git_reference_free(r);
+ if (git_buf_join_n(&log_path, '/', 3,
+ git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0)
return -1;
+
+ if (!git_path_isfile(git_buf_cstr(&log_path))) {
+ giterr_set(GITERR_INVALID,
+ "Log file for reference '%s' doesn't exist.", reflog->ref_name);
+ goto cleanup;
+ }
+
+ if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&reflog->entries, i, entry) {
+ if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
+ goto cleanup;
}
- git_oid_tostr(new, GIT_OID_HEXSZ+1, oid);
+ error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
+ goto success;
- git_reference_free(r);
+cleanup:
+ git_filebuf_cleanup(&fbuf);
+
+success:
+ git_buf_free(&log);
+ git_buf_free(&log_path);
+ return error;
+}
+
+int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
+ const git_signature *committer, const char *msg)
+{
+ git_reflog_entry *entry;
+ const git_reflog_entry *previous;
+ const char *newline;
- error = git_buf_join_n(&log_path, '/', 3,
- ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
- if (error < 0)
+ assert(reflog && new_oid && committer);
+
+ if (reflog_entry_new(&entry) < 0)
+ return -1;
+
+ if ((entry->committer = git_signature_dup(committer)) == NULL)
goto cleanup;
- if (git_path_exists(log_path.ptr) == false) {
- error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE);
- } else if (git_path_isfile(log_path.ptr) == false) {
- giterr_set(GITERR_REFERENCE,
- "Failed to write reflog. `%s` is directory", log_path.ptr);
- error = -1;
- } else if (oid_old == NULL) {
- giterr_set(GITERR_REFERENCE,
- "Failed to write reflog. Old OID cannot be NULL for existing reference");
- error = -1;
+ if (msg != NULL) {
+ if ((entry->msg = git__strdup(msg)) == NULL)
+ goto cleanup;
+
+ newline = strchr(msg, '\n');
+
+ if (newline) {
+ if (newline[1] != '\0') {
+ giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
+ goto cleanup;
+ }
+
+ entry->msg[newline - msg] = '\0';
+ }
}
- if (error < 0)
- goto cleanup;
- if (oid_old)
- git_oid_tostr(old, sizeof(old), oid_old);
+ previous = git_reflog_entry_byindex(reflog, 0);
+
+ if (previous == NULL)
+ git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
else
- p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0);
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
+
+ git_oid_cpy(&entry->oid_cur, new_oid);
- error = reflog_write(log_path.ptr, old, new, committer, msg);
+ if (git_vector_insert(&reflog->entries, entry) < 0)
+ goto cleanup;
+
+ return 0;
cleanup:
- git_buf_free(&log_path);
- return error;
+ reflog_entry_free(entry);
+ return -1;
}
int git_reflog_rename(git_reference *ref, const char *new_name)
{
- int error;
+ int error = 0, fd;
git_buf old_path = GIT_BUF_INIT;
git_buf new_path = GIT_BUF_INIT;
+ git_buf temp_path = GIT_BUF_INIT;
+ git_buf normalized = GIT_BUF_INIT;
- if (!git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository,
- GIT_REFLOG_DIR, ref->name) &&
- !git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository,
- GIT_REFLOG_DIR, new_name))
- error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path));
- else
+ assert(ref && new_name);
+
+ if ((error = git_reference__normalize_name(
+ &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
+ return error;
+
+ if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
+ return -1;
+
+ /*
+ * Move the reflog to a temporary place. This two-phase renaming is required
+ * in order to cope with funny renaming use cases when one tries to move a reference
+ * to a partially colliding namespace:
+ * - a/b -> a/b/c
+ * - a/b/c/d -> a/b/c
+ */
+ if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
+ return -1;
+
+ if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ p_close(fd);
+
+ if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_path_isdir(git_buf_cstr(&new_path)) &&
+ (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
error = -1;
+ }
+cleanup:
+ git_buf_free(&temp_path);
git_buf_free(&old_path);
git_buf_free(&new_path);
+ git_buf_free(&normalized);
return error;
}
@@ -292,8 +413,7 @@ int git_reflog_delete(git_reference *ref)
int error;
git_buf path = GIT_BUF_INIT;
- error = git_buf_join_n(
- &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
+ error = retrieve_reflog_path(&path, ref);
if (!error && git_path_exists(path.ptr))
error = p_unlink(path.ptr);
@@ -303,38 +423,99 @@ int git_reflog_delete(git_reference *ref)
return error;
}
-unsigned int git_reflog_entrycount(git_reflog *reflog)
+size_t git_reflog_entrycount(git_reflog *reflog)
{
assert(reflog);
return reflog->entries.length;
}
-const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)
+GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total)
+{
+ return (total - 1) - idx;
+}
+
+const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
{
assert(reflog);
- return git_vector_get(&reflog->entries, idx);
+
+ if (idx >= reflog->entries.length)
+ return NULL;
+
+ return git_vector_get(
+ &reflog->entries, reflog_inverse_index(idx, reflog->entries.length));
}
-const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry)
+const git_oid * git_reflog_entry_id_old(const git_reflog_entry *entry)
{
assert(entry);
return &entry->oid_old;
}
-const git_oid * git_reflog_entry_oidnew(const git_reflog_entry *entry)
+const git_oid * git_reflog_entry_id_new(const git_reflog_entry *entry)
{
assert(entry);
return &entry->oid_cur;
}
-git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
+const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
{
assert(entry);
return entry->committer;
}
-char * git_reflog_entry_msg(const git_reflog_entry *entry)
+const char * git_reflog_entry_message(const git_reflog_entry *entry)
{
assert(entry);
return entry->msg;
}
+
+int git_reflog_drop(
+ git_reflog *reflog,
+ size_t idx,
+ int rewrite_previous_entry)
+{
+ size_t entrycount;
+ git_reflog_entry *entry, *previous;
+
+ assert(reflog);
+
+ entrycount = git_reflog_entrycount(reflog);
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+
+ if (entry == NULL)
+ return GIT_ENOTFOUND;
+
+ reflog_entry_free(entry);
+
+ if (git_vector_remove(
+ &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0)
+ return -1;
+
+ if (!rewrite_previous_entry)
+ return 0;
+
+ /* No need to rewrite anything when removing the most recent entry */
+ if (idx == 0)
+ return 0;
+
+ /* Have the latest entry just been dropped? */
+ if (entrycount == 1)
+ return 0;
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
+
+ /* If the oldest entry has just been removed... */
+ if (idx == entrycount - 1) {
+ /* ...clear the oid_old member of the "new" oldest entry */
+ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
+ return -1;
+
+ return 0;
+ }
+
+ previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
+
+ return 0;
+}
diff --git a/src/reflog.h b/src/reflog.h
index 33cf0776c..9444ebd10 100644
--- a/src/reflog.h
+++ b/src/reflog.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -28,6 +28,7 @@ struct git_reflog_entry {
struct git_reflog {
char *ref_name;
+ git_repository *owner;
git_vector entries;
};
diff --git a/src/refs.c b/src/refs.c
index 1ef3e13a4..b1f679632 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,9 +11,15 @@
#include "fileops.h"
#include "pack.h"
#include "reflog.h"
+#include "refdb.h"
#include <git2/tag.h>
#include <git2/object.h>
+#include <git2/oid.h>
+#include <git2/branch.h>
+#include <git2/refs.h>
+#include <git2/refdb.h>
+#include <git2/refdb_backend.h>
GIT__USE_STRMAP;
@@ -25,789 +31,55 @@ enum {
GIT_PACKREF_WAS_LOOSE = 2
};
-struct packref {
- git_oid oid;
- git_oid peel;
- char flags;
- char name[GIT_FLEX_ARRAY];
-};
-
-static int reference_read(
- git_buf *file_content,
- time_t *mtime,
- const char *repo_path,
- const char *ref_name,
- int *updated);
-
-/* loose refs */
-static int loose_parse_symbolic(git_reference *ref, git_buf *file_content);
-static int loose_parse_oid(git_oid *ref, git_buf *file_content);
-static int loose_lookup(git_reference *ref);
-static int loose_lookup_to_packfile(struct packref **ref_out,
- git_repository *repo, const char *name);
-static int loose_write(git_reference *ref);
-
-/* packed refs */
-static int packed_parse_peel(struct packref *tag_ref,
- const char **buffer_out, const char *buffer_end);
-static int packed_parse_oid(struct packref **ref_out,
- const char **buffer_out, const char *buffer_end);
-static int packed_load(git_repository *repo);
-static int packed_loadloose(git_repository *repository);
-static int packed_write_ref(struct packref *ref, git_filebuf *file);
-static int packed_find_peel(git_repository *repo, struct packref *ref);
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
-static int packed_sort(const void *a, const void *b);
-static int packed_lookup(git_reference *ref);
-static int packed_write(git_repository *repo);
-
-/* internal helpers */
-static int reference_path_available(git_repository *repo,
- const char *ref, const char *old_ref);
-static int reference_delete(git_reference *ref);
-static int reference_lookup(git_reference *ref);
-
-/* name normalization */
-static int normalize_name(char *buffer_out, size_t out_size,
- const char *name, int is_oid_ref);
-
-
-void git_reference_free(git_reference *reference)
-{
- if (reference == NULL)
- return;
-
- git__free(reference->name);
- reference->name = NULL;
-
- if (reference->flags & GIT_REF_SYMBOLIC) {
- git__free(reference->target.symbolic);
- reference->target.symbolic = NULL;
- }
-
- git__free(reference);
-}
-
-static int reference_alloc(
- git_reference **ref_out,
- git_repository *repo,
- const char *name)
-{
- git_reference *reference = NULL;
-
- assert(ref_out && repo && name);
-
- reference = git__malloc(sizeof(git_reference));
- GITERR_CHECK_ALLOC(reference);
-
- memset(reference, 0x0, sizeof(git_reference));
- reference->owner = repo;
-
- reference->name = git__strdup(name);
- GITERR_CHECK_ALLOC(reference->name);
-
- *ref_out = reference;
- return 0;
-}
-
-static int reference_read(
- git_buf *file_content,
- time_t *mtime,
- const char *repo_path,
- const char *ref_name,
- int *updated)
-{
- git_buf path = GIT_BUF_INIT;
- int result;
-
- assert(file_content && repo_path && ref_name);
-
- /* Determine the full path of the file */
- if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
- return -1;
-
- result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);
- git_buf_free(&path);
- return result;
-}
-
-static int loose_parse_symbolic(git_reference *ref, git_buf *file_content)
-{
- const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
- const char *refname_start;
- char *eol;
-
- refname_start = (const char *)file_content->ptr;
-
- if (git_buf_len(file_content) < header_len + 1)
- goto corrupt;
-
- /*
- * Assume we have already checked for the header
- * before calling this function
- */
- refname_start += header_len;
-
- ref->target.symbolic = git__strdup(refname_start);
- GITERR_CHECK_ALLOC(ref->target.symbolic);
-
- /* remove newline at the end of file */
- eol = strchr(ref->target.symbolic, '\n');
- if (eol == NULL)
- goto corrupt;
-
- *eol = '\0';
- if (eol[-1] == '\r')
- eol[-1] = '\0';
-
- return 0;
-
-corrupt:
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
- return -1;
-}
-
-static int loose_parse_oid(git_oid *oid, git_buf *file_content)
-{
- char *buffer;
-
- buffer = (char *)file_content->ptr;
-
- /* File format: 40 chars (OID) + newline */
- if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1)
- goto corrupt;
-
- if (git_oid_fromstr(oid, buffer) < 0)
- goto corrupt;
-
- buffer = buffer + GIT_OID_HEXSZ;
- if (*buffer == '\r')
- buffer++;
-
- if (*buffer != '\n')
- goto corrupt;
-
- return 0;
-
-corrupt:
- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
- return -1;
-}
-
-static git_ref_t loose_guess_rtype(const git_buf *full_path)
-{
- git_buf ref_file = GIT_BUF_INIT;
- git_ref_t type;
-
- type = GIT_REF_INVALID;
-
- if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
- type = GIT_REF_SYMBOLIC;
- else
- type = GIT_REF_OID;
- }
-
- git_buf_free(&ref_file);
- return type;
-}
-
-static int loose_lookup(git_reference *ref)
-{
- int result, updated;
- git_buf ref_file = GIT_BUF_INIT;
-
- result = reference_read(&ref_file, &ref->mtime,
- ref->owner->path_repository, ref->name, &updated);
-
- if (result < 0)
- return result;
-
- if (!updated)
- return 0;
-
- if (ref->flags & GIT_REF_SYMBOLIC) {
- git__free(ref->target.symbolic);
- ref->target.symbolic = NULL;
- }
-
- ref->flags = 0;
-
- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
- ref->flags |= GIT_REF_SYMBOLIC;
- result = loose_parse_symbolic(ref, &ref_file);
- } else {
- ref->flags |= GIT_REF_OID;
- result = loose_parse_oid(&ref->target.oid, &ref_file);
- }
-
- git_buf_free(&ref_file);
- return result;
-}
-
-static int loose_lookup_to_packfile(
- struct packref **ref_out,
- git_repository *repo,
- const char *name)
-{
- git_buf ref_file = GIT_BUF_INIT;
- struct packref *ref = NULL;
- size_t name_len;
-
- *ref_out = NULL;
-
- if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0)
- return -1;
-
- name_len = strlen(name);
- ref = git__malloc(sizeof(struct packref) + name_len + 1);
- GITERR_CHECK_ALLOC(ref);
-
- memcpy(ref->name, name, name_len);
- ref->name[name_len] = 0;
-
- if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
- git_buf_free(&ref_file);
- git__free(ref);
- return -1;
- }
-
- ref->flags = GIT_PACKREF_WAS_LOOSE;
-
- *ref_out = ref;
- git_buf_free(&ref_file);
- return 0;
-}
-
-static int loose_write(git_reference *ref)
-{
- git_filebuf file = GIT_FILEBUF_INIT;
- git_buf ref_path = GIT_BUF_INIT;
- struct stat st;
-
- if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
- /* Remove a possibly existing empty directory hierarchy
- * which name would collide with the reference name
- */
- if (git_path_isdir(git_buf_cstr(&ref_path)) &&
- (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
- git_buf_free(&ref_path);
- return -1;
- }
-
- if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
- git_buf_free(&ref_path);
- return -1;
- }
-
- git_buf_free(&ref_path);
-
- if (ref->flags & GIT_REF_OID) {
- char oid[GIT_OID_HEXSZ + 1];
-
- git_oid_fmt(oid, &ref->target.oid);
- oid[GIT_OID_HEXSZ] = '\0';
-
- git_filebuf_printf(&file, "%s\n", oid);
-
- } else if (ref->flags & GIT_REF_SYMBOLIC) {
- git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
- } else {
- assert(0); /* don't let this happen */
- }
-
- if (p_stat(ref_path.ptr, &st) == 0)
- ref->mtime = st.st_mtime;
-
- return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
-}
-
-static int packed_parse_peel(
- struct packref *tag_ref,
- const char **buffer_out,
- const char *buffer_end)
-{
- const char *buffer = *buffer_out + 1;
-
- assert(buffer[-1] == '^');
-
- /* Ensure it's not the first entry of the file */
- if (tag_ref == NULL)
- goto corrupt;
-
- /* Ensure reference is a tag */
- if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
- goto corrupt;
-
- if (buffer + GIT_OID_HEXSZ >= buffer_end)
- goto corrupt;
-
- /* Is this a valid object id? */
- if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
- goto corrupt;
-
- buffer = buffer + GIT_OID_HEXSZ;
- if (*buffer == '\r')
- buffer++;
-
- if (*buffer != '\n')
- goto corrupt;
-
- *buffer_out = buffer + 1;
- return 0;
-
-corrupt:
- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
- return -1;
-}
-
-static int packed_parse_oid(
- struct packref **ref_out,
- const char **buffer_out,
- const char *buffer_end)
-{
- struct packref *ref = NULL;
-
- const char *buffer = *buffer_out;
- const char *refname_begin, *refname_end;
-
- size_t refname_len;
- git_oid id;
-
- refname_begin = (buffer + GIT_OID_HEXSZ + 1);
- if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
- goto corrupt;
-
- /* Is this a valid object id? */
- if (git_oid_fromstr(&id, buffer) < 0)
- goto corrupt;
-
- refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
- if (refname_end == NULL)
- goto corrupt;
-
- if (refname_end[-1] == '\r')
- refname_end--;
-
- refname_len = refname_end - refname_begin;
-
- ref = git__malloc(sizeof(struct packref) + refname_len + 1);
- GITERR_CHECK_ALLOC(ref);
-
- memcpy(ref->name, refname_begin, refname_len);
- ref->name[refname_len] = 0;
-
- git_oid_cpy(&ref->oid, &id);
-
- ref->flags = 0;
-
- *ref_out = ref;
- *buffer_out = refname_end + 1;
-
- return 0;
-
-corrupt:
- git__free(ref);
- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
- return -1;
-}
-
-static int packed_load(git_repository *repo)
-{
- int result, updated;
- git_buf packfile = GIT_BUF_INIT;
- const char *buffer_start, *buffer_end;
- git_refcache *ref_cache = &repo->references;
-
- /* First we make sure we have allocated the hash table */
- if (ref_cache->packfile == NULL) {
- ref_cache->packfile = git_strmap_alloc();
- GITERR_CHECK_ALLOC(ref_cache->packfile);
- }
-
- result = reference_read(&packfile, &ref_cache->packfile_time,
- repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
-
- /*
- * If we couldn't find the file, we need to clear the table and
- * return. On any other error, we return that error. If everything
- * went fine and the file wasn't updated, then there's nothing new
- * for us here, so just return. Anything else means we need to
- * refresh the packed refs.
- */
- if (result == GIT_ENOTFOUND) {
- git_strmap_clear(ref_cache->packfile);
- return 0;
- }
-
- if (result < 0)
- return -1;
-
- if (!updated)
- return 0;
-
- /*
- * At this point, we want to refresh the packed refs. We already
- * have the contents in our buffer.
- */
- git_strmap_clear(ref_cache->packfile);
-
- buffer_start = (const char *)packfile.ptr;
- buffer_end = (const char *)(buffer_start) + packfile.size;
- while (buffer_start < buffer_end && buffer_start[0] == '#') {
- buffer_start = strchr(buffer_start, '\n');
- if (buffer_start == NULL)
- goto parse_failed;
-
- buffer_start++;
- }
-
- while (buffer_start < buffer_end) {
- int err;
- struct packref *ref = NULL;
-
- if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
- goto parse_failed;
-
- if (buffer_start[0] == '^') {
- if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
- goto parse_failed;
- }
-
- git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
- if (err < 0)
- goto parse_failed;
- }
-
- git_buf_free(&packfile);
- return 0;
-
-parse_failed:
- git_strmap_free(ref_cache->packfile);
- ref_cache->packfile = NULL;
- git_buf_free(&packfile);
- return -1;
-}
-
-
-struct dirent_list_data {
- git_repository *repo;
- size_t repo_path_len;
- unsigned int list_flags;
-
- int (*callback)(const char *, void *);
- void *callback_payload;
-};
-
-static int _dirent_loose_listall(void *_data, git_buf *full_path)
-{
- struct dirent_list_data *data = (struct dirent_list_data *)_data;
- const char *file_path = full_path->ptr + data->repo_path_len;
-
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_listall, _data);
-
- /* do not add twice a reference that exists already in the packfile */
- if ((data->list_flags & GIT_REF_PACKED) != 0 &&
- git_strmap_exists(data->repo->references.packfile, file_path))
- return 0;
-
- if (data->list_flags != GIT_REF_LISTALL) {
- if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
- return 0; /* we are filtering out this reference */
- }
-
- return data->callback(file_path, data->callback_payload);
-}
-
-static int _dirent_loose_load(void *data, git_buf *full_path)
-{
- git_repository *repository = (git_repository *)data;
- void *old_ref = NULL;
- struct packref *ref;
- const char *file_path;
- int err;
-
- if (git_path_isdir(full_path->ptr) == true)
- return git_path_direach(full_path, _dirent_loose_load, repository);
-
- file_path = full_path->ptr + strlen(repository->path_repository);
-
- if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)
- return -1;
-
- git_strmap_insert2(
- repository->references.packfile, ref->name, ref, old_ref, err);
- if (err < 0) {
- git__free(ref);
- return -1;
- }
-
- git__free(old_ref);
- return 0;
-}
-
-/*
- * Load all the loose references from the repository
- * into the in-memory Packfile, and build a vector with
- * all the references so it can be written back to
- * disk.
- */
-static int packed_loadloose(git_repository *repository)
+git_reference *git_reference__alloc(
+ git_refdb *refdb,
+ const char *name,
+ const git_oid *oid,
+ const char *symbolic)
{
- git_buf refs_path = GIT_BUF_INIT;
- int result;
-
- /* the packfile must have been previously loaded! */
- assert(repository->references.packfile);
-
- if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)
- return -1;
-
- /*
- * Load all the loose files from disk into the Packfile table.
- * This will overwrite any old packed entries with their
- * updated loose versions
- */
- result = git_path_direach(&refs_path, _dirent_loose_load, repository);
- git_buf_free(&refs_path);
-
- return result;
-}
+ git_reference *ref;
+ size_t namelen;
-/*
- * Write a single reference into a packfile
- */
-static int packed_write_ref(struct packref *ref, git_filebuf *file)
-{
- char oid[GIT_OID_HEXSZ + 1];
+ assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic)));
- git_oid_fmt(oid, &ref->oid);
- oid[GIT_OID_HEXSZ] = 0;
+ namelen = strlen(name);
- /*
- * For references that peel to an object in the repo, we must
- * write the resulting peel on a separate line, e.g.
- *
- * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
- * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
- *
- * This obviously only applies to tags.
- * The required peels have already been loaded into `ref->peel_target`.
- */
- if (ref->flags & GIT_PACKREF_HAS_PEEL) {
- char peel[GIT_OID_HEXSZ + 1];
- git_oid_fmt(peel, &ref->peel);
- peel[GIT_OID_HEXSZ] = 0;
+ if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
+ return NULL;
- if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
- return -1;
+ if (oid) {
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
} else {
- if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
- return -1;
- }
-
- return 0;
-}
-
-/*
- * Find out what object this reference resolves to.
- *
- * For references that point to a 'big' tag (e.g. an
- * actual tag object on the repository), we need to
- * cache on the packfile the OID of the object to
- * which that 'big tag' is pointing to.
- */
-static int packed_find_peel(git_repository *repo, struct packref *ref)
-{
- git_object *object;
-
- if (ref->flags & GIT_PACKREF_HAS_PEEL)
- return 0;
-
- /*
- * Only applies to tags, i.e. references
- * in the /refs/tags folder
- */
- if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
- return 0;
-
- /*
- * Find the tagged object in the repository
- */
- if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)
- return -1;
-
- /*
- * If the tagged object is a Tag object, we need to resolve it;
- * if the ref is actually a 'weak' ref, we don't need to resolve
- * anything.
- */
- if (git_object_type(object) == GIT_OBJ_TAG) {
- git_tag *tag = (git_tag *)object;
-
- /*
- * Find the object pointed at by this tag
- */
- git_oid_cpy(&ref->peel, git_tag_target_oid(tag));
- ref->flags |= GIT_PACKREF_HAS_PEEL;
-
- /*
- * The reference has now cached the resolved OID, and is
- * marked at such. When written to the packfile, it'll be
- * accompanied by this resolved oid
- */
- }
-
- git_object_free(object);
- return 0;
-}
-
-/*
- * Remove all loose references
- *
- * Once we have successfully written a packfile,
- * all the loose references that were packed must be
- * removed from disk.
- *
- * This is a dangerous method; make sure the packfile
- * is well-written, because we are destructing references
- * here otherwise.
- */
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
-{
- unsigned int i;
- git_buf full_path = GIT_BUF_INIT;
- int failed = 0;
-
- for (i = 0; i < packing_list->length; ++i) {
- struct packref *ref = git_vector_get(packing_list, i);
-
- if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
- continue;
-
- if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0)
- return -1; /* critical; do not try to recover on oom */
-
- if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
- if (failed)
- continue;
+ ref->type = GIT_REF_SYMBOLIC;
- giterr_set(GITERR_REFERENCE,
- "Failed to remove loose reference '%s' after packing: %s",
- full_path.ptr, strerror(errno));
-
- failed = 1;
+ if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) {
+ git__free(ref);
+ return NULL;
}
-
- /*
- * if we fail to remove a single file, this is *not* good,
- * but we should keep going and remove as many as possible.
- * After we've removed as many files as possible, we return
- * the error code anyway.
- */
}
- git_buf_free(&full_path);
- return failed ? -1 : 0;
-}
-
-static int packed_sort(const void *a, const void *b)
-{
- const struct packref *ref_a = (const struct packref *)a;
- const struct packref *ref_b = (const struct packref *)b;
+ ref->db = refdb;
+ memcpy(ref->name, name, namelen + 1);
- return strcmp(ref_a->name, ref_b->name);
+ return ref;
}
-/*
- * Write all the contents in the in-memory packfile to disk.
- */
-static int packed_write(git_repository *repo)
+void git_reference_free(git_reference *reference)
{
- git_filebuf pack_file = GIT_FILEBUF_INIT;
- unsigned int i;
- git_buf pack_file_path = GIT_BUF_INIT;
- git_vector packing_list;
- unsigned int total_refs;
-
- assert(repo && repo->references.packfile);
-
- total_refs =
- (unsigned int)git_strmap_num_entries(repo->references.packfile);
-
- if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
- return -1;
-
- /* Load all the packfile into a vector */
- {
- struct packref *reference;
-
- /* cannot fail: vector already has the right size */
- git_strmap_foreach_value(repo->references.packfile, reference, {
- git_vector_insert(&packing_list, reference);
- });
- }
-
- /* sort the vector so the entries appear sorted on the packfile */
- git_vector_sort(&packing_list);
-
- /* Now we can open the file! */
- if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)
- goto cleanup_memory;
-
- if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
- goto cleanup_packfile;
-
- /* Packfiles have a header... apparently
- * This is in fact not required, but we might as well print it
- * just for kicks */
- if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
- goto cleanup_packfile;
-
- for (i = 0; i < packing_list.length; ++i) {
- struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
-
- if (packed_find_peel(repo, ref) < 0)
- goto cleanup_packfile;
+ if (reference == NULL)
+ return;
- if (packed_write_ref(ref, &pack_file) < 0)
- goto cleanup_packfile;
+ if (reference->type == GIT_REF_SYMBOLIC) {
+ git__free(reference->target.symbolic);
+ reference->target.symbolic = NULL;
}
- /* if we've written all the references properly, we can commit
- * the packfile to make the changes effective */
- if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
- goto cleanup_memory;
-
- /* when and only when the packfile has been properly written,
- * we can go ahead and remove the loose refs */
- if (packed_remove_loose(repo, &packing_list) < 0)
- goto cleanup_memory;
-
- {
- struct stat st;
- if (p_stat(pack_file_path.ptr, &st) == 0)
- repo->references.packfile_time = st.st_mtime;
- }
-
- git_vector_free(&packing_list);
- git_buf_free(&pack_file_path);
-
- /* we're good now */
- return 0;
-
-cleanup_packfile:
- git_filebuf_cleanup(&pack_file);
+ reference->db = NULL;
+ reference->type = GIT_REF_INVALID;
-cleanup_memory:
- git_vector_free(&packing_list);
- git_buf_free(&pack_file_path);
-
- return -1;
+ git__free(reference);
}
struct reference_available_t {
@@ -843,15 +115,17 @@ static int reference_path_available(
const char *ref,
const char* old_ref)
{
+ int error;
struct reference_available_t data;
data.new_ref = ref;
data.old_ref = old_ref;
data.available = 1;
- if (git_reference_foreach(repo, GIT_REF_LISTALL,
- _reference_available_cb, (void *)&data) < 0)
- return -1;
+ error = git_reference_foreach(
+ repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data);
+ if (error < 0)
+ return error;
if (!data.available) {
giterr_set(GITERR_REFERENCE,
@@ -862,28 +136,6 @@ static int reference_path_available(
return 0;
}
-static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
-{
- git_buf ref_path = GIT_BUF_INIT;
-
- if (packed_load(repo) < 0)
- return -1;
-
- if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)
- return -1;
-
- if (git_path_isfile(ref_path.ptr) == true ||
- git_strmap_exists(repo->references.packfile, ref_path.ptr))
- {
- *exists = 1;
- } else {
- *exists = 0;
- }
-
- git_buf_free(&ref_path);
- return 0;
-}
-
/*
* Check if a reference could be written to disk, based on:
*
@@ -899,6 +151,11 @@ static int reference_can_write(
const char *previous_name,
int force)
{
+ git_refdb *refdb;
+
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
+
/* see if the reference shares a path with an existing reference;
* if a path is shared, we cannot create the reference, even when forcing */
if (reference_path_available(repo, refname, previous_name) < 0)
@@ -909,7 +166,7 @@ static int reference_can_write(
if (!force) {
int exists;
- if (reference_exists(&exists, repo, refname) < 0)
+ if (git_refdb_exists(&exists, refdb, refname) < 0)
return -1;
/* We cannot proceed if the reference already exists and we're not forcing
@@ -936,139 +193,9 @@ static int reference_can_write(
return 0;
}
-
-static int packed_lookup(git_reference *ref)
-{
- struct packref *pack_ref = NULL;
- git_strmap *packfile_refs;
- khiter_t pos;
-
- if (packed_load(ref->owner) < 0)
- return -1;
-
- /* maybe the packfile hasn't changed at all, so we don't
- * have to re-lookup the reference */
- if ((ref->flags & GIT_REF_PACKED) &&
- ref->mtime == ref->owner->references.packfile_time)
- return 0;
-
- if (ref->flags & GIT_REF_SYMBOLIC) {
- git__free(ref->target.symbolic);
- ref->target.symbolic = NULL;
- }
-
- /* Look up on the packfile */
- packfile_refs = ref->owner->references.packfile;
- pos = git_strmap_lookup_index(packfile_refs, ref->name);
- if (!git_strmap_valid_index(packfile_refs, pos)) {
- giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name);
- return GIT_ENOTFOUND;
- }
-
- pack_ref = git_strmap_value_at(packfile_refs, pos);
-
- ref->flags = GIT_REF_OID | GIT_REF_PACKED;
- ref->mtime = ref->owner->references.packfile_time;
- git_oid_cpy(&ref->target.oid, &pack_ref->oid);
-
- return 0;
-}
-
-static int reference_lookup(git_reference *ref)
-{
- int result;
-
- result = loose_lookup(ref);
- if (result == 0)
- return 0;
-
- /* only try to lookup this reference on the packfile if it
- * wasn't found on the loose refs; not if there was a critical error */
- if (result == GIT_ENOTFOUND) {
- giterr_clear();
- result = packed_lookup(ref);
- if (result == 0)
- return 0;
- }
-
- /* unexpected error; free the reference */
- git_reference_free(ref);
- return result;
-}
-
-/*
- * Delete a reference.
- * This is an internal method; the reference is removed
- * from disk or the packfile, but the pointer is not freed
- */
-static int reference_delete(git_reference *ref)
-{
- int result;
-
- assert(ref);
-
- /* If the reference is packed, this is an expensive operation.
- * We need to reload the packfile, remove the reference from the
- * packing list, and repack */
- if (ref->flags & GIT_REF_PACKED) {
- git_strmap *packfile_refs;
- struct packref *packref;
- khiter_t pos;
-
- /* load the existing packfile */
- if (packed_load(ref->owner) < 0)
- return -1;
-
- packfile_refs = ref->owner->references.packfile;
- pos = git_strmap_lookup_index(packfile_refs, ref->name);
- if (!git_strmap_valid_index(packfile_refs, pos)) {
- giterr_set(GITERR_REFERENCE,
- "Reference %s stopped existing in the packfile", ref->name);
- return -1;
- }
-
- packref = git_strmap_value_at(packfile_refs, pos);
- git_strmap_delete_at(packfile_refs, pos);
-
- git__free(packref);
- if (packed_write(ref->owner) < 0)
- return -1;
-
- /* If the reference is loose, we can just remove the reference
- * from the filesystem */
- } else {
- git_reference *ref_in_pack;
- git_buf full_path = GIT_BUF_INIT;
-
- if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
- result = p_unlink(full_path.ptr);
- git_buf_free(&full_path); /* done with path at this point */
-
- if (result < 0) {
- giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);
- return -1;
- }
-
- /* When deleting a loose reference, we have to ensure that an older
- * packed version of it doesn't exist */
- if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) {
- assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
- return git_reference_delete(ref_in_pack);
- }
-
- giterr_clear();
- }
-
- return 0;
-}
-
int git_reference_delete(git_reference *ref)
{
- int result = reference_delete(ref);
- git_reference_free(ref);
- return result;
+ return git_refdb_delete(ref->db, ref);
}
int git_reference_lookup(git_reference **ref_out,
@@ -1077,7 +204,7 @@ int git_reference_lookup(git_reference **ref_out,
return git_reference_lookup_resolved(ref_out, repo, name, 0);
}
-int git_reference_name_to_oid(
+int git_reference_name_to_id(
git_oid *out, git_repository *repo, const char *name)
{
int error;
@@ -1086,7 +213,7 @@ int git_reference_name_to_oid(
if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
return error;
- git_oid_cpy(out, git_reference_oid(ref));
+ git_oid_cpy(out, git_reference_target(ref));
git_reference_free(ref);
return 0;
}
@@ -1097,8 +224,11 @@ int git_reference_lookup_resolved(
const char *name,
int max_nesting)
{
- git_reference *scan;
- int result, nesting;
+ char scan_name[GIT_REFNAME_MAX];
+ git_ref_t scan_type;
+ int error = 0, nesting;
+ git_reference *ref = NULL;
+ git_refdb *refdb;
assert(ref_out && repo && name);
@@ -1108,409 +238,294 @@ int git_reference_lookup_resolved(
max_nesting = MAX_NESTING_LEVEL;
else if (max_nesting < 0)
max_nesting = DEFAULT_NESTING_LEVEL;
+
+ strncpy(scan_name, name, GIT_REFNAME_MAX);
+ scan_type = GIT_REF_SYMBOLIC;
+
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
- scan = git__calloc(1, sizeof(git_reference));
- GITERR_CHECK_ALLOC(scan);
-
- scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));
- GITERR_CHECK_ALLOC(scan->name);
-
- if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) {
- git_reference_free(scan);
- return result;
- }
-
- scan->target.symbolic = git__strdup(scan->name);
- GITERR_CHECK_ALLOC(scan->target.symbolic);
-
- scan->owner = repo;
- scan->flags = GIT_REF_SYMBOLIC;
+ if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)
+ return error;
for (nesting = max_nesting;
- nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0;
+ nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
nesting--)
{
- if (nesting != max_nesting)
- strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX);
-
- scan->mtime = 0;
+ if (nesting != max_nesting) {
+ strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);
+ git_reference_free(ref);
+ }
- if ((result = reference_lookup(scan)) < 0)
- return result; /* lookup git_reference_free on scan already */
+ if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
+ return error;
+
+ scan_type = ref->type;
}
- if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) {
+ if (scan_type != GIT_REF_OID && max_nesting != 0) {
giterr_set(GITERR_REFERENCE,
"Cannot resolve reference (>%u levels deep)", max_nesting);
- git_reference_free(scan);
+ git_reference_free(ref);
return -1;
}
- *ref_out = scan;
+ *ref_out = ref;
return 0;
}
/**
* Getters
*/
-git_ref_t git_reference_type(git_reference *ref)
-{
- assert(ref);
-
- if (ref->flags & GIT_REF_OID)
- return GIT_REF_OID;
-
- if (ref->flags & GIT_REF_SYMBOLIC)
- return GIT_REF_SYMBOLIC;
-
- return GIT_REF_INVALID;
-}
-
-int git_reference_is_packed(git_reference *ref)
+git_ref_t git_reference_type(const git_reference *ref)
{
assert(ref);
- return !!(ref->flags & GIT_REF_PACKED);
+ return ref->type;
}
-const char *git_reference_name(git_reference *ref)
+const char *git_reference_name(const git_reference *ref)
{
assert(ref);
return ref->name;
}
-git_repository *git_reference_owner(git_reference *ref)
+git_repository *git_reference_owner(const git_reference *ref)
{
assert(ref);
- return ref->owner;
+ return ref->db->repo;
}
-const git_oid *git_reference_oid(git_reference *ref)
+const git_oid *git_reference_target(const git_reference *ref)
{
assert(ref);
- if ((ref->flags & GIT_REF_OID) == 0)
+ if (ref->type != GIT_REF_OID)
return NULL;
return &ref->target.oid;
}
-const char *git_reference_target(git_reference *ref)
+const char *git_reference_symbolic_target(const git_reference *ref)
{
assert(ref);
- if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
+ if (ref->type != GIT_REF_SYMBOLIC)
return NULL;
return ref->target.symbolic;
}
-int git_reference_create_symbolic(
+static int reference__create(
git_reference **ref_out,
git_repository *repo,
const char *name,
- const char *target,
+ const git_oid *oid,
+ const char *symbolic,
int force)
{
char normalized[GIT_REFNAME_MAX];
+ git_refdb *refdb;
git_reference *ref = NULL;
-
- if (normalize_name(normalized, sizeof(normalized), name, 0) < 0)
- return -1;
-
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
-
- if (reference_alloc(&ref, repo, normalized) < 0)
+ int error = 0;
+
+ if (ref_out)
+ *ref_out = NULL;
+
+ if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 ||
+ (error = reference_can_write(repo, normalized, NULL, force)) < 0 ||
+ (error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
+
+ if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL)
return -1;
- ref->flags |= GIT_REF_SYMBOLIC;
-
- /* set the target; this will normalize the name automatically
- * and write the reference on disk */
- if (git_reference_set_target(ref, target) < 0) {
+ if ((error = git_refdb_write(refdb, ref)) < 0) {
git_reference_free(ref);
- return -1;
+ return error;
}
- if (ref_out == NULL) {
+
+ if (ref_out == NULL)
git_reference_free(ref);
- } else {
+ else
*ref_out = ref;
- }
return 0;
}
-int git_reference_create_oid(
+int git_reference_create(
git_reference **ref_out,
git_repository *repo,
const char *name,
- const git_oid *id,
+ const git_oid *oid,
int force)
{
- git_reference *ref = NULL;
- char normalized[GIT_REFNAME_MAX];
+ git_odb *odb;
+ int error = 0;
- if (normalize_name(normalized, sizeof(normalized), name, 1) < 0)
- return -1;
-
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
-
- if (reference_alloc(&ref, repo, name) < 0)
- return -1;
-
- ref->flags |= GIT_REF_OID;
-
- /* set the oid; this will write the reference on disk */
- if (git_reference_set_oid(ref, id) < 0) {
- git_reference_free(ref);
+ 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;
}
-
- if (ref_out == NULL) {
- git_reference_free(ref);
- } else {
- *ref_out = ref;
- }
-
- return 0;
+
+ return reference__create(ref_out, repo, name, oid, NULL, force);
}
-/*
- * Change the OID target of a reference.
- *
- * For both loose and packed references, just change
- * the oid in memory and (over)write the file in disk.
- *
- * We do not repack packed references because of performance
- * reasons.
- */
-int git_reference_set_oid(git_reference *ref, const git_oid *id)
-{
- git_odb *odb = NULL;
- if ((ref->flags & GIT_REF_OID) == 0) {
- giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
- return -1;
- }
+int git_reference_symbolic_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force)
+{
+ char normalized[GIT_REFNAME_MAX];
+ int error = 0;
- assert(ref->owner);
+ assert(repo && name && target);
+
+ if ((error = git_reference__normalize_name_lax(
+ normalized, sizeof(normalized), target)) < 0)
+ return error;
- if (git_repository_odb__weakptr(&odb, ref->owner) < 0)
- return -1;
+ return reference__create(ref_out, repo, name, NULL, normalized, force);
+}
- /* Don't let the user create references to OIDs that
- * don't exist in the ODB */
- if (!git_odb_exists(odb, id)) {
- giterr_set(GITERR_REFERENCE,
- "Target OID for the reference doesn't exist on the repository");
+int git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id)
+{
+ assert(out && ref && id);
+
+ if (ref->type != GIT_REF_OID) {
+ giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
return -1;
}
- /* Update the OID value on `ref` */
- git_oid_cpy(&ref->target.oid, id);
-
- /* Write back to disk */
- return loose_write(ref);
+ return git_reference_create(out, ref->db->repo, ref->name, id, 1);
}
-/*
- * Change the target of a symbolic reference.
- *
- * This is easy because symrefs cannot be inside
- * a pack. We just change the target in memory
- * and overwrite the file on disk.
- */
-int git_reference_set_target(git_reference *ref, const char *target)
+int git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target)
{
- char normalized[GIT_REFNAME_MAX];
-
- if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
+ assert(out && ref && target);
+
+ if (ref->type != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_REFERENCE,
"Cannot set symbolic target on a direct reference");
return -1;
}
-
- if (normalize_name(normalized, sizeof(normalized), target, 0))
- return -1;
-
- git__free(ref->target.symbolic);
- ref->target.symbolic = git__strdup(normalized);
- GITERR_CHECK_ALLOC(ref->target.symbolic);
-
- return loose_write(ref);
+
+ return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);
}
-int git_reference_rename(git_reference *ref, const char *new_name, int force)
+int git_reference_rename(
+ git_reference **out,
+ git_reference *ref,
+ const char *new_name,
+ int force)
{
- int result;
- git_buf aux_path = GIT_BUF_INIT;
+ unsigned int normalization_flags;
char normalized[GIT_REFNAME_MAX];
-
- const char *head_target = NULL;
- git_reference *head = NULL;
-
- if (normalize_name(normalized, sizeof(normalized),
- new_name, ref->flags & GIT_REF_OID) < 0)
- return -1;
-
- if (reference_can_write(ref->owner, normalized, ref->name, force) < 0)
- return -1;
-
- /* Initialize path now so we won't get an allocation failure once
- * we actually start removing things. */
- if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)
- return -1;
-
- /*
- * Now delete the old ref and remove an possibly existing directory
- * named `new_name`. Note that using the internal `reference_delete`
- * method deletes the ref from disk but doesn't free the pointer, so
- * we can still access the ref's attributes for creating the new one
- */
- if (reference_delete(ref) < 0)
- goto cleanup;
+ bool should_head_be_updated = false;
+ git_reference *result = NULL;
+ git_oid *oid;
+ const char *symbolic;
+ int error = 0;
+ int reference_has_log;
+
+ *out = NULL;
+
+ normalization_flags = ref->type == GIT_REF_SYMBOLIC ?
+ GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL;
+
+ if ((error = git_reference_normalize_name(normalized, sizeof(normalized), new_name, normalization_flags)) < 0 ||
+ (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0)
+ return error;
/*
- * Finally we can create the new reference.
+ * Create the new reference.
*/
- if (ref->flags & GIT_REF_SYMBOLIC) {
- result = git_reference_create_symbolic(
- NULL, ref->owner, new_name, ref->target.symbolic, force);
+ if (ref->type == GIT_REF_OID) {
+ oid = &ref->target.oid;
+ symbolic = NULL;
} else {
- result = git_reference_create_oid(
- NULL, ref->owner, new_name, &ref->target.oid, force);
+ oid = NULL;
+ symbolic = ref->target.symbolic;
}
+
+ if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL)
+ return -1;
- if (result < 0)
- goto rollback;
-
- /*
- * Check if we have to update HEAD.
- */
- if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) {
- giterr_set(GITERR_REFERENCE,
- "Failed to update HEAD after renaming reference");
- goto cleanup;
- }
+ /* Check if we have to update HEAD. */
+ if ((error = git_branch_is_head(ref)) < 0)
+ goto on_error;
- head_target = git_reference_target(head);
+ should_head_be_updated = (error > 0);
- if (head_target && !strcmp(head_target, ref->name)) {
- if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) {
- giterr_set(GITERR_REFERENCE,
- "Failed to update HEAD after renaming reference");
- goto cleanup;
- }
+ /* Now delete the old ref and save the new one. */
+ if ((error = git_refdb_delete(ref->db, ref)) < 0)
+ goto on_error;
+
+ /* Save the new reference. */
+ if ((error = git_refdb_write(ref->db, result)) < 0)
+ goto rollback;
+
+ /* Update HEAD it was poiting to the reference being renamed. */
+ if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) {
+ giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
+ goto on_error;
}
- /*
- * Rename the reflog file.
- */
- if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0)
- goto cleanup;
-
- if (git_path_exists(aux_path.ptr) == true) {
- if (git_reflog_rename(ref, new_name) < 0)
- goto cleanup;
- } else {
- giterr_clear();
+ /* Rename the reflog file, if it exists. */
+ reference_has_log = git_reference_has_log(ref);
+ if (reference_has_log < 0) {
+ error = reference_has_log;
+ goto on_error;
}
+ if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
+ goto on_error;
- /*
- * Change the name of the reference given by the user.
- */
- git__free(ref->name);
- ref->name = git__strdup(new_name);
+ *out = result;
- /* The reference is no longer packed */
- ref->flags &= ~GIT_REF_PACKED;
-
- git_reference_free(head);
- git_buf_free(&aux_path);
- return 0;
-
-cleanup:
- git_reference_free(head);
- git_buf_free(&aux_path);
- return -1;
+ return error;
rollback:
- /*
- * Try to create the old reference again, ignore failures
- */
- if (ref->flags & GIT_REF_SYMBOLIC)
- git_reference_create_symbolic(
- NULL, ref->owner, ref->name, ref->target.symbolic, 0);
- else
- git_reference_create_oid(
- NULL, ref->owner, ref->name, &ref->target.oid, 0);
+ git_refdb_write(ref->db, ref);
- /* The reference is no longer packed */
- ref->flags &= ~GIT_REF_PACKED;
+on_error:
+ git_reference_free(result);
- git_buf_free(&aux_path);
- return -1;
+ return error;
}
-int git_reference_resolve(git_reference **ref_out, git_reference *ref)
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
{
- if (ref->flags & GIT_REF_OID)
- return git_reference_lookup(ref_out, ref->owner, ref->name);
+ if (ref->type == GIT_REF_OID)
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
else
- return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1);
-}
-
-int git_reference_packall(git_repository *repo)
-{
- if (packed_load(repo) < 0 || /* load the existing packfile */
- packed_loadloose(repo) < 0 || /* add all the loose refs */
- packed_write(repo) < 0) /* write back to disk */
- return -1;
-
- return 0;
+ return git_reference_lookup_resolved(ref_out, ref->db->repo,
+ ref->target.symbolic, -1);
}
int git_reference_foreach(
git_repository *repo,
unsigned int list_flags,
- int (*callback)(const char *, void *),
+ git_reference_foreach_cb callback,
void *payload)
{
- int result;
- struct dirent_list_data data;
- git_buf refs_path = GIT_BUF_INIT;
-
- /* list all the packed references first */
- if (list_flags & GIT_REF_PACKED) {
- const char *ref_name;
- void *ref;
- GIT_UNUSED(ref);
-
- if (packed_load(repo) < 0)
- return -1;
-
- git_strmap_foreach(repo->references.packfile, ref_name, ref, {
- if (callback(ref_name, payload) < 0)
- return 0;
- });
- }
-
- /* now list the loose references, trying not to
- * duplicate the ref names already in the packed-refs file */
-
- data.repo_path_len = strlen(repo->path_repository);
- data.list_flags = list_flags;
- data.repo = repo;
- data.callback = callback;
- data.callback_payload = payload;
-
- if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)
- return -1;
-
- result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
- git_buf_free(&refs_path);
+ git_refdb *refdb;
+ git_repository_refdb__weakptr(&refdb, repo);
- return result;
+ return git_refdb_foreach(refdb, list_flags, callback, payload);
}
static int cb__reflist_add(const char *ref, void *data)
@@ -1544,26 +559,6 @@ int git_reference_list(
return 0;
}
-int git_reference_reload(git_reference *ref)
-{
- return reference_lookup(ref);
-}
-
-void git_repository__refcache_free(git_refcache *refs)
-{
- assert(refs);
-
- if (refs->packfile) {
- struct packref *reference;
-
- git_strmap_foreach_value(refs->packfile, reference, {
- git__free(reference);
- });
-
- git_strmap_free(refs->packfile);
- }
-}
-
static int is_valid_ref_char(char ch)
{
if ((unsigned) ch <= ' ')
@@ -1583,112 +578,202 @@ static int is_valid_ref_char(char ch)
}
}
-static int normalize_name(
- char *buffer_out,
- size_t out_size,
- const char *name,
- int is_oid_ref)
+static int ensure_segment_validity(const char *name)
{
- const char *name_end, *buffer_out_start;
- const char *current;
- int contains_a_slash = 0;
+ const char *current = name;
+ char prev = '\0';
+ const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
+ int segment_len;
- assert(name && buffer_out);
+ if (*current == '.')
+ return -1; /* Refname starts with "." */
- buffer_out_start = buffer_out;
- current = name;
- name_end = name + strlen(name);
+ for (current = name; ; current++) {
+ if (*current == '\0' || *current == '/')
+ break;
- /* Terminating null byte */
- out_size--;
+ if (!is_valid_ref_char(*current))
+ return -1; /* Illegal character in refname */
- /* A refname can not be empty */
- if (name_end == name)
- goto invalid_name;
+ if (prev == '.' && *current == '.')
+ return -1; /* Refname contains ".." */
- /* A refname can not end with a dot or a slash */
- if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
- goto invalid_name;
+ if (prev == '@' && *current == '{')
+ return -1; /* Refname contains "@{" */
- while (current < name_end && out_size) {
- if (!is_valid_ref_char(*current))
- goto invalid_name;
+ prev = *current;
+ }
+
+ segment_len = (int)(current - name);
+
+ /* A refname component can not end with ".lock" */
+ if (segment_len >= lock_len &&
+ !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
+ return -1;
+
+ return segment_len;
+}
+
+static bool is_all_caps_and_underscore(const char *name, size_t len)
+{
+ size_t i;
+ char c;
- if (buffer_out > buffer_out_start) {
- char prev = *(buffer_out - 1);
+ assert(name && len > 0);
+
+ for (i = 0; i < len; i++)
+ {
+ c = name[i];
+ if ((c < 'A' || c > 'Z') && c != '_')
+ return false;
+ }
- /* A refname can not start with a dot nor contain a double dot */
- if (*current == '.' && ((prev == '.') || (prev == '/')))
- goto invalid_name;
+ if (*name == '_' || name[len - 1] == '_')
+ return false;
- /* '@{' is forbidden within a refname */
- if (*current == '{' && prev == '@')
- goto invalid_name;
+ return true;
+}
+
+int git_reference__normalize_name(
+ git_buf *buf,
+ const char *name,
+ unsigned int flags)
+{
+ // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100
- /* Prevent multiple slashes from being added to the output */
- if (*current == '/' && prev == '/') {
- current++;
- continue;
+ char *current;
+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
+ unsigned int process_flags;
+ bool normalize = (buf != NULL);
+ assert(name);
+
+ process_flags = flags;
+ current = (char *)name;
+
+ if (*current == '/')
+ goto cleanup;
+
+ if (normalize)
+ git_buf_clear(buf);
+
+ while (true) {
+ segment_len = ensure_segment_validity(current);
+ if (segment_len < 0) {
+ if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
+ current[0] == '*' &&
+ (current[1] == '\0' || current[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
+ segment_len = 1;
+ } else
+ goto cleanup;
+ }
+
+ if (segment_len > 0) {
+ if (normalize) {
+ size_t cur_len = git_buf_len(buf);
+
+ git_buf_joinpath(buf, git_buf_cstr(buf), current);
+ git_buf_truncate(buf,
+ cur_len + segment_len + (segments_count ? 1 : 0));
+
+ if (git_buf_oom(buf)) {
+ error = -1;
+ goto cleanup;
+ }
}
+
+ segments_count++;
}
- if (*current == '/')
- contains_a_slash = 1;
+ /* No empty segment is allowed when not normalizing */
+ if (segment_len == 0 && !normalize)
+ goto cleanup;
+
+ if (current[segment_len] == '\0')
+ break;
- *buffer_out++ = *current++;
- out_size--;
+ current += segment_len + 1;
}
- if (!out_size)
- goto invalid_name;
+ /* A refname can not be empty */
+ if (segment_len == 0 && segments_count == 0)
+ goto cleanup;
- /* Object id refname have to contain at least one slash, except
- * for HEAD in a detached state or MERGE_HEAD if we're in the
- * middle of a merge */
- if (is_oid_ref &&
- !contains_a_slash &&
- strcmp(name, GIT_HEAD_FILE) != 0 &&
- strcmp(name, GIT_MERGE_HEAD_FILE) != 0 &&
- strcmp(name, GIT_FETCH_HEAD_FILE) != 0)
- goto invalid_name;
+ /* A refname can not end with "." */
+ if (current[segment_len - 1] == '.')
+ goto cleanup;
- /* A refname can not end with ".lock" */
- if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
- goto invalid_name;
+ /* A refname can not end with "/" */
+ if (current[segment_len - 1] == '/')
+ goto cleanup;
- *buffer_out = '\0';
+ if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
+ goto cleanup;
- /*
- * For object id references, name has to start with refs/. Again,
- * we need to allow HEAD to be in a detached state.
- */
- if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||
- strcmp(buffer_out_start, GIT_HEAD_FILE)))
- goto invalid_name;
+ if ((segments_count == 1 ) &&
+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
+ ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
+ goto cleanup;
- return 0;
+ if ((segments_count > 1)
+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
+ goto cleanup;
+
+ error = 0;
-invalid_name:
- giterr_set(GITERR_REFERENCE, "The given reference name is not valid");
- return -1;
+cleanup:
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(
+ GITERR_REFERENCE,
+ "The given reference name '%s' is not valid", name);
+
+ if (error && normalize)
+ git_buf_free(buf);
+
+ return error;
}
-int git_reference__normalize_name(
+int git_reference_normalize_name(
char *buffer_out,
- size_t out_size,
- const char *name)
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags)
{
- return normalize_name(buffer_out, out_size, name, 0);
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
+ goto cleanup;
+
+ if (git_buf_len(&buf) > buffer_size - 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "The provided buffer is too short to hold the normalization of '%s'", name);
+ error = GIT_EBUFS;
+ goto cleanup;
+ }
+
+ git_buf_copy_cstr(buffer_out, buffer_size, &buf);
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
}
-int git_reference__normalize_name_oid(
+int git_reference__normalize_name_lax(
char *buffer_out,
size_t out_size,
const char *name)
{
- return normalize_name(buffer_out, out_size, name, 1);
+ return git_reference_normalize_name(
+ buffer_out,
+ out_size,
+ name,
+ GIT_REF_FORMAT_ALLOW_ONELEVEL);
}
-
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
int git_reference_cmp(git_reference *ref1, git_reference *ref2)
@@ -1696,12 +781,185 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2)
assert(ref1 && ref2);
/* let's put symbolic refs before OIDs */
- if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK))
- return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1;
+ if (ref1->type != ref2->type)
+ return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1;
- if (ref1->flags & GIT_REF_SYMBOLIC)
+ if (ref1->type == GIT_REF_SYMBOLIC)
return strcmp(ref1->target.symbolic, ref2->target.symbolic);
return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);
}
+static int reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ int nesting)
+{
+ git_reference *ref;
+ int error = 0;
+
+ 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. */
+ if (error == GIT_ENOTFOUND) {
+ 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,
+ nesting+1);
+ git_reference_free(ref);
+ } else {
+ git_reference_free(ref);
+ error = git_reference_create(NULL, repo, ref_name, oid, 1);
+ }
+
+ return error;
+}
+
+/*
+ * Starting with the reference given by `ref_name`, follows symbolic
+ * references until a direct reference is found and updated the OID
+ * on that direct reference to `oid`.
+ */
+int git_reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid)
+{
+ return reference__update_terminal(repo, ref_name, oid, 0);
+}
+
+int git_reference_foreach_glob(
+ git_repository *repo,
+ const char *glob,
+ unsigned int list_flags,
+ int (*callback)(
+ const char *reference_name,
+ void *payload),
+ void *payload)
+{
+ git_refdb *refdb;
+
+ assert(repo && glob && callback);
+
+ git_repository_refdb__weakptr(&refdb, repo);
+
+ return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload);
+}
+
+int git_reference_has_log(
+ git_reference *ref)
+{
+ git_buf path = GIT_BUF_INIT;
+ int result;
+
+ assert(ref);
+
+ if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository,
+ GIT_REFLOG_DIR, ref->name) < 0)
+ return -1;
+
+ result = git_path_isfile(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return result;
+}
+
+int git_reference__is_branch(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_reference_is_branch(git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_branch(ref->name);
+}
+
+int git_reference__is_remote(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
+}
+
+int git_reference_is_remote(git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_remote(ref->name);
+}
+
+static int peel_error(int error, git_reference *ref, const char* msg)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "The reference '%s' cannot be peeled - %s", git_reference_name(ref), 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,
+ git_otype target_type)
+{
+ git_reference *resolved = NULL;
+ git_object *target = NULL;
+ int error;
+
+ assert(ref);
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+
+ if ((error = reference_target(&target, resolved)) < 0) {
+ peel_error(error, ref, "Cannot retrieve reference target");
+ goto cleanup;
+ }
+
+ if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
+ error = git_object_dup(peeled, target);
+ else
+ error = git_object_peel(peeled, target, target_type);
+
+cleanup:
+ git_object_free(target);
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_reference__is_valid_name(
+ const char *refname,
+ unsigned int flags)
+{
+ int error;
+
+ error = git_reference__normalize_name(NULL, refname, flags) == 0;
+ giterr_clear();
+
+ return error;
+}
+
+int git_reference_is_valid_name(
+ const char *refname)
+{
+ return git_reference__is_valid_name(
+ refname,
+ GIT_REF_FORMAT_ALLOW_ONELEVEL);
+}
diff --git a/src/refs.h b/src/refs.h
index 369e91e1c..7d63c3fbd 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,7 +10,9 @@
#include "common.h"
#include "git2/oid.h"
#include "git2/refs.h"
+#include "git2/refdb.h"
#include "strmap.h"
+#include "buffer.h"
#define GIT_REFS_DIR "refs/"
#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
@@ -27,33 +29,43 @@
#define GIT_PACKEDREFS_FILE_MODE 0666
#define GIT_HEAD_FILE "HEAD"
+#define GIT_ORIG_HEAD_FILE "ORIG_HEAD"
#define GIT_FETCH_HEAD_FILE "FETCH_HEAD"
#define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
+#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"
+#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD"
+#define GIT_BISECT_LOG_FILE "BISECT_LOG"
+#define GIT_REBASE_MERGE_DIR "rebase-merge/"
+#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive"
+#define GIT_REBASE_APPLY_DIR "rebase-apply/"
+#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing"
+#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying"
#define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master"
+#define GIT_STASH_FILE "stash"
+#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE
+
#define GIT_REFNAME_MAX 1024
struct git_reference {
- unsigned int flags;
- git_repository *owner;
- char *name;
- time_t mtime;
+ git_refdb *db;
+
+ git_ref_t type;
union {
git_oid oid;
char *symbolic;
} target;
+
+ char name[0];
};
-typedef struct {
- git_strmap *packfile;
- time_t packfile_time;
-} git_refcache;
-
-void git_repository__refcache_free(git_refcache *refs);
-
-int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name);
-int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name);
+int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name);
+int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags);
+int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid);
+int git_reference__is_valid_name(const char *refname, unsigned int flags);
+int git_reference__is_branch(const char *ref_name);
+int git_reference__is_remote(const char *ref_name);
/**
* Lookup a reference by name and try to resolve to an OID.
diff --git a/src/refspec.c b/src/refspec.c
index 697b1bf87..a51b0cfab 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -11,36 +11,127 @@
#include "refspec.h"
#include "util.h"
#include "posix.h"
+#include "refs.h"
-int git_refspec_parse(git_refspec *refspec, const char *str)
+int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
{
- char *delim;
+ // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636
+
+ size_t llen;
+ int is_glob = 0;
+ const char *lhs, *rhs;
+ int flags;
+
+ assert(refspec && input);
memset(refspec, 0x0, sizeof(git_refspec));
- if (*str == '+') {
+ lhs = input;
+ if (*lhs == '+') {
refspec->force = 1;
- str++;
+ lhs++;
}
- delim = strchr(str, ':');
- if (delim == NULL) {
- refspec->src = git__strdup(str);
- GITERR_CHECK_ALLOC(refspec->src);
+ rhs = strrchr(lhs, ':');
+
+ /*
+ * Before going on, special case ":" (or "+:") as a refspec
+ * for matching refs.
+ */
+ if (!is_fetch && rhs == lhs && rhs[1] == '\0') {
+ refspec->matching = 1;
return 0;
}
- refspec->src = git__strndup(str, delim - str);
- GITERR_CHECK_ALLOC(refspec->src);
+ if (rhs) {
+ size_t rlen = strlen(++rhs);
+ is_glob = (1 <= rlen && strchr(rhs, '*'));
+ refspec->dst = git__strndup(rhs, rlen);
+ }
+
+ llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
+ if (1 <= llen && memchr(lhs, '*', llen)) {
+ if ((rhs && !is_glob) || (!rhs && is_fetch))
+ goto invalid;
+ is_glob = 1;
+ } else if (rhs && is_glob)
+ goto invalid;
- refspec->dst = git__strdup(delim + 1);
- if (refspec->dst == NULL) {
- git__free(refspec->src);
- refspec->src = NULL;
- return -1;
+ refspec->pattern = is_glob;
+ refspec->src = git__strndup(lhs, llen);
+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL
+ | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
+
+ if (is_fetch) {
+ /*
+ * LHS
+ * - empty is allowed; it means HEAD.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ /*
+ * RHS
+ * - missing is ok, and is same as empty.
+ * - empty is ok; it means not to store.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst)
+ ; /* ok */
+ else if (!*refspec->dst)
+ ; /* ok */
+ else if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ } else {
+ /*
+ * LHS
+ * - empty is allowed; it means delete.
+ * - when wildcarded, it must be a valid looking ref.
+ * - otherwise, it must be an extended SHA-1, but
+ * there is no existing way to validate this.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (is_glob) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ }
+ else {
+ ; /* anything goes, for now */
+ }
+ /*
+ * RHS
+ * - missing is allowed, but LHS then must be a
+ * valid looking ref.
+ * - empty is not allowed.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ } else if (!*refspec->dst) {
+ goto invalid;
+ } else {
+ if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ }
}
return 0;
+
+ invalid:
+ return -1;
+}
+
+void git_refspec__free(git_refspec *refspec)
+{
+ if (refspec == NULL)
+ return;
+
+ git__free(refspec->src);
+ git__free(refspec->dst);
}
const char *git_refspec_src(const git_refspec *refspec)
@@ -53,6 +144,13 @@ const char *git_refspec_dst(const git_refspec *refspec)
return refspec == NULL ? NULL : refspec->dst;
}
+int git_refspec_force(const git_refspec *refspec)
+{
+ assert(refspec);
+
+ return refspec->force;
+}
+
int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->src == NULL)
@@ -61,11 +159,19 @@ int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
return (p_fnmatch(refspec->src, refname, 0) == 0);
}
-int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
+{
+ if (refspec == NULL || refspec->dst == NULL)
+ return false;
+
+ return (p_fnmatch(refspec->dst, refname, 0) == 0);
+}
+
+static int refspec_transform_internal(char *out, size_t outlen, const char *from, const char *to, const char *name)
{
size_t baselen, namelen;
- baselen = strlen(spec->dst);
+ baselen = strlen(to);
if (outlen <= baselen) {
giterr_set(GITERR_INVALID, "Reference name too long");
return GIT_EBUFS;
@@ -75,8 +181,8 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
* No '*' at the end means that it's mapped to one specific local
* branch, so no actual transformation is needed.
*/
- if (spec->dst[baselen - 1] != '*') {
- memcpy(out, spec->dst, baselen + 1); /* include '\0' */
+ if (to[baselen - 1] != '*') {
+ memcpy(out, to, baselen + 1); /* include '\0' */
return 0;
}
@@ -84,7 +190,7 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
baselen--;
/* skip the prefix, -1 is for the '*' */
- name += strlen(spec->src) - 1;
+ name += strlen(from) - 1;
namelen = strlen(name);
@@ -93,26 +199,36 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
return GIT_EBUFS;
}
- memcpy(out, spec->dst, baselen);
+ memcpy(out, to, baselen);
memcpy(out + baselen, name, namelen + 1);
return 0;
}
-int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)
+int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+{
+ return refspec_transform_internal(out, outlen, spec->src, spec->dst, name);
+}
+
+int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+{
+ return refspec_transform_internal(out, outlen, spec->dst, spec->src, name);
+}
+
+static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name)
{
- if (git_buf_sets(out, spec->dst) < 0)
+ if (git_buf_sets(out, to) < 0)
return -1;
/*
- * No '*' at the end means that it's mapped to one specific local
+ * No '*' at the end means that it's mapped to one specific
* branch, so no actual transformation is needed.
*/
if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*')
return 0;
git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */
- git_buf_puts(out, name + strlen(spec->src) - 1);
+ git_buf_puts(out, name + strlen(from) - 1);
if (git_buf_oom(out))
return -1;
@@ -120,3 +236,31 @@ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *n
return 0;
}
+int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)
+{
+ return refspec_transform(out, spec->src, spec->dst, name);
+}
+
+int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name)
+{
+ return refspec_transform(out, spec->dst, spec->src, name);
+}
+
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
+{
+ if (refspec->force)
+ git_buf_putc(out, '+');
+
+ git_buf_printf(out, "%s:%s",
+ refspec->src != NULL ? refspec->src : "",
+ refspec->dst != NULL ? refspec->dst : "");
+
+ return git_buf_oom(out) == false;
+}
+
+int git_refspec_is_wildcard(const git_refspec *spec)
+{
+ assert(spec && spec->src);
+
+ return (spec->src[strlen(spec->src) - 1] == '*');
+}
diff --git a/src/refspec.h b/src/refspec.h
index 2db504910..a7a4dd834 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -19,7 +19,15 @@ struct git_refspec {
matching :1;
};
+#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"
+
int git_refspec_parse(struct git_refspec *refspec, const char *str);
+int git_refspec__parse(
+ struct git_refspec *refspec,
+ const char *str,
+ bool is_fetch);
+
+void git_refspec__free(git_refspec *refspec);
/**
* Transform a reference to its target following the refspec's rules,
@@ -32,4 +40,25 @@ int git_refspec_parse(struct git_refspec *refspec, const char *str);
*/
int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name);
+/**
+ * Transform a reference from its target following the refspec's rules,
+ * and writes the results into a git_buf.
+ *
+ * @param out where to store the source name
+ * @param spec the refspec
+ * @param name the name of the reference to transform
+ * @return 0 or error if buffer allocation fails
+ */
+int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name);
+
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec);
+
+/**
+ * Determines if a refspec is a wildcard refspec.
+ *
+ * @param spec the refspec
+ * @return 1 if the refspec is a wildcard, 0 otherwise
+ */
+int git_refspec_is_wildcard(const git_refspec *spec);
+
#endif
diff --git a/src/remote.c b/src/remote.c
index 9740344f8..56853834b 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1,74 +1,94 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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/remote.h"
#include "git2/config.h"
#include "git2/types.h"
+#include "git2/oid.h"
#include "config.h"
#include "repository.h"
#include "remote.h"
#include "fetch.h"
#include "refs.h"
+#include "refspec.h"
+#include "fetchhead.h"
#include <regex.h>
-static int refspec_parse(git_refspec *refspec, const char *str)
+static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch)
{
- char *delim;
+ int error;
+ const char *val;
- memset(refspec, 0x0, sizeof(git_refspec));
+ if ((error = git_config_get_string(&val, cfg, var)) < 0)
+ return error;
- if (*str == '+') {
- refspec->force = 1;
- str++;
- }
+ return git_refspec__parse(refspec, val, is_fetch);
+}
- delim = strchr(str, ':');
- if (delim == NULL) {
- giterr_set(GITERR_NET, "Invalid refspec, missing ':'");
+static int download_tags_value(git_remote *remote, git_config *cfg)
+{
+ const char *val;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET)
+ return 0;
+
+ /* This is the default, let's see if we need to change it */
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
return -1;
- }
- refspec->src = git__strndup(str, delim - str);
- GITERR_CHECK_ALLOC(refspec->src);
+ error = git_config_get_string(&val, cfg, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+ if (!error && !strcmp(val, "--no-tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+ else if (!error && !strcmp(val, "--tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
- refspec->dst = git__strdup(delim + 1);
- GITERR_CHECK_ALLOC(refspec->dst);
+ if (error == GIT_ENOTFOUND)
+ error = 0;
- return 0;
+ return error;
}
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)
+static int ensure_remote_name_is_valid(const char *name)
{
- int error;
- const char *val;
+ int error = 0;
- if ((error = git_config_get_string(&val, cfg, var)) < 0)
- return error;
+ if (!git_remote_is_valid_name(name)) {
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name);
+ error = GIT_EINVALIDSPEC;
+ }
- return refspec_parse(refspec, val);
+ return error;
}
-int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
+static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
git_remote *remote;
+ git_buf fetchbuf = GIT_BUF_INIT;
+ int error = -1;
/* name is optional */
assert(out && repo && url);
- remote = git__malloc(sizeof(git_remote));
+ remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);
- memset(remote, 0x0, sizeof(git_remote));
remote->repo = repo;
+ remote->check_cert = 1;
+ remote->update_fetchhead = 1;
if (git_vector_init(&remote->refs, 32, NULL) < 0)
- return -1;
+ goto on_error;
remote->url = git__strdup(url);
GITERR_CHECK_ALLOC(remote->url);
@@ -79,18 +99,93 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
}
if (fetch != NULL) {
- if (refspec_parse(&remote->fetch, fetch) < 0)
+ if (git_refspec__parse(&remote->fetch, fetch, true) < 0)
goto on_error;
}
+ /* A remote without a name doesn't download tags */
+ if (!name) {
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+ }
+
+ *out = remote;
+ git_buf_free(&fetchbuf);
+ return 0;
+
+on_error:
+ git_remote_free(remote);
+ git_buf_free(&fetchbuf);
+ return error;
+}
+
+static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
+{
+ int error;
+ git_remote *remote;
+
+ error = git_remote_load(&remote, repo, name);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ git_remote_free(remote);
+
+ giterr_set(
+ GITERR_CONFIG,
+ "Remote '%s' already exists.", name);
+
+ return GIT_EEXISTS;
+}
+
+
+int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_remote *remote = NULL;
+ int error;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
+ return error;
+
+ if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return -1;
+
+ if (create_internal(&remote, repo, name, url, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+
+ if (git_remote_save(remote) < 0)
+ goto on_error;
+
*out = remote;
+
return 0;
on_error:
+ git_buf_free(&buf);
git_remote_free(remote);
return -1;
}
+int git_remote_create_inmemory(git_remote **out, git_repository *repo, const char *fetch, const char *url)
+{
+ int error;
+ git_remote *remote;
+
+ if ((error = create_internal(&remote, repo, NULL, url, fetch)) < 0)
+ return error;
+
+ *out = remote;
+ return 0;
+}
+
int git_remote_load(git_remote **out, git_repository *repo, const char *name)
{
git_remote *remote;
@@ -101,6 +196,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
assert(out && repo && name);
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
@@ -108,6 +206,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
GITERR_CHECK_ALLOC(remote);
memset(remote, 0x0, sizeof(git_remote));
+ remote->check_cert = 1;
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
@@ -123,18 +223,46 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)
goto cleanup;
+
+ if (strlen(val) == 0) {
+ giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name);
+ error = -1;
+ goto cleanup;
+ }
remote->repo = repo;
remote->url = git__strdup(val);
GITERR_CHECK_ALLOC(remote->url);
git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_config_get_string(&val, config, git_buf_cstr(&buf));
+ if (error == GIT_ENOTFOUND) {
+ val = NULL;
+ error = 0;
+ }
+
+ if (error < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (val) {
+ remote->pushurl = git__strdup(val);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ }
+
+ git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) {
error = -1;
goto cleanup;
}
- error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf));
+ error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -149,7 +277,7 @@ 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));
+ error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -158,6 +286,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
goto cleanup;
}
+ if (download_tags_value(remote, config) < 0)
+ goto cleanup;
+
*out = remote;
cleanup:
@@ -169,15 +300,61 @@ cleanup:
return error;
}
+static int update_config_refspec(
+ git_config *config,
+ const char *remote_name,
+ const git_refspec *refspec,
+ int git_direction)
+{
+ git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ int error = -1;
+
+ if (refspec->src == NULL || refspec->dst == NULL)
+ return 0;
+
+ if (git_buf_printf(
+ &name,
+ "remote.%s.%s",
+ remote_name,
+ git_direction == GIT_DIRECTION_FETCH ? "fetch" : "push") < 0)
+ goto cleanup;
+
+ if (git_refspec__serialize(&value, refspec) < 0)
+ goto cleanup;
+
+ error = git_config_set_string(
+ config,
+ git_buf_cstr(&name),
+ git_buf_cstr(&value));
+
+cleanup:
+ git_buf_free(&name);
+ git_buf_free(&value);
+
+ return error;
+}
+
int git_remote_save(const git_remote *remote)
{
+ int error;
git_config *config;
- git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ const char *tagopt = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(remote);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't save an in-memory remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(remote->name)) < 0)
+ return error;
if (git_repository_config__weakptr(&config, remote->repo) < 0)
return -1;
- if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0)
+ if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0)
return -1;
if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) {
@@ -185,71 +362,142 @@ int git_remote_save(const git_remote *remote)
return -1;
}
- if (remote->fetch.src != NULL && remote->fetch.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.fetch", remote->name);
- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0)
+ return -1;
+
+ if (remote->pushurl) {
+ if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) {
+ git_buf_free(&buf);
+ return -1;
+ }
+ } else {
+ int error = git_config_delete_entry(config, git_buf_cstr(&buf));
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+ if (error < 0) {
+ git_buf_free(&buf);
return -1;
+ }
+ }
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->fetch,
+ GIT_DIRECTION_FETCH) < 0)
goto on_error;
- }
- if (remote->push.src != NULL && remote->push.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.push", remote->name);
- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->push,
+ GIT_DIRECTION_PUSH) < 0)
+ goto on_error;
+
+ /*
+ * What action to take depends on the old and new values. This
+ * is describes by the table below. tagopt means whether the
+ * is already a value set in the config
+ *
+ * AUTO ALL or NONE
+ * +-----------------------+
+ * tagopt | remove | set |
+ * +---------+-------------|
+ * !tagopt | nothing | set |
+ * +---------+-------------+
+ */
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
+ goto on_error;
+
+ error = git_config_get_string(&tagopt, config, git_buf_cstr(&buf));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ if (git_config_set_string(config, git_buf_cstr(&buf), "--tags") < 0)
+ goto on_error;
+ } else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+ if (git_config_set_string(config, git_buf_cstr(&buf), "--no-tags") < 0)
+ goto on_error;
+ } else if (tagopt) {
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
}
git_buf_free(&buf);
- git_buf_free(&value);
return 0;
on_error:
git_buf_free(&buf);
- git_buf_free(&value);
return -1;
}
-const char *git_remote_name(git_remote *remote)
+const char *git_remote_name(const git_remote *remote)
{
assert(remote);
return remote->name;
}
-const char *git_remote_url(git_remote *remote)
+const char *git_remote_url(const git_remote *remote)
{
assert(remote);
return remote->url;
}
+int git_remote_set_url(git_remote *remote, const char* url)
+{
+ assert(remote);
+ assert(url);
+
+ git__free(remote->url);
+ remote->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(remote->url);
+
+ return 0;
+}
+
+const char *git_remote_pushurl(const git_remote *remote)
+{
+ assert(remote);
+ return remote->pushurl;
+}
+
+int git_remote_set_pushurl(git_remote *remote, const char* url)
+{
+ assert(remote);
+
+ git__free(remote->pushurl);
+ if (url) {
+ remote->pushurl = git__strdup(url);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ } else {
+ remote->pushurl = NULL;
+ }
+ return 0;
+}
+
int git_remote_set_fetchspec(git_remote *remote, const char *spec)
{
git_refspec refspec;
assert(remote && spec);
- if (refspec_parse(&refspec, spec) < 0)
+ if (git_refspec__parse(&refspec, spec, true) < 0)
return -1;
- git__free(remote->fetch.src);
- git__free(remote->fetch.dst);
- remote->fetch.src = refspec.src;
- remote->fetch.dst = refspec.dst;
+ git_refspec__free(&remote->fetch);
+ memcpy(&remote->fetch, &refspec, sizeof(git_refspec));
return 0;
}
-const git_refspec *git_remote_fetchspec(git_remote *remote)
+const git_refspec *git_remote_fetchspec(const git_remote *remote)
{
assert(remote);
return &remote->fetch;
@@ -261,35 +509,65 @@ int git_remote_set_pushspec(git_remote *remote, const char *spec)
assert(remote && spec);
- if (refspec_parse(&refspec, spec) < 0)
+ if (git_refspec__parse(&refspec, spec, false) < 0)
return -1;
- git__free(remote->push.src);
- git__free(remote->push.dst);
+ git_refspec__free(&remote->push);
remote->push.src = refspec.src;
remote->push.dst = refspec.dst;
return 0;
}
-const git_refspec *git_remote_pushspec(git_remote *remote)
+const git_refspec *git_remote_pushspec(const git_remote *remote)
{
assert(remote);
return &remote->push;
}
-int git_remote_connect(git_remote *remote, int direction)
+const char* git_remote__urlfordirection(git_remote *remote, int direction)
+{
+ assert(remote);
+
+ if (direction == GIT_DIRECTION_FETCH) {
+ return remote->url;
+ }
+
+ if (direction == GIT_DIRECTION_PUSH) {
+ return remote->pushurl ? remote->pushurl : remote->url;
+ }
+
+ return NULL;
+}
+
+int git_remote_connect(git_remote *remote, git_direction direction)
{
git_transport *t;
+ const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
assert(remote);
- if (git_transport_new(&t, remote->url) < 0)
+ t = remote->transport;
+
+ url = git_remote__urlfordirection(remote, direction);
+ if (url == NULL )
return -1;
- if (t->connect(t, direction) < 0) {
+ /* A transport could have been supplied in advance with
+ * git_remote_set_transport */
+ if (!t && git_transport_new(&t, remote, url) < 0)
+ return -1;
+
+ if (t->set_callbacks &&
+ t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0)
+ goto on_error;
+
+ if (!remote->check_cert)
+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
+
+ if (t->connect(t, url, remote->cred_acquire_cb, remote->cred_acquire_payload, direction, flags) < 0)
goto on_error;
- }
remote->transport = t;
@@ -297,6 +575,10 @@ int git_remote_connect(git_remote *remote, int direction)
on_error:
t->free(t);
+
+ if (t == remote->transport)
+ remote->transport = NULL;
+
return -1;
}
@@ -304,59 +586,283 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
assert(remote);
- if (!remote->transport || !remote->transport->connected) {
- giterr_set(GITERR_NET, "The remote is not connected");
+ return remote->transport->ls(remote->transport, list_cb, payload);
+}
+
+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url)
+{
+ git_config *cfg;
+ const char *val;
+
+ assert(remote);
+
+ if (!proxy_url || !remote->repo)
return -1;
+
+ *proxy_url = NULL;
+
+ if (git_repository_config__weakptr(&cfg, remote->repo) < 0)
+ return -1;
+
+ /* Go through the possible sources for proxy configuration, from most specific
+ * to least specific. */
+
+ /* remote.<name>.proxy config setting */
+ if (remote->name && 0 != *(remote->name)) {
+ git_buf buf = GIT_BUF_INIT;
+
+ if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0)
+ return -1;
+
+ if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) &&
+ val && ('\0' != *val)) {
+ git_buf_free(&buf);
+
+ *proxy_url = git__strdup(val);
+ GITERR_CHECK_ALLOC(*proxy_url);
+ return 0;
+ }
+
+ git_buf_free(&buf);
}
- return remote->transport->ls(remote->transport, list_cb, payload);
+ /* http.proxy config setting */
+ if (!git_config_get_string(&val, cfg, "http.proxy") &&
+ val && ('\0' != *val)) {
+ *proxy_url = git__strdup(val);
+ GITERR_CHECK_ALLOC(*proxy_url);
+ return 0;
+ }
+
+ /* HTTP_PROXY / HTTPS_PROXY environment variables */
+ val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY");
+
+ if (val && ('\0' != *val)) {
+ *proxy_url = git__strdup(val);
+ GITERR_CHECK_ALLOC(*proxy_url);
+ return 0;
+ }
+
+ return 0;
}
-int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+int git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
int error;
- assert(remote && bytes && stats);
+ assert(remote);
if ((error = git_fetch_negotiate(remote)) < 0)
return error;
- return git_fetch_download_pack(remote, bytes, stats);
+ 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;
+ git_remote_head *remote_ref;
+
+ assert(update_heads && fetchspec_src);
+
+ *out = NULL;
+
+ git_vector_foreach(update_heads, i, remote_ref) {
+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {
+ *out = remote_ref;
+ break;
+ }
+ }
+
+ return 0;
}
-int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b))
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, 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);
+
+ *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) {
+ /* Not an error if HEAD is orphaned or no tracking branch */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ goto cleanup;
+ }
+
+ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
+
+cleanup:
+ git_reference_free(tracking_ref);
+ git_reference_free(resolved_ref);
+ git_buf_free(&remote_name);
+ return error;
+}
+
+static int git_remote_write_fetchhead(git_remote *remote, 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;
+ git_vector fetchhead_refs;
+ bool include_all_fetchheads;
+ unsigned int i = 0;
+ int error = 0;
+
+ assert(remote);
+
+ /* no heads, nothing to do */
+ 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;
+
+ /* Iff refspec is * (but not subdir slash star), include tags */
+ include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);
+
+ /* 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)
+ goto cleanup;
+ } else {
+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
+ goto cleanup;
+ }
+
+ /* Create the FETCH_HEAD file */
+ git_vector_foreach(update_heads, i, remote_ref) {
+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);
+
+ if (!include_all_fetchheads &&
+ !git_refspec_src_matches(spec, remote_ref->name) &&
+ !merge_this_fetchhead)
+ continue;
+
+ if (git_fetchhead_ref_create(&fetchhead_ref,
+ &remote_ref->oid,
+ merge_this_fetchhead,
+ remote_ref->name,
+ git_remote_url(remote)) < 0)
+ goto cleanup;
+
+ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
+ goto cleanup;
+ }
+
+ git_fetchhead_write(remote->repo, &fetchhead_refs);
+
+cleanup:
+ for (i = 0; i < fetchhead_refs.length; ++i)
+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);
+
+ git_vector_free(&fetchhead_refs);
+ git_reference_free(head_ref);
+
+ return error;
+}
+
+int git_remote_update_tips(git_remote *remote)
+{
+ int error = 0, autotag;
unsigned int i = 0;
git_buf refname = GIT_BUF_INIT;
git_oid old;
- git_vector *refs = &remote->refs;
+ git_odb *odb;
git_remote_head *head;
git_reference *ref;
- struct git_refspec *spec = &remote->fetch;
+ struct git_refspec *spec;
+ git_refspec tagspec;
+ git_vector refs, update_heads;
assert(remote);
- if (refs->length == 0)
- return 0;
+ spec = &remote->fetch;
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ return -1;
- /* HEAD is only allowed to be the first in the list */
- head = refs->contents[0];
- if (!strcmp(head->name, GIT_HEAD_FILE)) {
- if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
- return -1;
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
- i = 1;
- git_reference_free(ref);
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&refs, 16, NULL) < 0 ||
+ git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
+
+ if (git_remote_ls(remote, update_tips_callback, &refs) < 0)
+ goto on_error;
+
+ /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
+ if (refs.length > 0) {
+ head = (git_remote_head *)refs.contents[0];
+
+ if (!strcmp(head->name, GIT_HEAD_FILE)) {
+ if (git_reference_create(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
+ goto on_error;
+
+ i = 1;
+ git_reference_free(ref);
+ }
}
- for (; i < refs->length; ++i) {
- head = refs->contents[i];
+ for (; i < refs.length; ++i) {
+ head = (git_remote_head *)refs.contents[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_transform_r(&refname, spec, head->name) < 0)
+ goto on_error;
+ } else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+
+ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL)
+ autotag = 1;
- if (git_refspec_transform_r(&refname, spec, head->name) < 0)
+ if (!git_refspec_src_matches(&tagspec, head->name))
+ continue;
+
+ git_buf_clear(&refname);
+ if (git_buf_puts(&refname, head->name) < 0)
+ goto on_error;
+ } else {
+ continue;
+ }
+
+ if (autotag && !git_odb_exists(odb, &head->oid))
+ continue;
+
+ if (git_vector_insert(&update_heads, head) < 0)
goto on_error;
- error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
@@ -366,21 +872,33 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co
if (!git_oid_cmp(&old, &head->oid))
continue;
- if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)
- break;
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag);
+ if (error < 0 && error != GIT_EEXISTS)
+ goto on_error;
git_reference_free(ref);
- if (cb != NULL) {
- if (cb(refname.ptr, &old, &head->oid) < 0)
+ if (remote->callbacks.update_tips != NULL) {
+ if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.payload) < 0)
goto on_error;
}
}
+ if (git_remote_update_fetchhead(remote) &&
+ (error = git_remote_write_fetchhead(remote, &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);
return -1;
@@ -389,15 +907,28 @@ on_error:
int git_remote_connected(git_remote *remote)
{
assert(remote);
- return remote->transport == NULL ? 0 : remote->transport->connected;
+
+ if (!remote->transport || !remote->transport->is_connected)
+ return 0;
+
+ /* Ask the transport if it's connected. */
+ return remote->transport->is_connected(remote->transport);
+}
+
+void git_remote_stop(git_remote *remote)
+{
+ assert(remote);
+
+ if (remote->transport && remote->transport->cancel)
+ remote->transport->cancel(remote->transport);
}
void git_remote_disconnect(git_remote *remote)
{
assert(remote);
- if (remote->transport != NULL && remote->transport->connected)
- remote->transport->close(remote->transport);
+ if (git_remote_connected(remote))
+ remote->transport->close(remote->transport);
}
void git_remote_free(git_remote *remote)
@@ -414,11 +945,10 @@ void git_remote_free(git_remote *remote)
git_vector_free(&remote->refs);
- git__free(remote->fetch.src);
- git__free(remote->fetch.dst);
- git__free(remote->push.src);
- git__free(remote->push.dst);
+ git_refspec__free(&remote->fetch);
+ git_refspec__free(&remote->push);
git__free(remote->url);
+ git__free(remote->pushurl);
git__free(remote->name);
git__free(remote);
}
@@ -428,12 +958,12 @@ struct cb_data {
regex_t *preg;
};
-static int remote_list_cb(const char *name, const char *value, void *data_)
+static int remote_list_cb(const git_config_entry *entry, void *data_)
{
struct cb_data *data = (struct cb_data *)data_;
size_t nmatch = 2;
regmatch_t pmatch[2];
- GIT_UNUSED(value);
+ const char *name = entry->name;
if (!regexec(data->preg, name, nmatch, pmatch, 0)) {
char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so);
@@ -477,6 +1007,11 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
}
git_vector_free(&list);
+
+ /* cb error is converted to GIT_EUSER by git_config_foreach */
+ if (error == GIT_EUSER)
+ error = -1;
+
return error;
}
@@ -486,25 +1021,371 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
return 0;
}
-int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url)
+void git_remote_check_cert(git_remote *remote, int check)
{
- git_buf buf = GIT_BUF_INIT;
+ assert(remote);
+
+ remote->check_cert = check;
+}
+
+int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks)
+{
+ assert(remote && callbacks);
+
+ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+
+ memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));
+
+ if (remote->transport && remote->transport->set_callbacks)
+ remote->transport->set_callbacks(remote->transport,
+ remote->callbacks.progress,
+ NULL,
+ remote->callbacks.payload);
- if (git_buf_printf(&buf, "refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return 0;
+}
+
+void git_remote_set_cred_acquire_cb(
+ git_remote *remote,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *payload)
+{
+ assert(remote);
+
+ remote->cred_acquire_cb = cred_acquire_cb;
+ remote->cred_acquire_payload = payload;
+}
+
+int git_remote_set_transport(git_remote *remote, git_transport *transport)
+{
+ assert(remote && transport);
+
+ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+
+ if (remote->transport) {
+ giterr_set(GITERR_NET, "A transport is already bound to this remote");
return -1;
+ }
- if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0)
- goto on_error;
+ remote->transport = transport;
+ return 0;
+}
- git_buf_free(&buf);
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
+}
- if (git_remote_save(*out) < 0)
- goto on_error;
+git_remote_autotag_option_t git_remote_autotag(git_remote *remote)
+{
+ return remote->download_tags;
+}
+
+void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t value)
+{
+ remote->download_tags = value;
+}
+
+static int rename_remote_config_section(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_buf old_section_name = GIT_BUF_INIT,
+ new_section_name = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)
+ goto cleanup;
+
+ error = git_config_rename_section(
+ repo,
+ git_buf_cstr(&old_section_name),
+ git_buf_cstr(&new_section_name));
+
+cleanup:
+ git_buf_free(&old_section_name);
+ git_buf_free(&new_section_name);
+
+ return error;
+}
+
+struct update_data
+{
+ git_config *config;
+ const char *old_remote_name;
+ const char *new_remote_name;
+};
+
+static int update_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ struct update_data *data = (struct update_data *)payload;
+
+ if (strcmp(entry->value, data->old_remote_name))
+ return 0;
+
+ return git_config_set_string(
+ data->config,
+ entry->name,
+ data->new_remote_name);
+}
+
+static int update_branch_remote_config_entry(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_config *config;
+ struct update_data data;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ return -1;
+
+ data.config = config;
+ data.old_remote_name = old_name;
+ data.new_remote_name = new_name;
+
+ return git_config_foreach_match(
+ config,
+ "branch\\..+\\.remote",
+ update_config_entries_cb, &data);
+}
+
+static int rename_cb(const char *ref, void *data)
+{
+ if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR))
+ return 0;
+
+ return git_vector_insert((git_vector *)data, git__strdup(ref));
+}
+
+static int rename_one_remote_reference(
+ git_repository *repo,
+ const char *reference_name,
+ const char *old_remote_name,
+ const char *new_remote_name)
+{
+ int error = -1;
+ git_buf new_name = GIT_BUF_INIT;
+ git_reference *reference = NULL;
+ git_reference *newref = NULL;
+
+ if (git_buf_printf(
+ &new_name,
+ GIT_REFS_REMOTES_DIR "%s%s",
+ new_remote_name,
+ reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)
+ return -1;
+
+ if (git_reference_lookup(&reference, repo, reference_name) < 0)
+ goto cleanup;
+
+ error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0);
+ git_reference_free(reference);
+
+cleanup:
+ git_reference_free(newref);
+ git_buf_free(&new_name);
+ return error;
+}
+
+static int rename_remote_references(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_vector refnames;
+ int error = -1;
+ unsigned int i;
+ char *name;
+
+ if (git_vector_init(&refnames, 8, NULL) < 0)
+ goto cleanup;
+
+ if (git_reference_foreach(
+ repo,
+ GIT_REF_LISTALL,
+ rename_cb,
+ &refnames) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&refnames, i, name) {
+ if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0)
+ goto cleanup;
+ }
+
+ error = 0;
+cleanup:
+ git_vector_foreach(&refnames, i, name) {
+ git__free(name);
+ }
+
+ git_vector_free(&refnames);
+ return error;
+}
+
+static int rename_fetch_refspecs(
+ git_remote *remote,
+ const char *new_name,
+ int (*callback)(const char *problematic_refspec, void *payload),
+ void *payload)
+{
+ git_config *config;
+ const git_refspec *fetch_refspec;
+ git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT;
+ const char* pos;
+ 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)
+ goto cleanup;
+
+ /* Is it an in-memory remote? */
+ if (!remote->name) {
+ error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
+ goto cleanup;
+ }
+
+ if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0)
+ goto cleanup;
+
+ pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix));
+
+ /* 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;
+ }
+
+ if (git_buf_splice(
+ &serialized,
+ pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"),
+ strlen(remote->name), new_name,
+ strlen(new_name)) < 0)
+ goto cleanup;
+
+ git_refspec__free(&remote->fetch);
+
+ 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;
+
+ error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIRECTION_FETCH);
+
+cleanup:
+ git_buf_free(&serialized);
+ git_buf_free(&dst_prefix);
+ return error;
+}
+
+int git_remote_rename(
+ git_remote *remote,
+ const char *new_name,
+ git_remote_rename_problem_cb callback,
+ void *payload)
+{
+ int error;
+
+ assert(remote && new_name);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't rename an in-memory remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(new_name)) < 0)
+ return error;
+
+ if (remote->repo) {
+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
+ return error;
+
+ if (!remote->name) {
+ if ((error = rename_fetch_refspecs(
+ remote,
+ new_name,
+ callback,
+ payload)) < 0)
+ return error;
+
+ remote->name = git__strdup(new_name);
+
+ if (!remote->name) return 0;
+ return git_remote_save(remote);
+ }
+
+ if ((error = rename_remote_config_section(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = update_branch_remote_config_entry(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = rename_remote_references(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = rename_fetch_refspecs(
+ remote,
+ new_name,
+ callback,
+ payload)) < 0)
+ return error;
+ }
+
+ git__free(remote->name);
+ remote->name = git__strdup(new_name);
return 0;
+}
+
+int git_remote_update_fetchhead(git_remote *remote)
+{
+ return remote->update_fetchhead;
+}
+
+void git_remote_set_update_fetchhead(git_remote *remote, int value)
+{
+ remote->update_fetchhead = value;
+}
+
+int git_remote_is_valid_name(
+ const char *remote_name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!remote_name || *remote_name == '\0')
+ return 0;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
-on_error:
git_buf_free(&buf);
- git_remote_free(*out);
- return -1;
+ git_refspec__free(&refspec);
+
+ giterr_clear();
+ return error == 0;
}
diff --git a/src/remote.h b/src/remote.h
index 5a1625d05..4c1a18aa7 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,19 +7,34 @@
#ifndef INCLUDE_remote_h__
#define INCLUDE_remote_h__
+#include "git2/remote.h"
+#include "git2/transport.h"
+
#include "refspec.h"
-#include "transport.h"
#include "repository.h"
+#define GIT_REMOTE_ORIGIN "origin"
+
struct git_remote {
char *name;
char *url;
+ char *pushurl;
git_vector refs;
struct git_refspec fetch;
struct git_refspec push;
+ git_cred_acquire_cb cred_acquire_cb;
+ void *cred_acquire_payload;
git_transport *transport;
git_repository *repo;
- unsigned int need_pack:1;
+ git_remote_callbacks callbacks;
+ git_transfer_progress stats;
+ unsigned int need_pack;
+ git_remote_autotag_option_t download_tags;
+ unsigned int check_cert;
+ unsigned int update_fetchhead;
};
+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);
+
#endif
diff --git a/src/repo_template.h b/src/repo_template.h
new file mode 100644
index 000000000..099279aa7
--- /dev/null
+++ b/src/repo_template.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_repo_template_h__
+#define INCLUDE_repo_template_h__
+
+#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"
+#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"
+
+#define GIT_HOOKS_DIR "hooks/"
+#define GIT_HOOKS_DIR_MODE 0777
+
+#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"
+#define GIT_HOOKS_README_MODE 0777
+#define GIT_HOOKS_README_CONTENT \
+"#!/bin/sh\n"\
+"#\n"\
+"# Place appropriately named executable hook scripts into this directory\n"\
+"# to intercept various actions that git takes. See `git help hooks` for\n"\
+"# more information.\n"
+
+#define GIT_INFO_DIR "info/"
+#define GIT_INFO_DIR_MODE 0777
+
+#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"
+#define GIT_INFO_EXCLUDE_MODE 0666
+#define GIT_INFO_EXCLUDE_CONTENT \
+"# File patterns to ignore; see `git help ignore` for more information.\n"\
+"# Lines that start with '#' are comments.\n"
+
+#define GIT_DESC_FILE "description"
+#define GIT_DESC_MODE 0666
+#define GIT_DESC_CONTENT \
+"Unnamed repository; edit this file 'description' to name the repository.\n"
+
+typedef struct {
+ const char *path;
+ mode_t mode;
+ const char *content;
+} repo_template_item;
+
+static repo_template_item repo_template[] = {
+ { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */
+ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */
+ { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */
+ { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */
+ { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */
+ { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */
+ { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },
+ { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },
+ { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },
+ { NULL, 0, NULL }
+};
+
+#endif
diff --git a/src/repository.c b/src/repository.c
index 6ce3a560f..0ad7449ba 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,6 +8,7 @@
#include <ctype.h>
#include "git2/object.h"
+#include "git2/refdb.h"
#include "common.h"
#include "repository.h"
@@ -17,9 +18,10 @@
#include "fileops.h"
#include "config.h"
#include "refs.h"
-
-#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"
-#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"
+#include "filter.h"
+#include "odb.h"
+#include "remote.h"
+#include "merge.h"
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
@@ -27,6 +29,8 @@
#define GIT_REPO_VERSION 0
+#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
+
static void drop_odb(git_repository *repo)
{
if (repo->_odb != NULL) {
@@ -36,6 +40,15 @@ static void drop_odb(git_repository *repo)
}
}
+static void drop_refdb(git_repository *repo)
+{
+ if (repo->_refdb != NULL) {
+ GIT_REFCOUNT_OWN(repo->_refdb, NULL);
+ git_refdb_free(repo->_refdb);
+ repo->_refdb = NULL;
+ }
+}
+
static void drop_config(git_repository *repo)
{
if (repo->_config != NULL) {
@@ -62,7 +75,6 @@ void git_repository_free(git_repository *repo)
return;
git_cache_free(&repo->objects);
- git_repository__refcache_free(&repo->references);
git_attr_cache_flush(repo);
git_submodule_config_free(repo);
@@ -72,6 +84,7 @@ void git_repository_free(git_repository *repo)
drop_config(repo);
drop_index(repo);
drop_odb(repo);
+ drop_refdb(repo);
git__free(repo);
}
@@ -124,11 +137,12 @@ static int load_config_data(git_repository *repo)
if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
+ /* Try to figure out if it's bare, default to non-bare if it's not set */
if (git_config_get_bool(&is_bare, config, "core.bare") < 0)
- return -1; /* FIXME: We assume that a missing core.bare
- variable is an error. Is this right? */
+ repo->is_bare = 0;
+ else
+ repo->is_bare = is_bare;
- repo->is_bare = is_bare;
return 0;
}
@@ -146,8 +160,13 @@ static int load_workdir(git_repository *repo, git_buf *parent_path)
return -1;
error = git_config_get_string(&worktree, config, "core.worktree");
- if (!error && worktree != NULL)
- repo->workdir = git__strdup(worktree);
+ if (!error && worktree != NULL) {
+ error = git_path_prettify_dir(
+ &worktree_buf, worktree, repo->path_repository);
+ if (error < 0)
+ return error;
+ repo->workdir = git_buf_detach(&worktree_buf);
+ }
else if (error != GIT_ENOTFOUND)
return error;
else {
@@ -237,16 +256,17 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
git_buf_rtrim(&file);
- if (file.size <= prefix_len ||
- memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)
+ if (git_buf_len(&file) <= prefix_len ||
+ memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)
{
giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path);
error = -1;
}
else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) {
- const char *gitlink = ((const char *)file.ptr) + prefix_len;
+ const char *gitlink = git_buf_cstr(&file) + prefix_len;
while (*gitlink && git__isspace(*gitlink)) gitlink++;
- error = git_path_prettify_dir(path_out, gitlink, path_out->ptr);
+ error = git_path_prettify_dir(
+ path_out, gitlink, git_buf_cstr(path_out));
}
git_buf_free(&file);
@@ -351,16 +371,18 @@ static int find_repo(
int git_repository_open_ext(
git_repository **repo_ptr,
const char *start_path,
- uint32_t flags,
+ unsigned int flags,
const char *ceiling_dirs)
{
int error;
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT;
git_repository *repo;
- *repo_ptr = NULL;
+ if (repo_ptr)
+ *repo_ptr = NULL;
- if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0)
+ error = find_repo(&path, &parent, start_path, flags, ceiling_dirs);
+ if (error < 0 || !repo_ptr)
return error;
repo = repository_alloc();
@@ -387,6 +409,19 @@ int git_repository_open(git_repository **repo_out, const char *path)
repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);
}
+int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb)
+{
+ git_repository *repo;
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ git_repository_set_odb(repo, odb);
+ *repo_out = repo;
+
+ return 0;
+}
+
int git_repository_discover(
char *repository_path,
size_t size,
@@ -407,7 +442,7 @@ int git_repository_discover(
if (size < (size_t)(path.size + 1)) {
giterr_set(GITERR_REPOSITORY,
- "The given buffer is too long to store the discovered path");
+ "The given buffer is too small to store the discovered path");
git_buf_free(&path);
return -1;
}
@@ -422,34 +457,49 @@ static int load_config(
git_config **out,
git_repository *repo,
const char *global_config_path,
+ const char *xdg_config_path,
const char *system_config_path)
{
+ int error;
git_buf config_path = GIT_BUF_INIT;
git_config *cfg = NULL;
assert(repo && out);
- if (git_config_new(&cfg) < 0)
- return -1;
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
- if (git_buf_joinpath(
- &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0)
+ error = git_buf_joinpath(
+ &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
+ if (error < 0)
goto on_error;
- if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0)
+ if ((error = git_config_add_file_ondisk(
+ cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
goto on_error;
git_buf_free(&config_path);
- if (global_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0)
- goto on_error;
- }
+ if (global_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
- if (system_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0)
- goto on_error;
- }
+ if (xdg_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (system_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear(); /* clear any lingering ENOTFOUND errors */
*out = cfg;
return 0;
@@ -458,27 +508,32 @@ on_error:
git_buf_free(&config_path);
git_config_free(cfg);
*out = NULL;
- return -1;
+ return error;
}
int git_repository_config__weakptr(git_config **out, git_repository *repo)
{
if (repo->_config == NULL) {
- git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT;
+ 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;
+ if (git_config_find_xdg_r(&xdg_buf) == 0)
+ xdg_config_path = xdg_buf.ptr;
+
if (git_config_find_system_r(&system_buf) == 0)
system_config_path = system_buf.ptr;
- res = load_config(&repo->_config, repo, global_config_path, system_config_path);
+ res = load_config(&repo->_config, repo, global_config_path, xdg_config_path, system_config_path);
git_buf_free(&global_buf);
+ git_buf_free(&xdg_buf);
git_buf_free(&system_buf);
if (res < 0)
@@ -508,6 +563,7 @@ void git_repository_set_config(git_repository *repo, git_config *config)
repo->_config = config;
GIT_REFCOUNT_OWN(repo->_config, repo);
+ GIT_REFCOUNT_INC(repo->_config);
}
int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
@@ -554,6 +610,45 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb)
GIT_REFCOUNT_INC(odb);
}
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
+{
+ assert(out && repo);
+
+ if (repo->_refdb == NULL) {
+ int res;
+
+ res = git_refdb_open(&repo->_refdb, repo);
+
+ if (res < 0)
+ return -1;
+
+ GIT_REFCOUNT_OWN(repo->_refdb, repo);
+ }
+
+ *out = repo->_refdb;
+ return 0;
+}
+
+int git_repository_refdb(git_refdb **out, git_repository *repo)
+{
+ if (git_repository_refdb__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+void git_repository_set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ assert (repo && refdb);
+
+ drop_refdb(repo);
+
+ repo->_refdb = refdb;
+ GIT_REFCOUNT_OWN(repo->_refdb, repo);
+ GIT_REFCOUNT_INC(refdb);
+}
+
int git_repository_index__weakptr(git_index **out, git_repository *repo)
{
assert(out && repo);
@@ -572,6 +667,9 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo)
return -1;
GIT_REFCOUNT_OWN(repo->_index, repo);
+
+ if (git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER) < 0)
+ return -1;
}
*out = repo->_index;
@@ -598,14 +696,10 @@ void git_repository_set_index(git_repository *repo, git_index *index)
GIT_REFCOUNT_INC(index);
}
-static int check_repositoryformatversion(git_repository *repo)
+static int check_repositoryformatversion(git_config *config)
{
- git_config *config;
int version;
- if (git_repository_config__weakptr(&config, repo) < 0)
- return -1;
-
if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0)
return -1;
@@ -619,105 +713,215 @@ static int check_repositoryformatversion(git_repository *repo)
return 0;
}
-static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare)
+static int repo_init_create_head(const char *git_dir, const char *ref_name)
{
- git_repository *repo = NULL;
+ git_buf ref_path = GIT_BUF_INIT;
+ git_filebuf ref = GIT_FILEBUF_INIT;
+ const char *fmt;
- GIT_UNUSED(is_bare);
+ if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||
+ git_filebuf_open(&ref, ref_path.ptr, 0) < 0)
+ goto fail;
- if (git_repository_open(&repo, repository_path) < 0)
- return -1;
+ if (!ref_name)
+ ref_name = GIT_BRANCH_MASTER;
- if (check_repositoryformatversion(repo) < 0) {
- git_repository_free(repo);
- return -1;
- }
+ if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)
+ fmt = "ref: %s\n";
+ else
+ fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";
- /* TODO: reinitialize the templates */
+ if (git_filebuf_printf(&ref, fmt, ref_name) < 0 ||
+ git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0)
+ goto fail;
- *repo_out = repo;
+ git_buf_free(&ref_path);
return 0;
+
+fail:
+ git_buf_free(&ref_path);
+ git_filebuf_cleanup(&ref);
+ return -1;
}
-static int repo_init_createhead(const char *git_dir)
+static bool is_chmod_supported(const char *file_path)
{
- git_buf ref_path = GIT_BUF_INIT;
- git_filebuf ref = GIT_FILEBUF_INIT;
+ struct stat st1, st2;
+ static int _is_supported = -1;
- if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||
- git_filebuf_open(&ref, ref_path.ptr, 0) < 0 ||
- git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 ||
- git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0)
+ if (_is_supported > -1)
+ return _is_supported;
+
+ if (p_stat(file_path, &st1) < 0)
+ return false;
+
+ if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0)
+ return false;
+
+ if (p_stat(file_path, &st2) < 0)
+ return false;
+
+ _is_supported = (st1.st_mode != st2.st_mode);
+
+ return _is_supported;
+}
+
+static bool is_filesystem_case_insensitive(const char *gitdir_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ static int _is_insensitive = -1;
+
+ if (_is_insensitive > -1)
+ return _is_insensitive;
+
+ if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0)
+ goto cleanup;
+
+ _is_insensitive = git_path_exists(git_buf_cstr(&path));
+
+cleanup:
+ git_buf_free(&path);
+ return _is_insensitive;
+}
+
+static bool are_symlinks_supported(const char *wd_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ struct stat st;
+ static int _symlinks_supported = -1;
+
+ if (_symlinks_supported > -1)
+ return _symlinks_supported;
+
+ if ((fd = git_futils_mktmp(&path, wd_path)) < 0 ||
+ p_close(fd) < 0 ||
+ p_unlink(path.ptr) < 0 ||
+ p_symlink("testing", path.ptr) < 0 ||
+ p_lstat(path.ptr, &st) < 0)
+ _symlinks_supported = false;
+ else
+ _symlinks_supported = (S_ISLNK(st.st_mode) != 0);
+
+ (void)p_unlink(path.ptr);
+ git_buf_free(&path);
+
+ return _symlinks_supported;
+}
+
+static int create_empty_file(const char *path, mode_t mode)
+{
+ int fd;
+
+ if ((fd = p_creat(path, mode)) < 0) {
+ giterr_set(GITERR_OS, "Error while creating '%s'", path);
return -1;
+ }
+
+ if (p_close(fd) < 0) {
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ return -1;
+ }
- git_buf_free(&ref_path);
return 0;
}
-static int repo_init_config(const char *git_dir, int is_bare)
+static int repo_init_config(
+ const char *repo_dir,
+ const char *work_dir,
+ git_repository_init_options *opts)
{
+ int error = 0;
git_buf cfg_path = GIT_BUF_INIT;
git_config *config = NULL;
-#define SET_REPO_CONFIG(type, name, val) {\
- if (git_config_set_##type(config, name, val) < 0) { \
- git_buf_free(&cfg_path); \
- git_config_free(config); \
- return -1; } \
-}
+#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\
+ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
+ goto cleanup; } while (0)
- if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
+ if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
return -1;
- if (git_config_open_ondisk(&config, cfg_path.ptr) < 0) {
+ if (!git_path_isfile(git_buf_cstr(&cfg_path)) &&
+ create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) {
+ git_buf_free(&cfg_path);
+ return -1;
+ }
+
+ if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) {
git_buf_free(&cfg_path);
return -1;
}
- SET_REPO_CONFIG(bool, "core.bare", is_bare);
- SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);
- /* TODO: what other defaults? */
+ if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 &&
+ (error = check_repositoryformatversion(config)) < 0)
+ goto cleanup;
- git_buf_free(&cfg_path);
- git_config_free(config);
- return 0;
-}
+ SET_REPO_CONFIG(
+ bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
+ SET_REPO_CONFIG(
+ int32, "core.repositoryformatversion", GIT_REPO_VERSION);
+ SET_REPO_CONFIG(
+ bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));
-#define GIT_HOOKS_DIR "hooks/"
-#define GIT_HOOKS_DIR_MODE 0755
+ if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) {
+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
-#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"
-#define GIT_HOOKS_README_MODE 0755
-#define GIT_HOOKS_README_CONTENT \
-"#!/bin/sh\n"\
-"#\n"\
-"# Place appropriately named executable hook scripts into this directory\n"\
-"# to intercept various actions that git takes. See `git help hooks` for\n"\
-"# more information.\n"
+ if (!are_symlinks_supported(work_dir))
+ SET_REPO_CONFIG(bool, "core.symlinks", false);
-#define GIT_INFO_DIR "info/"
-#define GIT_INFO_DIR_MODE 0755
+ if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
+ SET_REPO_CONFIG(string, "core.worktree", work_dir);
+ }
+ else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) {
+ if (git_config_delete_entry(config, "core.worktree") < 0)
+ giterr_clear();
+ }
+ } else {
+ if (!are_symlinks_supported(repo_dir))
+ SET_REPO_CONFIG(bool, "core.symlinks", false);
+ }
-#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"
-#define GIT_INFO_EXCLUDE_MODE 0644
-#define GIT_INFO_EXCLUDE_CONTENT \
-"# File patterns to ignore; see `git help ignore` for more information.\n"\
-"# Lines that start with '#' are comments.\n"
+ if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) &&
+ is_filesystem_case_insensitive(repo_dir))
+ SET_REPO_CONFIG(bool, "core.ignorecase", true);
-#define GIT_DESC_FILE "description"
-#define GIT_DESC_MODE 0644
-#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n"
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 1);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
+ }
+ else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 2);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
+ }
+
+cleanup:
+ git_buf_free(&cfg_path);
+ git_config_free(config);
+
+ return error;
+}
static int repo_write_template(
- const char *git_dir, const char *file, mode_t mode, const char *content)
+ const char *git_dir,
+ bool allow_overwrite,
+ const char *file,
+ mode_t mode,
+ bool hidden,
+ const char *content)
{
git_buf path = GIT_BUF_INIT;
- int fd, error = 0;
+ int fd, error = 0, flags;
if (git_buf_joinpath(&path, git_dir, file) < 0)
return -1;
- fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (allow_overwrite)
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ else
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+
+ fd = p_open(git_buf_cstr(&path), flags, mode);
if (fd >= 0) {
error = p_write(fd, content, strlen(content));
@@ -727,6 +931,15 @@ static int repo_write_template(
else if (errno != EEXIST)
error = fd;
+#ifdef GIT_WIN32
+ if (!error && hidden) {
+ if (p_hide_directory__w32(path.ptr) < 0)
+ error = -1;
+ }
+#else
+ GIT_UNUSED(hidden);
+#endif
+
git_buf_free(&path);
if (error)
@@ -736,83 +949,369 @@ static int repo_write_template(
return error;
}
-static int repo_init_structure(const char *git_dir, int is_bare)
-{
- int i;
- struct { const char *dir; mode_t mode; } dirs[] = {
- { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */
- { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */
- { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */
- { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */
- { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */
- { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */
- { NULL, 0 }
- };
- struct { const char *file; mode_t mode; const char *content; } tmpl[] = {
- { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },
- { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },
- { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },
- { NULL, 0, NULL }
- };
-
- /* Make the base directory */
- if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0)
+static int repo_write_gitlink(
+ const char *in_dir, const char *to_repo)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ struct stat st;
+
+ git_path_dirname_r(&buf, to_repo);
+ git_path_to_dir(&buf);
+ if (git_buf_oom(&buf))
return -1;
- /* Hides the ".git" directory */
- if (!is_bare) {
+ /* don't write gitlink to natural workdir */
+ if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 &&
+ strcmp(in_dir, buf.ptr) == 0)
+ {
+ error = GIT_PASSTHROUGH;
+ goto cleanup;
+ }
+
+ if ((error = git_buf_joinpath(&buf, in_dir, DOT_GIT)) < 0)
+ goto cleanup;
+
+ if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) {
+ giterr_set(GITERR_REPOSITORY,
+ "Cannot overwrite gitlink file into path '%s'", in_dir);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
+
+ git_buf_clear(&buf);
+
+ error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo);
+
+ if (!error)
+ error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr);
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+static mode_t pick_dir_mode(git_repository_init_options *opts)
+{
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
+ return 0777;
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
+ return (0775 | S_ISGID);
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL)
+ return (0777 | S_ISGID);
+ return opts->mode;
+}
+
+#include "repo_template.h"
+
+static int repo_init_structure(
+ const char *repo_dir,
+ const char *work_dir,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ repo_template_item *tpl;
+ bool external_tpl =
+ ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0);
+ mode_t dmode = pick_dir_mode(opts);
+
+ /* Hide the ".git" directory */
#ifdef GIT_WIN32
- if (p_hide_directory__w32(git_dir) < 0) {
+ if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {
+ if (p_hide_directory__w32(repo_dir) < 0) {
giterr_set(GITERR_REPOSITORY,
"Failed to mark Git repository folder as hidden");
return -1;
}
-#endif
}
+#endif
- /* Make subdirectories as needed */
- for (i = 0; dirs[i].dir != NULL; ++i) {
- if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0)
+ /* Create the .git gitlink if appropriate */
+ if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 &&
+ (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0)
+ {
+ if (repo_write_gitlink(work_dir, repo_dir) < 0)
return -1;
}
- /* Make template files as needed */
- for (i = 0; tmpl[i].file != NULL; ++i) {
- if (repo_write_template(
- git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0)
- return -1;
+ /* Copy external template if requested */
+ if (external_tpl) {
+ git_config *cfg;
+ const char *tdir;
+
+ if (opts->template_path)
+ tdir = opts->template_path;
+ else if ((error = git_config_open_default(&cfg)) < 0)
+ return error;
+ else {
+ error = git_config_get_string(&tdir, cfg, "init.templatedir");
+
+ git_config_free(cfg);
+
+ if (error && error != GIT_ENOTFOUND)
+ return error;
+
+ giterr_clear();
+ tdir = GIT_TEMPLATE_DIR;
+ }
+
+ error = git_futils_cp_r(tdir, repo_dir,
+ GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS |
+ GIT_CPDIR_SIMPLE_TO_MODE, dmode);
+
+ if (error < 0) {
+ if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0)
+ return error;
+
+ /* if template was default, ignore error and use internal */
+ giterr_clear();
+ external_tpl = false;
+ error = 0;
+ }
}
- return 0;
+ /* Copy internal template
+ * - always ensure existence of dirs
+ * - only create files if no external template was specified
+ */
+ for (tpl = repo_template; !error && tpl->path; ++tpl) {
+ if (!tpl->content)
+ error = git_futils_mkdir(
+ tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD);
+ else if (!external_tpl) {
+ const char *content = tpl->content;
+
+ if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0)
+ content = opts->description;
+
+ error = repo_write_template(
+ repo_dir, false, tpl->path, tpl->mode, false, content);
+ }
+ }
+
+ return error;
}
-int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare)
+static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2)
{
- git_buf repository_path = GIT_BUF_INIT;
-
- assert(repo_out && path);
+ /* When making parent directories during repository initialization
+ * don't try to set gid or grant world write access
+ */
+ return git_futils_mkdir(
+ buf->ptr, NULL, mode & ~(S_ISGID | 0002),
+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR |
+ (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST));
+}
- if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0)
+static int repo_init_directories(
+ git_buf *repo_path,
+ git_buf *wd_path,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ bool is_bare, add_dotgit, has_dotgit, natural_wd;
+ mode_t dirmode;
+
+ /* There are three possible rules for what we are allowed to create:
+ * - MKPATH means anything we need
+ * - MKDIR means just the .git directory and its parent and the workdir
+ * - Neither means only the .git directory can be created
+ *
+ * There are 5 "segments" of path that we might need to deal with:
+ * 1. The .git directory
+ * 2. The parent of the .git directory
+ * 3. Everything above the parent of the .git directory
+ * 4. The working directory (often the same as #2)
+ * 5. Everything above the working directory (often the same as #3)
+ *
+ * For all directories created, we start with the init_mode value for
+ * permissions and then strip off bits in some cases:
+ *
+ * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH
+ * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID
+ * For all rules, we create #1 using the untouched init_mode
+ */
+
+ /* set up repo path */
+
+ is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
+
+ add_dotgit =
+ (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 &&
+ !is_bare &&
+ git__suffixcmp(given_repo, "/" DOT_GIT) != 0 &&
+ git__suffixcmp(given_repo, "/" GIT_DIR) != 0;
+
+ if (git_buf_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0)
return -1;
- if (git_path_isdir(repository_path.ptr) == true) {
- if (valid_repository_path(&repository_path) == true) {
- int res = repo_init_reinit(repo_out, repository_path.ptr, is_bare);
- git_buf_free(&repository_path);
- return res;
+ has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0);
+ if (has_dotgit)
+ opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT;
+
+ /* set up workdir path */
+
+ if (!is_bare) {
+ if (opts->workdir_path) {
+ if (git_path_join_unrooted(
+ wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0)
+ return -1;
+ } else if (has_dotgit) {
+ if (git_path_dirname_r(wd_path, repo_path->ptr) < 0)
+ return -1;
+ } else {
+ giterr_set(GITERR_REPOSITORY, "Cannot pick working directory"
+ " for non-bare repository that isn't a '.git' directory");
+ return -1;
}
+
+ if (git_path_to_dir(wd_path) < 0)
+ return -1;
+ } else {
+ git_buf_clear(wd_path);
}
- if (repo_init_structure(repository_path.ptr, is_bare) < 0 ||
- repo_init_config(repository_path.ptr, is_bare) < 0 ||
- repo_init_createhead(repository_path.ptr) < 0 ||
- git_repository_open(repo_out, repository_path.ptr) < 0) {
- git_buf_free(&repository_path);
- return -1;
+ natural_wd =
+ has_dotgit &&
+ wd_path->size > 0 &&
+ wd_path->size + strlen(GIT_DIR) == repo_path->size &&
+ memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0;
+ if (natural_wd)
+ opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD;
+
+ /* create directories as needed / requested */
+
+ dirmode = pick_dir_mode(opts);
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) {
+ /* create path #5 */
+ if (wd_path->size > 0 &&
+ (error = mkdir_parent(wd_path, dirmode, false)) < 0)
+ return error;
+
+ /* create path #3 (if not the same as #5) */
+ if (!natural_wd &&
+ (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0)
+ return error;
}
- git_buf_free(&repository_path);
- return 0;
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)
+ {
+ /* create path #4 */
+ if (wd_path->size > 0 &&
+ (error = git_futils_mkdir(
+ wd_path->ptr, NULL, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR)) < 0)
+ return error;
+
+ /* create path #2 (if not the same as #4) */
+ if (!natural_wd &&
+ (error = git_futils_mkdir(
+ repo_path->ptr, NULL, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0)
+ return error;
+ }
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 ||
+ has_dotgit)
+ {
+ /* create path #1 */
+ error = git_futils_mkdir(repo_path->ptr, NULL, dirmode,
+ GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0));
+ }
+
+ /* prettify both directories now that they are created */
+
+ if (!error) {
+ error = git_path_prettify_dir(repo_path, repo_path->ptr, NULL);
+
+ if (!error && wd_path->size > 0)
+ error = git_path_prettify_dir(wd_path, wd_path->ptr, NULL);
+ }
+
+ return error;
+}
+
+static int repo_init_create_origin(git_repository *repo, const char *url)
+{
+ int error;
+ git_remote *remote;
+
+ if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) {
+ git_remote_free(remote);
+ }
+
+ return error;
+}
+
+int git_repository_init(
+ git_repository **repo_out, const char *path, unsigned is_bare)
+{
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */
+ if (is_bare)
+ opts.flags |= GIT_REPOSITORY_INIT_BARE;
+
+ return git_repository_init_ext(repo_out, path, &opts);
+}
+
+int git_repository_init_ext(
+ git_repository **out,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error;
+ git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;
+
+ assert(out && given_repo && opts);
+
+ GITERR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options");
+
+ error = repo_init_directories(&repo_path, &wd_path, given_repo, opts);
+ if (error < 0)
+ goto cleanup;
+
+ if (valid_repository_path(&repo_path)) {
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {
+ giterr_set(GITERR_REPOSITORY,
+ "Attempt to reinitialize '%s'", given_repo);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
+
+ opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
+
+ error = repo_init_config(
+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts);
+
+ /* TODO: reinitialize the templates */
+ }
+ else {
+ if (!(error = repo_init_structure(
+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) &&
+ !(error = repo_init_config(
+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)))
+ error = repo_init_create_head(
+ git_buf_cstr(&repo_path), opts->initial_head);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ error = git_repository_open(out, git_buf_cstr(&repo_path));
+
+ if (!error && opts->origin_url)
+ error = repo_init_create_origin(*out, opts->origin_url);
+
+cleanup:
+ git_buf_free(&repo_path);
+ git_buf_free(&wd_path);
+
+ return error;
}
int git_repository_head_detached(git_repository *repo)
@@ -832,7 +1331,7 @@ int git_repository_head_detached(git_repository *repo)
return 0;
}
- exists = git_odb_exists(odb, git_reference_oid(ref));
+ exists = git_odb_exists(odb, git_reference_target(ref));
git_reference_free(ref);
return exists;
@@ -840,7 +1339,21 @@ int git_repository_head_detached(git_repository *repo)
int git_repository_head(git_reference **head_out, git_repository *repo)
{
- return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1);
+ git_reference *head;
+ int error;
+
+ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if (git_reference_type(head) == GIT_REF_OID) {
+ *head_out = head;
+ return 0;
+ }
+
+ error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1);
+ git_reference_free(head);
+
+ return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error;
}
int git_repository_head_orphan(git_repository *repo)
@@ -851,7 +1364,7 @@ int git_repository_head_orphan(git_repository *repo)
error = git_repository_head(&ref, repo);
git_reference_free(ref);
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_EORPHANEDHEAD)
return 1;
if (error < 0)
@@ -860,36 +1373,47 @@ int git_repository_head_orphan(git_repository *repo)
return 0;
}
-int git_repository_is_empty(git_repository *repo)
+static int at_least_one_cb(const char *refname, void *payload)
{
- git_reference *head = NULL, *branch = NULL;
- int error;
+ GIT_UNUSED(refname);
+ GIT_UNUSED(payload);
- if (git_reference_lookup(&head, repo, "HEAD") < 0)
- return -1;
+ return GIT_EUSER;
+}
- if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
- git_reference_free(head);
- return 0;
- }
+static int repo_contains_no_reference(git_repository *repo)
+{
+ int error;
+
+ error = git_reference_foreach(repo, GIT_REF_LISTALL, at_least_one_cb, NULL);
- if (strcmp(git_reference_target(head), "refs/heads/master") != 0) {
- git_reference_free(head);
+ if (error == GIT_EUSER)
return 0;
- }
- error = git_reference_resolve(&branch, head);
-
- git_reference_free(head);
- git_reference_free(branch);
+ return error == 0 ? 1 : error;
+}
- if (error == GIT_ENOTFOUND)
- return 1;
+int git_repository_is_empty(git_repository *repo)
+{
+ git_reference *head = NULL;
+ int error;
- if (error < 0)
+ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
return -1;
- return 0;
+ if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC))
+ goto cleanup;
+
+ if (!(error = strcmp(
+ git_reference_symbolic_target(head),
+ GIT_REFS_HEADS_DIR "master") == 0))
+ goto cleanup;
+
+ error = repo_contains_no_reference(repo);
+
+cleanup:
+ git_reference_free(head);
+ return error < 0 ? -1 : error;
}
const char *git_repository_path(git_repository *repo)
@@ -908,8 +1432,10 @@ const char *git_repository_workdir(git_repository *repo)
return repo->workdir;
}
-int git_repository_set_workdir(git_repository *repo, const char *workdir)
+int git_repository_set_workdir(
+ git_repository *repo, const char *workdir, int update_gitlink)
{
+ int error = 0;
git_buf path = GIT_BUF_INIT;
assert(repo && workdir);
@@ -917,11 +1443,37 @@ int git_repository_set_workdir(git_repository *repo, const char *workdir)
if (git_path_prettify_dir(&path, workdir, NULL) < 0)
return -1;
- git__free(repo->workdir);
+ if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0)
+ return 0;
- repo->workdir = git_buf_detach(&path);
- repo->is_bare = 0;
- return 0;
+ if (update_gitlink) {
+ git_config *config;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ return -1;
+
+ error = repo_write_gitlink(path.ptr, git_repository_path(repo));
+
+ /* passthrough error means gitlink is unnecessary */
+ if (error == GIT_PASSTHROUGH)
+ error = git_config_delete_entry(config, "core.worktree");
+ else if (!error)
+ error = git_config_set_string(config, "core.worktree", path.ptr);
+
+ if (!error)
+ error = git_config_set_bool(config, "core.bare", false);
+ }
+
+ if (!error) {
+ char *old_workdir = repo->workdir;
+
+ repo->workdir = git_buf_detach(&path);
+ repo->is_bare = 0;
+
+ git__free(old_workdir);
+ }
+
+ return error;
}
int git_repository_is_bare(git_repository *repo)
@@ -932,20 +1484,248 @@ int git_repository_is_bare(git_repository *repo)
int git_repository_head_tree(git_tree **tree, git_repository *repo)
{
- git_oid head_oid;
- git_object *obj = NULL;
+ git_reference *head;
+ git_object *obj;
+ int error;
- if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) {
- /* cannot resolve HEAD - probably brand new repo */
- giterr_clear();
- *tree = NULL;
- return 0;
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ if ((error = git_reference_peel(&obj, head, GIT_OBJ_TREE)) < 0)
+ goto cleanup;
+
+ *tree = (git_tree *)obj;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+int git_repository_message(char *buffer, size_t len, git_repository *repo)
+{
+ git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
+ if (errno == ENOENT)
+ error = GIT_ENOTFOUND;
+ }
+ else if (buffer != NULL) {
+ error = git_futils_readbuffer(&buf, git_buf_cstr(&path));
+ git_buf_copy_cstr(buffer, len, &buf);
}
- if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0 ||
- git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0)
+ git_buf_free(&path);
+ git_buf_free(&buf);
+
+ if (!error)
+ error = (int)st.st_size + 1; /* add 1 for NUL byte */
+
+ return error;
+}
+
+int git_repository_message_remove(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
- *tree = (git_tree *)obj;
- return 0;
+ error = p_unlink(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_repository_hashfile(
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path)
+{
+ int error;
+ git_vector filters = GIT_VECTOR_INIT;
+ git_file fd = -1;
+ git_off_t len;
+ git_buf full_path = GIT_BUF_INIT;
+
+ assert(out && path && repo); /* as_path can be NULL */
+
+ /* At some point, it would be nice if repo could be NULL to just
+ * apply filter rules defined in system and global files, but for
+ * now that is not possible because git_filters_load() needs it.
+ */
+
+ error = git_path_join_unrooted(
+ &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL);
+ if (error < 0)
+ return error;
+
+ if (!as_path)
+ as_path = path;
+
+ /* passing empty string for "as_path" indicated --no-filters */
+ if (strlen(as_path) > 0) {
+ error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB);
+ if (error < 0)
+ return error;
+ } else {
+ error = 0;
+ }
+
+ /* at this point, error is a count of the number of loaded filters */
+
+ fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0) {
+ error = fd;
+ goto cleanup;
+ }
+
+ len = git_futils_filesize(fd);
+ if (len < 0) {
+ error = (int)len;
+ goto cleanup;
+ }
+
+ if (!git__is_sizet(len)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters);
+
+cleanup:
+ if (fd >= 0)
+ p_close(fd);
+ git_filters_free(&filters);
+ git_buf_free(&full_path);
+
+ return error;
+}
+
+static bool looks_like_a_branch(const char *refname)
+{
+ return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_repository_set_head(
+ git_repository* repo,
+ const char* refname)
+{
+ git_reference *ref,
+ *new_head = NULL;
+ int error;
+
+ assert(repo && refname);
+
+ error = git_reference_lookup(&ref, repo, refname);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ if (!error) {
+ if (git_reference_is_branch(ref))
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1);
+ else
+ error = git_repository_set_head_detached(repo, git_reference_target(ref));
+ } else if (looks_like_a_branch(refname))
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, 1);
+
+ git_reference_free(ref);
+ git_reference_free(new_head);
+ return error;
+}
+
+int git_repository_set_head_detached(
+ git_repository* repo,
+ const git_oid* commitish)
+{
+ int error;
+ git_object *object,
+ *peeled = NULL;
+ git_reference *new_head = NULL;
+
+ assert(repo && commitish);
+
+ if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), 1);
+
+cleanup:
+ git_object_free(object);
+ git_object_free(peeled);
+ git_reference_free(new_head);
+ return error;
+}
+
+int git_repository_detach_head(
+ git_repository* repo)
+{
+ git_reference *old_head = NULL,
+ *new_head = NULL;
+ git_object *object = NULL;
+ int error;
+
+ assert(repo);
+
+ if ((error = git_repository_head(&old_head, repo)) < 0)
+ return error;
+
+ if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), 1);
+
+cleanup:
+ git_object_free(object);
+ git_reference_free(old_head);
+ git_reference_free(new_head);
+ return error;
+}
+
+/**
+ * Loosely ported from git.git
+ * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289
+ */
+int git_repository_state(git_repository *repo)
+{
+ git_buf repo_path = GIT_BUF_INIT;
+ int state = GIT_REPOSITORY_STATE_NONE;
+
+ assert(repo);
+
+ if (git_buf_puts(&repo_path, repo->path_repository) < 0)
+ return -1;
+
+ if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR))
+ state = GIT_REPOSITORY_STATE_REBASE_MERGE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_MERGE;
+ else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_REVERT;
+ else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_CHERRY_PICK;
+ else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))
+ state = GIT_REPOSITORY_STATE_BISECT;
+
+ git_buf_free(&repo_path);
+ return state;
}
diff --git a/src/repository.h b/src/repository.h
index 91c69a655..cc2f8c2b8 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -18,8 +18,10 @@
#include "refs.h"
#include "buffer.h"
#include "odb.h"
-#include "attr.h"
+#include "object.h"
+#include "attrcache.h"
#include "strmap.h"
+#include "refdb.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
@@ -68,20 +70,21 @@ typedef enum {
GIT_EOL_DEFAULT = GIT_EOL_NATIVE
} git_cvar_value;
-/** Base git object for inheritance */
-struct git_object {
- git_cached_obj cached;
- git_repository *repo;
- git_otype type;
+/* internal repository init flags */
+enum {
+ GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16),
+ GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17),
+ GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18),
};
+/** Internal structure for repository object */
struct git_repository {
git_odb *_odb;
+ git_refdb *_refdb;
git_config *_config;
git_index *_index;
git_cache objects;
- git_refcache references;
git_attr_cache attrcache;
git_strmap *submodules;
@@ -94,15 +97,6 @@ struct git_repository {
git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
};
-/* fully free the object; internal method, do not
- * export */
-void git_object__free(void *object);
-
-int git_object__resolve_to_type(git_object **obj, git_otype type);
-
-int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
-void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
-
GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
{
return &repo->attrcache;
@@ -119,10 +113,11 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo);
*/
int git_repository_config__weakptr(git_config **out, git_repository *repo);
int git_repository_odb__weakptr(git_odb **out, git_repository *repo);
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo);
int git_repository_index__weakptr(git_index **out, git_repository *repo);
/*
- * CVAR cache
+ * CVAR cache
*
* Efficient access to the most used config variables of a repository.
* The cache is cleared everytime the config backend is replaced.
@@ -135,4 +130,19 @@ void git_repository__cvar_cache_clear(git_repository *repo);
*/
extern void git_submodule_config_free(git_repository *repo);
+GIT_INLINE(int) git_repository__ensure_not_bare(
+ git_repository *repo,
+ const char *operation_name)
+{
+ if (!git_repository_is_bare(repo))
+ return 0;
+
+ giterr_set(
+ GITERR_REPOSITORY,
+ "Cannot %s. This operation is not allowed against bare repositories.",
+ operation_name);
+
+ return GIT_EBAREREPO;
+}
+
#endif
diff --git a/src/reset.c b/src/reset.c
new file mode 100644
index 000000000..c1e1f865e
--- /dev/null
+++ b/src/reset.c
@@ -0,0 +1,163 @@
+/*
+ * 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 "commit.h"
+#include "tag.h"
+#include "merge.h"
+#include "diff.h"
+#include "git2/reset.h"
+#include "git2/checkout.h"
+#include "git2/merge.h"
+#include "git2/refs.h"
+
+#define ERROR_MSG "Cannot perform reset"
+
+int git_reset_default(
+ git_repository *repo,
+ git_object *target,
+ git_strarray* pathspecs)
+{
+ git_object *commit = NULL;
+ git_tree *tree = NULL;
+ git_diff_list *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ size_t i;
+ git_diff_delta *delta;
+ git_index_entry entry;
+ int error;
+ git_index *index = NULL;
+
+ assert(pathspecs != NULL && pathspecs->count > 0);
+
+ memset(&entry, 0, sizeof(git_index_entry));
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if (target) {
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s_default - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+ }
+
+ opts.pathspec = *pathspecs;
+ opts.flags = GIT_DIFF_REVERSE;
+
+ if ((error = git_diff_tree_to_index(
+ &diff, repo, tree, index, &opts)) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((error = git_index_conflict_remove(index, delta->old_file.path)) < 0)
+ goto cleanup;
+
+ assert(delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_MODIFIED ||
+ delta->status == GIT_DELTA_DELETED);
+
+ if (delta->status == GIT_DELTA_DELETED) {
+ if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
+ goto cleanup;
+ } else {
+ entry.mode = delta->new_file.mode;
+ git_oid_cpy(&entry.oid, &delta->new_file.oid);
+ entry.path = (char *)delta->new_file.path;
+
+ if ((error = git_index_add(index, &entry)) < 0)
+ goto cleanup;
+ }
+ }
+
+ error = git_index_write(index);
+
+cleanup:
+ git_object_free(commit);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_diff_list_free(diff);
+
+ return error;
+}
+
+int git_reset(
+ git_repository *repo,
+ git_object *target,
+ git_reset_t reset_type)
+{
+ git_object *commit = NULL;
+ git_index *index = NULL;
+ git_tree *tree = NULL;
+ int error = 0;
+ git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
+
+ assert(repo && target);
+
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if (reset_type != GIT_RESET_SOFT &&
+ (error = git_repository__ensure_not_bare(repo,
+ reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)
+ return error;
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_SOFT &&
+ (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||
+ git_index_has_conflicts(index)))
+ {
+ giterr_set(GITERR_OBJECT, "%s (soft) in the middle of a merge.", ERROR_MSG);
+ error = GIT_EUNMERGED;
+ goto cleanup;
+ }
+
+ /* move HEAD to the new target */
+ if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,
+ git_object_id(commit))) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_HARD) {
+ /* overwrite working directory with HEAD */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
+ goto cleanup;
+ }
+
+ if (reset_type > GIT_RESET_SOFT) {
+ /* reset index to the target content */
+
+ if ((error = git_index_read_tree(index, tree)) < 0 ||
+ (error = git_index_write(index)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_merge_cleanup(repo)) < 0) {
+ giterr_set(GITERR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ git_object_free(commit);
+ git_index_free(index);
+ git_tree_free(tree);
+
+ return error;
+}
diff --git a/src/revparse.c b/src/revparse.c
new file mode 100644
index 000000000..74635ed04
--- /dev/null
+++ b/src/revparse.c
@@ -0,0 +1,912 @@
+/*
+ * 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 <assert.h>
+
+#include "common.h"
+#include "buffer.h"
+#include "tree.h"
+#include "refdb.h"
+
+#include "git2.h"
+
+static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
+{
+ int error, i;
+ bool fallbackmode = true;
+ git_reference *ref;
+ git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
+
+ static const char* formatters[] = {
+ "%s",
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
+ NULL
+ };
+
+ if (*refname)
+ git_buf_puts(&name, refname);
+ else {
+ git_buf_puts(&name, GIT_HEAD_FILE);
+ fallbackmode = false;
+ }
+
+ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
+
+ git_buf_clear(&refnamebuf);
+
+ if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
+ goto cleanup;
+
+ if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
+ error = GIT_EINVALIDSPEC;
+ continue;
+ }
+
+ error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
+
+ if (!error) {
+ *out = ref;
+ error = 0;
+ goto cleanup;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&name);
+ git_buf_free(&refnamebuf);
+ return error;
+}
+
+static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)
+{
+ git_oid oid;
+
+ if (git_oid_fromstrn(&oid, spec, speclen) < 0)
+ return GIT_ENOTFOUND;
+
+ return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
+}
+
+static int maybe_sha(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ if (speclen != GIT_OID_HEXSZ)
+ return GIT_ENOTFOUND;
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int build_regex(regex_t *regex, const char *pattern)
+{
+ int error;
+
+ if (*pattern == '\0') {
+ giterr_set(GITERR_REGEX, "Empty pattern");
+ return GIT_EINVALIDSPEC;
+ }
+
+ error = regcomp(regex, pattern, REG_EXTENDED);
+ if (!error)
+ return 0;
+
+ error = giterr_set_regex(regex, error);
+
+ regfree(regex);
+
+ return error;
+}
+
+static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
+{
+ const char *substr;
+ int error;
+ regex_t regex;
+
+ substr = strstr(spec, "-g");
+
+ if (substr == NULL)
+ return GIT_ENOTFOUND;
+
+ if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
+ return -1;
+
+ error = regexec(&regex, spec, 0, NULL, 0);
+ regfree(&regex);
+
+ if (error)
+ return GIT_ENOTFOUND;
+
+ return maybe_abbrev(out, repo, substr+2);
+}
+
+static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
+{
+ int error;
+ git_reference *ref;
+
+ error = maybe_sha(out, repo, spec);
+ if (!error)
+ return 0;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ error = disambiguate_refname(&ref, repo, spec);
+ if (!error) {
+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+ git_reference_free(ref);
+ return error;
+ }
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ error = maybe_abbrev(out, repo, spec);
+ if (!error)
+ return 0;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ error = maybe_describe(out, repo, spec);
+ if (!error)
+ return 0;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec);
+ return GIT_ENOTFOUND;
+}
+
+static int try_parse_numeric(int *n, const char *curly_braces_content)
+{
+ int32_t content;
+ const char *end_ptr;
+
+ if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
+ return -1;
+
+ if (*end_ptr != '\0')
+ return -1;
+
+ *n = (int)content;
+ return 0;
+}
+
+static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref = NULL;
+ git_reflog *reflog = NULL;
+ regex_t preg;
+ int error = -1;
+ size_t i, numentries, cur;
+ const git_reflog_entry *entry;
+ const char *msg;
+ regmatch_t regexmatches[2];
+ git_buf buf = GIT_BUF_INIT;
+
+ cur = position;
+
+ if (*identifier != '\0' || *base_ref != NULL)
+ return GIT_EINVALIDSPEC;
+
+ if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
+ return -1;
+
+ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ if (git_reflog_read(&reflog, ref) < 0)
+ goto cleanup;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ msg = git_reflog_entry_message(entry);
+
+ if (regexec(&preg, msg, 2, regexmatches, 0))
+ continue;
+
+ cur--;
+
+ if (cur > 0)
+ continue;
+
+ git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
+
+ if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)
+ goto cleanup;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ error = maybe_abbrev(out, repo, git_buf_cstr(&buf));
+
+ goto cleanup;
+ }
+
+ error = GIT_ENOTFOUND;
+
+cleanup:
+ git_reference_free(ref);
+ git_buf_free(&buf);
+ regfree(&preg);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
+{
+ git_reflog *reflog;
+ int error = -1;
+ size_t numentries;
+ const git_reflog_entry *entry;
+ bool search_by_pos = (identifier <= 100000000);
+
+ if (git_reflog_read(&reflog, ref) < 0)
+ return -1;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ if (search_by_pos) {
+ if (numentries < identifier + 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ,
+ git_reference_name(ref), numentries, identifier);
+
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ entry = git_reflog_entry_byindex(reflog, identifier);
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ error = 0;
+ goto cleanup;
+
+ } else {
+ size_t i;
+ git_time commit_time;
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ commit_time = git_reflog_entry_committer(entry)->when;
+
+ if (commit_time.time > (git_time_t)identifier)
+ continue;
+
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ error = 0;
+ goto cleanup;
+ }
+
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref;
+ git_oid oid;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (position == 0) {
+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+ goto cleanup;
+ }
+
+ if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
+ goto cleanup;
+
+ error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
+{
+ git_reference *tracking, *ref;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (!git_reference_is_branch(ref)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if ((error = git_branch_upstream(&tracking, ref)) < 0)
+ goto cleanup;
+
+ *base_ref = tracking;
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content)
+{
+ bool is_numeric;
+ int parsed = 0, error = -1;
+ git_buf identifier = GIT_BUF_INIT;
+ git_time_t timestamp;
+
+ assert(*out == NULL);
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
+
+ if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if (is_numeric) {
+ if (parsed < 0)
+ error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
+ else
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
+
+ goto cleanup;
+ }
+
+ if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
+ error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
+
+ goto cleanup;
+ }
+
+ if (git__date_parse(&timestamp, curly_braces_content) < 0)
+ goto cleanup;
+
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
+
+cleanup:
+ git_buf_free(&identifier);
+ return error;
+}
+
+static git_otype parse_obj_type(const char *str)
+{
+ if (!strcmp(str, "commit"))
+ return GIT_OBJ_COMMIT;
+
+ if (!strcmp(str, "tree"))
+ return GIT_OBJ_TREE;
+
+ if (!strcmp(str, "blob"))
+ return GIT_OBJ_BLOB;
+
+ if (!strcmp(str, "tag"))
+ return GIT_OBJ_TAG;
+
+ return GIT_OBJ_BAD;
+}
+
+static int dereference_to_non_tag(git_object **out, git_object *obj)
+{
+ if (git_object_type(obj) == GIT_OBJ_TAG)
+ return git_tag_peel(out, (git_tag *)obj);
+
+ return git_object_dup(out, obj);
+}
+
+static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ if (n == 0) {
+ *out = temp_commit;
+ return 0;
+ }
+
+ error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_linear_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_colon_syntax(
+ git_object **out,
+ git_object *obj,
+ const char *path)
+{
+ git_object *tree;
+ int error = -1;
+ git_tree_entry *entry = NULL;
+
+ if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)
+ return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
+
+ if (*path == '\0') {
+ *out = tree;
+ return 0;
+ }
+
+ /*
+ * TODO: Handle the relative path syntax
+ * (:./relative/path and :../relative/path)
+ */
+ if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
+ goto cleanup;
+
+ error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
+
+cleanup:
+ git_tree_entry_free(entry);
+ git_object_free(tree);
+
+ return error;
+}
+
+static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
+{
+ int error;
+ git_oid oid;
+ git_object *obj;
+
+ while (!(error = git_revwalk_next(&oid, walk))) {
+
+ error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
+ if ((error < 0) && (error != GIT_ENOTFOUND))
+ return -1;
+
+ if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
+ *out = obj;
+ return 0;
+ }
+
+ git_object_free(obj);
+ }
+
+ if (error < 0 && error == GIT_ITEROVER)
+ error = GIT_ENOTFOUND;
+
+ return error;
+}
+
+static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
+{
+ regex_t preg;
+ git_revwalk *walk = NULL;
+ int error;
+
+ if ((error = build_regex(&preg, pattern)) < 0)
+ return error;
+
+ if ((error = git_revwalk_new(&walk, repo)) < 0)
+ goto cleanup;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if (spec_oid == NULL) {
+ // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails
+ if ((error = git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*")) < 0)
+ goto cleanup;
+ } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
+ goto cleanup;
+
+ error = walk_and_search(out, walk, &preg);
+
+cleanup:
+ regfree(&preg);
+ git_revwalk_free(walk);
+
+ return error;
+}
+
+static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
+{
+ git_otype expected_type;
+
+ if (*curly_braces_content == '\0')
+ return dereference_to_non_tag(out, obj);
+
+ if (*curly_braces_content == '/')
+ return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
+
+ expected_type = parse_obj_type(curly_braces_content);
+
+ if (expected_type == GIT_OBJ_BAD)
+ return GIT_EINVALIDSPEC;
+
+ return git_object_peel(out, obj, expected_type);
+}
+
+static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == '^' || spec[*pos] == '@');
+
+ (*pos)++;
+
+ if (spec[*pos] == '\0' || spec[*pos] != '{')
+ return GIT_EINVALIDSPEC;
+
+ (*pos)++;
+
+ while (spec[*pos] != '}') {
+ if (spec[*pos] == '\0')
+ return GIT_EINVALIDSPEC;
+
+ git_buf_putc(buf, spec[(*pos)++]);
+ }
+
+ (*pos)++;
+
+ return 0;
+}
+
+static int extract_path(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == ':');
+
+ (*pos)++;
+
+ if (git_buf_puts(buf, spec + *pos) < 0)
+ return -1;
+
+ *pos += git_buf_len(buf);
+
+ return 0;
+}
+
+static int extract_how_many(int *n, const char *spec, size_t *pos)
+{
+ const char *end_ptr;
+ int parsed, accumulated;
+ char kind = spec[*pos];
+
+ assert(spec[*pos] == '^' || spec[*pos] == '~');
+
+ accumulated = 0;
+
+ do {
+ do {
+ (*pos)++;
+ accumulated++;
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ if (git__isdigit(spec[*pos])) {
+ if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)
+ return GIT_EINVALIDSPEC;
+
+ accumulated += (parsed - 1);
+ *pos = end_ptr - spec;
+ }
+
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ *n = accumulated;
+
+ return 0;
+}
+
+static int object_from_reference(git_object **object, git_reference *reference)
+{
+ git_reference *resolved = NULL;
+ int error;
+
+ if (git_reference_resolve(&resolved, reference) < 0)
+ return -1;
+
+ error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);
+ git_reference_free(resolved);
+
+ return error;
+}
+
+static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)
+{
+ int error;
+ git_buf identifier = GIT_BUF_INIT;
+
+ if (*object != NULL)
+ return 0;
+
+ if (*reference != NULL) {
+ if ((error = object_from_reference(object, *reference)) < 0)
+ return error;
+
+ git_reference_free(*reference);
+ *reference = NULL;
+ return 0;
+ }
+
+ if (!allow_empty_identifier && identifier_len == 0)
+ return GIT_EINVALIDSPEC;
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));
+ git_buf_free(&identifier);
+
+ return error;
+}
+
+static int ensure_base_rev_is_not_known_yet(git_object *object)
+{
+ if (object == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
+{
+ if (object != NULL)
+ return true;
+
+ if (reference != NULL)
+ return true;
+
+ if (identifier_len > 0)
+ return true;
+
+ return false;
+}
+
+static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
+{
+ if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+{
+ size_t pos = 0, identifier_len = 0;
+ int error = -1, n;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_reference *reference = NULL;
+ git_object *base_rev = NULL;
+
+ assert(out && repo && spec);
+
+ *out = NULL;
+
+ while (spec[pos]) {
+ switch (spec[pos]) {
+ case '^':
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ } else {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ }
+ break;
+
+ case '~':
+ {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case ':':
+ {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_path(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
+ goto cleanup;
+
+ if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+ } else {
+ if (*git_buf_cstr(&buf) == '/') {
+ if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
+ goto cleanup;
+ } else {
+
+ /*
+ * TODO: support merge-stage path lookup (":2:Makefile")
+ * and plain index blob lookup (:i-am/a/blob)
+ */
+ giterr_set(GITERR_INVALID, "Unimplemented");
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+ }
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case '@':
+ {
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
+ goto cleanup;
+
+ if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ if (temp_object != NULL)
+ base_rev = temp_object;
+ break;
+ } else {
+ /* Fall through */
+ }
+ }
+
+ default:
+ if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
+ goto cleanup;
+
+ pos++;
+ identifier_len++;
+ }
+ }
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ *out = base_rev;
+ error = 0;
+
+cleanup:
+ if (error) {
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(GITERR_INVALID,
+ "Failed to parse revision specifier - Invalid pattern '%s'", spec);
+
+ git_object_free(base_rev);
+ }
+ git_reference_free(reference);
+ git_buf_free(&buf);
+ return error;
+}
+
+
+int git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec)
+{
+ const char *dotdot;
+ int error = 0;
+
+ assert(revspec && repo && spec);
+
+ memset(revspec, 0x0, sizeof(*revspec));
+
+ if ((dotdot = strstr(spec, "..")) != NULL) {
+ char *lstr;
+ const char *rstr;
+ revspec->flags = GIT_REVPARSE_RANGE;
+
+ lstr = git__substrdup(spec, dotdot - spec);
+ rstr = dotdot + 2;
+ if (dotdot[2] == '.') {
+ revspec->flags |= GIT_REVPARSE_MERGE_BASE;
+ rstr++;
+ }
+
+ if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) {
+ return error;
+ }
+
+ if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) {
+ return error;
+ }
+
+ git__free((void*)lstr);
+ } else {
+ revspec->flags = GIT_REVPARSE_SINGLE;
+ error = git_revparse_single(&revspec->from, repo, spec);
+ }
+
+ return error;
+}
+
diff --git a/src/revwalk.c b/src/revwalk.c
index e64d93f20..16f06624d 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,146 +8,18 @@
#include "common.h"
#include "commit.h"
#include "odb.h"
-#include "pqueue.h"
#include "pool.h"
-#include "oidmap.h"
-#include "git2/revwalk.h"
-#include "git2/merge.h"
+#include "revwalk.h"
+#include "git2/revparse.h"
+#include "merge.h"
#include <regex.h>
-GIT__USE_OIDMAP;
-
-#define PARENT1 (1 << 0)
-#define PARENT2 (1 << 1)
-#define RESULT (1 << 2)
-#define STALE (1 << 3)
-
-typedef struct commit_object {
- git_oid oid;
- uint32_t time;
- unsigned int seen:1,
- uninteresting:1,
- topo_delay:1,
- parsed:1,
- flags : 4;
-
- unsigned short in_degree;
- unsigned short out_degree;
-
- struct commit_object **parents;
-} commit_object;
-
-typedef struct commit_list {
- commit_object *item;
- struct commit_list *next;
-} commit_list;
-
-struct git_revwalk {
- git_repository *repo;
- git_odb *odb;
-
- git_oidmap *commits;
- git_pool commit_pool;
-
- commit_list *iterator_topo;
- commit_list *iterator_rand;
- commit_list *iterator_reverse;
- git_pqueue iterator_time;
-
- int (*get_next)(commit_object **, git_revwalk *);
- int (*enqueue)(git_revwalk *, commit_object *);
-
- unsigned walking:1;
- unsigned int sorting;
-
- /* merge base calculation */
- commit_object *one;
- git_vector twos;
-};
-
-static int commit_time_cmp(void *a, void *b)
-{
- commit_object *commit_a = (commit_object *)a;
- commit_object *commit_b = (commit_object *)b;
-
- return (commit_a->time < commit_b->time);
-}
-
-static commit_list *commit_list_insert(commit_object *item, commit_list **list_p)
-{
- commit_list *new_list = git__malloc(sizeof(commit_list));
- if (new_list != NULL) {
- new_list->item = item;
- new_list->next = *list_p;
- }
- *list_p = new_list;
- return new_list;
-}
-
-static commit_list *commit_list_insert_by_date(commit_object *item, commit_list **list_p)
-{
- commit_list **pp = list_p;
- commit_list *p;
-
- while ((p = *pp) != NULL) {
- if (commit_time_cmp(p->item, item) < 0)
- break;
-
- pp = &p->next;
- }
-
- return commit_list_insert(item, pp);
-}
-static void commit_list_free(commit_list **list_p)
-{
- commit_list *list = *list_p;
-
- while (list) {
- commit_list *temp = list;
- list = temp->next;
- git__free(temp);
- }
-
- *list_p = NULL;
-}
-
-static commit_object *commit_list_pop(commit_list **stack)
-{
- commit_list *top = *stack;
- commit_object *item = top ? top->item : NULL;
-
- if (top) {
- *stack = top->next;
- git__free(top);
- }
- return item;
-}
-
-#define PARENTS_PER_COMMIT 2
-#define COMMIT_ALLOC \
- (sizeof(commit_object) + PARENTS_PER_COMMIT * sizeof(commit_object *))
-
-static commit_object *alloc_commit(git_revwalk *walk)
-{
- return (commit_object *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC);
-}
-
-static commit_object **alloc_parents(
- git_revwalk *walk, commit_object *commit, size_t n_parents)
-{
- if (n_parents <= PARENTS_PER_COMMIT)
- return (commit_object **)((char *)commit + sizeof(commit_object));
-
- return (commit_object **)git_pool_malloc(
- &walk->commit_pool, (uint32_t)(n_parents * sizeof(commit_object *)));
-}
-
-
-static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
+git_commit_list_node *git_revwalk__commit_lookup(
+ git_revwalk *walk, const git_oid *oid)
{
- commit_object *commit;
+ git_commit_list_node *commit;
khiter_t pos;
int ret;
@@ -156,7 +28,7 @@ static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
if (pos != kh_end(walk->commits))
return kh_value(walk->commits, pos);
- commit = alloc_commit(walk);
+ commit = git_commit_list_alloc_node(walk);
if (commit == NULL)
return NULL;
@@ -169,227 +41,7 @@ static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
return commit;
}
-static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw)
-{
- 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;
-
- int i, parents = 0;
- int commit_time;
-
- buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;
-
- parents_start = buffer;
- while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {
- parents++;
- buffer += parent_len;
- }
-
- commit->parents = alloc_parents(walk, commit, parents);
- GITERR_CHECK_ALLOC(commit->parents);
-
- buffer = parents_start;
- for (i = 0; i < parents; ++i) {
- git_oid oid;
-
- if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0)
- return -1;
-
- commit->parents[i] = commit_lookup(walk, &oid);
- if (commit->parents[i] == NULL)
- return -1;
-
- buffer += parent_len;
- }
-
- commit->out_degree = (unsigned short)parents;
-
- if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Object is corrupted");
- return -1;
- }
-
- buffer = memchr(buffer, '>', buffer_end - buffer);
- if (buffer == NULL) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Can't find author");
- return -1;
- }
-
- if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < 0) {
- giterr_set(GITERR_ODB, "Failed to parse commit. Can't parse commit time");
- return -1;
- }
-
- commit->time = (time_t)commit_time;
- commit->parsed = 1;
- return 0;
-}
-
-static int commit_parse(git_revwalk *walk, commit_object *commit)
-{
- git_odb_object *obj;
- int error;
-
- if (commit->parsed)
- return 0;
-
- if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
- return error;
-
- if (obj->raw.type != GIT_OBJ_COMMIT) {
- git_odb_object_free(obj);
- giterr_set(GITERR_INVALID, "Failed to parse commit. Object is no commit object");
- return -1;
- }
-
- error = commit_quick_parse(walk, commit, &obj->raw);
- git_odb_object_free(obj);
- return error;
-}
-
-static int interesting(git_pqueue *list)
-{
- unsigned int i;
- for (i = 1; i < git_pqueue_size(list); i++) {
- commit_object *commit = list->d[i];
- if ((commit->flags & STALE) == 0)
- return 1;
- }
-
- return 0;
-}
-
-static int merge_bases_many(commit_list **out, git_revwalk *walk, commit_object *one, git_vector *twos)
-{
- int error;
- unsigned int i;
- commit_object *two;
- commit_list *result = NULL, *tmp = NULL;
- git_pqueue list;
-
- /* if the commit is repeated, we have a our merge base already */
- git_vector_foreach(twos, i, two) {
- if (one == two)
- return commit_list_insert(one, out) ? 0 : -1;
- }
-
- if (git_pqueue_init(&list, twos->length * 2, commit_time_cmp) < 0)
- return -1;
-
- if (commit_parse(walk, one) < 0)
- return -1;
-
- one->flags |= PARENT1;
- if (git_pqueue_insert(&list, one) < 0)
- return -1;
-
- git_vector_foreach(twos, i, two) {
- commit_parse(walk, two);
- two->flags |= PARENT2;
- if (git_pqueue_insert(&list, two) < 0)
- return -1;
- }
-
- /* as long as there are non-STALE commits */
- while (interesting(&list)) {
- commit_object *commit;
- int flags;
-
- commit = git_pqueue_pop(&list);
-
- flags = commit->flags & (PARENT1 | PARENT2 | STALE);
- if (flags == (PARENT1 | PARENT2)) {
- if (!(commit->flags & RESULT)) {
- commit->flags |= RESULT;
- if (commit_list_insert(commit, &result) == NULL)
- return -1;
- }
- /* we mark the parents of a merge stale */
- flags |= STALE;
- }
-
- for (i = 0; i < commit->out_degree; i++) {
- commit_object *p = commit->parents[i];
- if ((p->flags & flags) == flags)
- continue;
-
- if ((error = commit_parse(walk, p)) < 0)
- return error;
-
- p->flags |= flags;
- if (git_pqueue_insert(&list, p) < 0)
- return -1;
- }
- }
-
- git_pqueue_free(&list);
-
- /* filter out any stale commits in the results */
- tmp = result;
- result = NULL;
-
- while (tmp) {
- struct commit_list *next = tmp->next;
- if (!(tmp->item->flags & STALE))
- if (commit_list_insert_by_date(tmp->item, &result) == NULL)
- return -1;
-
- git__free(tmp);
- tmp = next;
- }
-
- *out = result;
- return 0;
-}
-
-int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two)
-{
- git_revwalk *walk;
- git_vector list;
- commit_list *result = NULL;
- commit_object *commit;
- void *contents[1];
-
- if (git_revwalk_new(&walk, repo) < 0)
- return -1;
-
- commit = commit_lookup(walk, two);
- if (commit == NULL)
- goto on_error;
-
- /* This is just one value, so we can do it on the stack */
- memset(&list, 0x0, sizeof(git_vector));
- contents[0] = commit;
- list.length = 1;
- list.contents = contents;
-
- commit = commit_lookup(walk, one);
- if (commit == NULL)
- goto on_error;
-
- if (merge_bases_many(&result, walk, commit, &list) < 0)
- goto on_error;
-
- if (!result) {
- git_revwalk_free(walk);
- return GIT_ENOTFOUND;
- }
-
- git_oid_cpy(out, &result->item->oid);
- commit_list_free(&result);
- git_revwalk_free(walk);
-
- return 0;
-
-on_error:
- git_revwalk_free(walk);
- return -1;
-}
-
-static void mark_uninteresting(commit_object *commit)
+static void mark_uninteresting(git_commit_list_node *commit)
{
unsigned short i;
assert(commit);
@@ -405,7 +57,7 @@ static void mark_uninteresting(commit_object *commit)
mark_uninteresting(commit->parents[i]);
}
-static int process_commit(git_revwalk *walk, commit_object *commit, int hide)
+static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide)
{
int error;
@@ -417,13 +69,13 @@ static int process_commit(git_revwalk *walk, commit_object *commit, int hide)
commit->seen = 1;
- if ((error = commit_parse(walk, commit)) < 0)
+ if ((error = git_commit_list_parse(walk, commit)) < 0)
return error;
return walk->enqueue(walk, commit);
}
-static int process_commit_parents(git_revwalk *walk, commit_object *commit)
+static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit)
{
unsigned short i;
int error = 0;
@@ -436,9 +88,22 @@ static int process_commit_parents(git_revwalk *walk, commit_object *commit)
static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting)
{
- commit_object *commit;
+ git_object *obj;
+ git_otype type;
+ git_commit_list_node *commit;
+
+ if (git_object_lookup(&obj, walk->repo, oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ type = git_object_type(obj);
+ git_object_free(obj);
- commit = commit_lookup(walk, oid);
+ if (type != GIT_OBJ_COMMIT) {
+ giterr_set(GITERR_INVALID, "Object is no commit object");
+ return -1;
+ }
+
+ commit = git_revwalk__commit_lookup(walk, oid);
if (commit == NULL)
return -1; /* error already reported by failed lookup */
@@ -470,7 +135,7 @@ static int push_ref(git_revwalk *walk, const char *refname, int hide)
{
git_oid oid;
- if (git_reference_name_to_oid(&oid, walk->repo, refname) < 0)
+ if (git_reference_name_to_id(&oid, walk->repo, refname) < 0)
return -1;
return push_commit(walk, &oid, hide);
@@ -478,7 +143,6 @@ static int push_ref(git_revwalk *walk, const char *refname, int hide)
struct push_cb_data {
git_revwalk *walk;
- const char *glob;
int hide;
};
@@ -486,10 +150,7 @@ static int push_glob_cb(const char *refname, void *data_)
{
struct push_cb_data *data = (struct push_cb_data *)data_;
- if (!p_fnmatch(data->glob, refname, 0))
- return push_ref(data->walk, refname, data->hide);
-
- return 0;
+ return push_ref(data->walk, refname, data->hide);
}
static int push_glob(git_revwalk *walk, const char *glob, int hide)
@@ -522,11 +183,10 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide)
goto on_error;
data.walk = walk;
- data.glob = git_buf_cstr(&buf);
data.hide = hide;
- if (git_reference_foreach(
- walk->repo, GIT_REF_LISTALL, push_glob_cb, &data) < 0)
+ if (git_reference_foreach_glob(
+ walk->repo, git_buf_cstr(&buf), GIT_REF_LISTALL, push_glob_cb, &data) < 0)
goto on_error;
regfree(&preg);
@@ -569,26 +229,51 @@ int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
return push_ref(walk, refname, 0);
}
+int git_revwalk_push_range(git_revwalk *walk, const char *range)
+{
+ git_revspec revspec;
+ int error = 0;
+
+ if ((error = git_revparse(&revspec, walk->repo, range)))
+ return error;
+
+ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
+ /* TODO: support "<commit>...<commit>" */
+ giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = push_commit(walk, git_object_id(revspec.from), 1)))
+ goto out;
+
+ error = push_commit(walk, git_object_id(revspec.to), 0);
+
+out:
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ return error;
+}
+
int git_revwalk_hide_ref(git_revwalk *walk, const char *refname)
{
assert(walk && refname);
return push_ref(walk, refname, 1);
}
-static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit)
+static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit)
{
return git_pqueue_insert(&walk->iterator_time, commit);
}
-static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit)
+static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit)
{
- return commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
+ return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
}
-static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk)
{
int error;
- commit_object *next;
+ git_commit_list_node *next;
while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
if ((error = process_commit_parents(walk, next)) < 0)
@@ -600,15 +285,16 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)
}
}
- return GIT_REVWALKOVER;
+ giterr_clear();
+ return GIT_ITEROVER;
}
-static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk)
{
int error;
- commit_object *next;
+ git_commit_list_node *next;
- while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) {
+ while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL) {
if ((error = process_commit_parents(walk, next)) < 0)
return error;
@@ -618,18 +304,21 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)
}
}
- return GIT_REVWALKOVER;
+ giterr_clear();
+ return GIT_ITEROVER;
}
-static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk)
{
- commit_object *next;
+ git_commit_list_node *next;
unsigned short i;
for (;;) {
- next = commit_list_pop(&walk->iterator_topo);
- if (next == NULL)
- return GIT_REVWALKOVER;
+ next = git_commit_list_pop(&walk->iterator_topo);
+ if (next == NULL) {
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
if (next->in_degree > 0) {
next->topo_delay = 1;
@@ -637,11 +326,11 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
}
for (i = 0; i < next->out_degree; ++i) {
- commit_object *parent = next->parents[i];
+ git_commit_list_node *parent = next->parents[i];
if (--parent->in_degree == 0 && parent->topo_delay) {
parent->topo_delay = 0;
- if (commit_list_insert(parent, &walk->iterator_topo) == NULL)
+ if (git_commit_list_insert(parent, &walk->iterator_topo) == NULL)
return -1;
}
}
@@ -651,10 +340,10 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
}
}
-static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk)
{
- *object_out = commit_list_pop(&walk->iterator_reverse);
- return *object_out ? 0 : GIT_REVWALKOVER;
+ *object_out = git_commit_list_pop(&walk->iterator_reverse);
+ return *object_out ? 0 : GIT_ITEROVER;
}
@@ -662,21 +351,23 @@ static int prepare_walk(git_revwalk *walk)
{
int error;
unsigned int i;
- commit_object *next, *two;
- commit_list *bases = NULL;
+ git_commit_list_node *next, *two;
+ git_commit_list *bases = NULL;
/*
* If walk->one is NULL, there were no positive references,
* so we know that the walk is already over.
*/
- if (walk->one == NULL)
- return GIT_REVWALKOVER;
+ if (walk->one == NULL) {
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
/* first figure out what the merge bases are */
- if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0)
+ if (git_merge__bases_many(&bases, walk, walk->one, &walk->twos) < 0)
return -1;
- commit_list_free(&bases);
+ git_commit_list_free(&bases);
if (process_commit(walk, walk->one, walk->one->uninteresting) < 0)
return -1;
@@ -690,15 +381,15 @@ static int prepare_walk(git_revwalk *walk)
while ((error = walk->get_next(&next, walk)) == 0) {
for (i = 0; i < next->out_degree; ++i) {
- commit_object *parent = next->parents[i];
+ git_commit_list_node *parent = next->parents[i];
parent->in_degree++;
}
- if (commit_list_insert(next, &walk->iterator_topo) == NULL)
+ if (git_commit_list_insert(next, &walk->iterator_topo) == NULL)
return -1;
}
- if (error != GIT_REVWALKOVER)
+ if (error != GIT_ITEROVER)
return error;
walk->get_next = &revwalk_next_toposort;
@@ -707,10 +398,10 @@ static int prepare_walk(git_revwalk *walk)
if (walk->sorting & GIT_SORT_REVERSE) {
while ((error = walk->get_next(&next, walk)) == 0)
- if (commit_list_insert(next, &walk->iterator_reverse) == NULL)
+ if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL)
return -1;
- if (error != GIT_REVWALKOVER)
+ if (error != GIT_ITEROVER)
return error;
walk->get_next = &revwalk_next_reverse;
@@ -721,9 +412,6 @@ static int prepare_walk(git_revwalk *walk)
}
-
-
-
int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
{
git_revwalk *walk;
@@ -736,7 +424,7 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
walk->commits = git_oidmap_alloc();
GITERR_CHECK_ALLOC(walk->commits);
- if (git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp) < 0 ||
+ if (git_pqueue_init(&walk->iterator_time, 8, git_commit_list_time_cmp) < 0 ||
git_vector_init(&walk->twos, 4, NULL) < 0 ||
git_pool_init(&walk->commit_pool, 1,
git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0)
@@ -798,7 +486,7 @@ void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
int git_revwalk_next(git_oid *oid, git_revwalk *walk)
{
int error;
- commit_object *next;
+ git_commit_list_node *next;
assert(walk && oid);
@@ -809,9 +497,10 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk)
error = walk->get_next(&next, walk);
- if (error == GIT_REVWALKOVER) {
+ if (error == GIT_ITEROVER) {
git_revwalk_reset(walk);
- return GIT_REVWALKOVER;
+ giterr_clear();
+ return GIT_ITEROVER;
}
if (!error)
@@ -822,7 +511,7 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk)
void git_revwalk_reset(git_revwalk *walk)
{
- commit_object *commit;
+ git_commit_list_node *commit;
assert(walk);
@@ -834,9 +523,9 @@ void git_revwalk_reset(git_revwalk *walk)
});
git_pqueue_clear(&walk->iterator_time);
- commit_list_free(&walk->iterator_topo);
- commit_list_free(&walk->iterator_rand);
- commit_list_free(&walk->iterator_reverse);
+ git_commit_list_free(&walk->iterator_topo);
+ git_commit_list_free(&walk->iterator_rand);
+ git_commit_list_free(&walk->iterator_reverse);
walk->walking = 0;
walk->one = NULL;
diff --git a/src/revwalk.h b/src/revwalk.h
new file mode 100644
index 000000000..22696dfcd
--- /dev/null
+++ b/src/revwalk.h
@@ -0,0 +1,44 @@
+/*
+ * 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_revwalk_h__
+#define INCLUDE_revwalk_h__
+
+#include "git2/revwalk.h"
+#include "oidmap.h"
+#include "commit_list.h"
+#include "pqueue.h"
+#include "pool.h"
+#include "vector.h"
+
+GIT__USE_OIDMAP;
+
+struct git_revwalk {
+ git_repository *repo;
+ git_odb *odb;
+
+ git_oidmap *commits;
+ git_pool commit_pool;
+
+ git_commit_list *iterator_topo;
+ git_commit_list *iterator_rand;
+ git_commit_list *iterator_reverse;
+ git_pqueue iterator_time;
+
+ int (*get_next)(git_commit_list_node **, git_revwalk *);
+ int (*enqueue)(git_revwalk *, git_commit_list_node *);
+
+ unsigned walking:1;
+ unsigned int sorting;
+
+ /* merge base calculation */
+ git_commit_list_node *one;
+ git_vector twos;
+};
+
+git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid);
+
+#endif
diff --git a/src/sha1.h b/src/sha1.h
deleted file mode 100644
index 93a244d76..000000000
--- a/src/sha1.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-typedef struct {
- unsigned long long size;
- unsigned int H[5];
- unsigned int W[16];
-} blk_SHA_CTX;
-
-void git__blk_SHA1_Init(blk_SHA_CTX *ctx);
-void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len);
-void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);
-
-#define SHA_CTX blk_SHA_CTX
-#define SHA1_Init git__blk_SHA1_Init
-#define SHA1_Update git__blk_SHA1_Update
-#define SHA1_Final git__blk_SHA1_Final
diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c
index 096da1739..b7e66cc69 100644
--- a/src/sha1_lookup.c
+++ b/src/sha1_lookup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h
index cd40a9d57..9a3537273 100644
--- a/src/sha1_lookup.h
+++ b/src/sha1_lookup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/signature.c b/src/signature.c
index 7d329c4c9..164e8eb67 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -22,80 +22,60 @@ void git_signature_free(git_signature *sig)
git__free(sig);
}
-static const char *skip_leading_spaces(const char *buffer, const char *buffer_end)
-{
- while (*buffer == ' ' && buffer < buffer_end)
- buffer++;
-
- return buffer;
-}
-
-static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end)
-{
- while (*buffer_end == ' ' && buffer_end > buffer_start)
- buffer_end--;
-
- return buffer_end;
-}
-
static int signature_error(const char *msg)
{
giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg);
return -1;
}
-static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty)
+static bool contains_angle_brackets(const char *input)
{
- const char *left, *right;
- size_t trimmed_input_length;
-
- assert(storage);
-
- left = skip_leading_spaces(input, input_end);
- right = skip_trailing_spaces(input, input_end - 1);
-
- if (right < left) {
- if (fail_when_empty)
- return signature_error("input is either empty of contains only spaces");
+ return strchr(input, '<') != NULL || strchr(input, '>') != NULL;
+}
- right = left - 1;
+static char *extract_trimmed(const char *ptr, size_t len)
+{
+ while (len && ptr[0] == ' ') {
+ ptr++; len--;
}
- trimmed_input_length = right - left + 1;
-
- *storage = git__malloc(trimmed_input_length + 1);
- GITERR_CHECK_ALLOC(*storage);
-
- memcpy(*storage, left, trimmed_input_length);
- (*storage)[trimmed_input_length] = 0;
+ while (len && ptr[len - 1] == ' ') {
+ len--;
+ }
- return 0;
+ return git__substrdup(ptr, len);
}
int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset)
{
- int error;
git_signature *p = NULL;
assert(name && email);
*sig_out = NULL;
+ if (contains_angle_brackets(name) ||
+ contains_angle_brackets(email)) {
+ return signature_error(
+ "Neither `name` nor `email` should contain angle brackets chars.");
+ }
+
p = git__calloc(1, sizeof(git_signature));
GITERR_CHECK_ALLOC(p);
- if ((error = process_trimming(name, &p->name, name + strlen(name), 1)) < 0 ||
- (error = process_trimming(email, &p->email, email + strlen(email), 1)) < 0)
- {
+ p->name = extract_trimmed(name, strlen(name));
+ p->email = extract_trimmed(email, strlen(email));
+
+ if (p->name == NULL || p->email == NULL ||
+ p->name[0] == '\0' || p->email[0] == '\0') {
git_signature_free(p);
- return error;
+ return -1;
}
-
+
p->when.time = time;
p->when.offset = offset;
*sig_out = p;
-
return 0;
}
@@ -111,36 +91,26 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema
{
time_t now;
time_t offset;
- struct tm *utc_tm, *local_tm;
+ struct tm *utc_tm;
git_signature *sig;
-
-#ifndef GIT_WIN32
- struct tm _utc, _local;
-#endif
+ struct tm _utc;
*sig_out = NULL;
- time(&now);
-
- /**
- * On Win32, `gmtime_r` doesn't exist but
- * `gmtime` is threadsafe, so we can use that
+ /*
+ * Get the current time as seconds since the epoch and
+ * transform that into a tm struct containing the time at
+ * UTC. Give that to mktime which considers it a local time
+ * (tm_isdst = -1 asks it to take DST into account) and gives
+ * us that time as seconds since the epoch. The difference
+ * between its return value and 'now' is our offset to UTC.
*/
-#ifdef GIT_WIN32
- utc_tm = gmtime(&now);
- local_tm = localtime(&now);
-#else
- utc_tm = gmtime_r(&now, &_utc);
- local_tm = localtime_r(&now, &_local);
-#endif
-
- offset = mktime(local_tm) - mktime(utc_tm);
+ time(&now);
+ utc_tm = p_gmtime_r(&now, &_utc);
+ utc_tm->tm_isdst = -1;
+ offset = (time_t)difftime(now, mktime(utc_tm));
offset /= 60;
- /* mktime takes care of setting tm_isdst correctly */
- if (local_tm->tm_isdst)
- offset += 60;
-
if (git_signature_new(&sig, name, email, now, (int)offset) < 0)
return -1;
@@ -149,169 +119,71 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema
return 0;
}
-static int timezone_error(const char *msg)
-{
- giterr_set(GITERR_INVALID, "Failed to parse TZ offset - %s", msg);
- return -1;
-}
-
-static int parse_timezone_offset(const char *buffer, int *offset_out)
-{
- int dec_offset;
- int mins, hours, offset;
-
- const char *offset_start;
- const char *offset_end;
-
- offset_start = buffer;
-
- if (*offset_start == '\n') {
- *offset_out = 0;
- return 0;
- }
-
- if (offset_start[0] != '-' && offset_start[0] != '+')
- return timezone_error("does not start with '+' or '-'");
-
- if (offset_start[1] < '0' || offset_start[1] > '9')
- return timezone_error("expected initial digit");
-
- if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < 0)
- return timezone_error("not a valid number");
-
- if (offset_end - offset_start != 5)
- return timezone_error("invalid length");
-
- if (dec_offset > 1400)
- return timezone_error("value too large");
-
- hours = dec_offset / 100;
- mins = dec_offset % 100;
-
- if (hours > 14) // see http://www.worldtimezone.com/faq.html
- return timezone_error("hour value too large");
-
- if (mins > 59)
- return timezone_error("minutes value too large");
-
- offset = (hours * 60) + mins;
-
- if (offset_start[0] == '-')
- offset *= -1;
-
- *offset_out = offset;
-
- return 0;
-}
-
-static int process_next_token(const char **buffer_out, char **storage,
- const char *token_end, const char *right_boundary)
-{
- int error = process_trimming(*buffer_out, storage, token_end, 0);
- if (error < 0)
- return error;
-
- *buffer_out = token_end + 1;
-
- if (*buffer_out > right_boundary)
- return signature_error("signature is too short");
-
- return 0;
-}
-
-static const char *scan_for_previous_token(const char *buffer, const char *left_boundary)
-{
- const char *start;
-
- if (buffer <= left_boundary)
- return NULL;
-
- start = skip_trailing_spaces(left_boundary, buffer);
-
- /* Search for previous occurence of space */
- while (start[-1] != ' ' && start > left_boundary)
- start--;
-
- return start;
-}
-
-static int parse_time(git_time_t *time_out, const char *buffer)
-{
- int time;
- int error;
-
- if (*buffer == '+' || *buffer == '-') {
- giterr_set(GITERR_INVALID, "Failed while parsing time. '%s' actually looks like a timezone offset.", buffer);
- return -1;
- }
-
- error = git__strtol32(&time, buffer, &buffer, 10);
-
- if (!error)
- *time_out = (git_time_t)time;
-
- return error;
-}
-
int git_signature__parse(git_signature *sig, const char **buffer_out,
const char *buffer_end, const char *header, char ender)
{
const char *buffer = *buffer_out;
- const char *line_end, *name_end, *email_end, *tz_start, *time_start;
- int error = 0;
+ const char *email_start, *email_end;
- memset(sig, 0x0, sizeof(git_signature));
+ memset(sig, 0, sizeof(git_signature));
- if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
+ if ((buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
return signature_error("no newline given");
if (header) {
const size_t header_len = strlen(header);
- if (memcmp(buffer, header, header_len) != 0)
+ if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0)
return signature_error("expected prefix doesn't match actual");
buffer += header_len;
}
- if (buffer > line_end)
- return signature_error("signature too short");
-
- if ((name_end = strchr(buffer, '<')) == NULL)
- return signature_error("character '<' not allowed in signature");
+ email_start = git__memrchr(buffer, '<', buffer_end - buffer);
+ email_end = git__memrchr(buffer, '>', buffer_end - buffer);
- if ((email_end = strchr(name_end, '>')) == NULL)
- return signature_error("character '>' not allowed in signature");
-
- if (email_end < name_end)
+ if (!email_start || !email_end || email_end <= email_start)
return signature_error("malformed e-mail");
- error = process_next_token(&buffer, &sig->name, name_end, line_end);
- if (error < 0)
- return error;
-
- error = process_next_token(&buffer, &sig->email, email_end, line_end);
- if (error < 0)
- return error;
-
- tz_start = scan_for_previous_token(line_end - 1, buffer);
-
- if (tz_start == NULL)
- goto clean_exit; /* No timezone nor date */
-
- time_start = scan_for_previous_token(tz_start - 1, buffer);
- if (time_start == NULL || parse_time(&sig->when.time, time_start) < 0) {
- /* The tz_start might point at the time */
- parse_time(&sig->when.time, tz_start);
- goto clean_exit;
- }
-
- if (parse_timezone_offset(tz_start, &sig->when.offset) < 0) {
- sig->when.time = 0; /* Bogus timezone, we reset the time */
+ email_start += 1;
+ sig->name = extract_trimmed(buffer, email_start - buffer - 1);
+ sig->email = extract_trimmed(email_start, email_end - email_start);
+
+ /* Do we even have a time at the end of the signature? */
+ if (email_end + 2 < buffer_end) {
+ const char *time_start = email_end + 2;
+ const char *time_end;
+
+ if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)
+ return signature_error("invalid Unix timestamp");
+
+ /* do we have a timezone? */
+ if (time_end + 1 < buffer_end) {
+ int offset, hours, mins;
+ const char *tz_start, *tz_end;
+
+ tz_start = time_end + 1;
+
+ if ((tz_start[0] != '-' && tz_start[0] != '+') ||
+ git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0)
+ return signature_error("malformed timezone");
+
+ hours = offset / 100;
+ mins = offset % 100;
+
+ /*
+ * only store timezone if it's not overflowing;
+ * see http://www.worldtimezone.com/faq.html
+ */
+ if (hours < 14 && mins < 59) {
+ sig->when.offset = (hours * 60) + mins;
+ if (tz_start[0] == '-')
+ sig->when.offset = -sig->when.offset;
+ }
+ }
}
-clean_exit:
- *buffer_out = line_end + 1;
+ *buffer_out = buffer_end + 1;
return 0;
}
diff --git a/src/signature.h b/src/signature.h
index 97b3a055e..24655cbf5 100644
--- a/src/signature.h
+++ b/src/signature.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/stash.c b/src/stash.c
new file mode 100644
index 000000000..355c5dc9c
--- /dev/null
+++ b/src/stash.c
@@ -0,0 +1,663 @@
+/*
+ * 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 "commit.h"
+#include "tree.h"
+#include "reflog.h"
+#include "git2/diff.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/checkout.h"
+#include "signature.h"
+
+static int create_error(int error, const char *msg)
+{
+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
+ return error;
+}
+
+static int retrieve_head(git_reference **out, git_repository *repo)
+{
+ int error = git_repository_head(out, repo);
+
+ if (error == GIT_EORPHANEDHEAD)
+ return create_error(error, "You do not have the initial commit yet.");
+
+ return error;
+}
+
+static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
+{
+ char *formatted_oid;
+
+ formatted_oid = git_oid_allocfmt(b_commit);
+ GITERR_CHECK_ALLOC(formatted_oid);
+
+ git_buf_put(out, formatted_oid, 7);
+ git__free(formatted_oid);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int append_commit_description(git_buf *out, git_commit* commit)
+{
+ const char *message;
+ size_t pos = 0, len;
+
+ if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
+ return -1;
+
+ message = git_commit_message(commit);
+ len = strlen(message);
+
+ /* TODO: Replace with proper commit short message
+ * when git_commit_message_short() is implemented.
+ */
+ while (pos < len && message[pos] != '\n')
+ pos++;
+
+ git_buf_putc(out, ' ');
+ git_buf_put(out, message, pos);
+ git_buf_putc(out, '\n');
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int retrieve_base_commit_and_message(
+ git_commit **b_commit,
+ git_buf *stash_message,
+ git_repository *repo)
+{
+ git_reference *head = NULL;
+ int error;
+
+ if ((error = retrieve_head(&head, repo)) < 0)
+ return error;
+
+ if (strcmp("HEAD", git_reference_name(head)) == 0)
+ error = git_buf_puts(stash_message, "(no branch): ");
+ else
+ error = git_buf_printf(
+ stash_message,
+ "%s: ",
+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_commit_lookup(
+ b_commit, repo, git_reference_target(head))) < 0)
+ goto cleanup;
+
+ if ((error = append_commit_description(stash_message, *b_commit)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+static int build_tree_from_index(git_tree **out, git_index *index)
+{
+ int error;
+ git_oid i_tree_oid;
+
+ if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
+ return -1;
+
+ return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
+}
+
+static int commit_index(
+ git_commit **i_commit,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ const git_commit *parent)
+{
+ git_tree *i_tree = NULL;
+ git_oid i_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_tree_from_index(&i_tree, index)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &i_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ i_tree,
+ 1,
+ &parent)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+struct cb_data {
+ git_index *index;
+
+ int error;
+
+ bool include_changed;
+ bool include_untracked;
+ bool include_ignored;
+};
+
+static int update_index_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ struct cb_data *data = (struct cb_data *)payload;
+ const char *add_path = NULL;
+
+ GIT_UNUSED(progress);
+
+ switch (delta->status) {
+ case GIT_DELTA_IGNORED:
+ if (data->include_ignored)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_UNTRACKED:
+ if (data->include_untracked)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ if (data->include_changed)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_DELETED:
+ if (!data->include_changed)
+ break;
+ if (git_index_find(NULL, data->index, delta->old_file.path) == 0)
+ data->error = git_index_remove(
+ data->index, delta->old_file.path, 0);
+ break;
+
+ default:
+ /* Unimplemented */
+ giterr_set(
+ GITERR_INVALID,
+ "Cannot update index. Unimplemented status (%d)",
+ delta->status);
+ data->error = -1;
+ break;
+ }
+
+ if (add_path != NULL)
+ data->error = git_index_add_bypath(data->index, add_path);
+
+ return data->error;
+}
+
+static int build_untracked_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *i_tree = NULL;
+ git_diff_list *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct cb_data data = {0};
+ int error;
+
+ git_index_clear(index);
+
+ data.index = index;
+
+ if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ data.include_untracked = true;
+ }
+
+ if (flags & GIT_STASH_INCLUDE_IGNORED) {
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ data.include_ignored = true;
+ }
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_workdir(
+ &diff, git_index_owner(index), i_tree, &opts)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_foreach(
+ diff, update_index_cb, NULL, NULL, &data)) < 0)
+ {
+ if (error == GIT_EUSER)
+ error = data.error;
+ goto cleanup;
+ }
+
+ error = build_tree_from_index(tree_out, index);
+
+cleanup:
+ git_diff_list_free(diff);
+ git_tree_free(i_tree);
+ return error;
+}
+
+static int commit_untracked(
+ git_commit **u_commit,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *u_tree = NULL;
+ git_oid u_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &u_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ u_tree,
+ 0,
+ NULL)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
+
+cleanup:
+ git_tree_free(u_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+static int build_workdir_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *b_commit)
+{
+ git_repository *repo = git_index_owner(index);
+ git_tree *b_tree = NULL;
+ git_diff_list *diff = NULL, *diff2 = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct cb_data data = {0};
+ int error;
+
+ if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_index_to_workdir(&diff2, repo, NULL, &opts)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_merge(diff, diff2)) < 0)
+ goto cleanup;
+
+ data.index = index;
+ data.include_changed = true;
+
+ if ((error = git_diff_foreach(
+ diff, update_index_cb, NULL, NULL, &data)) < 0)
+ {
+ if (error == GIT_EUSER)
+ error = data.error;
+ goto cleanup;
+ }
+
+
+ if ((error = build_tree_from_index(tree_out, index)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_diff_list_free(diff);
+ git_diff_list_free(diff2);
+ git_tree_free(b_tree);
+
+ return error;
+}
+
+static int commit_worktree(
+ git_oid *w_commit_oid,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ git_commit *b_commit,
+ git_commit *u_commit)
+{
+ int error = 0;
+ git_tree *w_tree = NULL, *i_tree = NULL;
+ const git_commit *parents[] = { NULL, NULL, NULL };
+
+ parents[0] = b_commit;
+ parents[1] = i_commit;
+ parents[2] = u_commit;
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_index_read_tree(index, i_tree)) < 0)
+ goto cleanup;
+
+ if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
+ goto cleanup;
+
+ error = git_commit_create(
+ w_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ message,
+ w_tree,
+ u_commit ? 3 : 2,
+ parents);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_tree_free(w_tree);
+ return error;
+}
+
+static int prepare_worktree_commit_message(
+ git_buf* msg,
+ const char *user_message)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)
+ return error;
+
+ git_buf_clear(msg);
+
+ if (!user_message)
+ git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));
+ else {
+ const char *colon;
+
+ if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)
+ goto cleanup;
+
+ git_buf_puts(msg, "On ");
+ git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);
+ git_buf_printf(msg, ": %s\n", user_message);
+ }
+
+ error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;
+
+cleanup:
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int update_reflog(
+ git_oid *w_commit_oid,
+ git_repository *repo,
+ git_signature *stasher,
+ const char *message)
+{
+ git_reference *stash = NULL;
+ git_reflog *reflog = NULL;
+ int error;
+
+ if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_write(reflog)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int is_dirty_cb(const char *path, unsigned int status, void *payload)
+{
+ GIT_UNUSED(path);
+ GIT_UNUSED(status);
+ GIT_UNUSED(payload);
+
+ return 1;
+}
+
+static int ensure_there_are_changes_to_stash(
+ git_repository *repo,
+ bool include_untracked_files,
+ bool include_ignored_files)
+{
+ int error;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ if (include_untracked_files)
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ if (include_ignored_files)
+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED;
+
+ error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);
+
+ if (error == GIT_EUSER)
+ return 0;
+
+ if (!error)
+ return create_error(GIT_ENOTFOUND, "There is nothing to stash.");
+
+ return error;
+}
+
+static int reset_index_and_workdir(
+ git_repository *repo,
+ git_commit *commit,
+ bool remove_untracked)
+{
+ git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ if (remove_untracked)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ return git_checkout_tree(repo, (git_object *)commit, &opts);
+}
+
+int git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ git_signature *stasher,
+ const char *message,
+ uint32_t flags)
+{
+ git_index *index = NULL;
+ git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ assert(out && repo && stasher);
+
+ if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)
+ return error;
+
+ if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_there_are_changes_to_stash(
+ repo,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if ((error = commit_index(
+ &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
+ goto cleanup;
+
+ if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
+ (error = commit_untracked(
+ &u_commit, index, stasher, git_buf_cstr(&msg),
+ i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = prepare_worktree_commit_message(&msg, message)) < 0)
+ goto cleanup;
+
+ if ((error = commit_worktree(
+ out, index, stasher, git_buf_cstr(&msg),
+ i_commit, b_commit, u_commit)) < 0)
+ goto cleanup;
+
+ git_buf_rtrim(&msg);
+
+ if ((error = update_reflog(out, repo, stasher, git_buf_cstr(&msg))) < 0)
+ goto cleanup;
+
+ if ((error = reset_index_and_workdir(
+ repo,
+ ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0)) < 0)
+ goto cleanup;
+
+cleanup:
+
+ git_buf_free(&msg);
+ git_commit_free(i_commit);
+ git_commit_free(b_commit);
+ git_commit_free(u_commit);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_stash_foreach(
+ git_repository *repo,
+ git_stash_cb callback,
+ void *payload)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t i, max;
+ const git_reflog_entry *entry;
+
+ error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
+ if (error == GIT_ENOTFOUND)
+ return 0;
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ for (i = 0; i < max; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+
+ if (callback(i,
+ git_reflog_entry_message(entry),
+ git_reflog_entry_id_new(entry),
+ payload)) {
+ error = GIT_EUSER;
+ break;
+ }
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+int git_stash_drop(
+ git_repository *repo,
+ size_t index)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ size_t max;
+ int error;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ return error;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+
+ if (index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ if ((error = git_reflog_drop(reflog, index, true)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_write(reflog)) < 0)
+ goto cleanup;
+
+ if (max == 1) {
+ error = git_reference_delete(stash);
+ git_reference_free(stash);
+ stash = NULL;
+ } else if (index == 0) {
+ const git_reflog_entry *entry;
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+
+ git_reference_free(stash);
+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1);
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
diff --git a/src/status.c b/src/status.c
index e9ad3cfe4..ac6b4379b 100644
--- a/src/status.c
+++ b/src/status.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -17,6 +17,7 @@
#include "git2/diff.h"
#include "diff.h"
+#include "diff_output.h"
static unsigned int index_delta2status(git_delta_t index_status)
{
@@ -25,7 +26,6 @@ static unsigned int index_delta2status(git_delta_t index_status)
switch (index_status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_COPIED:
- case GIT_DELTA_RENAMED:
st = GIT_STATUS_INDEX_NEW;
break;
case GIT_DELTA_DELETED:
@@ -34,6 +34,12 @@ static unsigned int index_delta2status(git_delta_t index_status)
case GIT_DELTA_MODIFIED:
st = GIT_STATUS_INDEX_MODIFIED;
break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_INDEX_RENAMED;
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_INDEX_TYPECHANGE;
+ break;
default:
break;
}
@@ -47,8 +53,8 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
switch (workdir_status) {
case GIT_DELTA_ADDED:
- case GIT_DELTA_COPIED:
case GIT_DELTA_RENAMED:
+ case GIT_DELTA_COPIED:
case GIT_DELTA_UNTRACKED:
st = GIT_STATUS_WT_NEW;
break;
@@ -61,6 +67,9 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
case GIT_DELTA_IGNORED:
st = GIT_STATUS_IGNORED;
break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_WT_TYPECHANGE;
+ break;
default:
break;
}
@@ -68,29 +77,76 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
return st;
}
+typedef struct {
+ git_status_cb cb;
+ void *payload;
+ const git_status_options *opts;
+} status_user_callback;
+
+static int status_invoke_cb(
+ git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
+{
+ status_user_callback *usercb = payload;
+ const char *path = NULL;
+ unsigned int status = 0;
+
+ if (i2w) {
+ path = i2w->old_file.path;
+ status |= workdir_delta2status(i2w->status);
+ }
+ if (h2i) {
+ path = h2i->old_file.path;
+ status |= index_delta2status(h2i->status);
+ }
+
+ /* if excluding submodules and this is a submodule everywhere */
+ if (usercb->opts &&
+ (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ {
+ bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED);
+ bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED);
+ bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED);
+
+ if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) &&
+ (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) &&
+ (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT))
+ return 0;
+ }
+
+ return usercb->cb(path, status, usercb->payload);
+}
+
int git_status_foreach_ext(
git_repository *repo,
const git_status_options *opts,
- int (*cb)(const char *, unsigned int, void *),
- void *cbdata)
+ git_status_cb cb,
+ void *payload)
{
- int err = 0, cmp;
- git_diff_options diffopt;
- git_diff_list *idx2head = NULL, *wd2idx = NULL;
+ int err = 0;
+ git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
+ git_diff_list *head2idx = NULL, *idx2wd = NULL;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- git_diff_delta *i2h, *w2i;
- unsigned int i, j, i_max, j_max;
+ status_user_callback usercb;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
- if ((err = git_repository_head_tree(&head, repo)) < 0)
+ GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
+
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
+ (err = git_repository__ensure_not_bare(repo, "status")) < 0)
return err;
- memset(&diffopt, 0, sizeof(diffopt));
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ if (((err = git_repository_head_tree(&head, repo)) < 0) &&
+ !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))
+ return err;
+
memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
+
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
@@ -99,62 +155,58 @@ int git_status_foreach_ext(
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
- /* TODO: support EXCLUDE_SUBMODULES flag */
+ if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
+ if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+
+ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
+ if (err < 0)
+ goto cleanup;
+ }
- if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
- (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)
- goto cleanup;
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
+ err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
+ if (err < 0)
+ goto cleanup;
+ }
- if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0)
- goto cleanup;
+ usercb.cb = cb;
+ usercb.payload = payload;
+ usercb.opts = opts;
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
- for (i = 0; !err && i < idx2head->deltas.length; i++) {
- i2h = GIT_VECTOR_GET(&idx2head->deltas, i);
- err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata);
- }
- git_diff_list_free(idx2head);
- idx2head = NULL;
- }
+ if ((err = git_diff__paired_foreach(
+ head2idx, NULL, status_invoke_cb, &usercb)) < 0)
+ goto cleanup;
- i_max = idx2head ? idx2head->deltas.length : 0;
- j_max = wd2idx ? wd2idx->deltas.length : 0;
-
- for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) {
- i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
- w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
-
- cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path);
-
- if (cmp < 0) {
- err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata);
- i++;
- } else if (cmp > 0) {
- err = cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata);
- j++;
- } else {
- err = cb(i2h->old_file.path, index_delta2status(i2h->status) |
- workdir_delta2status(w2i->status), cbdata);
- i++; j++;
- }
+ git_diff_list_free(head2idx);
+ head2idx = NULL;
}
+ err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
+
cleanup:
git_tree_free(head);
- git_diff_list_free(idx2head);
- git_diff_list_free(wd2idx);
+ git_diff_list_free(head2idx);
+ git_diff_list_free(idx2wd);
+
+ if (err == GIT_EUSER)
+ giterr_clear();
+
return err;
}
int git_status_foreach(
git_repository *repo,
- int (*callback)(const char *, unsigned int, void *),
+ git_status_cb callback,
void *payload)
{
- git_status_options opts;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
- memset(&opts, 0, sizeof(opts));
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
@@ -164,22 +216,29 @@ int git_status_foreach(
}
struct status_file_info {
+ char *expected;
unsigned int count;
unsigned int status;
- char *expected;
+ int fnm_flags;
+ int ambiguous;
};
static int get_one_status(const char *path, unsigned int status, void *data)
{
struct status_file_info *sfi = data;
+ int (*strcomp)(const char *a, const char *b);
sfi->count++;
sfi->status = status;
- if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) {
- giterr_set(GITERR_INVALID,
- "Ambiguous path '%s' given to git_status_file", sfi->expected);
- return -1;
+ strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;
+
+ if (sfi->count > 1 ||
+ (strcomp(sfi->expected, path) != 0 &&
+ p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
+ {
+ sfi->ambiguous = true;
+ return GIT_EAMBIGUOUS;
}
return 0;
@@ -191,16 +250,20 @@ int git_status_file(
const char *path)
{
int error;
- git_status_options opts;
- struct status_file_info sfi;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_file_info sfi = {0};
+ git_index *index;
assert(status_flags && repo && path);
- memset(&sfi, 0, sizeof(sfi));
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
+
if ((sfi.expected = git__strdup(path)) == NULL)
return -1;
+ if (index->ignore_case)
+ sfi.fnm_flags = FNM_CASEFOLD;
- memset(&opts, 0, sizeof(opts));
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
@@ -211,10 +274,29 @@ int git_status_file(
error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
- if (!error && !sfi.count) {
+ if (error < 0 && sfi.ambiguous) {
giterr_set(GITERR_INVALID,
- "Attempt to get status of nonexistent file '%s'", path);
- error = GIT_ENOTFOUND;
+ "Ambiguous path '%s' given to git_status_file", sfi.expected);
+ error = GIT_EAMBIGUOUS;
+ }
+
+ if (!error && !sfi.count) {
+ git_buf full = GIT_BUF_INIT;
+
+ /* if the file actually exists and we still did not get a callback
+ * for it, then it must be contained inside an ignored directory, so
+ * mark it as such instead of generating an error.
+ */
+ if (!git_buf_joinpath(&full, git_repository_workdir(repo), path) &&
+ git_path_exists(full.ptr))
+ sfi.status = GIT_STATUS_IGNORED;
+ else {
+ giterr_set(GITERR_INVALID,
+ "Attempt to get status of nonexistent file '%s'", path);
+ error = GIT_ENOTFOUND;
+ }
+
+ git_buf_free(&full);
}
*status_flags = sfi.status;
@@ -229,14 +311,6 @@ int git_status_should_ignore(
git_repository *repo,
const char *path)
{
- int error;
- git_ignores ignores;
-
- if (git_ignore__for_path(repo, path, &ignores) < 0)
- return -1;
-
- error = git_ignore__lookup(&ignores, path, ignored);
- git_ignore__free(&ignores);
- return error;
+ return git_ignore_path_is_ignored(ignored, repo, path);
}
diff --git a/src/strmap.h b/src/strmap.h
index da5ca0dba..44176a0fc 100644
--- a/src/strmap.h
+++ b/src/strmap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * 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.
@@ -19,7 +19,7 @@ __KHASH_TYPE(str, const char *, void *);
typedef khash_t(str) git_strmap;
#define GIT__USE_STRMAP \
- __KHASH_IMPL(str, static inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
+ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
#define git_strmap_alloc() kh_init(str)
#define git_strmap_free(h) kh_destroy(str, h), h = NULL
diff --git a/src/submodule.c b/src/submodule.c
index 3c07e657d..2fdaf2f77 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 the libgit2 contributors
+ * 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.
@@ -12,64 +12,867 @@
#include "git2/index.h"
#include "git2/submodule.h"
#include "buffer.h"
+#include "buf_text.h"
#include "vector.h"
#include "posix.h"
#include "config_file.h"
#include "config.h"
#include "repository.h"
+#include "submodule.h"
+#include "tree.h"
+#include "iterator.h"
+
+#define GIT_MODULES_FILE ".gitmodules"
static git_cvar_map _sm_update_map[] = {
{GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
{GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
- {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}
+ {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
};
static git_cvar_map _sm_ignore_map[] = {
- {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
- {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
- {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}
+ {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
+ {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
};
-static inline khint_t str_hash_no_trailing_slash(const char *s)
+static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
{
khint_t h;
for (h = 0; *s; ++s)
- if (s[1] || *s != '/')
+ if (s[1] != '\0' || *s != '/')
h = (h << 5) - h + *s;
return h;
}
-static inline int str_equal_no_trailing_slash(const char *a, const char *b)
+static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
{
size_t alen = a ? strlen(a) : 0;
size_t blen = b ? strlen(b) : 0;
- if (alen && a[alen] == '/')
+ if (alen > 0 && a[alen - 1] == '/')
alen--;
- if (blen && b[blen] == '/')
+ if (blen > 0 && b[blen - 1] == '/')
blen--;
return (alen == blen && strncmp(a, b, alen) == 0);
}
-__KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash);
+__KHASH_IMPL(
+ str, static kh_inline, const char *, void *, 1,
+ str_hash_no_trailing_slash, str_equal_no_trailing_slash);
+
+static int load_submodule_config(git_repository *repo);
+static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *);
+static int lookup_head_remote(git_buf *url, git_repository *repo);
+static int submodule_get(git_submodule **, git_repository *, const char *, const char *);
+static void submodule_release(git_submodule *sm, int decr);
+static int submodule_load_from_index(git_repository *, const git_index_entry *);
+static int submodule_load_from_head(git_repository*, const char*, const git_oid*);
+static int submodule_load_from_config(const git_config_entry *, void *);
+static int submodule_load_from_wd_lite(git_submodule *, const char *, void *);
+static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
+static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);
+static int submodule_index_status(unsigned int *status, git_submodule *sm);
+static int submodule_wd_status(unsigned int *status, git_submodule *sm);
-static git_submodule *submodule_alloc(const char *name)
+static int submodule_cmp(const void *a, const void *b)
{
- git_submodule *sm = git__calloc(1, sizeof(git_submodule));
- if (sm == NULL)
- return sm;
+ return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+}
- sm->path = sm->name = git__strdup(name);
- if (!sm->name) {
- git__free(sm);
+static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
+{
+ ssize_t idx = git_buf_rfind(key, '.');
+ git_buf_truncate(key, (size_t)(idx + 1));
+ return git_buf_puts(key, suffix);
+}
+
+/*
+ * PUBLIC APIS
+ */
+
+int git_submodule_lookup(
+ git_submodule **sm_ptr, /* NULL if user only wants to test existence */
+ git_repository *repo,
+ const char *name) /* trailing slash is allowed */
+{
+ int error;
+ khiter_t pos;
+
+ assert(repo && name);
+
+ if ((error = load_submodule_config(repo)) < 0)
+ return error;
+
+ pos = git_strmap_lookup_index(repo->submodules, name);
+
+ if (!git_strmap_valid_index(repo->submodules, pos)) {
+ error = GIT_ENOTFOUND;
+
+ /* check if a plausible submodule exists at path */
+ if (git_repository_workdir(repo)) {
+ git_buf path = GIT_BUF_INIT;
+
+ if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0)
+ return -1;
+
+ if (git_path_contains_dir(&path, DOT_GIT))
+ error = GIT_EEXISTS;
+
+ git_buf_free(&path);
+ }
+
+ return error;
+ }
+
+ if (sm_ptr)
+ *sm_ptr = git_strmap_value_at(repo->submodules, pos);
+
+ return 0;
+}
+
+int git_submodule_foreach(
+ git_repository *repo,
+ int (*callback)(git_submodule *sm, const char *name, void *payload),
+ void *payload)
+{
+ int error;
+ git_submodule *sm;
+ git_vector seen = GIT_VECTOR_INIT;
+ seen._cmp = submodule_cmp;
+
+ assert(repo && callback);
+
+ if ((error = load_submodule_config(repo)) < 0)
+ return error;
+
+ git_strmap_foreach_value(repo->submodules, sm, {
+ /* Usually the following will not come into play - it just prevents
+ * us from issuing a callback twice for a submodule where the name
+ * and path are not the same.
+ */
+ if (sm->refcount > 1) {
+ if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND)
+ continue;
+ if ((error = git_vector_insert(&seen, sm)) < 0)
+ break;
+ }
+
+ if (callback(sm, sm->name, payload)) {
+ giterr_clear();
+ error = GIT_EUSER;
+ break;
+ }
+ });
+
+ git_vector_free(&seen);
+
+ return error;
+}
+
+void git_submodule_config_free(git_repository *repo)
+{
+ git_strmap *smcfg;
+ git_submodule *sm;
+
+ assert(repo);
+
+ smcfg = repo->submodules;
+ repo->submodules = NULL;
+
+ if (smcfg == NULL)
+ return;
+
+ git_strmap_foreach_value(smcfg, sm, {
+ submodule_release(sm,1);
+ });
+ git_strmap_free(smcfg);
+}
+
+int git_submodule_add_setup(
+ git_submodule **submodule,
+ git_repository *repo,
+ const char *url,
+ const char *path,
+ int use_gitlink)
+{
+ int error = 0;
+ git_config_backend *mods = NULL;
+ git_submodule *sm;
+ git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ git_repository *subrepo = NULL;
+
+ assert(repo && url && path);
+
+ /* see if there is already an entry for this submodule */
+
+ if (git_submodule_lookup(&sm, repo, path) < 0)
+ giterr_clear();
+ else {
+ giterr_set(GITERR_SUBMODULE,
+ "Attempt to add a submodule that already exists");
+ return GIT_EEXISTS;
+ }
+
+ /* resolve parameters */
+
+ if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {
+ if (!(error = lookup_head_remote(&real_url, repo)))
+ error = git_path_apply_relative(&real_url, url);
+ } else if (strchr(url, ':') != NULL || url[0] == '/') {
+ error = git_buf_sets(&real_url, url);
+ } else {
+ giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
+ error = -1;
+ }
+ if (error)
+ goto cleanup;
+
+ /* validate and normalize path */
+
+ if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
+ path += strlen(git_repository_workdir(repo));
+
+ if (git_path_root(path) >= 0) {
+ giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");
+ error = -1;
+ goto cleanup;
+ }
+
+ /* update .gitmodules */
+
+ if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {
+ giterr_set(GITERR_SUBMODULE,
+ "Adding submodules to a bare repository is not supported (for now)");
+ return -1;
+ }
+
+ if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, path)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
+ goto cleanup;
+
+ git_buf_clear(&name);
+
+ /* init submodule repository and add origin remote as needed */
+
+ error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
+ * gitlink in the sub-repo workdir directory to that repository
+ *
+ * Old style: sub-repo goes directly into repo/<name>/.git/
+ */
+
+ initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
+ GIT_REPOSITORY_INIT_NO_REINIT;
+ initopt.origin_url = real_url.ptr;
+
+ if (git_path_exists(name.ptr) &&
+ git_path_contains(&name, DOT_GIT))
+ {
+ /* repo appears to already exist - reinit? */
+ }
+ else if (use_gitlink) {
+ git_buf repodir = GIT_BUF_INIT;
+
+ error = git_buf_join_n(
+ &repodir, '/', 3, git_repository_path(repo), "modules", path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.workdir_path = name.ptr;
+ initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
+
+ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+
+ git_buf_free(&repodir);
+ }
+ else {
+ error = git_repository_init_ext(&subrepo, name.ptr, &initopt);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ /* add submodule to hash and "reload" it */
+
+ if (!(error = submodule_get(&sm, repo, path, NULL)) &&
+ !(error = git_submodule_reload(sm)))
+ error = git_submodule_init(sm, false);
+
+cleanup:
+ if (submodule != NULL)
+ *submodule = !error ? sm : NULL;
+
+ if (mods != NULL)
+ git_config_file_free(mods);
+ git_repository_free(subrepo);
+ git_buf_free(&real_url);
+ git_buf_free(&name);
+
+ return error;
+}
+
+int git_submodule_add_finalize(git_submodule *sm)
+{
+ int error;
+ git_index *index;
+
+ assert(sm);
+
+ if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
+ (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
+ return error;
+
+ return git_submodule_add_to_index(sm, true);
+}
+
+int git_submodule_add_to_index(git_submodule *sm, int write_index)
+{
+ int error;
+ git_repository *repo, *sm_repo = NULL;
+ git_index *index;
+ git_buf path = GIT_BUF_INIT;
+ git_commit *head;
+ git_index_entry entry;
+ struct stat st;
+
+ assert(sm);
+
+ repo = sm->owner;
+
+ /* force reload of wd OID by git_submodule_open */
+ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
+
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
+ (error = git_buf_joinpath(
+ &path, git_repository_workdir(repo), sm->path)) < 0 ||
+ (error = git_submodule_open(&sm_repo, sm)) < 0)
+ goto cleanup;
+
+ /* read stat information for submodule working directory */
+ if (p_stat(path.ptr, &st) < 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without working directory");
+ error = -1;
+ goto cleanup;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = sm->path;
+ git_index_entry__init_from_stat(&entry, &st);
+
+ /* calling git_submodule_open will have set sm->wd_oid if possible */
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without HEAD to index");
+ error = -1;
+ goto cleanup;
+ }
+ git_oid_cpy(&entry.oid, &sm->wd_oid);
+
+ if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
+ goto cleanup;
+
+ entry.ctime.seconds = git_commit_time(head);
+ entry.ctime.nanoseconds = 0;
+ entry.mtime.seconds = git_commit_time(head);
+ entry.mtime.nanoseconds = 0;
+
+ git_commit_free(head);
+
+ /* add it */
+ error = git_index_add(index, &entry);
+
+ /* write it, if requested */
+ if (!error && write_index) {
+ error = git_index_write(index);
+
+ if (!error)
+ git_oid_cpy(&sm->index_oid, &sm->wd_oid);
+ }
+
+cleanup:
+ git_repository_free(sm_repo);
+ git_buf_free(&path);
+ return error;
+}
+
+int git_submodule_save(git_submodule *submodule)
+{
+ int error = 0;
+ git_config_backend *mods;
+ git_buf key = GIT_BUF_INIT;
+
+ assert(submodule);
+
+ mods = open_gitmodules(submodule->owner, true, NULL);
+ if (!mods) {
+ giterr_set(GITERR_SUBMODULE,
+ "Adding submodules to a bare repository is not supported (for now)");
+ return -1;
+ }
+
+ if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)
+ goto cleanup;
+
+ /* save values for path, url, update, ignore, fetchRecurseSubmodules */
+
+ if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 ||
+ (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 ||
+ (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0)
+ goto cleanup;
+
+ if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
+ submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
+ {
+ const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ NULL : _sm_update_map[submodule->update].str_match;
+ error = git_config_file_set_string(mods, key.ptr, val);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
+ submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT)
+ {
+ const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ?
+ NULL : _sm_ignore_map[submodule->ignore].str_match;
+ error = git_config_file_set_string(mods, key.ptr, val);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(
+ &key, "fetchRecurseSubmodules")) < 0 ||
+ (error = git_config_file_set_string(
+ mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0)
+ goto cleanup;
+
+ /* update internal defaults */
+
+ submodule->ignore_default = submodule->ignore;
+ submodule->update_default = submodule->update;
+ submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+
+cleanup:
+ if (mods != NULL)
+ git_config_file_free(mods);
+ git_buf_free(&key);
+
+ return error;
+}
+
+git_repository *git_submodule_owner(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->owner;
+}
+
+const char *git_submodule_name(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->name;
+}
+
+const char *git_submodule_path(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->path;
+}
+
+const char *git_submodule_url(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->url;
+}
+
+int git_submodule_set_url(git_submodule *submodule, const char *url)
+{
+ assert(submodule && url);
+
+ git__free(submodule->url);
+
+ submodule->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(submodule->url);
+
+ return 0;
+}
+
+const git_oid *git_submodule_index_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
+ return &submodule->index_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_head_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
+ return &submodule->head_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_wd_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
+ git_repository *subrepo;
+
+ /* calling submodule open grabs the HEAD OID if possible */
+ if (!git_submodule_open(&subrepo, submodule))
+ git_repository_free(subrepo);
+ else
+ giterr_clear();
+ }
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
+ return &submodule->wd_oid;
+ else
return NULL;
+}
+
+git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->ignore;
+}
+
+git_submodule_ignore_t git_submodule_set_ignore(
+ git_submodule *submodule, git_submodule_ignore_t ignore)
+{
+ git_submodule_ignore_t old;
+
+ assert(submodule);
+
+ if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)
+ ignore = submodule->ignore_default;
+
+ old = submodule->ignore;
+ submodule->ignore = ignore;
+ return old;
+}
+
+git_submodule_update_t git_submodule_update(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->update;
+}
+
+git_submodule_update_t git_submodule_set_update(
+ git_submodule *submodule, git_submodule_update_t update)
+{
+ git_submodule_update_t old;
+
+ assert(submodule);
+
+ if (update == GIT_SUBMODULE_UPDATE_DEFAULT)
+ update = submodule->update_default;
+
+ old = submodule->update;
+ submodule->update = update;
+ return old;
+}
+
+int git_submodule_fetch_recurse_submodules(
+ git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->fetch_recurse;
+}
+
+int git_submodule_set_fetch_recurse_submodules(
+ git_submodule *submodule,
+ int fetch_recurse_submodules)
+{
+ int old;
+
+ assert(submodule);
+
+ old = submodule->fetch_recurse;
+ submodule->fetch_recurse = (fetch_recurse_submodules != 0);
+ return old;
+}
+
+int git_submodule_init(git_submodule *submodule, int overwrite)
+{
+ int error;
+
+ /* write "submodule.NAME.url" */
+
+ if (!submodule->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", submodule->name);
+ return -1;
+ }
+
+ error = submodule_update_config(
+ submodule, "url", submodule->url, overwrite != 0, false);
+ if (error < 0)
+ return error;
+
+ /* write "submodule.NAME.update" if not default */
+
+ if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT)
+ error = submodule_update_config(
+ submodule, "update", NULL, (overwrite != 0), false);
+ else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
+ error = submodule_update_config(
+ submodule, "update",
+ _sm_update_map[submodule->update].str_match,
+ (overwrite != 0), false);
+
+ return error;
+}
+
+int git_submodule_sync(git_submodule *submodule)
+{
+ if (!submodule->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", submodule->name);
+ return -1;
+ }
+
+ /* copy URL over to config only if it already exists */
+
+ return submodule_update_config(
+ submodule, "url", submodule->url, true, true);
+}
+
+int git_submodule_open(
+ git_repository **subrepo,
+ git_submodule *submodule)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo;
+ const char *workdir;
+
+ assert(submodule && subrepo);
+
+ repo = submodule->owner;
+ workdir = git_repository_workdir(repo);
+
+ if (!workdir) {
+ giterr_set(GITERR_REPOSITORY,
+ "Cannot open submodule repository in a bare repo");
+ return GIT_ENOTFOUND;
+ }
+
+ if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) {
+ giterr_set(GITERR_REPOSITORY,
+ "Cannot open submodule repository that is not checked out");
+ return GIT_ENOTFOUND;
+ }
+
+ if (git_buf_joinpath(&path, workdir, submodule->path) < 0)
+ return -1;
+
+ error = git_repository_open(subrepo, path.ptr);
+
+ git_buf_free(&path);
+
+ /* if we have opened the submodule successfully, let's grab the HEAD OID */
+ if (!error) {
+ if (!git_reference_name_to_id(
+ &submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
+ submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
+ else
+ giterr_clear();
}
+ return error;
+}
+
+int git_submodule_reload_all(git_repository *repo)
+{
+ assert(repo);
+ git_submodule_config_free(repo);
+ return load_submodule_config(repo);
+}
+
+int git_submodule_reload(git_submodule *submodule)
+{
+ git_repository *repo;
+ git_index *index;
+ int error;
+ size_t pos;
+ git_tree *head;
+ git_config_backend *mods;
+
+ assert(submodule);
+
+ /* refresh index data */
+
+ repo = submodule->owner;
+ if (git_repository_index__weakptr(&index, repo) < 0)
+ return -1;
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
+
+ if (!git_index_find(&pos, index, submodule->path)) {
+ const git_index_entry *entry = git_index_get_byindex(index, pos);
+
+ if (S_ISGITLINK(entry->mode)) {
+ if ((error = submodule_load_from_index(repo, entry)) < 0)
+ return error;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
+ }
+ }
+
+ /* refresh HEAD tree data */
+
+ if (!(error = git_repository_head_tree(&head, repo))) {
+ git_tree_entry *te;
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
+
+ if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) {
+
+ if (S_ISGITLINK(te->attr)) {
+ error = submodule_load_from_head(repo, submodule->path, &te->oid);
+ } else {
+ submodule_mode_mismatch(
+ repo, submodule->path,
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
+ }
+
+ git_tree_entry_free(te);
+ }
+ else if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ git_tree_free(head);
+ }
+
+ if (error < 0)
+ return error;
+
+ /* refresh config data */
+
+ if ((mods = open_gitmodules(repo, false, NULL)) != NULL) {
+ git_buf path = GIT_BUF_INIT;
+
+ git_buf_sets(&path, "submodule\\.");
+ git_buf_text_puts_escape_regex(&path, submodule->name);
+ git_buf_puts(&path, ".*");
+
+ if (git_buf_oom(&path))
+ error = -1;
+ else
+ error = git_config_file_foreach_match(
+ mods, path.ptr, submodule_load_from_config, repo);
+
+ git_buf_free(&path);
+ git_config_file_free(mods);
+ }
+
+ if (error < 0)
+ return error;
+
+ /* refresh wd data */
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID);
+
+ error = submodule_load_from_wd_lite(submodule, submodule->path, NULL);
+
+ return error;
+}
+
+int git_submodule_status(
+ unsigned int *status,
+ git_submodule *submodule)
+{
+ int error = 0;
+ unsigned int status_val;
+
+ assert(status && submodule);
+
+ status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);
+
+ if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {
+ if (!(error = submodule_index_status(&status_val, submodule)))
+ error = submodule_wd_status(&status_val, submodule);
+ }
+
+ *status = status_val;
+
+ return error;
+}
+
+int git_submodule_location(
+ unsigned int *location_status,
+ git_submodule *submodule)
+{
+ assert(location_status && submodule);
+
+ *location_status = submodule->flags &
+ (GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD);
+
+ return 0;
+}
+
+
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+static git_submodule *submodule_alloc(git_repository *repo, const char *name)
+{
+ git_submodule *sm;
+
+ if (!name || !strlen(name)) {
+ giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
+ return NULL;
+ }
+
+ sm = git__calloc(1, sizeof(git_submodule));
+ if (sm == NULL)
+ goto fail;
+
+ sm->path = sm->name = git__strdup(name);
+ if (!sm->name)
+ goto fail;
+
+ sm->owner = repo;
+ sm->refcount = 1;
+
return sm;
+
+fail:
+ submodule_release(sm, 0);
+ return NULL;
}
static void submodule_release(git_submodule *sm, int decr)
@@ -80,70 +883,122 @@ static void submodule_release(git_submodule *sm, int decr)
sm->refcount -= decr;
if (sm->refcount == 0) {
- if (sm->name != sm->path)
+ if (sm->name != sm->path) {
git__free(sm->path);
+ sm->path = NULL;
+ }
+
git__free(sm->name);
+ sm->name = NULL;
+
git__free(sm->url);
+ sm->url = NULL;
+
+ sm->owner = NULL;
+
git__free(sm);
}
}
-static int submodule_from_entry(
- git_strmap *smcfg, git_index_entry *entry)
+static int submodule_get(
+ git_submodule **sm_ptr,
+ git_repository *repo,
+ const char *name,
+ const char *alternate)
{
- git_submodule *sm;
- void *old_sm;
+ git_strmap *smcfg = repo->submodules;
khiter_t pos;
+ git_submodule *sm;
int error;
- pos = git_strmap_lookup_index(smcfg, entry->path);
+ assert(repo && name);
- if (git_strmap_valid_index(smcfg, pos))
- sm = git_strmap_value_at(smcfg, pos);
- else
- sm = submodule_alloc(entry->path);
+ pos = git_strmap_lookup_index(smcfg, name);
- git_oid_cpy(&sm->oid, &entry->oid);
+ if (!git_strmap_valid_index(smcfg, pos) && alternate)
+ pos = git_strmap_lookup_index(smcfg, alternate);
- if (strcmp(sm->path, entry->path) != 0) {
- if (sm->path != sm->name) {
- git__free(sm->path);
- sm->path = sm->name;
+ if (!git_strmap_valid_index(smcfg, pos)) {
+ sm = submodule_alloc(repo, name);
+
+ /* insert value at name - if another thread beats us to it, then use
+ * their record and release our own.
+ */
+ pos = kh_put(str, smcfg, sm->name, &error);
+
+ if (error < 0) {
+ submodule_release(sm, 1);
+ sm = NULL;
+ } else if (error == 0) {
+ submodule_release(sm, 1);
+ sm = git_strmap_value_at(smcfg, pos);
+ } else {
+ git_strmap_set_value_at(smcfg, pos, sm);
}
- sm->path = git__strdup(entry->path);
- if (!sm->path)
- goto fail;
+ } else {
+ sm = git_strmap_value_at(smcfg, pos);
}
- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ *sm_ptr = sm;
+
+ return (sm != NULL) ? 0 : -1;
+}
- if (old_sm && ((git_submodule *)old_sm) != sm) {
- /* TODO: log warning about multiple entrys for same submodule path */
- submodule_release(old_sm, 1);
+static int submodule_load_from_index(
+ git_repository *repo, const git_index_entry *entry)
+{
+ git_submodule *sm;
+
+ if (submodule_get(&sm, repo, entry->path, NULL) < 0)
+ return -1;
+
+ if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+ return 0;
}
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;
+
+ git_oid_cpy(&sm->index_oid, &entry->oid);
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
+
return 0;
+}
-fail:
- submodule_release(sm, 0);
+static int submodule_load_from_head(
+ git_repository *repo, const char *path, const git_oid *oid)
+{
+ git_submodule *sm;
+
+ if (submodule_get(&sm, repo, path, NULL) < 0)
+ return -1;
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
+
+ git_oid_cpy(&sm->head_oid, oid);
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+
+ return 0;
+}
+
+static int submodule_config_error(const char *property, const char *value)
+{
+ giterr_set(GITERR_INVALID,
+ "Invalid value for submodule '%s' property: '%s'", property, value);
return -1;
}
-static int submodule_from_config(
- const char *key, const char *value, void *data)
+static int submodule_load_from_config(
+ const git_config_entry *entry, void *data)
{
- git_strmap *smcfg = data;
- const char *namestart;
- const char *property;
+ git_repository *repo = data;
+ git_strmap *smcfg = repo->submodules;
+ const char *namestart, *property, *alternate = NULL;
+ const char *key = entry->name, *value = entry->value;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
- void *old_sm = NULL;
bool is_path;
- khiter_t pos;
- int error;
+ int error = 0;
if (git__prefixcmp(key, "submodule.") != 0)
return 0;
@@ -153,235 +1008,504 @@ static int submodule_from_config(
if (property == NULL)
return 0;
property++;
- is_path = (strcmp(property, "path") == 0);
+ is_path = (strcasecmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1;
- pos = git_strmap_lookup_index(smcfg, name.ptr);
- if (!git_strmap_valid_index(smcfg, pos) && is_path)
- pos = git_strmap_lookup_index(smcfg, value);
- if (!git_strmap_valid_index(smcfg, pos))
- sm = submodule_alloc(name.ptr);
- else
- sm = git_strmap_value_at(smcfg, pos);
- if (!sm)
- goto fail;
+ if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) {
+ git_buf_free(&name);
+ return -1;
+ }
- if (strcmp(sm->name, name.ptr) != 0) {
- assert(sm->path == sm->name);
- sm->name = git_buf_detach(&name);
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
- git_strmap_insert2(smcfg, sm->name, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ /* Only from config might we get differing names & paths. If so, then
+ * update the submodule and insert under the alternative key.
+ */
+
+ /* TODO: if case insensitive filesystem, then the following strcmps
+ * should be strcasecmp
+ */
+
+ if (strcmp(sm->name, name.ptr) != 0) {
+ alternate = sm->name = git_buf_detach(&name);
+ } else if (is_path && value && strcmp(sm->path, value) != 0) {
+ alternate = sm->path = git__strdup(value);
+ if (!sm->path)
+ error = -1;
}
- else if (is_path && strcmp(sm->path, value) != 0) {
- assert(sm->path == sm->name);
- sm->path = git__strdup(value);
- if (sm->path == NULL)
- goto fail;
+ if (alternate) {
+ void *old_sm = NULL;
+ git_strmap_insert2(smcfg, alternate, sm, old_sm, error);
- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ if (error >= 0)
+ sm->refcount++; /* inserted under a new key */
+
+ /* if we replaced an old module under this key, release the old one */
+ if (old_sm && ((git_submodule *)old_sm) != sm) {
+ submodule_release(old_sm, 1);
+ /* TODO: log warning about multiple submodules with same path */
+ }
}
+
git_buf_free(&name);
+ if (error < 0)
+ return error;
- if (old_sm && ((git_submodule *)old_sm) != sm) {
- /* TODO: log warning about multiple submodules with same path */
- submodule_release(old_sm, 1);
- }
+ /* TODO: Look up path in index and if it is present but not a GITLINK
+ * then this should be deleted (at least to match git's behavior)
+ */
if (is_path)
return 0;
/* copy other properties into submodule entry */
- if (strcmp(property, "url") == 0) {
- if (sm->url) {
- git__free(sm->url);
- sm->url = NULL;
- }
- if ((sm->url = git__strdup(value)) == NULL)
- goto fail;
+ if (strcasecmp(property, "url") == 0) {
+ git__free(sm->url);
+ sm->url = NULL;
+
+ if (value != NULL && (sm->url = git__strdup(value)) == NULL)
+ return -1;
}
- else if (strcmp(property, "update") == 0) {
+ else if (strcasecmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
- _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule update property: '%s'", value);
- goto fail;
- }
- sm->update = (git_submodule_update_t)val;
+ &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0)
+ return submodule_config_error("update", value);
+ sm->update_default = sm->update = (git_submodule_update_t)val;
}
- else if (strcmp(property, "fetchRecurseSubmodules") == 0) {
- if (git__parse_bool(&sm->fetch_recurse, value) < 0) {
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value);
- goto fail;
- }
+ else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
+ if (git__parse_bool(&sm->fetch_recurse, value) < 0)
+ return submodule_config_error("fetchRecurseSubmodules", value);
}
- else if (strcmp(property, "ignore") == 0) {
+ else if (strcasecmp(property, "ignore") == 0) {
int val;
if (git_config_lookup_map_value(
- _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule ignore property: '%s'", value);
- goto fail;
- }
- sm->ignore = (git_submodule_ignore_t)val;
+ &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0)
+ return submodule_config_error("ignore", value);
+ sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
return 0;
+}
-fail:
- submodule_release(sm, 0);
- git_buf_free(&name);
- return -1;
+static int submodule_load_from_wd_lite(
+ git_submodule *sm, const char *name, void *payload)
+{
+ git_repository *repo = git_submodule_owner(sm);
+ git_buf path = GIT_BUF_INIT;
+
+ GIT_UNUSED(name);
+ GIT_UNUSED(payload);
+
+ if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0)
+ return -1;
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (git_path_contains(&path, DOT_GIT))
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
+
+ git_buf_free(&path);
+
+ return 0;
}
-static int load_submodule_config(git_repository *repo)
+static void submodule_mode_mismatch(
+ git_repository *repo, const char *path, unsigned int flag)
+{
+ khiter_t pos = git_strmap_lookup_index(repo->submodules, path);
+
+ if (git_strmap_valid_index(repo->submodules, pos)) {
+ git_submodule *sm = git_strmap_value_at(repo->submodules, pos);
+
+ sm->flags |= flag;
+ }
+}
+
+static int load_submodule_config_from_index(
+ git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_index *index;
- unsigned int i, max_i;
- git_oid gitmodules_oid;
- git_strmap *smcfg;
- struct git_config_file *mods = NULL;
+ git_iterator *i;
+ const git_index_entry *entry;
- if (repo->submodules)
- return 0;
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
+ (error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0)
+ return error;
- /* submodule data is kept in a hashtable with each submodule stored
- * under both its name and its path. These are usually the same, but
- * that is not guaranteed.
- */
- smcfg = git_strmap_alloc();
- GITERR_CHECK_ALLOC(smcfg);
+ error = git_iterator_current(&entry, i);
- /* scan index for gitmodules (and .gitmodules entry) */
- if ((error = git_repository_index__weakptr(&index, repo)) < 0)
- goto cleanup;
- memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
- max_i = git_index_entrycount(index);
+ while (!error && entry != NULL) {
- for (i = 0; i < max_i; i++) {
- git_index_entry *entry = git_index_get(index, i);
if (S_ISGITLINK(entry->mode)) {
- if ((error = submodule_from_entry(smcfg, entry)) < 0)
- goto cleanup;
+ error = submodule_load_from_index(repo, entry);
+ if (error < 0)
+ break;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
+
+ if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
+ git_oid_cpy(gitmodules_oid, &entry->oid);
}
- else if (strcmp(entry->path, ".gitmodules") == 0)
- git_oid_cpy(&gitmodules_oid, &entry->oid);
+
+ error = git_iterator_advance(&entry, i);
}
- /* load .gitmodules from workdir if it exists */
- if (git_repository_workdir(repo) != NULL) {
- /* look in workdir for .gitmodules */
- git_buf path = GIT_BUF_INIT;
- if (!git_buf_joinpath(
- &path, git_repository_workdir(repo), ".gitmodules") &&
- git_path_isfile(path.ptr))
- {
- if (!(error = git_config_file__ondisk(&mods, path.ptr)))
- error = git_config_file_open(mods);
+ git_iterator_free(i);
+
+ return error;
+}
+
+static int load_submodule_config_from_head(
+ git_repository *repo, git_oid *gitmodules_oid)
+{
+ int error;
+ git_tree *head;
+ git_iterator *i;
+ const git_index_entry *entry;
+
+ if ((error = git_repository_head_tree(&head, repo)) < 0)
+ return error;
+
+ if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {
+ git_tree_free(head);
+ return error;
+ }
+
+ error = git_iterator_current(&entry, i);
+
+ while (!error && entry != NULL) {
+
+ if (S_ISGITLINK(entry->mode)) {
+ error = submodule_load_from_head(repo, entry->path, &entry->oid);
+ if (error < 0)
+ break;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
+
+ if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
+ git_oid_iszero(gitmodules_oid))
+ git_oid_cpy(gitmodules_oid, &entry->oid);
}
- git_buf_free(&path);
+
+ error = git_iterator_advance(&entry, i);
}
- /* load .gitmodules from object cache if not in workdir */
- if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) {
- /* TODO: is it worth loading gitmodules from object cache? */
+ git_iterator_free(i);
+ git_tree_free(head);
+
+ return error;
+}
+
+static git_config_backend *open_gitmodules(
+ git_repository *repo,
+ bool okay_to_create,
+ const git_oid *gitmodules_oid)
+{
+ const char *workdir = git_repository_workdir(repo);
+ git_buf path = GIT_BUF_INIT;
+ git_config_backend *mods = NULL;
+
+ if (workdir != NULL) {
+ if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
+ return NULL;
+
+ if (okay_to_create || git_path_isfile(path.ptr)) {
+ /* git_config_file__ondisk should only fail if OOM */
+ if (git_config_file__ondisk(&mods, path.ptr) < 0)
+ mods = NULL;
+ /* open should only fail here if the file is malformed */
+ else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ git_config_file_free(mods);
+ mods = NULL;
+ }
+ }
+ }
+
+ if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
+ /* TODO: Retrieve .gitmodules content from ODB */
+
+ /* Should we actually do this? Core git does not, but it means you
+ * can't really get much information about submodules on bare repos.
+ */
+ }
+
+ git_buf_free(&path);
+
+ return mods;
+}
+
+static int load_submodule_config(git_repository *repo)
+{
+ int error;
+ git_oid gitmodules_oid;
+ git_buf path = GIT_BUF_INIT;
+ git_config_backend *mods = NULL;
+
+ if (repo->submodules)
+ return 0;
+
+ memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
+
+ /* Submodule data is kept in a hashtable keyed by both name and path.
+ * These are usually the same, but that is not guaranteed.
+ */
+ if (!repo->submodules) {
+ repo->submodules = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(repo->submodules);
}
- /* process .gitmodules info */
- if (!error && mods != NULL)
- error = git_config_file_foreach(mods, submodule_from_config, smcfg);
+ /* add submodule information from index */
+
+ if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)
+ goto cleanup;
- /* store submodule config in repo */
- if (!error)
- repo->submodules = smcfg;
+ /* add submodule information from HEAD */
+
+ if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)
+ goto cleanup;
+
+ /* add submodule information from .gitmodules */
+
+ if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL)
+ error = git_config_file_foreach(mods, submodule_load_from_config, repo);
+
+ if (error != 0)
+ goto cleanup;
+
+ /* shallow scan submodules in work tree */
+
+ if (!git_repository_is_bare(repo))
+ error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);
cleanup:
+ git_buf_free(&path);
+
if (mods != NULL)
git_config_file_free(mods);
+
if (error)
- git_strmap_free(smcfg);
+ git_submodule_config_free(repo);
+
return error;
}
-void git_submodule_config_free(git_repository *repo)
+static int lookup_head_remote(git_buf *url, git_repository *repo)
{
- git_strmap *smcfg = repo->submodules;
- git_submodule *sm;
+ int error;
+ git_config *cfg;
+ git_reference *head = NULL, *remote = NULL;
+ const char *tgt, *scan;
+ git_buf key = GIT_BUF_INIT;
+
+ /* 1. resolve HEAD -> refs/heads/BRANCH
+ * 2. lookup config branch.BRANCH.remote -> ORIGIN
+ * 3. lookup remote.ORIGIN.url
+ */
- repo->submodules = NULL;
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
- if (smcfg == NULL)
- return;
+ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot resolve relative URL when HEAD cannot be resolved");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
- git_strmap_foreach_value(smcfg, sm, {
- submodule_release(sm,1);
- });
- git_strmap_free(smcfg);
-}
+ if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot resolve relative URL when HEAD is not symbolic");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
-static int submodule_cmp(const void *a, const void *b)
-{
- return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+ if ((error = git_branch_upstream(&remote, head)) < 0)
+ goto cleanup;
+
+ /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
+
+ if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||
+ git__prefixcmp(git_reference_symbolic_target(remote), GIT_REFS_REMOTES_DIR) != 0)
+ {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot resolve relative URL when HEAD is not symbolic");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ scan = tgt = git_reference_symbolic_target(remote) + strlen(GIT_REFS_REMOTES_DIR);
+ while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\')))
+ scan++; /* find non-escaped slash to end ORIGIN name */
+
+ error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)
+ goto cleanup;
+
+ error = git_buf_sets(url, tgt);
+
+cleanup:
+ git_buf_free(&key);
+ git_reference_free(head);
+ git_reference_free(remote);
+
+ return error;
}
-int git_submodule_foreach(
- git_repository *repo,
- int (*callback)(const char *name, void *payload),
- void *payload)
+static int submodule_update_config(
+ git_submodule *submodule,
+ const char *attr,
+ const char *value,
+ bool overwrite,
+ bool only_existing)
{
int error;
- git_submodule *sm;
- git_vector seen = GIT_VECTOR_INIT;
- seen._cmp = submodule_cmp;
+ git_config *config;
+ git_buf key = GIT_BUF_INIT;
+ const char *old = NULL;
- if ((error = load_submodule_config(repo)) < 0)
+ assert(submodule);
+
+ error = git_repository_config__weakptr(&config, submodule->owner);
+ if (error < 0)
return error;
- git_strmap_foreach_value(repo->submodules, sm, {
- /* usually the following will not come into play */
- if (sm->refcount > 1) {
- if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
- continue;
- if ((error = git_vector_insert(&seen, sm)) < 0)
- break;
- }
+ error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);
+ if (error < 0)
+ goto cleanup;
- if ((error = callback(sm->name, payload)) < 0)
- break;
- });
+ if (git_config_get_string(&old, config, key.ptr) < 0)
+ giterr_clear();
- git_vector_free(&seen);
+ if (!old && only_existing)
+ goto cleanup;
+ if (old && !overwrite)
+ goto cleanup;
+ if ((!old && !value) || (old && value && strcmp(old, value) == 0))
+ goto cleanup;
+
+ if (!value)
+ error = git_config_delete_entry(config, key.ptr);
+ else
+ error = git_config_set_string(config, key.ptr, value);
+cleanup:
+ git_buf_free(&key);
return error;
}
-int git_submodule_lookup(
- git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
- git_repository *repo,
- const char *name) /* trailing slash is allowed */
+static int submodule_index_status(unsigned int *status, git_submodule *sm)
{
- khiter_t pos;
+ const git_oid *head_oid = git_submodule_head_id(sm);
+ const git_oid *index_oid = git_submodule_index_id(sm);
- if (load_submodule_config(repo) < 0)
- return -1;
+ if (!head_oid) {
+ if (index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
+ }
+ else if (!index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
+ else if (!git_oid_equal(head_oid, index_oid))
+ *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
- pos = git_strmap_lookup_index(repo->submodules, name);
- if (!git_strmap_valid_index(repo->submodules, pos))
- return GIT_ENOTFOUND;
+ return 0;
+}
- if (sm_ptr)
- *sm_ptr = git_strmap_value_at(repo->submodules, pos);
+static int submodule_wd_status(unsigned int *status, git_submodule *sm)
+{
+ int error = 0;
+ const git_oid *wd_oid, *index_oid;
+ git_repository *sm_repo = NULL;
+
+ /* open repo now if we need it (so wd_id() call won't reopen) */
+ if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||
+ sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)
+ {
+ if ((error = git_submodule_open(&sm_repo, sm)) < 0)
+ return error;
+ }
- return 0;
+ index_oid = git_submodule_index_id(sm);
+ wd_oid = git_submodule_wd_id(sm);
+
+ if (!index_oid) {
+ if (wd_oid)
+ *status |= GIT_SUBMODULE_STATUS_WD_ADDED;
+ }
+ else if (!wd_oid) {
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
+ else
+ *status |= GIT_SUBMODULE_STATUS_WD_DELETED;
+ }
+ else if (!git_oid_equal(index_oid, wd_oid))
+ *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
+
+ if (sm_repo != NULL) {
+ git_tree *sm_head;
+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+ git_diff_list *diff;
+
+ /* the diffs below could be optimized with an early termination
+ * option to the git_diff functions, but for now this is sufficient
+ * (and certainly no worse that what core git does).
+ */
+
+ /* perform head-to-index diff on submodule */
+
+ if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)
+ return error;
+
+ if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt);
+
+ if (!error) {
+ if (git_diff_num_deltas(diff) > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
+
+ git_diff_list_free(diff);
+ diff = NULL;
+ }
+
+ git_tree_free(sm_head);
+
+ if (error < 0)
+ return error;
+
+ /* perform index-to-workdir diff on submodule */
+
+ error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt);
+
+ if (!error) {
+ size_t untracked =
+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
+
+ if (untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+
+ if (git_diff_num_deltas(diff) != untracked)
+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+
+ git_diff_list_free(diff);
+ diff = NULL;
+ }
+
+ git_repository_free(sm_repo);
+ }
+
+ return error;
}
diff --git a/src/submodule.h b/src/submodule.h
new file mode 100644
index 000000000..ba8e2518e
--- /dev/null
+++ b/src/submodule.h
@@ -0,0 +1,102 @@
+/*
+ * 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_submodule_h__
+#define INCLUDE_submodule_h__
+
+/* Notes:
+ *
+ * Submodule information can be in four places: the index, the config files
+ * (both .git/config and .gitmodules), the HEAD tree, and the working
+ * directory.
+ *
+ * In the index:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the HEAD tree:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the config files:
+ * - submodule is found by submodule "name" which is usually the path
+ * - may be missing or present
+ * - will have a name, path, url, and other properties
+ *
+ * In the working directory:
+ * - submodule is found by path
+ * - may be missing, an empty directory, a checked out directory,
+ * or of the wrong type
+ * - if checked out, will have a HEAD oid
+ * - if checked out, will have git history that can be used to compare oids
+ * - if checked out, may have modified files and/or untracked files
+ */
+
+/**
+ * Description of submodule
+ *
+ * This record describes a submodule found in a repository. There should be
+ * an entry for every submodule found in the HEAD and index, and for every
+ * submodule described in .gitmodules. The fields are as follows:
+ *
+ * - `owner` is the git_repository containing this submodule
+ * - `name` is the name of the submodule from .gitmodules.
+ * - `path` is the path to the submodule from the repo root. It is almost
+ * always the same as `name`.
+ * - `url` is the url for the submodule.
+ * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD.
+ * - `index_oid` is the SHA1 for the submodule recorded in the index.
+ * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule.
+ * - `update` is a git_submodule_update_t value - see gitmodules(5) update.
+ * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.
+ * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.
+ * - `refcount` tracks how many hashmap entries there are for this submodule.
+ * It only comes into play if the name and path of the submodule differ.
+ * - `flags` is for internal use, tracking where this submodule has been
+ * found (head, index, config, workdir) and other misc info about it.
+ *
+ * If the submodule has been added to .gitmodules but not yet git added,
+ * then the `index_oid` will be valid and zero. If the submodule has been
+ * deleted, but the delete has not been committed yet, then the `index_oid`
+ * will be set, but the `url` will be NULL.
+ */
+struct git_submodule {
+ git_repository *owner;
+ char *name;
+ char *path; /* important: may point to same string data as "name" */
+ char *url;
+ uint32_t flags;
+ git_oid head_oid;
+ git_oid index_oid;
+ git_oid wd_oid;
+ /* information from config */
+ git_submodule_update_t update;
+ git_submodule_update_t update_default;
+ git_submodule_ignore_t ignore;
+ git_submodule_ignore_t ignore_default;
+ int fetch_recurse;
+ /* internal information */
+ int refcount;
+};
+
+/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
+enum {
+ GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),
+ GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),
+ GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),
+ GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
+};
+
+#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
+ ((S) & ~(0xFFFFFFFFu << 20))
+
+#endif
diff --git a/src/tag.c b/src/tag.c
index 63424f530..735ba7e1d 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -22,41 +22,41 @@ void git_tag__free(git_tag *tag)
git__free(tag);
}
-const git_oid *git_tag_id(git_tag *c)
+const git_oid *git_tag_id(const git_tag *c)
{
- return git_object_id((git_object *)c);
+ return git_object_id((const git_object *)c);
}
-int git_tag_target(git_object **target, git_tag *t)
+int git_tag_target(git_object **target, const git_tag *t)
{
assert(t);
return git_object_lookup(target, t->object.repo, &t->target, t->type);
}
-const git_oid *git_tag_target_oid(git_tag *t)
+const git_oid *git_tag_target_id(const git_tag *t)
{
assert(t);
return &t->target;
}
-git_otype git_tag_type(git_tag *t)
+git_otype git_tag_target_type(const git_tag *t)
{
assert(t);
return t->type;
}
-const char *git_tag_name(git_tag *t)
+const char *git_tag_name(const git_tag *t)
{
assert(t);
return t->tag_name;
}
-const git_signature *git_tag_tagger(git_tag *t)
+const git_signature *git_tag_tagger(const git_tag *t)
{
return t->tagger;
}
-const char *git_tag_message(git_tag *t)
+const char *git_tag_message(const git_tag *t)
{
assert(t);
return t->message;
@@ -131,7 +131,7 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
buffer = search + 1;
tag->tagger = NULL;
- if (*buffer != '\n') {
+ if (buffer < buffer_end && *buffer != '\n') {
tag->tagger = git__malloc(sizeof(git_signature));
GITERR_CHECK_ALLOC(tag->tagger);
@@ -139,16 +139,19 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
return -1;
}
- if( *buffer != '\n' )
- return tag_error("No new line before message");
+ tag->message = NULL;
+ if (buffer < buffer_end) {
+ if( *buffer != '\n' )
+ return tag_error("No new line before message");
- text_len = buffer_end - ++buffer;
+ text_len = buffer_end - ++buffer;
- tag->message = git__malloc(text_len + 1);
- GITERR_CHECK_ALLOC(tag->message);
+ tag->message = git__malloc(text_len + 1);
+ GITERR_CHECK_ALLOC(tag->message);
- memcpy(tag->message, buffer, text_len);
- tag->message[text_len] = '\0';
+ memcpy(tag->message, buffer, text_len);
+ tag->message[text_len] = '\0';
+ }
return 0;
}
@@ -185,7 +188,7 @@ static int retrieve_tag_reference_oid(
if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
return -1;
- return git_reference_name_to_oid(oid, repo, ref_name_out->ptr);
+ return git_reference_name_to_id(oid, repo, ref_name_out->ptr);
}
static int write_tag_annotation(
@@ -196,7 +199,7 @@ static int write_tag_annotation(
const git_signature *tagger,
const char *message)
{
- git_buf tag = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT;
+ git_buf tag = GIT_BUF_INIT;
git_odb *odb;
git_oid__writebuf(&tag, "object ", git_object_id(target));
@@ -205,15 +208,9 @@ static int write_tag_annotation(
git_signature__writebuf(&tag, "tagger ", tagger);
git_buf_putc(&tag, '\n');
- /* Remove comments by default */
- if (git_message_prettify(&cleaned_message, message, 1) < 0)
- goto on_error;
-
- if (git_buf_puts(&tag, git_buf_cstr(&cleaned_message)) < 0)
+ if (git_buf_puts(&tag, message) < 0)
goto on_error;
- git_buf_free(&cleaned_message);
-
if (git_repository_odb__weakptr(&odb, repo) < 0)
goto on_error;
@@ -225,7 +222,6 @@ static int write_tag_annotation(
on_error:
git_buf_free(&tag);
- git_buf_free(&cleaned_message);
giterr_set(GITERR_OBJECT, "Failed to create tag annotation.");
return -1;
}
@@ -255,7 +251,7 @@ static int git_tag_create__internal(
error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name);
if (error < 0 && error != GIT_ENOTFOUND)
- return -1;
+ goto cleanup;
/** Ensure the tag name doesn't conflict with an already existing
* reference unless overwriting has explictly been requested **/
@@ -271,8 +267,9 @@ static int git_tag_create__internal(
} else
git_oid_cpy(oid, git_object_id(target));
- error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
+cleanup:
git_reference_free(new_ref);
git_buf_free(&ref_name);
return error;
@@ -362,7 +359,7 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu
return -1;
}
- error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);
git_reference_free(new_ref);
git_buf_free(&ref_name);
@@ -379,18 +376,21 @@ on_error:
int git_tag_delete(git_repository *repo, const char *tag_name)
{
- int error;
git_reference *tag_ref;
git_buf ref_name = GIT_BUF_INIT;
+ int error;
error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name);
git_buf_free(&ref_name);
if (error < 0)
- return -1;
+ return error;
- return git_reference_delete(tag_ref);
+ if ((error = git_reference_delete(tag_ref)) == 0)
+ git_reference_free(tag_ref);
+
+ return error;
}
int git_tag__parse(git_tag *tag, git_odb_object *obj)
@@ -400,22 +400,52 @@ int git_tag__parse(git_tag *tag, git_odb_object *obj)
}
typedef struct {
- git_vector *taglist;
- const char *pattern;
+ git_repository *repo;
+ git_tag_foreach_cb cb;
+ void *cb_data;
+} tag_cb_data;
+
+static int tags_cb(const char *ref, void *data)
+{
+ git_oid oid;
+ tag_cb_data *d = (tag_cb_data *)data;
+
+ if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0)
+ return 0; /* no tag */
+
+ if (git_reference_name_to_id(&oid, d->repo, ref) < 0)
+ return -1;
+
+ return d->cb(ref, &oid, d->cb_data);
+}
+
+int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)
+{
+ tag_cb_data data;
+
+ assert(repo && cb);
+
+ data.cb = cb;
+ data.cb_data = cb_data;
+ data.repo = repo;
+
+ return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data);
+}
+
+typedef struct {
+ git_vector *taglist;
+ const char *pattern;
} tag_filter_data;
#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR)
-static int tag_list_cb(const char *tag_name, void *payload)
+static int tag_list_cb(const char *tag_name, git_oid *oid, void *data)
{
- tag_filter_data *filter;
+ tag_filter_data *filter = (tag_filter_data *)data;
+ GIT_UNUSED(oid);
- if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0)
- return 0;
-
- filter = (tag_filter_data *)payload;
if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0)
- return git_vector_insert(filter->taglist, git__strdup(tag_name));
+ return git_vector_insert(filter->taglist, git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN));
return 0;
}
@@ -434,7 +464,7 @@ int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_reposit
filter.taglist = &taglist;
filter.pattern = pattern;
- error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter);
+ error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter);
if (error < 0) {
git_vector_free(&taglist);
return -1;
@@ -450,22 +480,7 @@ int git_tag_list(git_strarray *tag_names, git_repository *repo)
return git_tag_list_match(tag_names, "", repo);
}
-int git_tag_peel(git_object **tag_target, git_tag *tag)
+int git_tag_peel(git_object **tag_target, const git_tag *tag)
{
- int error;
- git_object *target;
-
- assert(tag_target && tag);
-
- if (git_tag_target(&target, tag) < 0)
- return -1;
-
- if (git_object_type(target) == GIT_OBJ_TAG) {
- error = git_tag_peel(tag_target, (git_tag *)target);
- git_object_free(target);
- return error;
- }
-
- *tag_target = target;
- return 0;
+ return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY);
}
diff --git a/src/tag.h b/src/tag.h
index 47f425509..c8e421ee6 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/thread-utils.c b/src/thread-utils.c
index 0ca01ef82..c3baf411a 100644
--- a/src/thread-utils.c
+++ b/src/thread-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/thread-utils.h b/src/thread-utils.h
index a309e93d1..2ca290adf 100644
--- a/src/thread-utils.h
+++ b/src/thread-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -38,13 +38,13 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
#define git_mutex_unlock(a) pthread_mutex_unlock(a)
#define git_mutex_free(a) pthread_mutex_destroy(a)
-/* Pthreads condition vars -- disabled by now */
-#define git_cond unsigned int //pthread_cond_t
-#define git_cond_init(c, a) (void)0 //pthread_cond_init(c, a)
-#define git_cond_free(c) (void)0 //pthread_cond_destroy(c)
-#define git_cond_wait(c, l) (void)0 //pthread_cond_wait(c, l)
-#define git_cond_signal(c) (void)0 //pthread_cond_signal(c)
-#define git_cond_broadcast(c) (void)0 //pthread_cond_broadcast(c)
+/* Pthreads condition vars */
+#define git_cond pthread_cond_t
+#define git_cond_init(c) pthread_cond_init(c, NULL)
+#define git_cond_free(c) pthread_cond_destroy(c)
+#define git_cond_wait(c, l) pthread_cond_wait(c, l)
+#define git_cond_signal(c) pthread_cond_signal(c)
+#define git_cond_broadcast(c) pthread_cond_broadcast(c)
GIT_INLINE(int) git_atomic_inc(git_atomic *a)
{
@@ -79,7 +79,7 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
/* Pthreads Mutex */
#define git_mutex unsigned int
#define git_mutex_init(a) (void)0
-#define git_mutex_lock(a) (void)0
+#define git_mutex_lock(a) 0
#define git_mutex_unlock(a) (void)0
#define git_mutex_free(a) (void)0
diff --git a/src/trace.c b/src/trace.c
new file mode 100644
index 000000000..159ac91cc
--- /dev/null
+++ b/src/trace.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "buffer.h"
+#include "common.h"
+#include "global.h"
+#include "trace.h"
+#include "git2/trace.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data git_trace__data = {0};
+
+#endif
+
+int git_trace_set(git_trace_level_t level, git_trace_callback callback)
+{
+#ifdef GIT_TRACE
+ assert(level == 0 || callback != NULL);
+
+ git_trace__data.level = level;
+ git_trace__data.callback = callback;
+ GIT_MEMORY_BARRIER;
+
+ return 0;
+#else
+ GIT_UNUSED(level);
+ GIT_UNUSED(callback);
+
+ giterr_set(GITERR_INVALID,
+ "This version of libgit2 was not built with tracing.");
+ return -1;
+#endif
+}
+
diff --git a/src/trace.h b/src/trace.h
new file mode 100644
index 000000000..f4bdff88a
--- /dev/null
+++ b/src/trace.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_trace_h__
+#define INCLUDE_trace_h__
+
+#include <stdarg.h>
+
+#include <git2/trace.h>
+#include "buffer.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data {
+ git_trace_level_t level;
+ git_trace_callback callback;
+};
+
+extern struct git_trace_data git_trace__data;
+
+GIT_INLINE(void) git_trace__write_fmt(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ git_trace_callback callback = git_trace__data.callback;
+ git_buf message = GIT_BUF_INIT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ git_buf_vprintf(&message, fmt, ap);
+ va_end(ap);
+
+ callback(level, git_buf_cstr(&message));
+
+ git_buf_free(&message);
+}
+
+#define git_trace_level() (git_trace__data.level)
+#define git_trace(l, ...) { \
+ if (git_trace__data.level >= l && \
+ git_trace__data.callback != NULL) { \
+ git_trace__write_fmt(l, __VA_ARGS__); \
+ } \
+ }
+
+#else
+
+#define git_trace_level() ((void)0)
+#define git_trace(lvl, ...) ((void)0)
+
+#endif
+
+#endif
diff --git a/src/transport.c b/src/transport.c
index 5b2cd7ea4..adb6d5355 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,76 +8,116 @@
#include "git2/types.h"
#include "git2/remote.h"
#include "git2/net.h"
-#include "transport.h"
+#include "git2/transport.h"
#include "path.h"
-static struct {
+typedef struct transport_definition {
char *prefix;
+ unsigned priority;
git_transport_cb fn;
-} transports[] = {
- {"git://", git_transport_git},
- {"http://", git_transport_http},
- {"https://", git_transport_dummy},
- {"file://", git_transport_local},
- {"git+ssh://", git_transport_dummy},
- {"ssh+git://", git_transport_dummy},
- {NULL, 0}
+ void *param;
+} transport_definition;
+
+static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
+
+static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
+static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
+
+static transport_definition transports[] = {
+ {"git://", 1, git_transport_smart, &git_subtransport_definition},
+ {"http://", 1, git_transport_smart, &http_subtransport_definition},
+ {"https://", 1, git_transport_smart, &http_subtransport_definition},
+ {"file://", 1, git_transport_local, NULL},
+ {"git+ssh://", 1, git_transport_dummy, NULL},
+ {"ssh+git://", 1, git_transport_dummy, NULL},
+ {NULL, 0, 0}
};
#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
-static git_transport_cb transport_find_fn(const char *url)
+static int transport_find_fn(const char *url, git_transport_cb *callback, void **param)
{
size_t i = 0;
+ unsigned priority = 0;
+ transport_definition *definition = NULL, *definition_iter;
// First, check to see if it's an obvious URL, which a URL scheme
for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
- if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix)))
- return transports[i].fn;
- }
+ definition_iter = &transports[i];
+
+ if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
+ continue;
- /* still here? Check to see if the path points to a file on the local file system */
- if ((git_path_exists(url) == 0) && git_path_isdir(url))
- return &git_transport_local;
+ if (definition_iter->priority > priority)
+ definition = definition_iter;
+ }
- /* It could be a SSH remote path. Check to see if there's a : */
- if (strrchr(url, ':'))
- return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */
+#ifdef GIT_WIN32
+ /* On Windows, it might not be possible to discern between absolute local
+ * and ssh paths - first check if this is a valid local path that points
+ * to a directory and if so assume local path, else assume SSH */
+
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+
+ /* It could be a SSH remote path. Check to see if there's a :
+ * SSH is an unsupported transport mechanism in this version of libgit2 */
+ if (!definition && strrchr(url, ':'))
+ definition = &dummy_transport_definition;
+#else
+ /* For other systems, perform the SSH check first, to avoid going to the
+ * filesystem if it is not necessary */
+
+ /* It could be a SSH remote path. Check to see if there's a :
+ * SSH is an unsupported transport mechanism in this version of libgit2 */
+ if (!definition && strrchr(url, ':'))
+ definition = &dummy_transport_definition;
+
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+#endif
+
+ if (!definition)
+ return -1;
- return NULL;
+ *callback = definition->fn;
+ *param = definition->param;
+
+ return 0;
}
/**************
* Public API *
**************/
-int git_transport_dummy(git_transport **transport)
+int git_transport_dummy(git_transport **transport, git_remote *owner, void *param)
{
GIT_UNUSED(transport);
+ GIT_UNUSED(owner);
+ GIT_UNUSED(param);
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
return -1;
}
-int git_transport_new(git_transport **out, const char *url)
+int git_transport_new(git_transport **out, git_remote *owner, const char *url)
{
git_transport_cb fn;
git_transport *transport;
+ void *param;
int error;
- fn = transport_find_fn(url);
-
- if (fn == NULL) {
+ if (transport_find_fn(url, &fn, &param) < 0) {
giterr_set(GITERR_NET, "Unsupported URL protocol");
return -1;
}
- error = fn(&transport);
+ error = fn(&transport, owner, param);
if (error < 0)
return error;
- transport->url = git__strdup(url);
- GITERR_CHECK_ALLOC(transport->url);
-
*out = transport;
return 0;
@@ -86,12 +126,19 @@ int git_transport_new(git_transport **out, const char *url)
/* from remote.h */
int git_remote_valid_url(const char *url)
{
- return transport_find_fn(url) != NULL;
+ git_transport_cb fn;
+ void *param;
+
+ return !transport_find_fn(url, &fn, &param);
}
int git_remote_supported_url(const char* url)
{
- git_transport_cb transport_fn = transport_find_fn(url);
+ git_transport_cb fn;
+ void *param;
+
+ if (transport_find_fn(url, &fn, &param) < 0)
+ return 0;
- return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy));
+ return fn != &git_transport_dummy;
}
diff --git a/src/transport.h b/src/transport.h
deleted file mode 100644
index 125df2745..000000000
--- a/src/transport.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * 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_transport_h__
-#define INCLUDE_transport_h__
-
-#include "git2/net.h"
-#include "git2/indexer.h"
-#include "vector.h"
-
-#define GIT_CAP_OFS_DELTA "ofs-delta"
-
-typedef struct git_transport_caps {
- int common:1,
- ofs_delta:1;
-} git_transport_caps;
-
-/*
- * A day in the life of a network operation
- * ========================================
- *
- * The library gets told to ls-remote/push/fetch on/to/from some
- * remote. We look at the URL of the remote and fill the function
- * table with whatever is appropriate (the remote may be git over git,
- * ssh or http(s). It may even be an hg or svn repository, the library
- * at this level doesn't care, it just calls the helpers.
- *
- * The first call is to ->connect() which connects to the remote,
- * making use of the direction if necessary. This function must also
- * store the remote heads and any other information it needs.
- *
- * The next useful step is to call ->ls() to get the list of
- * references available to the remote. These references may have been
- * collected on connect, or we may build them now. For ls-remote,
- * nothing else is needed other than closing the connection.
- * Otherwise, the higher leves decide which objects we want to
- * have. ->send_have() is used to tell the other end what we have. If
- * we do need to download a pack, ->download_pack() is called.
- *
- * When we're done, we call ->close() to close the
- * connection. ->free() takes care of freeing all the resources.
- */
-
-struct git_transport {
- /**
- * Where the repo lives
- */
- char *url;
- /**
- * Whether we want to push or fetch
- */
- int direction : 1, /* 0 fetch, 1 push */
- connected : 1;
- /**
- * Connect and store the remote heads
- */
- int (*connect)(struct git_transport *transport, int dir);
- /**
- * Give a list of references, useful for ls-remote
- */
- int (*ls)(struct git_transport *transport, git_headlist_cb list_cb, void *opaque);
- /**
- * Push the changes over
- */
- int (*push)(struct git_transport *transport);
- /**
- * Negotiate the minimal amount of objects that need to be
- * retrieved
- */
- int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants);
- /**
- * Download the packfile
- */
- int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);
- /**
- * Fetch the changes
- */
- int (*fetch)(struct git_transport *transport);
- /**
- * Close the connection
- */
- int (*close)(struct git_transport *transport);
- /**
- * Free the associated resources
- */
- void (*free)(struct git_transport *transport);
-};
-
-
-int git_transport_new(struct git_transport **transport, const char *url);
-int git_transport_local(struct git_transport **transport);
-int git_transport_git(struct git_transport **transport);
-int git_transport_http(struct git_transport **transport);
-int git_transport_dummy(struct git_transport **transport);
-
-/**
- Returns true if the passed URL is valid (a URL with a Git supported scheme,
- or pointing to an existing path)
-*/
-int git_transport_valid_url(const char *url);
-
-typedef struct git_transport git_transport;
-typedef int (*git_transport_cb)(git_transport **transport);
-
-#endif
diff --git a/src/transports/cred.c b/src/transports/cred.c
new file mode 100644
index 000000000..ecb026062
--- /dev/null
+++ b/src/transports/cred.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "smart.h"
+#include "git2/cred_helpers.h"
+
+static void plaintext_free(struct git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ size_t pass_len = strlen(c->password);
+
+ git__free(c->username);
+
+ /* Zero the memory which previously held the password */
+ memset(c->password, 0x0, pass_len);
+ git__free(c->password);
+
+ memset(c, 0, sizeof(*c));
+
+ git__free(c);
+}
+
+int git_cred_userpass_plaintext_new(
+ git_cred **cred,
+ const char *username,
+ const char *password)
+{
+ git_cred_userpass_plaintext *c;
+
+ if (!cred)
+ return -1;
+
+ c = git__malloc(sizeof(git_cred_userpass_plaintext));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ c->parent.free = plaintext_free;
+ c->username = git__strdup(username);
+
+ if (!c->username) {
+ git__free(c);
+ return -1;
+ }
+
+ c->password = git__strdup(password);
+
+ if (!c->password) {
+ git__free(c->username);
+ git__free(c);
+ return -1;
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c
new file mode 100644
index 000000000..d420e3e3c
--- /dev/null
+++ b/src/transports/cred_helpers.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/cred_helpers.h"
+
+int git_cred_userpass(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;
+ const char *effective_username = NULL;
+
+ GIT_UNUSED(url);
+
+ if (!userpass || !userpass->password) return -1;
+
+ /* Username resolution: a username can be passed with the URL, the
+ * credentials payload, or both. Here's what we do. Note that if we get
+ * this far, we know that any password the url may contain has already
+ * failed at least once, so we ignore it.
+ *
+ * | Payload | URL | Used |
+ * +-------------+----------+-----------+
+ * | yes | no | payload |
+ * | yes | yes | payload |
+ * | no | yes | url |
+ * | no | no | FAIL |
+ */
+ if (userpass->username)
+ effective_username = userpass->username;
+ else if (user_from_url)
+ effective_username = user_from_url;
+ else
+ return -1;
+
+ if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
+ git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/src/transports/git.c b/src/transports/git.c
index 5baa810f0..3a0b86345 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -1,50 +1,42 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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/net.h"
-#include "git2/common.h"
-#include "git2/types.h"
-#include "git2/errors.h"
-#include "git2/net.h"
-#include "git2/revwalk.h"
-
-#include "vector.h"
-#include "transport.h"
-#include "pkt.h"
-#include "common.h"
+#include "git2.h"
+#include "buffer.h"
#include "netops.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "fetch.h"
-#include "protocol.h"
+
+#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git[] = "git://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
typedef struct {
- git_transport parent;
- git_protocol proto;
- GIT_SOCKET socket;
- git_vector refs;
- git_remote_head **heads;
- git_transport_caps caps;
- char buff[1024];
- gitno_buffer buf;
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-} transport_git;
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} git_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+ git_stream *current_stream;
+} git_subtransport;
/*
- * Create a git procol request.
+ * Create a git protocol request.
*
* For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
*/
static int gen_proto(git_buf *request, const char *cmd, const char *url)
{
char *delim, *repo;
- char default_command[] = "git-upload-pack";
char host[] = "host=";
size_t len;
@@ -60,9 +52,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
if (delim == NULL)
delim = strchr(url, '/');
- if (cmd == NULL)
- cmd = default_command;
-
len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
git_buf_grow(request, len);
@@ -77,402 +66,287 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
return 0;
}
-static int send_request(GIT_SOCKET s, const char *cmd, const char *url)
+static int send_command(git_stream *s)
{
int error;
git_buf request = GIT_BUF_INIT;
- error = gen_proto(&request, cmd, url);
+ error = gen_proto(&request, s->cmd, s->url);
if (error < 0)
goto cleanup;
- error = gitno_send(s, request.ptr, request.size, 0);
+ /* It looks like negative values are errors here, and positive values
+ * are the number of bytes sent. */
+ error = gitno_send(&s->socket, request.ptr, request.size, 0);
+
+ if (error >= 0)
+ s->sent_command = 1;
cleanup:
git_buf_free(&request);
return error;
}
-/*
- * Parse the URL and connect to a server, storing the socket in
- * out. For convenience this also takes care of asking for the remote
- * refs
- */
-static int do_connect(transport_git *t, const char *url)
+static int git_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
{
- char *host, *port;
- const char prefix[] = "git://";
- int error;
-
- t->socket = INVALID_SOCKET;
+ git_stream *s = (git_stream *)stream;
+ gitno_buffer buf;
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
+ *bytes_read = 0;
- if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ if (!s->sent_command && send_command(s) < 0)
return -1;
- if ((error = gitno_connect(&t->socket, host, port)) == 0) {
- error = send_request(t->socket, NULL, url);
- }
-
- git__free(host);
- git__free(port);
-
- if (error < 0 && t->socket != INVALID_SOCKET) {
- gitno_close(t->socket);
- t->socket = INVALID_SOCKET;
- }
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
- if (t->socket == INVALID_SOCKET) {
- giterr_set(GITERR_NET, "Failed to connect to the host");
+ if (gitno_recv(&buf) < 0)
return -1;
- }
+
+ *bytes_read = buf.offset;
return 0;
}
-/*
- * Read from the socket and store the references in the vector
- */
-static int store_refs(transport_git *t)
+static int git_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- gitno_buffer *buf = &t->buf;
- int ret = 0;
-
- while (1) {
- if ((ret = gitno_recv(buf)) < 0)
- return -1;
- if (ret == 0) /* Orderly shutdown, so exit */
- return 0;
-
- ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset);
- if (ret == GIT_EBUFS) {
- gitno_consume_n(buf, buf->len);
- continue;
- }
-
- if (ret < 0)
- return ret;
-
- gitno_consume_n(buf, buf->offset);
-
- if (t->proto.flush) { /* No more refs */
- t->proto.flush = 0;
- return 0;
- }
- }
+ git_stream *s = (git_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ return gitno_send(&s->socket, buffer, len, 0);
}
-static int detect_caps(transport_git *t)
+static void git_stream_free(git_smart_subtransport_stream *stream)
{
- git_vector *refs = &t->refs;
- git_pkt_ref *pkt;
- git_transport_caps *caps = &t->caps;
- const char *ptr;
-
- pkt = git_vector_get(refs, 0);
- /* No refs or capabilites, odd but not a problem */
- if (pkt == NULL || pkt->capabilities == NULL)
- return 0;
+ git_stream *s = (git_stream *)stream;
+ git_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
- ptr = pkt->capabilities;
- while (ptr != NULL && *ptr != '\0') {
- if (*ptr == ' ')
- ptr++;
+ GIT_UNUSED(ret);
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
- caps->common = caps->ofs_delta = 1;
- ptr += strlen(GIT_CAP_OFS_DELTA);
- continue;
- }
+ t->current_stream = NULL;
- /* We don't know this capability, so skip it */
- ptr = strchr(ptr, ' ');
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
}
- return 0;
+ git__free(s->url);
+ git__free(s);
}
-/*
- * Since this is a network connection, we need to parse and store the
- * pkt-lines at this stage and keep them there.
- */
-static int git_connect(git_transport *transport, int direction)
+static int git_stream_alloc(
+ git_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
-
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over git:// is not supported");
- return -1;
- }
+ git_stream *s;
- t->parent.direction = direction;
- if (git_vector_init(&t->refs, 16, NULL) < 0)
+ if (!stream)
return -1;
- /* Connect and ask for the refs */
- if (do_connect(t, transport->url) < 0)
- goto cleanup;
+ s = git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
- t->parent.connected = 1;
- if (store_refs(t) < 0)
- goto cleanup;
+ s->cmd = cmd;
+ s->url = git__strdup(url);
- if (detect_caps(t) < 0)
- goto cleanup;
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+ *stream = &s->parent;
return 0;
-cleanup:
- git_vector_free(&t->refs);
- return -1;
}
-static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)
+static int _git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- git_pkt *p = NULL;
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
+
+ *stream = NULL;
- git_vector_foreach(refs, i, p) {
- git_pkt_ref *pkt = NULL;
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (p->type != GIT_PKT_REF)
- continue;
+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
- pkt = (git_pkt_ref *)p;
+ s = (git_stream *)*stream;
- if (list_cb(&pkt->head, opaque) < 0) {
- giterr_set(GITERR_NET, "User callback returned error");
- return -1;
- }
- }
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
}
-/* Wait until we get an ack from the */
-static int recv_pkt(gitno_buffer *buf)
+static int _git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- const char *ptr = buf->data, *line_end;
- git_pkt *pkt;
- int pkt_type, error;
-
- do {
- /* Wait for max. 1 second */
- if ((error = gitno_select_in(buf, 1, 0)) < 0) {
- return -1;
- } else if (error == 0) {
- /*
- * Some servers don't respond immediately, so if this
- * happens, we keep sending information until it
- * answers. Pretend we received a NAK to convince higher
- * layers to do so.
- */
- return GIT_PKT_NAK;
- }
-
- if ((error = gitno_recv(buf)) < 0)
- return -1;
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- if (error == GIT_EBUFS)
- continue;
- if (error < 0)
- return -1;
- } while (error);
-
- gitno_consume(buf, line_end);
- pkt_type = pkt->type;
- git__free(pkt);
-
- return pkt_type;
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
}
-static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_revwalk *walk;
- git_oid oid;
- int error;
- unsigned int i;
- git_buf data = GIT_BUF_INIT;
- gitno_buffer *buf = &t->buf;
+ char *host, *port, *user=NULL, *pass=NULL;
+ git_stream *s;
- if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0)
- return -1;
+ *stream = NULL;
- if (git_fetch_setup_walk(&walk, repo) < 0)
- goto on_error;
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
- goto on_error;
+ if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
+ return -1;
- git_buf_clear(&data);
- /*
- * We don't support any kind of ACK extensions, so the negotiation
- * boils down to sending what we have and listening for an ACK
- * every once in a while.
- */
- i = 0;
- while ((error = git_revwalk_next(&oid, walk)) == 0) {
- git_pkt_buffer_have(&oid, &data);
- i++;
- if (i % 20 == 0) {
- int pkt_type;
-
- git_pkt_buffer_flush(&data);
- if (git_buf_oom(&data))
- goto on_error;
-
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
- goto on_error;
-
- pkt_type = recv_pkt(buf);
-
- if (pkt_type == GIT_PKT_ACK) {
- break;
- } else if (pkt_type == GIT_PKT_NAK) {
- continue;
- } else {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- goto on_error;
- }
-
- }
- }
- if (error < 0 && error != GIT_REVWALKOVER)
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
- /* Tell the other end that we're done negotiating */
- git_buf_clear(&data);
- git_pkt_buffer_flush(&data);
- git_pkt_buffer_done(&data);
- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
goto on_error;
- git_buf_free(&data);
- git_revwalk_free(walk);
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
on_error:
- git_buf_free(&data);
- git_revwalk_free(walk);
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
return -1;
}
-static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)
+static int _git_receivepack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- int error = 0, read_bytes;
- gitno_buffer *buf = &t->buf;
- git_pkt *pkt;
- const char *line_end, *ptr;
-
- /*
- * For now, we ignore everything and wait for the pack
- */
- do {
- ptr = buf->data;
- /* Whilst we're searching for the pack */
- while (1) {
- if (buf->offset == 0) {
- break;
- }
-
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- if (error == GIT_EBUFS)
- break;
-
- if (error < 0)
- return error;
-
- if (pkt->type == GIT_PKT_PACK) {
- git__free(pkt);
- return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats);
- }
-
- /* For now we don't care about anything */
- git__free(pkt);
- gitno_consume(buf, line_end);
- }
-
- read_bytes = gitno_recv(buf);
- } while (read_bytes);
-
- return read_bytes;
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
}
-static int git_close(git_transport *transport)
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
{
- transport_git *t = (transport_git*) transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
- /* Can't do anything if there's an error, so don't bother checking */
- git_pkt_send_flush(t->socket);
- if (gitno_close(t->socket) < 0) {
- giterr_set(GITERR_NET, "Failed to close socket");
- return -1;
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return _git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
}
-#ifdef GIT_WIN32
- WSACleanup();
-#endif
+ *stream = NULL;
+ return -1;
+}
+
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
return 0;
}
-static void git_free(git_transport *transport)
+static void _git_free(git_smart_subtransport *subtransport)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
+ git_subtransport *t = (git_subtransport *) subtransport;
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
- }
+ assert(!t->current_stream);
- git_vector_free(refs);
- git__free(t->heads);
- git_buf_free(&t->proto.buf);
- git__free(t->parent.url);
git__free(t);
}
-int git_transport_git(git_transport **out)
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
{
- transport_git *t;
-#ifdef GIT_WIN32
- int ret;
-#endif
-
- t = git__malloc(sizeof(transport_git));
- GITERR_CHECK_ALLOC(t);
-
- memset(t, 0x0, sizeof(transport_git));
+ git_subtransport *t;
- t->parent.connect = git_connect;
- t->parent.ls = git_ls;
- t->parent.negotiate_fetch = git_negotiate_fetch;
- t->parent.download_pack = git_download_pack;
- t->parent.close = git_close;
- t->parent.free = git_free;
- t->proto.refs = &t->refs;
- t->proto.transport = (git_transport *) t;
+ if (!out)
+ return -1;
- *out = (git_transport *) t;
+ t = git__calloc(sizeof(git_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
-#ifdef GIT_WIN32
- ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
- if (ret != 0) {
- git_free(*out);
- giterr_set(GITERR_NET, "Winsock init failed");
- return -1;
- }
-#endif
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.close = _git_close;
+ t->parent.free = _git_free;
+ *out = (git_smart_subtransport *) t;
return 0;
}
diff --git a/src/transports/http.c b/src/transports/http.c
index 2a8ebbb09..eca06ead2 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -1,25 +1,35 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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 GIT_WINHTTP
-#include <stdlib.h>
#include "git2.h"
#include "http_parser.h"
-
-#include "transport.h"
-#include "common.h"
-#include "netops.h"
#include "buffer.h"
-#include "pkt.h"
-#include "refs.h"
-#include "pack.h"
-#include "fetch.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "protocol.h"
+#include "netops.h"
+#include "smart.h"
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const char *get_verb = "GET";
+static const char *post_verb = "POST";
+static const char *basic_authtype = "Basic";
+
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
+
+#define PARSE_ERROR_GENERIC -1
+#define PARSE_ERROR_REPLAY -2
+
+#define CHUNK_SIZE 4096
enum last_cb {
NONE,
@@ -27,54 +37,132 @@ enum last_cb {
VALUE
};
+typedef enum {
+ GIT_HTTP_AUTH_BASIC = 1,
+} http_authmechanism_t;
+
typedef struct {
- git_transport parent;
- git_protocol proto;
- git_vector refs;
- git_vector common;
- GIT_SOCKET socket;
- git_buf buf;
- git_remote_head **heads;
- int error;
- int transfer_finished :1,
- ct_found :1,
- ct_finished :1,
- pack_ready :1;
- enum last_cb last_cb;
- http_parser parser;
- char *content_type;
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ char *redirect_url;
+ const char *verb;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1,
+ redirect_count : 3;
+} http_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_socket socket;
+ const char *path;
char *host;
char *port;
- char *service;
- git_transport_caps caps;
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-} transport_http;
-
-static int gen_request(git_buf *buf, const char *url, const char *host, const char *op,
- const char *service, ssize_t content_length, int ls)
+ char *user_from_url;
+ char *pass_from_url;
+ git_cred *cred;
+ git_cred *url_cred;
+ http_authmechanism_t auth_mechanism;
+ unsigned connected : 1,
+ use_ssl : 1;
+
+ /* Parser structures */
+ http_parser parser;
+ http_parser_settings settings;
+ gitno_buffer parse_buffer;
+ git_buf parse_header_name;
+ git_buf parse_header_value;
+ char parse_buffer_data[2048];
+ char *content_type;
+ char *location;
+ git_vector www_authenticate;
+ enum last_cb last_cb;
+ int parse_error;
+ unsigned parse_finished : 1;
+} http_subtransport;
+
+typedef struct {
+ http_stream *s;
+ http_subtransport *t;
+
+ /* Target buffer details from read() */
+ char *buffer;
+ size_t buf_size;
+ size_t *bytes_read;
+} parser_context;
+
+static int apply_basic_credential(git_buf *buf, git_cred *cred)
{
- const char *path = url;
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
- path = strchr(path, '/');
- if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
- path = "/";
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(buf, "Authorization: Basic ") < 0 ||
+ git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||
+ git_buf_puts(buf, "\r\n") < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git_buf_free(&raw);
+ return error;
+}
+
+static int gen_request(
+ git_buf *buf,
+ http_stream *s,
+ size_t content_length)
+{
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!t->path)
+ t->path = "/";
+
+ /* If we were redirected, make sure to respect that here */
+ if (s->redirect_url)
+ git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url);
+ else
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);
- if (ls) {
- git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
- } else {
- git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
- }
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
- git_buf_printf(buf, "Host: %s\r\n", host);
- if (content_length > 0) {
- git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
- git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
- git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
- } else {
+ git_buf_printf(buf, "Host: %s\r\n", t->host);
+
+ if (s->chunked || content_length > 0) {
+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
+
+ if (s->chunked)
+ git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
+ else
+ git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
+ } else
git_buf_puts(buf, "Accept: */*\r\n");
+
+ /* Apply credentials to the request */
+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
+ apply_basic_credential(buf, t->cred) < 0)
+ return -1;
+
+ /* Use url-parsed basic auth if username and password are both provided */
+ if (!t->cred && t->user_from_url && t->pass_from_url) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
+ return -1;
+ if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
}
+
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
@@ -83,600 +171,783 @@ static int gen_request(git_buf *buf, const char *url, const char *host, const ch
return 0;
}
-static int do_connect(transport_http *t, const char *host, const char *port)
+static int parse_unauthorized_response(
+ git_vector *www_authenticate,
+ int *allowed_types,
+ http_authmechanism_t *auth_mechanism)
{
- GIT_SOCKET s;
+ unsigned i;
+ char *entry;
+
+ git_vector_foreach(www_authenticate, i, entry) {
+ if (!strncmp(entry, basic_authtype, 5) &&
+ (entry[5] == '\0' || entry[5] == ' ')) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_HTTP_AUTH_BASIC;
+ }
+ }
- if (t->parent.connected && http_should_keep_alive(&t->parser))
- return 0;
+ return 0;
+}
- if (gitno_connect(&s, host, port) < 0)
- return -1;
+static int on_header_ready(http_subtransport *t)
+{
+ git_buf *name = &t->parse_header_name;
+ git_buf *value = &t->parse_header_value;
+
+ if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ }
+ else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
+ char *dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
- t->socket = s;
- t->parent.connected = 1;
+ git_vector_insert(&t->www_authenticate, dup);
+ }
+ else if (!strcasecmp("Location", git_buf_cstr(name))) {
+ if (!t->location) {
+ t->location= git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->location);
+ }
+ }
return 0;
}
-/*
- * The HTTP parser is streaming, so we need to wait until we're in the
- * field handler before we can be sure that we can store the previous
- * value. Right now, we only care about the
- * Content-Type. on_header_{field,value} should be kept generic enough
- * to work for any request.
- */
-
-static const char *typestr = "Content-Type";
-
static int on_header_field(http_parser *parser, const char *str, size_t len)
{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
-
- if (t->last_cb == VALUE && t->ct_found) {
- t->ct_finished = 1;
- t->ct_found = 0;
- t->content_type = git__strdup(git_buf_cstr(buf));
- GITERR_CHECK_ALLOC(t->content_type);
- git_buf_clear(buf);
- }
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (t->ct_found) {
- t->last_cb = FIELD;
- return 0;
- }
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- if (t->last_cb != FIELD)
- git_buf_clear(buf);
+ if (NONE == t->last_cb || VALUE == t->last_cb)
+ git_buf_clear(&t->parse_header_name);
- git_buf_put(buf, str, len);
- t->last_cb = FIELD;
+ if (git_buf_put(&t->parse_header_name, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- return git_buf_oom(buf);
+ t->last_cb = FIELD;
+ return 0;
}
static int on_header_value(http_parser *parser, const char *str, size_t len)
{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (t->ct_finished) {
- t->last_cb = VALUE;
- return 0;
- }
+ assert(NONE != t->last_cb);
- if (t->last_cb == VALUE)
- git_buf_put(buf, str, len);
+ if (FIELD == t->last_cb)
+ git_buf_clear(&t->parse_header_value);
- if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
- t->ct_found = 1;
- git_buf_clear(buf);
- git_buf_put(buf, str, len);
- }
+ if (git_buf_put(&t->parse_header_value, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
t->last_cb = VALUE;
-
- return git_buf_oom(buf);
+ return 0;
}
static int on_headers_complete(http_parser *parser)
{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ http_stream *s = ctx->s;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 &&
+ get_verb == s->verb &&
+ t->owner->cred_acquire_cb) {
+ int allowed_types = 0;
+
+ if (parse_unauthorized_response(&t->www_authenticate,
+ &allowed_types, &t->auth_mechanism) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+
+ if (t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ t->user_from_url,
+ allowed_types,
+ t->owner->cred_acquire_payload) < 0)
+ return PARSE_ERROR_GENERIC;
+
+ assert(t->cred);
+
+ /* Successfully acquired a credential. */
+ return t->parse_error = PARSE_ERROR_REPLAY;
+ }
+ }
- /* The content-type is text/plain for 404, so don't validate */
- if (parser->status_code == 404) {
- git_buf_clear(buf);
- return 0;
+ /* Check for a redirect.
+ * Right now we only permit a redirect to the same hostname. */
+ if ((parser->status_code == 301 ||
+ parser->status_code == 302 ||
+ (parser->status_code == 303 && get_verb == s->verb) ||
+ parser->status_code == 307) &&
+ t->location) {
+
+ if (s->redirect_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (t->location[0] != '/') {
+ giterr_set(GITERR_NET, "Only relative redirects are supported");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* Set the redirect URL on the stream. This is a transfer of
+ * ownership of the memory. */
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ s->redirect_url = t->location;
+ t->location = NULL;
+
+ t->connected = 0;
+ s->redirect_count++;
+
+ return t->parse_error = PARSE_ERROR_REPLAY;
}
- if (t->content_type == NULL) {
- t->content_type = git__strdup(git_buf_cstr(buf));
- if (t->content_type == NULL)
- return t->error = -1;
+ /* Check for a 200 HTTP status code. */
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET,
+ "Unexpected HTTP status code: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- git_buf_clear(buf);
- git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
- if (git_buf_oom(buf))
- return t->error = -1;
+ /* The response must contain a Content-Type header. */
+ if (!t->content_type) {
+ giterr_set(GITERR_NET, "No Content-Type header in response");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
- if (strcmp(t->content_type, git_buf_cstr(buf)))
- return t->error = -1;
+ /* The Content-Type header must match our expectation. */
+ if (get_verb == s->verb)
+ git_buf_printf(&buf,
+ "application/x-git-%s-advertisement",
+ ctx->s->service);
+ else
+ git_buf_printf(&buf,
+ "application/x-git-%s-result",
+ ctx->s->service);
+
+ if (git_buf_oom(&buf))
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (strcmp(t->content_type, git_buf_cstr(&buf))) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_NET,
+ "Invalid Content-Type: %s",
+ t->content_type);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ git_buf_free(&buf);
- git_buf_clear(buf);
return 0;
}
-static int on_body_store_refs(http_parser *parser, const char *str, size_t len)
+static int on_message_complete(http_parser *parser)
{
- transport_http *t = (transport_http *) parser->data;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (parser->status_code == 404) {
- return git_buf_put(&t->buf, str, len);
- }
+ t->parse_finished = 1;
- return git_protocol_store_refs(&t->proto, str, len);
+ return 0;
}
-static int on_message_complete(http_parser *parser)
+static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
{
- transport_http *t = (transport_http *) parser->data;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- t->transfer_finished = 1;
-
- if (parser->status_code == 404) {
- giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
- t->error = -1;
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "Can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
+ memcpy(ctx->buffer, str, len);
+ *(ctx->bytes_read) += len;
+ ctx->buffer += len;
+ ctx->buf_size -= len;
+
return 0;
}
-static int store_refs(transport_http *t)
+static void clear_parser_state(http_subtransport *t)
{
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
- git_pkt *pkt;
- int ret;
+ unsigned i;
+ char *entry;
http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- memset(&settings, 0x0, sizeof(http_parser_settings));
- settings.on_header_field = on_header_field;
- settings.on_header_value = on_header_value;
- settings.on_headers_complete = on_headers_complete;
- settings.on_body = on_body_store_refs;
- settings.on_message_complete = on_message_complete;
+ gitno_buffer_setup(&t->socket,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+ t->last_cb = NONE;
+ t->parse_error = 0;
+ t->parse_finished = 0;
- while(1) {
- size_t parsed;
-
- if ((ret = gitno_recv(&buf)) < 0)
- return -1;
+ git_buf_free(&t->parse_header_name);
+ git_buf_init(&t->parse_header_name, 0);
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- /* Both should happen at the same time */
- if (parsed != buf.offset || t->error < 0)
- return t->error;
+ git_buf_free(&t->parse_header_value);
+ git_buf_init(&t->parse_header_value, 0);
- gitno_consume_n(&buf, parsed);
+ git__free(t->content_type);
+ t->content_type = NULL;
- if (ret == 0 || t->transfer_finished)
- return 0;
- }
+ git__free(t->location);
+ t->location = NULL;
- pkt = git_vector_get(&t->refs, 0);
- if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
- giterr_set(GITERR_NET, "Invalid HTTP response");
- return t->error = -1;
- } else {
- git_vector_remove(&t->refs, 0);
- }
+ git_vector_foreach(&t->www_authenticate, i, entry)
+ git__free(entry);
- return 0;
+ git_vector_free(&t->www_authenticate);
}
-static int http_connect(git_transport *transport, int direction)
+static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
{
- transport_http *t = (transport_http *) transport;
- int ret;
- git_buf request = GIT_BUF_INIT;
- const char *service = "upload-pack";
- const char *url = t->parent.url, *prefix = "http://";
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", (unsigned)len);
+
+ if (git_buf_oom(&buf))
+ return -1;
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
+ if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) {
+ git_buf_free(&buf);
return -1;
}
- t->parent.direction = direction;
- if (git_vector_init(&t->refs, 16, NULL) < 0)
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (len > 0 && gitno_send(socket, buffer, len, 0) < 0)
+ return -1;
+
+ /* Chunk footer */
+ if (gitno_send(socket, "\r\n", 2, 0) < 0)
return -1;
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
+ return 0;
+}
- if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, "80")) < 0)
- goto cleanup;
+static int http_connect(http_subtransport *t)
+{
+ int flags = 0;
- t->service = git__strdup(service);
- GITERR_CHECK_ALLOC(t->service);
+ if (t->connected &&
+ http_should_keep_alive(&t->parser) &&
+ http_body_is_final(&t->parser))
+ return 0;
- if ((ret = do_connect(t, t->host, t->port)) < 0)
- goto cleanup;
+ if (t->socket.socket)
+ gitno_close(&t->socket);
- /* Generate and send the HTTP request */
- if ((ret = gen_request(&request, url, t->host, "GET", service, 0, 1)) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
- goto cleanup;
- }
+ if (t->use_ssl) {
+ int tflags;
- if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0)
- goto cleanup;
+ if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0)
+ return -1;
- ret = store_refs(t);
+ flags |= GITNO_CONNECT_SSL;
-cleanup:
- git_buf_free(&request);
- git_buf_clear(&t->buf);
+ if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)
+ flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
+ }
+
+ if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
+ return -1;
- return ret;
+ t->connected = 1;
+ return 0;
}
-static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)
+static int http_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- git_pkt_ref *p;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ parser_context ctx;
+ size_t bytes_parsed;
+
+replay:
+ *bytes_read = 0;
+
+ assert(t->connected);
+
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
+ clear_parser_state(t);
- git_vector_foreach(refs, i, p) {
- if (p->type != GIT_PKT_REF)
- continue;
+ if (gen_request(&request, s, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- if (list_cb(&p->head, opaque) < 0) {
- giterr_set(GITERR_NET, "The user callback returned error");
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
return -1;
}
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
}
- return 0;
-}
+ if (!s->received_response) {
+ if (s->chunked) {
+ assert(s->verb == post_verb);
-static int on_body_parse_response(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
- git_vector *common = &t->common;
- int error;
- const char *line_end, *ptr;
-
- if (len == 0) { /* EOF */
- if (git_buf_len(buf) != 0) {
- giterr_set(GITERR_NET, "Unexpected EOF");
- return t->error = -1;
- } else {
- return 0;
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
+ return -1;
}
+
+ s->received_response = 1;
}
- git_buf_put(buf, str, len);
- ptr = buf->ptr;
- while (1) {
- git_pkt *pkt;
+ while (!*bytes_read && !t->parse_finished) {
+ t->parse_buffer.offset = 0;
- if (git_buf_len(buf) == 0)
- return 0;
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
- error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf));
- if (error == GIT_EBUFS) {
- return 0; /* Ask for more */
- }
- if (error < 0)
- return t->error = -1;
+ /* This call to http_parser_execute will result in invocations of the
+ * on_* family of callbacks. The most interesting of these is
+ * on_body_fill_buffer, which is called when data is ready to be copied
+ * into the target buffer. We need to marshal the buffer, buf_size, and
+ * bytes_read parameters to this callback. */
+ ctx.t = t;
+ ctx.s = s;
+ ctx.buffer = buffer;
+ ctx.buf_size = buf_size;
+ ctx.bytes_read = bytes_read;
- git_buf_consume(buf, line_end);
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
- if (pkt->type == GIT_PKT_PACK) {
- git__free(pkt);
- t->pack_ready = 1;
- return 0;
- }
+ bytes_parsed = http_parser_execute(&t->parser,
+ &t->settings,
+ t->parse_buffer.data,
+ t->parse_buffer.offset);
- if (pkt->type == GIT_PKT_NAK) {
- git__free(pkt);
- return 0;
- }
+ t->parser.data = NULL;
+
+ /* If there was a handled authentication failure, then parse_error
+ * will have signaled us that we should replay the request. */
+ if (PARSE_ERROR_REPLAY == t->parse_error) {
+ s->sent_request = 0;
- if (pkt->type != GIT_PKT_ACK) {
- git__free(pkt);
- continue;
+ if (http_connect(t) < 0)
+ return -1;
+
+ goto replay;
}
- if (git_vector_insert(common, pkt) < 0)
+ if (t->parse_error < 0)
return -1;
- }
- return error;
+ if (bytes_parsed != t->parse_buffer.offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ return -1;
+ }
+ }
+ return 0;
}
-static int parse_response(transport_http *t)
+static int http_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- int ret = 0;
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
- http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- t->transfer_finished = 0;
- memset(&settings, 0x0, sizeof(http_parser_settings));
- settings.on_header_field = on_header_field;
- settings.on_header_value = on_header_value;
- settings.on_headers_complete = on_headers_complete;
- settings.on_body = on_body_parse_response;
- settings.on_message_complete = on_message_complete;
+ assert(t->connected);
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+ /* Send the request, if necessary */
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
- while(1) {
- size_t parsed;
+ clear_parser_state(t);
- if ((ret = gitno_recv(&buf)) < 0)
+ if (gen_request(&request, s, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
return -1;
+ }
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- /* Both should happen at the same time */
- if (parsed != buf.offset || t->error < 0)
- return t->error;
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
+ }
+
+ if (len > CHUNK_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
- gitno_consume_n(&buf, parsed);
+ /* Write chunk directly */
+ if (write_chunk(&t->socket, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CHUNK_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
- if (ret == 0 || t->transfer_finished || t->pack_ready) {
- return 0;
+ /* Is the buffer full? If so, then flush */
+ if (CHUNK_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = len;
+ }
}
}
- return ret;
+ return 0;
}
-static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int http_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- transport_http *t = (transport_http *) transport;
- int ret;
- unsigned int i;
- char buff[128];
- gitno_buffer buf;
- git_revwalk *walk = NULL;
- git_oid oid;
- git_pkt_ack *pkt;
- git_vector *common = &t->common;
- const char *prefix = "http://", *url = t->parent.url;
- git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT;
- gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket);
-
- /* TODO: Store url in the transport */
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
-
- if (git_vector_init(common, 16, NULL) < 0)
- return -1;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
- if (git_fetch_setup_walk(&walk, repo) < 0)
+ assert(t->connected);
+
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
return -1;
+ }
- do {
- if ((ret = do_connect(t, t->host, t->port)) < 0)
- goto cleanup;
+ clear_parser_state(t);
- if ((ret = git_pkt_buffer_wants(wants, &t->caps, &data)) < 0)
- goto cleanup;
+ if (gen_request(&request, s, len) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- /* We need to send these on each connection */
- git_vector_foreach (common, i, pkt) {
- if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
- goto cleanup;
- }
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
+ goto on_error;
- i = 0;
- while ((i < 20) && ((ret = git_revwalk_next(&oid, walk)) == 0)) {
- if ((ret = git_pkt_buffer_have(&oid, &data)) < 0)
- goto cleanup;
+ if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
+ goto on_error;
- i++;
- }
+ git_buf_free(&request);
+ s->sent_request = 1;
- git_pkt_buffer_done(&data);
+ return 0;
+
+on_error:
+ git_buf_free(&request);
+ return -1;
+}
- if ((ret = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0)) < 0)
- goto cleanup;
+static void http_stream_free(git_smart_subtransport_stream *stream)
+{
+ http_stream *s = (http_stream *)stream;
- if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0)
- goto cleanup;
+ if (s->chunk_buffer)
+ git__free(s->chunk_buffer);
- if ((ret = gitno_send(t->socket, data.ptr, data.size, 0)) < 0)
- goto cleanup;
+ if (s->redirect_url)
+ git__free(s->redirect_url);
- git_buf_clear(&request);
- git_buf_clear(&data);
+ git__free(s);
+}
- if (ret < 0 || i >= 256)
- break;
+static int http_stream_alloc(http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
- if ((ret = parse_response(t)) < 0)
- goto cleanup;
+ if (!stream)
+ return -1;
- if (t->pack_ready) {
- ret = 0;
- goto cleanup;
- }
+ s = git__calloc(sizeof(http_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- } while(1);
+ s->parent.subtransport = &t->parent;
+ s->parent.read = http_stream_read;
+ s->parent.write = http_stream_write_single;
+ s->parent.free = http_stream_free;
-cleanup:
- git_buf_free(&request);
- git_buf_free(&data);
- git_revwalk_free(walk);
- return ret;
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
}
-typedef struct {
- git_indexer_stream *idx;
- git_indexer_stats *stats;
- transport_http *transport;
-} download_pack_cbdata;
-
-static int on_message_complete_download_pack(http_parser *parser)
+static int http_uploadpack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
- data->transport->transfer_finished = 1;
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
return 0;
}
-static int on_body_download_pack(http_parser *parser, const char *str, size_t len)
+
+static int http_uploadpack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;
- transport_http *t = data->transport;
- git_indexer_stream *idx = data->idx;
- git_indexer_stats *stats = data->stats;
+ http_stream *s;
- return t->error = git_indexer_stream_add(idx, str, len, stats);
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
}
-/*
- * As the server is probably using Transfer-Encoding: chunked, we have
- * to use the HTTP parser to download the pack instead of giving it to
- * the simple downloader. Furthermore, we're using keep-alive
- * connections, so the simple downloader would just hang.
- */
-static int http_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)
+static int http_receivepack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- transport_http *t = (transport_http *) transport;
- git_buf *oldbuf = &t->buf;
- int recvd;
- http_parser_settings settings;
- char buffer[1024];
- gitno_buffer buf;
- git_indexer_stream *idx = NULL;
- download_pack_cbdata data;
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
+static int http_receivepack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
- if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) {
- giterr_set(GITERR_NET, "The pack doesn't start with a pack signature");
+ if (http_stream_alloc(t, stream) < 0)
return -1;
- }
- if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0)
+ s = (http_stream *)*stream;
+
+ /* Use Transfer-Encoding: chunked for this request */
+ s->chunked = 1;
+ s->parent.write = http_stream_write_chunked;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int http_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *t = (http_subtransport *)subtransport;
+ const char *default_port = NULL;
+ int ret;
+
+ if (!stream)
return -1;
+ if (!t->host || !t->port || !t->path) {
+ if (!git__prefixcmp(url, prefix_http)) {
+ url = url + strlen(prefix_http);
+ default_port = "80";
+ }
- /*
- * This is part of the previous response, so we don't want to
- * re-init the parser, just set these two callbacks.
- */
- memset(stats, 0, sizeof(git_indexer_stats));
- data.stats = stats;
- data.idx = idx;
- data.transport = t;
- t->parser.data = &data;
- t->transfer_finished = 0;
- memset(&settings, 0x0, sizeof(settings));
- settings.on_message_complete = on_message_complete_download_pack;
- settings.on_body = on_body_download_pack;
- *bytes = git_buf_len(oldbuf);
-
- if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0)
- goto on_error;
+ if (!git__prefixcmp(url, prefix_https)) {
+ url += strlen(prefix_https);
+ default_port = "443";
+ t->use_ssl = 1;
+ }
- do {
- size_t parsed;
+ if (!default_port)
+ return -1;
- if ((recvd = gitno_recv(&buf)) < 0)
- goto on_error;
+ if ((ret = gitno_extract_url_parts(&t->host, &t->port,
+ &t->user_from_url, &t->pass_from_url, url, default_port)) < 0)
+ return ret;
- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);
- if (parsed != buf.offset || t->error < 0)
- goto on_error;
+ t->path = strchr(url, '/');
+ }
- *bytes += recvd;
- gitno_consume_n(&buf, parsed);
- } while (recvd > 0 && !t->transfer_finished);
+ if (http_connect(t) < 0)
+ return -1;
- if (git_indexer_stream_finalize(idx, stats) < 0)
- goto on_error;
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return http_uploadpack_ls(t, stream);
- git_indexer_stream_free(idx);
- return 0;
+ case GIT_SERVICE_UPLOADPACK:
+ return http_uploadpack(t, stream);
-on_error:
- git_indexer_stream_free(idx);
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return http_receivepack_ls(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return http_receivepack(t, stream);
+ }
+
+ *stream = NULL;
return -1;
}
-static int http_close(git_transport *transport)
+static int http_close(git_smart_subtransport *subtransport)
{
- transport_http *t = (transport_http *) transport;
+ http_subtransport *t = (http_subtransport *) subtransport;
- if (gitno_close(t->socket) < 0) {
- giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
- return -1;
+ clear_parser_state(t);
+
+ if (t->socket.socket) {
+ gitno_close(&t->socket);
+ memset(&t->socket, 0x0, sizeof(gitno_socket));
}
- return 0;
-}
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
-static void http_free(git_transport *transport)
-{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &t->refs;
- git_vector *common = &t->common;
- unsigned int i;
- git_pkt *p;
-
-#ifdef GIT_WIN32
- /* cleanup the WSA context. note that this context
- * can be initialized more than once with WSAStartup(),
- * and needs to be cleaned one time for each init call
- */
- WSACleanup();
-#endif
-
- git_vector_foreach(refs, i, p) {
- git_pkt_free(p);
+ if (t->host) {
+ git__free(t->host);
+ t->host = NULL;
}
- git_vector_free(refs);
- git_vector_foreach(common, i, p) {
- git_pkt_free(p);
+
+ if (t->port) {
+ git__free(t->port);
+ t->port = NULL;
}
- git_vector_free(common);
- git_buf_free(&t->buf);
- git_buf_free(&t->proto.buf);
- git__free(t->heads);
- git__free(t->content_type);
- git__free(t->host);
- git__free(t->port);
- git__free(t->service);
- git__free(t->parent.url);
+
+ if (t->user_from_url) {
+ git__free(t->user_from_url);
+ t->user_from_url = NULL;
+ }
+
+ if (t->pass_from_url) {
+ git__free(t->pass_from_url);
+ t->pass_from_url = NULL;
+ }
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ http_close(subtransport);
+
git__free(t);
}
-int git_transport_http(git_transport **out)
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
{
- transport_http *t;
+ http_subtransport *t;
- t = git__malloc(sizeof(transport_http));
- GITERR_CHECK_ALLOC(t);
+ if (!out)
+ return -1;
- memset(t, 0x0, sizeof(transport_http));
+ t = git__calloc(sizeof(http_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
- t->parent.connect = http_connect;
- t->parent.ls = http_ls;
- t->parent.negotiate_fetch = http_negotiate_fetch;
- t->parent.download_pack = http_download_pack;
+ t->owner = (transport_smart *)owner;
+ t->parent.action = http_action;
t->parent.close = http_close;
t->parent.free = http_free;
- t->proto.refs = &t->refs;
- t->proto.transport = (git_transport *) t;
-
-#ifdef GIT_WIN32
- /* on win32, the WSA context needs to be initialized
- * before any socket calls can be performed */
- if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
- http_free((git_transport *) t);
- giterr_set(GITERR_OS, "Winsock init failed");
- return -1;
- }
-#endif
- *out = (git_transport *) t;
+ t->settings.on_header_field = on_header_field;
+ t->settings.on_header_value = on_header_value;
+ t->settings.on_headers_complete = on_headers_complete;
+ t->settings.on_body = on_body_fill_buffer;
+ t->settings.on_message_complete = on_message_complete;
+
+ *out = (git_smart_subtransport *) t;
return 0;
}
+
+#endif /* !GIT_WINHTTP */
diff --git a/src/transports/local.c b/src/transports/local.c
index 000993e69..8af970eac 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,16 +10,34 @@
#include "git2/repository.h"
#include "git2/object.h"
#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/revwalk.h"
+#include "git2/odb_backend.h"
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "git2/push.h"
+#include "pack-objects.h"
#include "refs.h"
-#include "transport.h"
#include "posix.h"
#include "path.h"
#include "buffer.h"
+#include "repository.h"
+#include "odb.h"
+#include "push.h"
+#include "remote.h"
typedef struct {
git_transport parent;
+ git_remote *owner;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
git_repository *repo;
git_vector refs;
+ unsigned connected : 1,
+ have_refs : 1;
} transport_local;
static int add_ref(transport_local *t, const char *name)
@@ -28,15 +46,28 @@ static int add_ref(transport_local *t, const char *name)
git_remote_head *head;
git_object *obj = NULL, *target = NULL;
git_buf buf = GIT_BUF_INIT;
+ int error;
- head = git__malloc(sizeof(git_remote_head));
+ head = git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
head->name = git__strdup(name);
GITERR_CHECK_ALLOC(head->name);
- if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0 ||
- git_vector_insert(&t->refs, head) < 0)
+ error = git_reference_name_to_id(&head->oid, t->repo, name);
+ if (error < 0) {
+ git__free(head->name);
+ git__free(head);
+ if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
+ /* This is actually okay. Empty repos often have a HEAD that points to
+ * a nonexistent "refs/heads/master". */
+ giterr_clear();
+ return 0;
+ }
+ return error;
+ }
+
+ if (git_vector_insert(&t->refs, head) < 0)
{
git__free(head->name);
git__free(head);
@@ -52,14 +83,16 @@ static int add_ref(transport_local *t, const char *name)
head = NULL;
- /* If it's not an annotated tag, just get out */
- if (git_object_type(obj) != GIT_OBJ_TAG) {
+ /* If it's not an annotated tag, or if we're mocking
+ * git-receive-pack, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG ||
+ t->direction != GIT_DIRECTION_FETCH) {
git_object_free(obj);
return 0;
}
/* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(sizeof(git_remote_head));
+ head = git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
if (git_buf_join(&buf, 0, name, peeled) < 0)
return -1;
@@ -92,14 +125,14 @@ static int store_refs(transport_local *t)
assert(t);
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
+ git_vector_init(&t->refs, ref_names.count, NULL) < 0)
goto on_error;
/* Sort the references first */
git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
- /* Add HEAD */
- if (add_ref(t, GIT_HEAD_FILE) < 0)
+ /* Add HEAD iff direction is fetch */
+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
goto on_error;
for (i = 0; i < ref_names.count; ++i) {
@@ -107,6 +140,7 @@ static int store_refs(transport_local *t)
goto on_error;
}
+ t->have_refs = 1;
git_strarray_free(&ref_names);
return 0;
@@ -116,28 +150,16 @@ on_error:
return -1;
}
-static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
-{
- transport_local *t = (transport_local *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
- git_remote_head *h;
-
- assert(transport && transport->connected);
-
- git_vector_foreach(refs, i, h) {
- if (list_cb(h, payload) < 0)
- return -1;
- }
-
- return 0;
-}
-
/*
* Try to open the url as a git directory. The direction doesn't
* matter in this case because we're calulating the heads ourselves.
*/
-static int local_connect(git_transport *transport, int direction)
+static int local_connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction, int flags)
{
git_repository *repo;
int error;
@@ -145,18 +167,24 @@ static int local_connect(git_transport *transport, int direction)
const char *path;
git_buf buf = GIT_BUF_INIT;
- GIT_UNUSED(direction);
+ GIT_UNUSED(cred_acquire_cb);
+ GIT_UNUSED(cred_acquire_payload);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+ t->direction = direction;
+ t->flags = flags;
/* The repo layer doesn't want the prefix */
- if (!git__prefixcmp(transport->url, "file://")) {
- if (git_path_fromurl(&buf, transport->url) < 0) {
+ if (!git__prefixcmp(t->url, "file://")) {
+ if (git_path_fromurl(&buf, t->url) < 0) {
git_buf_free(&buf);
return -1;
}
path = git_buf_cstr(&buf);
} else { /* We assume transport->url is already a path */
- path = transport->url;
+ path = t->url;
}
error = git_repository_open(&repo, path);
@@ -171,47 +199,411 @@ static int local_connect(git_transport *transport, int direction)
if (store_refs(t) < 0)
return -1;
- t->parent.connected = 1;
+ t->connected = 1;
+
+ return 0;
+}
+
+static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
+{
+ transport_local *t = (transport_local *)transport;
+ unsigned int i;
+ git_remote_head *head = NULL;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ git_vector_foreach(&t->refs, i, head) {
+ if (list_cb(head, payload))
+ return GIT_EUSER;
+ }
return 0;
}
-static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int local_negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count)
{
- GIT_UNUSED(transport);
- GIT_UNUSED(repo);
- GIT_UNUSED(wants);
+ transport_local *t = (transport_local*)transport;
+ git_remote_head *rhead;
+ unsigned int i;
- giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");
- return -1;
+ GIT_UNUSED(refs);
+ GIT_UNUSED(count);
+
+ /* Fill in the loids */
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+
+ int error = git_revparse_single(&obj, repo, rhead->name);
+ if (!error)
+ git_oid_cpy(&rhead->loid, git_object_id(obj));
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ git_object_free(obj);
+ giterr_clear();
+ }
+
+ return 0;
+}
+
+static int local_push_copy_object(
+ git_odb *local_odb,
+ git_odb *remote_odb,
+ git_pobject *obj)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+ git_odb_stream *odb_stream;
+ size_t odb_obj_size;
+ git_otype odb_obj_type;
+ git_oid remote_odb_obj_oid;
+
+ /* Object already exists in the remote ODB; do nothing and return 0*/
+ if (git_odb_exists(remote_odb, &obj->id))
+ return 0;
+
+ if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
+ return error;
+
+ odb_obj_size = git_odb_object_size(odb_obj);
+ odb_obj_type = git_odb_object_type(odb_obj);
+
+ if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
+ odb_obj_size, odb_obj_type)) < 0)
+ goto on_error;
+
+ if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj),
+ odb_obj_size) < 0 ||
+ odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
+ error = -1;
+ } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) {
+ giterr_set(GITERR_ODB, "Error when writing object to remote odb "
+ "during local push operation. Remote odb object oid does not "
+ "match local oid.");
+ error = -1;
+ }
+
+ odb_stream->free(odb_stream);
+
+on_error:
+ git_odb_object_free(odb_obj);
+ return error;
+}
+
+static int local_push_update_remote_ref(
+ git_repository *remote_repo,
+ const char *lref,
+ const char *rref,
+ git_oid *loid,
+ git_oid *roid)
+{
+ int error;
+ git_reference *remote_ref = NULL;
+
+ /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
+ rref = rref ? rref : lref;
+
+ if (lref) {
+ /* Create or update a ref */
+ if ((error = git_reference_create(NULL, remote_repo, rref, loid,
+ !git_oid_iszero(roid))) < 0)
+ return error;
+ } else {
+ /* Delete a ref */
+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ return error;
+ }
+
+ if ((error = git_reference_delete(remote_ref)) < 0)
+ return error;
+
+ git_reference_free(remote_ref);
+ }
+
+ return 0;
+}
+
+static int local_push(
+ git_transport *transport,
+ git_push *push)
+{
+ transport_local *t = (transport_local *)transport;
+ git_odb *remote_odb = NULL;
+ git_odb *local_odb = NULL;
+ git_repository *remote_repo = NULL;
+ push_spec *spec;
+ char *url = NULL;
+ int error;
+ unsigned int i;
+ size_t j;
+
+ if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0)
+ return error;
+
+ /* We don't currently support pushing locally to non-bare repos. Proper
+ non-bare repo push support would require checking configs to see if
+ we should override the default 'don't let this happen' behavior */
+ if (!remote_repo->is_bare) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
+ (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
+ goto on_error;
+
+ for (i = 0; i < push->pb->nr_objects; i++) {
+ if ((error = local_push_copy_object(local_odb, remote_odb,
+ &push->pb->object_list[i])) < 0)
+ goto on_error;
+ }
+
+ push->unpack_ok = 1;
+
+ git_vector_foreach(&push->specs, j, spec) {
+ push_status *status;
+ const git_error *last;
+ char *ref = spec->rref ? spec->rref : spec->lref;
+
+ status = git__calloc(sizeof(push_status), 1);
+ if (!status)
+ goto on_error;
+
+ status->ref = git__strdup(ref);
+ if (!status->ref) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
+ &spec->loid, &spec->roid);
+
+ switch (error) {
+ case GIT_OK:
+ break;
+ case GIT_EINVALIDSPEC:
+ status->msg = git__strdup("funny refname");
+ break;
+ case GIT_ENOTFOUND:
+ status->msg = git__strdup("Remote branch not found to delete");
+ break;
+ default:
+ last = giterr_last();
+
+ if (last && last->message)
+ status->msg = git__strdup(last->message);
+ else
+ status->msg = git__strdup("Unspecified error encountered");
+ break;
+ }
+
+ /* failed to allocate memory for a status message */
+ if (error < 0 && !status->msg) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ /* failed to insert the ref update status */
+ if ((error = git_vector_insert(&push->status, status)) < 0) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+ }
+
+ if (push->specs.length) {
+ int flags = t->flags;
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url,
+ push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_repository_free(remote_repo);
+ git__free(url);
+
+ return error;
+}
+
+typedef struct foreach_data {
+ git_transfer_progress *stats;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
+ git_odb_writepack *writepack;
+} foreach_data;
+
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ foreach_data *data = (foreach_data*)payload;
+
+ data->stats->received_bytes += len;
+ return data->writepack->add(data->writepack, buf, len, data->stats);
+}
+
+static int local_download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ transport_local *t = (transport_local*)transport;
+ git_revwalk *walk = NULL;
+ git_remote_head *rhead;
+ unsigned int i;
+ int error = -1;
+ git_oid oid;
+ git_packbuilder *pack = NULL;
+ git_odb_writepack *writepack = NULL;
+ git_odb *odb = NULL;
+
+ if ((error = git_revwalk_new(&walk, t->repo)) < 0)
+ goto cleanup;
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
+ goto cleanup;
+
+ stats->total_objects = 0;
+ stats->indexed_objects = 0;
+ stats->received_objects = 0;
+ stats->received_bytes = 0;
+
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if (git_object_type(obj) == GIT_OBJ_COMMIT) {
+ /* Revwalker includes only wanted commits */
+ error = git_revwalk_push(walk, &rhead->oid);
+ if (!git_oid_iszero(&rhead->loid))
+ error = git_revwalk_hide(walk, &rhead->loid);
+ } else {
+ /* Tag or some other wanted object. Add it on its own */
+ error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
+ }
+ git_object_free(obj);
+ }
+
+ /* Walk the objects, building a packfile */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_revwalk_next(&oid, walk)) == 0) {
+ git_commit *commit;
+
+ /* Skip commits we already have */
+ if (git_odb_exists(odb, &oid)) continue;
+
+ if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
+ const git_oid *tree_oid = git_commit_tree_id(commit);
+
+ /* Add the commit and its tree */
+ if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
+ (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
+ git_commit_free(commit);
+ goto cleanup;
+ }
+
+ git_commit_free(commit);
+ }
+ }
+
+ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
+ goto cleanup;
+
+ /* Write the data to the ODB */
+ {
+ foreach_data data = {0};
+ data.stats = stats;
+ data.progress_cb = progress_cb;
+ data.progress_payload = progress_payload;
+ data.writepack = writepack;
+
+ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)
+ goto cleanup;
+ }
+ error = writepack->commit(writepack, stats);
+
+cleanup:
+ if (writepack) writepack->free(writepack);
+ git_packbuilder_free(pack);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int local_is_connected(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ return t->connected;
+}
+
+static int local_read_flags(git_transport *transport, int *flags)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static void local_cancel(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
}
static int local_close(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
- git_repository_free(t->repo);
- t->repo = NULL;
+ t->connected = 0;
+
+ if (t->repo) {
+ git_repository_free(t->repo);
+ t->repo = NULL;
+ }
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
return 0;
}
static void local_free(git_transport *transport)
{
- unsigned int i;
- transport_local *t = (transport_local *) transport;
- git_vector *vec = &t->refs;
- git_remote_head *h;
+ transport_local *t = (transport_local *)transport;
+ size_t i;
+ git_remote_head *head;
- assert(transport);
+ /* Close the transport, if it's still open. */
+ local_close(transport);
- git_vector_foreach (vec, i, h) {
- git__free(h->name);
- git__free(h);
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
}
- git_vector_free(vec);
- git__free(t->parent.url);
+ git_vector_free(&t->refs);
+
+ /* Free the transport */
git__free(t);
}
@@ -219,20 +611,28 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out)
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
{
transport_local *t;
- t = git__malloc(sizeof(transport_local));
- GITERR_CHECK_ALLOC(t);
+ GIT_UNUSED(param);
- memset(t, 0x0, sizeof(transport_local));
+ t = git__calloc(1, sizeof(transport_local));
+ GITERR_CHECK_ALLOC(t);
+ t->parent.version = GIT_TRANSPORT_VERSION;
t->parent.connect = local_connect;
- t->parent.ls = local_ls;
t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
+ t->parent.push = local_push;
t->parent.close = local_close;
t->parent.free = local_free;
+ t->parent.ls = local_ls;
+ t->parent.is_connected = local_is_connected;
+ t->parent.read_flags = local_read_flags;
+ t->parent.cancel = local_cancel;
+
+ t->owner = owner;
*out = (git_transport *) t;
diff --git a/src/transports/smart.c b/src/transports/smart.c
new file mode 100644
index 000000000..416eb221f
--- /dev/null
+++ b/src/transports/smart.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "smart.h"
+#include "refs.h"
+
+static int git_smart__recv_cb(gitno_buffer *buf)
+{
+ transport_smart *t = (transport_smart *) buf->cb_data;
+ size_t old_len, bytes_read;
+ int error;
+
+ assert(t->current_stream);
+
+ old_len = buf->offset;
+
+ if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
+ return error;
+
+ buf->offset += bytes_read;
+
+ if (t->packetsize_cb)
+ t->packetsize_cb(bytes_read, t->packetsize_payload);
+
+ return (int)(buf->offset - old_len);
+}
+
+GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
+{
+ if (t->current_stream) {
+ t->current_stream->free(t->current_stream);
+ t->current_stream = NULL;
+ }
+
+ if (close_subtransport &&
+ t->wrapped->close(t->wrapped) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int git_smart__set_callbacks(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ void *message_cb_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ t->progress_cb = progress_cb;
+ t->error_cb = error_cb;
+ t->message_cb_payload = message_cb_payload;
+
+ return 0;
+}
+
+static int git_smart__connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction,
+ int flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+ git_pkt *pkt;
+ git_pkt_ref *first;
+ git_smart_service_t service;
+
+ if (git_smart__reset_stream(t, true) < 0)
+ return -1;
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+
+ t->direction = direction;
+ t->flags = flags;
+ t->cred_acquire_cb = cred_acquire_cb;
+ t->cred_acquire_payload = cred_acquire_payload;
+
+ if (GIT_DIRECTION_FETCH == t->direction)
+ service = GIT_SERVICE_UPLOADPACK_LS;
+ else if (GIT_DIRECTION_PUSH == t->direction)
+ service = GIT_SERVICE_RECEIVEPACK_LS;
+ else {
+ giterr_set(GITERR_NET, "Invalid direction");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
+ return error;
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ /* 2 flushes for RPC; 1 for stateful */
+ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
+ return error;
+
+ /* Strip the comment packet for RPC */
+ if (t->rpc) {
+ pkt = (git_pkt *)git_vector_get(&t->refs, 0);
+
+ if (!pkt || GIT_PKT_COMMENT != pkt->type) {
+ giterr_set(GITERR_NET, "Invalid response");
+ return -1;
+ } else {
+ /* Remove the comment pkt from the list */
+ git_vector_remove(&t->refs, 0);
+ git__free(pkt);
+ }
+ }
+
+ /* We now have loaded the refs. */
+ t->have_refs = 1;
+
+ first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
+
+ /* Detect capabilities */
+ if (git_smart__detect_caps(first, &t->caps) < 0)
+ return -1;
+
+ /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
+ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
+ git_oid_iszero(&first->head.oid)) {
+ git_vector_clear(&t->refs);
+ git_pkt_free((git_pkt *)first);
+ }
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
+}
+
+static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+ unsigned int i;
+ git_pkt *p = NULL;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ git_vector_foreach(&t->refs, i, p) {
+ git_pkt_ref *pkt = NULL;
+
+ if (p->type != GIT_PKT_REF)
+ continue;
+
+ pkt = (git_pkt_ref *)p;
+
+ if (list_cb(&pkt->head, payload))
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_FETCH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for fetch");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ if ((error = stream->write(stream, (const char *)data, len)) < 0)
+ return error;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
+{
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_PUSH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for push");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == *stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = *stream;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+static void git_smart__cancel(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int git_smart__is_connected(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ return t->connected;
+}
+
+static int git_smart__read_flags(git_transport *transport, int *flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static int git_smart__close(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+ int ret;
+
+ ret = git_smart__reset_stream(t, true);
+
+ git_vector_foreach(common, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(common);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
+
+ t->connected = 0;
+
+ return ret;
+}
+
+static void git_smart__free(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+ git_pkt *p;
+
+ /* Make sure that the current stream is closed, if we have one. */
+ git_smart__close(transport);
+
+ /* Free the subtransport */
+ t->wrapped->free(t->wrapped);
+
+ git_vector_foreach(refs, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(refs);
+
+ git__free(t);
+}
+
+static int ref_name_cmp(const void *a, const void *b)
+{
+ const git_pkt_ref *ref_a = a, *ref_b = b;
+
+ return strcmp(ref_a->head.name, ref_b->head.name);
+}
+
+int git_transport_smart(git_transport **out, git_remote *owner, void *param)
+{
+ transport_smart *t;
+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
+
+ if (!param)
+ return -1;
+
+ t = git__calloc(sizeof(transport_smart), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.version = GIT_TRANSPORT_VERSION;
+ t->parent.set_callbacks = git_smart__set_callbacks;
+ t->parent.connect = git_smart__connect;
+ t->parent.close = git_smart__close;
+ t->parent.free = git_smart__free;
+ t->parent.negotiate_fetch = git_smart__negotiate_fetch;
+ t->parent.download_pack = git_smart__download_pack;
+ t->parent.push = git_smart__push;
+ t->parent.ls = git_smart__ls;
+ t->parent.is_connected = git_smart__is_connected;
+ t->parent.read_flags = git_smart__read_flags;
+ t->parent.cancel = git_smart__cancel;
+
+ t->owner = owner;
+ t->rpc = definition->rpc;
+
+ if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (definition->callback(&t->wrapped, &t->parent) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ *out = (git_transport *) t;
+ return 0;
+}
diff --git a/src/transports/smart.h b/src/transports/smart.h
new file mode 100644
index 000000000..c52401a3c
--- /dev/null
+++ b/src/transports/smart.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "vector.h"
+#include "netops.h"
+#include "buffer.h"
+#include "push.h"
+
+#define GIT_SIDE_BAND_DATA 1
+#define GIT_SIDE_BAND_PROGRESS 2
+#define GIT_SIDE_BAND_ERROR 3
+
+#define GIT_CAP_OFS_DELTA "ofs-delta"
+#define GIT_CAP_MULTI_ACK "multi_ack"
+#define GIT_CAP_SIDE_BAND "side-band"
+#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
+#define GIT_CAP_DELETE_REFS "delete-refs"
+#define GIT_CAP_REPORT_STATUS "report-status"
+
+enum git_pkt_type {
+ GIT_PKT_CMD,
+ GIT_PKT_FLUSH,
+ GIT_PKT_REF,
+ GIT_PKT_HAVE,
+ GIT_PKT_ACK,
+ GIT_PKT_NAK,
+ GIT_PKT_PACK,
+ GIT_PKT_COMMENT,
+ GIT_PKT_ERR,
+ GIT_PKT_DATA,
+ GIT_PKT_PROGRESS,
+ GIT_PKT_OK,
+ GIT_PKT_NG,
+ GIT_PKT_UNPACK,
+};
+
+/* Used for multi-ack */
+enum git_ack_status {
+ GIT_ACK_NONE,
+ GIT_ACK_CONTINUE,
+ GIT_ACK_COMMON,
+ GIT_ACK_READY
+};
+
+/* This would be a flush pkt */
+typedef struct {
+ enum git_pkt_type type;
+} git_pkt;
+
+struct git_pkt_cmd {
+ enum git_pkt_type type;
+ char *cmd;
+ char *path;
+ char *host;
+};
+
+/* This is a pkt-line with some info in it */
+typedef struct {
+ enum git_pkt_type type;
+ git_remote_head head;
+ char *capabilities;
+} git_pkt_ref;
+
+/* Useful later */
+typedef struct {
+ enum git_pkt_type type;
+ git_oid oid;
+ enum git_ack_status status;
+} git_pkt_ack;
+
+typedef struct {
+ enum git_pkt_type type;
+ char comment[GIT_FLEX_ARRAY];
+} git_pkt_comment;
+
+typedef struct {
+ enum git_pkt_type type;
+ int len;
+ char data[GIT_FLEX_ARRAY];
+} git_pkt_data;
+
+typedef git_pkt_data git_pkt_progress;
+
+typedef struct {
+ enum git_pkt_type type;
+ int len;
+ char error[GIT_FLEX_ARRAY];
+} git_pkt_err;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+} git_pkt_ok;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+ char *msg;
+} git_pkt_ng;
+
+typedef struct {
+ enum git_pkt_type type;
+ int unpack_ok;
+} git_pkt_unpack;
+
+typedef struct transport_smart_caps {
+ int common:1,
+ ofs_delta:1,
+ multi_ack: 1,
+ side_band:1,
+ side_band_64k:1,
+ include_tag:1,
+ delete_refs:1,
+ report_status:1;
+} transport_smart_caps;
+
+typedef void (*packetsize_cb)(size_t received, void *payload);
+
+typedef struct {
+ git_transport parent;
+ git_remote *owner;
+ char *url;
+ git_cred_acquire_cb cred_acquire_cb;
+ void *cred_acquire_payload;
+ int direction;
+ int flags;
+ git_transport_message_cb progress_cb;
+ git_transport_message_cb error_cb;
+ void *message_cb_payload;
+ git_smart_subtransport *wrapped;
+ git_smart_subtransport_stream *current_stream;
+ transport_smart_caps caps;
+ git_vector refs;
+ git_vector common;
+ git_atomic cancelled;
+ packetsize_cb packetsize_cb;
+ void *packetsize_payload;
+ unsigned rpc : 1,
+ have_refs : 1,
+ connected : 1;
+ gitno_buffer buffer;
+ char buffer_data[65536];
+} transport_smart;
+
+/* smart_protocol.c */
+int git_smart__store_refs(transport_smart *t, int flushes);
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
+int git_smart__push(git_transport *transport, git_push *push);
+
+int git_smart__negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count);
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
+/* smart.c */
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
+
+/* smart_pkt.c */
+int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
+int git_pkt_buffer_flush(git_buf *buf);
+int git_pkt_send_flush(GIT_SOCKET s);
+int git_pkt_buffer_done(git_buf *buf);
+int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf);
+int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
+void git_pkt_free(git_pkt *pkt);
diff --git a/src/pkt.c b/src/transports/smart_pkt.c
index 95430ddfc..99da37567 100644
--- a/src/pkt.c
+++ b/src/transports/smart_pkt.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,7 +12,7 @@
#include "git2/refs.h"
#include "git2/revwalk.h"
-#include "pkt.h"
+#include "smart.h"
#include "util.h"
#include "netops.h"
#include "posix.h"
@@ -42,15 +42,29 @@ static int flush_pkt(git_pkt **out)
/* the rest of the line will be useful for multi_ack */
static int ack_pkt(git_pkt **out, const char *line, size_t len)
{
- git_pkt *pkt;
+ git_pkt_ack *pkt;
GIT_UNUSED(line);
GIT_UNUSED(len);
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = git__calloc(1, sizeof(git_pkt_ack));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ACK;
- *out = pkt;
+ line += 3;
+ len -= 3;
+
+ if (len >= GIT_OID_HEXSZ) {
+ git_oid_fromstr(&pkt->oid, line + 1);
+ line += GIT_OID_HEXSZ + 1;
+ len -= GIT_OID_HEXSZ + 1;
+ }
+
+ if (len >= 7) {
+ if (!git__prefixcmp(line + 1, "continue"))
+ pkt->status = GIT_ACK_CONTINUE;
+ }
+
+ *out = (git_pkt *) pkt;
return 0;
}
@@ -108,6 +122,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
memcpy(pkt->error, line, len);
pkt->error[len] = '\0';
@@ -116,6 +131,61 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
return 0;
}
+static int data_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_data *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_data) + len);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_DATA;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int progress_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_progress *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_progress) + len);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_PROGRESS;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+
+ return 0;
+}
+
/*
* Parse an other-ref line.
*/
@@ -164,6 +234,83 @@ error_out:
return error;
}
+static int ok_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ok *pkt;
+ const char *ptr;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_OK;
+
+ line += 3; /* skip "ok " */
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->ref = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int ng_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ng *pkt;
+ const char *ptr;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_NG;
+
+ line += 3; /* skip "ng " */
+ ptr = strchr(line, ' ');
+ len = ptr - line;
+
+ pkt->ref = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ line = ptr + 1;
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->msg = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->msg);
+
+ memcpy(pkt->msg, line, len);
+ pkt->msg[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int unpack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_unpack *pkt;
+
+ GIT_UNUSED(len);
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_UNPACK;
+ if (!git__prefixcmp(line, "unpack ok"))
+ pkt->unpack_ok = 1;
+ else
+ pkt->unpack_ok = 0;
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
static int32_t parse_len(const char *line)
{
char num[PKT_LEN_SIZE + 1];
@@ -249,8 +396,13 @@ int git_pkt_parse_line(
len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
- /* Assming the minimal size is actually 4 */
- if (!git__prefixcmp(line, "ACK"))
+ if (*line == GIT_SIDE_BAND_DATA)
+ ret = data_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_PROGRESS)
+ ret = progress_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_ERROR)
+ ret = sideband_error_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ACK"))
ret = ack_pkt(head, line, len);
else if (!git__prefixcmp(line, "NAK"))
ret = nak_pkt(head);
@@ -258,6 +410,12 @@ int git_pkt_parse_line(
ret = err_pkt(head, line, len);
else if (*line == '#')
ret = comment_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ok"))
+ ret = ok_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ng"))
+ ret = ng_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "unpack"))
+ ret = unpack_pkt(head, line, len);
else
ret = ref_pkt(head, line, len);
@@ -273,6 +431,17 @@ void git_pkt_free(git_pkt *pkt)
git__free(p->head.name);
}
+ if (pkt->type == GIT_PKT_OK) {
+ git_pkt_ok *p = (git_pkt_ok *) pkt;
+ git__free(p->ref);
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ git_pkt_ng *p = (git_pkt_ng *) pkt;
+ git__free(p->ref);
+ git__free(p->msg);
+ }
+
git__free(pkt);
}
@@ -281,28 +450,40 @@ int git_pkt_buffer_flush(git_buf *buf)
return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
}
-int git_pkt_send_flush(GIT_SOCKET s)
-{
-
- return gitno_send(s, pkt_flush_str, strlen(pkt_flush_str), 0);
-}
-
-static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf)
+static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
{
- char capstr[20];
+ git_buf str = GIT_BUF_INIT;
char oid[GIT_OID_HEXSZ +1] = {0};
unsigned int len;
+ /* Prefer side-band-64k if the server supports both */
+ if (caps->side_band) {
+ if (caps->side_band_64k)
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K);
+ else
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND);
+ }
if (caps->ofs_delta)
- strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr));
+ git_buf_puts(&str, GIT_CAP_OFS_DELTA " ");
+
+ if (caps->multi_ack)
+ git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");
+
+ if (caps->include_tag)
+ git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");
+
+ if (git_buf_oom(&str))
+ return -1;
len = (unsigned int)
(strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ +
- strlen(capstr) + 1 /* LF */);
+ git_buf_len(&str) + 1 /* LF */);
git_buf_grow(buf, git_buf_len(buf) + len);
-
git_oid_fmt(oid, &head->oid);
- return git_buf_printf(buf, "%04xwant %s%c%s\n", len, oid, 0, capstr);
+ git_buf_printf(buf, "%04xwant %s %s\n", len, oid, git_buf_cstr(&str));
+ git_buf_free(&str);
+
+ return git_buf_oom(buf);
}
/*
@@ -310,28 +491,32 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps
* is overwrite the OID each time.
*/
-int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf)
+int git_pkt_buffer_wants(
+ const git_remote_head * const *refs,
+ size_t count,
+ transport_smart_caps *caps,
+ git_buf *buf)
{
- unsigned int i = 0;
- git_remote_head *head;
+ size_t i = 0;
+ const git_remote_head *head;
if (caps->common) {
- for (; i < refs->length; ++i) {
- head = refs->contents[i];
+ for (; i < count; ++i) {
+ head = refs[i];
if (!head->local)
break;
}
- if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0)
+ if (buffer_want_with_caps(refs[i], caps, buf) < 0)
return -1;
i++;
}
- for (; i < refs->length; ++i) {
+ for (; i < count; ++i) {
char oid[GIT_OID_HEXSZ];
- head = refs->contents[i];
+ head = refs[i];
if (head->local)
continue;
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
new file mode 100644
index 000000000..8acedeb49
--- /dev/null
+++ b/src/transports/smart_protocol.c
@@ -0,0 +1,856 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+
+#include "smart.h"
+#include "refs.h"
+#include "repository.h"
+#include "push.h"
+#include "pack-objects.h"
+#include "remote.h"
+
+#define NETWORK_XFER_THRESHOLD (100*1024)
+
+int git_smart__store_refs(transport_smart *t, int flushes)
+{
+ gitno_buffer *buf = &t->buffer;
+ git_vector *refs = &t->refs;
+ int error, flush = 0, recvd;
+ const char *line_end;
+ git_pkt *pkt;
+
+ /* Clear existing refs in case git_remote_connect() is called again
+ * after git_remote_disconnect().
+ */
+ git_vector_clear(refs);
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+
+ if (recvd == 0 && !flush) {
+ giterr_set(GITERR_NET, "Early EOF");
+ return -1;
+ }
+
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+ if (pkt->type == GIT_PKT_ERR) {
+ giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
+ git__free(pkt);
+ return -1;
+ }
+
+ if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_FLUSH) {
+ flush++;
+ git_pkt_free(pkt);
+ }
+ } while (flush < flushes);
+
+ return flush;
+}
+
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
+{
+ const char *ptr;
+
+ /* No refs or capabilites, odd but not a problem */
+ if (pkt == NULL || pkt->capabilities == NULL)
+ return 0;
+
+ ptr = pkt->capabilities;
+ while (ptr != NULL && *ptr != '\0') {
+ if (*ptr == ' ')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ caps->common = caps->ofs_delta = 1;
+ ptr += strlen(GIT_CAP_OFS_DELTA);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
+ caps->common = caps->multi_ack = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ caps->common = caps->include_tag = 1;
+ ptr += strlen(GIT_CAP_INCLUDE_TAG);
+ continue;
+ }
+
+ /* Keep side-band check after side-band-64k */
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
+ caps->common = caps->side_band_64k = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND_64K);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
+ caps->common = caps->side_band = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {
+ caps->common = caps->delete_refs = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
+ /* We don't know this capability, so skip it */
+ ptr = strchr(ptr, ' ');
+ }
+
+ return 0;
+}
+
+static int recv_pkt(git_pkt **out, gitno_buffer *buf)
+{
+ const char *ptr = buf->data, *line_end = ptr;
+ git_pkt *pkt;
+ int pkt_type, error = 0, ret;
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error == 0)
+ break; /* return the pkt */
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if ((ret = gitno_recv(buf)) < 0)
+ return -1;
+ } while (error);
+
+ gitno_consume(buf, line_end);
+ pkt_type = pkt->type;
+ if (out != NULL)
+ *out = pkt;
+ else
+ git__free(pkt);
+
+ return pkt_type;
+}
+
+static int store_common(transport_smart *t)
+{
+ git_pkt *pkt = NULL;
+ gitno_buffer *buf = &t->buffer;
+
+ do {
+ if (recv_pkt(&pkt, buf) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_ACK) {
+ if (git_vector_insert(&t->common, pkt) < 0)
+ return -1;
+ } else {
+ git__free(pkt);
+ return 0;
+ }
+
+ } while (1);
+
+ return 0;
+}
+
+static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
+{
+ git_revwalk *walk;
+ git_strarray refs;
+ unsigned int i;
+ git_reference *ref;
+
+ if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
+ return -1;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ for (i = 0; i < refs.count; ++i) {
+ /* No tags */
+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
+ continue;
+
+ if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
+ goto on_error;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+ if (git_revwalk_push(walk, git_reference_target(ref)) < 0)
+ goto on_error;
+
+ git_reference_free(ref);
+ }
+
+ git_strarray_free(&refs);
+ *out = walk;
+ return 0;
+
+on_error:
+ git_reference_free(ref);
+ git_strarray_free(&refs);
+ return -1;
+}
+
+int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_buf data = GIT_BUF_INIT;
+ git_revwalk *walk = NULL;
+ int error = -1, pkt_type;
+ unsigned int i;
+ git_oid oid;
+
+ /* No own logic, do our thing */
+ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ return error;
+
+ if ((error = fetch_setup_walk(&walk, repo)) < 0)
+ goto on_error;
+ /*
+ * We don't support any kind of ACK extensions, so the negotiation
+ * boils down to sending what we have and listening for an ACK
+ * every once in a while.
+ */
+ i = 0;
+ while (true) {
+ error = git_revwalk_next(&oid, walk);
+
+ if (error < 0) {
+ if (GIT_ITEROVER == error)
+ break;
+
+ goto on_error;
+ }
+
+ git_pkt_buffer_have(&oid, &data);
+ i++;
+ if (i % 20 == 0) {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ git_pkt_buffer_flush(&data);
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_clear(&data);
+ if (t->caps.multi_ack) {
+ if ((error = store_common(t)) < 0)
+ goto on_error;
+ } else {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type == GIT_PKT_ACK) {
+ break;
+ } else if (pkt_type == GIT_PKT_NAK) {
+ continue;
+ } else if (pkt_type < 0) {
+ /* recv_pkt returned an error */
+ error = pkt_type;
+ goto on_error;
+ } else {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ if (t->common.length > 0)
+ break;
+
+ if (i % 20 == 0 && t->rpc) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ /* Tell the other end that we're done negotiating */
+ if (t->rpc && t->common.length > 0) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+ if ((error = git_pkt_buffer_done(&data)) < 0)
+ goto on_error;
+
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_free(&data);
+ git_revwalk_free(walk);
+
+ /* Now let's eat up whatever the server gives us */
+ if (!t->caps.multi_ack) {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type < 0) {
+ return pkt_type;
+ } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ return -1;
+ }
+ } else {
+ git_pkt_ack *pkt;
+ do {
+ if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)
+ return error;
+
+ if (pkt->type == GIT_PKT_NAK ||
+ (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
+ git__free(pkt);
+ break;
+ }
+
+ git__free(pkt);
+ } while (1);
+ }
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ git_buf_free(&data);
+ return error;
+}
+
+static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats)
+{
+ int recvd;
+
+ do {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ return GIT_EUSER;
+ }
+
+ if (writepack->add(writepack, buf->data, buf->offset, stats) < 0)
+ return -1;
+
+ gitno_consume_n(buf, buf->offset);
+
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+ } while(recvd > 0);
+
+ if (writepack->commit(writepack, stats))
+ return -1;
+
+ return 0;
+}
+
+struct network_packetsize_payload
+{
+ git_transfer_progress_callback callback;
+ void *payload;
+ git_transfer_progress *stats;
+ size_t last_fired_bytes;
+};
+
+static void network_packetsize(size_t received, void *payload)
+{
+ struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
+
+ /* Accumulate bytes */
+ npp->stats->received_bytes += received;
+
+ /* Fire notification if the threshold is reached */
+ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
+ npp->last_fired_bytes = npp->stats->received_bytes;
+ npp->callback(npp->stats, npp->payload);
+ }
+}
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_odb *odb;
+ struct git_odb_writepack *writepack = NULL;
+ int error = -1;
+ struct network_packetsize_payload npp = {0};
+
+ memset(stats, 0, sizeof(git_transfer_progress));
+
+ if (progress_cb) {
+ npp.callback = progress_cb;
+ npp.payload = progress_payload;
+ npp.stats = stats;
+ t->packetsize_cb = &network_packetsize;
+ t->packetsize_payload = &npp;
+
+ /* We might have something in the buffer already from negotiate_fetch */
+ if (t->buffer.offset > 0)
+ t->packetsize_cb(t->buffer.offset, t->packetsize_payload);
+ }
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0))
+ goto on_error;
+
+ /*
+ * If the remote doesn't support the side-band, we can feed
+ * the data directly to the pack writer. Otherwise, we need to
+ * check which one belongs there.
+ */
+ if (!t->caps.side_band && !t->caps.side_band_64k) {
+ if (no_sideband(t, writepack, buf, stats) < 0)
+ goto on_error;
+
+ goto on_success;
+ }
+
+ do {
+ git_pkt *pkt;
+
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ if (recv_pkt(&pkt, buf) < 0)
+ goto on_error;
+
+ if (pkt->type == GIT_PKT_PROGRESS) {
+ if (t->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ t->progress_cb(p->data, p->len, t->message_cb_payload);
+ }
+ git__free(pkt);
+ } else if (pkt->type == GIT_PKT_DATA) {
+ git_pkt_data *p = (git_pkt_data *) pkt;
+ error = writepack->add(writepack, p->data, p->len, stats);
+
+ git__free(pkt);
+ if (error < 0)
+ goto on_error;
+ } else if (pkt->type == GIT_PKT_FLUSH) {
+ /* A flush indicates the end of the packfile */
+ git__free(pkt);
+ break;
+ }
+ } while (1);
+
+ if (writepack->commit(writepack, stats) < 0)
+ goto on_error;
+
+on_success:
+ error = 0;
+
+on_error:
+ if (writepack)
+ writepack->free(writepack);
+
+ /* Trailing execution of progress_cb, if necessary */
+ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)
+ npp.callback(npp.stats, npp.payload);
+
+ return error;
+}
+
+static int gen_pktline(git_buf *buf, git_push *push)
+{
+ push_spec *spec;
+ size_t i, len;
+ char old_id[41], new_id[41];
+
+ old_id[40] = '\0'; new_id[40] = '\0';
+
+ git_vector_foreach(&push->specs, i, spec) {
+ len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->rref);
+
+ if (i == 0) {
+ ++len; /* '\0' */
+ if (push->report_status)
+ len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+ len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
+ }
+
+ git_oid_fmt(old_id, &spec->roid);
+ git_oid_fmt(new_id, &spec->loid);
+
+ git_buf_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->rref);
+
+ if (i == 0) {
+ git_buf_putc(buf, '\0');
+ /* Core git always starts their capabilities string with a space */
+ if (push->report_status) {
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);
+ }
+
+ git_buf_putc(buf, '\n');
+ }
+
+ git_buf_puts(buf, "0000");
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int add_push_report_pkt(git_push *push, git_pkt *pkt)
+{
+ push_status *status;
+
+ switch (pkt->type) {
+ case GIT_PKT_OK:
+ status = git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->msg = NULL;
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ if (!status->ref ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_NG:
+ status = git__calloc(sizeof(push_status), 1);
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ if (!status->ref || !status->msg ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_UNPACK:
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ break;
+ case GIT_PKT_FLUSH:
+ return GIT_ITEROVER;
+ default:
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)
+{
+ git_pkt *pkt;
+ const char *line = data_pkt->data, *line_end;
+ size_t line_len = data_pkt->len;
+ int error;
+
+ while (line_len > 0) {
+ error = git_pkt_parse_line(&pkt, line, &line_end, line_len);
+
+ if (error < 0)
+ return error;
+
+ /* Advance in the buffer */
+ line_len -= (line_end - line);
+ line = line_end;
+
+ error = add_push_report_pkt(push, pkt);
+
+ git_pkt_free(pkt);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ return error;
+ }
+
+ return 0;
+}
+
+static int parse_report(gitno_buffer *buf, git_push *push)
+{
+ git_pkt *pkt;
+ const char *line_end;
+ int error, recvd;
+
+ for (;;) {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data,
+ &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+
+ if (recvd == 0) {
+ giterr_set(GITERR_NET, "Early EOF");
+ return -1;
+ }
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+
+ error = 0;
+
+ switch (pkt->type) {
+ case GIT_PKT_DATA:
+ /* This is a sideband packet which contains other packets */
+ error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt);
+ break;
+ case GIT_PKT_ERR:
+ giterr_set(GITERR_NET, "report-status: Error reported: %s",
+ ((git_pkt_err *)pkt)->error);
+ error = -1;
+ break;
+ case GIT_PKT_PROGRESS:
+ break;
+ default:
+ error = add_push_report_pkt(push, pkt);
+ break;
+ }
+
+ git_pkt_free(pkt);
+
+ /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */
+ if (error == GIT_ITEROVER)
+ return 0;
+
+ if (error < 0)
+ return error;
+ }
+}
+
+static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec)
+{
+ git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref));
+ GITERR_CHECK_ALLOC(added);
+
+ added->type = GIT_PKT_REF;
+ git_oid_cpy(&added->head.oid, &push_spec->loid);
+ added->head.name = git__strdup(push_spec->rref);
+
+ if (!added->head.name ||
+ git_vector_insert(refs, added) < 0) {
+ git_pkt_free((git_pkt *)added);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int update_refs_from_report(
+ git_vector *refs,
+ git_vector *push_specs,
+ git_vector *push_report)
+{
+ git_pkt_ref *ref;
+ push_spec *push_spec;
+ push_status *push_status;
+ size_t i, j, refs_len;
+ int cmp;
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report */
+ if (push_specs->length != push_report->length) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ /* We require that push_specs be sorted with push_spec_rref_cmp,
+ * and that push_report be sorted with push_status_ref_cmp */
+ git_vector_sort(push_specs);
+ git_vector_sort(push_report);
+
+ git_vector_foreach(push_specs, i, push_spec) {
+ push_status = git_vector_get(push_report, i);
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report which matches */
+ if (strcmp(push_spec->rref, push_status->ref)) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+ }
+
+ /* We require that refs be sorted with ref_name_cmp */
+ git_vector_sort(refs);
+ i = j = 0;
+ refs_len = refs->length;
+
+ /* Merge join push_specs with refs */
+ while (i < push_specs->length && j < refs_len) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+ ref = git_vector_get(refs, j);
+
+ cmp = strcmp(push_spec->rref, ref->head.name);
+
+ /* Iterate appropriately */
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+
+ /* Add case */
+ if (cmp < 0 &&
+ !push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+
+ /* Update case, delete case */
+ if (cmp == 0 &&
+ !push_status->msg)
+ git_oid_cpy(&ref->head.oid, &push_spec->loid);
+ }
+
+ for (; i < push_specs->length; i++) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+
+ /* Add case */
+ if (!push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+ }
+
+ /* Remove any refs which we updated to have a zero OID. */
+ git_vector_rforeach(refs, i, ref) {
+ if (git_oid_iszero(&ref->head.oid)) {
+ git_vector_remove(refs, i);
+ git_pkt_free((git_pkt *)ref);
+ }
+ }
+
+ git_vector_sort(refs);
+
+ return 0;
+}
+
+static int stream_thunk(void *buf, size_t size, void *data)
+{
+ git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data;
+
+ return s->write(s, (const char *)buf, size);
+}
+
+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;
+
+#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) {
+ git_oid_fmt(hex, &head->oid);
+ fprintf(stderr, "%s (%s)\n", hex, head->name);
+ }
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_oid_fmt(hex, &spec->roid);
+ fprintf(stderr, "%s (%s) -> ", hex, spec->lref);
+ git_oid_fmt(hex, &spec->loid);
+ fprintf(stderr, "%s (%s)\n", hex, spec->rref ?
+ spec->rref : spec->lref);
+ }
+}
+#endif
+
+ 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)
+ goto on_error;
+
+ /* If we sent nothing or the server doesn't support report-status, then
+ * we consider the pack to have been unpacked successfully */
+ if (!push->specs.length || !push->report_status)
+ push->unpack_ok = 1;
+ else if (parse_report(&t->buffer, push) < 0)
+ goto on_error;
+
+ if (push->status.length &&
+ update_refs_from_report(&t->refs, &push->specs, &push->status) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ git_buf_free(&pktline);
+
+ return error;
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
new file mode 100644
index 000000000..e502001cb
--- /dev/null
+++ b/src/transports/winhttp.c
@@ -0,0 +1,1136 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_WINHTTP
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "posix.h"
+#include "netops.h"
+#include "smart.h"
+#include "remote.h"
+#include "repository.h"
+
+#include <winhttp.h>
+#pragma comment(lib, "winhttp")
+
+/* For UuidCreate */
+#pragma comment(lib, "rpcrt4")
+
+#define WIDEN2(s) L ## s
+#define WIDEN(s) WIDEN2(s)
+
+#define MAX_CONTENT_TYPE_LEN 100
+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+#define CACHED_POST_BODY_BUF_SIZE 4096
+#define UUID_LENGTH_CCH 32
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const wchar_t *get_verb = L"GET";
+static const wchar_t *post_verb = L"POST";
+static const wchar_t *pragma_nocache = L"Pragma: no-cache";
+static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
+static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
+
+typedef enum {
+ GIT_WINHTTP_AUTH_BASIC = 1,
+} winhttp_authmechanism_t;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const wchar_t *verb;
+ HINTERNET request;
+ wchar_t *request_uri;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ HANDLE post_body;
+ DWORD post_body_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1;
+} winhttp_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ const char *path;
+ char *host;
+ char *port;
+ char *user_from_url;
+ char *pass_from_url;
+ git_cred *cred;
+ git_cred *url_cred;
+ int auth_mechanism;
+ HINTERNET session;
+ HINTERNET connection;
+ unsigned use_ssl : 1;
+} winhttp_subtransport;
+
+static int apply_basic_credential(HINTERNET request, git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;
+ wchar_t *wide = NULL;
+ int error = -1, wide_len = 0;
+
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(&buf, "Authorization: Basic ") < 0 ||
+ git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
+ goto on_error;
+
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ wide = git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!wide)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, wide, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */
+ if (wide)
+ memset(wide, 0x0, wide_len * sizeof(wchar_t));
+
+ if (buf.size)
+ memset(buf.ptr, 0x0, buf.size);
+
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git__free(wide);
+ git_buf_free(&buf);
+ git_buf_free(&raw);
+ return error;
+}
+
+static int winhttp_stream_connect(winhttp_stream *s)
+{
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf buf = GIT_BUF_INIT;
+ char *proxy_url = NULL;
+ wchar_t ct[MAX_CONTENT_TYPE_LEN];
+ wchar_t *types[] = { L"*/*", NULL };
+ BOOL peerdist = FALSE;
+ int error = -1, wide_len;
+
+ /* Prepare URL */
+ git_buf_printf(&buf, "%s%s", t->path, s->service_url);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* Convert URL to wide characters */
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ s->request_uri = git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!s->request_uri)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, s->request_uri, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ /* Establish request */
+ s->request = WinHttpOpenRequest(
+ t->connection,
+ s->verb,
+ s->request_uri,
+ NULL,
+ WINHTTP_NO_REFERER,
+ types,
+ t->use_ssl ? WINHTTP_FLAG_SECURE : 0);
+
+ if (!s->request) {
+ giterr_set(GITERR_OS, "Failed to open request");
+ goto on_error;
+ }
+
+ /* Set proxy if necessary */
+ if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0)
+ goto on_error;
+
+ if (proxy_url) {
+ WINHTTP_PROXY_INFO proxy_info;
+ wchar_t *proxy_wide;
+
+ /* Convert URL to wide characters */
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ proxy_url, -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ proxy_wide = git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!proxy_wide)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ proxy_url, -1, proxy_wide, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ git__free(proxy_wide);
+ goto on_error;
+ }
+
+ /* Strip any trailing forward slash on the proxy URL;
+ * WinHTTP doesn't like it if one is present */
+ if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2])
+ proxy_wide[wide_len - 2] = L'\0';
+
+ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy_info.lpszProxy = proxy_wide;
+ proxy_info.lpszProxyBypass = NULL;
+
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PROXY,
+ &proxy_info,
+ sizeof(WINHTTP_PROXY_INFO))) {
+ giterr_set(GITERR_OS, "Failed to set proxy");
+ git__free(proxy_wide);
+ goto on_error;
+ }
+
+ git__free(proxy_wide);
+ }
+
+ /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
+ * adds itself. This option may not be supported by the underlying
+ * platform, so we do not error-check it */
+ WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PEERDIST_EXTENSION_STATE,
+ &peerdist,
+ sizeof(peerdist));
+
+ /* Send Pragma: no-cache header */
+ if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ /* Send Content-Type header -- only necessary on a POST */
+ if (post_verb == s->verb) {
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0)
+ goto on_error;
+
+ git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+ }
+
+ /* If requested, disable certificate validation */
+ if (t->use_ssl) {
+ int flags;
+
+ if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
+ goto on_error;
+
+ if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&
+ !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
+ (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
+ giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
+ goto on_error;
+ }
+ }
+
+ /* If we have a credential on the subtransport, apply it to the request */
+ if (t->cred &&
+ t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC &&
+ apply_basic_credential(s->request, t->cred) < 0)
+ goto on_error;
+
+ /* If no other credentials have been applied and the URL has username and
+ * password, use those */
+ if (!t->cred && t->user_from_url && t->pass_from_url) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
+ goto on_error;
+ if (apply_basic_credential(s->request, t->url_cred) < 0)
+ goto on_error;
+ }
+
+ /* We've done everything up to calling WinHttpSendRequest. */
+
+ error = 0;
+
+on_error:
+ git__free(proxy_url);
+ git_buf_free(&buf);
+ return error;
+}
+
+static int parse_unauthorized_response(
+ HINTERNET request,
+ int *allowed_types,
+ int *auth_mechanism)
+{
+ DWORD supported, first, target;
+
+ *allowed_types = 0;
+ *auth_mechanism = 0;
+
+ /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
+ * We can assume this was already done, since we know we are unauthorized.
+ */
+ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
+ giterr_set(GITERR_OS, "Failed to parse supported auth schemes");
+ return -1;
+ }
+
+ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
+
+ return 0;
+}
+
+static int write_chunk(HINTERNET request, const char *buffer, size_t len)
+{
+ DWORD bytes_written;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (!WinHttpWriteData(request,
+ git_buf_cstr(&buf), (DWORD)git_buf_len(&buf),
+ &bytes_written)) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_OS, "Failed to write chunk header");
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (!WinHttpWriteData(request,
+ buffer, (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk");
+ return -1;
+ }
+
+ /* Chunk footer */
+ if (!WinHttpWriteData(request,
+ "\r\n", 2,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk footer");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int winhttp_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD dw_bytes_read;
+ char replay_count = 0;
+
+replay:
+ /* Enforce a reasonable cap on the number of replays */
+ if (++replay_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects or authentication replays");
+ return -1;
+ }
+
+ /* Connect if necessary */
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->received_response) {
+ DWORD status_code, status_code_length, content_type_length, bytes_written;
+ char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
+ wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
+
+ if (!s->sent_request) {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ s->post_body_len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (!WinHttpWriteData(s->request,
+ "0\r\n\r\n", 5,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write final chunk");
+ return -1;
+ }
+ }
+ else if (s->post_body) {
+ char *buffer;
+ DWORD len = s->post_body_len, bytes_read;
+
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
+ 0, 0, FILE_BEGIN) &&
+ NO_ERROR != GetLastError()) {
+ giterr_set(GITERR_OS, "Failed to reset file pointer");
+ return -1;
+ }
+
+ buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ while (len > 0) {
+ DWORD bytes_written;
+
+ if (!ReadFile(s->post_body, buffer,
+ min(CACHED_POST_BODY_BUF_SIZE, len),
+ &bytes_read, NULL) ||
+ !bytes_read) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to read from temp file");
+ return -1;
+ }
+
+ if (!WinHttpWriteData(s->request, buffer,
+ bytes_read, &bytes_written)) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ len -= bytes_read;
+ assert(bytes_read == bytes_written);
+ }
+
+ git__free(buffer);
+
+ /* Eagerly close the temp file */
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (!WinHttpReceiveResponse(s->request, 0)) {
+ giterr_set(GITERR_OS, "Failed to receive response");
+ return -1;
+ }
+
+ /* Verify that we got a 200 back */
+ status_code_length = sizeof(status_code);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &status_code, &status_code_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve status code");
+ return -1;
+ }
+
+ /* The implementation of WinHTTP prior to Windows 7 will not
+ * redirect to an identical URI. Some Git hosters use self-redirects
+ * as part of their DoS mitigation strategy. Check first to see if we
+ * have a redirect status code, and that we haven't already streamed
+ * a post body. (We can't replay a streamed POST.) */
+ if (!s->chunked &&
+ (HTTP_STATUS_MOVED == status_code ||
+ HTTP_STATUS_REDIRECT == status_code ||
+ (HTTP_STATUS_REDIRECT_METHOD == status_code &&
+ get_verb == s->verb) ||
+ HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
+
+ /* Check for Windows 7. This workaround is only necessary on
+ * Windows Vista and earlier. Windows 7 is version 6.1. */
+ if (!git_has_win32_version(6, 1)) {
+ wchar_t *location;
+ DWORD location_length;
+ int redirect_cmp;
+
+ /* OK, fetch the Location header from the redirect. */
+ if (WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WINHTTP_NO_OUTPUT_BUFFER,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ return -1;
+ }
+
+ location = git__malloc(location_length);
+ GITERR_CHECK_ALLOC(location);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ location,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ git__free(location);
+ return -1;
+ }
+
+ /* Compare the Location header with the request URI */
+ redirect_cmp = wcscmp(location, s->request_uri);
+ git__free(location);
+
+ if (!redirect_cmp) {
+ /* Replay the request */
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ goto replay;
+ }
+ }
+ }
+
+ /* Handle authentication failures */
+ if (HTTP_STATUS_DENIED == status_code &&
+ get_verb == s->verb && t->owner->cred_acquire_cb) {
+ int allowed_types;
+
+ if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
+ return -1;
+
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+
+ if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->user_from_url, allowed_types, t->owner->cred_acquire_payload) < 0)
+ return -1;
+
+ assert(t->cred);
+
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ /* Successfully acquired a credential */
+ goto replay;
+ }
+ }
+
+ if (HTTP_STATUS_OK != status_code) {
+ giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
+ return -1;
+ }
+
+ /* Verify that we got the correct content-type back */
+ if (post_verb == s->verb)
+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
+ else
+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
+
+ git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8);
+ content_type_length = sizeof(content_type);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_CONTENT_TYPE,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &content_type, &content_type_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve response content-type");
+ return -1;
+ }
+
+ if (wcscmp(expected_content_type, content_type)) {
+ giterr_set(GITERR_NET, "Received unexpected content-type");
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ if (!WinHttpReadData(s->request,
+ (LPVOID)buffer,
+ (DWORD)buf_size,
+ &dw_bytes_read))
+ {
+ giterr_set(GITERR_OS, "Failed to read data");
+ return -1;
+ }
+
+ *bytes_read = dw_bytes_read;
+
+ return 0;
+}
+
+static int winhttp_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* This implementation of write permits only a single call. */
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ (DWORD)len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+
+ if (!WinHttpWriteData(s->request,
+ (LPCVOID)buffer,
+ (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
+{
+ UUID uuid;
+ RPC_STATUS status = UuidCreate(&uuid);
+ HRESULT result;
+
+ if (RPC_S_OK != status &&
+ RPC_S_UUID_LOCAL_ONLY != status &&
+ RPC_S_UUID_NO_ADDRESS != status) {
+ giterr_set(GITERR_NET, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer too small for name of temp file");
+ return -1;
+ }
+
+ result = StringCbPrintfW(
+ buffer, buffer_len_cch,
+ L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+ uuid.Data1, uuid.Data2, uuid.Data3,
+ uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
+ uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
+
+ if (FAILED(result)) {
+ giterr_set(GITERR_OS, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ size_t len;
+
+ if (!GetTempPathW(buffer_len_cch, buffer)) {
+ giterr_set(GITERR_OS, "Failed to get temp path");
+ return -1;
+ }
+
+ len = wcslen(buffer);
+
+ if (buffer[len - 1] != '\\' && len < buffer_len_cch)
+ buffer[len++] = '\\';
+
+ if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int winhttp_stream_write_buffered(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* Buffer the payload, using a temporary file so we delegate
+ * memory management of the data to the operating system. */
+ if (!s->post_body) {
+ wchar_t temp_path[MAX_PATH + 1];
+
+ if (get_temp_file(temp_path, MAX_PATH + 1) < 0)
+ return -1;
+
+ s->post_body = CreateFileW(temp_path,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_DELETE, NULL,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == s->post_body) {
+ s->post_body = NULL;
+ giterr_set(GITERR_OS, "Failed to create temporary file");
+ return -1;
+ }
+ }
+
+ if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {
+ giterr_set(GITERR_OS, "Failed to write to temporary file");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ s->post_body_len += bytes_written;
+
+ return 0;
+}
+
+static int winhttp_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->sent_request) {
+ /* Send Transfer-Encoding: chunked header */
+ if (!WinHttpAddRequestHeaders(s->request,
+ transfer_encoding, (ULONG) -1L,
+ WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (len > CACHED_POST_BODY_BUF_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(s->request, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, (int)len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Is there any remaining data from the source? */
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = (unsigned int)len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void winhttp_stream_free(git_smart_subtransport_stream *stream)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+
+ if (s->chunk_buffer) {
+ git__free(s->chunk_buffer);
+ s->chunk_buffer = NULL;
+ }
+
+ if (s->post_body) {
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (s->request_uri) {
+ git__free(s->request_uri);
+ s->request_uri = NULL;
+ }
+
+ if (s->request) {
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ }
+
+ git__free(s);
+}
+
+static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(winhttp_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = winhttp_stream_read;
+ s->parent.write = winhttp_stream_write_single;
+ s->parent.free = winhttp_stream_free;
+
+ *stream = s;
+
+ return 0;
+}
+
+static int winhttp_connect(
+ winhttp_subtransport *t,
+ const char *url)
+{
+ wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
+ wchar_t host[GIT_WIN_PATH];
+ int32_t port;
+ const char *default_port;
+ int ret;
+
+ if (!git__prefixcmp(url, prefix_http)) {
+ url = url + strlen(prefix_http);
+ default_port = "80";
+ }
+
+ if (!git__prefixcmp(url, prefix_https)) {
+ url += strlen(prefix_https);
+ default_port = "443";
+ t->use_ssl = 1;
+ }
+
+ if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url,
+ &t->pass_from_url, url, default_port)) < 0)
+ return ret;
+
+ t->path = strchr(url, '/');
+
+ /* Prepare port */
+ if (git__strtol32(&port, t->port, NULL, 10) < 0)
+ return -1;
+
+ /* Prepare host */
+ git__utf8_to_16(host, GIT_WIN_PATH, t->host);
+
+ /* Establish session */
+ t->session = WinHttpOpen(
+ ua,
+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+
+ if (!t->session) {
+ giterr_set(GITERR_OS, "Failed to init WinHTTP");
+ return -1;
+ }
+
+ /* Establish connection */
+ t->connection = WinHttpConnect(
+ t->session,
+ host,
+ port,
+ 0);
+
+ if (!t->connection) {
+ giterr_set(GITERR_OS, "Failed to connect to host");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int winhttp_uploadpack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_uploadpack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ /* WinHTTP only supports Transfer-Encoding: chunked
+ * on Windows Vista (NT 6.0) and higher. */
+ s->chunked = git_has_win32_version(6, 0);
+
+ if (s->chunked)
+ s->parent.write = winhttp_stream_write_chunked;
+ else
+ s->parent.write = winhttp_stream_write_buffered;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ winhttp_stream *s;
+ int ret = -1;
+
+ if (!t->connection &&
+ winhttp_connect(t, url) < 0)
+ return -1;
+
+ if (winhttp_stream_alloc(t, &s) < 0)
+ return -1;
+
+ if (!stream)
+ return -1;
+
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ ret = winhttp_uploadpack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_UPLOADPACK:
+ ret = winhttp_uploadpack(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ ret = winhttp_receivepack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK:
+ ret = winhttp_receivepack(t, s);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ if (!ret)
+ *stream = &s->parent;
+
+ return ret;
+}
+
+static int winhttp_close(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ int ret = 0;
+
+ if (t->host) {
+ git__free(t->host);
+ t->host = NULL;
+ }
+
+ if (t->port) {
+ git__free(t->port);
+ t->port = NULL;
+ }
+
+ if (t->user_from_url) {
+ git__free(t->user_from_url);
+ t->user_from_url = NULL;
+ }
+
+ if (t->pass_from_url) {
+ git__free(t->pass_from_url);
+ t->pass_from_url = NULL;
+ }
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
+ if (t->connection) {
+ if (!WinHttpCloseHandle(t->connection)) {
+ giterr_set(GITERR_OS, "Unable to close connection");
+ ret = -1;
+ }
+
+ t->connection = NULL;
+ }
+
+ if (t->session) {
+ if (!WinHttpCloseHandle(t->session)) {
+ giterr_set(GITERR_OS, "Unable to close session");
+ ret = -1;
+ }
+
+ t->session = NULL;
+ }
+
+ return ret;
+}
+
+static void winhttp_free(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+
+ winhttp_close(subtransport);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
+{
+ winhttp_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(winhttp_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = winhttp_action;
+ t->parent.close = winhttp_close;
+ t->parent.free = winhttp_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_WINHTTP */
diff --git a/src/tree-cache.c b/src/tree-cache.c
index ebc2c6807..97ffc2acf 100644
--- a/src/tree-cache.c
+++ b/src/tree-cache.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -69,7 +69,7 @@ const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char
return NULL;
}
- if (end == NULL || end + 1 == '\0')
+ if (end == NULL || *end + 1 == '\0')
return tree;
ptr = end + 1;
@@ -104,7 +104,7 @@ static int read_tree_internal(git_tree_cache **out,
tree->name[name_len] = '\0';
/* Blank-terminated ASCII decimal number of entries in this tree */
- if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < -1)
+ if (git__strtol32(&count, buffer, &buffer, 10) < 0)
goto corrupted;
tree->entries = count;
diff --git a/src/tree-cache.h b/src/tree-cache.h
index 41fde997a..805483a78 100644
--- a/src/tree-cache.h
+++ b/src/tree-cache.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/tree.c b/src/tree.c
index 92b1b1e39..17b3c378d 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,29 +12,89 @@
#include "git2/object.h"
#define DEFAULT_TREE_SIZE 16
-#define MAX_FILEMODE 0777777
#define MAX_FILEMODE_BYTES 6
-static int valid_attributes(const int attributes)
+static bool valid_filemode(const int filemode)
{
- return attributes >= 0 && attributes <= MAX_FILEMODE;
+ return (filemode == GIT_FILEMODE_TREE
+ || filemode == GIT_FILEMODE_BLOB
+ || filemode == GIT_FILEMODE_BLOB_EXECUTABLE
+ || filemode == GIT_FILEMODE_LINK
+ || filemode == GIT_FILEMODE_COMMIT);
+}
+
+GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
+{
+ /* Tree bits set, but it's not a commit */
+ if (filemode & GIT_FILEMODE_TREE && !(filemode & 0100000))
+ return GIT_FILEMODE_TREE;
+
+ /* If any of the x bits is set */
+ if (filemode & 0111)
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ /* 16XXXX means commit */
+ if ((filemode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT)
+ return GIT_FILEMODE_COMMIT;
+
+ /* 12XXXX means commit */
+ if ((filemode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
+ return GIT_FILEMODE_LINK;
+
+ /* Otherwise, return a blob */
+ return GIT_FILEMODE_BLOB;
}
static int valid_entry_name(const char *filename)
{
- return strlen(filename) > 0 && strchr(filename, '/') == NULL;
+ return *filename != '\0' &&
+ strchr(filename, '/') == NULL &&
+ (*filename != '.' ||
+ (strcmp(filename, ".") != 0 &&
+ strcmp(filename, "..") != 0 &&
+ strcmp(filename, DOT_GIT) != 0));
}
static int entry_sort_cmp(const void *a, const void *b)
{
- const git_tree_entry *entry_a = (const git_tree_entry *)(a);
- const git_tree_entry *entry_b = (const git_tree_entry *)(b);
+ const git_tree_entry *e1 = (const git_tree_entry *)a;
+ const git_tree_entry *e2 = (const git_tree_entry *)b;
+
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncmp);
+}
+
+int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
+ return entry_sort_cmp(e1, e2);
+}
+int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
return git_path_cmp(
- entry_a->filename, entry_a->filename_len, git_tree_entry__is_tree(entry_a),
- entry_b->filename, entry_b->filename_len, git_tree_entry__is_tree(entry_b));
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncasecmp);
}
+static git_tree_entry *alloc_entry(const char *filename)
+{
+ git_tree_entry *entry = NULL;
+ size_t filename_len = strlen(filename);
+
+ entry = git__malloc(sizeof(git_tree_entry) + filename_len + 1);
+ if (!entry)
+ return NULL;
+
+ memset(entry, 0x0, sizeof(git_tree_entry));
+ memcpy(entry->filename, filename, filename_len);
+ entry->filename[filename_len] = 0;
+ entry->filename_len = filename_len;
+
+ return entry;
+}
struct tree_key_search {
const char *filename;
@@ -76,74 +136,114 @@ static int homing_search_cmp(const void *key, const void *array_member)
* ambiguous because of folder vs file sorting, we look linearly
* around the area for our target file.
*/
-static int tree_key_search(git_vector *entries, const char *filename)
+static int tree_key_search(
+ size_t *at_pos, git_vector *entries, const char *filename, size_t filename_len)
{
struct tree_key_search ksearch;
const git_tree_entry *entry;
-
- int homing, i;
+ size_t homing, i;
ksearch.filename = filename;
- ksearch.filename_len = strlen(filename);
+ ksearch.filename_len = filename_len;
/* Initial homing search; find an entry on the tree with
* the same prefix as the filename we're looking for */
- homing = git_vector_bsearch2(entries, &homing_search_cmp, &ksearch);
- if (homing < 0)
- return homing;
+ if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0)
+ return GIT_ENOTFOUND;
/* We found a common prefix. Look forward as long as
* there are entries that share the common prefix */
- for (i = homing; i < (int)entries->length; ++i) {
+ for (i = homing; i < entries->length; ++i) {
entry = entries->contents[i];
if (homing_search_cmp(&ksearch, entry) < 0)
break;
- if (strcmp(filename, entry->filename) == 0)
- return i;
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
}
/* If we haven't found our filename yet, look backwards
* too as long as we have entries with the same prefix */
- for (i = homing - 1; i >= 0; --i) {
- entry = entries->contents[i];
+ if (homing > 0) {
+ i = homing - 1;
- if (homing_search_cmp(&ksearch, entry) > 0)
- break;
+ do {
+ entry = entries->contents[i];
+
+ if (homing_search_cmp(&ksearch, entry) > 0)
+ break;
- if (strcmp(filename, entry->filename) == 0)
- return i;
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ } while (i-- > 0);
}
/* The filename doesn't exist at all */
return GIT_ENOTFOUND;
}
-void git_tree__free(git_tree *tree)
+void git_tree_entry_free(git_tree_entry *entry)
{
- unsigned int i;
+ if (entry == NULL)
+ return;
- for (i = 0; i < tree->entries.length; ++i) {
- git_tree_entry *e;
- e = git_vector_get(&tree->entries, i);
+ git__free(entry);
+}
- git__free(e->filename);
- git__free(e);
- }
+git_tree_entry *git_tree_entry_dup(const git_tree_entry *entry)
+{
+ size_t total_size;
+ git_tree_entry *copy;
+
+ assert(entry);
+
+ total_size = sizeof(git_tree_entry) + entry->filename_len + 1;
+
+ copy = git__malloc(total_size);
+ if (!copy)
+ return NULL;
+
+ memcpy(copy, entry, total_size);
+
+ return copy;
+}
+
+void git_tree__free(git_tree *tree)
+{
+ size_t i;
+ git_tree_entry *e;
+
+ git_vector_foreach(&tree->entries, i, e)
+ git_tree_entry_free(e);
git_vector_free(&tree->entries);
git__free(tree);
}
-const git_oid *git_tree_id(git_tree *c)
+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_id((git_object *)c);
+ return git_object_owner((const git_object *)t);
}
-unsigned int git_tree_entry_attributes(const git_tree_entry *entry)
+git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
{
- return entry->attr;
+ return (git_filemode_t)entry->attr;
}
const char *git_tree_entry_name(const git_tree_entry *entry)
@@ -179,36 +279,61 @@ int git_tree_entry_to_object(
return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY);
}
-const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename)
+static const git_tree_entry *entry_fromname(
+ git_tree *tree, const char *name, size_t name_len)
{
- int idx;
-
- assert(tree && filename);
+ size_t idx;
- idx = tree_key_search(&tree->entries, filename);
- if (idx == GIT_ENOTFOUND)
+ if (tree_key_search(&idx, &tree->entries, name, name_len) < 0)
return NULL;
return git_vector_get(&tree->entries, idx);
}
-const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx)
+const git_tree_entry *git_tree_entry_byname(
+ git_tree *tree, const char *filename)
+{
+ assert(tree && filename);
+ return entry_fromname(tree, filename, strlen(filename));
+}
+
+const git_tree_entry *git_tree_entry_byindex(
+ git_tree *tree, size_t idx)
{
assert(tree);
return git_vector_get(&tree->entries, idx);
}
+const git_tree_entry *git_tree_entry_byoid(
+ const git_tree *tree, const git_oid *oid)
+{
+ size_t i;
+ const git_tree_entry *e;
+
+ assert(tree);
+
+ git_vector_foreach(&tree->entries, i, e) {
+ if (memcmp(&e->oid.id, &oid->id, sizeof(oid->id)) == 0)
+ return e;
+ }
+
+ return NULL;
+}
+
int git_tree__prefix_position(git_tree *tree, const char *path)
{
git_vector *entries = &tree->entries;
struct tree_key_search ksearch;
- unsigned int at_pos;
+ size_t at_pos;
+
+ if (!path)
+ return 0;
ksearch.filename = path;
ksearch.filename_len = strlen(path);
/* Find tree entry with appropriate prefix */
- git_vector_bsearch3(&at_pos, entries, &homing_search_cmp, &ksearch);
+ git_vector_bsearch2(&at_pos, entries, &homing_search_cmp, &ksearch);
for (; at_pos < entries->length; ++at_pos) {
const git_tree_entry *entry = entries->contents[at_pos];
@@ -222,18 +347,27 @@ int git_tree__prefix_position(git_tree *tree, const char *path)
break;
}
- return at_pos;
+ return (int)at_pos;
}
-unsigned int git_tree_entrycount(git_tree *tree)
+size_t git_tree_entrycount(const git_tree *tree)
{
assert(tree);
return tree->entries.length;
}
-static int tree_error(const char *str)
+unsigned int git_treebuilder_entrycount(git_treebuilder *bld)
+{
+ assert(bld);
+ return (unsigned int)bld->entrycount;
+}
+
+static int tree_error(const char *str, const char *path)
{
- giterr_set(GITERR_TREE, "%s", str);
+ if (path)
+ giterr_set(GITERR_TREE, "%s - %s", str, path);
+ else
+ giterr_set(GITERR_TREE, "%s", str);
return -1;
}
@@ -244,28 +378,31 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf
while (buffer < buffer_end) {
git_tree_entry *entry;
- int tmp;
-
- entry = git__calloc(1, sizeof(git_tree_entry));
- GITERR_CHECK_ALLOC(entry);
+ int attr;
- if (git_vector_insert(&tree->entries, entry) < 0)
- return -1;
-
- if (git__strtol32(&tmp, buffer, &buffer, 8) < 0 ||
- !buffer || !valid_attributes(tmp))
- return tree_error("Failed to parse tree. Can't parse attributes");
+ if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer)
+ return tree_error("Failed to parse tree. Can't parse filemode", NULL);
- entry->attr = tmp;
+ attr = normalize_filemode(attr); /* make sure to normalize the filemode */
if (*buffer++ != ' ')
- return tree_error("Failed to parse tree. Object is corrupted");
+ return tree_error("Failed to parse tree. Object is corrupted", NULL);
if (memchr(buffer, 0, buffer_end - buffer) == NULL)
- return tree_error("Failed to parse tree. Object is corrupted");
+ return tree_error("Failed to parse tree. Object is corrupted", NULL);
+
+ /** Allocate the entry and store it in the entries vector */
+ {
+ entry = alloc_entry(buffer);
+ GITERR_CHECK_ALLOC(entry);
- entry->filename = git__strdup(buffer);
- entry->filename_len = strlen(buffer);
+ if (git_vector_insert(&tree->entries, entry) < 0) {
+ git__free(entry);
+ return -1;
+ }
+
+ entry->attr = attr;
+ }
while (buffer < buffer_end && *buffer != 0)
buffer++;
@@ -285,14 +422,13 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj)
return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len);
}
-static unsigned int find_next_dir(const char *dirname, git_index *index, unsigned int start)
+static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
{
- unsigned int i, entries = git_index_entrycount(index);
- size_t dirlen;
+ size_t dirlen, i, entries = git_index_entrycount(index);
dirlen = strlen(dirname);
for (i = start; i < entries; ++i) {
- git_index_entry *entry = git_index_get(index, i);
+ const git_index_entry *entry = git_index_get_byindex(index, i);
if (strlen(entry->path) < dirlen ||
memcmp(entry->path, dirname, dirlen) ||
(dirlen > 0 && entry->path[dirlen] != '/')) {
@@ -303,22 +439,29 @@ static unsigned int find_next_dir(const char *dirname, git_index *index, unsigne
return i;
}
-static int append_entry(git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)
+static int append_entry(
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
{
git_tree_entry *entry;
- entry = git__calloc(1, sizeof(git_tree_entry));
- GITERR_CHECK_ALLOC(entry);
+ if (!valid_entry_name(filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
- entry->filename = git__strdup(filename);
- entry->filename_len = strlen(entry->filename);
+ entry = alloc_entry(filename);
+ GITERR_CHECK_ALLOC(entry);
git_oid_cpy(&entry->oid, id);
- entry->attr = attributes;
+ entry->attr = (uint16_t)filemode;
- if (git_vector_insert(&bld->entries, entry) < 0)
+ if (git_vector_insert(&bld->entries, entry) < 0) {
+ git__free(entry);
return -1;
+ }
+ bld->entrycount++;
return 0;
}
@@ -327,11 +470,10 @@ static int write_tree(
git_repository *repo,
git_index *index,
const char *dirname,
- unsigned int start)
+ size_t start)
{
git_treebuilder *bld = NULL;
-
- unsigned int i, entries = git_index_entrycount(index);
+ size_t i, entries = git_index_entrycount(index);
int error;
size_t dirname_len = strlen(dirname);
const git_tree_cache *cache;
@@ -339,13 +481,11 @@ static int write_tree(
cache = git_tree_cache_get(index->tree, dirname);
if (cache != NULL && cache->entries >= 0){
git_oid_cpy(oid, &cache->oid);
- return find_next_dir(dirname, index, start);
+ return (int)find_next_dir(dirname, index, start);
}
- error = git_treebuilder_create(&bld, NULL);
- if (bld == NULL) {
+ if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL)
return -1;
- }
/*
* This loop is unfortunate, but necessary. The index doesn't have
@@ -353,8 +493,8 @@ static int write_tree(
* need to keep track of the current position.
*/
for (i = start; i < entries; ++i) {
- git_index_entry *entry = git_index_get(index, i);
- char *filename, *next_slash;
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ const char *filename, *next_slash;
/*
* If we've left our (sub)tree, exit the loop and return. The
@@ -385,7 +525,8 @@ 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");
+ tree_error("Failed to write subtree", subdir);
+ git__free(subdir);
goto on_error;
} else {
i = written - 1; /* -1 because of the loop increment */
@@ -403,18 +544,15 @@ static int write_tree(
} else {
last_comp = subdir;
}
+
error = append_entry(bld, last_comp, &sub_oid, S_IFDIR);
git__free(subdir);
- if (error < 0) {
- tree_error("Failed to insert dir");
+ if (error < 0)
goto on_error;
- }
} else {
error = append_entry(bld, filename, &entry->oid, entry->mode);
- if (error < 0) {
- tree_error("Failed to insert file");
+ if (error < 0)
goto on_error;
- }
}
}
@@ -422,43 +560,54 @@ static int write_tree(
goto on_error;
git_treebuilder_free(bld);
- return i;
+ return (int)i;
on_error:
git_treebuilder_free(bld);
return -1;
}
-int git_tree_create_fromindex(git_oid *oid, git_index *index)
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo)
{
int ret;
- git_repository *repo;
+ bool old_ignore_case = false;
- repo = (git_repository *)GIT_REFCOUNT_OWNER(index);
+ assert(oid && index && repo);
- if (repo == NULL)
- return tree_error("Failed to create tree. "
- "The index file is not backed up by an existing repository");
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_INDEX,
+ "Cannot create a tree from a not fully merged index.");
+ return GIT_EUNMERGED;
+ }
if (index->tree != NULL && index->tree->entries >= 0) {
git_oid_cpy(oid, &index->tree->oid);
return 0;
}
- /* The tree cache didn't help us */
+ /* The tree cache didn't help us; we'll have to write
+ * out a tree. If the index is ignore_case, we must
+ * make it case-sensitive for the duration of the tree-write
+ * operation. */
+
+ if (index->ignore_case) {
+ old_ignore_case = true;
+ git_index__set_ignore_case(index, false);
+ }
+
ret = write_tree(oid, repo, index, "", 0);
- return ret < 0 ? ret : 0;
-}
-static void sort_entries(git_treebuilder *bld)
-{
- git_vector_sort(&bld->entries);
+ if (old_ignore_case)
+ git_index__set_ignore_case(index, true);
+
+ return ret < 0 ? ret : 0;
}
int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
{
git_treebuilder *bld;
- unsigned int i, source_entries = DEFAULT_TREE_SIZE;
+ size_t i, source_entries = DEFAULT_TREE_SIZE;
assert(builder_p);
@@ -472,10 +621,13 @@ int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
goto on_error;
if (source != NULL) {
- for (i = 0; i < source->entries.length; ++i) {
- git_tree_entry *entry_src = source->entries.contents[i];
+ git_tree_entry *entry_src;
- if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0)
+ git_vector_foreach(&source->entries, i, entry_src) {
+ if (append_entry(
+ bld, entry_src->filename,
+ &entry_src->oid,
+ entry_src->attr) < 0)
goto on_error;
}
}
@@ -488,42 +640,46 @@ on_error:
return -1;
}
-int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)
+int git_treebuilder_insert(
+ const git_tree_entry **entry_out,
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
{
git_tree_entry *entry;
- int pos;
+ size_t pos;
assert(bld && id && filename);
- if (!valid_attributes(attributes))
- return tree_error("Failed to insert entry. Invalid attributes");
+ if (!valid_filemode(filemode))
+ return tree_error("Failed to insert entry. Invalid filemode for file", filename);
if (!valid_entry_name(filename))
- return tree_error("Failed to insert entry. Invalid name for a tree entry");
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
- pos = tree_key_search(&bld->entries, filename);
-
- if (pos >= 0) {
+ if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) {
entry = git_vector_get(&bld->entries, pos);
- if (entry->removed)
+ if (entry->removed) {
entry->removed = 0;
+ bld->entrycount++;
+ }
} else {
- entry = git__calloc(1, sizeof(git_tree_entry));
+ entry = alloc_entry(filename);
GITERR_CHECK_ALLOC(entry);
- entry->filename = git__strdup(filename);
- entry->filename_len = strlen(entry->filename);
+ if (git_vector_insert(&bld->entries, entry) < 0) {
+ git__free(entry);
+ return -1;
+ }
+
+ bld->entrycount++;
}
git_oid_cpy(&entry->oid, id);
- entry->attr = attributes;
-
- if (pos == GIT_ENOTFOUND) {
- if (git_vector_insert(&bld->entries, entry) < 0)
- return -1;
- }
+ entry->attr = filemode;
- if (entry_out != NULL)
+ if (entry_out)
*entry_out = entry;
return 0;
@@ -531,13 +687,12 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con
static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
{
- int idx;
+ size_t idx;
git_tree_entry *entry;
assert(bld && filename);
- idx = tree_key_search(&bld->entries, filename);
- if (idx < 0)
+ if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0)
return NULL;
entry = git_vector_get(&bld->entries, idx);
@@ -557,27 +712,29 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
git_tree_entry *remove_ptr = treebuilder_get(bld, filename);
if (remove_ptr == NULL || remove_ptr->removed)
- return tree_error("Failed to remove entry. File isn't in the tree");
+ return tree_error("Failed to remove entry. File isn't in the tree", filename);
remove_ptr->removed = 1;
+ bld->entrycount--;
return 0;
}
int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
{
- unsigned int i;
+ int error = 0;
+ size_t i;
git_buf tree = GIT_BUF_INIT;
git_odb *odb;
assert(bld);
- sort_entries(bld);
+ git_vector_sort(&bld->entries);
/* Grow the buffer beforehand to an estimated size */
- git_buf_grow(&tree, bld->entries.length * 72);
+ error = git_buf_grow(&tree, bld->entries.length * 72);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *entry = bld->entries.contents[i];
+ for (i = 0; i < bld->entries.length && !error; ++i) {
+ git_tree_entry *entry = git_vector_get(&bld->entries, i);
if (entry->removed)
continue;
@@ -585,153 +742,154 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b
git_buf_printf(&tree, "%o ", entry->attr);
git_buf_put(&tree, entry->filename, entry->filename_len + 1);
git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ);
- }
- if (git_buf_oom(&tree))
- goto on_error;
-
- if (git_repository_odb__weakptr(&odb, repo) < 0)
- goto on_error;
-
-
- if (git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE) < 0)
- goto on_error;
+ if (git_buf_oom(&tree))
+ error = -1;
+ }
- git_buf_free(&tree);
- return 0;
+ if (!error &&
+ !(error = git_repository_odb__weakptr(&odb, repo)))
+ error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
-on_error:
git_buf_free(&tree);
- return -1;
+ return error;
}
-void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload)
+void git_treebuilder_filter(
+ git_treebuilder *bld,
+ git_treebuilder_filter_cb filter,
+ void *payload)
{
- unsigned int i;
+ size_t i;
+ git_tree_entry *entry;
assert(bld && filter);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *entry = bld->entries.contents[i];
- if (!entry->removed && filter(entry, payload))
+ git_vector_foreach(&bld->entries, i, entry) {
+ if (!entry->removed && filter(entry, payload)) {
entry->removed = 1;
+ bld->entrycount--;
+ }
}
}
void git_treebuilder_clear(git_treebuilder *bld)
{
- unsigned int i;
+ size_t i;
+ git_tree_entry *e;
+
assert(bld);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *e = bld->entries.contents[i];
- git__free(e->filename);
- git__free(e);
- }
+ git_vector_foreach(&bld->entries, i, e)
+ git_tree_entry_free(e);
git_vector_clear(&bld->entries);
+ bld->entrycount = 0;
}
void git_treebuilder_free(git_treebuilder *bld)
{
+ if (bld == NULL)
+ return;
+
git_treebuilder_clear(bld);
git_vector_free(&bld->entries);
git__free(bld);
}
-static int tree_frompath(
- git_tree **parent_out,
+static size_t subpath_len(const char *path)
+{
+ const char *slash_pos = strchr(path, '/');
+ if (slash_pos == NULL)
+ return strlen(path);
+
+ return slash_pos - path;
+}
+
+int git_tree_entry_bypath(
+ git_tree_entry **entry_out,
git_tree *root,
- git_buf *treeentry_path,
- size_t offset)
+ const char *path)
{
- char *slash_pos = NULL;
- const git_tree_entry* entry;
int error = 0;
git_tree *subtree;
+ const git_tree_entry *entry;
+ size_t filename_len;
- if (!*(treeentry_path->ptr + offset)) {
- giterr_set(GITERR_INVALID,
- "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr);
- return -1;
- }
-
- slash_pos = (char *)strchr(treeentry_path->ptr + offset, '/');
+ /* Find how long is the current path component (i.e.
+ * the filename between two slashes */
+ filename_len = subpath_len(path);
- if (slash_pos == NULL)
- return git_tree_lookup(
- parent_out,
- root->object.repo,
- git_object_id((const git_object *)root)
- );
-
- if (slash_pos == treeentry_path->ptr + offset) {
- giterr_set(GITERR_INVALID,
- "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr);
- return -1;
+ if (filename_len == 0) {
+ giterr_set(GITERR_TREE, "Invalid tree path given");
+ return GIT_ENOTFOUND;
}
- *slash_pos = '\0';
-
- entry = git_tree_entry_byname(root, treeentry_path->ptr + offset);
-
- if (slash_pos != NULL)
- *slash_pos = '/';
+ entry = entry_fromname(root, path, filename_len);
if (entry == NULL) {
giterr_set(GITERR_TREE,
- "No tree entry can be found from "
- "the given tree and relative path '%s'.", treeentry_path->ptr);
+ "The path '%s' does not exist in the given tree", path);
return GIT_ENOTFOUND;
}
+ switch (path[filename_len]) {
+ case '/':
+ /* If there are more components in the path...
+ * then this entry *must* be a tree */
+ if (!git_tree_entry__is_tree(entry)) {
+ giterr_set(GITERR_TREE,
+ "The path '%s' does not exist in the given tree", path);
+ return GIT_ENOTFOUND;
+ }
+
+ /* If there's only a slash left in the path, we
+ * return the current entry; otherwise, we keep
+ * walking down the path */
+ if (path[filename_len + 1] != '\0')
+ break;
+
+ case '\0':
+ /* If there are no more components in the path, return
+ * this entry */
+ *entry_out = git_tree_entry_dup(entry);
+ return 0;
+ }
if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0)
- return error;
+ return -1;
- error = tree_frompath(
- parent_out,
+ error = git_tree_entry_bypath(
+ entry_out,
subtree,
- treeentry_path,
- (slash_pos - treeentry_path->ptr) + 1
+ path + filename_len + 1
);
git_tree_free(subtree);
return error;
}
-int git_tree_get_subtree(
- git_tree **subtree,
- git_tree *root,
- const char *subtree_path)
-{
- int error;
- git_buf buffer = GIT_BUF_INIT;
-
- assert(subtree && root && subtree_path);
-
- if ((error = git_buf_sets(&buffer, subtree_path)) == 0)
- error = tree_frompath(subtree, root, &buffer, 0);
-
- git_buf_free(&buffer);
-
- return error;
-}
-
-static int tree_walk_post(
- git_tree *tree,
+static int tree_walk(
+ const git_tree *tree,
git_treewalk_cb callback,
git_buf *path,
- void *payload)
+ void *payload,
+ bool preorder)
{
int error = 0;
- unsigned int i;
-
- for (i = 0; i < tree->entries.length; ++i) {
- git_tree_entry *entry = tree->entries.contents[i];
+ size_t i;
+ const git_tree_entry *entry;
- if (callback(path->ptr, entry, payload) < 0)
- continue;
+ git_vector_foreach(&tree->entries, i, entry) {
+ if (preorder) {
+ error = callback(path->ptr, entry, payload);
+ if (error > 0)
+ continue;
+ if (error < 0) {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+ }
if (git_tree_entry__is_tree(entry)) {
git_tree *subtree;
@@ -748,36 +906,41 @@ static int tree_walk_post(
if (git_buf_oom(path))
return -1;
- if (tree_walk_post(subtree, callback, path, payload) < 0)
- return -1;
+ error = tree_walk(subtree, callback, path, payload, preorder);
+ if (error != 0)
+ break;
git_buf_truncate(path, path_len);
git_tree_free(subtree);
}
+
+ if (!preorder && callback(path->ptr, entry, payload) < 0) {
+ giterr_clear();
+ error = GIT_EUSER;
+ break;
+ }
}
- return 0;
+ return error;
}
-int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload)
+int git_tree_walk(
+ const git_tree *tree,
+ git_treewalk_mode mode,
+ git_treewalk_cb callback,
+ void *payload)
{
int error = 0;
git_buf root_path = GIT_BUF_INIT;
- switch (mode) {
- case GIT_TREEWALK_POST:
- error = tree_walk_post(tree, callback, &root_path, payload);
- break;
-
- case GIT_TREEWALK_PRE:
- tree_error("Preorder tree walking is still not implemented");
- return -1;
-
- default:
- giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
- return -1;
+ if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {
+ giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
+ return -1;
}
+ error = tree_walk(
+ tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE));
+
git_buf_free(&root_path);
return error;
diff --git a/src/tree.h b/src/tree.h
index 498a90d66..b77bfd961 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -13,11 +13,11 @@
#include "vector.h"
struct git_tree_entry {
- unsigned int attr;
- char *filename;
+ uint16_t removed;
+ uint16_t attr;
git_oid oid;
size_t filename_len;
- int removed;
+ char filename[1];
};
struct git_tree {
@@ -27,14 +27,16 @@ struct git_tree {
struct git_treebuilder {
git_vector entries;
+ size_t entrycount; /* vector may contain "removed" entries */
};
-
GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e)
{
return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr));
}
+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);
@@ -48,4 +50,15 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj);
int git_tree__prefix_position(git_tree *tree, const char *prefix);
+/**
+ * Write a tree to the given repository
+ */
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo);
+
+/**
+ * Obsolete mode kept for compatibility reasons
+ */
+#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664
+
#endif
diff --git a/src/tsort.c b/src/tsort.c
index f54c21e50..4885e435b 100644
--- a/src/tsort.c
+++ b/src/tsort.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -23,9 +23,8 @@
# define MIN(x,y) (((x) < (y) ? (x) : (y)))
#endif
-typedef int (*cmp_ptr_t)(const void *, const void *);
-
-static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
+static int binsearch(
+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
{
int l, c, r;
void *lx, *cx;
@@ -38,12 +37,12 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
lx = dst[l];
/* check for beginning conditions */
- if (cmp(x, lx) < 0)
+ if (cmp(x, lx, payload) < 0)
return 0;
- else if (cmp(x, lx) == 0) {
+ else if (cmp(x, lx, payload) == 0) {
int i = 1;
- while (cmp(x, dst[i]) == 0)
+ while (cmp(x, dst[i], payload) == 0)
i++;
return i;
}
@@ -51,7 +50,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
/* guaranteed not to be >= rx */
cx = dst[c];
while (1) {
- const int val = cmp(x, cx);
+ const int val = cmp(x, cx, payload);
if (val < 0) {
if (c - l <= 1) return c;
r = c;
@@ -62,7 +61,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
} else {
do {
cx = dst[++c];
- } while (cmp(x, cx) == 0);
+ } while (cmp(x, cx, payload) == 0);
return c;
}
c = l + ((r - l) >> 1);
@@ -71,7 +70,8 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
}
/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
-static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp)
+static void bisort(
+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
{
size_t i;
void *x;
@@ -80,12 +80,12 @@ static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp)
for (i = start; i < size; i++) {
int j;
/* If this entry is already correct, just move along */
- if (cmp(dst[i - 1], dst[i]) <= 0)
+ if (cmp(dst[i - 1], dst[i], payload) <= 0)
continue;
/* Else we need to find the right place, shift everything over, and squeeze in */
x = dst[i];
- location = binsearch(dst, x, i, cmp);
+ location = binsearch(dst, x, i, cmp, payload);
for (j = (int)i - 1; j >= location; j--) {
dst[j + 1] = dst[j];
}
@@ -102,7 +102,8 @@ struct tsort_run {
struct tsort_store {
size_t alloc;
- cmp_ptr_t cmp;
+ git__sort_r_cmp cmp;
+ void *payload;
void **storage;
};
@@ -118,7 +119,8 @@ static void reverse_elements(void **dst, ssize_t start, ssize_t end)
}
}
-static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
+static ssize_t count_run(
+ void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
{
ssize_t curr = start + 2;
@@ -126,7 +128,7 @@ static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_s
return 1;
if (start >= size - 2) {
- if (store->cmp(dst[size - 2], dst[size - 1]) > 0) {
+ if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) {
void *tmp = dst[size - 1];
dst[size - 1] = dst[size - 2];
dst[size - 2] = tmp;
@@ -135,13 +137,15 @@ static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_s
return 2;
}
- if (store->cmp(dst[start], dst[start + 1]) <= 0) {
- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0)
+ if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0)
curr++;
return curr - start;
} else {
- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0)
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) > 0)
curr++;
/* reverse in-place */
@@ -219,7 +223,7 @@ static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr,
for (k = curr; k < curr + A + B; k++) {
if ((i < A) && (j < curr + A + B)) {
- if (store->cmp(storage[i], dst[j]) <= 0)
+ if (store->cmp(storage[i], dst[j], store->payload) <= 0)
dst[k] = storage[i++];
else
dst[k] = dst[j++];
@@ -235,7 +239,7 @@ static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr,
for (k = curr + A + B - 1; k >= curr; k--) {
if ((i >= 0) && (j >= curr)) {
- if (store->cmp(dst[j], storage[i]) > 0)
+ if (store->cmp(dst[j], storage[i], store->payload) > 0)
dst[k] = dst[j--];
else
dst[k] = storage[i--];
@@ -307,7 +311,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
if (run < minrun) run = minrun;\
if (run > (ssize_t)size - curr) run = size - curr;\
if (run > len) {\
- bisort(&dst[curr], len, run, cmp);\
+ bisort(&dst[curr], len, run, cmp, payload);\
len = run;\
}\
run_stack[stack_curr].start = curr;\
@@ -329,7 +333,8 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
}\
while (0)
-void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
+void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
{
struct tsort_store _store, *store = &_store;
struct tsort_run run_stack[128];
@@ -340,7 +345,7 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
ssize_t minrun;
if (size < 64) {
- bisort(dst, 1, size, cmp);
+ bisort(dst, 1, size, cmp, payload);
return;
}
@@ -351,6 +356,7 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
store->alloc = 0;
store->storage = NULL;
store->cmp = cmp;
+ store->payload = payload;
PUSH_NEXT();
PUSH_NEXT();
@@ -365,3 +371,13 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
PUSH_NEXT();
}
}
+
+static int tsort_r_cmp(const void *a, const void *b, void *payload)
+{
+ return ((git__tsort_cmp)payload)(a, b);
+}
+
+void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)
+{
+ git__tsort_r(dst, size, tsort_r_cmp, cmp);
+}
diff --git a/src/unix/map.c b/src/unix/map.c
index 772f4e247..7de99c99d 100644
--- a/src/unix/map.c
+++ b/src/unix/map.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -31,8 +31,11 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs
mflag = MAP_SHARED;
else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE)
mflag = MAP_PRIVATE;
+ else
+ mflag = MAP_SHARED;
out->data = mmap(NULL, len, mprot, mflag, fd, offset);
+
if (!out->data || out->data == MAP_FAILED) {
giterr_set(GITERR_OS, "Failed to mmap. Could not write data");
return -1;
@@ -47,6 +50,7 @@ int p_munmap(git_map *map)
{
assert(map != NULL);
munmap(map->data, map->len);
+
return 0;
}
diff --git a/src/unix/posix.h b/src/unix/posix.h
index 48b492941..f4886c5d1 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,25 +7,29 @@
#ifndef INCLUDE_posix__w32_h__
#define INCLUDE_posix__w32_h__
-#ifndef __sun
-# include <fnmatch.h>
-# define p_fnmatch(p, s, f) fnmatch(p, s, f)
-#else
-# include "compat/fnmatch.h"
-#endif
-
#include <stdio.h>
+#include <sys/param.h>
#define p_lstat(p,b) lstat(p,b)
#define p_readlink(a, b, c) readlink(a, b, c)
+#define p_symlink(o,n) symlink(o, n)
#define p_link(o,n) link(o, n)
#define p_unlink(p) unlink(p)
#define p_mkdir(p,m) mkdir(p, m)
#define p_fsync(fd) fsync(fd)
-#define p_realpath(p, po) realpath(p, po)
+
+/* The OpenBSD realpath function behaves differently */
+#if !defined(__OpenBSD__)
+# define p_realpath(p, po) realpath(p, po)
+#endif
+
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
#define p_mkstemp(p) mkstemp(p)
#define p_setenv(n,v,o) setenv(n,v,o)
+#define p_inet_pton(a, b, c) inet_pton(a, b, c)
+
+/* see win32/posix.h for explanation about why this exists */
+#define p_lstat_posixly(p,b) lstat(p,b)
#endif
diff --git a/src/unix/realpath.c b/src/unix/realpath.c
new file mode 100644
index 000000000..15601bd22
--- /dev/null
+++ b/src/unix/realpath.c
@@ -0,0 +1,30 @@
+/*
+ * 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/common.h>
+
+#ifdef __OpenBSD__
+
+#include <limits.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+char *p_realpath(const char *pathname, char *resolved)
+{
+ char *ret;
+
+ if ((ret = realpath(pathname, resolved)) == NULL)
+ return NULL;
+
+ /* Figure out if the file exists */
+ if (!access(ret, F_OK))
+ return ret;
+
+ return NULL;
+}
+
+#endif
diff --git a/src/util.c b/src/util.c
index ce770203a..8e83d298e 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <ctype.h>
#include "posix.h"
+#include "fileops.h"
#ifdef _MSC_VER
# include <Shlwapi.h>
@@ -22,6 +23,91 @@ void git_libgit2_version(int *major, int *minor, int *rev)
*rev = LIBGIT2_VER_REVISION;
}
+int git_libgit2_capabilities()
+{
+ return 0
+#ifdef GIT_THREADS
+ | GIT_CAP_THREADS
+#endif
+#if defined(GIT_SSL) || defined(GIT_WINHTTP)
+ | GIT_CAP_HTTPS
+#endif
+ ;
+}
+
+/* Declarations for tuneable settings */
+extern size_t git_mwindow__window_size;
+extern size_t git_mwindow__mapped_limit;
+extern size_t git_odb__cache_size;
+
+static int config_level_to_futils_dir(int config_level)
+{
+ int val = -1;
+
+ switch (config_level) {
+ case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_FUTILS_DIR_SYSTEM; break;
+ case GIT_CONFIG_LEVEL_XDG: val = GIT_FUTILS_DIR_XDG; break;
+ case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_FUTILS_DIR_GLOBAL; break;
+ default:
+ giterr_set(
+ GITERR_INVALID, "Invalid config path selector %d", config_level);
+ }
+
+ return val;
+}
+
+int git_libgit2_opts(int key, ...)
+{
+ int error = 0;
+ va_list ap;
+
+ va_start(ap, key);
+
+ switch (key) {
+ case GIT_OPT_SET_MWINDOW_SIZE:
+ git_mwindow__window_size = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_SIZE:
+ *(va_arg(ap, size_t *)) = git_mwindow__window_size;
+ break;
+
+ case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
+ git_mwindow__mapped_limit = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
+ *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
+ break;
+
+ case GIT_OPT_GET_SEARCH_PATH:
+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0) {
+ char *out = va_arg(ap, char *);
+ size_t outlen = va_arg(ap, size_t);
+
+ error = git_futils_dirs_get_str(out, outlen, error);
+ }
+ break;
+
+ case GIT_OPT_SET_SEARCH_PATH:
+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0)
+ error = git_futils_dirs_set(error, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_GET_ODB_CACHE_SIZE:
+ *(va_arg(ap, size_t *)) = git_odb__cache_size;
+ break;
+
+ case GIT_OPT_SET_ODB_CACHE_SIZE:
+ git_odb__cache_size = va_arg(ap, size_t);
+ break;
+ }
+
+ va_end(ap);
+
+ return error;
+}
+
void git_strarray_free(git_strarray *array)
{
size_t i;
@@ -29,6 +115,8 @@ void git_strarray_free(git_strarray *array)
git__free(array->strings[i]);
git__free(array->strings);
+
+ memset(array, 0, sizeof(*array));
}
int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
@@ -46,8 +134,10 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
GITERR_CHECK_ALLOC(tgt->strings);
for (i = 0; i < src->count; ++i) {
- tgt->strings[tgt->count] = git__strdup(src->strings[i]);
+ if (!src->strings[i])
+ continue;
+ tgt->strings[tgt->count] = git__strdup(src->strings[i]);
if (!tgt->strings[tgt->count]) {
git_strarray_free(tgt);
memset(tgt, 0, sizeof(*tgt));
@@ -162,6 +252,42 @@ int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int ba
return error;
}
+int git__strcmp(const char *a, const char *b)
+{
+ while (*a && *b && *a == *b)
+ ++a, ++b;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strcasecmp(const char *a, const char *b)
+{
+ while (*a && *b && tolower(*a) == tolower(*b))
+ ++a, ++b;
+ return (tolower(*a) - tolower(*b));
+}
+
+int git__strncmp(const char *a, const char *b, size_t sz)
+{
+ while (sz && *a && *b && *a == *b)
+ --sz, ++a, ++b;
+ if (!sz)
+ return 0;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strncasecmp(const char *a, const char *b, size_t sz)
+{
+ int al, bl;
+
+ do {
+ al = (unsigned char)tolower(*a);
+ bl = (unsigned char)tolower(*b);
+ ++a, ++b;
+ } while (--sz && al && al == bl);
+
+ return al - bl;
+}
+
void git__strntolower(char *str, size_t len)
{
size_t i;
@@ -179,7 +305,7 @@ void git__strtolower(char *str)
int git__prefixcmp(const char *str, const char *prefix)
{
for (;;) {
- char p = *(prefix++), s;
+ unsigned char p = *(prefix++), s;
if (!p)
return 0;
if ((s = *(str++)) != p)
@@ -187,6 +313,11 @@ int git__prefixcmp(const char *str, const char *prefix)
}
}
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+ return strncasecmp(str, prefix, strlen(prefix));
+}
+
int git__suffixcmp(const char *str, const char *suffix)
{
size_t a = strlen(str);
@@ -221,6 +352,24 @@ char *git__strtok(char **end, const char *sep)
return NULL;
}
+/* Similar to strtok, but does not collapse repeated tokens. */
+char *git__strsep(char **end, const char *sep)
+{
+ char *start = *end, *ptr = *end;
+
+ while (*ptr && !strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ *end = ptr + 1;
+ *ptr = '\0';
+
+ return start;
+ }
+
+ return NULL;
+}
+
void git__hexdump(const char *buffer, size_t len)
{
static const size_t LINE_WIDTH = 16;
@@ -367,6 +516,30 @@ uint32_t git__hash(const void *key, int len, uint32_t seed)
*
* Copyright (c) 1990 Regents of the University of California.
* All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. [rescinded 22 July 1999]
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
*/
int git__bsearch(
void **array,
@@ -375,11 +548,11 @@ int git__bsearch(
int (*compare)(const void *, const void *),
size_t *position)
{
- unsigned int lim;
+ size_t lim;
int cmp = -1;
void **part, **base = array;
- for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) {
+ for (lim = array_len; lim != 0; lim >>= 1) {
part = base + (lim >> 1);
cmp = (*compare)(key, *part);
if (cmp == 0) {
@@ -395,12 +568,43 @@ int git__bsearch(
if (position)
*position = (base - array);
- return (cmp == 0) ? 0 : -1;
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *, const void *, void *),
+ void *payload,
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare_r)(key, *part, payload);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
}
/**
* A strcmp wrapper
- *
+ *
* We don't want direct pointers to the CRT on Windows, we may
* get stdcall conflicts.
*/
@@ -415,12 +619,8 @@ int git__strcmp_cb(const void *a, const void *b)
int git__parse_bool(int *out, const char *value)
{
/* A missing value means true */
- if (value == NULL) {
- *out = 1;
- return 0;
- }
-
- if (!strcasecmp(value, "true") ||
+ if (value == NULL ||
+ !strcasecmp(value, "true") ||
!strcasecmp(value, "yes") ||
!strcasecmp(value, "on")) {
*out = 1;
@@ -428,10 +628,82 @@ int git__parse_bool(int *out, const char *value)
}
if (!strcasecmp(value, "false") ||
!strcasecmp(value, "no") ||
- !strcasecmp(value, "off")) {
+ !strcasecmp(value, "off") ||
+ value[0] == '\0') {
*out = 0;
return 0;
}
return -1;
}
+
+size_t git__unescape(char *str)
+{
+ char *scan, *pos = str;
+
+ for (scan = str; *scan; pos++, scan++) {
+ if (*scan == '\\' && *(scan + 1) != '\0')
+ scan++; /* skip '\' but include next char */
+ if (pos != scan)
+ *pos = *scan;
+ }
+
+ if (pos != scan) {
+ *pos = '\0';
+ }
+
+ return (pos - str);
+}
+
+#if defined(GIT_WIN32) || defined(BSD)
+typedef struct {
+ git__sort_r_cmp cmp;
+ void *payload;
+} git__qsort_r_glue;
+
+static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
+ void *payload, const void *a, const void *b)
+{
+ git__qsort_r_glue *glue = payload;
+ return glue->cmp(a, b, glue->payload);
+}
+#endif
+
+void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
+{
+#if defined(__MINGW32__) || defined(__OpenBSD__)
+ git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
+#elif defined(GIT_WIN32)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
+#elif defined(BSD)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);
+#else
+ qsort_r(els, nel, elsize, cmp, payload);
+#endif
+}
+
+void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload)
+{
+ uint8_t *base = els;
+ uint8_t *end = base + nel * elsize;
+ uint8_t *i, *j;
+ bool freeswap = !swapel;
+
+ if (freeswap)
+ swapel = git__malloc(elsize);
+
+ for (i = base + elsize; i < end; i += elsize)
+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {
+ memcpy(swapel, j, elsize);
+ memcpy(j, j - elsize, elsize);
+ memcpy(j - elsize, swapel, elsize);
+ }
+
+ if (freeswap)
+ git__free(swapel);
+}
diff --git a/src/util.h b/src/util.h
index c6851ac7e..c0f271997 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -13,6 +13,9 @@
#ifndef min
# define min(a,b) ((a) < (b) ? (a) : (b))
#endif
+#ifndef max
+# define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
/*
* Custom memory allocation wrappers
@@ -42,25 +45,31 @@ GIT_INLINE(char *) git__strdup(const char *str)
GIT_INLINE(char *) git__strndup(const char *str, size_t n)
{
- size_t length;
+ size_t length = 0;
char *ptr;
- length = strlen(str);
- if (n < length)
- length = n;
+ while (length < n && str[length])
+ ++length;
- ptr = (char*)malloc(length + 1);
- if (!ptr) {
- giterr_set_oom();
- return NULL;
- }
+ ptr = (char*)git__malloc(length + 1);
+
+ if (length)
+ memcpy(ptr, str, length);
- memcpy(ptr, str, length);
ptr[length] = '\0';
return ptr;
}
+/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */
+GIT_INLINE(char *) git__substrdup(const char *start, size_t n)
+{
+ char *ptr = (char*)git__malloc(n+1);
+ memcpy(ptr, start, n);
+ ptr[n] = '\0';
+ return ptr;
+}
+
GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
{
void *new_ptr = realloc(ptr, size);
@@ -70,9 +79,21 @@ GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
#define git__free(ptr) free(ptr)
+#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \
+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))
+
+#define CASESELECT(IGNORE_CASE, ICASE, CASE) \
+ ((IGNORE_CASE) ? (ICASE) : (CASE))
+
extern int git__prefixcmp(const char *str, const char *prefix);
+extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
+GIT_INLINE(int) git__signum(int val)
+{
+ return ((val > 0) - (val < 0));
+}
+
extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base);
@@ -94,6 +115,7 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
#endif
extern char *git__strtok(char **end, const char *sep);
+extern char *git__strsep(char **end, const char *sep);
extern void git__strntolower(char *str, size_t len);
extern void git__strtolower(char *str);
@@ -105,22 +127,64 @@ GIT_INLINE(const char *) git__next_line(const char *s)
return s;
}
-extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));
+GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return cp;
+ } while (--n != 0);
+ }
+
+ return NULL;
+}
+
+typedef int (*git__tsort_cmp)(const void *a, const void *b);
+
+extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);
+
+typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);
+
+extern void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);
+
+extern void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
+
+extern void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload);
/**
* @param position If non-NULL, this will be set to the position where the
* element is or would be inserted if not found.
- * @return pos (>=0) if found or -1 if not found
+ * @return 0 if found; GIT_ENOTFOUND if not found
*/
extern int git__bsearch(
void **array,
size_t array_len,
const void *key,
- int (*compare)(const void *, const void *),
+ int (*compare)(const void *key, const void *element),
+ size_t *position);
+
+extern int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *key, const void *element, void *payload),
+ void *payload,
size_t *position);
extern int git__strcmp_cb(const void *a, const void *b);
+extern int git__strcmp(const char *a, const char *b);
+extern int git__strcasecmp(const char *a, const char *b);
+extern int git__strncmp(const char *a, const char *b, size_t sz);
+extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+
typedef struct {
short refcount;
void *owner;
@@ -204,9 +268,14 @@ GIT_INLINE(bool) git__isalpha(int c)
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
+GIT_INLINE(bool) git__isdigit(int c)
+{
+ return (c >= '0' && c <= '9');
+}
+
GIT_INLINE(bool) git__isspace(int c)
{
- return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
}
GIT_INLINE(bool) git__iswildcard(int c)
@@ -223,4 +292,23 @@ GIT_INLINE(bool) git__iswildcard(int c)
*/
extern int git__parse_bool(int *out, const char *value);
+/*
+ * Parse a string into a value as a git_time_t.
+ *
+ * Sample valid input:
+ * - "yesterday"
+ * - "July 17, 2003"
+ * - "2003-7-17 08:23"
+ */
+int git__date_parse(git_time_t *out, const char *date);
+
+/*
+ * Unescapes a string in-place.
+ *
+ * Edge cases behavior:
+ * - "jackie\" -> "jacky\"
+ * - "chan\\" -> "chan\"
+ */
+extern size_t git__unescape(char *str);
+
#endif /* INCLUDE_util_h__ */
diff --git a/src/vector.c b/src/vector.c
index 6f9aacccf..f4a818ed2 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -9,18 +9,60 @@
#include "repository.h"
#include "vector.h"
-static const double resize_factor = 1.75;
-static const unsigned int minimum_size = 8;
+/* In elements, not bytes */
+#define MIN_ALLOCSIZE 8
-static int resize_vector(git_vector *v)
+GIT_INLINE(size_t) compute_new_size(git_vector *v)
{
- v->_alloc_size = ((unsigned int)(v->_alloc_size * resize_factor)) + 1;
- if (v->_alloc_size < minimum_size)
- v->_alloc_size = minimum_size;
+ size_t new_size = v->_alloc_size;
+
+ /* Use a resize factor of 1.5, which is quick to compute using integer
+ * instructions and less than the golden ratio (1.618...) */
+ if (new_size < MIN_ALLOCSIZE)
+ new_size = MIN_ALLOCSIZE;
+ else if (new_size <= (SIZE_MAX / 3) * 2)
+ new_size += new_size / 2;
+ else
+ new_size = SIZE_MAX;
+
+ return new_size;
+}
+
+GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size)
+{
+ size_t new_bytes = new_size * sizeof(void *);
+ void *new_contents;
+
+ /* Check for overflow */
+ if (new_bytes / sizeof(void *) != new_size)
+ GITERR_CHECK_ALLOC(NULL);
- v->contents = git__realloc(v->contents, v->_alloc_size * sizeof(void *));
+ new_contents = git__realloc(v->contents, new_bytes);
+ GITERR_CHECK_ALLOC(new_contents);
+
+ v->_alloc_size = new_size;
+ v->contents = new_contents;
+
+ return 0;
+}
+
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)
+{
+ size_t bytes;
+
+ assert(v && src);
+
+ bytes = src->length * sizeof(void *);
+
+ v->_alloc_size = src->length;
+ v->_cmp = cmp;
+ v->length = src->length;
+ v->sorted = src->sorted && cmp == src->_cmp;
+ v->contents = git__malloc(bytes);
GITERR_CHECK_ALLOC(v->contents);
+ memcpy(v->contents, src->contents, bytes);
+
return 0;
}
@@ -35,25 +77,17 @@ void git_vector_free(git_vector *v)
v->_alloc_size = 0;
}
-int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp)
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp)
{
assert(v);
- memset(v, 0x0, sizeof(git_vector));
-
- if (initial_size == 0)
- initial_size = minimum_size;
-
- v->_alloc_size = initial_size;
+ v->_alloc_size = 0;
v->_cmp = cmp;
-
v->length = 0;
v->sorted = 1;
+ v->contents = NULL;
- v->contents = git__malloc(v->_alloc_size * sizeof(void *));
- GITERR_CHECK_ALLOC(v->contents);
-
- return 0;
+ return resize_vector(v, max(initial_size, MIN_ALLOCSIZE));
}
int git_vector_insert(git_vector *v, void *element)
@@ -61,7 +95,7 @@ int git_vector_insert(git_vector *v, void *element)
assert(v);
if (v->length >= v->_alloc_size &&
- resize_vector(v) < 0)
+ resize_vector(v, compute_new_size(v)) < 0)
return -1;
v->contents[v->length++] = element;
@@ -82,26 +116,25 @@ int git_vector_insert_sorted(
git_vector_sort(v);
if (v->length >= v->_alloc_size &&
- resize_vector(v) < 0)
+ resize_vector(v, compute_new_size(v)) < 0)
return -1;
/* If we find the element and have a duplicate handler callback,
* invoke it. If it returns non-zero, then cancel insert, otherwise
* proceed with normal insert.
*/
- if (git__bsearch(v->contents, v->length, element, v->_cmp, &pos) >= 0 &&
- on_dup != NULL &&
- (result = on_dup(&v->contents[pos], element)) < 0)
+ if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) &&
+ on_dup && (result = on_dup(&v->contents[pos], element)) < 0)
return result;
/* shift elements to the right */
- if (pos < v->length) {
+ if (pos < v->length)
memmove(v->contents + pos + 1, v->contents + pos,
(v->length - pos) * sizeof(void *));
- }
v->contents[pos] = element;
v->length++;
+
return 0;
}
@@ -109,47 +142,44 @@ void git_vector_sort(git_vector *v)
{
assert(v);
- if (v->sorted || v->_cmp == NULL)
+ if (v->sorted || !v->_cmp)
return;
git__tsort(v->contents, v->length, v->_cmp);
v->sorted = 1;
}
-int git_vector_bsearch3(
- unsigned int *at_pos,
+int git_vector_bsearch2(
+ size_t *at_pos,
git_vector *v,
git_vector_cmp key_lookup,
const void *key)
{
- int rval;
- size_t pos;
-
assert(v && key && key_lookup);
/* need comparison function to sort the vector */
- assert(v->_cmp != NULL);
+ if (!v->_cmp)
+ return -1;
git_vector_sort(v);
- rval = git__bsearch(v->contents, v->length, key, key_lookup, &pos);
-
- if (at_pos != NULL)
- *at_pos = (unsigned int)pos;
-
- return (rval >= 0) ? (int)pos : GIT_ENOTFOUND;
+ return git__bsearch(v->contents, v->length, key, key_lookup, at_pos);
}
int git_vector_search2(
- git_vector *v, git_vector_cmp key_lookup, const void *key)
+ size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key)
{
- unsigned int i;
+ size_t i;
assert(v && key && key_lookup);
for (i = 0; i < v->length; ++i) {
- if (key_lookup(key, v->contents[i]) == 0)
- return i;
+ if (key_lookup(key, v->contents[i]) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
}
return GIT_ENOTFOUND;
@@ -160,22 +190,25 @@ static int strict_comparison(const void *a, const void *b)
return (a == b) ? 0 : -1;
}
-int git_vector_search(git_vector *v, const void *entry)
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry)
{
- return git_vector_search2(v, v->_cmp ? v->_cmp : strict_comparison, entry);
+ return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry);
}
-int git_vector_remove(git_vector *v, unsigned int idx)
+int git_vector_remove(git_vector *v, size_t idx)
{
- unsigned int i;
+ size_t shift_count;
assert(v);
- if (idx >= v->length || v->length == 0)
+ if (idx >= v->length)
return GIT_ENOTFOUND;
- for (i = idx; i < v->length - 1; ++i)
- v->contents[i] = v->contents[i + 1];
+ shift_count = v->length - idx - 1;
+
+ if (shift_count)
+ memmove(&v->contents[idx], &v->contents[idx + 1],
+ shift_count * sizeof(void *));
v->length--;
return 0;
@@ -190,7 +223,7 @@ void git_vector_pop(git_vector *v)
void git_vector_uniq(git_vector *v)
{
git_vector_cmp cmp;
- unsigned int i, j;
+ size_t i, j;
if (v->length <= 1)
return;
@@ -207,6 +240,21 @@ void git_vector_uniq(git_vector *v)
v->length -= j - i - 1;
}
+void git_vector_remove_matching(
+ git_vector *v, int (*match)(const git_vector *v, size_t idx))
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; j < v->length; ++j) {
+ v->contents[i] = v->contents[j];
+
+ if (!match(v, i))
+ i++;
+ }
+
+ v->length = i;
+}
+
void git_vector_clear(git_vector *v)
{
assert(v);
@@ -218,10 +266,41 @@ void git_vector_swap(git_vector *a, git_vector *b)
{
git_vector t;
- if (!a || !b || a == b)
- return;
+ assert(a && b);
- memcpy(&t, a, sizeof(t));
- memcpy(a, b, sizeof(t));
- memcpy(b, &t, sizeof(t));
+ if (a != b) {
+ memcpy(&t, a, sizeof(t));
+ memcpy(a, b, sizeof(t));
+ memcpy(b, &t, sizeof(t));
+ }
+}
+
+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));
+
+ v->length = new_length;
+
+ return 0;
+}
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value)
+{
+ if (git_vector_resize_to(v, position + 1) < 0)
+ return -1;
+
+ if (old != NULL)
+ *old = v->contents[position];
+
+ v->contents[position] = value;
+
+ return 0;
}
diff --git a/src/vector.h b/src/vector.h
index 9139db345..e2f729b83 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -12,52 +12,50 @@
typedef int (*git_vector_cmp)(const void *, const void *);
typedef struct git_vector {
- unsigned int _alloc_size;
+ size_t _alloc_size;
git_vector_cmp _cmp;
void **contents;
- unsigned int length;
+ size_t length;
int sorted;
} git_vector;
#define GIT_VECTOR_INIT {0}
-int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
void git_vector_clear(git_vector *v);
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp);
void git_vector_swap(git_vector *a, git_vector *b);
void git_vector_sort(git_vector *v);
-int git_vector_search(git_vector *v, const void *entry);
-int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);
+/** Linear search for matching entry using internal comparison function */
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry);
-int git_vector_bsearch3(
- unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+/** Linear search for matching entry using explicit comparison function */
+int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key);
-GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key)
-{
- return git_vector_bsearch3(NULL, v, v->_cmp, key);
-}
-
-GIT_INLINE(int) git_vector_bsearch2(
- git_vector *v, git_vector_cmp cmp, const void *key)
-{
- return git_vector_bsearch3(NULL, v, cmp, key);
-}
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
+int git_vector_bsearch2(
+ size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
-GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
+/** Binary search for matching entry using internal comparison function */
+GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key)
{
- return (position < v->length) ? v->contents[position] : NULL;
+ return git_vector_bsearch2(at_pos, v, v->_cmp, key);
}
-GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position)
+GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position)
{
return (position < v->length) ? v->contents[position] : NULL;
}
#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL)
-GIT_INLINE(void *) git_vector_last(git_vector *v)
+GIT_INLINE(void *) git_vector_last(const git_vector *v)
{
return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
}
@@ -66,13 +64,18 @@ GIT_INLINE(void *) git_vector_last(git_vector *v)
for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
#define git_vector_rforeach(v, iter, elem) \
- for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- )
+ for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- )
int git_vector_insert(git_vector *v, void *element);
int git_vector_insert_sorted(git_vector *v, void *element,
int (*on_dup)(void **old, void *new));
-int git_vector_remove(git_vector *v, unsigned int idx);
+int git_vector_remove(git_vector *v, size_t idx);
void git_vector_pop(git_vector *v);
void git_vector_uniq(git_vector *v);
+void git_vector_remove_matching(
+ git_vector *v, int (*match)(const git_vector *v, size_t idx));
+
+int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_set(void **old, git_vector *v, size_t position, void *value);
#endif
diff --git a/src/win32/dir.c b/src/win32/dir.c
index bc3d40fa5..95ae5060e 100644
--- a/src/win32/dir.c
+++ b/src/win32/dir.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,7 +7,6 @@
#define GIT__WIN32_NO_WRAP_DIR
#include "dir.h"
#include "utf-conv.h"
-#include "git2/windows.h"
static int init_filter(char *filter, size_t n, const char *dir)
{
@@ -26,8 +25,8 @@ static int init_filter(char *filter, size_t n, const char *dir)
git__DIR *git__opendir(const char *dir)
{
- char filter[4096];
- wchar_t* filter_w = NULL;
+ char filter[GIT_WIN_PATH];
+ wchar_t filter_w[GIT_WIN_PATH];
git__DIR *new = NULL;
if (!dir || !init_filter(filter, sizeof(filter), dir))
@@ -41,12 +40,8 @@ git__DIR *git__opendir(const char *dir)
if (!new->dir)
goto fail;
- filter_w = gitwin_to_utf16(filter);
- if (!filter_w)
- goto fail;
-
+ git__utf8_to_16(filter_w, GIT_WIN_PATH, filter);
new->h = FindFirstFileW(filter_w, &new->f);
- git__free(filter_w);
if (new->h == INVALID_HANDLE_VALUE) {
giterr_set(GITERR_OS, "Could not open directory '%s'", dir);
@@ -85,16 +80,9 @@ int git__readdir_ext(
if (wcslen(d->f.cFileName) >= sizeof(entry->d_name))
return -1;
+ git__utf16_to_8(entry->d_name, d->f.cFileName);
entry->d_ino = 0;
- if (WideCharToMultiByte(
- gitwin_get_codepage(), 0, d->f.cFileName, -1,
- entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0)
- {
- giterr_set(GITERR_OS, "Could not convert filename to UTF-8");
- return -1;
- }
-
*result = entry;
if (is_dir != NULL)
@@ -113,8 +101,8 @@ struct git__dirent *git__readdir(git__DIR *d)
void git__rewinddir(git__DIR *d)
{
- char filter[4096];
- wchar_t* filter_w;
+ char filter[GIT_WIN_PATH];
+ wchar_t filter_w[GIT_WIN_PATH];
if (!d)
return;
@@ -125,12 +113,11 @@ void git__rewinddir(git__DIR *d)
d->first = 0;
}
- if (!init_filter(filter, sizeof(filter), d->dir) ||
- (filter_w = gitwin_to_utf16(filter)) == NULL)
+ if (!init_filter(filter, sizeof(filter), d->dir))
return;
+ git__utf8_to_16(filter_w, GIT_WIN_PATH, filter);
d->h = FindFirstFileW(filter_w, &d->f);
- git__free(filter_w);
if (d->h == INVALID_HANDLE_VALUE)
giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir);
diff --git a/src/win32/dir.h b/src/win32/dir.h
index c816d79bb..7696d468e 100644
--- a/src/win32/dir.h
+++ b/src/win32/dir.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/win32/error.c b/src/win32/error.c
new file mode 100644
index 000000000..4a9a0631f
--- /dev/null
+++ b/src/win32/error.c
@@ -0,0 +1,77 @@
+/*
+ * 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 "error.h"
+
+#ifdef GIT_WINHTTP
+# include <winhttp.h>
+#endif
+
+#define WC_ERR_INVALID_CHARS 0x80
+
+char *git_win32_get_error_message(DWORD error_code)
+{
+ LPWSTR lpMsgBuf = NULL;
+ HMODULE hModule = NULL;
+ char *utf8_msg = NULL;
+ int utf8_size;
+ DWORD dwFlags =
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ if (!error_code)
+ return NULL;
+
+#ifdef GIT_WINHTTP
+ /* Errors raised by WinHTTP are not in the system resource table */
+ if (error_code >= WINHTTP_ERROR_BASE &&
+ error_code <= WINHTTP_ERROR_LAST)
+ hModule = GetModuleHandleW(L"winhttp");
+#endif
+
+ GIT_UNUSED(hModule);
+
+ if (hModule)
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ else
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (FormatMessageW(dwFlags, hModule, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&lpMsgBuf, 0, NULL)) {
+
+ /* Invalid code point check supported on Vista+ only */
+ if (git_has_win32_version(6, 0))
+ dwFlags = WC_ERR_INVALID_CHARS;
+ else
+ dwFlags = 0;
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags,
+ lpMsgBuf, -1, NULL, 0, NULL, NULL);
+
+ if (!utf8_size) {
+ assert(0);
+ goto on_error;
+ }
+
+ utf8_msg = git__malloc(utf8_size);
+
+ if (!utf8_msg)
+ goto on_error;
+
+ if (!WideCharToMultiByte(CP_UTF8, dwFlags,
+ lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) {
+ git__free(utf8_msg);
+ goto on_error;
+ }
+
+on_error:
+ LocalFree(lpMsgBuf);
+ }
+
+ return utf8_msg;
+}
diff --git a/src/win32/error.h b/src/win32/error.h
new file mode 100644
index 000000000..12947a2e6
--- /dev/null
+++ b/src/win32/error.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_win32_error_h__
+#define INCLUDE_git_win32_error_h__
+
+extern char *git_win32_get_error_message(DWORD error_code);
+
+#endif
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
new file mode 100644
index 000000000..bc36b6b45
--- /dev/null
+++ b/src/win32/findfile.c
@@ -0,0 +1,238 @@
+/*
+ * 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 "utf-conv.h"
+#include "path.h"
+#include "findfile.h"
+
+#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+
+#ifndef _WIN64
+#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
+#else
+#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+#endif
+
+int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ)
+{
+ s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
+ return s_root->len ? 0 : -1;
+}
+
+static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16)
+{
+ char temp_utf8[GIT_PATH_MAX];
+
+ git__utf16_to_8(temp_utf8, path_utf16);
+ git_path_mkposix(temp_utf8);
+
+ return git_buf_sets(path_utf8, temp_utf8);
+}
+
+int git_win32__find_file(
+ git_buf *path, const struct git_win32__path *root, const char *filename)
+{
+ size_t len, alloc_len;
+ wchar_t *file_utf16 = NULL;
+
+ if (!root || !filename || (len = strlen(filename)) == 0)
+ return GIT_ENOTFOUND;
+
+ /* allocate space for wchar_t path to file */
+ alloc_len = root->len + len + 2;
+ file_utf16 = git__calloc(alloc_len, sizeof(wchar_t));
+ GITERR_CHECK_ALLOC(file_utf16);
+
+ /* append root + '\\' + filename as wchar_t */
+ memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
+
+ if (*filename == '/' || *filename == '\\')
+ filename++;
+
+ git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename);
+
+ /* check access */
+ if (_waccess(file_utf16, F_OK) < 0) {
+ git__free(file_utf16);
+ return GIT_ENOTFOUND;
+ }
+
+ win32_path_utf16_to_8(path, file_utf16);
+ git__free(file_utf16);
+
+ return 0;
+}
+
+static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
+{
+ wchar_t term, *base = path;
+
+ assert(path && buf && buflen);
+
+ term = (*path == L'"') ? *path++ : L';';
+
+ for (buflen--; *path && *path != term && buflen; buflen--)
+ *buf++ = *path++;
+
+ *buf = L'\0'; /* reserved a byte via initial subtract */
+
+ while (*path == term || *path == L';')
+ path++;
+
+ return (path != base) ? path : NULL;
+}
+
+static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)
+{
+ wchar_t *env = _wgetenv(L"PATH"), lastch;
+ struct git_win32__path root;
+ size_t gitexe_len = wcslen(gitexe);
+
+ if (!env)
+ return -1;
+
+ while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {
+ root.len = (DWORD)wcslen(root.path);
+ lastch = root.path[root.len - 1];
+
+ /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
+ if (lastch != L'/' && lastch != L'\\') {
+ root.path[root.len++] = L'\\';
+ root.path[root.len] = L'\0';
+ }
+
+ if (root.len + gitexe_len >= MAX_PATH)
+ continue;
+ wcscpy(&root.path[root.len], gitexe);
+
+ if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
+ /* replace "bin\\" or "cmd\\" with "etc\\" */
+ wcscpy(&root.path[root.len - 4], L"etc\\");
+
+ win32_path_utf16_to_8(buf, root.path);
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+static int win32_find_git_in_registry(
+ git_buf *buf, const HKEY hieve, const wchar_t *key)
+{
+ HKEY hKey;
+ DWORD dwType = REG_SZ;
+ struct git_win32__path path16;
+
+ assert(buf);
+
+ path16.len = 0;
+
+ if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {
+ if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,
+ (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS)
+ {
+ /* InstallLocation points to the root of the git directory */
+
+ if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */
+ giterr_set(GITERR_OS, "Cannot locate git - path too long");
+ return -1;
+ }
+
+ wcscat(path16.path, L"etc\\");
+ path16.len += 4;
+
+ win32_path_utf16_to_8(buf, path16.path);
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return path16.len ? 0 : GIT_ENOTFOUND;
+}
+
+static int win32_find_existing_dirs(
+ git_buf *out, const wchar_t *tmpl[], char *temp[])
+{
+ struct git_win32__path path16;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_clear(out);
+
+ for (; *tmpl != NULL; tmpl++) {
+ if (!git_win32__expand_path(&path16, *tmpl) &&
+ path16.path[0] != L'%' &&
+ !_waccess(path16.path, F_OK))
+ {
+ win32_path_utf16_to_8(&buf, path16.path);
+
+ if (buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+ }
+ }
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_system_dirs(git_buf *out)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* directories where git.exe & git.cmd are found */
+ if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size)
+ git_buf_set(out, buf.ptr, buf.size);
+ else
+ git_buf_clear(out);
+
+ if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ /* directories where git is installed according to registry */
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_global_dirs(git_buf *out)
+{
+ char *temp[3];
+ static const wchar_t *global_tmpls[4] = {
+ L"%HOME%\\",
+ L"%HOMEDRIVE%%HOMEPATH%\\",
+ L"%USERPROFILE%\\",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls, temp);
+}
+
+int git_win32__find_xdg_dirs(git_buf *out)
+{
+ char *temp[6];
+ static const wchar_t *global_tmpls[7] = {
+ L"%XDG_CONFIG_HOME%\\git",
+ L"%APPDATA%\\git",
+ L"%LOCALAPPDATA%\\git",
+ L"%HOME%\\.config\\git",
+ L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
+ L"%USERPROFILE%\\.config\\git",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls, temp);
+}
+
diff --git a/src/win32/findfile.h b/src/win32/findfile.h
new file mode 100644
index 000000000..fc79e1b72
--- /dev/null
+++ b/src/win32/findfile.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_findfile_h__
+#define INCLUDE_git_findfile_h__
+
+struct git_win32__path {
+ wchar_t path[MAX_PATH];
+ DWORD len;
+};
+
+extern int git_win32__expand_path(
+ struct git_win32__path *s_root, const wchar_t *templ);
+
+extern int git_win32__find_file(
+ git_buf *path, const struct git_win32__path *root, const char *filename);
+
+extern int git_win32__find_system_dirs(git_buf *out);
+extern int git_win32__find_global_dirs(git_buf *out);
+extern int git_win32__find_xdg_dirs(git_buf *out);
+
+#endif
+
diff --git a/src/win32/git2.rc b/src/win32/git2.rc
index 3a65c0a0f..436913228 100644
--- a/src/win32/git2.rc
+++ b/src/win32/git2.rc
@@ -12,13 +12,13 @@ VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE
PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
- FILEFLAGS 1
+ FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0
#endif
- FILEOS VOS__WINDOWS32
+ FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
- FILESUBTYPE 0 // not used
+ FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -28,7 +28,7 @@ BEGIN
VALUE "FileDescription", "libgit2 - the Git linkable library\0"
VALUE "FileVersion", LIBGIT2_VERSION "\0"
VALUE "InternalName", LIBGIT2_FILENAME "\0"
- VALUE "LegalCopyright", "Copyright (C) 2009-2012 the libgit2 contributors\0"
+ VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0"
VALUE "OriginalFilename", LIBGIT2_FILENAME "\0"
VALUE "ProductName", "libgit2\0"
VALUE "ProductVersion", LIBGIT2_VERSION "\0"
diff --git a/src/win32/map.c b/src/win32/map.c
index f730120cc..44c6c4e2e 100644
--- a/src/win32/map.c
+++ b/src/win32/map.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h
index 6200dc094..7b97b48db 100644
--- a/src/win32/mingw-compat.h
+++ b/src/win32/mingw-compat.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h
index 3ef09c85b..50865ed17 100644
--- a/src/win32/msvc-compat.h
+++ b/src/win32/msvc-compat.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -21,6 +21,7 @@
/* stat: file mode type testing macros */
# define _S_IFLNK 0120000
# define S_IFLNK _S_IFLNK
+# define S_IXUSR 00100
# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
@@ -36,6 +37,15 @@
/* MSVC doesn't define ssize_t at all */
typedef SSIZE_T ssize_t;
+/* define snprintf using variadic macro support if available */
+#if _MSC_VER >= 1400
+# define snprintf(BUF, SZ, FMT, ...) _snprintf_s(BUF, SZ, _TRUNCATE, FMT, __VA_ARGS__)
+#else
+# define snprintf _snprintf
#endif
+#endif
+
+#define GIT_STDLIB_CALL __cdecl
+
#endif /* INCLUDE_msvc_compat__ */
diff --git a/src/win32/posix.h b/src/win32/posix.h
index baa4a3b4e..c49c2175c 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -8,7 +8,6 @@
#define INCLUDE_posix__w32_h__
#include "common.h"
-#include "compat/fnmatch.h"
#include "utf-conv.h"
GIT_INLINE(int) p_link(const char *old, const char *new)
@@ -21,18 +20,16 @@ GIT_INLINE(int) p_link(const char *old, const char *new)
GIT_INLINE(int) p_mkdir(const char *path, mode_t mode)
{
- wchar_t* buf = gitwin_to_utf16(path);
- int ret = _wmkdir(buf);
-
+ wchar_t buf[GIT_WIN_PATH];
GIT_UNUSED(mode);
-
- git__free(buf);
- return ret;
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _wmkdir(buf);
}
extern int p_unlink(const char *path);
extern int p_lstat(const char *file_name, struct stat *buf);
extern int p_readlink(const char *link, char *target, size_t target_len);
+extern int p_symlink(const char *old, const char *new);
extern int p_hide_directory__w32(const char *path);
extern char *p_realpath(const char *orig_path, char *buffer);
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
@@ -51,5 +48,14 @@ extern int p_getcwd(char *buffer_out, size_t size);
extern int p_rename(const char *from, const char *to);
extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
+extern int p_inet_pton(int af, const char* src, void* dst);
+
+/* p_lstat is almost but not quite POSIX correct. Specifically, the use of
+ * ENOTDIR is wrong, in that it does not mean precisely that a non-directory
+ * entry was encountered. Making it correct is potentially expensive,
+ * however, so this is a separate version of p_lstat to use when correct
+ * POSIX ENOTDIR semantics is required.
+ */
+extern int p_lstat_posixly(const char *filename, struct stat *buf);
#endif
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 10de70da8..4d56299f7 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,23 +7,18 @@
#include "../posix.h"
#include "path.h"
#include "utf-conv.h"
+#include "repository.h"
#include <errno.h>
#include <io.h>
#include <fcntl.h>
-
+#include <ws2tcpip.h>
int p_unlink(const char *path)
{
- int ret = 0;
- wchar_t* buf;
-
- if ((buf = gitwin_to_utf16(path)) != NULL) {
- _wchmod(buf, 0666);
- ret = _wunlink(buf);
- git__free(buf);
- }
-
- return ret;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ _wchmod(buf, 0666);
+ return _wunlink(buf);
}
int p_fsync(int fd)
@@ -57,16 +52,32 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
return (time_t)winTime;
}
-static int do_lstat(const char *file_name, struct stat *buf)
+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
+
+static int do_lstat(
+ const char *file_name, struct stat *buf, int posix_enotdir)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
- wchar_t* fbuf = gitwin_to_utf16(file_name);
- if (!fbuf)
- return -1;
+ wchar_t fbuf[GIT_WIN_PATH], lastch;
+ int flen;
+
+ flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name);
+
+ /* truncate trailing slashes */
+ for (; flen > 0; --flen) {
+ lastch = fbuf[flen - 1];
+ if (WIN32_IS_WSEP(lastch))
+ fbuf[flen - 1] = L'\0';
+ else if (lastch != L'\0')
+ break;
+ }
if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
int fMode = S_IREAD;
+ if (!buf)
+ return 0;
+
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
fMode |= S_IFDIR;
else
@@ -89,44 +100,59 @@ static int do_lstat(const char *file_name, struct stat *buf)
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
- git__free(fbuf);
- return 0;
- }
+ /* Windows symlinks have zero file size, call readlink to determine
+ * the length of the path pointed to, which we expect everywhere else
+ */
+ if (S_ISLNK(fMode)) {
+ char target[GIT_WIN_PATH];
+ int readlink_result;
- git__free(fbuf);
- return -1;
-}
+ readlink_result = p_readlink(file_name, target, GIT_WIN_PATH);
-int p_lstat(const char *file_name, struct stat *buf)
-{
- int error;
- size_t namelen;
- char *alt_name;
+ if (readlink_result == -1)
+ return -1;
- if (do_lstat(file_name, buf) == 0)
- return 0;
+ buf->st_size = strlen(target);
+ }
- /* if file_name ended in a '/', Windows returned ENOENT;
- * try again without trailing slashes
- */
- namelen = strlen(file_name);
- if (namelen && file_name[namelen-1] != '/')
- return -1;
+ return 0;
+ }
- while (namelen && file_name[namelen-1] == '/')
- --namelen;
+ errno = ENOENT;
- if (!namelen)
- return -1;
+ /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the
+ * file path is a regular file,otherwise ENOENT must be set.
+ */
+ if (posix_enotdir) {
+ /* scan up path until we find an existing item */
+ while (1) {
+ /* remove last directory component */
+ for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen);
+
+ if (flen <= 0)
+ break;
+
+ fbuf[flen] = L'\0';
+
+ if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
+ if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ break;
+ }
+ }
+ }
- alt_name = git__strndup(file_name, namelen);
- if (!alt_name)
- return -1;
+ return -1;
+}
- error = do_lstat(alt_name, buf);
+int p_lstat(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, 0);
+}
- git__free(alt_name);
- return error;
+int p_lstat_posixly(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, 1);
}
int p_readlink(const char *link, char *target, size_t target_len)
@@ -135,7 +161,7 @@ int p_readlink(const char *link, char *target, size_t target_len)
static fpath_func pGetFinalPath = NULL;
HANDLE hFile;
DWORD dwRet;
- wchar_t* link_w;
+ wchar_t link_w[GIT_WIN_PATH];
wchar_t* target_w;
int error = 0;
@@ -146,10 +172,10 @@ int p_readlink(const char *link, char *target, size_t target_len)
* it is not available in platforms older than Vista
*/
if (pGetFinalPath == NULL) {
- HINSTANCE library = LoadLibrary("kernel32");
+ HMODULE module = GetModuleHandle("kernel32");
- if (library != NULL)
- pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleW");
+ if (module != NULL)
+ pGetFinalPath = (fpath_func)GetProcAddress(module, "GetFinalPathNameByHandleW");
if (pGetFinalPath == NULL) {
giterr_set(GITERR_OS,
@@ -158,8 +184,7 @@ int p_readlink(const char *link, char *target, size_t target_len)
}
}
- link_w = gitwin_to_utf16(link);
- GITERR_CHECK_ALLOC(link_w);
+ git__utf8_to_16(link_w, GIT_WIN_PATH, link);
hFile = CreateFileW(link_w, // file to open
GENERIC_READ, // open for reading
@@ -169,8 +194,6 @@ int p_readlink(const char *link, char *target, size_t target_len)
FILE_FLAG_BACKUP_SEMANTICS, // normal file
NULL); // no attr. template
- git__free(link_w);
-
if (hFile == INVALID_HANDLE_VALUE) {
giterr_set(GITERR_OS, "Cannot open '%s' for reading", link);
return -1;
@@ -217,46 +240,43 @@ int p_readlink(const char *link, char *target, size_t target_len)
return dwRet;
}
+int p_symlink(const char *old, const char *new)
+{
+ /* Real symlinks on NTFS require admin privileges. Until this changes,
+ * libgit2 just creates a text file with the link target in the contents.
+ */
+ return git_futils_fake_symlink(old, new);
+}
+
int p_open(const char *path, int flags, ...)
{
- int fd;
- wchar_t* buf;
+ wchar_t buf[GIT_WIN_PATH];
mode_t mode = 0;
- buf = gitwin_to_utf16(path);
- if (!buf)
- return -1;
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
- if (flags & O_CREAT)
- {
+ if (flags & O_CREAT) {
va_list arg_list;
va_start(arg_list, flags);
- mode = va_arg(arg_list, mode_t);
+ mode = (mode_t)va_arg(arg_list, int);
va_end(arg_list);
}
- fd = _wopen(buf, flags | _O_BINARY, mode);
-
- git__free(buf);
- return fd;
+ return _wopen(buf, flags | _O_BINARY, mode);
}
int p_creat(const char *path, mode_t mode)
{
- int fd;
- wchar_t* buf = gitwin_to_utf16(path);
- if (!buf)
- return -1;
- fd = _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode);
- git__free(buf);
- return fd;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode);
}
int p_getcwd(char *buffer_out, size_t size)
{
int ret;
- wchar_t* buf;
+ wchar_t *buf;
if ((size_t)((int)size) != size)
return -1;
@@ -275,104 +295,78 @@ int p_getcwd(char *buffer_out, size_t size)
int p_stat(const char* path, struct stat* buf)
{
- return do_lstat(path, buf);
+ return do_lstat(path, buf, 0);
}
int p_chdir(const char* path)
{
- wchar_t* buf = gitwin_to_utf16(path);
- int ret;
- if (!buf)
- return -1;
- ret = _wchdir(buf);
- git__free(buf);
- return ret;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _wchdir(buf);
}
int p_chmod(const char* path, mode_t mode)
{
- wchar_t* buf = gitwin_to_utf16(path);
- int ret;
- if (!buf)
- return -1;
- ret = _wchmod(buf, mode);
- git__free(buf);
- return ret;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _wchmod(buf, mode);
}
int p_rmdir(const char* path)
{
- wchar_t* buf = gitwin_to_utf16(path);
- int ret;
- if (!buf)
- return -1;
- ret = _wrmdir(buf);
- git__free(buf);
- return ret;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _wrmdir(buf);
}
int p_hide_directory__w32(const char *path)
{
- int res;
- wchar_t* buf = gitwin_to_utf16(path);
- if (!buf)
- return -1;
-
- res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN);
- git__free(buf);
-
- return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1;
}
char *p_realpath(const char *orig_path, char *buffer)
{
- int ret, buffer_sz = 0;
- wchar_t* orig_path_w = gitwin_to_utf16(orig_path);
- wchar_t* buffer_w = (wchar_t*)git__malloc(GIT_PATH_MAX * sizeof(wchar_t));
+ int ret;
+ wchar_t orig_path_w[GIT_WIN_PATH];
+ wchar_t buffer_w[GIT_WIN_PATH];
- if (!orig_path_w || !buffer_w)
- return NULL;
+ git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path);
- ret = GetFullPathNameW(orig_path_w, GIT_PATH_MAX, buffer_w, NULL);
- git__free(orig_path_w);
+ /* Implicitly use GetCurrentDirectory which can be a threading issue */
+ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL);
/* According to MSDN, a return value equals to zero means a failure. */
- if (ret == 0 || ret > GIT_PATH_MAX) {
+ if (ret == 0 || ret > GIT_WIN_PATH)
buffer = NULL;
- goto done;
+
+ else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
+ buffer = NULL;
+ errno = ENOENT;
}
- if (buffer == NULL) {
- buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
+ else if (buffer == NULL) {
+ int buffer_sz = WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
if (!buffer_sz ||
!(buffer = (char *)git__malloc(buffer_sz)) ||
- !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
+ !WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
{
git__free(buffer);
buffer = NULL;
- goto done;
- }
- } else {
- if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) {
- buffer = NULL;
- goto done;
}
}
- if (!git_path_exists(buffer))
- {
- if (buffer_sz > 0)
- git__free(buffer);
-
+ else if (!WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL))
buffer = NULL;
- errno = ENOENT;
- }
-done:
- git__free(buffer_w);
if (buffer)
git_path_mkposix(buffer);
+
return buffer;
}
@@ -381,7 +375,8 @@ int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
#ifdef _MSC_VER
int len;
- if (count == 0 || (len = _vsnprintf(buffer, count, format, argptr)) < 0)
+ if (count == 0 ||
+ (len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0)
return _vscprintf(format, argptr);
return len;
@@ -414,7 +409,7 @@ int p_mkstemp(char *tmp_path)
return -1;
#endif
- return p_creat(tmp_path, 0744);
+ return p_creat(tmp_path, 0744); //-V536
}
int p_setenv(const char* name, const char* value, int overwrite)
@@ -427,32 +422,19 @@ int p_setenv(const char* name, const char* value, int overwrite)
int p_access(const char* path, mode_t mode)
{
- wchar_t *buf = gitwin_to_utf16(path);
- int ret;
- if (!buf)
- return -1;
-
- ret = _waccess(buf, mode);
- git__free(buf);
-
- return ret;
+ wchar_t buf[GIT_WIN_PATH];
+ git__utf8_to_16(buf, GIT_WIN_PATH, path);
+ return _waccess(buf, mode);
}
int p_rename(const char *from, const char *to)
{
- wchar_t *wfrom = gitwin_to_utf16(from);
- wchar_t *wto = gitwin_to_utf16(to);
- int ret;
-
- if (!wfrom || !wto)
- return -1;
-
- ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1;
-
- git__free(wfrom);
- git__free(wto);
+ wchar_t wfrom[GIT_WIN_PATH];
+ wchar_t wto[GIT_WIN_PATH];
- return ret;
+ git__utf8_to_16(wfrom, GIT_WIN_PATH, from);
+ git__utf8_to_16(wto, GIT_WIN_PATH, to);
+ return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1;
}
int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
@@ -470,3 +452,124 @@ int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
return send(socket, buffer, (int)length, flags);
}
+
+/**
+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
+ */
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+
+#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
+#else
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
+#endif
+
+#ifndef _TIMEZONE_DEFINED
+#define _TIMEZONE_DEFINED
+struct timezone
+{
+ int tz_minuteswest; /* minutes W of Greenwich */
+ int tz_dsttime; /* type of dst correction */
+};
+#endif
+
+int p_gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ FILETIME ft;
+ unsigned __int64 tmpres = 0;
+ static int tzflag;
+
+ if (NULL != tv)
+ {
+ GetSystemTimeAsFileTime(&ft);
+
+ tmpres |= ft.dwHighDateTime;
+ tmpres <<= 32;
+ tmpres |= ft.dwLowDateTime;
+
+ /*converting file time to unix epoch*/
+ tmpres /= 10; /*convert into microseconds*/
+ tmpres -= DELTA_EPOCH_IN_MICROSECS;
+ tv->tv_sec = (long)(tmpres / 1000000UL);
+ tv->tv_usec = (long)(tmpres % 1000000UL);
+ }
+
+ if (NULL != tz)
+ {
+ if (!tzflag)
+ {
+ _tzset();
+ tzflag++;
+ }
+ tz->tz_minuteswest = _timezone / 60;
+ tz->tz_dsttime = _daylight;
+ }
+
+ return 0;
+}
+
+int p_inet_pton(int af, const char* src, void* dst)
+{
+ union {
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin;
+ } sa;
+ int srcsize;
+
+ switch(af)
+ {
+ case AF_INET:
+ sa.sin.sin_family = AF_INET;
+ srcsize = (int)sizeof(sa.sin);
+ break;
+ case AF_INET6:
+ sa.sin6.sin6_family = AF_INET6;
+ srcsize = (int)sizeof(sa.sin6);
+ break;
+ default:
+ errno = WSAEPFNOSUPPORT;
+ return -1;
+ }
+
+ if (WSAStringToAddress((LPSTR)src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0)
+ {
+ errno = WSAGetLastError();
+ return -1;
+ }
+
+ switch(af)
+ {
+ case AF_INET:
+ memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr));
+ break;
+ case AF_INET6:
+ memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr));
+ break;
+ }
+
+ return 1;
+}
diff --git a/src/win32/precompiled.c b/src/win32/precompiled.c
new file mode 100644
index 000000000..5f656a45d
--- /dev/null
+++ b/src/win32/precompiled.c
@@ -0,0 +1 @@
+#include "precompiled.h"
diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h
new file mode 100644
index 000000000..5de7e6f34
--- /dev/null
+++ b/src/win32/precompiled.h
@@ -0,0 +1,19 @@
+#include "git2.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <regex.h>
+
+#include <io.h>
+#include <direct.h>
+#ifdef GIT_THREADS
+ #include "win32/pthread.h"
+#endif
diff --git a/src/win32/pthread.c b/src/win32/pthread.c
index 3a186c8d9..105f4b89e 100644
--- a/src/win32/pthread.c
+++ b/src/win32/pthread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -54,6 +54,74 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex)
return 0;
}
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
+{
+ /* We don't support non-default attributes. */
+ if (attr)
+ return EINVAL;
+
+ /* This is an auto-reset event. */
+ *cond = CreateEventW(NULL, FALSE, FALSE, NULL);
+ assert(*cond);
+
+ /* If we can't create the event, claim that the reason was out-of-memory.
+ * The actual reason can be fetched with GetLastError(). */
+ return *cond ? 0 : ENOMEM;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+ BOOL closed;
+
+ if (!cond)
+ return EINVAL;
+
+ closed = CloseHandle(*cond);
+ assert(closed);
+ GIT_UNUSED(closed);
+
+ *cond = NULL;
+ return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+ int error;
+ DWORD wait_result;
+
+ if (!cond || !mutex)
+ return EINVAL;
+
+ /* The caller must be holding the mutex. */
+ error = pthread_mutex_unlock(mutex);
+
+ if (error)
+ return error;
+
+ wait_result = WaitForSingleObject(*cond, INFINITE);
+ assert(WAIT_OBJECT_0 == wait_result);
+ GIT_UNUSED(wait_result);
+
+ return pthread_mutex_lock(mutex);
+}
+
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+ BOOL signaled;
+
+ if (!cond)
+ return EINVAL;
+
+ signaled = SetEvent(*cond);
+ assert(signaled);
+ GIT_UNUSED(signaled);
+
+ return 0;
+}
+
+/* pthread_cond_broadcast is not implemented because doing so with just Win32 events
+ * is quite complicated, and no caller in libgit2 uses it yet. */
+
int pthread_num_processors_np(void)
{
DWORD_PTR p, s;
diff --git a/src/win32/pthread.h b/src/win32/pthread.h
index b194cbfa7..a219a0137 100644
--- a/src/win32/pthread.h
+++ b/src/win32/pthread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -21,6 +21,7 @@ typedef int pthread_condattr_t;
typedef int pthread_attr_t;
typedef CRITICAL_SECTION pthread_mutex_t;
typedef HANDLE pthread_t;
+typedef HANDLE pthread_cond_t;
#define PTHREAD_MUTEX_INITIALIZER {(void*)-1};
@@ -35,6 +36,12 @@ int pthread_mutex_destroy(pthread_mutex_t *);
int pthread_mutex_lock(pthread_mutex_t *);
int pthread_mutex_unlock(pthread_mutex_t *);
+int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);
+int pthread_cond_destroy(pthread_cond_t *);
+int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);
+int pthread_cond_signal(pthread_cond_t *);
+/* pthread_cond_broadcast is not supported on Win32 yet. */
+
int pthread_num_processors_np(void);
#endif
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index 76f1e4237..c06f3a8c2 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -7,89 +7,75 @@
#include "common.h"
#include "utf-conv.h"
-#include "git2/windows.h"
-/*
- * Default codepage value
- */
-static int _active_codepage = CP_UTF8;
-
-void gitwin_set_codepage(unsigned int codepage)
-{
- _active_codepage = codepage;
-}
-
-unsigned int gitwin_get_codepage(void)
-{
- return _active_codepage;
-}
-
-void gitwin_set_utf8(void)
-{
- _active_codepage = CP_UTF8;
-}
+#define U16_LEAD(c) (wchar_t)(((c)>>10)+0xd7c0)
+#define U16_TRAIL(c) (wchar_t)(((c)&0x3ff)|0xdc00)
-wchar_t* gitwin_to_utf16(const char* str)
+#if 0
+void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
{
- wchar_t* ret;
- size_t cb;
-
- if (!str)
- return NULL;
-
- cb = strlen(str) * sizeof(wchar_t);
- if (cb == 0)
- return (wchar_t *)git__calloc(1, sizeof(wchar_t));
-
- /* Add space for null terminator */
- cb += sizeof(wchar_t);
-
- ret = (wchar_t *)git__malloc(cb);
- if (!ret)
- return NULL;
-
- if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, (int)cb) == 0) {
- giterr_set(GITERR_OS, "Could not convert string to UTF-16");
- git__free(ret);
- ret = NULL;
+ wchar_t *pDest = dest;
+ uint32_t ch;
+ const uint8_t* pSrc = (uint8_t*) src;
+
+ assert(dest && src && length);
+
+ length--;
+
+ while(*pSrc && length > 0) {
+ ch = *pSrc++;
+ length--;
+
+ if(ch < 0xc0) {
+ /*
+ * ASCII, or a trail byte in lead position which is treated like
+ * a single-byte sequence for better character boundary
+ * resynchronization after illegal sequences.
+ */
+ *pDest++ = (wchar_t)ch;
+ continue;
+ } else if(ch < 0xe0) { /* U+0080..U+07FF */
+ if (pSrc[0]) {
+ /* 0x3080 = (0xc0 << 6) + 0x80 */
+ *pDest++ = (wchar_t)((ch << 6) + *pSrc++ - 0x3080);
+ continue;
+ }
+ } else if(ch < 0xf0) { /* U+0800..U+FFFF */
+ if (pSrc[0] && pSrc[1]) {
+ /* no need for (ch & 0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */
+ /* 0x2080 = (0x80 << 6) + 0x80 */
+ ch = (ch << 12) + (*pSrc++ << 6);
+ *pDest++ = (wchar_t)(ch + *pSrc++ - 0x2080);
+ continue;
+ }
+ } else /* f0..f4 */ { /* U+10000..U+10FFFF */
+ if (length >= 1 && pSrc[0] && pSrc[1] && pSrc[2]) {
+ /* 0x3c82080 = (0xf0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80 */
+ ch = (ch << 18) + (*pSrc++ << 12);
+ ch += *pSrc++ << 6;
+ ch += *pSrc++ - 0x3c82080;
+ *(pDest++) = U16_LEAD(ch);
+ *(pDest++) = U16_TRAIL(ch);
+ length--; /* two bytes for this character */
+ continue;
+ }
+ }
+
+ /* truncated character at the end */
+ *pDest++ = 0xfffd;
+ break;
}
- return ret;
+ *pDest++ = 0x0;
}
+#endif
-int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)
+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
{
- int result = MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, (int)len);
- if (result == 0)
- giterr_set(GITERR_OS, "Could not convert string to UTF-16");
- return result;
+ return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
}
-char* gitwin_from_utf16(const wchar_t* str)
+int git__utf16_to_8(char *out, const wchar_t *input)
{
- char* ret;
- size_t cb;
-
- if (!str)
- return NULL;
-
- cb = wcslen(str) * sizeof(char);
- if (cb == 0)
- return (char *)git__calloc(1, sizeof(char));
-
- /* Add space for null terminator */
- cb += sizeof(char);
-
- ret = (char*)git__malloc(cb);
- if (!ret)
- return NULL;
-
- if (WideCharToMultiByte(_active_codepage, 0, str, -1, ret, (int)cb, NULL, NULL) == 0) {
- giterr_set(GITERR_OS, "Could not convert string to UTF-8");
- git__free(ret);
- ret = NULL;
- }
-
- return ret;
-
+ return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL);
}
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index ae9f29f6c..6cc9205f7 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * 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.
@@ -10,9 +10,10 @@
#ifndef INCLUDE_git_utfconv_h__
#define INCLUDE_git_utfconv_h__
-wchar_t* gitwin_to_utf16(const char* str);
-int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len);
-char* gitwin_from_utf16(const wchar_t* str);
+#define GIT_WIN_PATH (260 + 1)
+
+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src);
+int git__utf16_to_8(char *dest, const wchar_t *src);
#endif
diff --git a/src/win32/version.h b/src/win32/version.h
new file mode 100644
index 000000000..483962b57
--- /dev/null
+++ b/src/win32/version.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_win32_version_h__
+#define INCLUDE_win32_version_h__
+
+#include <windows.h>
+
+GIT_INLINE(int) git_has_win32_version(int major, int minor)
+{
+ WORD wVersion = LOWORD(GetVersion());
+
+ return LOBYTE(wVersion) > major ||
+ (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor);
+}
+
+#endif