diff options
135 files changed, 4756 insertions, 629 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a57394640..59c6b4388 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,8 @@ IF (MSVC) # Default to stdcall, as that's what the CLR expects and how the Windows API is built OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON) + STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}") IF (STDCALL) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz") @@ -94,9 +96,9 @@ IF (MSVC) SET(WIN_RC "src/win32/git2.rc") # Precompiled headers + ELSE () - SET(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS}") - SET(CMAKE_C_FLAGS "-O2 -g -D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") + SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") IF (MINGW) # MinGW always does PIC and complains if we tell it to STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") ELSE () @@ -108,10 +110,15 @@ ELSE () ENDIF () ENDIF() -# Build Debug by default -IF (NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) -ENDIF () +IF( NOT CMAKE_CONFIGURATION_TYPES ) + # Build Debug by default + IF (NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) + ENDIF () +ELSE() + # Using a multi-configuration generator eg MSVC or Xcode + # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE +ENDIF() IF (OPENSSL_FOUND) ADD_DEFINITIONS(-DGIT_SSL) diff --git a/examples/general.c b/examples/general.c index e001a6889..d9467f5b5 100644 --- a/examples/general.c +++ b/examples/general.c @@ -371,7 +371,7 @@ int main (int argc, char** argv) // All these properties are exported publicly in the `git_index_entry` struct ecount = git_index_entrycount(index); for (i = 0; i < ecount; ++i) { - git_index_entry *e = git_index_get(index, i); + git_index_entry *e = git_index_get_byindex(index, i); printf("path: %s\n", e->path); printf("mtime: %d\n", (int)e->mtime.seconds); diff --git a/examples/network/clone.c b/examples/network/clone.c index fb571bd3a..791600171 100644 --- a/examples/network/clone.c +++ b/examples/network/clone.c @@ -7,62 +7,72 @@ #include <pthread.h> #include <unistd.h> -struct dl_data { - git_indexer_stats fetch_stats; - git_indexer_stats checkout_stats; - git_checkout_opts opts; - int ret; - int finished; - const char *url; +typedef struct progress_data { + git_transfer_progress fetch_progress; + size_t completed_steps; + size_t total_steps; const char *path; -}; +} progress_data; -static void *clone_thread(void *ptr) +static void print_progress(const progress_data *pd) { - struct dl_data *data = (struct dl_data *)ptr; - git_repository *repo = NULL; - - // Kick off the clone - data->ret = git_clone(&repo, data->url, data->path, - &data->fetch_stats, &data->checkout_stats, - &data->opts); - if (repo) git_repository_free(repo); - data->finished = 1; + int network_percent = (100*pd->fetch_progress.received_objects) / pd->fetch_progress.total_objects; + int index_percent = (100*pd->fetch_progress.indexed_objects) / pd->fetch_progress.total_objects; + int checkout_percent = pd->total_steps > 0 + ? (100 * pd->completed_steps) / pd->total_steps + : 0.f; + int kbytes = pd->fetch_progress.received_bytes / 1024; + printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4lu/%4lu) %s\n", + network_percent, kbytes, + pd->fetch_progress.received_objects, pd->fetch_progress.total_objects, + index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects, + checkout_percent, pd->completed_steps, pd->total_steps, + pd->path); +} - pthread_exit(&data->ret); +static void fetch_progress(const git_transfer_progress *stats, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->fetch_progress = *stats; + print_progress(pd); +} +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->completed_steps = cur; + pd->total_steps = tot; + pd->path = path; + print_progress(pd); } int do_clone(git_repository *repo, int argc, char **argv) { - struct dl_data data = {0}; - pthread_t worker; + progress_data pd = {0}; + git_repository *cloned_repo = NULL; + git_checkout_opts checkout_opts = {0}; + const char *url = argv[1]; + const char *path = argv[2]; + int error; // Validate args if (argc < 3) { - printf("USAGE: %s <url> <path>\n", argv[0]); + printf ("USAGE: %s <url> <path>\n", argv[0]); return -1; } - // Data for background thread - data.url = argv[1]; - data.path = argv[2]; - data.opts.disable_filters = 1; - printf("Cloning '%s' to '%s'\n", data.url, data.path); + // Set up options + checkout_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + checkout_opts.progress_cb = checkout_progress; + checkout_opts.progress_payload = &pd; - // Create the worker thread - pthread_create(&worker, NULL, clone_thread, &data); - - // Watch for progress information - do { - usleep(10000); - printf("Fetch %d/%d – Checkout %d/%d\n", - data.fetch_stats.processed, data.fetch_stats.total, - data.checkout_stats.processed, data.checkout_stats.total); - } while (!data.finished); - printf("Fetch %d/%d – Checkout %d/%d\n", - data.fetch_stats.processed, data.fetch_stats.total, - data.checkout_stats.processed, data.checkout_stats.total); - - return data.ret; + // Do the clone + error = git_clone(&cloned_repo, url, path, &fetch_progress, &pd, &checkout_opts); + printf("\n"); + if (error != 0) { + const git_error *err = giterr_last(); + if (err) printf("ERROR %d: %s\n", err->klass, err->message); + else printf("ERROR %d: no detailed info\n", error); + } + else if (cloned_repo) git_repository_free(cloned_repo); + return error; } - diff --git a/examples/network/fetch.c b/examples/network/fetch.c index fa941b97a..496498e8c 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -8,8 +8,6 @@ struct dl_data { git_remote *remote; - git_off_t *bytes; - git_indexer_stats *stats; int ret; int finished; }; @@ -35,7 +33,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, data->bytes, data->stats) < 0) { + if (git_remote_download(data->remote, NULL, NULL) < 0) { data->ret = -1; goto exit; } @@ -69,15 +67,14 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo int fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; - git_off_t bytes = 0; - git_indexer_stats stats; + const git_transfer_progress *stats; pthread_t worker; struct dl_data data; git_remote_callbacks callbacks; argc = argc; // Figure out whether it's a named remote or a URL - printf("Fetching %s\n", argv[1]); + printf("Fetching %s for repo %p\n", argv[1], repo); if (git_remote_load(&remote, repo, argv[1]) < 0) { if (git_remote_new(&remote, repo, NULL, argv[1], NULL) < 0) return -1; @@ -91,11 +88,10 @@ int fetch(git_repository *repo, int argc, char **argv) // Set up the information for the background worker thread data.remote = remote; - data.bytes = &bytes; - data.stats = &stats; data.ret = 0; data.finished = 0; - memset(&stats, 0, sizeof(stats)); + + stats = git_remote_stats(remote); pthread_create(&worker, NULL, download, &data); @@ -106,16 +102,18 @@ int fetch(git_repository *repo, int argc, char **argv) do { usleep(10000); - if (stats.total > 0) + if (stats->total_objects > 0) printf("Received %d/%d objects (%d) in %d bytes\r", - stats.received, stats.total, stats.processed, bytes); + stats->received_objects, stats->total_objects, + stats->indexed_objects, stats->received_bytes); } while (!data.finished); if (data.ret < 0) goto on_error; pthread_join(worker, NULL); - printf("\rReceived %d/%d objects in %zu bytes\n", stats.processed, stats.total, bytes); + printf("\rReceived %d/%d objects in %zu bytes\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes); // Disconnect the underlying connection to prevent from idling. git_remote_disconnect(remote); diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c index 85aac4aff..4d3dc84d6 100644 --- a/examples/network/index-pack.c +++ b/examples/network/index-pack.c @@ -10,10 +10,10 @@ // This could be run in the main loop whilst the application waits for // the indexing to finish in a worker thread -static int index_cb(const git_indexer_stats *stats, void *data) +static int index_cb(const git_transfer_progress *stats, void *data) { data = data; - printf("\rProcessing %d of %d", stats->processed, stats->total); + printf("\rProcessing %d of %d", stats->indexed_objects, stats->total_objects); return 0; } @@ -21,7 +21,7 @@ static int index_cb(const git_indexer_stats *stats, void *data) int index_pack(git_repository *repo, int argc, char **argv) { git_indexer_stream *idx; - git_indexer_stats stats = {0, 0}; + git_transfer_progress stats = {0, 0}; int error, fd; char hash[GIT_OID_HEXSZ + 1] = {0}; ssize_t read_bytes; @@ -33,7 +33,7 @@ int index_pack(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - if (git_indexer_stream_new(&idx, ".") < 0) { + if (git_indexer_stream_new(&idx, ".", NULL, NULL) < 0) { puts("bad idx"); return -1; } @@ -63,7 +63,7 @@ int index_pack(git_repository *repo, int argc, char **argv) if ((error = git_indexer_stream_finalize(idx, &stats)) < 0) goto cleanup; - printf("\rIndexing %d of %d\n", stats.processed, stats.total); + printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects); git_oid_fmt(hash, git_indexer_stream_hash(idx)); puts(hash); diff --git a/examples/showindex.c b/examples/showindex.c index 7f2130b90..d26fbaebd 100644 --- a/examples/showindex.c +++ b/examples/showindex.c @@ -19,12 +19,13 @@ int main (int argc, char** argv) ecount = git_index_entrycount(index); for (i = 0; i < ecount; ++i) { - git_index_entry *e = git_index_get(index, i); + git_index_entry *e = git_index_get_byindex(index, i); oid = e->oid; git_oid_fmt(out, &oid); printf("File Path: %s\n", e->path); + printf(" Stage: %d\n", git_index_entry_stage(e)); printf(" Blob SHA: %s\n", out); printf("File Size: %d\n", (int)e->file_size); printf(" Device: %d\n", (int)e->dev); diff --git a/include/git2.h b/include/git2.h index d55543986..3bb2fce11 100644 --- a/include/git2.h +++ b/include/git2.h @@ -52,5 +52,6 @@ #include "git2/reset.h" #include "git2/message.h" #include "git2/pack.h" +#include "git2/stash.h" #endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h index b4f9ad081..390d2f215 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -65,9 +65,16 @@ typedef struct git_checkout_opts { const git_oid *blob_oid, int file_mode, void *payload); - void *notify_payload; + /* Optional callback to notify the consumer of checkout progress. */ + void (* progress_cb)( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload); + void *progress_payload; + /** When not NULL, array of fnmatch patterns specifying * which paths should be taken into account */ @@ -80,29 +87,25 @@ typedef struct git_checkout_opts { * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing * branch, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_head( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_checkout_opts *opts); /** * Updates files in the working tree to match the content of the index. * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_index( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_checkout_opts *opts); /** * Updates files in the index and working tree to match the content of the @@ -112,15 +115,13 @@ GIT_EXTERN(int) git_checkout_index( * @param treeish a commit, tag or tree which content will be used to update * the working directory * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_tree( git_repository *repo, git_object *treeish, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_checkout_opts *opts); /** @} */ GIT_END_DECL diff --git a/include/git2/clone.h b/include/git2/clone.h index c4dfc652b..7d8d32118 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -29,19 +29,22 @@ GIT_BEGIN_DECL * @param out pointer that will receive the resulting repository object * @param origin_url repository to clone from * @param workdir_path local directory to clone to - * @param fetch_stats pointer to structure that receives fetch progress - * information (may be NULL) + * @param fetch_progress_cb optional callback for fetch progress. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param fetch_progress_payload payload for fetch_progress_cb * @param checkout_opts options for the checkout step. If NULL, no checkout * is performed * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ -GIT_EXTERN(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); +GIT_EXTERN(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); /** * Create a bare clone of a remote repository. @@ -49,13 +52,18 @@ GIT_EXTERN(int) git_clone(git_repository **out, * @param out pointer that will receive the resulting repository object * @param origin_url repository to clone from * @param dest_path local directory to clone to - * @param fetch_stats pointer to structure that receives fetch progress information (may be NULL) + * @param fetch_progress_cb optional callback for fetch progress. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param fetch_progress_payload payload for fetch_progress_cb * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) */ -GIT_EXTERN(int) git_clone_bare(git_repository **out, - const char *origin_url, - const char *dest_path, - git_indexer_stats *fetch_stats); +GIT_EXTERN(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); /** @} */ GIT_END_DECL diff --git a/include/git2/errors.h b/include/git2/errors.h index 38b7fe0ae..8bb47f354 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -28,6 +28,7 @@ enum { GIT_EUSER = -7, GIT_EBAREREPO = -8, GIT_EORPHANEDHEAD = -9, + GIT_EUNMERGED = -10, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, @@ -58,6 +59,7 @@ typedef enum { GITERR_SSL, GITERR_SUBMODULE, GITERR_THREAD, + GITERR_STASH, } git_error_t; /** diff --git a/include/git2/index.h b/include/git2/index.h index 062932e1a..1d91663d8 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -84,12 +84,12 @@ typedef struct git_index_entry { char *path; } git_index_entry; -/** Representation of an unmerged file entry in the index. */ -typedef struct git_index_entry_unmerged { +/** Representation of a resolve undo entry in the index. */ +typedef struct git_index_reuc_entry { unsigned int mode[3]; git_oid oid[3]; char *path; -} git_index_entry_unmerged; +} git_index_reuc_entry; /** Capabilities of system that affect index actions. */ enum { @@ -99,6 +99,12 @@ enum { GIT_INDEXCAP_FROM_OWNER = ~0u }; +/** @name Index File Functions + * + * These functions work on the index file itself. + */ +/**@{*/ + /** * Create a new bare Git index object as a memory representation * of the Git index file in 'index_path', without a repository @@ -120,20 +126,19 @@ enum { GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path); /** - * Clear the contents (all the entries) of an index object. - * This clears the index object in memory; changes must be manually - * written to disk for them to take effect. + * Free an existing index object. * * @param index an existing index object */ -GIT_EXTERN(void) git_index_clear(git_index *index); +GIT_EXTERN(void) git_index_free(git_index *index); /** - * Free an existing index object. + * Get the repository this index relates to * - * @param index an existing index object + * @param index The index + * @return A pointer to the repository */ -GIT_EXTERN(void) git_index_free(git_index *index); +GIT_EXTERN(git_repository *) git_index_owner(const git_index *index); /** * Read index capabilities flags. @@ -175,44 +180,92 @@ GIT_EXTERN(int) git_index_read(git_index *index); GIT_EXTERN(int) git_index_write(git_index *index); /** - * Find the first index of any entries which point to given - * path in the Git index. + * Read a tree into the index file with stats + * + * The current index contents will be replaced by the specified tree. * * @param index an existing index object - * @param path path to search - * @return an index >= 0 if found, -1 otherwise + * @param tree tree to read + * @return 0 or an error code */ -GIT_EXTERN(int) git_index_find(git_index *index, const char *path); +GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree); + +/**@}*/ + +/** @name Raw Index Entry Functions + * + * These functions work on index entries, and allow for raw manipulation + * of the entries. + */ +/**@{*/ + +/* Index entry manipulation */ /** - * Remove all entries with equal path except last added + * Get the count of entries currently in the index * * @param index an existing index object + * @return integer of count of current entries */ -GIT_EXTERN(void) git_index_uniq(git_index *index); +GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index); /** - * Add or update an index entry from a file in disk + * Clear the contents (all the entries) of an index object. + * This clears the index object in memory; changes must be manually + * written to disk for them to take effect. * - * The file `path` must be relative to the repository's - * working folder and must be readable. + * @param index an existing index object + */ +GIT_EXTERN(void) git_index_clear(git_index *index); + +/** + * Get a pointer to one of the entries in the index * - * This method will fail in bare index instances. + * The values of this entry can be modified (except the path) + * and the changes will be written back to disk on the next + * write() call. * - * This forces the file to be added to the index, not looking - * at gitignore rules. Those rules can be evaluated through - * the git_status APIs (in status.h) before calling this. + * The entry should not be freed by the caller. * * @param index an existing index object - * @param path filename to add - * @param stage stage for the entry + * @param n the position of the entry + * @return a pointer to the entry; NULL if out of bounds + */ +GIT_EXTERN(git_index_entry *) git_index_get_byindex(git_index *index, size_t n); + +/** + * Get a pointer to one of the entries in the index + * + * The values of this entry can be modified (except the path) + * and the changes will be written back to disk on the next + * write() call. + * + * The entry should not be freed by the caller. + * + * @param index an existing index object + * @param path path to search + * @param stage stage to search + * @return a pointer to the entry; NULL if it was not found + */ +GIT_EXTERN(git_index_entry *) git_index_get_bypath(git_index *index, const char *path, int stage); + +/** + * Remove an entry from the index + * + * @param index an existing index object + * @param path path to search + * @param stage stage to search * @return 0 or an error code */ -GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); +GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage); /** * Add or update an index entry from an in-memory struct * + * If a previous index entry exists that has the same path and stage + * as the given 'source_entry', it will be replaced. Otherwise, the + * 'source_entry' will be added. + * * A full copy (including the 'path' string) of the given * 'source_entry' will be inserted on the index. * @@ -220,133 +273,207 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); * @param source_entry new entry object * @return 0 or an error code */ -GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_entry); /** - * Add (append) an index entry from a file in disk + * Return the stage number from a git index entry + * + * This entry is calculated from the entry's flag + * attribute like this: + * + * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT * - * A new entry will always be inserted into the index; - * if the index already contains an entry for such - * path, the old entry will **not** be replaced. + * @param entry The entry + * @returns the stage number + */ +GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); + +/**@}*/ + +/** @name Workdir Index Entry Functions + * + * These functions work on index entries specifically in the working + * directory (ie, stage 0). + */ +/**@{*/ + +/** + * Add or update an index entry from a file in disk * * The file `path` must be relative to the repository's * working folder and must be readable. * * This method will fail in bare index instances. * + * This forces the file to be added to the index, not looking + * at gitignore rules. Those rules can be evaluated through + * the git_status APIs (in status.h) before calling this. + * + * If this file currently is the result of a merge conflict, this + * file will no longer be marked as conflicting. The data about + * the conflict will be moved to the "resolve undo" (REUC) section. + * * @param index an existing index object * @param path filename to add - * @param stage stage for the entry * @return 0 or an error code */ -GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); +GIT_EXTERN(int) git_index_add_from_workdir(git_index *index, const char *path); /** - * Add (append) an index entry from an in-memory struct + * Find the first index of any entries which point to given + * path in the Git index. * - * A new entry will always be inserted into the index; - * if the index already contains an entry for the path - * in the `entry` struct, the old entry will **not** be - * replaced. + * @param index an existing index object + * @param path path to search + * @return an index >= 0 if found, -1 otherwise + */ +GIT_EXTERN(int) git_index_find(git_index *index, const char *path); + +/**@}*/ + +/** @name Conflict Index Entry Functions * - * A full copy (including the 'path' string) of the given - * 'source_entry' will be inserted on the index. + * These functions work on conflict index entries specifically (ie, stages 1-3) + */ +/**@{*/ + +/** + * Add or update index entries to represent a conflict + * + * The entries are the entries from the tree included in the merge. Any + * entry may be null to indicate that that file was not present in the + * trees during the merge. For example, ancestor_entry may be NULL to + * indicate that a file was added in both branches and must be resolved. * * @param index an existing index object - * @param source_entry new entry object + * @param ancestor_entry the entry data for the ancestor of the conflict + * @param our_entry the entry data for our side of the merge conflict + * @param their_entry the entry data for their side of the merge conflict * @return 0 or an error code */ -GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(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); /** - * Remove an entry from the index + * Get the index entries that represent a conflict of a single file. * + * The values of this entry can be modified (except the paths) + * and the changes will be written back to disk on the next + * write() call. + * + * @param ancestor_out Pointer to store the ancestor entry + * @param our_out Pointer to store the our entry + * @param their_out Pointer to store the their entry * @param index an existing index object - * @param position position of the entry to remove - * @return 0 or an error code + * @param path path to search */ -GIT_EXTERN(int) git_index_remove(git_index *index, int position); - +GIT_EXTERN(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); /** - * Get a pointer to one of the entries in the index + * Removes the index entries that represent a conflict of a single file. * - * This entry can be modified, and the changes will be written - * back to disk on the next write() call. - * - * The entry should not be freed by the caller. + * @param index an existing index object + * @param path to search + */ +GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path); + +/** + * Remove all conflicts in the index (entries with a stage greater than 0.) * * @param index an existing index object - * @param n the position of the entry - * @return a pointer to the entry; NULL if out of bounds */ -GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, size_t n); +GIT_EXTERN(void) git_index_conflict_cleanup(git_index *index); + +/**@}*/ + +/** @name Resolve Undo (REUC) index entry manipulation. + * + * These functions work on the Resolve Undo index extension and contains + * data about the original files that led to a merge conflict. + */ +/**@{*/ /** - * Get the count of entries currently in the index + * Get the count of resolve undo entries currently in the index. * * @param index an existing index object - * @return integer of count of current entries + * @return integer of count of current resolve undo entries */ -GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index); +GIT_EXTERN(unsigned int) git_index_reuc_entrycount(git_index *index); /** - * Get the count of unmerged entries currently in the index + * Finds the resolve undo entry that points to the given path in the Git + * index. * * @param index an existing index object - * @return integer of count of current unmerged entries + * @param path path to search + * @return an index >= 0 if found, -1 otherwise */ -GIT_EXTERN(unsigned int) git_index_entrycount_unmerged(git_index *index); +GIT_EXTERN(int) git_index_reuc_find(git_index *index, const char *path); /** - * Get an unmerged entry from the index. + * Get a resolve undo entry from the index. * * The returned entry is read-only and should not be modified * of freed by the caller. * * @param index an existing index object * @param path path to search - * @return the unmerged entry; NULL if not found + * @return the resolve undo entry; NULL if not found */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path); +GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path); /** - * Get an unmerged entry from the index. + * Get a resolve undo entry from the index. * * The returned entry is read-only and should not be modified * of freed by the caller. * * @param index an existing index object * @param n the position of the entry - * @return a pointer to the unmerged entry; NULL if out of bounds + * @return a pointer to the resolve undo entry; NULL if out of bounds */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, size_t n); +GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n); /** - * Return the stage number from a git index entry + * Adds an resolve undo entry for a file based on the given parameters. * - * This entry is calculated from the entry's flag - * attribute like this: + * The resolve undo entry contains the OIDs of files that were involved + * in a merge conflict after the conflict has been resolved. This allows + * conflicts to be re-resolved later. * - * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT + * If there exists a resolve undo entry for the given path in the index, + * it will be removed. * - * @param entry The entry - * @returns the stage number + * This method will fail in bare index instances. + * + * @param index an existing index object + * @param path filename to add + * @param ancestor_mode mode of the ancestor file + * @param ancestor_oid oid of the ancestor file + * @param our_mode mode of our file + * @param our_oid oid of our file + * @param their_mode mode of their file + * @param their_oid oid of their file + * @return 0 or an error code */ -GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); +GIT_EXTERN(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); /** - * Read a tree into the index file with stats - * - * The current index contents will be replaced by the specified tree. The total - * node count is collected in stats. + * Remove an resolve undo entry from the index * * @param index an existing index object - * @param tree tree to read - * @param stats structure that receives the total node count (may be NULL) + * @param position position of the resolve undo entry to remove * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats); +GIT_EXTERN(int) git_index_reuc_remove(git_index *index, int position); + +/**@}*/ /** @} */ GIT_END_DECL diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 87f48fe27..a2a155473 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -16,13 +16,19 @@ GIT_BEGIN_DECL * This is passed as the first argument to the callback to allow the * user to see the progress. */ -typedef struct git_indexer_stats { - unsigned int total; - unsigned int processed; - unsigned int received; -} git_indexer_stats; +typedef struct git_transfer_progress { + unsigned int total_objects; + unsigned int indexed_objects; + unsigned int received_objects; + size_t received_bytes; +} git_transfer_progress; +/** + * Type for progress callbacks during indexing + */ +typedef void (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload); + typedef struct git_indexer git_indexer; typedef struct git_indexer_stream git_indexer_stream; @@ -31,8 +37,14 @@ typedef struct git_indexer_stream git_indexer_stream; * * @param out where to store the indexer instance * @param path to the directory where the packfile should be stored + * @param progress_cb function to call with progress information + * @param progress_payload payload for the progress callback */ -GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *path); +GIT_EXTERN(int) git_indexer_stream_new( + git_indexer_stream **out, + const char *path, + git_transfer_progress_callback progress_cb, + void *progress_callback_payload); /** * Add data to the indexer @@ -42,7 +54,7 @@ GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *pat * @param size the size of the data * @param stats stat storage */ -GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats); /** * Finalize the pack and index @@ -51,7 +63,7 @@ GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data * * @param idx the indexer */ -GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats); /** * Get the packfile's hash @@ -88,7 +100,7 @@ GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname); * @param idx the indexer instance * @param stats storage for the running state */ -GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_transfer_progress *stats); /** * Write the index file to disk. diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 447915ef8..d48a89725 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -97,9 +97,9 @@ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog /** * Remove an entry from the reflog by its index * - * To ensure there's no gap in the log history, set the `rewrite_previosu_entry` to 1. - * When deleting entry `n`, member old_oid of entry `n-1` (if any) will be updated with - * the value of memeber new_oid of entry `n+1`. + * To ensure there's no gap in the log history, set `rewrite_previous_entry` + * param value to 1. When deleting entry `n`, member old_oid of entry `n-1` + * (if any) will be updated with the value of member new_oid of entry `n+1`. * * @param reflog a previously loaded reflog. * diff --git a/include/git2/remote.h b/include/git2/remote.h index 6471acc6a..ad5c38902 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -183,10 +183,16 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void * filename will be NULL and the function will return success. * * @param remote the remote to download from - * @param filename where to store the temporary filename + * @param progress_cb function to call with progress information. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param progress_payload payload for the progress callback * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); +GIT_EXTERN(int) git_remote_download( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload); /** * Check whether the remote is connected @@ -313,6 +319,11 @@ struct git_remote_callbacks { */ GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks); +/** + * Get the statistics structure that is filled in by the fetch operation. + */ +GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote); + enum { GIT_REMOTE_DOWNLOAD_TAGS_UNSET, GIT_REMOTE_DOWNLOAD_TAGS_NONE, @@ -336,6 +347,24 @@ GIT_EXTERN(int) git_remote_autotag(git_remote *remote); */ GIT_EXTERN(void) git_remote_set_autotag(git_remote *remote, int value); +/** + * Give the remote a new name + * + * All remote-tracking branches and configuration settings + * for the remote are updated. + * + * @param remote the remote to rename + * @param new_name the new name the remote should bear + * @param callback Optional callback to notify the consumer of fetch refspecs + * that haven't been automatically updated and need potential manual tweaking. + * @param payload Additional data to pass to the callback + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_rename( + git_remote *remote, + const char *new_name, + int (*callback)(const char *problematic_refspec, void *payload), + void *payload); /** @} */ GIT_END_DECL diff --git a/include/git2/repository.h b/include/git2/repository.h index 193ac9523..4d122265c 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -569,6 +569,28 @@ GIT_EXTERN(int) git_repository_set_head_detached( GIT_EXTERN(int) git_repository_detach_head( git_repository* repo); +typedef enum { + GIT_REPOSITORY_STATE_NONE, + GIT_REPOSITORY_STATE_MERGE, + GIT_REPOSITORY_STATE_REVERT, + GIT_REPOSITORY_STATE_CHERRY_PICK, + GIT_REPOSITORY_STATE_BISECT, + GIT_REPOSITORY_STATE_REBASE, + GIT_REPOSITORY_STATE_REBASE_INTERACTIVE, + GIT_REPOSITORY_STATE_REBASE_MERGE, + GIT_REPOSITORY_STATE_APPLY_MAILBOX, + GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE, +} git_repository_state_t; + +/** + * Determines the status of a git repository - ie, whether an operation + * (merge, cherry-pick, etc) is in progress. + * + * @param repo Repository pointer + * @return The state of the repository + */ +GIT_EXTERN(int) git_repository_state(git_repository *repo); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/stash.h b/include/git2/stash.h new file mode 100644 index 000000000..3ecd9e88d --- /dev/null +++ b/include/git2/stash.h @@ -0,0 +1,122 @@ +/* + * 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_git_stash_h__ +#define INCLUDE_git_stash_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/stash.h + * @brief Git stash management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_STASH_DEFAULT = 0, + + /* All changes already added to the index + * are left intact in the working directory + */ + GIT_STASH_KEEP_INDEX = (1 << 0), + + /* All untracked files are also stashed and then + * cleaned up from the working directory + */ + GIT_STASH_INCLUDE_UNTRACKED = (1 << 1), + + /* All ignored files are also stashed and then + * cleaned up from the working directory + */ + GIT_STASH_INCLUDE_IGNORED = (1 << 2), +}; + +/** + * Save the local modifications to a new stash. + * + * @param out Object id of the commit containing the stashed state. + * This commit is also the target of the direct reference refs/stash. + * + * @param repo The owning repository. + * + * @param stasher The identity of the person performing the stashing. + * + * @param message Optional description along with the stashed state. + * + * @param flags Flags to control the stashing process. + * + * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash, + * or error code. + */ + +GIT_EXTERN(int) git_stash_save( + git_oid *out, + git_repository *repo, + git_signature *stasher, + const char *message, + uint32_t flags); + +/** + * When iterating over all the stashed states, callback that will be + * issued per entry. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @param message The stash message. + * + * @param stash_oid The commit oid of the stashed state. + * + * @param payload Extra parameter to callback function. + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +typedef int (*stash_cb)( + size_t index, + const char* message, + const git_oid *stash_oid, + void *payload); + +/** + * Loop over all the stashed states and issue a callback for each one. + * + * If the callback returns a non-zero value, this will stop looping. + * + * @param repo Repository where to find the stash. + * + * @param callabck Callback to invoke per found stashed state. The most recent + * stash state will be enumerated first. + * + * @param payload Extra parameter to callback function. + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_stash_foreach( + git_repository *repo, + stash_cb callback, + void *payload); + +/** + * Remove a single stashed state from the stash list. + * + * @param repo The owning repository. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @return 0 on success, or error code + */ + +GIT_EXTERN(int) git_stash_drop( + git_repository *repo, + size_t index); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/attr.c b/src/attr.c index 025ad3c87..f5e09cc08 100644 --- a/src/attr.c +++ b/src/attr.c @@ -307,7 +307,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; diff --git a/src/branch.c b/src/branch.c index 991314508..43bebd9ef 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; } 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/checkout.c b/src/checkout.c index b56b459d2..b7bfa409a 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -27,12 +27,13 @@ struct checkout_diff_data 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; }; static int buffer_to_file( @@ -158,6 +159,18 @@ static int checkout_submodule( return 0; } +static void report_progress( + struct checkout_diff_data *data, + const char *path) +{ + if (data->checkout_opts->progress_cb) + data->checkout_opts->progress_cb( + path, + data->completed_steps, + data->total_steps, + data->checkout_opts->progress_payload); +} + static int checkout_blob( struct checkout_diff_data *data, const git_diff_file *file) @@ -191,7 +204,6 @@ static int checkout_remove_the_old( git_checkout_opts *opts = data->checkout_opts; GIT_UNUSED(progress); - data->stats->processed++; if ((delta->status == GIT_DELTA_UNTRACKED && (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) || @@ -202,6 +214,9 @@ static int checkout_remove_the_old( delta->new_file.path, git_repository_workdir(data->owner), GIT_DIRREMOVAL_FILES_AND_DIRS); + + data->completed_steps++; + report_progress(data, delta->new_file.path); } return data->error; @@ -216,7 +231,6 @@ static int checkout_create_the_new( bool do_checkout = false, do_notify = false; GIT_UNUSED(progress); - data->stats->processed++; if (delta->status == GIT_DELTA_MODIFIED || delta->status == GIT_DELTA_TYPECHANGE) @@ -243,14 +257,22 @@ static int checkout_create_the_new( if (do_checkout) { bool is_submodule = S_ISGITLINK(delta->old_file.mode); - if (is_submodule) + if (is_submodule) { data->found_submodules = true; + } - if (!is_submodule && !data->create_submodules) + if (!is_submodule && !data->create_submodules) { error = checkout_blob(data, &delta->old_file); + data->completed_steps++; + report_progress(data, delta->old_file.path); + } - else if (is_submodule && data->create_submodules) + else if (is_submodule && data->create_submodules) { error = checkout_submodule(data, &delta->old_file); + data->completed_steps++; + report_progress(data, delta->old_file.path); + } + } if (error) @@ -304,11 +326,9 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * int git_checkout_index( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats) + 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; @@ -339,20 +359,13 @@ 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; + data.total_steps = (size_t)git_diff_num_deltas(diff); if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) goto cleanup; @@ -367,6 +380,8 @@ int git_checkout_index( * checked out during pass #2. */ + report_progress(&data, NULL); + if (!(error = git_diff_foreach( diff, &data, checkout_remove_the_old, NULL, NULL)) && !(error = git_diff_foreach( @@ -378,7 +393,7 @@ int git_checkout_index( diff, &data, checkout_create_the_new, NULL, NULL); } - stats->processed = stats->total; + report_progress(&data, NULL); cleanup: if (error == GIT_EUSER) @@ -393,8 +408,7 @@ cleanup: int git_checkout_tree( git_repository *repo, git_object *treeish, - git_checkout_opts *opts, - git_indexer_stats *stats) + git_checkout_opts *opts) { git_index *index = NULL; git_tree *tree = NULL; @@ -411,13 +425,13 @@ int git_checkout_tree( if ((error = git_repository_index(&index, repo)) < 0) goto cleanup; - if ((error = git_index_read_tree(index, tree, NULL)) < 0) + if ((error = git_index_read_tree(index, tree)) < 0) goto cleanup; if ((error = git_index_write(index)) < 0) goto cleanup; - error = git_checkout_index(repo, opts, stats); + error = git_checkout_index(repo, opts); cleanup: git_index_free(index); @@ -427,8 +441,7 @@ cleanup: int git_checkout_head( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats) + git_checkout_opts *opts) { git_reference *head; int error; @@ -442,7 +455,7 @@ int git_checkout_head( if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0) goto cleanup; - error = git_checkout_tree(repo, tree, opts, stats); + error = git_checkout_tree(repo, tree, opts); cleanup: git_reference_free(head); diff --git a/src/clone.c b/src/clone.c index 85e69ad97..ab8b9bcbb 100644 --- a/src/clone.c +++ b/src/clone.c @@ -248,22 +248,20 @@ 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)) { /* 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,23 +309,21 @@ 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); @@ -338,15 +334,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 +352,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 +373,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/config.c b/src/config.c index f9bd205af..937510d7e 100644 --- a/src/config.c +++ b/src/config.c @@ -400,7 +400,7 @@ static int get_string_at_file(const char **out, git_config_file *file, const cha *out = NULL; res = file->get(file, name, &entry); - if (res != GIT_ENOTFOUND) + if (!res) *out = entry->value; return res; @@ -720,3 +720,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..a0569ec93 100644 --- a/src/config.h +++ b/src/config.h @@ -27,4 +27,9 @@ extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); +extern int git_config_rename_section( + git_repository *repo, + const char *old_section_name, /* eg "branch.dummy" */ + const char *new_section_name); /* NULL to drop the old section */ + #endif diff --git a/src/fetch.c b/src/fetch.c index dc01f6791..0aabe744f 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -19,6 +19,8 @@ #include "netops.h" #include "pkt.h" +#define NETWORK_XFER_THRESHOLD (100*1024) + struct filter_payload { git_remote *remote; const git_refspec *spec, *tagspec; @@ -302,7 +304,10 @@ on_error: return error; } -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; @@ -310,13 +315,14 @@ int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_st return 0; if (t->own_logic) - return t->download_pack(t, remote->repo, bytes, stats); + return t->download_pack(t, remote->repo, &remote->stats); - return git_fetch__download_pack(t, remote->repo, bytes, stats); + return git_fetch__download_pack(t, remote->repo, &remote->stats, + progress_cb, progress_payload); } -static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_off_t *bytes, git_indexer_stats *stats) +static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_transfer_progress *stats) { int recvd; @@ -333,8 +339,6 @@ static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer * if ((recvd = gitno_recv(buf)) < 0) return -1; - - *bytes += recvd; } while(recvd > 0); if (git_indexer_stream_finalize(idx, stats)) @@ -343,27 +347,58 @@ static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer * return 0; } +struct network_packetsize_payload +{ + git_transfer_progress_callback callback; + void *payload; + git_transfer_progress *stats; + git_off_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); + } +} + /* 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_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload) { git_buf path = GIT_BUF_INIT; gitno_buffer *buf = &t->buffer; git_indexer_stream *idx = NULL; int error = -1; + struct network_packetsize_payload npp = {0}; + + if (progress_cb) { + npp.callback = progress_cb; + npp.payload = progress_payload; + npp.stats = stats; + buf->packetsize_cb = &network_packetsize; + buf->packetsize_payload = &npp; + } 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) + if (git_indexer_stream_new(&idx, git_buf_cstr(&path), progress_cb, progress_payload) < 0) goto on_error; git_buf_free(&path); - memset(stats, 0, sizeof(git_indexer_stats)); - *bytes = 0; + memset(stats, 0, sizeof(git_transfer_progress)); /* * If the remote doesn't support the side-band, we can feed @@ -371,7 +406,7 @@ int git_fetch__download_pack( * check which one belongs there. */ if (!t->caps.side_band && !t->caps.side_band_64k) { - if (no_sideband(t, idx, buf, bytes, stats) < 0) + if (no_sideband(t, idx, buf, stats) < 0) goto on_error; git_indexer_stream_free(idx); @@ -398,7 +433,6 @@ int git_fetch__download_pack( 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; 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/index.c b/src/index.c index f9f3b14cc..44dd93417 100644 --- a/src/index.c +++ b/src/index.c @@ -81,6 +81,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 +95,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; + + ret = strcasecmp(srch_key->path, entry->path); + + if (ret == 0) + ret = srch_key->stage - index_entry_stage(entry); - return strcasecmp(key, entry->path); + 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 unmerged_srch(const void *key, const void *array_member) +static int reuc_srch(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 strcmp(key, reuc->path); } -static int unmerged_cmp(const void *a, const void *b) +static int reuc_isrch(const void *key, const void *array_member) { - const git_index_entry_unmerged *info_a = a; - const git_index_entry_unmerged *info_b = b; + const git_index_reuc_entry *reuc = array_member; + + return strcasecmp(key, reuc->path); +} + +static int reuc_cmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; return strcmp(info_a->path, info_b->path); } +static int reuc_icmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcasecmp(info_a->path, info_b->path); +} + static unsigned int index_create_mode(unsigned int mode) { if (S_ISLNK(mode)) @@ -165,9 +243,16 @@ 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) @@ -185,7 +270,10 @@ int git_index_open(git_index **index_out, const char *index_path) if (git_vector_init(&index->entries, 32, index_cmp) < 0) return -1; + index->entries_cmp_path = index_cmp_path; index->entries_search = index_srch; + index->entries_search_path = index_srch_path; + index->reuc_search = reuc_srch; /* Check if index file is stored on disk already */ if (git_path_exists(index->index_file_path) == true) @@ -199,6 +287,7 @@ int git_index_open(git_index **index_out, const char *index_path) 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 +295,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,15 +325,15 @@ 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); + git_vector_clear(&index->reuc); index->last_modified = 0; git_tree_cache_free(index->tree); @@ -340,6 +429,7 @@ int git_index_write(git_index *index) int error; 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) @@ -367,16 +457,25 @@ unsigned int git_index_entrycount(git_index *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) @@ -393,7 +492,7 @@ 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) +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,8 +501,6 @@ 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 || (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL) { @@ -436,7 +533,6 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const git_index__init_entry_from_stat(&st, entry); entry->oid = oid; - entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); entry->path = git__strdup(rel_path); GITERR_CHECK_ALLOC(entry->path); @@ -444,6 +540,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 +622,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 +646,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 +704,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 +713,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 +736,277 @@ 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_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_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; +} + +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; + + assert(index && path); + + 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; + } + + git_vector_remove(&index->entries, (unsigned int)pos); + } + + return 0; +} + +static int index_conflicts_match(git_vector *v, size_t idx) { - git_vector_uniq(&index->entries); + git_index_entry *entry = git_vector_get(v, idx); + + if (index_entry_stage(entry) > 0) + return 1; + + return 0; } -const git_index_entry_unmerged *git_index_get_unmerged_bypath( +void git_index_conflict_cleanup(git_index *index) +{ + assert(index); + git_vector_remove_matching(&index->entries, index_conflicts_match); +} + +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->unmerged.length) + 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 +1015,26 @@ 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) + if (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 +1051,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 +1068,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 +1076,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 +1184,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 +1383,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 +1468,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 +1483,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 +1511,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 +1519,14 @@ 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}; - - if (!stats) stats = &dummy_stats; - stats->total = 0; - rtd.stats = stats; - git_index_clear(index); - return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd); + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); +} + +git_repository *git_index_owner(const git_index *index) +{ + return INDEX_OWNER(index); } diff --git a/src/index.h b/src/index.h index 7dd23ee60..0fd59dd45 100644 --- a/src/index.h +++ b/src/index.h @@ -33,9 +33,12 @@ 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); diff --git a/src/indexer.c b/src/indexer.c index 7d4e18d7a..ec4ef7147 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -49,6 +49,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 +140,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 +153,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) @@ -273,7 +281,13 @@ on_error: 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; @@ -455,7 +474,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; } @@ -782,7 +801,7 @@ cleanup: 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 +816,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 +887,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..5fac41046 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -336,7 +336,7 @@ 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 && 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..d9663e63c 100644 --- a/src/netops.c +++ b/src/netops.c @@ -117,6 +117,7 @@ static int gitno__recv_ssl(gitno_buffer *buf) } buf->offset += ret; + if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload); return ret; } #endif @@ -132,6 +133,7 @@ int gitno__recv(gitno_buffer *buf) } buf->offset += ret; + if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload); return ret; } @@ -142,7 +144,6 @@ void gitno_buffer_setup_callback( 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; diff --git a/src/netops.h b/src/netops.h index 7c53fd0dc..64da7fba9 100644 --- a/src/netops.h +++ b/src/netops.h @@ -20,6 +20,8 @@ struct gitno_buffer { #endif int (*recv)(gitno_buffer *buffer); void *cb_data; + void (*packetsize_cb)(int received, void *payload); + void *packetsize_payload; }; void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len); diff --git a/src/reflog.c b/src/reflog.c index a1ea7a27d..5d1465eca 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, @@ -463,26 +469,26 @@ int git_reflog_drop( if (!rewrite_previous_entry) return 0; - /* No need to rewrite anything when removing the first entry */ - if (idx == 0) + /* No need to rewrite anything when removing the most recent entry */ + if (idx == entrycount - 1) return 0; /* There are no more entries in the log */ if (entrycount == 1) return 0; - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - /* If the last entry has just been removed... */ - if (idx == entrycount - 1) { - /* ...clear the oid_old member of the "new" last entry */ + /* If the oldest entry has just been removed... */ + if (idx == 0) { + /* ...clear the oid_old member of the "new" oldest entry */ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) return -1; return 0; } - previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); git_oid_cpy(&entry->oid_old, &previous->oid_cur); return 0; 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..8b69e9d8e 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -225,3 +225,14 @@ 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; +} diff --git a/src/refspec.h b/src/refspec.h index 6e0596a55..40da16afc 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -51,4 +51,6 @@ 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); + #endif diff --git a/src/remote.c b/src/remote.c index e05ea059f..d3a2c4564 100644 --- a/src/remote.c +++ b/src/remote.c @@ -201,12 +201,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; @@ -239,33 +302,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 +349,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; } @@ -472,16 +519,19 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) return 0; } -int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +int git_remote_download( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload) { int error; - assert(remote && bytes && stats); + assert(remote); if ((error = git_fetch_negotiate(remote)) < 0) return error; - return git_fetch_download_pack(remote, bytes, stats); + return git_fetch_download_pack(remote, progress_cb, progress_payload); } int git_remote_update_tips(git_remote *remote) @@ -742,6 +792,12 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback } } +const git_transfer_progress* git_remote_stats(git_remote *remote) +{ + assert(remote); + return &remote->stats; +} + int git_remote_autotag(git_remote *remote) { return remote->download_tags; @@ -751,3 +807,288 @@ 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; +} diff --git a/src/remote.h b/src/remote.h index 05073db8c..1b382e1bb 100644 --- a/src/remote.h +++ b/src/remote.h @@ -25,6 +25,7 @@ struct git_remote { 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; diff --git a/src/repository.c b/src/repository.c index 43e0eda8f..0e416e0b8 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:" @@ -1348,15 +1349,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 +1381,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 +1540,43 @@ 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_repository_head_detached(repo)) + return state; + + 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..7df1c1a57 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,6 +126,11 @@ 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; @@ -129,7 +142,7 @@ int git_reset( | GIT_CHECKOUT_OVERWRITE_MODIFIED | GIT_CHECKOUT_REMOVE_UNTRACKED; - if (git_checkout_index(repo, &opts, NULL) < 0) { + if (git_checkout_index(repo, &opts) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); goto cleanup; } diff --git a/src/stash.c b/src/stash.c new file mode 100644 index 000000000..9c9c5dce7 --- /dev/null +++ b/src/stash.c @@ -0,0 +1,659 @@ +/* + * 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_tree_create_fromindex(&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(git_index_owner(index), &opts, i_tree, &diff) < 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_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(git_index_owner(index), &opts, b_tree, &diff) < 0) + goto cleanup; + + if (git_diff_workdir_to_index(git_index_owner(index), &opts, &diff2) < 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_CREATE_MISSING | GIT_CHECKOUT_OVERWRITE_MODIFIED; + + 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, max - i - 1); + + 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, max - index - 1, 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/submodule.c b/src/submodule.c index e3657f9ad..d69559dc2 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); @@ -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) diff --git a/src/transport.h b/src/transport.h index 4c944b9e7..1a3eee57d 100644 --- a/src/transport.h +++ b/src/transport.h @@ -113,7 +113,7 @@ struct git_transport { /** * Download the packfile */ - int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); + int (*download_pack)(struct git_transport *transport, git_repository *repo, git_transfer_progress *stats); /** * Close the connection */ diff --git a/src/transports/http.c b/src/transports/http.c index 93dd0c326..8042aeba1 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -379,6 +379,8 @@ static int http_recv_cb(gitno_buffer *buf) #ifndef GIT_WINHTTP gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); + inner.packetsize_cb = buf->packetsize_cb; + inner.packetsize_payload = buf->packetsize_payload; if ((error = gitno_recv(&inner)) < 0) return -1; @@ -401,6 +403,7 @@ static int http_recv_cb(gitno_buffer *buf) memcpy(buf->data + buf->offset, buffer, recvd); buf->offset += recvd; + if (buf->packetsize_cb) buf->packetsize_cb(recvd, buf->packetsize_payload); #endif return (int)(buf->offset - old_len); diff --git a/src/tree.c b/src/tree.c index 8d3f2665c..9ecefbb61 100644 --- a/src/tree.c +++ b/src/tree.c @@ -353,7 +353,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] != '/')) { @@ -415,7 +415,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; /* diff --git a/src/vector.c b/src/vector.c index c6a644cc3..d9c4c9900 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); diff --git a/src/vector.h b/src/vector.h index 49ba754f0..32f790c71 100644 --- a/src/vector.h +++ b/src/vector.h @@ -75,5 +75,6 @@ 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)); #endif diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c index 4a317e4f3..b51d5e335 100644 --- a/tests-clar/attr/repo.c +++ b/tests-clar/attr/repo.c @@ -270,12 +270,12 @@ static void assert_proper_normalization(git_index *index, const char *filename, git_index_entry *entry; add_to_workdir(filename, CONTENT); - cl_git_pass(git_index_add(index, filename, 0)); + cl_git_pass(git_index_add_from_workdir(index, filename)); index_pos = git_index_find(index, filename); cl_assert(index_pos >= 0); - entry = git_index_get(index, index_pos); + entry = git_index_get_byindex(index, index_pos); cl_assert_equal_i(0, git_oid_streq(&entry->oid, expected_sha)); } diff --git a/tests-clar/buf/splice.c b/tests-clar/buf/splice.c new file mode 100644 index 000000000..e80c93105 --- /dev/null +++ b/tests-clar/buf/splice.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static git_buf _buf; + +void test_buf_splice__initialize(void) { + git_buf_init(&_buf, 16); +} + +void test_buf_splice__cleanup(void) { + git_buf_free(&_buf); +} + +void test_buf_splice__preprend(void) +{ + git_buf_sets(&_buf, "world!"); + + cl_git_pass(git_buf_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__append(void) +{ + git_buf_sets(&_buf, "Hello"); + + cl_git_pass(git_buf_splice(&_buf, git_buf_len(&_buf), 0, " world!", strlen(" world!"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__insert_at(void) +{ + git_buf_sets(&_buf, "Hell world!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__remove_at(void) +{ + git_buf_sets(&_buf, "Hello world of warcraft!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace(void) +{ + git_buf_sets(&_buf, "Hell0 w0rld!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace_with_longer(void) +{ + git_buf_sets(&_buf, "Hello you!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace_with_shorter(void) +{ + git_buf_sets(&_buf, "Brave new world!"); + + cl_git_pass(git_buf_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__truncate(void) +{ + git_buf_sets(&_buf, "Hello world!!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__dont_do_anything(void) +{ + git_buf_sets(&_buf, "Hello world!"); + + cl_git_pass(git_buf_splice(&_buf, 3, 0, "Hello", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} diff --git a/tests-clar/checkout/head.c b/tests-clar/checkout/head.c index d36034c52..103b9999e 100644 --- a/tests-clar/checkout/head.c +++ b/tests-clar/checkout/head.c @@ -18,5 +18,5 @@ void test_checkout_head__checking_out_an_orphaned_head_returns_GIT_EORPHANEDHEAD { make_head_orphaned(g_repo, NON_EXISTING_HEAD); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL, NULL)); + cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL)); } diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index f017a0fe2..89bc1da15 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -14,7 +14,7 @@ static void reset_index_to_treeish(git_object *treeish) cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE)); cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, (git_tree *)tree, NULL)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree)); cl_git_pass(git_index_write(index)); git_object_free(tree); @@ -69,7 +69,7 @@ void test_checkout_index__cannot_checkout_a_bare_repository(void) memset(&g_opts, 0, sizeof(g_opts)); g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); + cl_git_fail(git_checkout_index(g_repo, NULL)); } void test_checkout_index__can_create_missing_files(void) @@ -79,7 +79,7 @@ void test_checkout_index__can_create_missing_files(void) cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); @@ -95,7 +95,7 @@ void test_checkout_index__can_remove_untracked_files(void) cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir")); g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); } @@ -111,7 +111,7 @@ void test_checkout_index__honor_the_specified_pathspecs(void) cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); @@ -142,7 +142,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_mkfile("./testrepo/.gitattributes", attributes); set_core_autocrlf_to(false); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -157,7 +157,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(p_unlink("./testrepo/.gitattributes")); set_core_autocrlf_to(true); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/README", expected_readme_text); #endif @@ -172,7 +172,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) { set_repo_symlink_handling_cap_to(true); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); #ifdef GIT_WIN32 test_file_contents("./testrepo/link_to_new.txt", "new.txt"); @@ -194,7 +194,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) { set_repo_symlink_handling_cap_to(false); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/link_to_new.txt", "new.txt"); } @@ -204,7 +204,7 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void) cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); g_opts.checkout_strategy = 0; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); } @@ -214,7 +214,7 @@ void test_checkout_index__can_overwrite_modified_file(void) cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\n"); } @@ -224,14 +224,14 @@ void test_checkout_index__options_disable_filters(void) cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); g_opts.disable_filters = false; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\r\n"); p_unlink("./testrepo/new.txt"); g_opts.disable_filters = true; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\n"); } @@ -249,7 +249,7 @@ void test_checkout_index__options_dir_modes(void) reset_index_to_treeish((git_object *)commit); g_opts.dir_mode = 0701; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); cl_git_pass(p_stat("./testrepo/a", &st)); cl_assert_equal_i(st.st_mode & 0777, 0701); @@ -269,7 +269,7 @@ void test_checkout_index__options_override_file_modes(void) g_opts.file_mode = 0700; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); cl_git_pass(p_stat("./testrepo/new.txt", &st)); cl_assert_equal_i(st.st_mode & 0777, 0700); @@ -283,7 +283,7 @@ void test_checkout_index__options_open_flags(void) g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); } @@ -328,7 +328,7 @@ void test_checkout_index__can_notify_of_skipped_files(void) g_opts.skipped_notify_cb = notify_cb; g_opts.notify_payload = &data; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); } static int dont_notify_cb( @@ -358,5 +358,22 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) g_opts.skipped_notify_cb = dont_notify_cb; g_opts.notify_payload = NULL; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts)); +} + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_index__calls_progress_callback(void) +{ + bool was_called = 0; + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_checkout_index(g_repo, &g_opts)); + cl_assert_equal_i(was_called, true); } diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 6d573bfd7..c42aefc31 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -27,7 +27,7 @@ void test_checkout_tree__cannot_checkout_a_non_treeish(void) /* blob */ cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_fail(git_checkout_tree(g_repo, g_object, NULL, NULL)); + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL)); } void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) @@ -41,7 +41,7 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); @@ -58,8 +58,26 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt")); cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt")); } + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_tree__calls_progress_callback(void) +{ + bool was_called = 0; + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_assert_equal_i(was_called, true); +} diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c index f013617d5..e86af52ee 100644 --- a/tests-clar/checkout/typechange.c +++ b/tests-clar/checkout/typechange.c @@ -49,7 +49,7 @@ void test_checkout_typechange__checkout_typechanges(void) cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); /* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */ - cl_git_pass(git_checkout_tree(g_repo, obj, &opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); git_object_free(obj); diff --git a/tests-clar/clone/network.c b/tests-clar/clone/network.c index 1ebdfb5d1..19385f77a 100644 --- a/tests-clar/clone/network.c +++ b/tests-clar/clone/network.c @@ -43,7 +43,7 @@ void test_clone_network__network_bare(void) cl_set_cleanup(&cleanup_repository, "./test"); - cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL)); + cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL, NULL)); cl_assert(git_repository_is_bare(g_repo)); cl_git_pass(git_remote_load(&origin, g_repo, "origin")); @@ -91,18 +91,36 @@ void test_clone_network__can_prevent_the_checkout_of_a_standard_repo(void) git_buf_free(&path); } +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + (*was_called) = true; +} + +static void fetch_progress(const git_transfer_progress *stats, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(stats); + (*was_called) = true; +} + void test_clone_network__can_checkout_a_cloned_repo(void) { - git_checkout_opts opts; + git_checkout_opts opts = {0}; git_buf path = GIT_BUF_INIT; git_reference *head; + bool checkout_progress_cb_was_called = false, + fetch_progress_cb_was_called = false; - memset(&opts, 0, sizeof(opts)); opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + opts.progress_cb = &checkout_progress; + opts.progress_payload = &checkout_progress_cb_was_called; cl_set_cleanup(&cleanup_repository, "./default-checkout"); - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout", NULL, NULL, &opts)); + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout", + &fetch_progress, &fetch_progress_cb_was_called, &opts)); cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path))); @@ -111,6 +129,9 @@ void test_clone_network__can_checkout_a_cloned_repo(void) cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); cl_assert_equal_s("refs/heads/master", git_reference_target(head)); + cl_assert_equal_i(true, checkout_progress_cb_was_called); + cl_assert_equal_i(true, fetch_progress_cb_was_called); + git_reference_free(head); git_buf_free(&path); } diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c index 81f95b9b3..3984f3fe7 100644 --- a/tests-clar/clone/nonetwork.c +++ b/tests-clar/clone/nonetwork.c @@ -65,7 +65,7 @@ void test_clone_nonetwork__bad_url(void) /* Clone should clean up the mess if the URL isn't a git repository */ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", NULL, NULL, NULL)); cl_assert(!git_path_exists("./foo")); - cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL)); + cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL, NULL)); cl_assert(!git_path_exists("./foo.git")); } @@ -91,7 +91,7 @@ void test_clone_nonetwork__local_bare(void) #if DO_LOCAL_TEST cl_set_cleanup(&cleanup_repository, "./local.git"); - cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); + cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL, NULL)); #endif git_buf_free(&src); diff --git a/tests-clar/config/config_helpers.c b/tests-clar/config/config_helpers.c new file mode 100644 index 000000000..53bd945a0 --- /dev/null +++ b/tests-clar/config/config_helpers.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" +#include "config_helpers.h" +#include "repository.h" + +void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist) +{ + git_config *config; + const char *out; + int result; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + result = git_config_get_string(&out, config, name); + + if (is_supposed_to_exist) + cl_git_pass(result); + else + cl_assert_equal_i(GIT_ENOTFOUND, result); +} + +void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value) +{ + git_config *config; + const char *out; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + cl_git_pass(git_config_get_string(&out, config, name)); + + cl_assert_equal_s(expected_value, out); +} diff --git a/tests-clar/config/config_helpers.h b/tests-clar/config/config_helpers.h new file mode 100644 index 000000000..b887b3d38 --- /dev/null +++ b/tests-clar/config/config_helpers.h @@ -0,0 +1,9 @@ +extern void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist); + +extern void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value); diff --git a/tests-clar/core/vector.c b/tests-clar/core/vector.c index ef3d6c36d..b165905ae 100644 --- a/tests-clar/core/vector.c +++ b/tests-clar/core/vector.c @@ -189,3 +189,87 @@ void test_core_vector__5(void) git_vector_free(&x); } + +static int remove_ones(git_vector *v, size_t idx) +{ + return (git_vector_get(v, idx) == (void *)0x001); +} + +/* Test removal based on callback */ +void test_core_vector__remove_matching(void) +{ + git_vector x; + size_t i; + void *compare; + + git_vector_init(&x, 1, NULL); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 1); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 3); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 4); + + git_vector_free(&x); +} diff --git a/tests-clar/index/conflicts.c b/tests-clar/index/conflicts.c new file mode 100644 index 000000000..59df257c5 --- /dev/null +++ b/tests-clar/index/conflicts.c @@ -0,0 +1,217 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" +#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" +#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" + +#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" +#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" +#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" + +#define TEST_ANCESTOR_OID "f00ff00ff00ff00ff00ff00ff00ff00ff00ff00f" +#define TEST_OUR_OID "b44bb44bb44bb44bb44bb44bb44bb44bb44bb44b" +#define TEST_THEIR_OID "0123456789abcdef0123456789abcdef01234567" + +// Fixture setup and teardown +void test_index_conflicts__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_conflicts__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +void test_index_conflicts__add(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.oid, TEST_OUR_OID); + + their_entry.path = "test-one.txt"; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); +} + +void test_index_conflicts__add_fixes_incorrect_stage(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.oid, TEST_OUR_OID); + + their_entry.path = "test-one.txt"; + ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert(git_index_entry_stage(conflict_entry[0]) == 1); + cl_assert(git_index_entry_stage(conflict_entry[1]) == 2); + cl_assert(git_index_entry_stage(conflict_entry[2]) == 3); +} + +void test_index_conflicts__get(void) +{ + git_index_entry *conflict_entry[3]; + git_oid oid; + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-one.txt")); + + cl_assert(strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); + cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); + cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-two.txt")); + + cl_assert(strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0); +} + +void test_index_conflicts__remove(void) +{ + git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-one.txt")); + cl_assert(git_index_entrycount(repo_index) == 5); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); + } + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-two.txt")); + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-two.txt") != 0); + } +} + +void test_index_conflicts__moved_to_reuc(void) +{ + git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_mkfile("./mergedrepo/conflicts-one.txt", "new-file\n"); + + cl_git_pass(git_index_add_from_workdir(repo_index, "conflicts-one.txt")); + + cl_assert(git_index_entrycount(repo_index) == 6); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + + if (strcmp(entry->path, "conflicts-one.txt") == 0) + cl_assert(git_index_entry_stage(entry) == 0); + } +} + +void test_index_conflicts__remove_all_conflicts(void) +{ + size_t i; + git_index_entry *entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + git_index_conflict_cleanup(repo_index); + + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(git_index_entry_stage(entry) == 0); + } +} + +void test_index_conflicts__partial(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); + cl_assert(git_index_entrycount(repo_index) == 9); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert(git_oid_cmp(&ancestor_entry.oid, &conflict_entry[0]->oid) == 0); + cl_assert(conflict_entry[1] == NULL); + cl_assert(conflict_entry[2] == NULL); +} diff --git a/tests-clar/index/filemodes.c b/tests-clar/index/filemodes.c index 75c94e8e7..882d41748 100644 --- a/tests-clar/index/filemodes.c +++ b/tests-clar/index/filemodes.c @@ -25,7 +25,7 @@ void test_index_filemodes__read(void) cl_assert_equal_i(6, git_index_entrycount(index)); for (i = 0; i < 6; ++i) { - git_index_entry *entry = git_index_get(index, i); + git_index_entry *entry = git_index_get_byindex(index, i); cl_assert(entry != NULL); cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]); } @@ -56,35 +56,15 @@ static void add_and_check_mode( int pos; git_index_entry *entry; - cl_git_pass(git_index_add(index, filename, 0)); + cl_git_pass(git_index_add_from_workdir(index, filename)); pos = git_index_find(index, filename); cl_assert(pos >= 0); - entry = git_index_get(index, pos); + entry = git_index_get_byindex(index, pos); cl_assert(entry->mode == expect_mode); } -static void append_and_check_mode( - git_index *index, const char *filename, unsigned int expect_mode) -{ - unsigned int before, after; - git_index_entry *entry; - - before = git_index_entrycount(index); - - cl_git_pass(git_index_append(index, filename, 0)); - - after = git_index_entrycount(index); - cl_assert_equal_i(before + 1, after); - - /* bypass git_index_get since that resorts the index */ - entry = (git_index_entry *)git_vector_get(&index->entries, after - 1); - - cl_assert_equal_s(entry->path, filename); - cl_assert(expect_mode == entry->mode); -} - void test_index_filemodes__untrusted(void) { git_config *cfg; @@ -114,23 +94,7 @@ void test_index_filemodes__untrusted(void) replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - /* 5 - append 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 6 - append 0644 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 7 - append 0755 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 8 - append 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 9 - add new 0644 -> expect 0644 */ + /* 5 - add new 0644 -> expect 0644 */ cl_git_write2file("filemodes/new_off", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); @@ -139,7 +103,7 @@ void test_index_filemodes__untrusted(void) * that doesn't support filemodes correctly, so skip it. */ if (can_filemode) { - /* 10 - add 0755 -> expect 0755 */ + /* 6 - add 0755 -> expect 0755 */ cl_git_write2file("filemodes/new_on", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0755); add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); @@ -182,28 +146,12 @@ void test_index_filemodes__trusted(void) replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - /* 5 - append 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 6 - append 0644 over existing 0755 -> expect 0644 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB); - - /* 7 - append 0755 over existing 0644 -> expect 0755 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 8 - append 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 9 - add new 0644 -> expect 0644 */ + /* 5 - add new 0644 -> expect 0644 */ cl_git_write2file("filemodes/new_off", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); - /* 10 - add 0755 -> expect 0755 */ + /* 6 - add 0755 -> expect 0755 */ cl_git_write2file("filemodes/new_on", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0755); add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c index 0479332dc..f63a54bf2 100644 --- a/tests-clar/index/read_tree.c +++ b/tests-clar/index/read_tree.c @@ -24,16 +24,16 @@ void test_index_read_tree__read_write_involution(void) cl_git_mkfile("./read_tree/abc/d", NULL); cl_git_mkfile("./read_tree/abc_d", NULL); - cl_git_pass(git_index_add(index, "abc-d", 0)); - cl_git_pass(git_index_add(index, "abc_d", 0)); - cl_git_pass(git_index_add(index, "abc/d", 0)); + cl_git_pass(git_index_add_from_workdir(index, "abc-d")); + cl_git_pass(git_index_add_from_workdir(index, "abc_d")); + cl_git_pass(git_index_add_from_workdir(index, "abc/d")); /* write-tree */ cl_git_pass(git_tree_create_fromindex(&expected, index)); /* read-tree */ git_tree_lookup(&tree, repo, &expected); - cl_git_pass(git_index_read_tree(index, tree, NULL)); + cl_git_pass(git_index_read_tree(index, tree)); git_tree_free(tree); cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); diff --git a/tests-clar/index/rename.c b/tests-clar/index/rename.c index eecd257fd..e16ec00c1 100644 --- a/tests-clar/index/rename.c +++ b/tests-clar/index/rename.c @@ -19,28 +19,28 @@ void test_index_rename__single_file(void) cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); /* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */ - cl_git_pass(git_index_add(index, "lame.name.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "lame.name.txt")); cl_assert(git_index_entrycount(index) == 1); cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a")); position = git_index_find(index, "lame.name.txt"); - entry = git_index_get(index, position); + entry = git_index_get_byindex(index, position); cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); /* This removes the entry from the index, but not from the object database */ - cl_git_pass(git_index_remove(index, position)); + cl_git_pass(git_index_remove(index, "lame.name.txt", 0)); cl_assert(git_index_entrycount(index) == 0); p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt"); - cl_git_pass(git_index_add(index, "fancy.name.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "fancy.name.txt")); cl_assert(git_index_entrycount(index) == 1); position = git_index_find(index, "fancy.name.txt"); - entry = git_index_get(index, position); + entry = git_index_get_byindex(index, position); cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); git_index_free(index); diff --git a/tests-clar/index/reuc.c b/tests-clar/index/reuc.c new file mode 100644 index 000000000..c7c394444 --- /dev/null +++ b/tests-clar/index/reuc.c @@ -0,0 +1,236 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define ONE_ANCESTOR_OID "478871385b9cd03908c5383acfd568bef023c6b3" +#define ONE_OUR_OID "4458b8bc9e72b6c8755ae456f60e9844d0538d8c" +#define ONE_THEIR_OID "8b72416545c7e761b64cecad4f1686eae4078aa8" + +#define TWO_ANCESTOR_OID "9d81f82fccc7dcd7de7a1ffead1815294c2e092c" +#define TWO_OUR_OID "8f3c06cff9a83757cec40c80bc9bf31a2582bde9" +#define TWO_THEIR_OID "887b153b165d32409c70163e0f734c090f12f673" + +// Fixture setup and teardown +void test_index_reuc__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_reuc__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +void test_index_reuc__read_bypath(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "two.txt")); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "one.txt")); + + cl_assert(strcmp(reuc->path, "one.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__ignore_case(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + int index_caps; + + index_caps = git_index_caps(repo_index); + + index_caps &= ~GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert(!git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + index_caps |= GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__read_byindex(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "one.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__updates_existing(void) +{ + const git_index_reuc_entry *reuc; + git_oid ancestor_oid, our_oid, their_oid, oid; + int index_caps; + + git_index_clear(repo_index); + + index_caps = git_index_caps(repo_index); + + index_caps |= GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_reuc_add(repo_index, "TWO.txt", + 0100644, &our_oid, + 0100644, &their_oid, + 0100644, &ancestor_oid)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "TWO.txt") == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__remove(void) +{ + git_oid oid; + const git_index_reuc_entry *reuc; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_git_pass(git_index_reuc_remove(repo_index, 0)); + cl_git_fail(git_index_reuc_remove(repo_index, 1)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__write(void) +{ + git_oid ancestor_oid, our_oid, their_oid; + const git_index_reuc_entry *reuc; + + git_index_clear(repo_index); + + /* Write out of order to ensure sorting is correct */ + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); + git_oid_fromstr(&our_oid, ONE_OUR_OID); + git_oid_fromstr(&their_oid, ONE_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "one.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_write(repo_index)); + + cl_git_pass(git_index_read(repo_index)); + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + /* ensure sort order was round-tripped correct */ + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + cl_assert(strcmp(reuc->path, "one.txt") == 0); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + cl_assert(strcmp(reuc->path, "two.txt") == 0); +} + diff --git a/tests-clar/index/stage.c b/tests-clar/index/stage.c new file mode 100644 index 000000000..ba6f1b806 --- /dev/null +++ b/tests-clar/index/stage.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +// Fixture setup and teardown +void test_index_stage__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_stage__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + + +void test_index_stage__add_always_adds_stage_0(void) +{ + int entry_idx; + git_index_entry *entry; + + cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n"); + + cl_git_pass(git_index_add_from_workdir(repo_index, "new-file.txt")); + + cl_assert((entry_idx = git_index_find(repo_index, "new-file.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); +} + +void test_index_stage__find_gets_first_stage(void) +{ + int entry_idx; + git_index_entry *entry; + + cl_assert((entry_idx = git_index_find(repo_index, "one.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert((entry_idx = git_index_find(repo_index, "two.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert((entry_idx = git_index_find(repo_index, "conflicts-one.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); + + cl_assert((entry_idx = git_index_find(repo_index, "conflicts-two.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); +} + diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index a535d6815..cf971e1dd 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -231,15 +231,19 @@ void test_index_tests__add(void) cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* Add the new file to the index */ - cl_git_pass(git_index_add(index, "test.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "test.txt")); /* Wow... it worked! */ cl_assert(git_index_entrycount(index) == 1); - entry = git_index_get(index, 0); + entry = git_index_get_byindex(index, 0); /* And the built-in hashing mechanism worked as expected */ cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + git_index_free(index); git_repository_free(repo); } diff --git a/tests-clar/network/fetch.c b/tests-clar/network/fetch.c index 5ff7b0af8..be1a21c54 100644 --- a/tests-clar/network/fetch.c +++ b/tests-clar/network/fetch.c @@ -28,12 +28,17 @@ static int update_tips(const char *refname, const git_oid *a, const git_oid *b, return 0; } +static void progress(const git_transfer_progress *stats, void *payload) +{ + int *bytes_received = (int*)payload; + *bytes_received = stats->received_bytes; +} + static void do_fetch(const char *url, int flag, int n) { git_remote *remote; - git_off_t bytes; - git_indexer_stats stats; git_remote_callbacks callbacks; + int bytes_received = 0; memset(&callbacks, 0, sizeof(git_remote_callbacks)); callbacks.update_tips = update_tips; @@ -43,10 +48,11 @@ static void do_fetch(const char *url, int flag, int n) git_remote_set_callbacks(remote, &callbacks); git_remote_set_autotag(remote, flag); cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH)); - cl_git_pass(git_remote_download(remote, &bytes, &stats)); + cl_git_pass(git_remote_download(remote, progress, &bytes_received)); git_remote_disconnect(remote); cl_git_pass(git_remote_update_tips(remote)); cl_assert_equal_i(counter, n); + cl_assert(bytes_received > 0); git_remote_free(remote); } diff --git a/tests-clar/network/remoterename.c b/tests-clar/network/remoterename.c new file mode 100644 index 000000000..70041f45d --- /dev/null +++ b/tests-clar/network/remoterename.c @@ -0,0 +1,201 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_remote *_remote; +static git_repository *_repo; + +void test_network_remoterename__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); +} + +void test_network_remoterename__cleanup(void) +{ + git_remote_free(_remote); + + cl_git_sandbox_cleanup(); +} + +static int dont_call_me_cb(const char *fetch_refspec, void *payload) +{ + GIT_UNUSED(fetch_refspec); + GIT_UNUSED(payload); + + cl_assert(false); + + return -1; +} + +void test_network_remoterename__renaming_a_remote_moves_related_configuration_section(void) +{ + assert_config_entry_existence(_repo, "remote.test.fetch", true); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true); +} + +void test_network_remoterename__renaming_a_remote_updates_branch_related_configuration_entries(void) +{ + assert_config_entry_value(_repo, "branch.master.remote", "test"); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "branch.master.remote", "just/renamed"); +} + +void test_network_remoterename__renaming_a_remote_updates_default_fetchrefspec(void) +{ + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*"); +} + +void test_network_remoterename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void) +{ + git_config *config; + + git_remote_free(_remote); + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_delete(config, "remote.test.fetch")); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); +} + +static int ensure_refspecs(const char* refspec_name, void *payload) +{ + int i = 0; + bool found = false; + const char ** exp = (const char **)payload; + + while (exp[i]) { + if (strcmp(exp[i++], refspec_name)) + continue; + + found = true; + break; + } + + cl_assert(found); + + return 0; +} + +void test_network_remoterename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void) +{ + git_config *config; + + char *expected_refspecs[] = { + "+refs/*:refs/*", + NULL + }; + + git_remote_free(_remote); + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*")); + cl_git_pass(git_remote_load(&_remote, _repo, "test")); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", ensure_refspecs, &expected_refspecs)); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*"); +} + +void test_network_remoterename__new_name_can_contain_dots(void) +{ + cl_git_pass(git_remote_rename(_remote, "just.renamed", dont_call_me_cb, NULL)); + cl_assert_equal_s("just.renamed", git_remote_name(_remote)); +} + +void test_network_remoterename__new_name_must_conform_to_reference_naming_conventions(void) +{ + cl_git_fail(git_remote_rename(_remote, "new@{name", dont_call_me_cb, NULL)); +} + +void test_network_remoterename__renamed_name_is_persisted(void) +{ + git_remote *renamed; + git_repository *another_repo; + + cl_git_fail(git_remote_load(&renamed, _repo, "just/renamed")); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + cl_git_pass(git_repository_open(&another_repo, "testrepo.git")); + cl_git_pass(git_remote_load(&renamed, _repo, "just/renamed")); + + git_remote_free(renamed); + git_repository_free(another_repo); +} + +void test_network_remoterename__cannot_overwrite_an_existing_remote(void) +{ + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test", dont_call_me_cb, NULL)); + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test_with_pushurl", dont_call_me_cb, NULL)); +} + +void test_network_remoterename__renaming_an_inmemory_remote_persists_it(void) +{ + git_remote *remote; + + assert_config_entry_existence(_repo, "remote.durable.url", false); + + cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/durable.git", NULL)); + + assert_config_entry_existence(_repo, "remote.durable.url", false); + + cl_git_pass(git_remote_rename(remote, "durable", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "remote.durable.url", "git://github.com/libgit2/durable.git"); + + git_remote_free(remote); +} + +void test_network_remoterename__renaming_an_inmemory_nameless_remote_notifies_the_inability_to_update_the_fetch_refspec(void) +{ + git_remote *remote; + + char *expected_refspecs[] = { + "+refs/heads/*:refs/remotes/volatile/*", + NULL + }; + + assert_config_entry_existence(_repo, "remote.volatile.url", false); + + cl_git_pass(git_remote_new( + &remote, + _repo, + NULL, + "git://github.com/libgit2/volatile.git", + "+refs/heads/*:refs/remotes/volatile/*")); + + cl_git_pass(git_remote_rename(remote, "durable", ensure_refspecs, &expected_refspecs)); + + git_remote_free(remote); +} + +void test_network_remoterename__renaming_a_remote_moves_the_underlying_reference(void) +{ + git_reference *underlying; + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + git_reference_free(underlying); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master")); + git_reference_free(underlying); +} diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 91c3e879d..9fe67d856 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -10,9 +10,8 @@ static const git_refspec *_refspec; void test_network_remotes__initialize(void) { - cl_fixture_sandbox("testrepo.git"); + _repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_open(&_repo, "testrepo.git")); cl_git_pass(git_remote_load(&_remote, _repo, "test")); _refspec = git_remote_fetchspec(_remote); @@ -22,8 +21,7 @@ void test_network_remotes__initialize(void) void test_network_remotes__cleanup(void) { git_remote_free(_remote); - git_repository_free(_repo); - cl_fixture_cleanup("testrepo.git"); + cl_git_sandbox_cleanup(); } void test_network_remotes__parsing(void) @@ -73,7 +71,7 @@ void test_network_remotes__parsing_local_path_fails_if_path_not_found(void) void test_network_remotes__supported_transport_methods_are_supported(void) { - cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") ); + cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") ); } void test_network_remotes__unsupported_transport_methods_are_unsupported(void) @@ -226,6 +224,29 @@ void test_network_remotes__add(void) cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); } +void test_network_remotes__cannot_add_a_nameless_remote(void) +{ + git_remote *remote; + + cl_git_fail(git_remote_add(&remote, _repo, NULL, "git://github.com/libgit2/libgit2")); + cl_git_fail(git_remote_add(&remote, _repo, "", "git://github.com/libgit2/libgit2")); +} + +void test_network_remotes__cannot_save_a_nameless_remote(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_save(remote)); + git_remote_free(remote); + + cl_git_pass(git_remote_new(&remote, _repo, "", "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_save(remote)); + git_remote_free(remote); +} + void test_network_remotes__tagopt(void) { const char *opt; diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests-clar/object/commit/commitstagedfile.c index 882fb49ae..ac38acf5b 100644 --- a/tests-clar/object/commit/commitstagedfile.c +++ b/tests-clar/object/commit/commitstagedfile.c @@ -71,9 +71,9 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) */ cl_git_mkfile("treebuilder/test.txt", "test\n"); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "test.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "test.txt")); - entry = git_index_get(index, 0); + entry = git_index_get_byindex(index, 0); cl_assert(git_oid_cmp(&expected_blob_oid, &entry->oid) == 0); @@ -128,68 +128,3 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) git_tree_free(tree); git_index_free(index); } - -void test_object_commit_commitstagedfile__message_prettify(void) -{ - char buffer[100]; - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1); - cl_assert_equal_s(buffer, ""); - cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1); - cl_assert_equal_s(buffer, ""); - - cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0)); - cl_assert_equal_s("Short\n", buffer); - cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1)); - cl_assert_equal_s("Short\n", buffer); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0); - cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n"); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0); - cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n"); - - /* try out overflow */ - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678", - 0) > 0); - cl_assert_equal_s(buffer, - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n", - 0) > 0); - cl_assert_equal_s(buffer, - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); - - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "123456789", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x", - 0)); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" - "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" - "1234567890", - 1) > 0); - - cl_assert(git_message_prettify(NULL, 0, "", 0) == 1); - cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12); - cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15); -} diff --git a/tests-clar/object/message.c b/tests-clar/object/message.c index 43be8b152..7ef6374b3 100644 --- a/tests-clar/object/message.c +++ b/tests-clar/object/message.c @@ -169,3 +169,68 @@ void test_object_message__keep_comments(void) assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0); assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0); } + +void test_object_message__message_prettify(void) +{ + char buffer[100]; + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1); + cl_assert_equal_s(buffer, ""); + cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1); + cl_assert_equal_s(buffer, ""); + + cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0)); + cl_assert_equal_s("Short\n", buffer); + cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1)); + cl_assert_equal_s("Short\n", buffer); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0); + cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n"); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0); + cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n"); + + /* try out overflow */ + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678", + 0) > 0); + cl_assert_equal_s(buffer, + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n", + 0) > 0); + cl_assert_equal_s(buffer, + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); + + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "123456789", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x", + 0)); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" + "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" + "1234567890", + 1) > 0); + + cl_assert(git_message_prettify(NULL, 0, "", 0) == 1); + cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12); + cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15); +} diff --git a/tests-clar/pack/packbuilder.c b/tests-clar/pack/packbuilder.c index fa7bec14e..6d17a709f 100644 --- a/tests-clar/pack/packbuilder.c +++ b/tests-clar/pack/packbuilder.c @@ -33,7 +33,7 @@ void test_pack_packbuilder__cleanup(void) void test_pack_packbuilder__create_pack(void) { - git_indexer_stats stats; + git_transfer_progress stats; git_oid oid, *o; unsigned int i; diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c index 4e9c70904..da7db13fc 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests-clar/refs/branches/delete.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "refs.h" #include "repo/repo_helpers.h" +#include "config/config_helpers.h" static git_repository *repo; static git_reference *fake_remote; @@ -90,3 +91,17 @@ void test_refs_branches_delete__can_delete_a_remote_branch(void) cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); cl_git_pass(git_branch_delete(branch)); } + +void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void) +{ + git_reference *branch; + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); +}
\ No newline at end of file diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 62b6042c6..042469016 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "refs.h" +#include "config/config_helpers.h" static git_repository *repo; static git_reference *ref; @@ -63,6 +64,27 @@ void test_refs_branches_move__can_force_move_over_an_existing_branch(void) cl_git_pass(git_branch_move(ref, "master", 1)); } +void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + assert_config_entry_existence(repo, "branch.moved.remote", false); + assert_config_entry_existence(repo, "branch.moved.merge", false); + + cl_git_pass(git_branch_move(branch, "moved", 0)); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); + assert_config_entry_existence(repo, "branch.moved.remote", true); + assert_config_entry_existence(repo, "branch.moved.merge", true); + + git_reference_free(branch); +} + void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) { git_reference *branch; diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c index 86781c041..4be857b42 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests-clar/refs/reflog/drop.c @@ -43,57 +43,48 @@ void test_refs_reflog_drop__can_drop_an_entry(void) void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) { - const git_reflog_entry *before_previous, *before_next; - const git_reflog_entry *after_next; - git_oid before_next_old_oid; + const git_reflog_entry *before_current; + const git_reflog_entry *after_current; + git_oid before_current_old_oid, before_current_cur_oid; cl_assert(entrycount > 4); - before_previous = git_reflog_entry_byindex(g_reflog, 3); - before_next = git_reflog_entry_byindex(g_reflog, 1); - git_oid_cpy(&before_next_old_oid, &before_next->oid_old); + before_current = git_reflog_entry_byindex(g_reflog, 2); + git_oid_cpy(&before_current_old_oid, &before_current->oid_old); + git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur); cl_git_pass(git_reflog_drop(g_reflog, 2, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); - after_next = git_reflog_entry_byindex(g_reflog, 1); + after_current = git_reflog_entry_byindex(g_reflog, 2); - cl_assert_equal_i(0, git_oid_cmp(&before_next->oid_cur, &after_next->oid_cur)); - cl_assert(git_oid_cmp(&before_next_old_oid, &after_next->oid_old) != 0); - cl_assert_equal_i(0, git_oid_cmp(&before_previous->oid_cur, &after_next->oid_old)); + cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old)); + cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur)); } -void test_refs_reflog_drop__can_drop_the_first_entry(void) -{ - cl_assert(entrycount > 2); - - cl_git_pass(git_reflog_drop(g_reflog, 0, 0)); - cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); -} - -void test_refs_reflog_drop__can_drop_the_last_entry(void) +void test_refs_reflog_drop__can_drop_the_oldest_entry(void) { const git_reflog_entry *entry; cl_assert(entrycount > 2); - cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); + cl_git_pass(git_reflog_drop(g_reflog, 0, 0)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); - entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + entry = git_reflog_entry_byindex(g_reflog, 0); cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); } -void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history(void) +void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void) { const git_reflog_entry *entry; cl_assert(entrycount > 2); - cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); - entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + entry = git_reflog_entry_byindex(g_reflog, 0); cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); } diff --git a/tests-clar/repo/state.c b/tests-clar/repo/state.c new file mode 100644 index 000000000..c0aba1987 --- /dev/null +++ b/tests-clar/repo/state.c @@ -0,0 +1,98 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refs.h" +#include "posix.h" +#include "fileops.h" + +static git_repository *_repo; +static git_buf _path; + +void test_repo_state__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_state__cleanup(void) +{ + cl_git_sandbox_cleanup(); + git_buf_free(&_path); +} + +static void setup_simple_state(const char *filename) +{ + cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), filename)); + git_futils_mkpath2file(git_buf_cstr(&_path), 0777); + cl_git_mkfile(git_buf_cstr(&_path), "dummy"); + + cl_git_pass(git_repository_detach_head(_repo)); +} + +static void assert_repo_state(git_repository_state_t state) +{ + cl_assert_equal_i(state, git_repository_state(_repo)); +} + +void test_repo_state__none_with_HEAD_attached(void) +{ + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__none_with_HEAD_detached(void) +{ + cl_git_pass(git_repository_detach_head(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__merge(void) +{ + setup_simple_state(GIT_MERGE_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_MERGE); +} + +void test_repo_state__revert(void) +{ + setup_simple_state(GIT_REVERT_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REVERT); +} + +void test_repo_state__cherry_pick(void) +{ + setup_simple_state(GIT_CHERRY_PICK_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_CHERRY_PICK); +} + +void test_repo_state__bisect(void) +{ + setup_simple_state(GIT_BISECT_LOG_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_BISECT); +} + +void test_repo_state__rebase_interactive(void) +{ + setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE); +} + +void test_repo_state__rebase_merge(void) +{ + setup_simple_state(GIT_REBASE_MERGE_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE); +} + +void test_repo_state__rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE); +} + +void test_repo_state__apply_mailbox(void) +{ + setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX); +} + +void test_repo_state__apply_mailbox_or_rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE); +} diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c index fdab9c536..c3f041817 100644 --- a/tests-clar/reset/hard.c +++ b/tests-clar/reset/hard.c @@ -58,3 +58,38 @@ void test_reset_hard__cannot_reset_in_a_bare_repository(void) git_repository_free(bare); } + +void test_reset_hard__cleans_up_merge(void) +{ + git_buf merge_head_path = GIT_BUF_INIT, + merge_msg_path = GIT_BUF_INIT, + merge_mode_path = GIT_BUF_INIT, + orig_head_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n"); + + cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MODE")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), ""); + + cl_git_pass(git_buf_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD")); + cl_git_mkfile(git_buf_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + + retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); + + cl_assert(!git_path_exists(git_buf_cstr(&merge_head_path))); + cl_assert(!git_path_exists(git_buf_cstr(&merge_msg_path))); + cl_assert(!git_path_exists(git_buf_cstr(&merge_mode_path))); + + cl_assert(git_path_exists(git_buf_cstr(&orig_head_path))); + cl_git_pass(p_unlink(git_buf_cstr(&orig_head_path))); + + git_buf_free(&merge_head_path); + git_buf_free(&merge_msg_path); + git_buf_free(&merge_mode_path); + git_buf_free(&orig_head_path); +} diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c index 1872baf3b..d51e3f1f1 100644 --- a/tests-clar/reset/soft.c +++ b/tests-clar/reset/soft.c @@ -1,5 +1,7 @@ #include "clar_libgit2.h" +#include "posix.h" #include "reset_helpers.h" +#include "path.h" #include "repo/repo_helpers.h" static git_repository *repo; @@ -110,3 +112,19 @@ void test_reset_soft__resetting_against_an_orphaned_head_repo_makes_the_head_no_ git_reference_free(head); } + +void test_reset_soft__fails_when_merging(void) +{ + git_buf merge_head_path = GIT_BUF_INIT; + + cl_git_pass(git_repository_detach_head(repo)); + cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO); + + cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT)); + cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path))); + + git_buf_free(&merge_head_path); +} diff --git a/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..1f7391f92 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +master diff --git a/tests-clar/resources/mergedrepo/.gitted/HEAD b/tests-clar/resources/mergedrepo/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD new file mode 100644 index 000000000..a5bdf6e40 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD @@ -0,0 +1 @@ +e2809157a7766f272e4cfe26e61ef2678a5357ff diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG new file mode 100644 index 000000000..7c4d1f5a9 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG @@ -0,0 +1,5 @@ +Merge branch 'branch' + +Conflicts: + conflicts-one.txt + conflicts-two.txt diff --git a/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD new file mode 100644 index 000000000..13d4d6721 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD @@ -0,0 +1 @@ +3a34580a35add43a4cf361e8e9a30060a905c876 diff --git a/tests-clar/resources/mergedrepo/.gitted/config b/tests-clar/resources/mergedrepo/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/mergedrepo/.gitted/description b/tests-clar/resources/mergedrepo/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/mergedrepo/.gitted/index b/tests-clar/resources/mergedrepo/.gitted/index Binary files differnew file mode 100644 index 000000000..3d29f78e7 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/index diff --git a/tests-clar/resources/mergedrepo/.gitted/info/exclude b/tests-clar/resources/mergedrepo/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/HEAD b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD new file mode 100644 index 000000000..a385da67b --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial +9a05ccb4e0f948de03128e095f39dae6976751c5 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 checkout: moving from master to branch +9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch +e2809157a7766f272e4cfe26e61ef2678a5357ff 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371873 -0500 checkout: moving from branch to master +9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch new file mode 100644 index 000000000..26a5e8dc5 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 branch: Created from HEAD +9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..425f7bd89 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial +9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 Binary files differnew file mode 100644 index 000000000..9232f79d9 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 Binary files differnew file mode 100644 index 000000000..3e124d9a4 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 Binary files differnew file mode 100644 index 000000000..7bb19c873 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e Binary files differnew file mode 100644 index 000000000..487bcffb1 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 Binary files differnew file mode 100644 index 000000000..2eb3954fc --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 Binary files differnew file mode 100644 index 000000000..ebe83ccb2 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 new file mode 100644 index 000000000..0d4095ffc --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 @@ -0,0 +1,2 @@ +xK +1D]}Dx/O"F2oo<*ZoљuIhhrl"r YT8'#vm0.¨.:.#+9R^nG~[=VjR"IjD۔7|N`
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c Binary files differnew file mode 100644 index 000000000..33389c302 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 Binary files differnew file mode 100644 index 000000000..5361ea685 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da Binary files differnew file mode 100644 index 000000000..a60da877c --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad Binary files differnew file mode 100644 index 000000000..85e84d71e --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 Binary files differnew file mode 100644 index 000000000..b16b521e6 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 Binary files differnew file mode 100644 index 000000000..7c4e85ffb --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 Binary files differnew file mode 100644 index 000000000..65173fc4d --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 Binary files differnew file mode 100644 index 000000000..162fa4455 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c Binary files differnew file mode 100644 index 000000000..77a519f55 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda Binary files differnew file mode 100644 index 000000000..f624cd4f1 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 Binary files differnew file mode 100644 index 000000000..096474c03 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a Binary files differnew file mode 100644 index 000000000..a413bc6b0 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 Binary files differnew file mode 100644 index 000000000..3ac8f6018 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 Binary files differnew file mode 100644 index 000000000..589a5ae9b --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 Binary files differnew file mode 100644 index 000000000..6503985e3 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 Binary files differnew file mode 100644 index 000000000..2eaa80838 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 new file mode 100644 index 000000000..7373a80d8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 @@ -0,0 +1 @@ +x
!Dm@c6q##Ay/ ܁:#$ltH:闄*DXhV}
˷n[-K_;Z@JGԈbq3"go@I
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c Binary files differnew file mode 100644 index 000000000..c5a651f97 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d Binary files differnew file mode 100644 index 000000000..3e14b5dc8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 Binary files differnew file mode 100644 index 000000000..a641adc2e --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff new file mode 100644 index 000000000..fa86662e0 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff @@ -0,0 +1,3 @@ +xK +1D]t> xNq1(]{Pe
mٍ.S0[Dcd +ŅbMԝCgd@>glX].$!0*zu})/E_<ڪO:WځrơqѤh@mt;;5uZyVo\M
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 Binary files differnew file mode 100644 index 000000000..c9841c698 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 Binary files differnew file mode 100644 index 000000000..cd587dbec --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch new file mode 100644 index 000000000..a5bdf6e40 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch @@ -0,0 +1 @@ +e2809157a7766f272e4cfe26e61ef2678a5357ff diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master new file mode 100644 index 000000000..13d4d6721 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master @@ -0,0 +1 @@ +3a34580a35add43a4cf361e8e9a30060a905c876 diff --git a/tests-clar/resources/mergedrepo/conflicts-one.txt b/tests-clar/resources/mergedrepo/conflicts-one.txt new file mode 100644 index 000000000..8aad34cc8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/conflicts-one.txt @@ -0,0 +1,5 @@ +<<<<<<< HEAD +This is most certainly a conflict! +======= +This is a conflict!!! +>>>>>>> branch diff --git a/tests-clar/resources/mergedrepo/conflicts-two.txt b/tests-clar/resources/mergedrepo/conflicts-two.txt new file mode 100644 index 000000000..e62cac5c8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/conflicts-two.txt @@ -0,0 +1,5 @@ +<<<<<<< HEAD +This is without question another conflict! +======= +This is another conflict!!! +>>>>>>> branch diff --git a/tests-clar/resources/mergedrepo/one.txt b/tests-clar/resources/mergedrepo/one.txt new file mode 100644 index 000000000..75938de1e --- /dev/null +++ b/tests-clar/resources/mergedrepo/one.txt @@ -0,0 +1,10 @@ +This is file one! +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one! diff --git a/tests-clar/resources/mergedrepo/two.txt b/tests-clar/resources/mergedrepo/two.txt new file mode 100644 index 000000000..7b26923aa --- /dev/null +++ b/tests-clar/resources/mergedrepo/two.txt @@ -0,0 +1,12 @@ +This is file two! +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two! diff --git a/tests-clar/stash/drop.c b/tests-clar/stash/drop.c new file mode 100644 index 000000000..5bbc7452a --- /dev/null +++ b/tests-clar/stash/drop.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +static git_repository *repo; +static git_signature *signature; + +void test_stash_drop__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ +} + +void test_stash_drop__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); +} + +void test_stash_drop__cannot_drop_from_an_empty_stash(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +static void push_three_states(void) +{ + git_oid oid; + git_index *index; + + cl_git_mkfile("stash/zero.txt", "content\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, "zero.txt")); + commit_staged_files(&oid, index, signature); + + cl_git_mkfile("stash/one.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_mkfile("stash/two.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_mkfile("stash/three.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); + + git_index_free(index); +} + +void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void) +{ + push_three_states(); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 666)); + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 42)); + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 3)); +} + +void test_stash_drop__can_purge_the_stash_from_the_top(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +void test_stash_drop__can_purge_the_stash_from_the_bottom(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 2)); + cl_git_pass(git_stash_drop(repo, 1)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) +{ + git_reference *stash; + git_reflog *reflog; + const git_reflog_entry *entry; + git_oid oid; + size_t count; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash")); + + cl_git_pass(git_reflog_read(&reflog, stash)); + entry = git_reflog_entry_byindex(reflog, 1); + + git_oid_cpy(&oid, git_reflog_entry_oidold(entry)); + count = git_reflog_entrycount(reflog); + + git_reflog_free(reflog); + + cl_git_pass(git_stash_drop(repo, 1)); + + cl_git_pass(git_reflog_read(&reflog, stash)); + entry = git_reflog_entry_byindex(reflog, 1); + + cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_oidold(entry))); + cl_assert_equal_i(count - 1, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + + git_reference_free(stash); +} + +void test_stash_drop__dropping_the_last_entry_removes_the_stash(void) +{ + git_reference *stash; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash")); + git_reference_free(stash); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&stash, repo, "refs/stash")); +} diff --git a/tests-clar/stash/foreach.c b/tests-clar/stash/foreach.c new file mode 100644 index 000000000..818d906e7 --- /dev/null +++ b/tests-clar/stash/foreach.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +struct callback_data +{ + char **oids; + int invokes; +}; + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; +struct callback_data data; + +#define REPO_NAME "stash" + +void test_stash_foreach__initialize(void) +{ + cl_git_pass(git_signature_new( + &signature, + "nulltoken", + "emeric.fermas@gmail.com", + 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + memset(&data, 0, sizeof(struct callback_data)); +} + +void test_stash_foreach__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r(REPO_NAME, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); +} + +static int callback_cb( + size_t index, + const char* message, + const git_oid *stash_oid, + void *payload) +{ + struct callback_data *data = (struct callback_data *)payload; + + GIT_UNUSED(index); + GIT_UNUSED(message); + + cl_assert_equal_i(0, git_oid_streq(stash_oid, data->oids[data->invokes++])); + + return 0; +} + +void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void) +{ + char *oids[] = { NULL }; + + data.oids = oids; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + + cl_assert_equal_i(0, data.invokes); +} + +void test_stash_foreach__can_enumerate_a_repository(void) +{ + char *oids_default[] = { + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + char *oids_untracked[] = { + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + char *oids_ignored[] = { + "c95599a8fef20a7e57582c6727b1a0d02e0a5828", + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + setup_stash(repo, signature); + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_DEFAULT)); + + data.oids = oids_default; + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(1, data.invokes); + + data.oids = oids_untracked; + data.invokes = 0; + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(2, data.invokes); + + data.oids = oids_ignored; + data.invokes = 0; + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_IGNORED)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(3, data.invokes); +} diff --git a/tests-clar/stash/save.c b/tests-clar/stash/save.c new file mode 100644 index 000000000..01acf672c --- /dev/null +++ b/tests-clar/stash/save.c @@ -0,0 +1,370 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; + +/* + * Friendly reminder, in order to ease the reading of the following tests: + * + * "stash" points to the worktree commit + * "stash^1" points to the base commit (HEAD when the stash was created) + * "stash^2" points to the index commit + * "stash^3" points to the untracked commit + */ + +void test_stash_save__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + setup_stash(repo, signature); +} + +void test_stash_save__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); +} + +static void assert_object_oid(const char* revision, const char* expected_oid, git_otype type) +{ + git_object *object; + int result; + + result = git_revparse_single(&object, repo, revision); + + if (!expected_oid) { + cl_assert_equal_i(GIT_ENOTFOUND, result); + return; + } else + cl_assert_equal_i(0, result); + + cl_assert_equal_i(type, git_object_type(object)); + cl_git_pass(git_oid_streq(git_object_id(object), expected_oid)); + + git_object_free(object); +} + +static void assert_blob_oid(const char* revision, const char* expected_oid) +{ + assert_object_oid(revision, expected_oid, GIT_OBJ_BLOB); +} + +void test_stash_save__does_not_keep_index_by_default(void) +{ +/* +$ git stash + +$ git show refs/stash:what +see you later + +$ git show refs/stash:how +not so small and + +$ git show refs/stash:who +funky world + +$ git show refs/stash:when +fatal: Path 'when' exists on disk, but not in 'stash'. + +$ git show refs/stash^2:what +goodbye + +$ git show refs/stash^2:how +not so small and + +$ git show refs/stash^2:who +world + +$ git show refs/stash^2:when +fatal: Path 'when' exists on disk, but not in 'stash^2'. + +$ git status --short +?? when + +*/ + unsigned int status; + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + cl_git_pass(git_status_file(&status, repo, "when")); + + assert_blob_oid("refs/stash:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("refs/stash:when", NULL); + assert_blob_oid("refs/stash:just.ignore", NULL); + + assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("refs/stash^2:when", NULL); + assert_blob_oid("refs/stash^2:just.ignore", NULL); + + assert_blob_oid("refs/stash^3", NULL); + + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); +} + +static void assert_status( + const char *path, + int status_flags) +{ + unsigned int status; + int error; + + error = git_status_file(&status, repo, path); + + if (status_flags < 0) { + cl_assert_equal_i(status_flags, error); + return; + } + + cl_assert_equal_i(0, error); + cl_assert_equal_i((unsigned int)status_flags, status); +} + +void test_stash_save__can_keep_index(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX)); + + assert_status("what", GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); +} + +static void assert_commit_message_contains(const char *revision, const char *fragment) +{ + git_commit *commit; + + cl_git_pass(git_revparse_single(((git_object **)&commit), repo, revision)); + + cl_assert(strstr(git_commit_message(commit), fragment) != NULL); + + git_commit_free(commit); +} + +void test_stash_save__can_include_untracked_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", NULL); +} + +void test_stash_save__can_include_untracked_and_ignored_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", "78925fb1236b98b37a35e9723033e627f97aa88b"); +} + +#define MESSAGE "Look Ma! I'm on TV!" +void test_stash_save__can_accept_a_message(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, MESSAGE, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on master: "); + assert_commit_message_contains("refs/stash", "On master: " MESSAGE); +} + +void test_stash_save__cannot_stash_against_an_unborn_branch(void) +{ + git_reference *head; + + cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); + cl_git_pass(git_reference_set_target(head, "refs/heads/unborn")); + + cl_assert_equal_i(GIT_EORPHANEDHEAD, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + git_reference_free(head); +} + +void test_stash_save__cannot_stash_against_a_bare_repository(void) +{ + git_repository *local; + + cl_git_pass(git_repository_init(&local, "sorry-it-is-a-non-bare-only-party", 1)); + + cl_assert_equal_i(GIT_EBAREREPO, + git_stash_save(&stash_tip_oid, local, signature, NULL, GIT_STASH_DEFAULT)); + + git_repository_free(local); +} + +void test_stash_save__can_stash_against_a_detached_head(void) +{ + git_repository_detach_head(repo); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on (no branch): "); + assert_commit_message_contains("refs/stash", "WIP on (no branch): "); +} + +void test_stash_save__stashing_updates_the_reflog(void) +{ + char *sha; + + assert_object_oid("refs/stash@{0}", NULL, GIT_OBJ_COMMIT); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + sha = git_oid_allocfmt(&stash_tip_oid); + + assert_object_oid("refs/stash@{0}", sha, GIT_OBJ_COMMIT); + assert_object_oid("refs/stash@{1}", NULL, GIT_OBJ_COMMIT); + + git__free(sha); +} + +void test_stash_save__cannot_stash_when_there_are_no_local_change(void) +{ + git_index *index; + git_oid commit_oid, stash_tip_oid; + + cl_git_pass(git_repository_index(&index, repo)); + + /* + * 'what' and 'who' are being committed. + * 'when' remain untracked. + */ + git_index_add_from_workdir(index, "what"); + git_index_add_from_workdir(index, "who"); + cl_git_pass(git_index_write(index)); + commit_staged_files(&commit_oid, index, signature); + git_index_free(index); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + p_unlink("stash/when"); + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); +} + +void test_stash_save__can_stage_normal_then_stage_untracked(void) +{ + /* + * $ git ls-tree stash@{1}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob bc99dc98b3eba0e9157e94769cd4d49cb49de449 what + * 100644 blob a0400d4954659306a976567af43125a0b1aa8595 who + * + * $ git ls-tree stash@{1}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^3 + * fatal: Not a valid object name stash@{1}^3 + * + * $ git ls-tree stash@{0}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^3 + * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when + */ + + assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_WT_MODIFIED); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + assert_status("what", GIT_STATUS_CURRENT); + assert_status("how", GIT_STATUS_CURRENT); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + assert_status("what", GIT_STATUS_CURRENT); + assert_status("how", GIT_STATUS_CURRENT); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_ENOTFOUND); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + + assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("stash@{1}^0:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("stash@{1}^0:when", NULL); + + assert_blob_oid("stash@{1}^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("stash@{1}^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{1}^2:when", NULL); + + assert_object_oid("stash@{1}^3", NULL, GIT_OBJ_COMMIT); + + assert_blob_oid("stash@{0}^0:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^0:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^0:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^0:when", NULL); + + assert_blob_oid("stash@{0}^2:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^2:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^2:when", NULL); + + assert_blob_oid("stash@{0}^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); /* now */ +} + +#define EMPTY_TREE "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void) +{ + p_unlink("stash/when"); + + assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_WT_MODIFIED); + assert_status("when", GIT_ENOTFOUND); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJ_TREE); +} diff --git a/tests-clar/stash/stash_helpers.c b/tests-clar/stash/stash_helpers.c new file mode 100644 index 000000000..0e93ecff0 --- /dev/null +++ b/tests-clar/stash/stash_helpers.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +void commit_staged_files( + git_oid *commit_oid, + git_index *index, + git_signature *signature) +{ + git_tree *tree; + git_oid tree_oid; + git_repository *repo; + + repo = git_index_owner(index); + + cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + cl_git_pass(git_commit_create_v( + commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + "Initial commit", + tree, + 0)); + + git_tree_free(tree); +} + +void setup_stash(git_repository *repo, git_signature *signature) +{ + git_oid commit_oid; + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("stash/what", "hello\n"); /* ce013625030ba8dba906f756967f9e9ca394464a */ + cl_git_mkfile("stash/how", "small\n"); /* ac790413e2d7a26c3767e78c57bb28716686eebc */ + cl_git_mkfile("stash/who", "world\n"); /* cc628ccd10742baea8241c5924df992b5c019f71 */ + cl_git_mkfile("stash/when", "now\n"); /* b6ed15e81e2593d7bb6265eb4a991d29dc3e628b */ + cl_git_mkfile("stash/just.ignore", "me\n"); /* 78925fb1236b98b37a35e9723033e627f97aa88b */ + + cl_git_mkfile("stash/.gitignore", "*.ignore\n"); + + cl_git_pass(git_index_add_from_workdir(index, "what")); + cl_git_pass(git_index_add_from_workdir(index, "how")); + cl_git_pass(git_index_add_from_workdir(index, "who")); + cl_git_pass(git_index_add_from_workdir(index, ".gitignore")); + cl_git_pass(git_index_write(index)); + + commit_staged_files(&commit_oid, index, signature); + + cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ + cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ + cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */ + + cl_git_pass(git_index_add_from_workdir(index, "what")); + cl_git_pass(git_index_add_from_workdir(index, "how")); + cl_git_pass(git_index_write(index)); + + cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */ + + git_index_free(index); +} diff --git a/tests-clar/stash/stash_helpers.h b/tests-clar/stash/stash_helpers.h new file mode 100644 index 000000000..bb7fec4f5 --- /dev/null +++ b/tests-clar/stash/stash_helpers.h @@ -0,0 +1,8 @@ +void setup_stash( + git_repository *repo, + git_signature *signature); + +void commit_staged_files( + git_oid *commit_oid, + git_index *index, + git_signature *signature);
\ No newline at end of file diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 908d34510..116286f67 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -438,7 +438,7 @@ void test_status_worktree__first_commit_in_progress(void) cl_assert(result.status == GIT_STATUS_WT_NEW); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -486,7 +486,7 @@ static void fill_index_wth_head_entries(git_repository *repo, git_index *index) cl_git_pass(git_commit_lookup(&commit, repo, &oid)); cl_git_pass(git_commit_tree(&tree, commit)); - cl_git_pass(git_index_read_tree(index, tree, NULL)); + cl_git_pass(git_index_read_tree(index, tree)); cl_git_pass(git_index_write(index)); git_tree_free(tree); @@ -570,7 +570,7 @@ void test_status_worktree__bracket_in_filename(void) /* add the file to the index */ cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_BRACKET)); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -648,7 +648,7 @@ void test_status_worktree__space_in_filename(void) /* add the file to the index */ cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, FILE_WITH_SPACE, 0)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_SPACE)); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -816,7 +816,7 @@ void test_status_worktree__new_staged_file_must_handle_crlf(void) cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); cl_git_pass(git_index_write(index)); cl_git_pass(git_status_file(&status, repo, "testfile.txt")); diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index 63073ceca..eec028c40 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -105,7 +105,7 @@ void test_submodule_status__ignore_none(void) cl_git_pass(git_repository_index(&index, g_repo)); pos = git_index_find(index, "sm_changed_head"); cl_assert(pos >= 0); - cl_git_pass(git_index_remove(index, pos)); + cl_git_pass(git_index_remove(index, "sm_changed_head", 0)); cl_git_pass(git_index_write(index)); git_index_free(index); |
