summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attr.c38
-rw-r--r--src/attr_file.h10
-rw-r--r--src/branch.c51
-rw-r--r--src/buffer.c28
-rw-r--r--src/buffer.h25
-rw-r--r--src/cache.c1
-rw-r--r--src/checkout.c648
-rw-r--r--src/clone.c85
-rw-r--r--src/common.h3
-rw-r--r--src/config.c162
-rw-r--r--src/config.h18
-rw-r--r--src/config_file.c57
-rw-r--r--src/diff.c622
-rw-r--r--src/diff.h13
-rw-r--r--src/diff_output.c130
-rw-r--r--src/diff_output.h6
-rw-r--r--src/diff_tform.c466
-rw-r--r--src/errors.c51
-rw-r--r--src/fetch.c362
-rw-r--r--src/fetch.h14
-rw-r--r--src/fetchhead.c126
-rw-r--r--src/fetchhead.h27
-rw-r--r--src/filebuf.c55
-rw-r--r--src/filebuf.h4
-rw-r--r--src/fileops.c231
-rw-r--r--src/fileops.h84
-rw-r--r--src/filter.c4
-rw-r--r--src/global.c51
-rw-r--r--src/global.h9
-rw-r--r--src/hash.c75
-rw-r--r--src/hash.h30
-rw-r--r--src/hash/hash_generic.c (renamed from src/sha1/sha1.c)30
-rw-r--r--src/hash/hash_generic.h24
-rw-r--r--src/hash/hash_openssl.h45
-rw-r--r--src/hash/hash_ppc.c (renamed from src/ppc/sha1.c)24
-rw-r--r--src/hash/hash_ppc.h (renamed from src/ppc/sha1.h)20
-rw-r--r--src/hash/hash_ppc_core.S (renamed from src/ppc/sha1ppc.S)4
-rw-r--r--src/hash/hash_win32.c291
-rw-r--r--src/hash/hash_win32.h140
-rw-r--r--src/index.c845
-rw-r--r--src/index.h15
-rw-r--r--src/indexer.c84
-rw-r--r--src/iterator.c80
-rw-r--r--src/iterator.h14
-rw-r--r--src/merge.c48
-rw-r--r--src/merge.h19
-rw-r--r--src/netops.c197
-rw-r--r--src/netops.h55
-rw-r--r--src/odb.c80
-rw-r--r--src/odb_pack.c67
-rw-r--r--src/pack-objects.c67
-rw-r--r--src/pack-objects.h15
-rw-r--r--src/path.c5
-rw-r--r--src/pathspec.c151
-rw-r--r--src/pathspec.h32
-rw-r--r--src/pkt.h91
-rw-r--r--src/posix.c2
-rw-r--r--src/protocol.c110
-rw-r--r--src/protocol.h21
-rw-r--r--src/reflog.c49
-rw-r--r--src/refs.c20
-rw-r--r--src/refs.h12
-rw-r--r--src/refspec.c18
-rw-r--r--src/refspec.h10
-rw-r--r--src/remote.c682
-rw-r--r--src/remote.h7
-rw-r--r--src/repository.c173
-rw-r--r--src/reset.c22
-rw-r--r--src/revparse.c8
-rw-r--r--src/sha1.h33
-rw-r--r--src/stash.c660
-rw-r--r--src/status.c89
-rw-r--r--src/submodule.c14
-rw-r--r--src/tag.c19
-rw-r--r--src/transport.c91
-rw-r--r--src/transport.h148
-rw-r--r--src/transports/cred.c57
-rw-r--r--src/transports/git.c290
-rw-r--r--src/transports/http.c929
-rw-r--r--src/transports/local.c281
-rw-r--r--src/transports/smart.c284
-rw-r--r--src/transports/smart.h150
-rw-r--r--src/transports/smart_pkt.c (renamed from src/pkt.c)25
-rw-r--r--src/transports/smart_protocol.c481
-rw-r--r--src/transports/winhttp.c634
-rw-r--r--src/tree.c24
-rw-r--r--src/tree.h6
-rw-r--r--src/unix/posix.h4
-rw-r--r--src/util.c41
-rw-r--r--src/util.h21
-rw-r--r--src/vector.c44
-rw-r--r--src/vector.h13
-rw-r--r--src/win32/posix.h9
-rw-r--r--src/win32/posix_w32.c165
-rw-r--r--src/win32/utf-conv.c8
-rw-r--r--src/win32/utf-conv.h4
96 files changed, 8582 insertions, 3005 deletions
diff --git a/src/attr.c b/src/attr.c
index 025ad3c87..b5757446f 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -261,32 +261,26 @@ 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);
+ error = git_futils_readbuffer(&content, filename);
if (error < 0)
return error;
- 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;
- }
-
*data = git_buf_detach(&content);
return 0;
@@ -307,7 +301,7 @@ static int load_attr_blob_from_index(
(error = git_index_find(index, relfile)) < 0)
return error;
- entry = git_index_get(index, error);
+ entry = git_index_get_byindex(index, error);
if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
return GIT_ENOTFOUND;
@@ -386,7 +380,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);
@@ -408,12 +402,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);
@@ -448,7 +440,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*/
diff --git a/src/attr_file.h b/src/attr_file.h
index 877daf306..5bdfc7054 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -7,10 +7,12 @@
#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"
@@ -54,19 +56,13 @@ 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;
diff --git a/src/branch.c b/src/branch.c
index 991314508..c6173caca 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -92,6 +92,8 @@ cleanup:
int git_branch_delete(git_reference *branch)
{
int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
assert(branch);
@@ -110,7 +112,23 @@ int git_branch_delete(git_reference *branch)
return -1;
}
- return git_reference_delete(branch);
+ 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;
+
+ error = 0;
+
+on_error:
+ git_buf_free(&config_section);
+ return error;
}
typedef struct {
@@ -161,7 +179,9 @@ int git_branch_move(
const char *new_branch_name,
int force)
{
- git_buf new_reference_name = GIT_BUF_INIT;
+ git_buf new_reference_name = GIT_BUF_INIT,
+ old_config_section = GIT_BUF_INIT,
+ new_config_section = GIT_BUF_INIT;
int error;
assert(branch && new_branch_name);
@@ -172,10 +192,28 @@ int git_branch_move(
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
goto cleanup;
- error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force);
+ if (git_buf_printf(
+ &old_config_section,
+ "branch.%s",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0)
+ goto cleanup;
+
+ if ((error = git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section))) < 0)
+ goto cleanup;
cleanup:
git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
return error;
}
@@ -230,6 +268,11 @@ int git_branch_tracking(
if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0)
goto cleanup;
+
+ if (remote_name == NULL || merge_name == NULL) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
if (strcmp(".", remote_name) != 0) {
if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0)
@@ -274,7 +317,7 @@ int git_branch_is_head(
error = git_repository_head(&head, git_reference_owner(branch));
- if (error == GIT_EORPHANEDHEAD)
+ if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
return false;
if (error < 0)
diff --git a/src/buffer.c b/src/buffer.c
index b40b16b66..e55b0a230 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -549,3 +549,31 @@ void git_buf_unescape(git_buf *buf)
{
buf->size = git__unescape(buf->ptr);
}
+
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert)
+{
+ 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;
+
+ memmove(buf->ptr + where + nb_to_insert,
+ buf->ptr + where + nb_to_remove,
+ buf->size - where - nb_to_remove);
+
+ 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;
+}
diff --git a/src/buffer.h b/src/buffer.h
index 2aae06c7c..a2896d486 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -158,4 +158,29 @@ void git_buf_unescape(git_buf *buf);
/* Write data as base64 encoded in buffer */
int git_buf_put_base64(git_buf *buf, const char *data, size_t len);
+/*
+ * 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 1f5b8872c..edd3a47dd 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -41,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);
}
diff --git a/src/checkout.c b/src/checkout.c
index b56b459d2..eff14813d 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -21,19 +21,20 @@
#include "repository.h"
#include "filter.h"
#include "blob.h"
+#include "diff.h"
+#include "pathspec.h"
-struct checkout_diff_data
-{
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_checkout_opts *opts;
git_buf *path;
size_t workdir_len;
- git_checkout_opts *checkout_opts;
- git_indexer_stats *stats;
- git_repository *owner;
bool can_symlink;
- bool found_submodules;
- bool create_submodules;
int error;
-};
+ size_t total_steps;
+ size_t completed_steps;
+} checkout_diff_data;
static int buffer_to_file(
git_buf *buffer,
@@ -42,20 +43,23 @@ static int buffer_to_file(
int file_open_flags,
mode_t file_mode)
{
- int fd, error, error_close;
+ 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)
+ if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
return fd;
+ }
- error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
-
- error_close = p_close(fd);
-
- if (!error)
- error = error_close;
+ 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 &&
(file_mode & 0100) != 0 &&
@@ -107,7 +111,8 @@ static int blob_content_to_file(
if (!file_mode)
file_mode = entry_filemode;
- error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
+ error = buffer_to_file(
+ &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
cleanup:
git_filters_free(&filters);
@@ -118,7 +123,8 @@ cleanup:
return error;
}
-static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink)
+static int blob_content_to_link(
+ git_blob *blob, const char *path, bool can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
@@ -137,39 +143,52 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
}
static int checkout_submodule(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const git_diff_file *file)
{
+ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
+ if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
if (git_futils_mkdir(
- file->path, git_repository_workdir(data->owner),
- data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0)
+ file->path, git_repository_workdir(data->repo),
+ data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
return -1;
- /* TODO: two cases:
+ /* 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 request on the submodule, but a
- * clone command should probably be able to. Do we need a submodule
- * callback option?
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
*/
return 0;
}
+static void report_progress(
+ checkout_diff_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_blob(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const git_diff_file *file)
{
+ int error = 0;
git_blob *blob;
- int error;
git_buf_truncate(data->path, data->workdir_len);
- if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0)
+ if (git_buf_puts(data->path, file->path) < 0)
return -1;
- if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0)
+ if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
return error;
if (S_ISLNK(file->mode))
@@ -177,145 +196,421 @@ static int checkout_blob(
blob, git_buf_cstr(data->path), data->can_symlink);
else
error = blob_content_to_file(
- blob, git_buf_cstr(data->path), file->mode, data->checkout_opts);
+ blob, git_buf_cstr(data->path), file->mode, data->opts);
git_blob_free(blob);
return error;
}
-static int checkout_remove_the_old(
- void *cb_data, const git_diff_delta *delta, float progress)
+static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink)
{
- struct checkout_diff_data *data = cb_data;
- git_checkout_opts *opts = data->checkout_opts;
+ git_config *cfg;
+ int error;
- GIT_UNUSED(progress);
- data->stats->processed++;
+ if (git_repository_config__weakptr(&cfg, repo) < 0)
+ return -1;
- if ((delta->status == GIT_DELTA_UNTRACKED &&
- (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) ||
- (delta->status == GIT_DELTA_TYPECHANGE &&
- (opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0))
- {
- data->error = git_futils_rmdir_r(
- delta->new_file.path,
- git_repository_workdir(data->owner),
- GIT_DIRREMOVAL_FILES_AND_DIRS);
+ error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
+
+ /* If "core.symlinks" is not found anywhere, default to true. */
+ if (error == GIT_ENOTFOUND) {
+ *can_symlink = true;
+ error = 0;
}
- return data->error;
+ return error;
}
-static int checkout_create_the_new(
- void *cb_data, const git_diff_delta *delta, float progress)
+static void normalize_options(
+ git_checkout_opts *normalized, git_checkout_opts *proposed)
{
- int error = 0;
- struct checkout_diff_data *data = cb_data;
- git_checkout_opts *opts = data->checkout_opts;
- bool do_checkout = false, do_notify = false;
+ assert(normalized);
- GIT_UNUSED(progress);
- data->stats->processed++;
+ if (!proposed)
+ memset(normalized, 0, sizeof(git_checkout_opts));
+ else
+ memmove(normalized, proposed, sizeof(git_checkout_opts));
- if (delta->status == GIT_DELTA_MODIFIED ||
- delta->status == GIT_DELTA_TYPECHANGE)
- {
- if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
- do_checkout = true;
- else if (opts->skipped_notify_cb != NULL)
- do_notify = !data->create_submodules;
+ /* implied checkout strategies */
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 ||
+ (normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED;
+
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING;
+
+ /* opts->disable_filters is false by default */
+
+ if (!normalized->dir_mode)
+ normalized->dir_mode = GIT_DIR_MODE;
+
+ if (!normalized->file_open_flags)
+ normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+}
+
+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
+};
+
+static int checkout_confirm_update_blob(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ int action)
+{
+ int error;
+ unsigned int strat = data->opts->checkout_strategy;
+ struct stat st;
+ bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0);
+
+ /* for typechange, remove the old item first */
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else
+ action |= CHECKOUT_ACTION__REMOVE;
+
+ return action;
}
- else if (delta->status == GIT_DELTA_DELETED &&
- (opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
- do_checkout = true;
- if (do_notify) {
- if (opts->skipped_notify_cb(
- delta->old_file.path, &delta->old_file.oid,
- delta->old_file.mode, opts->notify_payload))
- {
- giterr_clear();
- error = GIT_EUSER;
+ git_buf_truncate(data->path, data->workdir_len);
+ if (git_buf_puts(data->path, delta->new_file.path) < 0)
+ return -1;
+
+ if ((error = p_lstat_posixly(git_buf_cstr(data->path), &st)) < 0) {
+ if (errno == ENOENT) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ } else if (errno == ENOTDIR) {
+ /* File exists where a parent dir needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
}
+ /* otherwise let error happen when we attempt blob checkout later */
+ }
+ else if (S_ISDIR(st.st_mode)) {
+ /* Directory exists where a blob needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
}
- if (do_checkout) {
- bool is_submodule = S_ISGITLINK(delta->old_file.mode);
+ return action;
+}
- if (is_submodule)
- data->found_submodules = true;
+static int checkout_action_for_delta(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *head_entry)
+{
+ int action = CHECKOUT_ACTION__NONE;
+ unsigned int strat = data->opts->checkout_strategy;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ if (!head_entry) {
+ /* file independently created in wd, even though not in HEAD */
+ if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) {
+ /* working directory was independently updated to match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ break;
+
+ case GIT_DELTA_ADDED:
+ /* Impossible. New files should be UNTRACKED or TYPECHANGE */
+ action = CHECKOUT_ACTION__CONFLICT;
+ break;
+
+ case GIT_DELTA_DELETED:
+ if (head_entry && /* working dir missing, but exists in HEAD */
+ (strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ break;
+
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ if (!head_entry) {
+ /* working dir was independently updated & does not match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ }
+ else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid))
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ break;
+
+ case GIT_DELTA_UNTRACKED:
+ if (!head_entry) {
+ if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
+ action = CHECKOUT_ACTION__REMOVE;
+ }
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) {
+ action = CHECKOUT_ACTION__REMOVE;
+ } else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) {
+ git_oid wd_oid;
+
+ /* if HEAD matches workdir, then remove, else conflict */
+
+ if (git_oid_iszero(&delta->new_file.oid) &&
+ git_diff__oid_for_file(
+ data->repo, delta->new_file.path, delta->new_file.mode,
+ delta->new_file.size, &wd_oid) < 0)
+ action = -1;
+ else if (git_oid_equal(&head_entry->oid, &wd_oid))
+ action = CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
+ } else {
+ /* present in HEAD and workdir, but absent in index */
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ break;
- if (!is_submodule && !data->create_submodules)
- error = checkout_blob(data, &delta->old_file);
+ case GIT_DELTA_IGNORED:
+ default:
+ /* just skip these files */
+ break;
+ }
+
+ if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->old_file.mode))
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
- else if (is_submodule && data->create_submodules)
- error = checkout_submodule(data, &delta->old_file);
+ action = checkout_confirm_update_blob(data, delta, action);
+ }
+
+ if (action == CHECKOUT_ACTION__CONFLICT &&
+ data->opts->conflict_cb != NULL &&
+ data->opts->conflict_cb(
+ delta->old_file.path, &delta->old_file.oid,
+ delta->old_file.mode, delta->new_file.mode,
+ data->opts->conflict_payload) != 0)
+ {
+ giterr_clear();
+ action = GIT_EUSER;
}
- if (error)
- data->error = error;
+ if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ action = (action & ~CHECKOUT_ACTION__REMOVE);
- return error;
+ return action;
}
-static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink)
+static int checkout_get_actions(
+ uint32_t **actions_ptr,
+ size_t **counts_ptr,
+ checkout_diff_data *data)
{
- git_config *cfg;
int error;
+ git_diff_list *diff = data->diff;
+ git_diff_delta *delta;
+ size_t i, *counts = NULL;
+ uint32_t *actions = NULL;
+ git_tree *head = NULL;
+ git_iterator *hiter = NULL;
+ char *pfx = git_pathspec_prefix(&data->opts->paths);
+ const git_index_entry *he;
+
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ (void)git_repository_head_tree(&head, data->repo);
+
+ if ((error = git_iterator_for_tree_range(
+ &hiter, data->repo, head, pfx, pfx)) < 0)
+ goto fail;
+
+ if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ !hiter->ignore_case &&
+ (error = git_iterator_spoolandsort(
+ &hiter, hiter, diff->entrycomp, true)) < 0)
+ goto fail;
+
+ if ((error = git_iterator_current(hiter, &he)) < 0)
+ goto fail;
+
+ git__free(pfx);
+ pfx = NULL;
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
- if (git_repository_config__weakptr(&cfg, repo) < 0)
- return -1;
+ git_vector_foreach(&diff->deltas, i, delta) {
+ int cmp = -1, act;
+
+ /* try to track HEAD entries parallel to deltas */
+ while (he) {
+ cmp = S_ISDIR(delta->new_file.mode) ?
+ diff->pfxcomp(he->path, delta->new_file.path) :
+ diff->strcomp(he->path, delta->old_file.path);
+ if (cmp >= 0)
+ break;
+ if (git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
+ }
- error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
+ act = checkout_action_for_delta(data, delta, !cmp ? he : NULL);
- /*
- * When no "core.symlinks" entry is found in any of the configuration
- * store (local, global or system), default value is "true".
- */
- if (error == GIT_ENOTFOUND) {
- *can_symlink = true;
- error = 0;
+ if (act < 0) {
+ error = act;
+ goto fail;
+ }
+
+ if (!cmp && git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
+
+ 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]++;
}
- return error;
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
+ (int)counts[CHECKOUT_ACTION__CONFLICT]);
+ goto fail;
+ }
+
+ git_iterator_free(hiter);
+ git_tree_free(head);
+
+ return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_iterator_free(hiter);
+ git_tree_free(head);
+ git__free(pfx);
+
+ return -1;
}
-static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed)
+static int checkout_remove_the_old(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
{
- assert(normalized);
+ git_diff_delta *delta;
+ size_t i;
- if (!proposed)
- memset(normalized, 0, sizeof(git_checkout_opts));
- else
- memmove(normalized, proposed, sizeof(git_checkout_opts));
+ git_buf_truncate(data->path, data->workdir_len);
- /* Default options */
- if (!normalized->checkout_strategy)
- normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT;
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {
+ int error = git_futils_rmdir_r(
+ delta->new_file.path,
+ git_buf_cstr(data->path), /* here set to work dir root */
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_BLOCKERS);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
- /* opts->disable_filters is false by default */
- if (!normalized->dir_mode)
- normalized->dir_mode = GIT_DIR_MODE;
+ return 0;
+}
- if (!normalized->file_open_flags)
- normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+static int checkout_create_the_new(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
+{
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
+ int error = checkout_blob(data, &delta->old_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_create_submodules(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
+{
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
+ int error = checkout_submodule(data, &delta->old_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+ }
+ }
+
+ return 0;
}
int git_checkout_index(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_index *index,
+ git_checkout_opts *opts)
{
git_diff_list *diff = NULL;
- git_indexer_stats dummy_stats;
-
git_diff_options diff_opts = {0};
git_checkout_opts checkout_opts;
-
- struct checkout_diff_data data;
+ checkout_diff_data data;
git_buf workdir = GIT_BUF_INIT;
-
+ uint32_t *actions = NULL;
+ size_t *counts = NULL;
int error;
assert(repo);
@@ -324,14 +619,13 @@ int git_checkout_index(
return error;
diff_opts.flags =
- GIT_DIFF_INCLUDE_UNTRACKED |
- GIT_DIFF_INCLUDE_TYPECHANGE |
- GIT_DIFF_SKIP_BINARY_CHECK;
+ GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
if (opts && opts->paths.count > 0)
diff_opts.pathspec = opts->paths;
- if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0)
+ if ((error = git_diff_workdir_to_index(&diff, repo, index, &diff_opts)) < 0)
goto cleanup;
if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0)
@@ -339,27 +633,10 @@ int git_checkout_index(
normalize_options(&checkout_opts, opts);
- if (!stats)
- stats = &dummy_stats;
-
- stats->processed = 0;
- /* total based on 3 passes, but it might be 2 if no submodules */
- stats->total = (unsigned int)git_diff_num_deltas(diff) * 3;
-
- memset(&data, 0, sizeof(data));
-
- data.path = &workdir;
- data.workdir_len = git_buf_len(&workdir);
- data.checkout_opts = &checkout_opts;
- data.stats = stats;
- data.owner = repo;
-
- if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0)
- goto cleanup;
-
- /* Checkout is best performed with three passes through the diff.
+ /* Checkout is best performed with up to four passes through the diff.
*
- * 1. First do removes, because we iterate in alphabetical order, thus
+ * 0. Figure out what actions should be taken and record for later.
+ * 1. Next do removes, because we iterate in alphabetical order, thus
* a new untracked directory will end up sorted *after* a blob that
* should be checked out with the same name.
* 2. Then checkout all blobs.
@@ -367,23 +644,45 @@ int git_checkout_index(
* checked out during pass #2.
*/
- if (!(error = git_diff_foreach(
- diff, &data, checkout_remove_the_old, NULL, NULL)) &&
- !(error = git_diff_foreach(
- diff, &data, checkout_create_the_new, NULL, NULL)) &&
- data.found_submodules)
- {
- data.create_submodules = true;
- error = git_diff_foreach(
- diff, &data, checkout_create_the_new, NULL, NULL);
- }
+ memset(&data, 0, sizeof(data));
+ data.path = &workdir;
+ data.workdir_len = git_buf_len(&workdir);
+ data.repo = repo;
+ data.diff = diff;
+ data.opts = &checkout_opts;
+
+ if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
+ goto cleanup;
+
+ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
+
+ if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0)
+ goto cleanup;
+
+ report_progress(&data, NULL); /* establish 0 baseline */
+
+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
+ (error = checkout_remove_the_old(diff, actions, &data)) < 0)
+ goto cleanup;
- stats->processed = stats->total;
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
+ (error = checkout_create_the_new(diff, actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
+ (error = checkout_create_submodules(diff, actions, &data)) < 0)
+ goto cleanup;
+
+ assert(data.completed_steps == data.total_steps);
cleanup:
if (error == GIT_EUSER)
- error = (data.error != 0) ? data.error : -1;
+ giterr_clear();
+ git__free(actions);
+ git__free(counts);
git_diff_list_free(diff);
git_buf_free(&workdir);
@@ -393,58 +692,49 @@ cleanup:
int git_checkout_tree(
git_repository *repo,
git_object *treeish,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_checkout_opts *opts)
{
+ int error = 0;
git_index *index = NULL;
git_tree *tree = NULL;
- int error;
-
assert(repo && treeish);
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
- giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree.");
- return GIT_ERROR;
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
}
- if ((error = git_repository_index(&index, repo)) < 0)
- goto cleanup;
+ /* TODO: create a temp index, load tree there and check it out */
- if ((error = git_index_read_tree(index, tree, NULL)) < 0)
- goto cleanup;
+ /* load paths in tree that match pathspec into index */
+ if (!(error = git_repository_index(&index, repo)) &&
+ !(error = git_index_read_tree_match(
+ index, tree, opts ? &opts->paths : NULL)) &&
+ !(error = git_index_write(index)))
+ error = git_checkout_index(repo, NULL, opts);
- if ((error = git_index_write(index)) < 0)
- goto cleanup;
-
- error = git_checkout_index(repo, opts, stats);
-
-cleanup:
git_index_free(index);
git_tree_free(tree);
+
return error;
}
int git_checkout_head(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_checkout_opts *opts)
{
- git_reference *head;
int error;
+ git_reference *head = NULL;
git_object *tree = NULL;
assert(repo);
- if ((error = git_repository_head(&head, repo)) < 0)
- return error;
-
- if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0)
- goto cleanup;
+ if (!(error = git_repository_head(&head, repo)) &&
+ !(error = git_reference_peel(&tree, head, GIT_OBJ_TREE)))
+ error = git_checkout_tree(repo, tree, opts);
- error = git_checkout_tree(repo, tree, opts, stats);
-
-cleanup:
git_reference_free(head);
git_object_free(tree);
diff --git a/src/clone.c b/src/clone.c
index 85e69ad97..9ef6f8100 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -18,7 +18,6 @@
#include "common.h"
#include "remote.h"
-#include "pkt.h"
#include "fileops.h"
#include "refs.h"
#include "path.h"
@@ -171,11 +170,19 @@ static int update_head_to_new_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;
- git_pkt_ref *pkt;
struct head_info head_info;
git_buf remote_master_name = GIT_BUF_INIT;
@@ -189,8 +196,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
}
/* Get the remote's HEAD. This is always the first ref in remote->refs. */
- pkt = remote->transport->refs.contents[0];
- remote_head = &pkt->head;
+ 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;
@@ -248,22 +260,26 @@ cleanup:
-static int setup_remotes_and_fetch(git_repository *repo,
- const char *origin_url,
- git_indexer_stats *fetch_stats)
+static int setup_remotes_and_fetch(
+ git_repository *repo,
+ const char *origin_url,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
int retcode = GIT_ERROR;
git_remote *origin = NULL;
- git_off_t bytes = 0;
- git_indexer_stats dummy_stats;
-
- if (!fetch_stats) fetch_stats = &dummy_stats;
/* Create the "origin" remote */
if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) {
+ /*
+ * 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);
+
/* Connect and download everything */
if (!git_remote_connect(origin, GIT_DIR_FETCH)) {
- if (!git_remote_download(origin, &bytes, fetch_stats)) {
+ if (!git_remote_download(origin, progress_cb, progress_payload)) {
/* Create "origin/foo" branches for all remote branches */
if (!git_remote_update_tips(origin)) {
/* Point HEAD to the same ref as the remote's head */
@@ -311,26 +327,24 @@ static int clone_internal(
git_repository **out,
const char *origin_url,
const char *path,
- git_indexer_stats *fetch_stats,
- git_indexer_stats *checkout_stats,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload,
git_checkout_opts *checkout_opts,
bool is_bare)
{
int retcode = GIT_ERROR;
git_repository *repo = NULL;
- git_indexer_stats dummy_stats;
-
- if (!fetch_stats) fetch_stats = &dummy_stats;
if (!path_is_okay(path)) {
return GIT_ERROR;
}
if (!(retcode = git_repository_init(&repo, path, is_bare))) {
- if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) {
+ if ((retcode = setup_remotes_and_fetch(repo, origin_url,
+ fetch_progress_cb, fetch_progress_payload)) < 0) {
/* Failed to fetch; clean up */
git_repository_free(repo);
- git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
+ git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES);
} else {
*out = repo;
retcode = 0;
@@ -338,15 +352,17 @@ static int clone_internal(
}
if (!retcode && should_checkout(repo, is_bare, checkout_opts))
- retcode = git_checkout_head(*out, checkout_opts, checkout_stats);
+ retcode = git_checkout_head(*out, checkout_opts);
return retcode;
}
-int git_clone_bare(git_repository **out,
- const char *origin_url,
- const char *dest_path,
- git_indexer_stats *fetch_stats)
+int git_clone_bare(
+ git_repository **out,
+ const char *origin_url,
+ const char *dest_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload)
{
assert(out && origin_url && dest_path);
@@ -354,19 +370,20 @@ int git_clone_bare(git_repository **out,
out,
origin_url,
dest_path,
- fetch_stats,
- NULL,
+ fetch_progress_cb,
+ fetch_progress_payload,
NULL,
1);
}
-int git_clone(git_repository **out,
- const char *origin_url,
- const char *workdir_path,
- git_indexer_stats *fetch_stats,
- git_indexer_stats *checkout_stats,
- git_checkout_opts *checkout_opts)
+int git_clone(
+ git_repository **out,
+ const char *origin_url,
+ const char *workdir_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload,
+ git_checkout_opts *checkout_opts)
{
assert(out && origin_url && workdir_path);
@@ -374,8 +391,8 @@ int git_clone(git_repository **out,
out,
origin_url,
workdir_path,
- fetch_stats,
- checkout_stats,
+ fetch_progress_cb,
+ fetch_progress_payload,
checkout_opts,
0);
}
diff --git a/src/common.h b/src/common.h
index 747bbf7ce..a35239e3d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -69,7 +69,4 @@ void giterr_set_regex(const regex_t *regex, int error_code);
#include "util.h"
-typedef struct git_transport git_transport;
-typedef struct gitno_buffer gitno_buffer;
-
#endif /* INCLUDE_common_h__ */
diff --git a/src/config.c b/src/config.c
index f9bd205af..fc9ea65fe 100644
--- a/src/config.c
+++ b/src/config.c
@@ -90,6 +90,13 @@ int git_config_add_file_ondisk(
git_config_file *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;
@@ -105,17 +112,22 @@ int git_config_add_file_ondisk(
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;
+
+ *out = NULL;
- if (git_config_add_file_ondisk(*cfg, path, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) {
- git_config_free(*cfg);
+ if (git_config_new(&config) < 0)
return -1;
- }
- return 0;
+ 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(
@@ -127,8 +139,6 @@ static int find_internal_file_by_level(
file_internal *internal;
unsigned int i;
- assert(cfg->files.length);
-
/* 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
@@ -267,6 +277,20 @@ int git_config_add_file(
return 0;
}
+int git_config_refresh(git_config *cfg)
+{
+ int error = 0;
+ unsigned int i;
+
+ for (i = 0; i < cfg->files.length && !error; ++i) {
+ file_internal *internal = git_vector_get(&cfg->files, i);
+ git_config_file *file = internal->file;
+ error = file->refresh(file);
+ }
+
+ return error;
+}
+
/*
* Loop over all the variables
*/
@@ -381,32 +405,19 @@ int git_config_get_int32(int32_t *out, git_config *cfg, const char *name)
return git_config_parse_int32(out, value);
}
-int git_config_get_bool(int *out, git_config *cfg, const char *name)
-{
- const char *value;
- int ret;
-
- if ((ret = git_config_get_string(&value, cfg, name)) < 0)
- return ret;
-
- return git_config_parse_bool(out, value);
-}
-
static int get_string_at_file(const char **out, git_config_file *file, const char *name)
{
const git_config_entry *entry;
int res;
- *out = NULL;
-
res = file->get(file, name, &entry);
- if (res != GIT_ENOTFOUND)
+ if (!res)
*out = entry->value;
return res;
}
-int git_config_get_string(const char **out, git_config *cfg, const char *name)
+static int get_string(const char **out, git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
@@ -423,7 +434,30 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name)
return GIT_ENOTFOUND;
}
-int git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name)
+int git_config_get_bool(int *out, git_config *cfg, const char *name)
+{
+ const char *value = NULL;
+ int ret;
+
+ if ((ret = get_string(&value, cfg, name)) < 0)
+ return ret;
+
+ return git_config_parse_bool(out, value);
+}
+
+int git_config_get_string(const char **out, git_config *cfg, const char *name)
+{
+ int ret;
+ const char *str = NULL;
+
+ if ((ret = get_string(&str, cfg, name)) < 0)
+ return ret;
+
+ *out = str == NULL ? "" : str;
+ return 0;
+}
+
+int git_config_get_entry(const git_config_entry **out, git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
@@ -720,3 +754,81 @@ fail_parse:
giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
return -1;
}
+
+struct rename_data
+{
+ git_config *config;
+ const char *old_name;
+ const char *new_name;
+};
+
+static int rename_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ struct rename_data *data = (struct rename_data *)payload;
+
+ if (data->new_name != NULL) {
+ git_buf name = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(
+ &name,
+ "%s.%s",
+ data->new_name,
+ entry->name + strlen(data->old_name) + 1) < 0)
+ return -1;
+
+ error = git_config_set_string(
+ data->config,
+ git_buf_cstr(&name),
+ entry->value);
+
+ git_buf_free(&name);
+
+ if (error)
+ return error;
+ }
+
+ return git_config_delete(data->config, entry->name);
+}
+
+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;
+ int error = -1;
+ struct rename_data data;
+
+ git_buf_puts_escape_regex(&pattern, old_section_name);
+ git_buf_puts(&pattern, "\\..+");
+ if (git_buf_oom(&pattern))
+ goto cleanup;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ goto cleanup;
+
+ data.config = config;
+ data.old_name = old_section_name;
+ data.new_name = new_section_name;
+
+ if ((error = git_config_foreach_match(
+ config,
+ git_buf_cstr(&pattern),
+ rename_config_entries_cb, &data)) < 0) {
+ giterr_set(GITERR_CONFIG,
+ "Cannot rename config section '%s' to '%s'",
+ old_section_name,
+ new_section_name);
+ goto cleanup;
+ }
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&pattern);
+ return error;
+}
diff --git a/src/config.h b/src/config.h
index 16b8413b7..c7595ee79 100644
--- a/src/config.h
+++ b/src/config.h
@@ -27,4 +27,22 @@ 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_rename_section(
+ git_repository *repo,
+ const char *old_section_name, /* eg "branch.dummy" */
+ const char *new_section_name); /* NULL to drop the old section */
+
+/**
+ * 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_file **out, const char *path);
+
#endif
diff --git a/src/config_file.c b/src/config_file.c
index 92fe13296..4d9f99986 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -75,7 +75,11 @@ 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, unsigned int level);
@@ -150,25 +154,53 @@ static int config_open(git_config_file *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, level) < 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 int config_refresh(git_config_file *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_file *_backend)
@@ -527,6 +559,7 @@ 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;
@@ -891,7 +924,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';
@@ -1197,8 +1230,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:
@@ -1361,7 +1398,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
value_start = var_end + 1;
do var_end--;
- while (git__isspace(*var_end));
+ while (var_end>line && git__isspace(*var_end));
*var_name = git__strndup(line, var_end - line + 1);
GITERR_CHECK_ALLOC(*var_name);
@@ -1395,8 +1432,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/diff.c b/src/diff.c
index 9f693bebf..d6f5bd454 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -10,76 +10,7 @@
#include "config.h"
#include "attr_file.h"
#include "filter.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; ++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_unescape(&prefix);
-
- return git_buf_detach(&prefix);
-}
-
-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 = strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
-
- if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) &&
- result == FNM_NOMATCH)
- 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;
-}
+#include "pathspec.h"
static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
@@ -110,85 +41,6 @@ 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)
-{
- 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;
-}
-
static int diff_delta__from_one(
git_diff_list *diff,
git_delta_t status,
@@ -204,7 +56,10 @@ static int diff_delta__from_one(
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0;
- if (!diff_path_matches_pathspec(diff, entry->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, entry->path,
+ (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
+ (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
@@ -332,13 +187,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;
@@ -353,7 +229,6 @@ 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;
@@ -361,7 +236,7 @@ static git_diff_list *git_diff_list_alloc(
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;
@@ -378,11 +253,28 @@ static git_diff_list *git_diff_list_alloc(
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
+ /* TODO: there are certain config settings where even if we were
+ * not given an options structure, we need the diff list to have one
+ * so that we can store the altered default values.
+ *
+ * - diff.ignoreSubmodules
+ * - diff.mnemonicprefix
+ * - diff.noprefix
+ */
+
if (opts == NULL)
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);
@@ -402,35 +294,6 @@ static git_diff_list *git_diff_list_alloc(
if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
- /* 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;
- match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
- 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;
- }
-
return diff;
fail:
@@ -441,7 +304,6 @@ fail:
static void diff_list_free(git_diff_list *diff)
{
git_diff_delta *delta;
- git_attr_fnmatch *match;
unsigned int i;
git_vector_foreach(&diff->deltas, i, delta) {
@@ -450,12 +312,7 @@ static void diff_list_free(git_diff_list *diff)
}
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);
}
@@ -473,24 +330,39 @@ void git_diff_list_addref(git_diff_list *diff)
GIT_REFCOUNT_INC(diff);
}
-static int oid_for_workdir_item(
+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 = 0;
git_buf full_path = GIT_BUF_INIT;
if (git_buf_joinpath(
- &full_path, git_repository_workdir(repo), item->path) < 0)
+ &full_path, git_repository_workdir(repo), path) < 0)
return -1;
+ 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(item->mode)) {
+ if (S_ISGITLINK(mode)) {
git_submodule *sm;
const git_oid *sm_oid;
- if (!git_submodule_lookup(&sm, repo, item->path) &&
+ if (!git_submodule_lookup(&sm, repo, path) &&
(sm_oid = git_submodule_wd_oid(sm)) != NULL)
git_oid_cpy(oid, sm_oid);
else {
@@ -500,23 +372,22 @@ static int oid_for_workdir_item(
giterr_clear();
memset(oid, 0, sizeof(*oid));
}
- } else if (S_ISLNK(item->mode))
+ } 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 {
git_vector filters = GIT_VECTOR_INIT;
- result = git_filters_load(
- &filters, repo, item->path, GIT_FILTER_TO_ODB);
+ 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)item->file_size, GIT_OBJ_BLOB, &filters);
+ oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters);
p_close(fd);
}
}
@@ -524,8 +395,8 @@ static int oid_for_workdir_item(
git_filters_free(&filters);
}
+cleanup:
git_buf_free(&full_path);
-
return result;
}
@@ -546,7 +417,10 @@ static int maybe_modified(
GIT_UNUSED(old_iter);
- if (!diff_path_matches_pathspec(diff, oitem->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, oitem->path,
+ (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
+ (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0))
return 0;
/* on platforms with no symlinks, preserve mode of existing symlinks */
@@ -582,8 +456,7 @@ static int maybe_modified(
}
/* 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 an unknown OID and a workdir iterator, then check some
@@ -637,44 +510,28 @@ static int maybe_modified(
* haven't calculated the OID of the new item, then calculate it now
*/
if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
- if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
- return -1;
- else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
+ 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;
+ }
+ if (omode == nmode && git_oid_equal(&oitem->oid, use_noid))
status = GIT_DELTA_UNMODIFIED;
-
- /* store calculated oid so we don't have to recalc later */
- use_noid = &noid;
}
return diff_delta__from_two(
diff, status, oitem, omode, nitem, nmode, use_noid);
}
-static int git_index_entry_cmp_case(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);
-}
-
-static 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 bool entry_is_prefixed(
+ git_diff_list *diff,
const git_index_entry *item,
- git_iterator *prefix_iterator,
const git_index_entry *prefix_item)
{
size_t pathlen;
- if (!prefix_item ||
- ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path))
+ if (!prefix_item || diff->pfxcomp(prefix_item->path, item->path))
return false;
pathlen = strlen(item->path);
@@ -684,44 +541,67 @@ static bool entry_is_prefixed(
prefix_item->path[pathlen] == '/');
}
-static int diff_from_iterators(
- git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
+static int diff_list_init_from_iterators(
+ git_diff_list *diff,
git_iterator *old_iter,
- git_iterator *new_iter,
- git_diff_list **diff_ptr)
+ git_iterator *new_iter)
{
- const git_index_entry *oitem, *nitem;
- git_buf ignore_prefix = GIT_BUF_INIT;
- git_diff_list *diff = git_diff_list_alloc(repo, opts);
- git_vector_cmp entry_compare;
-
- if (!diff)
- goto fail;
-
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 (!old_iter->ignore_case && !new_iter->ignore_case) {
- entry_compare = git_index_entry_cmp_case;
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 {
- entry_compare = git_index_entry_cmp_icase;
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 0;
+}
+
+static int diff_from_iterators(
+ git_diff_list **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ const git_index_entry *oitem, *nitem;
+ git_buf ignore_prefix = GIT_BUF_INIT;
+ git_diff_list *diff = git_diff_list_alloc(repo, opts);
+
+ *diff_ptr = NULL;
+
+ if (!diff ||
+ diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
+ goto fail;
+
+ if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
/* If one of the iterators doesn't have ignore_case set,
* then that's unfortunate because we'll have to spool
* its data, sort it icase, and then use that for our
* merge join to the other iterator that is icase sorted */
- if (!old_iter->ignore_case) {
- if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0)
- goto fail;
- } else if (!new_iter->ignore_case) {
- if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0)
- goto fail;
- }
+ if (!old_iter->ignore_case &&
+ git_iterator_spoolandsort(
+ &old_iter, old_iter, diff->entrycomp, true) < 0)
+ goto fail;
+
+ if (!new_iter->ignore_case &&
+ git_iterator_spoolandsort(
+ &new_iter, new_iter, diff->entrycomp, true) < 0)
+ goto fail;
}
if (git_iterator_current(old_iter, &oitem) < 0 ||
@@ -732,7 +612,7 @@ static int diff_from_iterators(
while (oitem || nitem) {
/* create DELETED records for old items not matched in new */
- if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
+ if (oitem && (!nitem || diff->entrycomp(oitem, nitem) < 0)) {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
goto fail;
@@ -740,7 +620,7 @@ static int diff_from_iterators(
* instead of just generating a DELETE record
*/
if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
- entry_is_prefixed(oitem, new_iter, nitem))
+ entry_is_prefixed(diff, oitem, nitem))
{
/* this entry has become a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
@@ -757,13 +637,12 @@ static int diff_from_iterators(
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
- else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) {
+ else if (nitem && (!oitem || diff->entrycomp(oitem, nitem) > 0)) {
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
/* check if contained in ignored parent directory */
if (git_buf_len(&ignore_prefix) &&
- ITERATOR_PREFIXCMP(*old_iter, 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)) {
@@ -772,7 +651,7 @@ static int diff_from_iterators(
* directories and it is not under an ignored directory.
*/
bool contains_tracked =
- entry_is_prefixed(nitem, old_iter, oitem);
+ entry_is_prefixed(diff, nitem, oitem);
bool recurse_untracked =
(delta_type == GIT_DELTA_UNTRACKED &&
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
@@ -836,7 +715,7 @@ static int diff_from_iterators(
*/
if (delta_type != GIT_DELTA_IGNORED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
- entry_is_prefixed(nitem, old_iter, oitem))
+ entry_is_prefixed(diff, nitem, oitem))
{
/* this entry was a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
@@ -854,7 +733,7 @@ static int diff_from_iterators(
* (or ADDED and DELETED pair if type changed)
*/
else {
- assert(oitem && nitem && entry_compare(oitem, nitem) == 0);
+ assert(oitem && nitem && diff->entrycomp(oitem, nitem) == 0);
if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
git_iterator_advance(old_iter, &oitem) < 0 ||
@@ -863,222 +742,105 @@ static int diff_from_iterators(
}
}
- 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; \
+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ error = 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_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_tree_range(&b, repo, new_tree, pfx, pfx)
+ );
- return diff_from_iterators(repo, opts, a, b, diff);
+ return error;
}
int git_diff_index_to_tree(
+ 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;
-
- assert(repo && diff);
+ int error = 0;
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_index_range(&b, repo, prefix, prefix) < 0)
- goto on_error;
+ assert(diff && repo);
- git__free(prefix);
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- return diff_from_iterators(repo, opts, a, b, diff);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_index_range(&b, index, pfx, pfx)
+ );
-on_error:
- git__free(prefix);
- git_iterator_free(a);
- return -1;
+ return error;
}
int git_diff_workdir_to_index(
+ 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;
- int error;
-
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && diff);
+ int error = 0;
- if ((error = git_iterator_for_index_range(&a, repo, prefix, prefix)) < 0 ||
- (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0)
- goto on_error;
+ assert(diff && repo);
- git__free(prefix);
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- return diff_from_iterators(repo, opts, a, b, diff);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index_range(&a, index, pfx, pfx),
+ git_iterator_for_workdir_range(&b, repo, pfx, pfx)
+ );
-on_error:
- git__free(prefix);
- git_iterator_free(a);
return error;
}
int git_diff_workdir_to_tree(
+ 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;
- int error;
-
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && old_tree && diff);
-
- if ((error = git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix)) < 0 ||
- (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0)
- goto on_error;
-
- git__free(prefix);
-
- return diff_from_iterators(repo, opts, a, b, diff);
-
-on_error:
- git__free(prefix);
- git_iterator_free(a);
- return error;
-}
-
-
-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;
-}
-
-
-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;
- 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, diff_delta__cmp) < 0 ||
- git_pool_init(&onto_pool, 1, 0) < 0)
- return -1;
+ assert(diff && repo);
- 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);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_workdir_range(&b, repo, pfx, pfx)
+ );
return error;
}
diff --git a/src/diff.h b/src/diff.h
index c6a26aee7..1e3be7593 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -28,6 +28,9 @@ enum {
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
+#define GIT_DELTA__TO_DELETE 10
+#define GIT_DELTA__TO_SPLIT 11
+
struct git_diff_list {
git_refcount rc;
git_repository *repo;
@@ -38,6 +41,11 @@ struct git_diff_list {
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(
@@ -45,8 +53,13 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff);
+extern int git_diff_delta__cmp(const void *a, const void *b);
+
extern 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 *);
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index 5f0d13c64..46a9e02bf 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -120,7 +120,7 @@ static int diff_delta_is_binary_by_attr(
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 & KNOWN_BINARY_FLAGS);
else
@@ -1002,7 +1002,7 @@ static int print_compact(
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 &&
@@ -1502,3 +1502,129 @@ notfound:
return GIT_ENOTFOUND;
}
+static int print_to_buffer_cb(
+ void *cb_data,
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len)
+{
+ git_buf *output = cb_data;
+ 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,
+ void *cb_data,
+ git_diff_data_fn print_cb)
+{
+ 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.cb_data = cb_data;
+ pi.buf = &temp;
+
+ error = print_patch_file(&pi, patch->delta, 0);
+
+ for (h = 0; h < patch->hunks_size && !error; ++h) {
+ diff_patch_hunk *hunk = &patch->hunks[h];
+
+ error = print_patch_hunk(&pi, patch->delta,
+ &hunk->range, hunk->header, hunk->header_len);
+
+ for (l = 0; l < hunk->line_count && !error; ++l) {
+ diff_patch_line *line = &patch->lines[hunk->line_start + l];
+
+ error = print_patch_line(
+ &pi, patch->delta, &hunk->range,
+ line->origin, line->ptr, line->len);
+ }
+ }
+
+ 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, &output, print_to_buffer_cb);
+
+ /* 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)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i),
+ void *cbref)
+{
+ int cmp;
+ git_diff_delta *i2h, *w2i;
+ size_t i, j, i_max, j_max;
+ bool icase = false;
+
+ i_max = idx2head ? idx2head->deltas.length : 0;
+ j_max = wd2idx ? wd2idx->deltas.length : 0;
+
+ if (idx2head && wd2idx &&
+ (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
+ {
+ /* Then use the ignore-case sorter... */
+ icase = true;
+
+ /* and assert that both are ignore-case sorted. If this function
+ * ever needs to support merge joining result sets that are not sorted
+ * by the same function, then it will need to be extended to do a spool
+ * and sort on one of the results before merge joining */
+ assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
+ }
+
+ 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 :
+ STRCMP_CASESELECT(icase, i2h->old_file.path, w2i->old_file.path);
+
+ if (cmp < 0) {
+ if (cb(cbref, i2h, NULL))
+ return GIT_EUSER;
+ i++;
+ } else if (cmp > 0) {
+ if (cb(cbref, NULL, w2i))
+ return GIT_EUSER;
+ j++;
+ } else {
+ if (cb(cbref, i2h, w2i))
+ return GIT_EUSER;
+ i++; j++;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/diff_output.h b/src/diff_output.h
index 5fed1d998..f74dd3a71 100644
--- a/src/diff_output.h
+++ b/src/diff_output.h
@@ -83,4 +83,10 @@ typedef struct {
uint32_t diffed : 1;
} diff_delta_context;
+extern int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i),
+ void *cbref);
+
#endif
diff --git a/src/diff_tform.c b/src/diff_tform.c
new file mode 100644
index 000000000..987d4b8e6
--- /dev/null
+++ b/src/diff_tform.c
@@ -0,0 +1,466 @@
+/*
+ * 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.
+ */
+#include "common.h"
+#include "diff.h"
+#include "git2/config.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;
+}
+
+#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;
+ const char *val;
+
+ if (diff->repo != NULL &&
+ git_repository_config__weakptr(&cfg, diff->repo) < 0)
+ return -1;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(*opts));
+ else {
+ memset(opts, 0, sizeof(*opts));
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ 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->status == GIT_DELTA__TO_DELETE) {
+ git__free(delta);
+ continue;
+ }
+
+ if (delta->status == GIT_DELTA__TO_SPLIT) {
+ git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ if (!deleted)
+ return -1;
+
+ 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_FILE_VALID_OID;
+
+ git_vector_insert(&onto, deleted);
+
+ 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_FILE_VALID_OID;
+ }
+
+ git_vector_insert(&onto, delta);
+ }
+
+ /* swap new delta list into place */
+ git_vector_sort(&onto);
+ git_vector_swap(&diff->deltas, &onto);
+ git_vector_free(&onto);
+
+ return 0;
+}
+
+static unsigned int calc_similarity(
+ void *cache, git_diff_file *old_file, git_diff_file *new_file)
+{
+ GIT_UNUSED(cache);
+
+ if (git_oid_cmp(&old_file->oid, &new_file->oid) == 0)
+ return 100;
+
+ /* TODO: insert actual similarity algo here */
+
+ return 0;
+}
+
+#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)
+{
+ unsigned int i, j, similarity;
+ git_diff_delta *from, *to;
+ git_diff_find_options opts;
+ unsigned int tried_targets, num_changes = 0;
+ git_vector matches = GIT_VECTOR_INIT;
+
+ if (normalize_find_opts(diff, &opts, given_opts) < 0)
+ return -1;
+
+ /* first do splits 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;
+
+ /* Right now, this doesn't work right because the similarity
+ * algorithm isn't actually implemented...
+ */
+ similarity = 100;
+ /* calc_similarity(NULL, &from->old_file, from->new_file); */
+
+ if (similarity < opts.break_rewrite_threshold) {
+ from->status = GIT_DELTA__TO_SPLIT;
+ num_changes++;
+ }
+ }
+
+ /* apply splits as needed */
+ if (num_changes > 0 &&
+ apply_splits_and_deletes(
+ diff, diff->deltas.length + num_changes) < 0)
+ return -1;
+ }
+
+ /* next find the most similar delta for each rename / copy candidate */
+
+ if (git_vector_init(&matches, diff->deltas.length, git_diff_delta__cmp) < 0)
+ return -1;
+
+ git_vector_foreach(&diff->deltas, i, from) {
+ tried_targets = 0;
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ if (i == j)
+ continue;
+
+ switch (to->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_RENAMED:
+ case GIT_DELTA_COPIED:
+ break;
+ default:
+ /* only the above status values should be checked */
+ continue;
+ }
+
+ /* skip all but DELETED files unless copy detection is on */
+ if (from->status != GIT_DELTA_DELETED &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ 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;
+
+ /* cap on maximum files we'll examine */
+ if (++tried_targets > opts.target_limit)
+ break;
+
+ /* calculate similarity and see if this pair beats the
+ * similarity score of the current best pair.
+ */
+ similarity = calc_similarity(NULL, &from->old_file, &to->new_file);
+
+ if (to->similarity < similarity) {
+ to->similarity = similarity;
+ if (git_vector_set(NULL, &matches, j, from) < 0)
+ return -1;
+ }
+ }
+ }
+
+ /* next rewrite the diffs with renames / copies */
+
+ num_changes = 0;
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ from = GIT_VECTOR_GET(&matches, j);
+ if (!from) {
+ assert(to->similarity == 0);
+ continue;
+ }
+
+ /* three possible outcomes here:
+ * 1. old DELETED and if over rename threshold,
+ * new becomes RENAMED and old goes away
+ * 2. 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
+ * 3. 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->status = GIT_DELTA__TO_DELETE;
+ num_changes++;
+
+ continue;
+ }
+
+ if (from->status == GIT_DELTA_MODIFIED &&
+ FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ to->similarity > opts.rename_threshold)
+ {
+ similarity = 100;
+ /* calc_similarity(NULL, &from->old_file, from->new_file); */
+
+ if (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_FILE_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));
+ }
+
+ git_vector_free(&matches);
+
+ if (num_changes > 0) {
+ assert(num_changes < diff->deltas.length);
+
+ if (apply_splits_and_deletes(
+ diff, diff->deltas.length - num_changes) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+#undef FLAG_SET
diff --git a/src/errors.c b/src/errors.c
index 942a2f799..ac7fa934d 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -40,52 +40,41 @@ 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;
+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0;
#endif
-
- if (error_class == GITERR_OS) {
- unix_error_code = errno;
- errno = 0;
-
-#ifdef GIT_WIN32
- win32_error_code = GetLastError();
- SetLastError(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) {
- git_buf_PUTS(&buf, ": ");
- git_buf_puts(&buf, strerror(unix_error_code));
- }
-
#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) {
+ if (win32_error_code) {
+ char *lpMsgBuf;
+
+ if (FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, win32_error_code, 0, (LPSTR)&lpMsgBuf, 0, NULL)) {
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))
diff --git a/src/fetch.c b/src/fetch.c
index dc01f6791..81136fc5f 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -8,16 +8,14 @@
#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"
#include "fetch.h"
#include "netops.h"
-#include "pkt.h"
struct filter_payload {
git_remote *remote;
@@ -86,61 +84,6 @@ cleanup:
return error;
}
-/* Wait until we get an ack from the */
-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(git_transport *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;
-}
-
/*
* In this first version, we push all our refs in and start sending
* them out. When we get an ACK we hide that commit and continue
@@ -149,13 +92,7 @@ static int store_common(git_transport *t)
int git_fetch_negotiate(git_remote *remote)
{
git_transport *t = remote->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;
-
+
if (filter_wants(remote) < 0) {
giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
return -1;
@@ -167,298 +104,23 @@ int git_fetch_negotiate(git_remote *remote)
/*
* Now we have everything set up so we can start tell the
- * server what we want and what we have. Call the function if
- * the transport has its own logic. This is transitional and
- * will be removed once this function can support git and http.
- */
- if (t->own_logic)
- return t->negotiate_fetch(t, remote->repo, &remote->refs);
-
- /* No own logic, do our thing */
- if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- return -1;
-
- if (git_fetch_setup_walk(&walk, remote->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.
+ * server what we want and what we have.
*/
- i = 0;
- while ((error = git_revwalk_next(&oid, walk)) == 0) {
- git_pkt_buffer_have(&oid, &data);
- i++;
- if (i % 20 == 0) {
- if (t->cancel.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))
- goto on_error;
-
- if (t->negotiation_step(t, data.ptr, data.size) < 0)
- goto on_error;
-
- git_buf_clear(&data);
- if (t->caps.multi_ack) {
- if (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 {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- goto on_error;
- }
- }
- }
-
- if (t->common.length > 0)
- break;
-
- if (i % 20 == 0 && t->rpc) {
- git_pkt_ack *pkt;
- unsigned int i;
-
- if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- goto on_error;
-
- git_vector_foreach(&t->common, i, pkt) {
- git_pkt_buffer_have(&pkt->oid, &data);
- }
-
- if (git_buf_oom(&data))
- goto on_error;
- }
- }
-
- if (error < 0 && error != GIT_ITEROVER)
- 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 (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- goto on_error;
-
- git_vector_foreach(&t->common, i, pkt) {
- git_pkt_buffer_have(&pkt->oid, &data);
- }
-
- if (git_buf_oom(&data))
- goto on_error;
- }
-
- git_pkt_buffer_done(&data);
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- error = GIT_EUSER;
- goto on_error;
- }
- if (t->negotiation_step(t, 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 != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- return -1;
- }
- } else {
- git_pkt_ack *pkt;
- do {
- if (recv_pkt((git_pkt **)&pkt, buf) < 0)
- return -1;
-
- 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;
+ 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;
- if (t->own_logic)
- return t->download_pack(t, remote->repo, bytes, stats);
-
- return git_fetch__download_pack(t, remote->repo, bytes, stats);
-
-}
-
-static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_off_t *bytes, git_indexer_stats *stats)
-{
- int recvd;
-
- do {
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- return GIT_EUSER;
- }
-
- if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0)
- return -1;
-
- gitno_consume_n(buf, buf->offset);
-
- if ((recvd = gitno_recv(buf)) < 0)
- return -1;
-
- *bytes += recvd;
- } while(recvd > 0);
-
- if (git_indexer_stream_finalize(idx, stats))
- return -1;
-
- return 0;
-}
-
-/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */
-int git_fetch__download_pack(
- git_transport *t,
- git_repository *repo,
- git_off_t *bytes,
- git_indexer_stats *stats)
-{
- git_buf path = GIT_BUF_INIT;
- gitno_buffer *buf = &t->buffer;
- git_indexer_stream *idx = NULL;
- int error = -1;
-
- if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0)
- return -1;
-
- if (git_indexer_stream_new(&idx, git_buf_cstr(&path)) < 0)
- goto on_error;
-
- git_buf_free(&path);
- memset(stats, 0, sizeof(git_indexer_stats));
- *bytes = 0;
-
- /*
- * If the remote doesn't support the side-band, we can feed
- * the data directly to the indexer. Otherwise, we need to
- * check which one belongs there.
- */
- if (!t->caps.side_band && !t->caps.side_band_64k) {
- if (no_sideband(t, idx, buf, bytes, stats) < 0)
- goto on_error;
-
- git_indexer_stream_free(idx);
- return 0;
- }
-
- do {
- git_pkt *pkt;
-
- if (t->cancel.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->cb_data);
- }
- git__free(pkt);
- } else if (pkt->type == GIT_PKT_DATA) {
- git_pkt_data *p = (git_pkt_data *) pkt;
- *bytes += p->len;
- if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0)
- goto on_error;
-
- git__free(pkt);
- } else if (pkt->type == GIT_PKT_FLUSH) {
- /* A flush indicates the end of the packfile */
- git__free(pkt);
- break;
- }
- } while (1);
-
- if (git_indexer_stream_finalize(idx, stats) < 0)
- goto on_error;
-
- git_indexer_stream_free(idx);
- return 0;
-
-on_error:
- git_buf_free(&path);
- git_indexer_stream_free(idx);
- return error;
-}
-
-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 87bb43b07..5b8c20665 100644
--- a/src/fetch.h
+++ b/src/fetch.h
@@ -10,9 +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(git_transport *t, 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..ed47bab48
--- /dev/null
+++ b/src/fetchhead.c
@@ -0,0 +1,126 @@
+/*
+ * 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 "git2/types.h"
+#include "git2/oid.h"
+
+#include "fetchhead.h"
+#include "common.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;
+
+ return strcmp(one->ref_name, two->ref_name);
+}
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **fetchhead_ref_out,
+ git_oid *oid,
+ int is_merge,
+ const char *ref_name,
+ const char *remote_url)
+{
+ git_fetchhead_ref *fetchhead_ref = NULL;
+
+ assert(fetchhead_ref_out && oid && ref_name && remote_url);
+
+ 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;
+ fetchhead_ref->ref_name = git__strdup(ref_name);
+ fetchhead_ref->remote_url = git__strdup(remote_url);
+
+ *fetchhead_ref_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);
+}
+
+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..ec7c1985b
--- /dev/null
+++ b/src/fetchhead.h
@@ -0,0 +1,27 @@
+/*
+ * 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_fetchhead_h__
+#define INCLUDE_fetchhead_h__
+
+#include "vector.h"
+
+typedef struct git_fetchhead_ref {
+ git_oid oid;
+ unsigned int is_merge;
+ char *ref_name;
+ char *remote_url;
+} git_fetchhead_ref;
+
+int git_fetchhead_ref_create(git_fetchhead_ref **fetchhead_ref_out, git_oid *oid, 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 *repository, 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 b9b908c8d..0eb5b458a 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -85,8 +85,8 @@ static int lock_file(git_filebuf *file, int flags)
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);
@@ -108,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);
@@ -149,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;
@@ -186,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;
@@ -221,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;
@@ -291,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;
}
@@ -466,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 377883147..dcaad9bd8 100644
--- a/src/filebuf.h
+++ b/src/filebuf.h
@@ -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;
@@ -82,5 +83,6 @@ 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 6342b1679..7f023bf69 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -14,7 +14,8 @@
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
return git_futils_mkdir(
- file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
+ 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)
@@ -142,10 +143,11 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
}
int git_futils_readbuffer_updated(
- git_buf *buf, const char *path, time_t *mtime, int *updated)
+ git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{
git_file fd;
struct stat st;
+ bool changed = false;
assert(buf && path && *path);
@@ -162,16 +164,25 @@ int git_futils_readbuffer_updated(
}
/*
- * 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;
if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
p_close(fd);
@@ -188,7 +199,7 @@ int git_futils_readbuffer_updated(
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)
@@ -240,6 +251,7 @@ int git_futils_mkdir(
mode_t mode,
uint32_t flags)
{
+ int error = -1;
git_buf make_path = GIT_BUF_INIT;
ssize_t root = 0;
char lastch, *tail;
@@ -287,12 +299,28 @@ int git_futils_mkdir(
*tail = '\0';
/* make directory */
- if (p_mkdir(make_path.ptr, mode) < 0 &&
- (errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
- {
- giterr_set(GITERR_OS, "Failed to make directory '%s'",
- make_path.ptr);
- goto fail;
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ if (errno == EEXIST) {
+ if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0) {
+ if (!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;
+ }
+ }
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'",
+ make_path.ptr);
+ error = GIT_EEXISTS;
+ goto fail;
+ }
+ } else {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'",
+ make_path.ptr);
+ goto fail;
+ }
}
/* chmod if requested */
@@ -314,7 +342,7 @@ int git_futils_mkdir(
fail:
git_buf_free(&make_path);
- return -1;
+ return error;
}
int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
@@ -322,57 +350,145 @@ 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);
}
-static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
+typedef struct {
+ const char *base;
+ uint32_t flags;
+ int error;
+} futils__rmdir_data;
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
{
- git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
+ 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);
- if (git_path_isdir(path->ptr) == true) {
- if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
- return -1;
+ return -1;
+}
- if (p_rmdir(path->ptr) < 0) {
- if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
- return 0;
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
- giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
- return -1;
- }
+ 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 futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
+{
+ struct stat st;
+ futils__rmdir_data *data = opaque;
+
+ 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 (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 (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;
+
+ data->error = p_rmdir(path->ptr);
+
+ 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_EMPTY_HIERARCHY) {
- giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
- return -1;
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ data->error = futils__error_cannot_rmdir(path->ptr, "still present");
+
+ return data->error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
+{
+ int error = p_rmdir(path->ptr);
+
+ GIT_UNUSED(opaque);
+
+ 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(path->ptr, NULL);
+ }
}
- return 0;
+ return error;
}
int git_futils_rmdir_r(
- const char *path, const char *base, git_directory_removal_type removal_type)
+ const char *path, const char *base, uint32_t flags)
{
int error;
git_buf fullpath = GIT_BUF_INIT;
-
- assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
- || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
- || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
+ 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;
- error = _rmdir_recurs_foreach(&removal_type, &fullpath);
+ data.base = base ? base : "";
+ 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;
+ }
git_buf_free(&fullpath);
@@ -660,3 +776,38 @@ int git_futils_cp_r(
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 19f7ffd54..a74f8b758 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -18,7 +18,8 @@
* 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);
/**
@@ -64,6 +65,7 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m
* * 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_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.
@@ -73,7 +75,8 @@ typedef enum {
GIT_MKDIR_PATH = 2,
GIT_MKDIR_CHMOD = 4,
GIT_MKDIR_CHMOD_PATH = 8,
- GIT_MKDIR_SKIP_LAST = 16
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_VERIFY_DIR = 32,
} git_futils_mkdir_flags;
/**
@@ -97,27 +100,40 @@ extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uin
*/
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 base Root for relative path.
- * @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 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, git_directory_removal_type removal_type);
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
/**
* Create and open a temporary file with a `_git2_` suffix.
@@ -266,4 +282,44 @@ extern int git_futils_find_system_file(git_buf *path, const char *filename);
*/
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 28a05235b..f2ab1b85a 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -33,10 +33,6 @@ void git_text_gather_stats(git_text_stats *stats, const git_buf *text)
else if (c == '\n')
stats->lf++;
- else if (c == 0x85)
- /* Unicode CR+LF */
- stats->crlf++;
-
else if (c == 127)
/* DEL */
stats->nonprintable++;
diff --git a/src/global.c b/src/global.c
index 22127faf7..d085089c3 100644
--- a/src/global.c
+++ b/src/global.c
@@ -6,6 +6,7 @@
*/
#include "common.h"
#include "global.h"
+#include "hash.h"
#include "git2/threads.h"
#include "thread-utils.h"
@@ -38,19 +39,39 @@ git_mutex git__mwindow_mutex;
* 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)
@@ -58,6 +79,9 @@ 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_global_st *git__global_state(void)
@@ -88,19 +112,31 @@ 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;
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;
+
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
}
git_global_st *git__global_state(void)
@@ -125,9 +161,10 @@ 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)
diff --git a/src/global.h b/src/global.h
index 0ad41ee63..b117714a8 100644
--- a/src/global.h
+++ b/src/global.h
@@ -8,6 +8,15 @@
#define INCLUDE_global_h__
#include "mwindow.h"
+#include "hash.h"
+
+#if defined(GIT_THREADS) && defined(_MSC_VER)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+#elif defined(GIT_THREADS)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+#else
+# define GIT_MEMORY_BARRIER /* noop */
+#endif
typedef struct {
git_error *last_error;
diff --git a/src/hash.c b/src/hash.c
index 460756913..21db2e129 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -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..127be282f 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -9,21 +9,35 @@
#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"
+#elif defined(PPC_SHA1)
+# include "hash/hash_ppc.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/sha1.c b/src/hash/hash_generic.c
index 8aaedeb8f..30d7a5d1e 100644
--- a/src/sha1/sha1.c
+++ b/src/hash/hash_generic.c
@@ -6,7 +6,8 @@
*/
#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..7c4be7873
--- /dev/null
+++ b/src/hash/hash_generic.h
@@ -0,0 +1,24 @@
+/*
+ * 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_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..3ae49a732
--- /dev/null
+++ b/src/hash/hash_openssl.h
@@ -0,0 +1,45 @@
+/*
+ * 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_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/ppc/sha1.c b/src/hash/hash_ppc.c
index 803b81d0a..de89e9f5e 100644
--- a/src/ppc/sha1.c
+++ b/src/hash/hash_ppc.c
@@ -4,14 +4,17 @@
* 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,
+#include "common.h"
+#include "hash.h"
+
+extern void hash_ppc_core(uint32_t *hash, const unsigned char *p,
unsigned int nblocks);
-int ppc_SHA1_Init(ppc_SHA_CTX *c)
+int git_hash_init(git_hash_ctx *c)
{
c->hash[0] = 0x67452301;
c->hash[1] = 0xEFCDAB89;
@@ -23,7 +26,7 @@ int ppc_SHA1_Init(ppc_SHA_CTX *c)
return 0;
}
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
+int git_hash_update(git_hash_ctx *c, const void *ptr, size_t n)
{
unsigned long nb;
const unsigned char *p = ptr;
@@ -36,12 +39,12 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long 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);
+ hash_ppc_core(c->hash, c->buf.b, 1);
c->cnt = 0;
}
} else {
nb = n >> 6;
- ppc_sha1_core(c->hash, p, nb);
+ hash_ppc_core(c->hash, p, nb);
nb <<= 6;
}
n -= nb;
@@ -50,7 +53,7 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
return 0;
}
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
+int git_hash_final(git_oid *oid, git_hash_ctx *c)
{
unsigned int cnt = c->cnt;
@@ -58,13 +61,14 @@ int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
if (cnt > 56) {
if (cnt < 64)
memset(&c->buf.b[cnt], 0, 64 - cnt);
- ppc_sha1_core(c->hash, c->buf.b, 1);
+ hash_ppc_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);
+ hash_ppc_core(c->hash, c->buf.b, 1);
+ memcpy(oid->id, c->hash, 20);
return 0;
}
+
diff --git a/src/ppc/sha1.h b/src/hash/hash_ppc.h
index aca4e5dda..935f73f7f 100644
--- a/src/ppc/sha1.h
+++ b/src/hash/hash_ppc.h
@@ -4,9 +4,13 @@
* 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_ppc_h__
+#define INCLUDE_hash_ppc_h__
+
#include <stdint.h>
-typedef struct {
+struct git_hash_ctx {
uint32_t hash[5];
uint32_t cnt;
uint64_t len;
@@ -14,13 +18,11 @@ typedef struct {
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 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)
-#define SHA_CTX ppc_SHA_CTX
-#define SHA1_Init ppc_SHA1_Init
-#define SHA1_Update ppc_SHA1_Update
-#define SHA1_Final ppc_SHA1_Final
+#endif /* INCLUDE_hash_generic_h__ */
diff --git a/src/ppc/sha1ppc.S b/src/hash/hash_ppc_core.S
index 1711eef6e..1de816cf5 100644
--- a/src/ppc/sha1ppc.S
+++ b/src/hash/hash_ppc_core.S
@@ -162,8 +162,8 @@ add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30
STEPUP4(fn, (t)+12, (s)+12,); \
STEPUP4(fn, (t)+16, (s)+16, loadk)
- .globl ppc_sha1_core
-ppc_sha1_core:
+ .globl hash_ppc_core
+hash_ppc_core:
stwu %r1,-80(%r1)
stmw %r13,4(%r1)
diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c
new file mode 100644
index 000000000..a89dffa7c
--- /dev/null
+++ b/src/hash/hash_win32.c
@@ -0,0 +1,291 @@
+/*
+ * 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 "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, 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, 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..b91da3e37
--- /dev/null
+++ b/src/hash/hash_win32.h
@@ -0,0 +1,140 @@
+/*
+ * 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_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/index.c b/src/index.c
index f9f3b14cc..128dd18cf 100644
--- a/src/index.c
+++ b/src/index.c
@@ -13,6 +13,8 @@
#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"
@@ -81,6 +83,11 @@ 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);
@@ -90,53 +97,126 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
static int is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file);
+static int index_find(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);
- return strcmp(key, 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;
- return strcasecmp(key, entry->path);
+ ret = strcasecmp(srch_key->path, 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 index_icmp(const void *a, const void *b)
{
+ int diff;
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
- return strcasecmp(entry_a->path, entry_b->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_srch(const void *key, const void *array_member)
+static int reuc_isrch(const void *key, const void *array_member)
{
- const git_index_entry_unmerged *entry = array_member;
+ const git_index_reuc_entry *reuc = array_member;
- return strcmp(key, entry->path);
+ return strcasecmp(key, reuc->path);
}
-static int unmerged_cmp(const void *a, const void *b)
+static int reuc_cmp(const void *a, const void *b)
{
- const git_index_entry_unmerged *info_a = a;
- const git_index_entry_unmerged *info_b = 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))
@@ -165,40 +245,59 @@ static unsigned int index_merge_mode(
static void index_set_ignore_case(git_index *index, bool 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)
return -1;
+ index->entries_cmp_path = index_cmp_path;
index->entries_search = index_srch;
-
- /* Check if index file is stored on disk already */
- if (git_path_exists(index->index_file_path) == true)
- index->on_disk = 1;
+ 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;
+}
+
+int git_index_new(git_index **out)
+{
+ return git_index_open(out, NULL);
}
static void index_free(git_index *index)
{
git_index_entry *e;
+ git_index_reuc_entry *reuc;
unsigned int i;
git_index_clear(index);
@@ -206,10 +305,10 @@ static void index_free(git_index *index)
index_entry_free(e);
}
git_vector_free(&index->entries);
- git_vector_foreach(&index->unmerged, i, e) {
- index_entry_free(e);
+ git_vector_foreach(&index->reuc, i, reuc) {
+ index_entry_reuc_free(reuc);
}
- git_vector_free(&index->unmerged);
+ git_vector_free(&index->reuc);
git__free(index->index_file_path);
git__free(index);
@@ -236,21 +335,27 @@ void git_index_clear(git_index *index)
git__free(e);
}
- for (i = 0; i < index->unmerged.length; ++i) {
- git_index_entry_unmerged *e;
- e = git_vector_get(&index->unmerged, i);
+ for (i = 0; i < index->reuc.length; ++i) {
+ git_index_reuc_entry *e;
+ e = git_vector_get(&index->reuc, i);
git__free(e->path);
git__free(e);
}
git_vector_clear(&index->entries);
- git_vector_clear(&index->unmerged);
- index->last_modified = 0;
+ git_vector_clear(&index->reuc);
+ 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;
@@ -265,11 +370,8 @@ int git_index_set_caps(git_index *index, unsigned int caps)
if (INDEX_OWNER(index) == NULL ||
git_repository_config__weakptr(&cfg, INDEX_OWNER(index)) < 0)
- {
- giterr_set(GITERR_INDEX,
- "Cannot get repository config to set index caps");
- return -1;
- }
+ 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);
@@ -301,11 +403,13 @@ unsigned int git_index_caps(const git_index *index)
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);
@@ -313,33 +417,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)
@@ -353,33 +459,63 @@ 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;
}
+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);
+}
+
unsigned int git_index_entrycount(git_index *index)
{
assert(index);
return (unsigned int)index->entries.length;
}
-unsigned int git_index_entrycount_unmerged(git_index *index)
+git_index_entry *git_index_get_byindex(git_index *index, size_t n)
{
assert(index);
- return (unsigned int)index->unmerged.length;
+ git_vector_sort(&index->entries);
+ return git_vector_get(&index->entries, n);
}
-git_index_entry *git_index_get(git_index *index, size_t n)
+git_index_entry *git_index_get_bypath(git_index *index, const char *path, int stage)
{
+ int pos;
+
+ assert(index);
+
git_vector_sort(&index->entries);
- return git_vector_get(&index->entries, n);
+
+ if((pos = index_find(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;
@@ -393,7 +529,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;
@@ -402,15 +554,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;
@@ -433,10 +586,9 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
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);
@@ -444,6 +596,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;
+
+ reuc->mode[0] = ancestor_mode;
+ git_oid_cpy(&reuc->oid[0], ancestor_oid);
+
+ reuc->mode[1] = our_mode;
+ git_oid_cpy(&reuc->oid[1], our_oid);
+
+ reuc->mode[2] = their_mode;
+ 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;
@@ -486,10 +678,10 @@ 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;;
+ entry->flags |= GIT_IDXENTRY_NAMEMASK;
/* look if an entry with this path already exists */
- if ((position = git_index_find(index, entry->path)) >= 0) {
+ if ((position = index_find(index, entry->path, index_entry_stage(entry))) >= 0) {
existing = (git_index_entry **)&index->entries.contents[position];
/* update filemode to existing values if stat is not trusted */
@@ -510,34 +702,56 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
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;
-int git_index_add(git_index *index, const char *path, int stage)
-{
- return index_add(index, path, stage, 1);
+ 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_append(git_index *index, const char *path, int stage)
+int git_index_add_from_workdir(git_index *index, const char *path)
{
- return index_add(index, path, stage, 0);
+ 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;
}
-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;
@@ -546,7 +760,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;
}
@@ -555,23 +769,17 @@ 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, 0);
-}
-
-int git_index_remove(git_index *index, int position)
+int git_index_remove(git_index *index, const char *path, int stage)
{
+ int position;
int error;
git_index_entry *entry;
git_vector_sort(&index->entries);
+ if ((position = index_find(index, path, stage)) < 0)
+ return position;
+
entry = git_vector_get(&index->entries, position);
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
@@ -584,45 +792,300 @@ int git_index_remove(git_index *index, int position)
return error;
}
+static int index_find(git_index *index, const char *path, int stage)
+{
+ struct entry_srch_key srch_key;
+
+ assert(path);
+
+ srch_key.path = path;
+ srch_key.stage = stage;
+
+ return git_vector_bsearch2(&index->entries, index->entries_search, &srch_key);
+}
+
int git_index_find(git_index *index, const char *path)
{
- return git_vector_bsearch2(&index->entries, index->entries_search, path);
+ int pos;
+
+ assert(index && path);
+
+ if ((pos = git_vector_bsearch2(&index->entries, index->entries_search_path, path)) < 0)
+ return pos;
+
+ /* Since our binary search only looked at path, we may be in the
+ * middle of a list of stages. */
+ while (pos > 0) {
+ git_index_entry *prev = git_vector_get(&index->entries, pos-1);
+
+ if (index->entries_cmp_path(prev->path, path) != 0)
+ break;
+
+ --pos;
+ }
+
+ return pos;
}
unsigned int git_index__prefix_position(git_index *index, const char *path)
{
+ struct entry_srch_key srch_key;
unsigned int pos;
- git_vector_bsearch3(&pos, &index->entries, index->entries_search, path);
+ srch_key.path = path;
+ srch_key.stage = 0;
+
+ git_vector_sort(&index->entries);
+ git_vector_bsearch3(
+ &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 };
+ size_t 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, 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 ((pos = git_index_find(index, path)) < 0)
+ return pos;
+
+ while ((unsigned int)pos < git_index_entrycount(index)) {
+ 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;
+ };
+
+ ++pos;
+ }
+
+ return error;
+}
+
+int git_index_conflict_remove(git_index *index, const char *path)
+{
int pos;
+ git_index_entry *conflict_entry;
+ int error = 0;
+
assert(index && path);
- if (!index->unmerged.length)
+ if ((pos = git_index_find(index, path)) < 0)
+ return pos;
+
+ while ((unsigned int)pos < git_index_entrycount(index)) {
+ 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;
+ }
+
+ error = git_vector_remove(&index->entries, (unsigned int)pos);
+
+ if (error >= 0)
+ index_entry_free(conflict_entry);
+ }
+
+ return error;
+}
+
+static int index_conflicts_match(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(git_index *index)
+{
+ unsigned int 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;
+ int position;
+
+ assert(index && reuc && reuc->path != NULL);
+
+ if ((position = git_index_reuc_find(index, reuc->path)) >= 0)
+ 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(git_index *index, const char *path)
+{
+ return git_vector_bsearch2(&index->reuc, index->reuc_search, path);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_bypath(
+ git_index *index, const char *path)
+{
+ int 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 ((pos = git_index_reuc_find(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(
+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, int 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, (unsigned int)position);
+
+ if (!error)
+ index_entry_reuc_free(reuc);
+
+ return error;
}
static int index_error_invalid(const char *message)
@@ -631,26 +1094,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 */
@@ -667,13 +1131,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;
@@ -684,7 +1148,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;
@@ -692,6 +1156,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;
}
@@ -797,7 +1264,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
@@ -996,6 +1463,69 @@ static int write_entries(git_index *index, git_filebuf *file)
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;
+ unsigned int 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 = 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;
@@ -1018,7 +1548,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);
@@ -1029,22 +1563,20 @@ 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);
}
typedef struct read_tree_data {
git_index *index;
- git_indexer_stats *stats;
+ git_transfer_progress *stats;
} read_tree_data;
static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)
{
- read_tree_data *rtd = data;
+ git_index *index = (git_index *)data;
git_index_entry *entry = NULL;
git_buf path = GIT_BUF_INIT;
- rtd->stats->total++;
-
if (git_tree_entry__is_tree(tentry))
return 0;
@@ -1059,7 +1591,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
entry->path = git_buf_detach(&path);
git_buf_free(&path);
- if (index_insert(rtd->index, entry, 0) < 0) {
+ if (index_insert(index, entry, 0) < 0) {
index_entry_free(entry);
return -1;
}
@@ -1067,16 +1599,65 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
return 0;
}
-int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats)
+int git_index_read_tree(git_index *index, git_tree *tree)
{
- git_indexer_stats dummy_stats;
- read_tree_data rtd = {index, NULL};
+ git_index_clear(index);
- if (!stats) stats = &dummy_stats;
- stats->total = 0;
- rtd.stats = stats;
+ return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index);
+}
- git_index_clear(index);
+git_repository *git_index_owner(const git_index *index)
+{
+ return INDEX_OWNER(index);
+}
- return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd);
+int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec)
+{
+#if 0
+ git_iterator *iter = NULL;
+ const git_index_entry *entry;
+ char *pfx = NULL;
+ git_vector pathspec = GIT_VECTOR_INIT;
+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
+#endif
+
+ if (!git_pathspec_is_interesting(strspec))
+ return git_index_read_tree(index, tree);
+
+ return git_index_read_tree(index, tree);
+
+#if 0
+ /* The following loads the matches into the index, but doesn't
+ * erase obsoleted entries (e.g. you load a blob at "a/b" which
+ * should obsolete a blob at "a/b/c/d" since b is no longer a tree)
+ */
+
+ if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0)
+ return -1;
+
+ pfx = git_pathspec_prefix(strspec);
+
+ if ((error = git_iterator_for_tree_range(
+ &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 ||
+ (error = git_iterator_current(iter, &entry)) < 0)
+ goto cleanup;
+
+ while (entry != NULL) {
+ if (git_pathspec_match_path(&pathspec, entry->path, false, false) &&
+ (error = git_index_add(index, entry)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_advance(iter, &entry)) < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ git_iterator_free(iter);
+ git_pathspec_free(&pathspec);
+ git_pool_clear(&pathpool);
+ git__free(pfx);
+
+ return error;
+#endif
}
diff --git a/src/index.h b/src/index.h
index 7dd23ee60..f0dcd64d5 100644
--- a/src/index.h
+++ b/src/index.h
@@ -22,7 +22,7 @@ struct git_index {
char *index_file_path;
- time_t last_modified;
+ git_futils_filestamp stamp;
git_vector entries;
unsigned int on_disk:1;
@@ -33,13 +33,22 @@ struct git_index {
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 unsigned int git_index__prefix_position(git_index *index, const char *path);
+extern int git_index_entry__cmp(const void *a, const void *b);
+extern int git_index_entry__cmp_icase(const void *a, const void *b);
+
+extern int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec);
+
#endif
diff --git a/src/indexer.c b/src/indexer.c
index 7d4e18d7a..e9f235a72 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -17,7 +17,6 @@
#include "posix.h"
#include "pack.h"
#include "filebuf.h"
-#include "sha1.h"
#define UINT31_MAX (0x7FFFFFFF)
@@ -49,6 +48,8 @@ struct git_indexer_stream {
git_vector deltas;
unsigned int fanout[256];
git_oid hash;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
};
struct delta_info {
@@ -138,7 +139,11 @@ static int cache_cmp(const void *a, const void *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;
@@ -147,6 +152,8 @@ int git_indexer_stream_new(git_indexer_stream **out, const char *prefix)
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)
@@ -242,8 +249,10 @@ 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)
+ if (git_vector_insert(&idx->pack->cache, pentry) < 0) {
+ git__free(pentry);
goto on_error;
+ }
git_oid_cpy(&entry->oid, &oid);
entry->crc = crc32(0L, Z_NULL, 0);
@@ -268,12 +277,17 @@ 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 void do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats)
+{
+ if (!idx->progress_cb) 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;
struct git_pack_header hdr;
@@ -282,7 +296,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
assert(idx && data && stats);
- processed = stats->processed;
+ processed = stats->indexed_objects;
if (git_filebuf_write(&idx->pack_file, data, size) < 0)
return -1;
@@ -324,8 +338,10 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0)
return -1;
- memset(stats, 0, sizeof(git_indexer_stats));
- stats->total = (unsigned int)idx->nr_objects;
+ stats->received_objects = 0;
+ stats->indexed_objects = 0;
+ stats->total_objects = (unsigned int)idx->nr_objects;
+ do_progress_callback(idx, stats);
}
/* Now that we have data in the pack, let's try to parse it */
@@ -361,7 +377,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
if (error < 0)
return error;
- stats->received++;
+ stats->received_objects++;
+ do_progress_callback(idx, stats);
continue;
}
@@ -379,8 +396,9 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
git__free(obj.data);
- stats->processed = (unsigned int)++processed;
- stats->received++;
+ stats->indexed_objects = (unsigned int)++processed;
+ stats->received_objects++;
+ do_progress_callback(idx, stats);
}
return 0;
@@ -412,7 +430,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;
@@ -428,13 +446,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;
@@ -443,7 +462,10 @@ 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;
+
+ 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) {
@@ -455,7 +477,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;
}
@@ -483,12 +505,11 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
}
/* 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_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) {
@@ -563,6 +584,7 @@ on_error:
p_close(idx->pack->mwf.fd);
git_filebuf_cleanup(&idx->index_file);
git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
return -1;
}
@@ -663,7 +685,10 @@ int git_indexer_write(git_indexer *idx)
struct entry *entry;
void *packfile_hash;
git_oid file_hash;
- SHA_CTX ctx;
+ git_hash_ctx ctx;
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
git_vector_sort(&idx->objects);
@@ -693,14 +718,14 @@ int git_indexer_write(git_indexer *idx)
}
/* 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)
+ if ((error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid))) < 0 ||
+ (error = git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ)) < 0)
goto cleanup;
}
- SHA1_Final(idx->hash.id, &ctx);
+
+ if ((error = git_hash_final(&idx->hash, &ctx)) < 0)
+ goto cleanup;
/* Write out the CRC32 values */
git_vector_foreach(&idx->objects, i, entry) {
@@ -778,11 +803,12 @@ cleanup:
if (error < 0)
git_filebuf_cleanup(&idx->file);
git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
return error;
}
-int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
+int git_indexer_run(git_indexer *idx, git_transfer_progress *stats)
{
git_mwindow_file *mwf;
git_off_t off = sizeof(struct git_pack_header);
@@ -797,8 +823,8 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
if (error < 0)
return error;
- stats->total = (unsigned int)idx->nr_objects;
- stats->processed = processed = 0;
+ stats->total_objects = (unsigned int)idx->nr_objects;
+ stats->indexed_objects = processed = 0;
while (processed < idx->nr_objects) {
git_rawobj obj;
@@ -868,7 +894,7 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
git__free(obj.data);
- stats->processed = ++processed;
+ stats->indexed_objects = ++processed;
}
cleanup:
diff --git a/src/iterator.c b/src/iterator.c
index df6da9a87..ee83a4fda 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -330,13 +330,14 @@ typedef struct {
git_iterator base;
git_index *index;
unsigned int current;
+ bool free_index;
} index_iterator;
static int index_iterator__current(
git_iterator *self, const git_index_entry **entry)
{
index_iterator *ii = (index_iterator *)self;
- git_index_entry *ie = git_index_get(ii->index, ii->current);
+ git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
if (ie != NULL &&
ii->base.end != NULL &&
@@ -387,32 +388,47 @@ static int index_iterator__reset(git_iterator *self)
static void index_iterator__free(git_iterator *self)
{
index_iterator *ii = (index_iterator *)self;
- git_index_free(ii->index);
+ if (ii->free_index)
+ git_index_free(ii->index);
ii->index = NULL;
}
int git_iterator_for_index_range(
git_iterator **iter,
- git_repository *repo,
+ git_index *index,
const char *start,
const char *end)
{
- int error;
index_iterator *ii;
ITERATOR_BASE_INIT(ii, index, INDEX);
- if ((error = git_repository_index(&ii->index, repo)) < 0)
- git__free(ii);
- else {
- ii->base.ignore_case = ii->index->ignore_case;
- ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
- *iter = (git_iterator *)ii;
- }
+ ii->index = index;
+ ii->base.ignore_case = ii->index->ignore_case;
+ ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
- return error;
+ *iter = (git_iterator *)ii;
+
+ return 0;
}
+int git_iterator_for_repo_index_range(
+ git_iterator **iter,
+ git_repository *repo,
+ const char *start,
+ const char *end)
+{
+ int error;
+ git_index *index;
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ return error;
+
+ if (!(error = git_iterator_for_index_range(iter, index, start, end)))
+ ((index_iterator *)(*iter))->free_index = true;
+
+ return error;
+}
typedef struct workdir_iterator_frame workdir_iterator_frame;
struct workdir_iterator_frame {
@@ -641,26 +657,23 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
wi->entry.path = ps->path;
- /* skip over .git entry */
+ /* skip over .git entries */
if (STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT "/") == 0 ||
STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT) == 0)
return workdir_iterator__advance((git_iterator *)wi, NULL);
- /* if there is an error processing the entry, treat as ignored */
- wi->is_ignored = 1;
+ 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 */
+ }
/* detect submodules */
if (S_ISDIR(wi->entry.mode)) {
@@ -693,24 +706,21 @@ int git_iterator_for_workdir_range(
assert(iter && repo);
- if ((error = git_repository__ensure_not_bare(repo, "scan working directory")) < 0)
+ if ((error = git_repository__ensure_not_bare(
+ repo, "scan working directory")) < 0)
return error;
ITERATOR_BASE_INIT(wi, workdir, WORKDIR);
-
wi->repo = repo;
- if ((error = git_repository_index(&index, repo)) < 0) {
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0) {
git__free(wi);
return error;
}
- /* Set the ignore_case flag for the workdir iterator to match
- * that of the index. */
+ /* Match ignore_case flag for iterator to that of the index */
wi->base.ignore_case = index->ignore_case;
- git_index_free(index);
-
if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
git_path_to_dir(&wi->path) < 0 ||
git_ignore__for_path(repo, "", &wi->ignores) < 0)
@@ -908,8 +918,18 @@ notfound:
int git_iterator_current_is_ignored(git_iterator *iter)
{
- return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
- ((workdir_iterator *)iter)->is_ignored;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ if (iter->type != GIT_ITERATOR_WORKDIR)
+ return 0;
+
+ if (wi->is_ignored != -1)
+ return wi->is_ignored;
+
+ if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
+ wi->is_ignored = 1;
+
+ return wi->is_ignored;
}
int git_iterator_advance_into_directory(
diff --git a/src/iterator.h b/src/iterator.h
index d7df50137..77ead76cc 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -52,13 +52,23 @@ GIT_INLINE(int) git_iterator_for_tree(
}
extern int git_iterator_for_index_range(
- git_iterator **iter, git_repository *repo,
+ git_iterator **iter, git_index *index,
const char *start, const char *end);
GIT_INLINE(int) git_iterator_for_index(
+ git_iterator **iter, git_index *index)
+{
+ return git_iterator_for_index_range(iter, index, NULL, NULL);
+}
+
+extern int git_iterator_for_repo_index_range(
+ git_iterator **iter, git_repository *repo,
+ const char *start, const char *end);
+
+GIT_INLINE(int) git_iterator_for_repo_index(
git_iterator **iter, git_repository *repo)
{
- return git_iterator_for_index_range(iter, repo, NULL, NULL);
+ return git_iterator_for_repo_index_range(iter, repo, NULL, NULL);
}
extern int git_iterator_for_workdir_range(
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 000000000..135af6a8c
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,48 @@
+/*
+ * 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 "repository.h"
+#include "buffer.h"
+#include "merge.h"
+#include "refs.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/reset.h"
+
+int git_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_mode_path, repo->path_repository, GIT_MERGE_MODE_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;
+}
+
diff --git a/src/merge.h b/src/merge.h
new file mode 100644
index 000000000..2117d9214
--- /dev/null
+++ b/src/merge.h
@@ -0,0 +1,19 @@
+/*
+ * 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_merge_h__
+#define INCLUDE_merge_h__
+
+#include "git2/types.h"
+
+#define GIT_MERGE_MSG_FILE "MERGE_MSG"
+#define GIT_MERGE_MODE_FILE "MERGE_MODE"
+
+#define MERGE_CONFIG_FILE_MODE 0666
+
+int git_merge__cleanup(git_repository *repo);
+
+#endif
diff --git a/src/netops.c b/src/netops.c
index df502e619..422ea63f1 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -14,12 +14,13 @@
#else
# include <ws2tcpip.h>
# ifdef _MSC_VER
-# pragma comment(lib, "ws2_32.lib")
+# pragma comment(lib, "ws2_32")
# endif
#endif
#ifdef GIT_SSL
# include <openssl/ssl.h>
+# include <openssl/err.h>
# include <openssl/x509v3.h>
#endif
@@ -30,7 +31,6 @@
#include "netops.h"
#include "posix.h"
#include "buffer.h"
-#include "transport.h"
#ifdef GIT_WIN32
static void net_set_error(const char *str)
@@ -41,6 +41,8 @@ static void net_set_error(const char *str)
size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
0, error, 0, (LPSTR)&err_str, 0, 0);
+ GIT_UNUSED(size);
+
giterr_set(GITERR_NET, "%s: %s", str, err_str);
LocalFree(err_str);
}
@@ -108,8 +110,8 @@ static int gitno__recv_ssl(gitno_buffer *buf)
int ret;
do {
- ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset);
- } while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ);
+ 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");
@@ -121,11 +123,11 @@ static int gitno__recv_ssl(gitno_buffer *buf)
}
#endif
-int gitno__recv(gitno_buffer *buf)
+static int gitno__recv(gitno_buffer *buf)
{
int ret;
- ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
+ 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;
@@ -136,31 +138,31 @@ int gitno__recv(gitno_buffer *buf)
}
void gitno_buffer_setup_callback(
- git_transport *t,
+ gitno_socket *socket,
gitno_buffer *buf,
char *data,
size_t len,
int (*recv)(gitno_buffer *buf), void *cb_data)
{
- memset(buf, 0x0, sizeof(gitno_buffer));
memset(data, 0x0, len);
buf->data = data;
buf->len = len;
buf->offset = 0;
- buf->fd = t->socket;
+ buf->socket = socket;
buf->recv = recv;
buf->cb_data = cb_data;
}
-void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len)
+void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
{
#ifdef GIT_SSL
- if (t->use_ssl) {
- gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL);
- buf->ssl = &t->ssl;
- } else
+ if (socket->ssl.ctx) {
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
+ return;
+ }
#endif
- gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL);
+
+ 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 */
@@ -186,31 +188,26 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
buf->offset -= cons;
}
-int gitno_ssl_teardown(git_transport *t)
-{
#ifdef GIT_SSL
- int ret;
-#endif
-
- if (!t->use_ssl)
- return 0;
-#ifdef GIT_SSL
+static int gitno_ssl_teardown(gitno_ssl *ssl)
+{
+ int ret;
do {
- ret = SSL_shutdown(t->ssl.ssl);
+ ret = SSL_shutdown(ssl->ssl);
} while (ret == 0);
+
if (ret < 0)
- return ssl_set_error(&t->ssl, ret);
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
- SSL_free(t->ssl.ssl);
- SSL_CTX_free(t->ssl.ctx);
-#endif
- return 0;
+ SSL_free(ssl->ssl);
+ SSL_CTX_free(ssl->ctx);
+ return ret;
}
-
-#ifdef GIT_SSL
/* Match host names according to RFC 2818 rules */
static int match_host(const char *pattern, const char *host)
{
@@ -261,7 +258,7 @@ static int check_host_name(const char *name, const char *host)
return 0;
}
-static int verify_server_cert(git_transport *t, const char *host)
+static int verify_server_cert(gitno_ssl *ssl, const char *host)
{
X509 *cert;
X509_NAME *peer_name;
@@ -274,24 +271,24 @@ static int verify_server_cert(git_transport *t, const char *host)
void *addr;
int i = -1,j;
- if (SSL_get_verify_result(t->ssl.ssl) != X509_V_OK) {
+ 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 (inet_pton(AF_INET, host, &addr4)) {
+ if (p_inet_pton(AF_INET, host, &addr4)) {
type = GEN_IPADD;
addr = &addr4;
} else {
- if(inet_pton(AF_INET6, host, &addr6)) {
+ if(p_inet_pton(AF_INET6, host, &addr6)) {
type = GEN_IPADD;
addr = &addr6;
}
}
- cert = SSL_get_peer_certificate(t->ssl.ssl);
+ cert = SSL_get_peer_certificate(ssl->ssl);
/* Check the alternative names */
alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
@@ -375,7 +372,7 @@ static int verify_server_cert(git_transport *t, const char *host)
on_error:
OPENSSL_free(peer_cn);
- return ssl_set_error(&t->ssl, 0);
+ return ssl_set_error(ssl, 0);
cert_fail:
OPENSSL_free(peer_cn);
@@ -383,51 +380,81 @@ cert_fail:
return -1;
}
-static int ssl_setup(git_transport *t, const char *host)
+static int ssl_setup(gitno_socket *socket, const char *host, int flags)
{
int ret;
SSL_library_init();
SSL_load_error_strings();
- t->ssl.ctx = SSL_CTX_new(SSLv23_method());
- if (t->ssl.ctx == NULL)
- return ssl_set_error(&t->ssl, 0);
+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
+ if (socket->ssl.ctx == NULL)
+ return ssl_set_error(&socket->ssl, 0);
- SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY);
- SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_NONE, NULL);
- if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx))
- return ssl_set_error(&t->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);
- t->ssl.ssl = SSL_new(t->ssl.ctx);
- if (t->ssl.ssl == NULL)
- return ssl_set_error(&t->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(t->ssl.ssl, t->socket)) == 0)
- return ssl_set_error(&t->ssl, ret);
+ if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
+ return ssl_set_error(&socket->ssl, ret);
- if ((ret = SSL_connect(t->ssl.ssl)) <= 0)
- return ssl_set_error(&t->ssl, ret);
+ if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
+ return ssl_set_error(&socket->ssl, ret);
- if (t->check_cert && verify_server_cert(t, host) < 0)
+ if ((GITNO_CONNECT_SSL_NO_CHECK_CERT & flags) || verify_server_cert(&socket->ssl, host) < 0)
return -1;
return 0;
}
-#else
-static int ssl_setup(git_transport *t, const char *host)
+#endif
+
+static int gitno__close(GIT_SOCKET s)
{
- GIT_UNUSED(t);
- GIT_UNUSED(host);
+#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(git_transport *t, const char *host, const char *port)
+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_socktype = SOCK_STREAM;
@@ -451,7 +478,7 @@ int gitno_connect(git_transport *t, 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;
}
@@ -461,46 +488,56 @@ int gitno_connect(git_transport *t, const char *host, const char *port)
return -1;
}
- t->socket = s;
+ s_out->socket = s;
p_freeaddrinfo(info);
- if (t->use_ssl && ssl_setup(t, host) < 0)
+#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
return 0;
}
#ifdef GIT_SSL
-static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len)
+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) {
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(git_transport *t, const char *msg, size_t len, int flags)
+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
{
int ret;
size_t off = 0;
#ifdef GIT_SSL
- if (t->use_ssl)
- return send_ssl(&t->ssl, msg, len);
+ if (socket->ssl.ctx)
+ return gitno_send_ssl(&socket->ssl, msg, len, flags);
#endif
while (off < len) {
errno = 0;
- ret = p_send(t->socket, msg + off, len - off, flags);
+ ret = p_send(socket->socket, msg + off, len - off, flags);
if (ret < 0) {
net_set_error("Error sending data");
return -1;
@@ -512,19 +549,17 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
return (int)off;
}
-
-#ifdef GIT_WIN32
-int gitno_close(GIT_SOCKET s)
+int gitno_close(gitno_socket *s)
{
- return closesocket(s) == SOCKET_ERROR ? -1 : 0;
-}
-#else
-int gitno_close(GIT_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;
@@ -534,10 +569,10 @@ 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)
diff --git a/src/netops.h b/src/netops.h
index 7c53fd0dc..efbbc65a4 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -10,31 +10,60 @@
#include "posix.h"
#include "common.h"
+#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;
-#ifdef GIT_SSL
- struct gitno_ssl *ssl;
-#endif
- int (*recv)(gitno_buffer *buffer);
+ gitno_socket *socket;
+ int (*recv)(struct gitno_buffer *buffer);
void *cb_data;
};
-void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len);
-void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
+typedef struct gitno_buffer gitno_buffer;
+
+/* Flags to gitno_connect */
+enum {
+ /* Attempt to create an SSL connection. */
+ GITNO_CONNECT_SSL = 1,
+
+ /* Valid only when GITNO_CONNECT_SSL is also specified.
+ * Indicates that the server certificate should not be validated. */
+ GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
+};
+
+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);
-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_transport *t, const char *host, const char *port);
-int gitno_send(git_transport *t, const char *msg, size_t len, int flags);
-int gitno_close(GIT_SOCKET s);
-int gitno_ssl_teardown(git_transport *t);
-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);
diff --git a/src/odb.c b/src/odb.c
index 7c21598f0..9c602d1d2 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -23,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;
@@ -30,6 +32,8 @@ typedef struct
int is_alternate;
} backend_internal;
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+
static int 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);
@@ -117,23 +121,27 @@ 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;
if (!git_object_typeisloose(type)) {
giterr_set(GITERR_INVALID, "Invalid object type for hash");
return -1;
}
- hdr_len = format_object_header(hdr, sizeof(hdr), size, type);
+ if ((error = git_hash_ctx_init(&ctx)) < 0)
+ return -1;
- ctx = git_hash_new_ctx();
- GITERR_CHECK_ALLOC(ctx);
+ hdr_len = format_object_header(hdr, sizeof(hdr), size, type);
- git_hash_update(ctx, hdr, hdr_len);
+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)
+ goto done;
while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
- git_hash_update(ctx, buffer, read_len);
+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)
+ goto done;
+
size -= read_len;
}
@@ -141,15 +149,18 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
* 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) {
- git_hash_free_ctx(ctx);
giterr_set(GITERR_OS, "Error reading file for hashing");
+ error = -1;
+
+ goto done;
return -1;
}
- git_hash_final(out, ctx);
- git_hash_free_ctx(ctx);
+ error = git_hash_final(out, &ctx);
- return 0;
+done:
+ git_hash_ctx_cleanup(&ctx);
+ return error;
}
int git_odb__hashfd_filtered(
@@ -388,7 +399,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;
@@ -402,10 +413,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;
@@ -413,6 +424,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;
@@ -433,14 +449,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;
}
@@ -461,8 +481,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;
@@ -766,6 +785,31 @@ 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)
+{
+ unsigned int 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);
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 964e82afb..9f7a6ee1f 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -26,6 +26,11 @@ struct pack_backend {
char *pack_folder;
};
+struct pack_writepack {
+ struct git_odb_writepack parent;
+ git_indexer_stream *indexer_stream;
+};
+
/**
* The wonderful tale of a Packed Object lookup query
* ===================================================
@@ -475,6 +480,67 @@ static int pack_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *o
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;
@@ -553,6 +619,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
backend->parent.read_header = NULL;
backend->parent.exists = &pack_backend__exists;
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/pack-objects.c b/src/pack-objects.c
index eb76e05a2..a146dc048 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -73,16 +73,16 @@ 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, default) \
- ret = git_config_get_int64((int64_t *)&dst, config, key); \
- if (ret == GIT_ENOTFOUND) \
- dst = default; \
- else if (ret < 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);
@@ -103,7 +103,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
*out = NULL;
- pb = git__calloc(sizeof(*pb), 1);
+ pb = git__calloc(1, sizeof(*pb));
GITERR_CHECK_ALLOC(pb);
pb->object_ix = git_oidmap_alloc();
@@ -113,9 +113,8 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
pb->repo = repo;
pb->nr_threads = 1; /* do not spawn any thread by default */
- pb->ctx = git_hash_new_ctx();
- if (!pb->ctx ||
+ if (git_hash_ctx_init(&pb->ctx) < 0 ||
git_repository_odb(&pb->odb, repo) < 0 ||
packbuilder_config(pb) < 0)
goto on_error;
@@ -297,14 +296,13 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)
if (git_buf_put(buf, (char *)hdr, hdr_len) < 0)
goto on_error;
- git_hash_update(pb->ctx, hdr, hdr_len);
+ 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)
+ 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;
-
- git_hash_update(pb->ctx, po->delta->id.id, GIT_OID_RAWSZ);
}
/* Write data */
@@ -319,11 +317,10 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)
size = zbuf.size;
}
- if (git_buf_put(buf, data, size) < 0)
+ if (git_buf_put(buf, data, size) < 0 ||
+ git_hash_update(&pb->ctx, data, size) < 0)
goto on_error;
- git_hash_update(pb->ctx, data, size);
-
if (po->delta_data)
git__free(po->delta_data);
@@ -573,7 +570,8 @@ static int write_pack(git_packbuilder *pb,
if (cb(&ph, sizeof(ph), data) < 0)
goto on_error;
- git_hash_update(pb->ctx, &ph, sizeof(ph));
+ if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0)
+ goto on_error;
pb->nr_remaining = pb->nr_objects;
do {
@@ -592,7 +590,9 @@ static int write_pack(git_packbuilder *pb,
git__free(write_order);
git_buf_free(&buf);
- git_hash_final(&pb->pack_oid, pb->ctx);
+
+ if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0)
+ goto on_error;
return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data);
@@ -604,8 +604,8 @@ on_error:
static int send_pack_file(void *buf, size_t size, void *data)
{
- git_transport *t = (git_transport *)data;
- return gitno_send(t, buf, size, 0);
+ gitno_socket *s = (gitno_socket *)data;
+ return gitno_send(s, buf, size, 0);
}
static int write_pack_buf(void *buf, size_t size, void *data)
@@ -1231,10 +1231,16 @@ static int prepare_pack(git_packbuilder *pb)
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
-int git_packbuilder_send(git_packbuilder *pb, git_transport *t)
+int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s)
{
PREPARE_PACK;
- return write_pack(pb, &send_pack_file, t);
+ return write_pack(pb, &send_pack_file, s);
+}
+
+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)
@@ -1286,6 +1292,16 @@ int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
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)
@@ -1302,14 +1318,13 @@ void git_packbuilder_free(git_packbuilder *pb)
if (pb->odb)
git_odb_free(pb->odb);
- if (pb->ctx)
- git_hash_free_ctx(pb->ctx);
-
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
index 0d4854d0d..e34cc2754 100644
--- a/src/pack-objects.h
+++ b/src/pack-objects.h
@@ -13,6 +13,7 @@
#include "buffer.h"
#include "hash.h"
#include "oidmap.h"
+#include "netops.h"
#include "git2/oid.h"
@@ -51,7 +52,7 @@ struct git_packbuilder {
git_repository *repo; /* associated repository */
git_odb *odb; /* associated object database */
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
uint32_t nr_objects,
nr_alloc,
@@ -70,18 +71,18 @@ struct git_packbuilder {
git_cond progress_cond;
/* configs */
- unsigned long delta_cache_size;
- unsigned long max_delta_cache_size;
- unsigned long cache_max_small_delta_size;
- unsigned long big_file_threshold;
- unsigned long window_memory_limit;
+ 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_send(git_packbuilder *pb, git_transport *t);
+int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s);
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
#endif /* INCLUDE_pack_objects_h__ */
diff --git a/src/path.c b/src/path.c
index 09556bd3f..98351bec3 100644
--- a/src/path.c
+++ b/src/path.c
@@ -382,9 +382,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++;
diff --git a/src/pathspec.c b/src/pathspec.c
new file mode 100644
index 000000000..fc6547afe
--- /dev/null
+++ b/src/pathspec.c
@@ -0,0 +1,151 @@
+/*
+ * 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 "pathspec.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_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_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_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;
+}
+
+/* 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_interesting(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)
+{
+ unsigned int 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 (!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 = 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)
+ 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..31a1cdad9
--- /dev/null
+++ b/src/pathspec.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011-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_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_interesting(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 */
+extern bool git_pathspec_match_path(
+ git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold);
+
+#endif
diff --git a/src/pkt.h b/src/pkt.h
deleted file mode 100644
index 0fdb5c7cd..000000000
--- a/src/pkt.h
+++ /dev/null
@@ -1,91 +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,
- GIT_PKT_DATA,
- GIT_PKT_PROGRESS,
-};
-
-/* 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;
- 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/posix.c b/src/posix.c
index 985221dd5..d207ce1a0 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -205,3 +205,5 @@ int p_write(git_file fd, const void *buf, size_t cnt)
}
return 0;
}
+
+
diff --git a/src/protocol.c b/src/protocol.c
deleted file mode 100644
index affad5173..000000000
--- a/src/protocol.c
+++ /dev/null
@@ -1,110 +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_transport *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;
-
- 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_protocol_detect_caps(git_pkt_ref *pkt, git_transport_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;
- }
-
-
- /* We don't know this capability, so skip it */
- ptr = strchr(ptr, ' ');
- }
-
- return 0;
-}
diff --git a/src/protocol.h b/src/protocol.h
deleted file mode 100644
index a990938e5..000000000
--- a/src/protocol.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.
- */
-#ifndef INCLUDE_protocol_h__
-#define INCLUDE_protocol_h__
-
-#include "transport.h"
-#include "buffer.h"
-#include "pkt.h"
-
-int git_protocol_store_refs(git_transport *t, int flushes);
-int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps);
-
-#define GIT_SIDE_BAND_DATA 1
-#define GIT_SIDE_BAND_PROGRESS 2
-#define GIT_SIDE_BAND_ERROR 3
-
-#endif
diff --git a/src/reflog.c b/src/reflog.c
index a1ea7a27d..7b07c6a9f 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -166,6 +166,9 @@ void git_reflog_free(git_reflog *reflog)
unsigned int i;
git_reflog_entry *entry;
+ if (reflog == NULL)
+ return;
+
for (i=0; i < reflog->entries.length; i++) {
entry = git_vector_get(&reflog->entries, i);
@@ -185,7 +188,10 @@ static int retrieve_reflog_path(git_buf *path, git_reference *ref)
static int create_new_reflog_file(const char *filepath)
{
- int fd;
+ 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,
@@ -285,8 +291,8 @@ success:
int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
const git_signature *committer, const char *msg)
{
- int count;
git_reflog_entry *entry;
+ const git_reflog_entry *previous;
const char *newline;
assert(reflog && new_oid && committer);
@@ -313,16 +319,12 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
}
}
- count = git_reflog_entrycount(reflog);
+ previous = git_reflog_entry_byindex(reflog, 0);
- if (count == 0)
+ if (previous == NULL)
git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
- else {
- const git_reflog_entry *previous;
-
- previous = git_reflog_entry_byindex(reflog, count -1);
+ else
git_oid_cpy(&entry->oid_old, &previous->oid_cur);
- }
git_oid_cpy(&entry->oid_cur, new_oid);
@@ -372,7 +374,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name)
goto cleanup;
if (git_path_isdir(git_buf_cstr(&new_path)) &&
- (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
+ (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0))
goto cleanup;
if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0)
@@ -411,8 +413,16 @@ unsigned int git_reflog_entrycount(git_reflog *reflog)
const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
{
+ int pos;
+
assert(reflog);
- return git_vector_get(&reflog->entries, idx);
+
+ pos = git_reflog_entrycount(reflog) - (idx + 1);
+
+ if (pos < 0)
+ return NULL;
+
+ return git_vector_get(&reflog->entries, pos);
}
const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry)
@@ -441,7 +451,7 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry)
int git_reflog_drop(
git_reflog *reflog,
- unsigned int idx,
+ size_t idx,
int rewrite_previous_entry)
{
unsigned int entrycount;
@@ -451,31 +461,32 @@ int git_reflog_drop(
entrycount = git_reflog_entrycount(reflog);
- if (idx >= entrycount)
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+
+ if (entry == NULL)
return GIT_ENOTFOUND;
- entry = git_vector_get(&reflog->entries, idx);
reflog_entry_free(entry);
- if (git_vector_remove(&reflog->entries, idx) < 0)
+ if (git_vector_remove(&reflog->entries, entrycount - (idx + 1)) < 0)
return -1;
if (!rewrite_previous_entry)
return 0;
- /* No need to rewrite anything when removing the first entry */
+ /* No need to rewrite anything when removing the most recent entry */
if (idx == 0)
return 0;
- /* There are no more entries in the log */
+ /* 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 last entry has just been removed... */
+ /* If the oldest entry has just been removed... */
if (idx == entrycount - 1) {
- /* ...clear the oid_old member of the "new" last entry */
+ /* ...clear the oid_old member of the "new" oldest entry */
if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
return -1;
diff --git a/src/refs.c b/src/refs.c
index 833f2fcc8..97c97563e 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -123,7 +123,8 @@ static int reference_read(
if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
return -1;
- result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);
+ result = git_futils_readbuffer_updated(
+ file_content, path.ptr, mtime, NULL, updated);
git_buf_free(&path);
return result;
@@ -273,18 +274,15 @@ static int loose_write(git_reference *ref)
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), NULL,
- GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0) {
- git_buf_free(&ref_path);
+ if (git_futils_rmdir_r(ref->name, ref->owner->path_repository,
+ GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
return -1;
- }
if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
git_buf_free(&ref_path);
@@ -1948,10 +1946,10 @@ int git_reference_peel(
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
+ else
error = git_object_peel(peeled, target, target_type);
cleanup:
diff --git a/src/refs.h b/src/refs.h
index 54359f07b..a58bebd0d 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -28,10 +28,22 @@
#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 {
diff --git a/src/refspec.c b/src/refspec.c
index b1790b32c..4d9915b7a 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -225,3 +225,21 @@ int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *n
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 6e0596a55..e27314cc3 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -51,4 +51,14 @@ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *n
*/
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 e05ea059f..4a4d160eb 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -14,7 +14,8 @@
#include "remote.h"
#include "fetch.h"
#include "refs.h"
-#include "pkt.h"
+#include "refspec.h"
+#include "fetchhead.h"
#include <regex.h>
@@ -69,6 +70,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
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;
@@ -117,6 +119,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
memset(remote, 0x0, sizeof(git_remote));
remote->check_cert = 1;
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
@@ -132,6 +135,12 @@ 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);
@@ -144,8 +153,10 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
}
error = git_config_get_string(&val, config, git_buf_cstr(&buf));
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ val = NULL;
error = 0;
+ }
if (error < 0) {
error = -1;
@@ -201,12 +212,75 @@ cleanup:
return error;
}
+static int ensure_remote_name_is_valid(const char *name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!name || *name == '\0')
+ goto cleanup;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
+
+ git_buf_free(&buf);
+ git_refspec__free(&refspec);
+
+cleanup:
+ if (error)
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name);
+
+ 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_DIR_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;
const char *tagopt = NULL;
- git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(remote);
+
+ if (ensure_remote_name_is_valid(remote->name) < 0)
+ return -1;
if (git_repository_config__weakptr(&config, remote->repo) < 0)
return -1;
@@ -232,6 +306,7 @@ int git_remote_save(const git_remote *remote)
int error = git_config_delete(config, git_buf_cstr(&buf));
if (error == GIT_ENOTFOUND) {
error = 0;
+ giterr_clear();
}
if (error < 0) {
git_buf_free(&buf);
@@ -239,33 +314,19 @@ int git_remote_save(const git_remote *remote)
}
}
- 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);
- if (remote->fetch.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- 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_DIR_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);
- if (remote->push.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- 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->push,
+ GIT_DIR_PUSH) < 0)
goto on_error;
- }
/*
* What action to take depends on the old and new values. This
@@ -300,13 +361,11 @@ int git_remote_save(const git_remote *remote)
}
git_buf_free(&buf);
- git_buf_free(&value);
return 0;
on_error:
git_buf_free(&buf);
- git_buf_free(&value);
return -1;
}
@@ -417,23 +476,30 @@ int git_remote_connect(git_remote *remote, int direction)
{
git_transport *t;
const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
assert(remote);
+ t = remote->transport;
+
url = git_remote__urlfordirection(remote, direction);
if (url == NULL )
return -1;
- if (git_transport_new(&t, url) < 0)
+ /* A transport could have been supplied in advance with
+ * git_remote_set_transport */
+ if (!t && git_transport_new(&t, url) < 0)
return -1;
- t->progress_cb = remote->callbacks.progress;
- t->cb_data = remote->callbacks.data;
+ if (t->set_callbacks &&
+ t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0)
+ goto on_error;
+
+ if (!remote->check_cert)
+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
- t->check_cert = remote->check_cert;
- if (t->connect(t, direction) < 0) {
+ if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0)
goto on_error;
- }
remote->transport = t;
@@ -446,42 +512,151 @@ on_error:
int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
- git_vector *refs = &remote->transport->refs;
- unsigned int i;
- git_pkt *p = NULL;
-
assert(remote);
- if (!remote->transport || !remote->transport->connected) {
+ if (!remote->transport) {
giterr_set(GITERR_NET, "The remote is not connected");
return -1;
}
- git_vector_foreach(refs, i, p) {
- git_pkt_ref *pkt = NULL;
+ return remote->transport->ls(remote->transport, list_cb, payload);
+}
- if (p->type != GIT_PKT_REF)
- continue;
+int git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ int error;
- pkt = (git_pkt_ref *)p;
+ assert(remote);
- if (list_cb(&pkt->head, payload))
- return GIT_EUSER;
+ if ((error = git_fetch_negotiate(remote)) < 0)
+ return error;
+
+ return git_fetch_download_pack(remote, progress_cb, progress_payload);
+}
+
+static int update_tips_callback(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *)payload;
+ git_vector_insert(refs, head);
+
+ return 0;
+}
+
+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_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
{
- int error;
+ git_reference *resolved_ref = NULL;
+ git_reference *tracking_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
+ int error = 0;
- assert(remote && bytes && stats);
+ assert(out && remote && ref);
- if ((error = git_fetch_negotiate(remote)) < 0)
- return error;
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
+ (!git_reference_is_branch(resolved_ref)) ||
+ (error = git_branch_tracking(&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);
+
+ 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 git_fetch_download_pack(remote, bytes, stats);
+ return error;
}
int git_remote_update_tips(git_remote *remote)
@@ -490,48 +665,48 @@ int git_remote_update_tips(git_remote *remote)
unsigned int i = 0;
git_buf refname = GIT_BUF_INIT;
git_oid old;
- git_pkt *pkt;
git_odb *odb;
- git_vector *refs;
git_remote_head *head;
git_reference *ref;
struct git_refspec *spec;
git_refspec tagspec;
+ git_vector refs, update_heads;
assert(remote);
- refs = &remote->transport->refs;
spec = &remote->fetch;
-
- if (refs->length == 0)
- return 0;
-
+
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
return -1;
if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
return -1;
- /* HEAD is only allowed to be the first in the list */
- pkt = refs->contents[0];
- head = &((git_pkt_ref *)pkt)->head;
- 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;
+ /* 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;
- i = 1;
- git_reference_free(ref);
+ if (remote->transport->ls(remote->transport, 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_oid(&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) {
- git_pkt *pkt = refs->contents[i];
+ for (; i < refs.length; ++i) {
+ head = (git_remote_head *)refs.contents[i];
autotag = 0;
- if (pkt->type == GIT_PKT_REF)
- head = &((git_pkt_ref *)pkt)->head;
- else
- continue;
-
/* Ignore malformed ref names (which also saves us from tag^{} */
if (!git_reference_is_valid_name(head->name))
continue;
@@ -557,6 +732,9 @@ int git_remote_update_tips(git_remote *remote)
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);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
@@ -580,11 +758,19 @@ int git_remote_update_tips(git_remote *remote)
}
}
+ 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;
@@ -593,21 +779,31 @@ on_error:
int git_remote_connected(git_remote *remote)
{
+ int connected;
+
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. */
+ remote->transport->is_connected(remote->transport, &connected);
+
+ return connected;
}
void git_remote_stop(git_remote *remote)
{
- git_atomic_set(&remote->transport->cancel, 1);
+ if (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)
@@ -736,10 +932,39 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
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.data);
+}
+
+void git_remote_set_cred_acquire_cb(
+ git_remote *remote,
+ git_cred_acquire_cb cred_acquire_cb)
+{
+ assert(remote);
+
+ remote->cred_acquire_cb = cred_acquire_cb;
+}
+
+int git_remote_set_transport(git_remote *remote, git_transport *transport)
+{
+ assert(remote && transport);
+
if (remote->transport) {
- remote->transport->progress_cb = remote->callbacks.progress;
- remote->transport->cb_data = remote->callbacks.data;
+ giterr_set(GITERR_NET, "A transport is already bound to this remote");
+ return -1;
}
+
+ remote->transport = transport;
+ return 0;
+}
+
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
}
int git_remote_autotag(git_remote *remote)
@@ -751,3 +976,298 @@ void git_remote_set_autotag(git_remote *remote, int value)
{
remote->download_tags = value;
}
+
+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;
+}
+
+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;
+
+ 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(reference, git_buf_cstr(&new_name), 0);
+
+cleanup:
+ git_reference_free(reference);
+ 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 == '\0') {
+ 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_DIR_FETCH);
+
+cleanup:
+ git_buf_free(&serialized);
+ git_buf_free(&dst_prefix);
+ return error;
+}
+
+int git_remote_rename(
+ git_remote *remote,
+ const char *new_name,
+ int (*callback)(const char *problematic_refspec, void *payload),
+ void *payload)
+{
+ int error;
+
+ assert(remote && new_name);
+
+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_name_is_valid(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);
+
+ 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;
+}
diff --git a/src/remote.h b/src/remote.h
index 05073db8c..840c9a905 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -8,9 +8,9 @@
#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"
@@ -22,12 +22,15 @@ struct git_remote {
git_vector refs;
struct git_refspec fetch;
struct git_refspec push;
+ git_cred_acquire_cb cred_acquire_cb;
git_transport *transport;
git_repository *repo;
git_remote_callbacks callbacks;
+ git_transfer_progress stats;
unsigned int need_pack:1,
download_tags:2, /* There are four possible values */
- check_cert:1;
+ check_cert:1,
+ update_fetchhead:1;
};
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
diff --git a/src/repository.c b/src/repository.c
index 43e0eda8f..f82dc108b 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -20,6 +20,7 @@
#include "filter.h"
#include "odb.h"
#include "remote.h"
+#include "merge.h"
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
@@ -449,37 +450,46 @@ static int load_config(
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, GIT_CONFIG_LEVEL_LOCAL, 0) < 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, GIT_CONFIG_LEVEL_GLOBAL, 0) < 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 (xdg_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0) < 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) {
- if (git_config_add_file_ondisk(cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0) < 0)
- 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;
@@ -488,7 +498,7 @@ 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)
@@ -749,6 +759,23 @@ static bool are_symlinks_supported(const char *wd_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;
+ }
+
+ return 0;
+}
+
static int repo_init_config(
const char *repo_dir,
const char *work_dir,
@@ -765,6 +792,12 @@ static int repo_init_config(
if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
return -1;
+ 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;
@@ -1206,9 +1239,19 @@ int git_repository_head_detached(git_repository *repo)
int git_repository_head(git_reference **head_out, git_repository *repo)
{
+ git_reference *head;
int error;
- error = git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1);
+ 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_target(head), -1);
+ git_reference_free(head);
return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error;
}
@@ -1230,36 +1273,47 @@ int git_repository_head_orphan(git_repository *repo)
return 0;
}
+static int at_least_one_cb(const char *refname, void *payload)
+{
+ GIT_UNUSED(refname);
+ GIT_UNUSED(payload);
+
+ return GIT_EUSER;
+}
+
+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 (error == GIT_EUSER)
+ return 0;
+
+ return error == 0 ? 1 : error;
+}
+
int git_repository_is_empty(git_repository *repo)
{
- git_reference *head = NULL, *branch = NULL;
+ git_reference *head = NULL;
int error;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
return -1;
- if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
- git_reference_free(head);
- return 0;
- }
+ if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC))
+ goto cleanup;
- if (strcmp(git_reference_target(head), GIT_REFS_HEADS_DIR "master") != 0) {
- git_reference_free(head);
- return 0;
- }
+ if (!(error = strcmp(
+ git_reference_target(head),
+ GIT_REFS_HEADS_DIR "master") == 0))
+ goto cleanup;
- error = git_reference_resolve(&branch, head);
+ error = repo_contains_no_reference(repo);
+cleanup:
git_reference_free(head);
- git_reference_free(branch);
-
- if (error == GIT_ENOTFOUND)
- return 1;
-
- if (error < 0)
- return -1;
-
- return 0;
+ return error < 0 ? -1 : error;
}
const char *git_repository_path(git_repository *repo)
@@ -1348,15 +1402,13 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo)
return 0;
}
-#define MERGE_MSG_FILE "MERGE_MSG"
-
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, MERGE_MSG_FILE) < 0)
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
@@ -1382,7 +1434,7 @@ int git_repository_message_remove(git_repository *repo)
git_buf path = GIT_BUF_INIT;
int error;
- if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0)
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
error = p_unlink(git_buf_cstr(&path));
@@ -1541,3 +1593,40 @@ cleanup:
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/reset.c b/src/reset.c
index 560ae17b1..8f470b26a 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -8,8 +8,10 @@
#include "common.h"
#include "commit.h"
#include "tag.h"
+#include "merge.h"
#include "git2/reset.h"
#include "git2/checkout.h"
+#include "git2/merge.h"
#define ERROR_MSG "Cannot perform reset"
@@ -88,6 +90,12 @@ int git_reset(
goto cleanup;
}
+ if (reset_type == GIT_RESET_SOFT && (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE)) {
+ giterr_set(GITERR_OBJECT, "%s (soft) while in the middle of a merge.", ERROR_MSG);
+ error = GIT_EUNMERGED;
+ goto cleanup;
+ }
+
//TODO: Check for unmerged entries
if (update_head(repo, commit) < 0)
@@ -108,7 +116,7 @@ int git_reset(
goto cleanup;
}
- if (git_index_read_tree(index, tree, NULL) < 0) {
+ if (git_index_read_tree(index, tree) < 0) {
giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG);
goto cleanup;
}
@@ -118,18 +126,20 @@ int git_reset(
goto cleanup;
}
+ if ((error = git_merge__cleanup(repo)) < 0) {
+ giterr_set(GITERR_INDEX, "%s - Failed to clean up merge data.", ERROR_MSG);
+ goto cleanup;
+ }
+
if (reset_type == GIT_RESET_MIXED) {
error = 0;
goto cleanup;
}
memset(&opts, 0, sizeof(opts));
- opts.checkout_strategy =
- GIT_CHECKOUT_CREATE_MISSING
- | GIT_CHECKOUT_OVERWRITE_MODIFIED
- | GIT_CHECKOUT_REMOVE_UNTRACKED;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
- if (git_checkout_index(repo, &opts, NULL) < 0) {
+ if (git_checkout_index(repo, NULL, &opts) < 0) {
giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG);
goto cleanup;
}
diff --git a/src/revparse.c b/src/revparse.c
index 83eea7d3f..6b49402c4 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -201,7 +201,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out,
numentries = git_reflog_entrycount(reflog);
- for (i = numentries - 1; i >= 0; i--) {
+ for (i = 0; i < numentries; i++) {
entry = git_reflog_entry_byindex(reflog, i);
msg = git_reflog_entry_msg(entry);
@@ -263,15 +263,15 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned i
}
entry = git_reflog_entry_byindex(reflog, identifier);
- git_oid_cpy(oid, git_reflog_entry_oidold(entry));
+ git_oid_cpy(oid, git_reflog_entry_oidnew(entry));
error = 0;
goto cleanup;
} else {
- int i;
+ unsigned int i;
git_time commit_time;
- for (i = numentries - 1; i >= 0; i--) {
+ for (i = 0; i < numentries; i++) {
entry = git_reflog_entry_byindex(reflog, i);
commit_time = git_reflog_entry_committer(entry)->when;
diff --git a/src/sha1.h b/src/sha1.h
deleted file mode 100644
index 41e8abad6..000000000
--- a/src/sha1.h
+++ /dev/null
@@ -1,33 +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_sha1_h__
-#define INCLUDE_sha1_h__
-
-#ifdef OPENSSL_SHA
-# include <openssl/sha.h>
-
-#else
-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
-
-#endif // OPENSSL_SHA
-
-#endif
diff --git a/src/stash.c b/src/stash.c
new file mode 100644
index 000000000..b74429aca
--- /dev/null
+++ b/src/stash.c
@@ -0,0 +1,660 @@
+/*
+ * 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 "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"
+
+static int create_error(int error, const char *msg)
+{
+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
+ return error;
+}
+
+static int ensure_non_bare_repository(git_repository *repo)
+{
+ if (!git_repository_is_bare(repo))
+ return 0;
+
+ return create_error(GIT_EBAREREPO,
+ "Stash related operations require a working directory.");
+}
+
+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;
+ int 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;
+
+ error = -1;
+
+ if (strcmp("HEAD", git_reference_name(head)) == 0)
+ git_buf_puts(stash_message, "(no branch): ");
+ else
+ git_buf_printf(
+ stash_message,
+ "%s: ",
+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
+
+ if (git_commit_lookup(b_commit, repo, git_reference_oid(head)) < 0)
+ goto cleanup;
+
+ if (append_commit_description(stash_message, *b_commit) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+static int build_tree_from_index(git_tree **out, git_index *index)
+{
+ git_oid i_tree_oid;
+
+ if (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 = -1;
+
+ if (build_tree_from_index(&i_tree, index) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&msg, "index on %s\n", message) < 0)
+ goto cleanup;
+
+ if (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;
+
+ bool include_changed;
+ bool include_untracked;
+ bool include_ignored;
+};
+
+static int update_index_cb(
+ void *cb_data,
+ const git_diff_delta *delta,
+ float progress)
+{
+ int pos;
+ struct cb_data *data = (struct cb_data *)cb_data;
+
+ GIT_UNUSED(progress);
+
+ switch (delta->status) {
+ case GIT_DELTA_IGNORED:
+ if (!data->include_ignored)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_UNTRACKED:
+ if (!data->include_untracked)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_ADDED:
+ /* Fall through */
+ case GIT_DELTA_MODIFIED:
+ if (!data->include_changed)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_DELETED:
+ if (!data->include_changed)
+ break;
+
+ if ((pos = git_index_find(data->index, delta->new_file.path)) < 0)
+ return -1;
+
+ if (git_index_remove(data->index, delta->new_file.path, 0) < 0)
+ return -1;
+
+ default:
+ /* Unimplemented */
+ giterr_set(
+ GITERR_INVALID,
+ "Cannot update index. Unimplemented status kind (%d)",
+ delta->status);
+ return -1;
+ }
+
+ return 0;
+}
+
+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 = {0};
+ struct cb_data data = {0};
+ int error = -1;
+
+ 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 (git_commit_tree(&i_tree, i_commit) < 0)
+ goto cleanup;
+
+ if (git_diff_workdir_to_tree(&diff, git_index_owner(index), i_tree, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0)
+ goto cleanup;
+
+ if (build_tree_from_index(tree_out, index) < 0)
+ goto cleanup;
+
+ error = 0;
+
+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 = -1;
+
+ if (build_untracked_tree(&u_tree, index, i_commit, flags) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&msg, "untracked files on %s\n", message) < 0)
+ goto cleanup;
+
+ if (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 = {0};
+ struct cb_data data = {0};
+ int error = -1;
+
+ if (git_commit_tree(&b_tree, b_commit) < 0)
+ goto cleanup;
+
+ if (git_diff_index_to_tree(&diff, repo, b_tree, NULL, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_workdir_to_index(&diff2, repo, NULL, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_merge(diff, diff2) < 0)
+ goto cleanup;
+
+ data.index = index;
+ data.include_changed = true;
+
+ if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0)
+ goto cleanup;
+
+ if (build_tree_from_index(tree_out, index) < 0)
+ goto cleanup;
+
+ error = 0;
+
+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)
+{
+ git_tree *w_tree = NULL, *i_tree = NULL;
+ int error = -1;
+
+ const git_commit *parents[] = { NULL, NULL, NULL };
+
+ parents[0] = b_commit;
+ parents[1] = i_commit;
+ parents[2] = u_commit;
+
+ if (git_commit_tree(&i_tree, i_commit) < 0)
+ return -1;
+
+ if (git_index_read_tree(index, i_tree) < 0)
+ goto cleanup;
+
+ if (build_workdir_tree(&w_tree, index, b_commit) < 0)
+ goto cleanup;
+
+ if (git_commit_create(
+ w_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ message,
+ w_tree,
+ u_commit ? 3 : 2, parents) < 0)
+ goto cleanup;
+
+ error = 0;
+
+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 = -1;
+
+ git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg));
+ 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_oid(&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;
+
+ error = 0;
+
+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;
+
+ memset(&opts, 0, sizeof(opts));
+ 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;
+
+ memset(&opts, 0, sizeof(git_checkout_opts));
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
+
+ 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 = ensure_non_bare_repository(repo)) < 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) == GIT_STASH_INCLUDE_UNTRACKED,
+ (flags & GIT_STASH_INCLUDE_IGNORED) == GIT_STASH_INCLUDE_IGNORED)) < 0)
+ goto cleanup;
+
+ error = -1;
+
+ if (git_repository_index(&index, repo) < 0)
+ goto cleanup;
+
+ if (commit_index(&i_commit, index, stasher, git_buf_cstr(&msg), b_commit) < 0)
+ goto cleanup;
+
+ if ((flags & GIT_STASH_INCLUDE_UNTRACKED || flags & GIT_STASH_INCLUDE_IGNORED)
+ && commit_untracked(&u_commit, index, stasher, git_buf_cstr(&msg), i_commit, flags) < 0)
+ goto cleanup;
+
+ if (prepare_worktree_commit_message(&msg, message) < 0)
+ goto cleanup;
+
+ if (commit_worktree(out, index, stasher, git_buf_cstr(&msg), i_commit, b_commit, u_commit) < 0)
+ goto cleanup;
+
+ git_buf_rtrim(&msg);
+ if (update_reflog(out, repo, stasher, git_buf_cstr(&msg)) < 0)
+ goto cleanup;
+
+ if (reset_index_and_workdir(
+ repo,
+ ((flags & GIT_STASH_KEEP_INDEX) == GIT_STASH_KEEP_INDEX) ?
+ i_commit : b_commit,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED) < 0)
+ goto cleanup;
+
+ error = 0;
+
+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,
+ 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_msg(entry),
+ git_reflog_entry_oidnew(entry),
+ payload)) {
+ error = GIT_EUSER;
+ goto cleanup;
+ }
+ }
+
+ error = 0;
+
+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);
+ stash = NULL;
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
diff --git a/src/status.c b/src/status.c
index f57100d11..b8c15ef92 100644
--- a/src/status.c
+++ b/src/status.c
@@ -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)
{
@@ -76,21 +77,43 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
return st;
}
+typedef struct {
+ int (*cb)(const char *, unsigned int, void *);
+ void *cbdata;
+} status_user_callback;
+
+static int status_invoke_cb(
+ void *cbref, git_diff_delta *i2h, git_diff_delta *w2i)
+{
+ status_user_callback *usercb = cbref;
+ const char *path = NULL;
+ unsigned int status = 0;
+
+ if (w2i) {
+ path = w2i->old_file.path;
+ status |= workdir_delta2status(w2i->status);
+ }
+ if (i2h) {
+ path = i2h->old_file.path;
+ status |= index_delta2status(i2h->status);
+ }
+
+ return usercb->cb(path, status, usercb->cbdata);
+}
+
int git_status_foreach_ext(
git_repository *repo,
const git_status_options *opts,
int (*cb)(const char *, unsigned int, void *),
void *cbdata)
{
- int err = 0, cmp;
+ int err = 0;
git_diff_options diffopt;
git_diff_list *idx2head = NULL, *wd2idx = NULL;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- git_diff_delta *i2h, *w2i;
- size_t i, j, i_max, j_max;
- bool ignore_case = false;
+ status_user_callback usercb;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
@@ -119,62 +142,26 @@ int git_status_foreach_ext(
/* TODO: support EXCLUDE_SUBMODULES flag */
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
- (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)
+ (err = git_diff_index_to_tree(&idx2head, repo, head, NULL, &diffopt)) < 0)
goto cleanup;
if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0)
+ (err = git_diff_workdir_to_index(&wd2idx, repo, NULL, &diffopt)) < 0)
goto cleanup;
+ usercb.cb = cb;
+ usercb.cbdata = cbdata;
+
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
- for (i = 0; !err && i < idx2head->deltas.length; i++) {
- i2h = GIT_VECTOR_GET(&idx2head->deltas, i);
- if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata))
- err = GIT_EUSER;
- }
+ if ((err = git_diff__paired_foreach(
+ idx2head, NULL, status_invoke_cb, &usercb)) < 0)
+ goto cleanup;
+
git_diff_list_free(idx2head);
idx2head = NULL;
}
- i_max = idx2head ? idx2head->deltas.length : 0;
- j_max = wd2idx ? wd2idx->deltas.length : 0;
-
- if (idx2head && wd2idx &&
- (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
- 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
- {
- /* Then use the ignore-case sorter... */
- ignore_case = true;
-
- /* and assert that both are ignore-case sorted. If this function
- * ever needs to support merge joining result sets that are not sorted
- * by the same function, then it will need to be extended to do a spool
- * and sort on one of the results before merge joining */
- assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
- 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
- }
-
- 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_CASESELECT(ignore_case, i2h->old_file.path, w2i->old_file.path);
-
- if (cmp < 0) {
- if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata))
- err = GIT_EUSER;
- i++;
- } else if (cmp > 0) {
- if (cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata))
- err = GIT_EUSER;
- j++;
- } else {
- if (cb(i2h->old_file.path, index_delta2status(i2h->status) |
- workdir_delta2status(w2i->status), cbdata))
- err = GIT_EUSER;
- i++; j++;
- }
- }
+ err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb);
cleanup:
git_tree_free(head);
@@ -217,7 +204,7 @@ static int get_one_status(const char *path, unsigned int status, void *data)
sfi->count++;
sfi->status = status;
- if (sfi->count > 1 ||
+ if (sfi->count > 1 ||
(strcmp(sfi->expected, path) != 0 &&
p_fnmatch(sfi->expected, path, 0) != 0)) {
giterr_set(GITERR_INVALID,
diff --git a/src/submodule.c b/src/submodule.c
index e3657f9ad..6eb1c52f7 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -332,7 +332,7 @@ int git_submodule_add_finalize(git_submodule *sm)
assert(sm);
if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
- (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0)
+ (error = git_index_add_from_workdir(index, GIT_MODULES_FILE)) < 0)
return error;
return git_submodule_add_to_index(sm, true);
@@ -371,7 +371,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
memset(&entry, 0, sizeof(entry));
entry.path = sm->path;
- git_index__init_entry_from_stat(&st, &entry);
+ 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) {
@@ -393,7 +393,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
git_commit_free(head);
/* add it */
- error = git_index_add2(index, &entry);
+ error = git_index_add(index, &entry);
/* write it, if requested */
if (!error && write_index) {
@@ -733,7 +733,7 @@ int git_submodule_reload(git_submodule *submodule)
pos = git_index_find(index, submodule->path);
if (pos >= 0) {
- git_index_entry *entry = git_index_get(index, pos);
+ git_index_entry *entry = git_index_get_byindex(index, pos);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_load_from_index(repo, entry)) < 0)
@@ -1118,7 +1118,7 @@ static int load_submodule_config_from_index(
git_iterator *i;
const git_index_entry *entry;
- if ((error = git_iterator_for_index(&i, repo)) < 0)
+ if ((error = git_iterator_for_repo_index(&i, repo)) < 0)
return error;
error = git_iterator_current(i, &entry);
@@ -1455,7 +1455,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
- error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
+ error = git_diff_index_to_tree(&diff, sm_repo, sm_head, NULL, &opt);
if (!error) {
if (git_diff_num_deltas(diff) > 0)
@@ -1472,7 +1472,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
/* perform index-to-workdir diff on submodule */
- error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
+ error = git_diff_workdir_to_index(&diff, sm_repo, NULL, &opt);
if (!error) {
size_t untracked =
diff --git a/src/tag.c b/src/tag.c
index 56f84a85f..13369d9fb 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -39,7 +39,7 @@ const git_oid *git_tag_target_oid(git_tag *t)
return &t->target;
}
-git_otype git_tag_type(git_tag *t)
+git_otype git_tag_target_type(git_tag *t)
{
assert(t);
return t->type;
@@ -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;
}
diff --git a/src/transport.c b/src/transport.c
index fb2b94946..8c242af6d 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -8,52 +8,78 @@
#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_https},
- {"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;
+
+ if (definition_iter->priority > priority)
+ definition = definition_iter;
}
- /* 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) {
+ /* 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))
+ definition = &local_transport_definition;
+
+ /* It could be a SSH remote path. Check to see if there's a : */
+ if (strrchr(url, ':'))
+ definition = &dummy_transport_definition; /* SSH is an unsupported transport mechanism in this version of libgit2 */
+ }
- /* 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 */
+ 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, void *param)
{
GIT_UNUSED(transport);
+ GIT_UNUSED(param);
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
return -1;
}
@@ -62,22 +88,18 @@ int git_transport_new(git_transport **out, 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, param);
if (error < 0)
return error;
- transport->url = git__strdup(url);
- GITERR_CHECK_ALLOC(transport->url);
-
*out = transport;
return 0;
@@ -86,12 +108,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 4c944b9e7..000000000
--- a/src/transport.h
+++ /dev/null
@@ -1,148 +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"
-#include "posix.h"
-#include "common.h"
-#include "netops.h"
-#ifdef GIT_SSL
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#endif
-
-
-#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"
-
-typedef struct git_transport_caps {
- int common:1,
- ofs_delta:1,
- multi_ack: 1,
- side_band:1,
- side_band_64k:1,
- include_tag:1;
-} git_transport_caps;
-
-#ifdef GIT_SSL
-typedef struct gitno_ssl {
- SSL_CTX *ctx;
- SSL *ssl;
-} gitno_ssl;
-#endif
-
-
-/*
- * 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,
- check_cert: 1,
- use_ssl : 1,
- own_logic: 1, /* transitional */
- rpc: 1; /* git-speak for the HTTP transport */
-#ifdef GIT_SSL
- struct gitno_ssl ssl;
-#endif
- git_vector refs;
- git_vector common;
- gitno_buffer buffer;
- GIT_SOCKET socket;
- git_transport_caps caps;
- void *cb_data;
- git_atomic cancel;
-
- /**
- * Connect and store the remote heads
- */
- int (*connect)(struct git_transport *transport, int dir);
- /**
- * Send our side of a negotiation
- */
- int (*negotiation_step)(struct git_transport *transport, void *data, size_t len);
- /**
- * 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);
- /**
- * Close the connection
- */
- int (*close)(struct git_transport *transport);
- /**
- * Free the associated resources
- */
- void (*free)(struct git_transport *transport);
- /**
- * Callbacks for the progress and error output
- */
- void (*progress_cb)(const char *str, int len, void *data);
- void (*error_cb)(const char *str, int len, void *data);
-};
-
-
-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_https(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 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..55295372f
--- /dev/null
+++ b/src/transports/cred.c
@@ -0,0 +1,57 @@
+/*
+ * 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 "git2.h"
+#include "smart.h"
+
+static void plaintext_free(struct git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ int 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);
+
+ 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_cred_userpass_plaintext *)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;
+} \ No newline at end of file
diff --git a/src/transports/git.c b/src/transports/git.c
index b757495c5..a895c1389 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -5,40 +5,37 @@
* 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";
typedef struct {
- git_transport parent;
- char buff[65536];
-#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;
@@ -54,9 +51,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);
@@ -71,175 +65,211 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
return 0;
}
-static int send_request(git_transport *t, 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(t, 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://";
+ 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 (gitno_connect((git_transport *)t, host, port) < 0)
- goto on_error;
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
- if (send_request((git_transport *)t, NULL, url) < 0)
- goto on_error;
+ if (gitno_recv(&buf) < 0)
+ return -1;
- git__free(host);
- git__free(port);
+ *bytes_read = buf.offset;
return 0;
-
-on_error:
- git__free(host);
- git__free(port);
- gitno_close(t->parent.socket);
- return -1;
}
-/*
- * 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_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- transport_git *t = (transport_git *) transport;
+ git_stream *s = (git_stream *)stream;
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over git:// is not supported");
+ if (!s->sent_command && send_command(s) < 0)
return -1;
+
+ return gitno_send(&s->socket, buffer, len, 0);
+}
+
+static void git_stream_free(git_smart_subtransport_stream *stream)
+{
+ git_stream *s = (git_stream *)stream;
+ git_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
}
- t->parent.direction = direction;
+ git__free(s->url);
+ git__free(s);
+}
- /* Connect and ask for the refs */
- if (do_connect(t, transport->url) < 0)
+static int git_stream_alloc(
+ git_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ git_stream *s;
+
+ if (!stream)
return -1;
- gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff));
+ s = (git_stream *)git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- t->parent.connected = 1;
- if (git_protocol_store_refs(transport, 1) < 0)
- return -1;
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
- if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
return -1;
+ }
+ *stream = &s->parent;
return 0;
}
-static int git_negotiation_step(struct git_transport *transport, void *data, size_t len)
+static int git_git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- return gitno_send(transport, data, len, 0);
-}
+ char *host, *port;
+ git_stream *s;
-static int git_close(git_transport *t)
-{
- git_buf buf = GIT_BUF_INIT;
+ *stream = NULL;
- if (git_pkt_buffer_flush(&buf) < 0)
- return -1;
- /* Can't do anything if there's an error, so don't bother checking */
- gitno_send(t, buf.ptr, buf.size, 0);
- git_buf_free(&buf);
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (gitno_close(t->socket) < 0) {
- giterr_set(GITERR_NET, "Failed to close socket");
+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
return -1;
- }
- t->connected = 0;
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
-#ifdef GIT_WIN32
- WSACleanup();
-#endif
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
}
-static void git_free(git_transport *transport)
+static int git_git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &transport->refs;
- unsigned int i;
+ GIT_UNUSED(url);
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
}
- git_vector_free(refs);
- refs = &transport->common;
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ git_subtransport *t = (git_subtransport *) smart_transport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return git_git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return git_git_uploadpack(t, url, stream);
}
- git_vector_free(refs);
- git__free(t->parent.url);
- git__free(t);
+ *stream = NULL;
+ return -1;
}
-int git_transport_git(git_transport **out)
+static void _git_free(git_smart_subtransport *smart_transport)
{
- transport_git *t;
-#ifdef GIT_WIN32
- int ret;
-#endif
+ git_subtransport *t = (git_subtransport *) smart_transport;
- t = git__malloc(sizeof(transport_git));
- GITERR_CHECK_ALLOC(t);
+ assert(!t->current_stream);
- memset(t, 0x0, sizeof(transport_git));
- if (git_vector_init(&t->parent.common, 8, NULL))
- goto on_error;
+ git__free(t);
+}
- if (git_vector_init(&t->parent.refs, 16, NULL) < 0)
- goto on_error;
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
+{
+ git_subtransport *t;
- t->parent.connect = git_connect;
- t->parent.negotiation_step = git_negotiation_step;
- t->parent.close = git_close;
- t->parent.free = git_free;
+ if (!out)
+ return -1;
- *out = (git_transport *) t;
+ t = (git_subtransport *)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.free = _git_free;
+ *out = (git_smart_subtransport *) t;
return 0;
-
-on_error:
- git__free(t);
- return -1;
}
diff --git a/src/transports/http.c b/src/transports/http.c
index 93dd0c326..ba4d8746f 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -4,28 +4,27 @@
* 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 <stdlib.h>
+#ifndef GIT_WINHTTP
+
#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"
-#if GIT_WINHTTP
-# include <winhttp.h>
-# pragma comment(lib, "winhttp.lib")
-#endif
-
-#define WIDEN2(s) L ## s
-#define WIDEN(s) WIDEN2(s)
+#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 *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
enum last_cb {
NONE,
@@ -33,44 +32,93 @@ enum last_cb {
VALUE
};
+typedef enum {
+ GIT_HTTP_AUTH_BASIC = 1,
+} http_authmechanism_t;
+
typedef struct {
- git_transport parent;
- http_parser_settings settings;
- git_buf buf;
- 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;
- char *path;
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const char *verb;
+ unsigned sent_request : 1;
+} http_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_socket socket;
+ const char *path;
char *host;
char *port;
- char *service;
- char buffer[65536];
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-#ifdef GIT_WINHTTP
- HINTERNET session;
- HINTERNET connection;
- HINTERNET request;
-#endif
-} transport_http;
-
-static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
- const char *service, ssize_t content_length, int ls)
+ git_cred *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;
+ 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)
{
- if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
+
+ 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,
+ const char *path,
+ const char *host,
+ git_cred *cred,
+ http_authmechanism_t auth_mechanism,
+ const char *op,
+ const char *service,
+ const char *service_url,
+ ssize_t content_length)
+{
+ if (!path)
path = "/";
- 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_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
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) {
@@ -80,6 +128,13 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
} else {
git_buf_puts(buf, "Accept: */*\r\n");
}
+
+ /* Apply credentials to the request */
+ if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ auth_mechanism == GIT_HTTP_AUTH_BASIC &&
+ apply_basic_credential(buf, cred) < 0)
+ return -1;
+
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
@@ -88,538 +143,508 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
return 0;
}
-static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls)
+static int parse_unauthorized_response(
+ git_vector *www_authenticate,
+ int *allowed_types,
+ http_authmechanism_t *auth_mechanism)
{
-#ifndef GIT_WINHTTP
- git_buf request = GIT_BUF_INIT;
- const char *verb;
- int error = -1;
-
- verb = ls ? "GET" : "POST";
- /* Generate and send the HTTP request */
- if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
- return -1;
+ 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;
+ }
}
+ return 0;
+}
- if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0)
- goto cleanup;
+static int on_header_ready(http_subtransport *t)
+{
+ git_buf *name = &t->parse_header_name;
+ git_buf *value = &t->parse_header_value;
+ char *dup;
- if (content_length) {
- if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
- goto cleanup;
+ if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
+ dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
+ git_vector_insert(&t->www_authenticate, dup);
}
- error = 0;
+ return 0;
+}
-cleanup:
- git_buf_free(&request);
- return error;
+static int on_header_field(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ /* 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;
-#else
- wchar_t *verb;
- wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH];
+ if (NONE == t->last_cb || VALUE == t->last_cb)
+ git_buf_clear(&t->parse_header_name);
+
+ if (git_buf_put(&t->parse_header_name, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = FIELD;
+ return 0;
+}
+
+static int on_header_value(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ assert(NONE != t->last_cb);
+
+ if (FIELD == t->last_cb)
+ git_buf_clear(&t->parse_header_value);
+
+ if (git_buf_put(&t->parse_header_value, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = VALUE;
+ return 0;
+}
+
+static int on_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- BOOL ret;
- DWORD flags;
- void *buffer;
- wchar_t *types[] = {
- L"*/*",
- NULL,
- };
-
- verb = ls ? L"GET" : L"POST";
- buffer = data ? data : WINHTTP_NO_REQUEST_DATA;
- flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0;
-
- if (ls)
- git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service);
- else
- git_buf_printf(&buf, "%s/git-%s", t->path, service);
- if (git_buf_oom(&buf))
- return -1;
+ /* 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;
- git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 &&
+ get_verb == s->verb &&
+ t->owner->cred_acquire_cb) {
+ int allowed_types = 0;
- t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags);
- if (t->request == NULL) {
- git_buf_free(&buf);
- giterr_set(GITERR_OS, "Failed to open request");
- return -1;
- }
+ if (parse_unauthorized_response(&t->www_authenticate,
+ &allowed_types, &t->auth_mechanism) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- git_buf_clear(&buf);
- if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0)
- goto on_error;
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
- git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf));
+ if (t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ allowed_types) < 0)
+ return PARSE_ERROR_GENERIC;
- if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
- giterr_set(GITERR_OS, "Failed to add a header to the request");
- goto on_error;
- }
+ assert(t->cred);
- if (!t->parent.check_cert) {
- int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
- if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) {
- giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
- goto on_error;
+ /* Successfully acquired a credential. */
+ return t->parse_error = PARSE_ERROR_REPLAY;
}
}
- if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) {
- giterr_set(GITERR_OS, "Failed to send request");
- goto on_error;
+ /* 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;
}
- ret = WinHttpReceiveResponse(t->request, NULL);
- if (ret == FALSE) {
- giterr_set(GITERR_OS, "Failed to receive response");
- goto on_error;
+ /* 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;
}
- return 0;
+ /* 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;
+ }
-on_error:
git_buf_free(&buf);
- if (t->request)
- WinHttpCloseHandle(t->request);
- t->request = NULL;
- return -1;
-#endif
+
+ return 0;
}
-static int do_connect(transport_http *t)
+static int on_message_complete(http_parser *parser)
{
-#ifndef GIT_WINHTTP
- if (t->parent.connected && http_should_keep_alive(&t->parser))
- return 0;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (gitno_connect((git_transport *) t, t->host, t->port) < 0)
- return -1;
-
- t->parent.connected = 1;
+ t->parse_finished = 1;
return 0;
-#else
- wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
- wchar_t host[GIT_WIN_PATH];
- int32_t port;
+}
- t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
- WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
+static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (t->session == NULL) {
- giterr_set(GITERR_OS, "Failed to init WinHTTP");
- goto on_error;
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "Can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- git__utf8_to_16(host, GIT_WIN_PATH, t->host);
-
- if (git__strtol32(&port, t->port, NULL, 10) < 0)
- goto on_error;
-
- t->connection = WinHttpConnect(t->session, host, port, 0);
- if (t->connection == NULL) {
- giterr_set(GITERR_OS, "Failed to connect to host");
- goto on_error;
- }
+ memcpy(ctx->buffer, str, len);
+ *(ctx->bytes_read) += len;
+ ctx->buffer += len;
+ ctx->buf_size -= len;
- t->parent.connected = 1;
return 0;
-
-on_error:
- if (t->session) {
- WinHttpCloseHandle(t->session);
- t->session = NULL;
- }
- return -1;
-#endif
}
-/*
- * 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 void clear_parser_state(http_subtransport *t)
+{
+ unsigned i;
+ char *entry;
-static const char *typestr = "Content-Type";
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ gitno_buffer_setup(&t->socket,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
-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;
+ t->last_cb = NONE;
+ t->parse_error = 0;
+ t->parse_finished = 0;
- 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);
- }
+ git_buf_free(&t->parse_header_name);
+ git_buf_init(&t->parse_header_name, 0);
- if (t->ct_found) {
- t->last_cb = FIELD;
- return 0;
- }
+ git_buf_free(&t->parse_header_value);
+ git_buf_init(&t->parse_header_value, 0);
- if (t->last_cb != FIELD)
- git_buf_clear(buf);
+ git__free(t->content_type);
+ t->content_type = NULL;
- git_buf_put(buf, str, len);
- t->last_cb = FIELD;
+ git_vector_foreach(&t->www_authenticate, i, entry)
+ git__free(entry);
- return git_buf_oom(buf);
+ git_vector_free(&t->www_authenticate);
}
-static int on_header_value(http_parser *parser, const char *str, size_t len)
+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 *) parser->data;
- git_buf *buf = &t->buf;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
+ parser_context ctx;
- if (t->ct_finished) {
- t->last_cb = VALUE;
- return 0;
- }
+replay:
+ *bytes_read = 0;
- if (t->last_cb == VALUE)
- git_buf_put(buf, str, len);
+ assert(t->connected);
- 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 (!s->sent_request) {
+ clear_parser_state(t);
- t->last_cb = VALUE;
+ if (gen_request(&request, t->path, t->host,
+ t->cred, t->auth_mechanism, s->verb,
+ s->service, s->service_url, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- return git_buf_oom(buf);
-}
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
-static int on_headers_complete(http_parser *parser)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ git_buf_free(&request);
+ s->sent_request = 1;
+ }
+
+ t->parse_buffer.offset = 0;
- /* The content-type is text/plain for 404, so don't validate */
- if (parser->status_code == 404) {
- git_buf_clear(buf);
+ if (t->parse_finished)
return 0;
- }
- if (t->content_type == NULL) {
- t->content_type = git__strdup(git_buf_cstr(buf));
- if (t->content_type == NULL)
- return t->error = -1;
- }
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
- git_buf_clear(buf);
- git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
- if (git_buf_oom(buf))
- 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;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ http_parser_execute(&t->parser,
+ &t->settings,
+ t->parse_buffer.data,
+ t->parse_buffer.offset);
+
+ 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;
+ goto replay;
+ }
- if (strcmp(t->content_type, git_buf_cstr(buf)))
- return t->error = -1;
+ if (t->parse_error < 0)
+ return -1;
- git_buf_clear(buf);
return 0;
}
-static int on_message_complete(http_parser *parser)
+static int http_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- transport_http *t = (transport_http *) parser->data;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
- t->transfer_finished = 1;
+ assert(t->connected);
- if (parser->status_code == 404) {
- giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
- t->error = -1;
- }
+ /* Since we have to write the Content-Length header up front, we're
+ * basically limited to a single call to write() per request. */
+ assert(!s->sent_request);
- return 0;
-}
+ if (!s->sent_request) {
+ clear_parser_state(t);
-static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
-{
- git_transport *transport = (git_transport *) parser->data;
- transport_http *t = (transport_http *) parser->data;
- gitno_buffer *buf = &transport->buffer;
+ if (gen_request(&request, t->path, t->host,
+ t->cred, t->auth_mechanism, s->verb,
+ s->service, s->service_url, len) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- if (buf->len - buf->offset < len) {
- giterr_set(GITERR_NET, "Can't fit data in the buffer");
- return t->error = -1;
- }
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
+ goto on_error;
+
+ if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
+ goto on_error;
- memcpy(buf->data + buf->offset, str, len);
- buf->offset += len;
+ git_buf_free(&request);
+ s->sent_request = 1;
+ }
return 0;
+
+on_error:
+ git_buf_free(&request);
+ return -1;
}
-static int http_recv_cb(gitno_buffer *buf)
+static void http_stream_free(git_smart_subtransport_stream *stream)
{
- git_transport *transport = (git_transport *) buf->cb_data;
- transport_http *t = (transport_http *) transport;
- size_t old_len;
- char buffer[2048];
-#ifdef GIT_WINHTTP
- DWORD recvd;
-#else
- gitno_buffer inner;
- int error;
-#endif
-
- if (t->transfer_finished)
- return 0;
+ http_stream *s = (http_stream *)stream;
-#ifndef GIT_WINHTTP
- gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
+ git__free(s);
+}
- if ((error = gitno_recv(&inner)) < 0)
- return -1;
+static int http_stream_alloc(http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
- old_len = buf->offset;
- http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
- if (t->error < 0)
- return t->error;
-#else
- old_len = buf->offset;
- if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) {
- giterr_set(GITERR_OS, "Failed to read data from the network");
- return t->error = -1;
- }
+ if (!stream)
+ return -1;
- if (buf->len - buf->offset < recvd) {
- giterr_set(GITERR_NET, "Can't fit data in the buffer");
- return t->error = -1;
- }
+ s = (http_stream *)git__calloc(sizeof(http_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- memcpy(buf->data + buf->offset, buffer, recvd);
- buf->offset += recvd;
-#endif
+ s->parent.subtransport = &t->parent;
+ s->parent.read = http_stream_read;
+ s->parent.write = http_stream_write;
+ s->parent.free = http_stream_free;
- return (int)(buf->offset - old_len);
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
}
-/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
-static void setup_gitno_buffer(git_transport *transport)
+static int http_uploadpack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- transport_http *t = (transport_http *) transport;
+ http_stream *s;
- /* WinHTTP takes care of this for us */
-#ifndef GIT_WINHTTP
- http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- t->transfer_finished = 0;
- memset(&t->settings, 0x0, sizeof(http_parser_settings));
- 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;
-#endif
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
- gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
}
-static int http_connect(git_transport *transport, int direction)
+static int http_uploadpack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- 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 = "http://", *prefix_https = "https://";
- const char *default_port;
- git_pkt *pkt;
+ http_stream *s;
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
+ if (http_stream_alloc(t, stream) < 0)
return -1;
- }
-
- t->parent.direction = direction;
- if (!git__prefixcmp(url, prefix_http)) {
- url = t->parent.url + strlen(prefix_http);
- default_port = "80";
- }
+ s = (http_stream *)*stream;
- if (!git__prefixcmp(url, prefix_https)) {
- url += strlen(prefix_https);
- default_port = "443";
- }
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
- t->path = strchr(url, '/');
+ return 0;
+}
- if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
- goto cleanup;
+static int http_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *t = (http_subtransport *)smart_transport;
+ const char *default_port;
+ int flags = 0, ret;
- t->service = git__strdup(service);
- GITERR_CHECK_ALLOC(t->service);
+ if (!stream)
+ return -1;
- if ((ret = do_connect(t)) < 0)
- goto cleanup;
+ if (!t->host || !t->port || !t->path) {
+ if (!git__prefixcmp(url, prefix_http)) {
+ url = url + strlen(prefix_http);
+ default_port = "80";
+ }
- if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0)
- goto cleanup;
+ if (!git__prefixcmp(url, prefix_https)) {
+ url += strlen(prefix_https);
+ default_port = "443";
+ t->use_ssl = 1;
+ }
- setup_gitno_buffer(transport);
- if ((ret = git_protocol_store_refs(transport, 2)) < 0)
- goto cleanup;
+ if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
+ url, default_port)) < 0)
+ return ret;
- pkt = git_vector_get(&transport->refs, 0);
- if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
- giterr_set(GITERR_NET, "Invalid HTTP response");
- return t->error = -1;
- } else {
- /* Remove the comment pkt from the list */
- git_vector_remove(&transport->refs, 0);
- git__free(pkt);
+ t->path = strchr(url, '/');
}
- if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
- return t->error = -1;
+ if (!t->connected || !http_should_keep_alive(&t->parser)) {
+ if (t->use_ssl) {
+ int transport_flags;
-cleanup:
- git_buf_free(&request);
- git_buf_clear(&t->buf);
+ if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0)
+ return -1;
- return ret;
-}
+ flags |= GITNO_CONNECT_SSL;
-static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
-{
- transport_http *t = (transport_http *) transport;
- int ret;
+ if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags)
+ flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
+ }
- /* First, send the data as a HTTP POST request */
- if ((ret = do_connect(t)) < 0)
- return -1;
+ if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
+ return -1;
- if (send_request(t, "upload-pack", data, len, 0) < 0)
- return -1;
-
- /* Then we need to set up the buffer to grab data from the HTTP response */
- setup_gitno_buffer(transport);
+ t->connected = 1;
+ }
- return 0;
-}
+ t->parse_finished = 0;
+ t->parse_error = 0;
-static int http_close(git_transport *transport)
-{
-#ifndef GIT_WINHTTP
- if (gitno_ssl_teardown(transport) < 0)
- return -1;
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return http_uploadpack_ls(t, stream);
- if (gitno_close(transport->socket) < 0) {
- giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
- return -1;
+ case GIT_SERVICE_UPLOADPACK:
+ return http_uploadpack(t, stream);
}
-#else
- transport_http *t = (transport_http *) transport;
- if (t->request)
- WinHttpCloseHandle(t->request);
- if (t->connection)
- WinHttpCloseHandle(t->connection);
- if (t->session)
- WinHttpCloseHandle(t->session);
-#endif
+ *stream = NULL;
+ return -1;
+}
- transport->connected = 0;
+static void http_free(git_smart_subtransport *smart_transport)
+{
+ http_subtransport *t = (http_subtransport *) smart_transport;
- return 0;
-}
+ clear_parser_state(t);
+ if (t->socket.socket)
+ gitno_close(&t->socket);
-static void http_free(git_transport *transport)
-{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &transport->refs;
- git_vector *common = &transport->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);
- }
- git_vector_free(refs);
- git_vector_foreach(common, i, p) {
- git_pkt_free(p);
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
}
- git_vector_free(common);
- git_buf_free(&t->buf);
- git__free(t->content_type);
+
git__free(t->host);
git__free(t->port);
- git__free(t->service);
- git__free(t->parent.url);
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 = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
- t->parent.connect = http_connect;
- t->parent.negotiation_step = http_negotiation_step;
- t->parent.close = http_close;
+ t->owner = (transport_smart *)owner;
+ t->parent.action = http_action;
t->parent.free = http_free;
- t->parent.rpc = 1;
-
- if (git_vector_init(&t->parent.refs, 16, NULL) < 0) {
- git__free(t);
- return -1;
- }
-#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
+ 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_transport *) t;
+ *out = (git_smart_subtransport *) t;
return 0;
}
-int git_transport_https(git_transport **out)
-{
-#if defined(GIT_SSL) || defined(GIT_WINHTTP)
- transport_http *t;
- if (git_transport_http((git_transport **)&t) < 0)
- return -1;
-
- t->parent.use_ssl = 1;
- t->parent.check_cert = 1;
- *out = (git_transport *) t;
-
- return 0;
-#else
- GIT_UNUSED(out);
-
- giterr_set(GITERR_NET, "HTTPS support not available");
- return -1;
-#endif
-}
+#endif /* !GIT_WINHTTP */
diff --git a/src/transports/local.c b/src/transports/local.c
index 561c84fa0..46c9218c7 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -10,16 +10,29 @@
#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 "pack-objects.h"
#include "refs.h"
-#include "transport.h"
#include "posix.h"
#include "path.h"
#include "buffer.h"
-#include "pkt.h"
+#include "repository.h"
+#include "odb.h"
typedef struct {
git_transport parent;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
git_repository *repo;
+ git_vector refs;
+ unsigned connected : 1;
} transport_local;
static int add_ref(transport_local *t, const char *name)
@@ -27,32 +40,24 @@ static int add_ref(transport_local *t, const char *name)
const char peeled[] = "^{}";
git_remote_head *head;
git_object *obj = NULL, *target = NULL;
- git_transport *transport = (git_transport *) t;
git_buf buf = GIT_BUF_INIT;
- git_pkt_ref *pkt;
- head = git__malloc(sizeof(git_remote_head));
+ head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
- pkt = git__malloc(sizeof(git_pkt_ref));
- GITERR_CHECK_ALLOC(pkt);
head->name = git__strdup(name);
GITERR_CHECK_ALLOC(head->name);
if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) {
+ git__free(head->name);
git__free(head);
- git__free(pkt->head.name);
- git__free(pkt);
+ return -1;
}
- pkt->type = GIT_PKT_REF;
- memcpy(&pkt->head, head, sizeof(git_remote_head));
- git__free(head);
-
- if (git_vector_insert(&transport->refs, pkt) < 0)
+ if (git_vector_insert(&t->refs, head) < 0)
{
- git__free(pkt->head.name);
- git__free(pkt);
+ git__free(head->name);
+ git__free(head);
return -1;
}
@@ -60,7 +65,7 @@ static int add_ref(transport_local *t, const char *name)
if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
return 0;
- if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0)
+ if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
return -1;
head = NULL;
@@ -72,27 +77,21 @@ static int add_ref(transport_local *t, const char *name)
}
/* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(sizeof(git_remote_head));
+ head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
if (git_buf_join(&buf, 0, name, peeled) < 0)
return -1;
head->name = git_buf_detach(&buf);
- pkt = git__malloc(sizeof(git_pkt_ref));
- GITERR_CHECK_ALLOC(pkt);
- pkt->type = GIT_PKT_REF;
-
if (git_tag_peel(&target, (git_tag *) obj) < 0)
goto on_error;
git_oid_cpy(&head->oid, git_object_id(target));
git_object_free(obj);
git_object_free(target);
- memcpy(&pkt->head, head, sizeof(git_remote_head));
- git__free(head);
- if (git_vector_insert(&transport->refs, pkt) < 0)
+ if (git_vector_insert(&t->refs, head) < 0)
return -1;
return 0;
@@ -107,12 +106,11 @@ static int store_refs(transport_local *t)
{
unsigned int i;
git_strarray ref_names = {0};
- git_transport *transport = (git_transport *) t;
assert(t);
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0)
+ git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
goto on_error;
/* Sort the references first */
@@ -131,7 +129,7 @@ static int store_refs(transport_local *t)
return 0;
on_error:
- git_vector_free(&transport->refs);
+ git_vector_free(&t->refs);
git_strarray_free(&ref_names);
return -1;
}
@@ -140,7 +138,11 @@ on_error:
* 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,
+ int direction, int flags)
{
git_repository *repo;
int error;
@@ -148,18 +150,23 @@ 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);
+
+ 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);
@@ -174,26 +181,194 @@ 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_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
{
- GIT_UNUSED(transport);
- GIT_UNUSED(repo);
- GIT_UNUSED(wants);
+ transport_local *t = (transport_local *)transport;
+ unsigned int i;
+ git_remote_head *head = NULL;
- giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");
- return -1;
+ if (!t->connected) {
+ giterr_set(GITERR_NET, "The transport is not connected");
+ 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_remote_head * const *refs,
+ size_t count)
+{
+ transport_local *t = (transport_local*)transport;
+ git_remote_head *rhead;
+ unsigned int i;
+
+ 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;
+}
+
+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_oid(commit);
+ git_commit_free(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)
+ goto cleanup;
+ }
+ }
+
+ 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, int *connected)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *connected = t->connected;
+
+ return 0;
+}
+
+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;
- t->parent.connected = 0;
+ t->connected = 0;
git_repository_free(t->repo);
t->repo = NULL;
@@ -204,18 +379,18 @@ static void local_free(git_transport *transport)
{
unsigned int i;
transport_local *t = (transport_local *) transport;
- git_vector *vec = &transport->refs;
- git_pkt_ref *pkt;
+ git_vector *vec = &t->refs;
+ git_remote_head *head;
assert(transport);
- git_vector_foreach (vec, i, pkt) {
- git__free(pkt->head.name);
- git__free(pkt);
+ git_vector_foreach (vec, i, head) {
+ git__free(head->name);
+ git__free(head);
}
git_vector_free(vec);
- git__free(t->parent.url);
+ git__free(t->url);
git__free(t);
}
@@ -223,20 +398,26 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out)
+int git_transport_local(git_transport **out, void *param)
{
transport_local *t;
+ GIT_UNUSED(param);
+
t = git__malloc(sizeof(transport_local));
GITERR_CHECK_ALLOC(t);
memset(t, 0x0, sizeof(transport_local));
-
- t->parent.own_logic = 1;
+
t->parent.connect = local_connect;
t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
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;
*out = (git_transport *) t;
diff --git a/src/transports/smart.c b/src/transports/smart.c
new file mode 100644
index 000000000..8f9715a3f
--- /dev/null
+++ b/src/transports/smart.c
@@ -0,0 +1,284 @@
+/*
+ * 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 "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(void) git_smart__reset_stream(transport_smart *t)
+{
+ if (t->current_stream) {
+ t->current_stream->free(t->current_stream);
+ t->current_stream = NULL;
+ }
+}
+
+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,
+ int direction,
+ int flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+ git_pkt *pkt;
+
+ git_smart__reset_stream(t);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+
+ t->direction = direction;
+ t->flags = flags;
+ t->cred_acquire_cb = cred_acquire_cb;
+
+ if (GIT_DIR_FETCH == direction)
+ {
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 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;
+
+ if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
+ return -1;
+
+ if (t->rpc)
+ git_smart__reset_stream(t);
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
+ }
+ else
+ {
+ giterr_set(GITERR_NET, "Push not implemented");
+ return -1;
+ }
+
+ return -1;
+}
+
+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);
+
+ if (GIT_DIR_FETCH == t->direction) {
+ 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;
+ }
+
+ giterr_set(GITERR_NET, "Push not implemented");
+ return -1;
+}
+
+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, int *connected)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *connected = t->connected;
+
+ return 0;
+}
+
+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_smart__reset_stream(t);
+
+ t->connected = 0;
+
+ return 0;
+}
+
+static void git_smart__free(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *refs = &t->refs;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+
+ /* 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_vector_foreach(common, i, p) {
+ git_pkt_free(p);
+ }
+ git_vector_free(common);
+
+ git__free(t->url);
+ git__free(t);
+}
+
+int git_transport_smart(git_transport **out, void *param)
+{
+ transport_smart *t;
+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
+
+ if (!param)
+ return -1;
+
+ t = (transport_smart *)git__calloc(sizeof(transport_smart), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ 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.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->rpc = definition->rpc;
+
+ if (git_vector_init(&t->refs, 16, NULL) < 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..046bc89a4
--- /dev/null
+++ b/src/transports/smart.h
@@ -0,0 +1,150 @@
+/*
+ * 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 "git2.h"
+#include "vector.h"
+#include "netops.h"
+#include "buffer.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"
+
+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,
+};
+
+/* 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;
+ char error[GIT_FLEX_ARRAY];
+} git_pkt_err;
+
+typedef struct transport_smart_caps {
+ int common:1,
+ ofs_delta:1,
+ multi_ack: 1,
+ side_band:1,
+ side_band_64k:1,
+ include_tag:1;
+} transport_smart_caps;
+
+typedef void (*packetsize_cb)(int received, void *payload);
+
+typedef struct {
+ git_transport parent;
+ char *url;
+ git_cred_acquire_cb cred_acquire_cb;
+ 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__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);
+
+/* 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 91f9b65c2..26fc0e4aa 100644
--- a/src/pkt.c
+++ b/src/transports/smart_pkt.c
@@ -12,12 +12,11 @@
#include "git2/refs.h"
#include "git2/revwalk.h"
-#include "pkt.h"
+#include "smart.h"
#include "util.h"
#include "netops.h"
#include "posix.h"
#include "buffer.h"
-#include "protocol.h"
#include <ctype.h>
@@ -335,7 +334,7 @@ int git_pkt_buffer_flush(git_buf *buf)
return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
}
-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)
{
git_buf str = GIT_BUF_INIT;
char oid[GIT_OID_HEXSZ +1] = {0};
@@ -376,28 +375,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..e24eb2783
--- /dev/null
+++ b/src/transports/smart_protocol.c
@@ -0,0 +1,481 @@
+/*
+ * 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 "smart.h"
+#include "refs.h"
+#include "repository.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;
+
+ 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;
+ }
+
+ /* 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_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;
+}
+
+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 (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ return -1;
+
+ if (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 ((error = git_revwalk_next(&oid, walk)) == 0) {
+ 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))
+ goto on_error;
+
+ if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0)
+ goto on_error;
+
+ git_buf_clear(&data);
+ if (t->caps.multi_ack) {
+ if (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 {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ goto on_error;
+ }
+ }
+ }
+
+ if (t->common.length > 0)
+ break;
+
+ if (i % 20 == 0 && t->rpc) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ git_pkt_buffer_have(&pkt->oid, &data);
+ }
+
+ if (git_buf_oom(&data))
+ goto on_error;
+ }
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ 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 (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ git_pkt_buffer_have(&pkt->oid, &data);
+ }
+
+ if (git_buf_oom(&data))
+ goto on_error;
+ }
+
+ git_pkt_buffer_done(&data);
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+ if (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 != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ return -1;
+ }
+ } else {
+ git_pkt_ack *pkt;
+ do {
+ if (recv_pkt((git_pkt **)&pkt, buf) < 0)
+ return -1;
+
+ 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(int 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;
+ if (writepack->add(writepack, p->data, p->len, stats) < 0)
+ goto on_error;
+
+ git__free(pkt);
+ } 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:
+ 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;
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
new file mode 100644
index 000000000..df6cd87ec
--- /dev/null
+++ b/src/transports/winhttp.c
@@ -0,0 +1,634 @@
+/*
+ * 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.
+ */
+
+#ifdef GIT_WINHTTP
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "posix.h"
+#include "netops.h"
+#include "smart.h"
+
+#include <winhttp.h>
+#pragma comment(lib, "winhttp")
+
+#define WIDEN2(s) L ## s
+#define WIDEN(s) WIDEN2(s)
+
+#define MAX_CONTENT_TYPE_LEN 100
+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+
+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 wchar_t *get_verb = L"GET";
+static const wchar_t *post_verb = L"POST";
+static const wchar_t *basic_authtype = L"Basic";
+static const wchar_t *pragma_nocache = L"Pragma: no-cache";
+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;
+ unsigned sent_request : 1,
+ received_response : 1;
+} winhttp_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ const char *path;
+ char *host;
+ char *port;
+ git_cred *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;
+
+ 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 = (wchar_t *)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;
+ wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
+ wchar_t *types[] = { L"*/*", NULL };
+ BOOL peerdist = FALSE;
+
+ /* Prepare URL */
+ git_buf_printf(&buf, "%s%s", t->path, s->service_url);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
+
+ /* Establish request */
+ s->request = WinHttpOpenRequest(
+ t->connection,
+ s->verb,
+ url,
+ 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;
+ }
+
+ /* 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;
+
+ /* We've done everything up to calling WinHttpSendRequest. */
+
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ return -1;
+}
+
+static int parse_unauthorized_response(
+ HINTERNET request,
+ int *allowed_types,
+ int *auth_mechanism)
+{
+ DWORD index, buf_size, last_error;
+ int error = 0;
+ wchar_t *buf = NULL;
+
+ *allowed_types = 0;
+
+ for (index = 0; ; index++) {
+ /* Make a first call to ask for the size of the buffer to allocate
+ * to hold the WWW-Authenticate header */
+ if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
+ WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER,
+ &buf_size, &index))
+ {
+ last_error = GetLastError();
+
+ if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) {
+ /* End of enumeration */
+ break;
+ } else if (ERROR_INSUFFICIENT_BUFFER == last_error) {
+ git__free(buf);
+ buf = (wchar_t *)git__malloc(buf_size);
+
+ if (!buf) {
+ error = -1;
+ break;
+ }
+ } else {
+ giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
+ error = -1;
+ break;
+ }
+ }
+
+ /* Actually receive the data into our now-allocated buffer */
+ if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
+ WINHTTP_HEADER_NAME_BY_INDEX, buf,
+ &buf_size, &index)) {
+ giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
+ error = -1;
+ break;
+ }
+
+ if (!wcsncmp(buf, basic_authtype, 5) &&
+ (buf[5] == L'\0' || buf[5] == L' ')) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
+ }
+
+ git__free(buf);
+ return error;
+}
+
+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;
+
+replay:
+ /* Connect if necessary */
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->sent_request &&
+ !WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ 0, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+
+ if (!s->received_response) {
+ DWORD status_code, status_code_length, content_type_length;
+ 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 (!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 retreive status code");
+ return -1;
+ }
+
+ /* 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, allowed_types) < 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,
+ 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(
+ 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;
+
+ /* Since we have to write the Content-Length header up front, we're
+ * basically limited to a single call to write() per request. */
+ if (!s->sent_request &&
+ !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 send request");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static void winhttp_stream_free(git_smart_subtransport_stream *stream)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+
+ if (s->request) {
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ }
+
+ git__free(s);
+}
+
+static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = (winhttp_stream *)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;
+ s->parent.free = winhttp_stream_free;
+
+ *stream = (git_smart_subtransport_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_host_and_port(&t->host, &t->port, 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,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!t->connection &&
+ winhttp_connect(t, url) < 0)
+ return -1;
+
+ if (winhttp_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (winhttp_stream *)*stream;
+
+ 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,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!t->connection &&
+ winhttp_connect(t, url) < 0)
+ return -1;
+
+ if (winhttp_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (winhttp_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)smart_transport;
+
+ if (!stream)
+ return -1;
+
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return winhttp_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return winhttp_uploadpack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static void winhttp_free(git_smart_subtransport *smart_transport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *) smart_transport;
+
+ git__free(t->host);
+ git__free(t->port);
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->connection) {
+ WinHttpCloseHandle(t->connection);
+ t->connection = NULL;
+ }
+
+ if (t->session) {
+ WinHttpCloseHandle(t->session);
+ t->session = NULL;
+ }
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
+{
+ winhttp_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = winhttp_action;
+ t->parent.free = winhttp_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_WINHTTP */
diff --git a/src/tree.c b/src/tree.c
index 8d3f2665c..6f9838880 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -26,7 +26,9 @@ static bool valid_filemode(const int filemode)
static int valid_entry_name(const char *filename)
{
- return *filename != '\0' && strchr(filename, '/') == NULL;
+ return *filename != '\0' && strchr(filename, '/') == NULL &&
+ strcmp(filename, "..") != 0 && strcmp(filename, ".") != 0 &&
+ strcmp(filename, ".git") != 0;
}
static int entry_sort_cmp(const void *a, const void *b)
@@ -353,7 +355,7 @@ static unsigned int find_next_dir(const char *dirname, git_index *index, unsigne
dirlen = strlen(dirname);
for (i = start; i < entries; ++i) {
- git_index_entry *entry = git_index_get(index, i);
+ 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] != '/')) {
@@ -372,6 +374,9 @@ static int append_entry(
{
git_tree_entry *entry;
+ if (!valid_entry_name(filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry");
+
entry = alloc_entry(filename);
GITERR_CHECK_ALLOC(entry);
@@ -415,7 +420,7 @@ 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);
+ git_index_entry *entry = git_index_get_byindex(index, i);
char *filename, *next_slash;
/*
@@ -491,16 +496,17 @@ on_error:
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;
- 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);
diff --git a/src/tree.h b/src/tree.h
index 24b517ce3..b67c55202 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -47,6 +47,12 @@ 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
*/
diff --git a/src/unix/posix.h b/src/unix/posix.h
index bcd800301..6980c36f1 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -21,5 +21,9 @@
#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/util.c b/src/util.c
index 0a82ccea6..3a08d4554 100644
--- a/src/util.c
+++ b/src/util.c
@@ -174,6 +174,36 @@ 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)
+{
+ while (sz && *a && *b && tolower(*a) == tolower(*b))
+ --sz, ++a, ++b;
+ return !sz ? 0 : (tolower(*a) - tolower(*b));
+}
+
void git__strntolower(char *str, size_t len)
{
size_t i;
@@ -432,12 +462,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;
@@ -445,7 +471,8 @@ 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;
}
diff --git a/src/util.h b/src/util.h
index 4f83d3bc1..cb1c4fdc2 100644
--- a/src/util.h
+++ b/src/util.h
@@ -42,12 +42,11 @@ 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) {
@@ -55,7 +54,9 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
return NULL;
}
- memcpy(ptr, str, length);
+ if (length)
+ memcpy(ptr, str, length);
+
ptr[length] = '\0';
return ptr;
@@ -80,6 +81,11 @@ 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);
@@ -128,6 +134,11 @@ extern int git__bsearch(
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;
diff --git a/src/vector.c b/src/vector.c
index c6a644cc3..4763792f5 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -223,6 +223,20 @@ void git_vector_uniq(git_vector *v)
v->length -= j - i - 1;
}
+void git_vector_remove_matching(git_vector *v, int (*match)(git_vector *v, size_t idx))
+{
+ unsigned int 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);
@@ -241,3 +255,33 @@ void git_vector_swap(git_vector *a, git_vector *b)
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;
+
+ while (new_length >= v->_alloc_size)
+ if (resize_vector(v) < 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 49ba754f0..6d820b8fc 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -29,17 +29,26 @@ void git_vector_swap(git_vector *a, git_vector *b);
void git_vector_sort(git_vector *v);
+/** Linear search for matching entry using internal comparison function */
int git_vector_search(git_vector *v, const void *entry);
+
+/** Linear search for matching entry using explicit comparison function */
int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
int git_vector_bsearch3(
unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+/** Binary search for matching entry using internal comparison function */
GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key)
{
return git_vector_bsearch3(NULL, v, v->_cmp, key);
}
+/** Binary search for matching entry using explicit comparison function */
GIT_INLINE(int) git_vector_bsearch2(
git_vector *v, git_vector_cmp cmp, const void *key)
{
@@ -75,5 +84,9 @@ int git_vector_insert_sorted(git_vector *v, void *element,
int git_vector_remove(git_vector *v, unsigned int 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)(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/posix.h b/src/win32/posix.h
index 80dcca5c1..ee61c2d05 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -48,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 649fe9b95..0efcaf597 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -11,7 +11,7 @@
#include <errno.h>
#include <io.h>
#include <fcntl.h>
-
+#include <ws2tcpip.h>
int p_unlink(const char *path)
{
@@ -52,17 +52,33 @@ 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[GIT_WIN_PATH];
+ wchar_t fbuf[GIT_WIN_PATH], lastch;
DWORD last_error;
+ int flen;
- git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name);
+ 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
@@ -84,48 +100,45 @@ static int do_lstat(const char *file_name, struct stat *buf)
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+
return 0;
}
- last_error = GetLastError();
- if (last_error == ERROR_FILE_NOT_FOUND)
- errno = ENOENT;
- else if (last_error == ERROR_PATH_NOT_FOUND)
- errno = ENOTDIR;
+ errno = ENOENT;
+
+ /* 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;
+ }
+ }
+ }
return -1;
}
-int p_lstat(const char *file_name, struct stat *buf)
+int p_lstat(const char *filename, struct stat *buf)
{
- int error;
- size_t namelen;
- char *alt_name;
-
- if (do_lstat(file_name, buf) == 0)
- return 0;
-
- /* 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;
-
- while (namelen && file_name[namelen-1] == '/')
- --namelen;
-
- if (!namelen)
- return -1;
-
- alt_name = git__strndup(file_name, namelen);
- if (!alt_name)
- return -1;
-
- error = do_lstat(alt_name, 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)
@@ -268,7 +281,7 @@ 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)
@@ -301,46 +314,42 @@ int p_hide_directory__w32(const char *path)
char *p_realpath(const char *orig_path, char *buffer)
{
- int ret, buffer_sz = 0;
+ int ret;
wchar_t orig_path_w[GIT_WIN_PATH];
wchar_t buffer_w[GIT_WIN_PATH];
git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path);
+
+ /* 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_WIN_PATH) {
+ if (ret == 0 || ret > GIT_WIN_PATH)
+ buffer = NULL;
+
+ else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
buffer = NULL;
- goto done;
+ 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:
if (buffer)
git_path_mkposix(buffer);
@@ -504,3 +513,45 @@ int p_gettimeofday(struct timeval *tv, struct timezone *tz)
return 0;
}
+
+int p_inet_pton(int af, const char* src, void* dst)
+{
+ union {
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin;
+ } sa;
+ size_t srcsize;
+
+ switch(af)
+ {
+ case AF_INET:
+ sa.sin.sin_family = AF_INET;
+ srcsize = sizeof (sa.sin);
+ break;
+ case AF_INET6:
+ sa.sin6.sin6_family = AF_INET6;
+ srcsize = sizeof (sa.sin6);
+ break;
+ default:
+ errno = WSAEPFNOSUPPORT;
+ return -1;
+ }
+
+ if (WSAStringToAddress(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/utf-conv.c b/src/win32/utf-conv.c
index 396af7cad..0659a5d1c 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
}
#endif
-void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
{
- MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
+ return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
}
-void git__utf16_to_8(char *out, const wchar_t *input)
+int git__utf16_to_8(char *out, const wchar_t *input)
{
- WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL);
+ 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 3bd1549bc..f62863a76 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -12,8 +12,8 @@
#define GIT_WIN_PATH (260 + 1)
-void git__utf8_to_16(wchar_t *dest, size_t length, const char *src);
-void git__utf16_to_8(char *dest, const wchar_t *src);
+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