diff options
44 files changed, 1190 insertions, 139 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b23e07d93..44fa00d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ v0.21 + 1 * Use a map for the treebuilder, making insertion O(1) +* Introduce reference transactions, which allow multiple references to + be locked at the same time and updates be queued. This also allows + us to safely update a reflog with arbitrary contents, as we need to + do for stash. + * LF -> CRLF filter refuses to handle mixed-EOL files * LF -> CRLF filter now runs when * text = auto (with Git for Windows 1.9.4) @@ -56,5 +61,14 @@ v0.21 + 1 * Add support for refspecs with the asterisk in the middle of a pattern. +* Fetching now performs opportunistic updates. To achieve this, we + introduce a difference between active and passive refspecs, which + make git_remote_download and git_remote_fetch to take a list of + resfpecs to be the active list, similarly to how git fetch accepts a + list on the command-line. + * Introduce git_merge_bases() and the git_oidarray type to expose all merge bases between two commits. + +* Introduce git_merge_bases_many() to expose all merge bases between + multiple commits. @@ -212,7 +212,7 @@ workflow, the libgit2 [coding conventions](CONVENTIONS.md), and out list of License ================================== -`libgit2` is under GPL2 **with linking exemption**. This means you can link to +`libgit2` is under GPL2 **with linking exception**. This means you can link to and use the library from any program, proprietary or open source; paid or gratis. However, you cannot modify libgit2 and distribute it without supplying the source. diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 8d882095b..ab78fc9bd 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -36,7 +36,7 @@ static void *download(void *ptr) // Download the packfile and index it. This function updates the // amount of received data and the indexer stats which lets you // inform the user about progress. - if (git_remote_download(data->remote) < 0) { + if (git_remote_download(data->remote, NULL) < 0) { data->ret = -1; goto exit; } diff --git a/include/git2.h b/include/git2.h index 6713b4961..baa7fcaf8 100644 --- a/include/git2.h +++ b/include/git2.h @@ -33,6 +33,7 @@ #include "git2/notes.h" #include "git2/object.h" #include "git2/odb.h" +#include "git2/odb_backend.h" #include "git2/oid.h" #include "git2/pack.h" #include "git2/patch.h" diff --git a/include/git2/merge.h b/include/git2/merge.h index bd5ebc1bd..ed1b9a30f 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -352,6 +352,21 @@ GIT_EXTERN(int) git_merge_base_many( const git_oid input_array[]); /** + * Find all merge bases given a list of commits + * + * @param out array in which to store the resulting ids + * @param repo the repository where the commits exist + * @param length The number of commits in the provided `input_array` + * @param input_array oids of the commits + * @return Zero on success; GIT_ENOTFOUND or -1 on failure. + */ +GIT_EXTERN(int) git_merge_bases_many( + git_oidarray *out, + git_repository *repo, + size_t length, + const git_oid input_array[]); + +/** * Find a merge base in preparation for an octopus merge * * @param out the OID of a merge base considering all the commits diff --git a/include/git2/reflog.h b/include/git2/reflog.h index ac42a231c..c949a28f0 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -102,7 +102,7 @@ GIT_EXTERN(size_t) git_reflog_entrycount(git_reflog *reflog); * equal to 0 (zero) and less than `git_reflog_entrycount()`. * @return the entry; NULL if not found */ -GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx); +GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(const git_reflog *reflog, size_t idx); /** * Remove an entry from the reflog by its index diff --git a/include/git2/remote.h b/include/git2/remote.h index 055f5e517..b714d3469 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -306,9 +306,12 @@ GIT_EXTERN(int) git_remote_ls(const git_remote_head ***out, size_t *size, git_r * The .idx file will be created and both it and the packfile with be * renamed to their final name. * + * @param remote the remote + * @param refspecs the refspecs to use for this negotiation and + * download. Use NULL to use the base refspecs * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_download(git_remote *remote); +GIT_EXTERN(int) git_remote_download(git_remote *remote, const git_strarray *refspecs); /** * Check whether the remote is connected @@ -373,6 +376,8 @@ GIT_EXTERN(int) git_remote_update_tips( * disconnect and update the remote-tracking branches. * * @param remote the remote to fetch from + * @param refspecs the refspecs to use for this fetch. Pass NULL to + * use the base refspecs. * @param signature The identity to use when updating reflogs * @param reflog_message The message to insert into the reflogs. If NULL, the * default is "fetch" @@ -380,6 +385,7 @@ GIT_EXTERN(int) git_remote_update_tips( */ GIT_EXTERN(int) git_remote_fetch( git_remote *remote, + const git_strarray *refspecs, const git_signature *signature, const char *reflog_message); diff --git a/src/hashsig.h b/include/git2/sys/hashsig.h index 8c920cbf1..cd735e1b5 100644 --- a/src/hashsig.h +++ b/include/git2/sys/hashsig.h @@ -4,10 +4,12 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hashsig_h__ -#define INCLUDE_hashsig_h__ +#ifndef INCLUDE_sys_hashsig_h__ +#define INCLUDE_sys_hashsig_h__ -#include "common.h" +#include "git2/common.h" + +GIT_BEGIN_DECL /** * Similarity signature of line hashes for a buffer @@ -35,7 +37,7 @@ typedef enum { * @param buflen The length of the data at `buf` * @param generate_pairwise_hashes Should pairwise runs be hashed */ -extern int git_hashsig_create( +GIT_EXTERN(int) git_hashsig_create( git_hashsig **out, const char *buf, size_t buflen, @@ -50,7 +52,7 @@ extern int git_hashsig_create( * This will return an error if the file doesn't contain enough data to * compute a valid signature. */ -extern int git_hashsig_create_fromfile( +GIT_EXTERN(int) git_hashsig_create_fromfile( git_hashsig **out, const char *path, git_hashsig_option_t opts); @@ -58,15 +60,17 @@ extern int git_hashsig_create_fromfile( /** * Release memory for a content similarity signature */ -extern void git_hashsig_free(git_hashsig *sig); +GIT_EXTERN(void) git_hashsig_free(git_hashsig *sig); /** * Measure similarity between two files * * @return <0 for error, [0 to 100] as similarity score */ -extern int git_hashsig_compare( +GIT_EXTERN(int) git_hashsig_compare( const git_hashsig *a, const git_hashsig *b); +GIT_END_DECL + #endif diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 3b216a287..d943e550f 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -153,6 +153,19 @@ struct git_refdb_backend { * Remove a reflog. */ int (*reflog_delete)(git_refdb_backend *backend, const char *name); + + /** + * Lock a reference. The opaque parameter will be passed to the unlock function + */ + int (*lock)(void **payload_out, git_refdb_backend *backend, const char *refname); + + /** + * Unlock a reference. Only one of target or symbolic_target + * will be set. success indicates whether to update the + * reference or discard the lock (if it's false) + */ + int (*unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog, + const git_reference *ref, const git_signature *sig, const char *message); }; #define GIT_REFDB_BACKEND_VERSION 1 diff --git a/include/git2/transaction.h b/include/git2/transaction.h new file mode 100644 index 000000000..64abb0c69 --- /dev/null +++ b/include/git2/transaction.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_transaction_h__ +#define INCLUDE_git_transaction_h__ + +#include "common.h" +GIT_BEGIN_DECL + +/** + * Create a new transaction object + * + * This does not lock anything, but sets up the transaction object to + * know from which repository to lock. + * + * @param out the resulting transaction + * @param repo the repository in which to lock + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transaction_new(git_transaction **out, git_repository *repo); + +/** + * Lock a reference + * + * Lock the specified reference. This is the first step to updating a + * reference. + * + * @param tx the transaction + * @param refname the reference to lock + * @return 0 or an error message + */ +GIT_EXTERN(int) git_transaction_lock_ref(git_transaction *tx, const char *refname); + +/** + * Set the target of a reference + * + * Set the target of the specified reference. This reference must be + * locked. + * + * @param tx the transaction + * @param refname reference to update + * @param target target to set the reference to + * @param sig signature to use in the reflog; pass NULL to read the identity from the config + * @param msg message to use in the reflog + * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code + */ +GIT_EXTERN(int) git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg); + +/** + * Set the target of a reference + * + * Set the target of the specified reference. This reference must be + * locked. + * + * @param tx the transaction + * @param refname reference to update + * @param target target to set the reference to + * @param sig signature to use in the reflog; pass NULL to read the identity from the config + * @param msg message to use in the reflog + * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code + */ +GIT_EXTERN(int) git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg); + +/** + * Set the reflog of a reference + * + * Set the specified reference's reflog. If this is combined with + * setting the target, that update won't be written to the reflog. + * + * @param tx the transaction + * @param refname the reference whose reflog to set + * @param reflog the reflog as it should be written out + * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code + */ +GIT_EXTERN(int) git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog); + +/** + * Remove a reference + * + * @param tx the transaction + * @param refname the reference to remove + * @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code + */ +GIT_EXTERN(int) git_transaction_remove(git_transaction *tx, const char *refname); + +/** + * Commit the changes from the transaction + * + * Perform the changes that have been queued. The updates will be made + * one by one, and the first failure will stop the processing. + * + * @param tx the transaction + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transaction_commit(git_transaction *tx); + +/** + * Free the resources allocated by this transaction + * + * If any references remain locked, they will be unlocked without any + * changes made to them. + * + * @param tx the transaction + */ +GIT_EXTERN(void) git_transaction_free(git_transaction *tx); + +GIT_END_DECL +#endif diff --git a/include/git2/types.h b/include/git2/types.h index 7ee7cc344..14b7071d2 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -171,6 +171,9 @@ typedef struct git_reference git_reference; /** Iterator for references */ typedef struct git_reference_iterator git_reference_iterator; +/** Transactional interface to references */ +typedef struct git_transaction git_transaction; + /** Merge heads, the input to merge */ typedef struct git_merge_head git_merge_head; diff --git a/src/clone.c b/src/clone.c index 43b839003..f18f07611 100644 --- a/src/clone.c +++ b/src/clone.c @@ -358,7 +358,7 @@ static int clone_into(git_repository *repo, git_remote *_remote, const git_check git_remote_set_update_fetchhead(remote, 0); git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) + if ((error = git_remote_fetch(remote, NULL, signature, git_buf_cstr(&reflog_message))) != 0) goto cleanup; error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); @@ -553,7 +553,7 @@ static int clone_local_into(git_repository *repo, git_remote *remote, const git_ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) + if ((error = git_remote_fetch(remote, NULL, signature, git_buf_cstr(&reflog_message))) != 0) goto cleanup; error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); diff --git a/src/config_file.c b/src/config_file.c index 7106f18db..8f55c42f3 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1163,7 +1163,7 @@ static int strip_comments(char *line, int in_quotes) } /* skip any space at the end */ - if (ptr > line && git__isspace(ptr[-1])) { + while (ptr > line && git__isspace(ptr[-1])) { ptr--; } ptr[0] = '\0'; diff --git a/src/diff_tform.c b/src/diff_tform.c index 423a0ca33..9ebce06a0 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -8,9 +8,9 @@ #include "git2/config.h" #include "git2/blob.h" +#include "git2/sys/hashsig.h" #include "diff.h" -#include "hashsig.h" #include "path.h" #include "fileops.h" #include "config.h" diff --git a/src/filter.c b/src/filter.c index b9e4f9ec8..b5a8bdd66 100644 --- a/src/filter.c +++ b/src/filter.c @@ -38,7 +38,7 @@ struct git_filter_list { }; typedef struct { - const char *filter_name; + char *filter_name; git_filter *filter; int priority; int initialized; @@ -75,6 +75,7 @@ static void filter_registry_shutdown(void) fdef->initialized = false; } + git__free(fdef->filter_name); git__free(fdef->attrdata); git__free(fdef); } @@ -230,6 +231,8 @@ int git_filter_register( size_t nattr = 0, nmatch = 0; git_buf attrs = GIT_BUF_INIT; + assert(name && filter); + if (filter_registry_initialize() < 0) return -1; @@ -246,7 +249,9 @@ int git_filter_register( sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1); GITERR_CHECK_ALLOC(fdef); - fdef->filter_name = name; + fdef->filter_name = git__strdup(name); + GITERR_CHECK_ALLOC(fdef->filter_name); + fdef->filter = filter; fdef->priority = priority; fdef->nattrs = nattr; @@ -256,6 +261,7 @@ int git_filter_register( filter_def_set_attrs(fdef); if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) { + git__free(fdef->filter_name); git__free(fdef->attrdata); git__free(fdef); return -1; @@ -270,6 +276,8 @@ int git_filter_unregister(const char *name) size_t pos; git_filter_def *fdef; + assert(name); + /* cannot unregister default filters */ if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name); @@ -288,6 +296,7 @@ int git_filter_unregister(const char *name) fdef->initialized = false; } + git__free(fdef->filter_name); git__free(fdef->attrdata); git__free(fdef); diff --git a/src/hashsig.c b/src/hashsig.c index 109f966ba..a6d5f2041 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -4,7 +4,7 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include "hashsig.h" +#include "git2/sys/hashsig.h" #include "fileops.h" #include "util.h" diff --git a/src/merge.c b/src/merge.c index ddeea8752..8252f6767 100644 --- a/src/merge.c +++ b/src/merge.c @@ -22,7 +22,6 @@ #include "tree.h" #include "merge_file.h" #include "blob.h" -#include "hashsig.h" #include "oid.h" #include "index.h" #include "filebuf.h" @@ -42,6 +41,7 @@ #include "git2/tree.h" #include "git2/oidarray.h" #include "git2/sys/index.h" +#include "git2/sys/hashsig.h" #define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) @@ -62,16 +62,14 @@ struct merge_diff_df_data { /* Merge base computation */ -int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) { - git_revwalk *walk; + git_revwalk *walk = NULL; git_vector list; git_commit_list *result = NULL; + git_commit_list_node *commit; int error = -1; unsigned int i; - git_commit_list_node *commit; - - assert(out && repo && input_array); if (length < 2) { giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length); @@ -82,37 +80,92 @@ int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const return -1; if (git_revwalk_new(&walk, repo) < 0) - goto cleanup; + goto on_error; for (i = 1; i < length; i++) { commit = git_revwalk__commit_lookup(walk, &input_array[i]); if (commit == NULL) - goto cleanup; + goto on_error; git_vector_insert(&list, commit); } commit = git_revwalk__commit_lookup(walk, &input_array[0]); if (commit == NULL) - goto cleanup; + goto on_error; if (git_merge__bases_many(&result, walk, commit, &list) < 0) - goto cleanup; + goto on_error; if (!result) { giterr_set(GITERR_MERGE, "No merge base found"); error = GIT_ENOTFOUND; - goto cleanup; + goto on_error; } + *out = result; + *walk_out = walk; + + git_vector_free(&list); + return 0; + +on_error: + git_vector_free(&list); + git_revwalk_free(walk); + return error; +} + +int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *result = NULL; + int error = 0; + + assert(out && repo && input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + git_oid_cpy(out, &result->item->oid); - error = 0; + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; +} + +int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *list, *result = NULL; + int error = 0; + git_array_oid_t array; + + assert(out && repo && input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + + git_array_init(array); + + list = result; + while (list) { + git_oid *id = git_array_alloc(array); + if (id == NULL) { + error = -1; + goto cleanup; + } + + git_oid_cpy(id, &list->item->oid); + list = list->next; + } + + git_oidarray__from_array(out, &array); cleanup: git_commit_list_free(&result); git_revwalk_free(walk); - git_vector_free(&list); + return error; } diff --git a/src/path.c b/src/path.c index 67133f97e..21b6e18d6 100644 --- a/src/path.c +++ b/src/path.c @@ -768,7 +768,7 @@ int git_path_cmp( int git_path_make_relative(git_buf *path, const char *parent) { const char *p, *q, *p_dirsep, *q_dirsep; - size_t plen = path->size, newlen, depth = 1, i; + size_t plen = path->size, newlen, depth = 1, i, offset; for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { if (*p == '/' && *q == '/') { @@ -808,8 +808,11 @@ int git_path_make_relative(git_buf *path, const char *parent) newlen = (depth * 3) + plen; + /* save the offset as we might realllocate the pointer */ + offset = p - path->ptr; if (git_buf_try_grow(path, newlen + 1, 1, 0) < 0) return -1; + p = path->ptr + offset; memmove(path->ptr + (depth * 3), p, plen + 1); diff --git a/src/refdb.c b/src/refdb.c index 69bf74734..16fb519a6 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -242,3 +242,22 @@ int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version) backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT); return 0; } + +int git_refdb_lock(void **payload, git_refdb *db, const char *refname) +{ + assert(payload && db && refname); + + if (!db->backend->lock) { + giterr_set(GITERR_REFERENCE, "backend does not support locking"); + return -1; + } + + return db->backend->lock(payload, db->backend, refname); +} + +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) +{ + assert(db); + + return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message); +} diff --git a/src/refdb.h b/src/refdb.h index cbad86faf..4ee3b8065 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -51,6 +51,7 @@ int git_refdb_reflog_write(git_reflog *reflog); int git_refdb_has_log(git_refdb *db, const char *refname); int git_refdb_ensure_log(git_refdb *refdb, const char *refname); - +int git_refdb_lock(void **payload, git_refdb *db, const char *refname); +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 0e36ca8ac..f39ba4f9c 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -745,6 +745,57 @@ static int loose_commit(git_filebuf *file, const git_reference *ref) return git_filebuf_commit(file); } +static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname) +{ + int error; + git_filebuf *lock; + refdb_fs_backend *backend = (refdb_fs_backend *) _backend; + + lock = git__calloc(1, sizeof(git_filebuf)); + GITERR_CHECK_ALLOC(lock); + + if ((error = loose_lock(lock, backend, refname)) < 0) { + git__free(lock); + return error; + } + + *out = lock; + return 0; +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_signature *who, + const char *message, + const git_oid *old_id, + const char *old_target); + +static int refdb_fs_backend__delete_tail( + git_refdb_backend *_backend, + git_filebuf *file, + const char *ref_name, + const git_oid *old_id, const char *old_target); + +static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog, + const git_reference *ref, const git_signature *sig, const char *message) +{ + git_filebuf *lock = (git_filebuf *) payload; + int error = 0; + + if (success == 2) + error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL); + else if (success) + error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL); + else + git_filebuf_cleanup(lock); + + git__free(lock); + return error; +} + /* * Find out what object this reference resolves to. * @@ -1063,7 +1114,6 @@ cleanup: return error; } - static int refdb_fs_backend__write( git_refdb_backend *_backend, const git_reference *ref, @@ -1075,9 +1125,7 @@ static int refdb_fs_backend__write( { refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_filebuf file = GIT_FILEBUF_INIT; - int error = 0, cmp = 0, should_write; - const char *new_target = NULL; - const git_oid *new_id = NULL; + int error = 0; assert(backend); @@ -1089,6 +1137,24 @@ static int refdb_fs_backend__write( if ((error = loose_lock(&file, backend, ref->name)) < 0) return error; + return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target); +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_signature *who, + const char *message, + const git_oid *old_id, + const char *old_target) +{ + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + int error = 0, cmp = 0, should_write; + const char *new_target = NULL; + const git_oid *new_id = NULL; + if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) goto on_error; @@ -1113,20 +1179,22 @@ static int refdb_fs_backend__write( goto on_error; /* not really error */ } - if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0) - goto on_error; - - if (should_write) { - if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) - goto on_error; - if ((error = maybe_append_head(backend, ref, who, message)) < 0) + if (update_reflog) { + if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0) goto on_error; + + if (should_write) { + if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) + goto on_error; + if ((error = maybe_append_head(backend, ref, who, message)) < 0) + goto on_error; + } } - return loose_commit(&file, ref); + return loose_commit(file, ref); on_error: - git_filebuf_cleanup(&file); + git_filebuf_cleanup(file); return error; } @@ -1136,17 +1204,29 @@ static int refdb_fs_backend__delete( const git_oid *old_id, const char *old_target) { refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - git_buf loose_path = GIT_BUF_INIT; - size_t pack_pos; git_filebuf file = GIT_FILEBUF_INIT; - int error = 0, cmp = 0; - bool loose_deleted = 0; + int error = 0; assert(backend && ref_name); if ((error = loose_lock(&file, backend, ref_name)) < 0) return error; + return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); +} + +static int refdb_fs_backend__delete_tail( + git_refdb_backend *_backend, + git_filebuf *file, + const char *ref_name, + const git_oid *old_id, const char *old_target) +{ + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + git_buf loose_path = GIT_BUF_INIT; + size_t pack_pos; + int error = 0, cmp = 0; + bool loose_deleted = 0; + error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); if (error < 0) goto cleanup; @@ -1192,7 +1272,7 @@ static int refdb_fs_backend__delete( error = packed_write(backend); cleanup: - git_filebuf_cleanup(&file); + git_filebuf_cleanup(file); return error; } @@ -1837,6 +1917,8 @@ int git_refdb_backend_fs( backend->parent.del = &refdb_fs_backend__delete; backend->parent.rename = &refdb_fs_backend__rename; backend->parent.compress = &refdb_fs_backend__compress; + backend->parent.lock = &refdb_fs_backend__lock; + backend->parent.unlock = &refdb_fs_backend__unlock; backend->parent.has_log = &refdb_reflog_fs__has_log; backend->parent.ensure_log = &refdb_reflog_fs__ensure_log; backend->parent.free = &refdb_fs_backend__free; diff --git a/src/reflog.c b/src/reflog.c index 8e41621ea..22aa7d8ed 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -148,7 +148,7 @@ size_t git_reflog_entrycount(git_reflog *reflog) return reflog->entries.length; } -const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) +const git_reflog_entry * git_reflog_entry_byindex(const git_reflog *reflog, size_t idx) { assert(reflog); diff --git a/src/refs.c b/src/refs.c index 1603876da..08e407e48 100644 --- a/src/refs.c +++ b/src/refs.c @@ -411,7 +411,7 @@ static int reference__create( return 0; } -static int log_signature(git_signature **out, git_repository *repo) +int git_reference__log_signature(git_signature **out, git_repository *repo) { int error; git_signature *who; @@ -441,7 +441,7 @@ int git_reference_create_matching( assert(id); if (!signature) { - if ((error = log_signature(&who, repo)) < 0) + if ((error = git_reference__log_signature(&who, repo)) < 0) return error; else signature = who; @@ -482,7 +482,7 @@ int git_reference_symbolic_create_matching( assert(target); if (!signature) { - if ((error = log_signature(&who, repo)) < 0) + if ((error = git_reference__log_signature(&who, repo)) < 0) return error; else signature = who; diff --git a/src/refs.h b/src/refs.h index 2e79bdfca..c6ec429a5 100644 --- a/src/refs.h +++ b/src/refs.h @@ -98,4 +98,6 @@ int git_reference_lookup_resolved( const char *name, int max_deref); +int git_reference__log_signature(git_signature **out, git_repository *repo); + #endif diff --git a/src/remote.c b/src/remote.c index dfad946d5..dc9cae133 100644 --- a/src/remote.c +++ b/src/remote.c @@ -21,7 +21,7 @@ static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); -static int add_refspec(git_remote *remote, const char *string, bool is_fetch) +static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) { git_refspec *spec; @@ -34,7 +34,7 @@ static int add_refspec(git_remote *remote, const char *string, bool is_fetch) } spec->push = !is_fetch; - if (git_vector_insert(&remote->refspecs, spec) < 0) { + if (git_vector_insert(vector, spec) < 0) { git_refspec__free(spec); git__free(spec); return -1; @@ -43,6 +43,11 @@ static int add_refspec(git_remote *remote, const char *string, bool is_fetch) return 0; } +static int add_refspec(git_remote *remote, const char *string, bool is_fetch) +{ + return add_refspec_to(&remote->refspecs, string, is_fetch); +} + static int download_tags_value(git_remote *remote, git_config *cfg) { const git_config_entry *ce; @@ -370,6 +375,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if (git_vector_init(&remote->refs, 32, NULL) < 0 || git_vector_init(&remote->refspecs, 2, NULL) < 0 || + git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 || git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { error = -1; goto cleanup; @@ -813,20 +819,43 @@ static int ls_to_vector(git_vector *out, git_remote *remote) return 0; } -int git_remote_download(git_remote *remote) +int git_remote_download(git_remote *remote, const git_strarray *refspecs) { - int error; - git_vector refs; + int error = -1; + size_t i; + git_vector refs, specs, *to_active; assert(remote); if (ls_to_vector(&refs, remote) < 0) return -1; + if ((git_vector_init(&specs, 0, NULL)) < 0) + goto on_error; + + remote->passed_refspecs = 0; + if (!refspecs) { + to_active = &remote->refspecs; + } else { + for (i = 0; i < refspecs->count; i++) { + if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0) + goto on_error; + } + + to_active = &specs; + remote->passed_refspecs = 1; + } + + free_refspecs(&remote->passive_refspecs); + if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0) + goto on_error; + free_refspecs(&remote->active_refspecs); + error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); - error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &refs); git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); if (error < 0) return error; @@ -835,10 +864,17 @@ int git_remote_download(git_remote *remote) return error; return git_fetch_download_pack(remote); + +on_error: + git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); + return error; } int git_remote_fetch( git_remote *remote, + const git_strarray *refspecs, const git_signature *signature, const char *reflog_message) { @@ -849,7 +885,7 @@ int git_remote_fetch( if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) != 0) return error; - error = git_remote_download(remote); + error = git_remote_download(remote, refspecs); /* We don't need to be connected anymore */ git_remote_disconnect(remote); @@ -1106,6 +1142,96 @@ on_error: } +/** + * Iteration over the three vectors, with a pause whenever we find a match + * + * On each stop, we store the iteration stat in the inout i,j,k + * parameters, and return the currently matching passive refspec as + * well as the head which we matched. + */ +static int next_head(const git_remote *remote, git_vector *refs, + git_refspec **out_spec, git_remote_head **out_head, + size_t *out_i, size_t *out_j, size_t *out_k) +{ + const git_vector *active, *passive; + git_remote_head *head; + git_refspec *spec, *passive_spec; + size_t i, j, k; + + active = &remote->active_refspecs; + passive = &remote->passive_refspecs; + + i = *out_i; + j = *out_j; + k = *out_k; + + for (; i < refs->length; i++) { + head = git_vector_get(refs, i); + + if (!git_reference_is_valid_name(head->name)) + continue; + + for (; j < active->length; j++) { + spec = git_vector_get(active, j); + + if (!git_refspec_src_matches(spec, head->name)) + continue; + + for (; k < passive->length; k++) { + passive_spec = git_vector_get(passive, k); + + if (!git_refspec_src_matches(passive_spec, head->name)) + continue; + + *out_spec = passive_spec; + *out_head = head; + *out_i = i; + *out_j = j; + *out_k = k + 1; + return 0; + + } + k = 0; + } + j = 0; + } + + return GIT_ITEROVER; +} + +static int opportunistic_updates(const git_remote *remote, git_vector *refs, const git_signature *sig, const char *msg) +{ + size_t i, j, k; + git_refspec *spec; + git_remote_head *head; + git_reference *ref; + git_buf refname = GIT_BUF_INIT; + int error; + + i = j = k = 0; + + while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) { + /* + * If we got here, there is a refspec which was used + * for fetching which matches the source of one of the + * passive refspecs, so we should update that + * remote-tracking branch, but not add it to + * FETCH_HEAD + */ + + if ((error = git_refspec_transform(&refname, spec, head->name)) < 0) + return error; + + error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, true, sig, msg); + git_buf_free(&refname); + + if (error < 0) + return error; + } + + return 0; +} + int git_remote_update_tips( git_remote *remote, const git_signature *signature, @@ -1136,6 +1262,10 @@ int git_remote_update_tips( goto out; } + /* only try to do opportunisitic updates if the refpec lists differ */ + if (remote->passed_refspecs) + error = opportunistic_updates(remote, &refs, signature, reflog_message); + out: git_vector_free(&refs); git_refspec__free(&tagspec); @@ -1189,6 +1319,9 @@ void git_remote_free(git_remote *remote) free_refspecs(&remote->active_refspecs); git_vector_free(&remote->active_refspecs); + free_refspecs(&remote->passive_refspecs); + git_vector_free(&remote->passive_refspecs); + git__free(remote->url); git__free(remote->pushurl); git__free(remote->name); @@ -1640,27 +1773,14 @@ void git_remote_clear_refspecs(git_remote *remote) git_vector_clear(&remote->refspecs); } -static int add_and_dwim(git_remote *remote, const char *str, int push) -{ - git_refspec *spec; - git_vector *vec; - - if (add_refspec(remote, str, !push) < 0) - return -1; - - vec = &remote->refspecs; - spec = git_vector_get(vec, vec->length - 1); - return git_refspec__dwim_one(&remote->active_refspecs, spec, &remote->refs); -} - int git_remote_add_fetch(git_remote *remote, const char *refspec) { - return add_and_dwim(remote, refspec, false); + return add_refspec(remote, refspec, true); } int git_remote_add_push(git_remote *remote, const char *refspec) { - return add_and_dwim(remote, refspec, true); + return add_refspec(remote, refspec, false); } static int set_refspecs(git_remote *remote, git_strarray *array, int push) @@ -1688,10 +1808,7 @@ static int set_refspecs(git_remote *remote, git_strarray *array, int push) return -1; } - free_refspecs(&remote->active_refspecs); - git_vector_clear(&remote->active_refspecs); - - return dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs); + return 0; } int git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array) diff --git a/src/remote.h b/src/remote.h index f88601e9b..b79ace438 100644 --- a/src/remote.h +++ b/src/remote.h @@ -23,6 +23,7 @@ struct git_remote { git_vector refs; git_vector refspecs; git_vector active_refspecs; + git_vector passive_refspecs; git_transport_cb transport_cb; void *transport_cb_payload; git_transport *transport; @@ -32,6 +33,7 @@ struct git_remote { unsigned int need_pack; git_remote_autotag_option_t download_tags; int update_fetchhead; + int passed_refspecs; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/repository.c b/src/repository.c index 51d39eb6d..f032c899d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1567,8 +1567,10 @@ int git_repository_head_unborn(git_repository *repo) error = git_repository_head(&ref, repo); git_reference_free(ref); - if (error == GIT_EUNBORNBRANCH) + if (error == GIT_EUNBORNBRANCH) { + giterr_clear(); return 1; + } if (error < 0) return -1; diff --git a/src/signature.c b/src/signature.c index 2a16b484a..514b153ac 100644 --- a/src/signature.c +++ b/src/signature.c @@ -106,6 +106,30 @@ int git_signature_dup(git_signature **dest, const git_signature *source) return 0; } +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool) +{ + git_signature *signature; + + if (source == NULL) + return 0; + + signature = git_pool_mallocz(pool, sizeof(git_signature)); + GITERR_CHECK_ALLOC(signature); + + signature->name = git_pool_strdup(pool, source->name); + GITERR_CHECK_ALLOC(signature->name); + + signature->email = git_pool_strdup(pool, source->email); + GITERR_CHECK_ALLOC(signature->email); + + signature->when.time = source->when.time; + signature->when.offset = source->when.offset; + + *dest = signature; + + return 0; +} + int git_signature_now(git_signature **sig_out, const char *name, const char *email) { time_t now; diff --git a/src/signature.h b/src/signature.h index 24655cbf5..eb71db7db 100644 --- a/src/signature.h +++ b/src/signature.h @@ -15,4 +15,6 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig); +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); + #endif diff --git a/src/stash.c b/src/stash.c index caffd0cea..cad87d120 100644 --- a/src/stash.c +++ b/src/stash.c @@ -15,6 +15,7 @@ #include "git2/status.h" #include "git2/checkout.h" #include "git2/index.h" +#include "git2/transaction.h" #include "signature.h" static int create_error(int error, const char *msg) @@ -601,14 +602,21 @@ int git_stash_drop( git_repository *repo, size_t index) { - git_reference *stash; + git_transaction *tx; + git_reference *stash = NULL; git_reflog *reflog = NULL; size_t max; int error; - if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + if ((error = git_transaction_new(&tx, repo)) < 0) return error; + if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; @@ -623,29 +631,25 @@ int git_stash_drop( if ((error = git_reflog_drop(reflog, index, true)) < 0) goto cleanup; - if ((error = git_reflog_write(reflog)) < 0) + if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) goto cleanup; if (max == 1) { - error = git_reference_delete(stash); - git_reference_free(stash); - stash = NULL; + if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; } else if (index == 0) { const git_reflog_entry *entry; entry = git_reflog_entry_byindex(reflog, 0); - - git_reference_free(stash); - error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1, NULL, NULL); - if (error < 0) + if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) goto cleanup; - - /* We need to undo the writing that we just did */ - error = git_reflog_write(reflog); } + error = git_transaction_commit(tx); + cleanup: git_reference_free(stash); + git_transaction_free(tx); git_reflog_free(reflog); return error; } diff --git a/src/transaction.c b/src/transaction.c new file mode 100644 index 000000000..ebb943cea --- /dev/null +++ b/src/transaction.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "repository.h" +#include "strmap.h" +#include "refdb.h" +#include "pool.h" +#include "reflog.h" +#include "signature.h" + +#include "git2/signature.h" +#include "git2/sys/refs.h" +#include "git2/sys/refdb_backend.h" + +GIT__USE_STRMAP; + +typedef struct { + const char *name; + void *payload; + + git_ref_t ref_type; + union { + git_oid id; + char *symbolic; + } target; + git_reflog *reflog; + + const char *message; + git_signature *sig; + + unsigned int committed :1, + remove :1; +} transaction_node; + +struct git_transaction { + git_repository *repo; + git_refdb *db; + + git_strmap *locks; + git_pool pool; +}; + +int git_transaction_new(git_transaction **out, git_repository *repo) +{ + int error; + git_pool pool; + git_transaction *tx = NULL; + + assert(out && repo); + + if ((error = git_pool_init(&pool, 1, 0)) < 0) + return error; + + tx = git_pool_mallocz(&pool, sizeof(git_transaction)); + if (!tx) { + error = -1; + goto on_error; + } + + if ((error = git_strmap_alloc(&tx->locks)) < 0) { + error = -1; + goto on_error; + } + + if ((error = git_repository_refdb(&tx->db, repo)) < 0) + goto on_error; + + memcpy(&tx->pool, &pool, sizeof(git_pool)); + tx->repo = repo; + *out = tx; + return 0; + +on_error: + git_pool_clear(&pool); + return error; +} + +int git_transaction_lock_ref(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + assert(tx && refname); + + node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); + GITERR_CHECK_ALLOC(node); + + node->name = git_pool_strdup(&tx->pool, refname); + GITERR_CHECK_ALLOC(node->name); + + if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) + return error; + + git_strmap_insert(tx->locks, node->name, node, error); + if (error < 0) + goto cleanup; + + return 0; + +cleanup: + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + + return error; +} + +static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) +{ + git_strmap_iter pos; + transaction_node *node; + + pos = git_strmap_lookup_index(tx->locks, refname); + if (!git_strmap_valid_index(tx->locks, pos)) { + giterr_set(GITERR_REFERENCE, "the specified reference is not locked"); + return GIT_ENOTFOUND; + } + + node = git_strmap_value_at(tx->locks, pos); + + *out = node; + return 0; +} + +static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg) +{ + if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0) + return -1; + + if (!node->sig) { + git_signature *tmp; + int error; + + if (git_reference__log_signature(&tmp, tx->repo) < 0) + return -1; + + /* make sure the sig we use is in our pool */ + error = git_signature__pdup(&node->sig, tmp, &tx->pool); + git_signature_free(tmp); + if (error < 0) + return error; + } + + if (msg) { + node->message = git_pool_strdup(&tx->pool, msg); + GITERR_CHECK_ALLOC(node->message); + } + + return 0; +} + +int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + assert(tx && refname && target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + git_oid_cpy(&node->target.id, target); + node->ref_type = GIT_REF_OID; + + return 0; +} + +int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + assert(tx && refname && target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + node->target.symbolic = git_pool_strdup(&tx->pool, target); + GITERR_CHECK_ALLOC(node->target.symbolic); + node->ref_type = GIT_REF_SYMBOLIC; + + return 0; +} + +int git_transaction_remove(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + node->remove = true; + node->ref_type = GIT_REF_OID; /* the id will be ignored */ + + return 0; +} + +static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool) +{ + git_reflog *reflog; + git_reflog_entry *entries; + size_t len, i; + + reflog = git_pool_mallocz(pool, sizeof(git_reflog)); + GITERR_CHECK_ALLOC(reflog); + + reflog->ref_name = git_pool_strdup(pool, in->ref_name); + GITERR_CHECK_ALLOC(reflog->ref_name); + + len = in->entries.length; + reflog->entries.length = len; + reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); + GITERR_CHECK_ALLOC(reflog->entries.contents); + + entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entries); + + for (i = 0; i < len; i++) { + const git_reflog_entry *src; + git_reflog_entry *tgt; + + tgt = &entries[i]; + reflog->entries.contents[i] = tgt; + + src = git_vector_get(&in->entries, i); + git_oid_cpy(&tgt->oid_old, &src->oid_old); + git_oid_cpy(&tgt->oid_cur, &src->oid_cur); + + tgt->msg = git_pool_strdup(pool, src->msg); + GITERR_CHECK_ALLOC(tgt->msg); + + if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0) + return -1; + } + + + *out = reflog; + return 0; +} + +int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog) +{ + int error; + transaction_node *node; + + assert(tx && refname && reflog); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0) + return error; + + return 0; +} + +static int update_target(git_refdb *db, transaction_node *node) +{ + git_reference *ref; + int error, update_reflog; + + if (node->ref_type == GIT_REF_OID) { + ref = git_reference__alloc(node->name, &node->target.id, NULL); + } else if (node->ref_type == GIT_REF_SYMBOLIC) { + ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); + } else { + assert(0); + } + + GITERR_CHECK_ALLOC(ref); + update_reflog = node->reflog == NULL; + + if (node->remove) { + error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); + } else if (node->ref_type == GIT_REF_OID) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else if (node->ref_type == GIT_REF_SYMBOLIC) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else { + assert(0); + } + + git_reference_free(ref); + node->committed = true; + + return error; +} + +int git_transaction_commit(git_transaction *tx) +{ + transaction_node *node; + git_strmap_iter pos; + int error; + + assert(tx); + + for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { + if (!git_strmap_has_data(tx->locks, pos)) + continue; + + node = git_strmap_value_at(tx->locks, pos); + if (node->reflog) { + if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) + return error; + } + + if (node->ref_type != GIT_REF_INVALID) { + if ((error = update_target(tx->db, node)) < 0) + return error; + } + } + + return 0; +} + +void git_transaction_free(git_transaction *tx) +{ + transaction_node *node; + git_pool pool; + git_strmap_iter pos; + + assert(tx); + + /* start by unlocking the ones we've left hanging, if any */ + for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { + if (!git_strmap_has_data(tx->locks, pos)) + continue; + + node = git_strmap_value_at(tx->locks, pos); + if (node->committed) + continue; + + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + } + + git_refdb_free(tx->db); + git_strmap_free(tx->locks); + + /* tx is inside the pool, so we need to extract the data */ + memcpy(&pool, &tx->pool, sizeof(git_pool)); + git_pool_clear(&pool); +} diff --git a/tests/config/stress.c b/tests/config/stress.c index 488915e79..e8e9d2b61 100644 --- a/tests/config/stress.c +++ b/tests/config/stress.c @@ -44,12 +44,24 @@ void test_config_stress__comments(void) cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config12"))); + cl_git_pass(git_config_get_string(&str, config, "some.section.test2")); + cl_assert_equal_s("hello", str); + + cl_git_pass(git_config_get_string(&str, config, "some.section.test3")); + cl_assert_equal_s("welcome", str); + cl_git_pass(git_config_get_string(&str, config, "some.section.other")); cl_assert_equal_s("hello! \" ; ; ; ", str); + cl_git_pass(git_config_get_string(&str, config, "some.section.other2")); + cl_assert_equal_s("cool! \" # # # ", str); + cl_git_pass(git_config_get_string(&str, config, "some.section.multi")); cl_assert_equal_s("hi, this is a ; multiline comment # with ;\n special chars and other stuff !@#", str); + cl_git_pass(git_config_get_string(&str, config, "some.section.multi2")); + cl_assert_equal_s("good, this is a ; multiline comment # with ;\n special chars and other stuff !@#", str); + cl_git_pass(git_config_get_string(&str, config, "some.section.back")); cl_assert_equal_s("this is \ba phrase", str); diff --git a/tests/core/buffer.c b/tests/core/buffer.c index 7482dadbe..641fed630 100644 --- a/tests/core/buffer.c +++ b/tests/core/buffer.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "buffer.h" #include "buf_text.h" -#include "hashsig.h" +#include "git2/sys/hashsig.h" #include "fileops.h" #define TESTSTR "Have you seen that? Have you seeeen that??" diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c index 7b64a6339..c8191c5f5 100644 --- a/tests/fetchhead/nonetwork.c +++ b/tests/fetchhead/nonetwork.c @@ -335,7 +335,7 @@ void test_fetchhead_nonetwork__unborn_with_upstream(void) cl_git_pass(git_remote_set_url(remote, cl_fixture("testrepo.git"))); cl_git_pass(git_remote_save(remote)); - cl_git_pass(git_remote_fetch(remote, NULL, NULL)); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); git_remote_free(remote); cl_git_pass(git_repository_fetchhead_foreach(repo, assert_master_for_merge, NULL)); diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c index 2298a302b..8b47f7dee 100644 --- a/tests/merge/trees/treediff.c +++ b/tests/merge/trees/treediff.c @@ -3,7 +3,7 @@ #include "merge.h" #include "../merge_helpers.h" #include "diff.h" -#include "hashsig.h" +#include "git2/sys/hashsig.h" static git_repository *repo; diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 8809f427d..bf0d0872b 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -36,7 +36,7 @@ void test_network_fetchlocal__complete(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_download(origin, NULL)); cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); @@ -74,7 +74,7 @@ void test_network_fetchlocal__partial(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_download(origin, NULL)); cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); git_strarray_free(&refnames); diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c index f1084fc38..c6c9e4ca9 100644 --- a/tests/network/remote/local.c +++ b/tests/network/remote/local.c @@ -116,16 +116,20 @@ void test_network_remote_local__nested_tags_are_completely_peeled(void) void test_network_remote_local__shorthand_fetch_refspec0(void) { - const char *refspec = "master:remotes/sloppy/master"; - const char *refspec2 = "master:boh/sloppy/master"; + char *refspec_strings[] = { + "master:remotes/sloppy/master", + "master:boh/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 2, + }; git_reference *ref; connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, refspec)); - cl_git_pass(git_remote_add_fetch(remote, refspec2)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); @@ -137,17 +141,21 @@ void test_network_remote_local__shorthand_fetch_refspec0(void) void test_network_remote_local__shorthand_fetch_refspec1(void) { - const char *refspec = "master"; - const char *refspec2 = "hard_tag"; + char *refspec_strings[] = { + "master", + "hard_tag", + }; + git_strarray array = { + refspec_strings, + 2, + }; git_reference *ref; connect_to_local_repository(cl_fixture("testrepo.git")); git_remote_clear_refspecs(remote); - cl_git_pass(git_remote_add_fetch(remote, refspec)); - cl_git_pass(git_remote_add_fetch(remote, refspec2)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/master")); @@ -162,7 +170,7 @@ void test_network_remote_local__tagopt(void) connect_to_local_repository(cl_fixture("testrepo.git")); git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_ALL); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, NULL)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); @@ -174,14 +182,20 @@ void test_network_remote_local__tagopt(void) void test_network_remote_local__push_to_bare_remote(void) { + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; /* Should be able to push to a bare remote */ git_remote *localremote; git_push *push; /* Get some commits */ connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, "master:master")); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); git_remote_disconnect(remote); @@ -210,6 +224,13 @@ void test_network_remote_local__push_to_bare_remote(void) void test_network_remote_local__push_to_bare_remote_with_file_url(void) { + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; /* Should be able to push to a bare remote */ git_remote *localremote; git_push *push; @@ -217,8 +238,7 @@ void test_network_remote_local__push_to_bare_remote_with_file_url(void) /* Get some commits */ connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, "master:master")); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); git_remote_disconnect(remote); @@ -251,14 +271,20 @@ void test_network_remote_local__push_to_bare_remote_with_file_url(void) void test_network_remote_local__push_to_non_bare_remote(void) { + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; /* Shouldn't be able to push to a non-bare remote */ git_remote *localremote; git_push *push; /* Get some commits */ connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, "master:master")); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); git_remote_disconnect(remote); @@ -287,7 +313,13 @@ void test_network_remote_local__push_to_non_bare_remote(void) void test_network_remote_local__fetch(void) { - const char *refspec = "master:remotes/sloppy/master"; + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; git_reflog *log; const git_reflog_entry *entry; @@ -297,9 +329,8 @@ void test_network_remote_local__fetch(void) cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com")); connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, refspec)); - cl_git_pass(git_remote_fetch(remote, sig, "UPDAAAAAATE!!")); + cl_git_pass(git_remote_fetch(remote, &array, sig, "UPDAAAAAATE!!")); cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); git_reference_free(ref); @@ -316,7 +347,13 @@ void test_network_remote_local__fetch(void) void test_network_remote_local__reflog(void) { - const char *refspec = "master:remotes/sloppy/master"; + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; git_reflog *log; const git_reflog_entry *entry; @@ -325,9 +362,8 @@ void test_network_remote_local__reflog(void) cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com")); connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, refspec)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, &array)); cl_git_pass(git_remote_update_tips(remote, sig, "UPDAAAAAATE!!")); cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); @@ -342,7 +378,13 @@ void test_network_remote_local__reflog(void) void test_network_remote_local__fetch_default_reflog_message(void) { - const char *refspec = "master:remotes/sloppy/master"; + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; git_reflog *log; const git_reflog_entry *entry; @@ -352,9 +394,8 @@ void test_network_remote_local__fetch_default_reflog_message(void) cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com")); connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_add_fetch(remote, refspec)); - cl_git_pass(git_remote_fetch(remote, sig, NULL)); + cl_git_pass(git_remote_fetch(remote, &array, sig, NULL)); cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); cl_assert_equal_i(1, git_reflog_entrycount(log)); @@ -367,3 +408,24 @@ void test_network_remote_local__fetch_default_reflog_message(void) git_reflog_free(log); git_signature_free(sig); } + +void test_network_remote_local__opportunistic_update(void) +{ + git_reference *ref; + char *refspec_strings[] = { + "master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + /* this remote has a passive refspec of "refs/heads/<star>:refs/remotes/origin/<star>" */ + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"))); + /* and we pass the active refspec "master" */ + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + /* and we expect that to update our copy of origin's master */ + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/origin/master")); + git_reference_free(ref); +} diff --git a/tests/online/fetch.c b/tests/online/fetch.c index f03a6faa6..ce92ee82b 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -47,7 +47,7 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) git_remote_set_callbacks(remote, &callbacks); git_remote_set_autotag(remote, flag); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, NULL)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); git_remote_disconnect(remote); cl_assert_equal_i(counter, n); @@ -86,11 +86,11 @@ void test_online_fetch__fetch_twice(void) git_remote *remote; cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git")); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, NULL)); git_remote_disconnect(remote); git_remote_connect(remote, GIT_DIRECTION_FETCH); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, NULL)); git_remote_disconnect(remote); git_remote_free(remote); @@ -128,7 +128,7 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date callbacks.transfer_progress = &transferProgressCallback; callbacks.payload = &invoked; git_remote_set_callbacks(remote, &callbacks); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, NULL)); cl_assert_equal_i(false, invoked); @@ -162,7 +162,7 @@ void test_online_fetch__can_cancel(void) git_remote_set_callbacks(remote, &callbacks); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_fail_with(git_remote_download(remote), -4321); + cl_git_fail_with(git_remote_download(remote, NULL), -4321); git_remote_disconnect(remote); git_remote_free(remote); } diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c index 3f27e1331..bd423bbb0 100644 --- a/tests/online/fetchhead.c +++ b/tests/online/fetchhead.c @@ -40,17 +40,19 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet git_remote *remote; git_buf fetchhead_buf = GIT_BUF_INIT; int equals = 0; + git_strarray array, *active_refs = NULL; cl_git_pass(git_remote_load(&remote, g_repo, "origin")); git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO); if(fetchspec != NULL) { - git_remote_clear_refspecs(remote); - git_remote_add_fetch(remote, fetchspec); + array.count = 1; + array.strings = (char **) &fetchspec; + active_refs = &array; } cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_download(remote, active_refs)); cl_git_pass(git_remote_update_tips(remote, NULL, NULL)); git_remote_disconnect(remote); git_remote_free(remote); diff --git a/tests/online/push.c b/tests/online/push.c index 70ec705fe..b09c7ad1f 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -408,7 +408,7 @@ void test_online_push__initialize(void) /* Now that we've deleted everything, fetch from the remote */ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(_remote)); + cl_git_pass(git_remote_download(_remote, NULL)); cl_git_pass(git_remote_update_tips(_remote, NULL, NULL)); git_remote_disconnect(_remote); } diff --git a/tests/refs/transactions.c b/tests/refs/transactions.c new file mode 100644 index 000000000..39ea1cae5 --- /dev/null +++ b/tests/refs/transactions.c @@ -0,0 +1,110 @@ +#include "clar_libgit2.h" +#include "git2/transaction.h" + +static git_repository *g_repo; +static git_transaction *g_tx; + +void test_refs_transactions__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_transaction_new(&g_tx, g_repo)); +} + +void test_refs_transactions__cleanup(void) +{ + git_transaction_free(g_tx); + cl_git_sandbox_cleanup(); +} + +void test_refs_transactions__single_ref_oid(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_symbolic(void) +{ + git_reference *ref; + + cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); + cl_git_pass(git_transaction_set_symbolic_target(g_tx, "HEAD", "refs/heads/foo", NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + + cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_mix_types(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); + cl_git_pass(git_transaction_set_symbolic_target(g_tx, "refs/heads/master", "refs/heads/foo", NULL, NULL)); + cl_git_pass(git_transaction_set_target(g_tx, "HEAD", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_delete(void) +{ + git_reference *ref; + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_remove(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, "refs/heads/master")); +} + +void test_refs_transactions__single_create(void) +{ + git_reference *ref; + const char *name = "refs/heads/new-branch"; + git_oid id; + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, name)); + + cl_git_pass(git_transaction_lock_ref(g_tx, name)); + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_transaction_set_target(g_tx, name, &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, name)); + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__unlocked_set(void) +{ + git_oid id; + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); +} diff --git a/tests/resources/config/config12 b/tests/resources/config/config12 index b57a81b08..6917880b5 100644 --- a/tests/resources/config/config12 +++ b/tests/resources/config/config12 @@ -1,7 +1,13 @@ [some "section"] test = hi ; comment + test2 = hello ; comment + test3 = welcome #comment other = "hello! \" ; ; ; " ; more test + other2 = "cool! \" # # # " # more test multi = "hi, this is a ; \ multiline comment # with ;\n special chars \ and other stuff !@#" + multi2 = "good, this is a ; \ +multiline comment # with ;\n special chars \ +and other stuff !@#" #^^^ back = "this is \ba phrase" diff --git a/tests/revwalk/mergebase.c b/tests/revwalk/mergebase.c index 677e1a1b6..fa974eb9a 100644 --- a/tests/revwalk/mergebase.c +++ b/tests/revwalk/mergebase.c @@ -127,7 +127,7 @@ void test_revwalk_mergebase__prefer_youngest_merge_base(void) { git_oid result, one, two, expected; - cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f ")); + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); @@ -140,7 +140,7 @@ void test_revwalk_mergebase__multiple_merge_bases(void) git_oid one, two, expected1, expected2; git_oidarray result = {NULL, 0}; - cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f ")); + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); @@ -153,6 +153,26 @@ void test_revwalk_mergebase__multiple_merge_bases(void) git_oidarray_free(&result); } +void test_revwalk_mergebase__multiple_merge_bases_many_commits(void) +{ + git_oid expected1, expected2; + git_oidarray result = {NULL, 0}; + + git_oid *input = git__malloc(sizeof(git_oid) * 2); + + cl_git_pass(git_oid_fromstr(&input[0], "a4a7dce85cf63874e984719f4fdd239f5145052f")); + cl_git_pass(git_oid_fromstr(&input[1], "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + cl_git_pass(git_merge_bases_many(&result, _repo, 2, input)); + cl_assert_equal_i(2, result.count); + cl_assert_equal_oid(&expected1, &result.ids[0]); + cl_assert_equal_oid(&expected2, &result.ids[1]); + + git_oidarray_free(&result); +} + void test_revwalk_mergebase__no_off_by_one_missing(void) { git_oid result, one, two; diff --git a/tests/stash/save.c b/tests/stash/save.c index 7873d20ba..a5bdd0cbe 100644 --- a/tests/stash/save.c +++ b/tests/stash/save.c @@ -370,8 +370,8 @@ void test_stash_save__including_untracked_without_any_untracked_file_creates_an_ void test_stash_save__ignored_directory(void) { - cl_git_pass(mkdir("stash/ignored_directory", 0777)); - cl_git_pass(mkdir("stash/ignored_directory/sub", 0777)); + cl_git_pass(p_mkdir("stash/ignored_directory", 0777)); + cl_git_pass(p_mkdir("stash/ignored_directory/sub", 0777)); cl_git_mkfile("stash/ignored_directory/sub/some_file", "stuff"); assert_status(repo, "ignored_directory/sub/some_file", GIT_STATUS_WT_NEW); |