diff options
38 files changed, 1205 insertions, 143 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..bc6344b93 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,3 @@ +{ + "postCreateCommand": "bash .devcontainer/setup.sh" +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 000000000..c328bf3b9 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +sudo apt-get update +sudo apt-get -y --no-install-recommends install cmake + +mkdir build +cd build +cmake ..
\ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..d47d93aab --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/libgit2_clar", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +}
\ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..64142d341 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "command": "cd build && cmake --build . --parallel", + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new" + } + }, + { + "label": "Run Tests", + "type": "shell", + "command": "build/libgit2_clar -v", + "group": "test", + "presentation": { + "reveal": "always", + "panel": "new" + } + } + ] + }
\ No newline at end of file diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake index 06672ab03..092cdfd05 100644 --- a/cmake/SelectHashes.cmake +++ b/cmake/SelectHashes.cmake @@ -56,6 +56,7 @@ ELSE() MESSAGE(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") ENDIF() +list(APPEND SRC_SHA1 "hash/sha1.h") list(SORT SRC_SHA1) ADD_FEATURE_INFO(SHA ON "using ${USE_SHA1}") diff --git a/docs/changelog.md b/docs/changelog.md index edd2c3f42..1bbbe607d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -509,6 +509,9 @@ with v0.28.0. The breaking change is that the `username` member of the underlying struct is now hidden, and a new `git_cred_get_username` function has been provided. +* Some errors of class `GIT_ERROR_NET` now have class `GIT_ERROR_HTTP`. + Most authentication failures now have error code `GIT_EAUTH` instead of `GIT_ERROR`. + ### Breaking CMake configuration changes * The CMake option to use a system http-parser library, instead of the diff --git a/include/git2/checkout.h b/include/git2/checkout.h index ca6f17aa6..c7aeee431 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -178,6 +178,12 @@ typedef enum { GIT_CHECKOUT_DONT_WRITE_INDEX = (1u << 23), /** + * Show what would be done by a checkout. Stop after sending + * notifications; don't update the working directory or index. + */ + GIT_CHECKOUT_DRY_RUN = (1u << 24), + + /** * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ diff --git a/include/git2/errors.h b/include/git2/errors.h index 8887b3299..de51582d5 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -42,14 +42,14 @@ typedef enum { GIT_ECONFLICT = -13, /**< Checkout conflicts prevented operation */ GIT_ELOCKED = -14, /**< Lock file prevented operation */ GIT_EMODIFIED = -15, /**< Reference value does not match expected */ - GIT_EAUTH = -16, /**< Authentication error */ - GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */ + GIT_EAUTH = -16, /**< Authentication error */ + GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */ GIT_EAPPLIED = -18, /**< Patch/merge has already been applied */ - GIT_EPEEL = -19, /**< The requested peel operation is not possible */ - GIT_EEOF = -20, /**< Unexpected EOF */ - GIT_EINVALID = -21, /**< Invalid operation or input */ + GIT_EPEEL = -19, /**< The requested peel operation is not possible */ + GIT_EEOF = -20, /**< Unexpected EOF */ + GIT_EINVALID = -21, /**< Invalid operation or input */ GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */ - GIT_EDIRECTORY = -23, /**< The operation is not valid for a directory */ + GIT_EDIRECTORY = -23, /**< The operation is not valid for a directory */ GIT_EMERGECONFLICT = -24, /**< A merge conflict exists and cannot continue */ GIT_PASSTHROUGH = -30, /**< A user-configured callback refused to act */ diff --git a/include/git2/odb.h b/include/git2/odb.h index 702e1bd30..dd484553f 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -391,6 +391,20 @@ GIT_EXTERN(int) git_odb_write_pack( void *progress_payload); /** + * Write a `multi-pack-index` file from all the `.pack` files in the ODB. + * + * If the ODB layer understands pack files, then this will create a file called + * `multi-pack-index` next to the `.pack` and `.idx` files, which will contain + * an index of all objects stored in `.pack` files. This will allow for + * O(log n) lookup for n objects (regardless of how many packfiles there + * exist). + * + * @param db object database where the `multi-pack-index` file will be written. + */ +GIT_EXTERN(int) git_odb_write_multi_pack_index( + git_odb *db); + +/** * Determine the object-ID (sha1 hash) of a data buffer * * The resulting SHA-1 OID will be the identifier for the data diff --git a/include/git2/sys/midx.h b/include/git2/sys/midx.h new file mode 100644 index 000000000..e3d749829 --- /dev/null +++ b/include/git2/sys/midx.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_midx_h__ +#define INCLUDE_sys_git_midx_h__ + +#include "git2/common.h" +#include "git2/types.h" + +/** + * @file git2/midx.h + * @brief Git multi-pack-index routines + * @defgroup git_midx Git multi-pack-index routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new writer for `multi-pack-index` files. + * + * @param out location to store the writer pointer. + * @param pack_dir the directory where the `.pack` and `.idx` files are. The + * `multi-pack-index` file will be written in this directory, too. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_midx_writer_new( + git_midx_writer **out, + const char *pack_dir); + +/** + * Free the multi-pack-index writer and its resources. + * + * @param w the writer to free. If NULL no action is taken. + */ +GIT_EXTERN(void) git_midx_writer_free(git_midx_writer *w); + +/** + * Add an `.idx` file to the writer. + * + * @param w the writer + * @param idx_path the path of an `.idx` file. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_midx_writer_add( + git_midx_writer *w, + const char *idx_path); + +/** + * Write a `multi-pack-index` file to a file. + * + * @param w the writer + * @return 0 or an error code + */ +GIT_EXTERN(int) git_midx_writer_commit( + git_midx_writer *w); + +/** + * Dump the contents of the `multi-pack-index` to an in-memory buffer. + * + * @param midx Buffer where to store the contents of the `multi-pack-index`. + * @param w the writer + * @return 0 or an error code + */ +GIT_EXTERN(int) git_midx_writer_dump( + git_buf *midx, + git_midx_writer *w); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 4dba460af..9ae0ed9b3 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -85,6 +85,13 @@ struct git_odb_backend { git_indexer_progress_cb progress_cb, void *progress_payload); /** + * If the backend supports pack files, this will create a + * `multi-pack-index` file which will contain an index of all objects + * across all the `.pack` files. + */ + int GIT_CALLBACK(writemidx)(git_odb_backend *); + + /** * "Freshens" an already existing object, updating its last-used * time. This occurs when `git_odb_write` was called, but the * object already existed (and will not be re-written). The diff --git a/include/git2/types.h b/include/git2/types.h index 4d5c72324..aac8e42e2 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -96,8 +96,8 @@ typedef struct git_odb_stream git_odb_stream; /** A stream to write a packfile to the ODB */ typedef struct git_odb_writepack git_odb_writepack; -/** a writer for commit-graph files. */ -typedef struct git_commit_graph_writer git_commit_graph_writer; +/** a writer for multi-pack-index files. */ +typedef struct git_midx_writer git_midx_writer; /** An open refs database handle. */ typedef struct git_refdb git_refdb; @@ -108,6 +108,9 @@ typedef struct git_refdb_backend git_refdb_backend; /** A git commit-graph */ typedef struct git_commit_graph git_commit_graph; +/** a writer for commit-graph files. */ +typedef struct git_commit_graph_writer git_commit_graph_writer; + /** * Representation of an existing git repository, * including all its object contents diff --git a/src/array.h b/src/array.h index e649d845b..e97688b36 100644 --- a/src/array.h +++ b/src/array.h @@ -41,8 +41,8 @@ typedef git_array_t(char) git_array_generic_t; -/* use a generic array for growth so this can return the new item */ -GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) +/* use a generic array for growth, return 0 on success */ +GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) { volatile git_array_generic_t *a = _a; size_t new_size; @@ -59,18 +59,18 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) goto on_oom; - a->ptr = new_array; a->asize = new_size; a->size++; - return a->ptr + (a->size - 1) * item_size; + a->ptr = new_array; + a->asize = new_size; + return 0; on_oom: git_array_clear(*a); - return NULL; + return -1; } #define git_array_alloc(a) \ - (((a).size >= (a).asize) ? \ - git_array_grow(&(a), sizeof(*(a).ptr)) : \ - ((a).ptr ? &(a).ptr[(a).size++] : (void *)NULL)) + (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ + &(a).ptr[(a).size++] : (void *)NULL) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) diff --git a/src/attrcache.c b/src/attrcache.c index 7fe2bfbdb..2b36b7a9c 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -127,7 +127,7 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) { int error = 0; git_attr_file_entry *entry; - git_attr_file *old = NULL; + git_attr_file *oldfile = NULL; if (!file) return 0; @@ -136,13 +136,13 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) return error; if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) - old = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); + oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); attr_cache_unlock(cache); - if (old) { - GIT_REFCOUNT_OWN(old, NULL); - git_attr_file__free(old); + if (oldfile == file) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); } return error; @@ -401,8 +401,7 @@ int git_attr_cache__init(git_repository *repo) (ret = git_pool_init(&cache->pool, 1)) < 0) goto cancel; - cache = git_atomic_compare_and_swap(&repo->attrcache, NULL, cache); - if (cache) + if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) goto cancel; /* raced with another thread, free this but no error */ git_config_free(cfg); diff --git a/src/checkout.c b/src/checkout.c index 31d473ec8..713159152 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2622,6 +2622,9 @@ int git_checkout_iterator( if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0) goto cleanup; + if (data.strategy & GIT_CHECKOUT_DRY_RUN) + goto cleanup; + data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + counts[CHECKOUT_ACTION__REMOVE_CONFLICT] + counts[CHECKOUT_ACTION__UPDATE_BLOB] + diff --git a/src/clone.c b/src/clone.c index 6d5ebf6b0..e29a4aab3 100644 --- a/src/clone.c +++ b/src/clone.c @@ -162,37 +162,6 @@ done: return error; } -static int update_remote_head_byname( - git_repository *repo, - const char *remote_name, - const char *tracking_branch_name, - const char *reflog_message) -{ - git_buf tracking_head_name = GIT_BUF_INIT; - git_reference *remote_head = NULL; - int error; - - if ((error = git_buf_printf(&tracking_head_name, - "%s%s/%s", - GIT_REFS_REMOTES_DIR, - remote_name, - GIT_HEAD_FILE)) < 0) - goto cleanup; - - error = git_reference_symbolic_create( - &remote_head, - repo, - git_buf_cstr(&tracking_head_name), - tracking_branch_name, - true, - reflog_message); - -cleanup: - git_reference_free(remote_head); - git_buf_dispose(&tracking_head_name); - return error; -} - static int update_remote_head( git_repository *repo, git_remote *remote, @@ -200,7 +169,9 @@ static int update_remote_head( const char *reflog_message) { git_refspec *refspec; - git_buf tracking_branch_name = GIT_BUF_INIT; + git_reference *remote_head = NULL; + git_buf remote_head_name = GIT_BUF_INIT; + git_buf remote_branch_name = GIT_BUF_INIT; int error; /* Determine the remote tracking ref name from the local branch */ @@ -213,19 +184,30 @@ static int update_remote_head( } if ((error = git_refspec_transform( - &tracking_branch_name, + &remote_branch_name, refspec, git_buf_cstr(target))) < 0) goto cleanup; - error = update_remote_head_byname( - repo, + if ((error = git_buf_printf(&remote_head_name, + "%s%s/%s", + GIT_REFS_REMOTES_DIR, git_remote_name(remote), - git_buf_cstr(&tracking_branch_name), + GIT_HEAD_FILE)) < 0) + goto cleanup; + + error = git_reference_symbolic_create( + &remote_head, + repo, + git_buf_cstr(&remote_head_name), + git_buf_cstr(&remote_branch_name), + true, reflog_message); cleanup: - git_buf_dispose(&tracking_branch_name); + git_reference_free(remote_head); + git_buf_dispose(&remote_branch_name); + git_buf_dispose(&remote_head_name); return error; } @@ -277,19 +259,20 @@ cleanup: static int update_head_to_branch( git_repository *repo, - const char *remote_name, + git_remote *remote, const char *branch, const char *reflog_message) { int retcode; git_buf remote_branch_name = GIT_BUF_INIT; git_reference* remote_ref = NULL; + git_buf default_branch = GIT_BUF_INIT; - GIT_ASSERT_ARG(remote_name); + GIT_ASSERT_ARG(remote); GIT_ASSERT_ARG(branch); if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", - remote_name, branch)) < 0 ) + git_remote_name(remote), branch)) < 0 ) goto cleanup; if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0) @@ -299,11 +282,18 @@ static int update_head_to_branch( reflog_message)) < 0) goto cleanup; - retcode = update_remote_head_byname(repo, remote_name, remote_branch_name.ptr, reflog_message); + if ((retcode = git_remote_default_branch(&default_branch, remote)) < 0) + goto cleanup; + + if (!git_remote__matching_refspec(remote, git_buf_cstr(&default_branch))) + goto cleanup; + + retcode = update_remote_head(repo, remote, &default_branch, reflog_message); cleanup: git_reference_free(remote_ref); git_buf_dispose(&remote_branch_name); + git_buf_dispose(&default_branch); return retcode; } @@ -388,8 +378,7 @@ static int checkout_branch(git_repository *repo, git_remote *remote, const git_c int error; if (branch) - error = update_head_to_branch(repo, git_remote_name(remote), branch, - reflog_message); + error = update_head_to_branch(repo, remote, branch, reflog_message); /* Point HEAD to the same ref as the remote's head */ else error = update_head_to_remote(repo, remote, reflog_message); diff --git a/src/diff_driver.c b/src/diff_driver.c index 8e9131feb..a3892d35e 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -141,18 +141,23 @@ static int diff_driver_funcname(const git_config_entry *entry, void *payload) static git_diff_driver_registry *git_repository_driver_registry( git_repository *repo) { - if (!repo->diff_drivers) { - git_diff_driver_registry *reg = git_diff_driver_registry_new(); - reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, reg); + git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg; + if (reg) + return reg; - if (reg != NULL) /* if we race, free losing allocation */ - git_diff_driver_registry_free(reg); - } - - if (!repo->diff_drivers) + newreg = git_diff_driver_registry_new(); + if (!newreg) { git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); - - return repo->diff_drivers; + return newreg; + } + reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg); + if (!reg) { + reg = newreg; + } else { + /* if we race, free losing allocation */ + git_diff_driver_registry_free(newreg); + } + return reg; } static int diff_driver_alloc( diff --git a/src/midx.c b/src/midx.c index d6bb9c1e7..9aab8b588 100644 --- a/src/midx.c +++ b/src/midx.c @@ -7,11 +7,15 @@ #include "midx.h" +#include "array.h" #include "buffer.h" +#include "filebuf.h" #include "futils.h" #include "hash.h" #include "odb.h" #include "pack.h" +#include "path.h" +#include "repository.h" #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ #define MIDX_VERSION 1 @@ -36,6 +40,8 @@ struct git_midx_chunk { size_t length; }; +typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data); + static int midx_error(const char *message) { git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message); @@ -475,3 +481,395 @@ void git_midx_free(git_midx_file *idx) git_midx_close(idx); git__free(idx); } + +static int packfile__cmp(const void *a_, const void *b_) +{ + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; + + return strcmp(a->pack_name, b->pack_name); +} + +int git_midx_writer_new( + git_midx_writer **out, + const char *pack_dir) +{ + git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer)); + GIT_ERROR_CHECK_ALLOC(w); + + if (git_buf_sets(&w->pack_dir, pack_dir) < 0) { + git__free(w); + return -1; + } + git_path_squash_slashes(&w->pack_dir); + + if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) { + git_buf_dispose(&w->pack_dir); + git__free(w); + return -1; + } + + *out = w; + return 0; +} + +void git_midx_writer_free(git_midx_writer *w) +{ + struct git_pack_file *p; + size_t i; + + if (!w) + return; + + git_vector_foreach (&w->packs, i, p) + git_mwindow_put_pack(p); + git_vector_free(&w->packs); + git_buf_dispose(&w->pack_dir); + git__free(w); +} + +int git_midx_writer_add( + git_midx_writer *w, + const char *idx_path) +{ + git_buf idx_path_buf = GIT_BUF_INIT; + int error; + struct git_pack_file *p; + + error = git_path_prettify(&idx_path_buf, idx_path, git_buf_cstr(&w->pack_dir)); + if (error < 0) + return error; + + error = git_mwindow_get_pack(&p, git_buf_cstr(&idx_path_buf)); + git_buf_dispose(&idx_path_buf); + if (error < 0) + return error; + + error = git_vector_insert(&w->packs, p); + if (error < 0) { + git_mwindow_put_pack(p); + return error; + } + + return 0; +} + +typedef git_array_t(git_midx_entry) object_entry_array_t; + +struct object_entry_cb_state { + uint32_t pack_index; + object_entry_array_t *object_entries_array; +}; + +static int object_entry__cb(const git_oid *oid, off64_t offset, void *data) +{ + struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; + + git_midx_entry *entry = git_array_alloc(*state->object_entries_array); + GIT_ERROR_CHECK_ALLOC(entry); + + git_oid_cpy(&entry->sha1, oid); + entry->offset = offset; + entry->pack_index = state->pack_index; + + return 0; +} + +static int object_entry__cmp(const void *a_, const void *b_) +{ + const git_midx_entry *a = (const git_midx_entry *)a_; + const git_midx_entry *b = (const git_midx_entry *)b_; + + return git_oid_cmp(&a->sha1, &b->sha1); +} + +static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + int error; + uint32_t word; + + word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + + return 0; +} + +static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + uint32_t word = htonl(chunk_id); + int error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + return write_offset(offset, write_cb, cb_data); + + return 0; +} + +static int midx_write_buf(const char *buf, size_t size, void *data) +{ + git_buf *b = (git_buf *)data; + return git_buf_put(b, buf, size); +} + +struct midx_write_hash_context { + midx_write_cb write_cb; + void *cb_data; + git_hash_ctx *ctx; +}; + +static int midx_write_hash(const char *buf, size_t size, void *data) +{ + struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; + int error; + + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + + return ctx->write_cb(buf, size, ctx->cb_data); +} + +static int midx_write( + git_midx_writer *w, + midx_write_cb write_cb, + void *cb_data) +{ + int error = 0; + size_t i; + struct git_pack_file *p; + struct git_midx_header hdr = {0}; + uint32_t oid_fanout_count; + uint32_t object_large_offsets_count; + uint32_t oid_fanout[256]; + off64_t offset; + git_buf packfile_names = GIT_BUF_INIT, + oid_lookup = GIT_BUF_INIT, + object_offsets = GIT_BUF_INIT, + object_large_offsets = GIT_BUF_INIT; + git_oid idx_checksum = {{0}}; + git_midx_entry *entry; + object_entry_array_t object_entries_array = GIT_ARRAY_INIT; + git_vector object_entries = GIT_VECTOR_INIT; + git_hash_ctx ctx; + struct midx_write_hash_context hash_cb_data = {0}; + + hdr.signature = htonl(MIDX_SIGNATURE); + hdr.version = MIDX_VERSION; + hdr.object_id_version = MIDX_OBJECT_ID_VERSION; + hdr.base_midx_files = 0; + + hash_cb_data.write_cb = write_cb; + hash_cb_data.cb_data = cb_data; + hash_cb_data.ctx = &ctx; + + error = git_hash_ctx_init(&ctx); + if (error < 0) + return error; + cb_data = &hash_cb_data; + write_cb = midx_write_hash; + + git_vector_sort(&w->packs); + git_vector_foreach (&w->packs, i, p) { + git_buf relative_index = GIT_BUF_INIT; + struct object_entry_cb_state state = {0}; + size_t path_len; + + state.pack_index = (uint32_t)i; + state.object_entries_array = &object_entries_array; + + error = git_buf_sets(&relative_index, p->pack_name); + if (error < 0) + goto cleanup; + error = git_path_make_relative(&relative_index, git_buf_cstr(&w->pack_dir)); + if (error < 0) { + git_buf_dispose(&relative_index); + goto cleanup; + } + path_len = git_buf_len(&relative_index); + if (path_len <= strlen(".pack") || git__suffixcmp(git_buf_cstr(&relative_index), ".pack") != 0) { + git_buf_dispose(&relative_index); + git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name); + error = -1; + goto cleanup; + } + path_len -= strlen(".pack"); + + git_buf_put(&packfile_names, git_buf_cstr(&relative_index), path_len); + git_buf_puts(&packfile_names, ".idx"); + git_buf_putc(&packfile_names, '\0'); + git_buf_dispose(&relative_index); + + error = git_pack_foreach_entry_offset(p, object_entry__cb, &state); + if (error < 0) + goto cleanup; + } + + /* Sort the object entries. */ + error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp); + if (error < 0) + goto cleanup; + git_array_foreach (object_entries_array, i, entry) + error = git_vector_set(NULL, &object_entries, i, entry); + git_vector_set_sorted(&object_entries, 0); + git_vector_sort(&object_entries); + git_vector_uniq(&object_entries, NULL); + + /* Pad the packfile names so it is a multiple of four. */ + while (git_buf_len(&packfile_names) & 3) + git_buf_putc(&packfile_names, '\0'); + + /* Fill the OID Fanout table. */ + oid_fanout_count = 0; + for (i = 0; i < 256; i++) { + while (oid_fanout_count < git_vector_length(&object_entries) && + ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i) + ++oid_fanout_count; + oid_fanout[i] = htonl(oid_fanout_count); + } + + /* Fill the OID Lookup table. */ + git_vector_foreach (&object_entries, i, entry) { + error = git_buf_put(&oid_lookup, (const char *)&entry->sha1, sizeof(entry->sha1)); + if (error < 0) + goto cleanup; + } + + /* Fill the Object Offsets and Object Large Offsets tables. */ + object_large_offsets_count = 0; + git_vector_foreach (&object_entries, i, entry) { + uint32_t word; + + word = htonl((uint32_t)entry->pack_index); + error = git_buf_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + if (entry->offset >= 0x80000000l) { + word = htonl(0x80000000u | object_large_offsets_count++); + error = write_offset(entry->offset, midx_write_buf, &object_large_offsets); + } else { + word = htonl((uint32_t)entry->offset & 0x7fffffffu); + } + error = git_buf_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + } + + /* Write the header. */ + hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs)); + hdr.chunks = 4; + if (git_buf_len(&object_large_offsets) > 0) + hdr.chunks++; + error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); + if (error < 0) + goto cleanup; + + /* Write the chunk headers. */ + offset = sizeof(hdr) + (hdr.chunks + 1) * 12; + error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_buf_len(&packfile_names); + error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += sizeof(oid_fanout); + error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_buf_len(&oid_lookup); + error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_buf_len(&object_offsets); + if (git_buf_len(&object_large_offsets) > 0) { + error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_buf_len(&object_large_offsets); + } + error = write_chunk_header(0, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + + /* Write all the chunks. */ + error = write_cb(git_buf_cstr(&packfile_names), git_buf_len(&packfile_names), cb_data); + if (error < 0) + goto cleanup; + error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_buf_cstr(&oid_lookup), git_buf_len(&oid_lookup), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_buf_cstr(&object_offsets), git_buf_len(&object_offsets), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_buf_cstr(&object_large_offsets), git_buf_len(&object_large_offsets), cb_data); + if (error < 0) + goto cleanup; + + /* Finalize the checksum and write the trailer. */ + error = git_hash_final(&idx_checksum, &ctx); + if (error < 0) + goto cleanup; + error = write_cb((const char *)&idx_checksum, sizeof(idx_checksum), cb_data); + if (error < 0) + goto cleanup; + +cleanup: + git_array_clear(object_entries_array); + git_vector_free(&object_entries); + git_buf_dispose(&packfile_names); + git_buf_dispose(&oid_lookup); + git_buf_dispose(&object_offsets); + git_buf_dispose(&object_large_offsets); + git_hash_ctx_cleanup(&ctx); + return error; +} + +static int midx_write_filebuf(const char *buf, size_t size, void *data) +{ + git_filebuf *f = (git_filebuf *)data; + return git_filebuf_write(f, buf, size); +} + +int git_midx_writer_commit( + git_midx_writer *w) +{ + int error; + int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; + git_buf midx_path = GIT_BUF_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + + error = git_buf_joinpath(&midx_path, git_buf_cstr(&w->pack_dir), "multi-pack-index"); + if (error < 0) + return error; + + if (git_repository__fsync_gitdir) + filebuf_flags |= GIT_FILEBUF_FSYNC; + error = git_filebuf_open(&output, git_buf_cstr(&midx_path), filebuf_flags, 0644); + git_buf_dispose(&midx_path); + if (error < 0) + return error; + + error = midx_write(w, midx_write_filebuf, &output); + if (error < 0) { + git_filebuf_cleanup(&output); + return error; + } + + return git_filebuf_commit(&output); +} + +int git_midx_writer_dump( + git_buf *midx, + git_midx_writer *w) +{ + return midx_write(w, midx_write_buf, midx); +} diff --git a/src/midx.h b/src/midx.h index 543ff2178..4ce17ce73 100644 --- a/src/midx.h +++ b/src/midx.h @@ -12,6 +12,8 @@ #include <ctype.h> +#include "git2/sys/midx.h" + #include "map.h" #include "mwindow.h" #include "odb.h" @@ -67,6 +69,20 @@ typedef struct git_midx_entry { git_oid sha1; } git_midx_entry; +/* + * A writer for `multi-pack-index` files. + */ +struct git_midx_writer { + /* + * The path of the directory where the .pack/.idx files are stored. The + * `multi-pack-index` file will be written to the same directory. + */ + git_buf pack_dir; + + /* The list of `git_pack_file`s. */ + git_vector packs; +}; + int git_midx_open( git_midx_file **idx_out, const char *path); @@ -1703,6 +1703,35 @@ int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_indexer_ return error; } +int git_odb_write_multi_pack_index(git_odb *db) +{ + size_t i, writes = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(db); + + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writemidx != NULL) { + ++writes; + error = b->writemidx(b); + } + } + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !writes) + error = git_odb__error_unsupported_in_backend("write multi-pack-index"); + + return error; +} + void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) { GIT_UNUSED(backend); diff --git a/src/odb_pack.c b/src/odb_pack.c index 3df8a4267..f4cb9a558 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -402,7 +402,6 @@ static int process_multi_pack_index_pack( const char *packfile_name) { int error; - size_t cmp_len = strlen(packfile_name); struct git_pack_file *pack; size_t found_position; git_buf pack_path = GIT_BUF_INIT, index_prefix = GIT_BUF_INIT; @@ -411,12 +410,11 @@ static int process_multi_pack_index_pack( if (error < 0) return error; - /* This is ensured by midx__parse_packfile_name() */ - if (cmp_len <= strlen(".idx") || git__suffixcmp(git_buf_cstr(&pack_path), ".idx") != 0) + /* This is ensured by midx_parse_packfile_name() */ + if (git_buf_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_buf_cstr(&pack_path), ".idx") != 0) return git_odb__error_notfound("midx file contained a non-index", NULL, 0); - cmp_len -= strlen(".idx"); - git_buf_attach_notowned(&index_prefix, git_buf_cstr(&pack_path), cmp_len); + git_buf_attach_notowned(&index_prefix, git_buf_cstr(&pack_path), git_buf_len(&pack_path) - strlen(".idx")); if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) { /* Pack was found in the packs list. Moving it to the midx_packs list. */ @@ -744,6 +742,81 @@ static int pack_backend__writepack(struct git_odb_writepack **out, return 0; } +static int get_idx_path( + git_buf *idx_path, + struct pack_backend *backend, + struct git_pack_file *p) +{ + size_t path_len; + int error; + + error = git_path_prettify(idx_path, p->pack_name, backend->pack_folder); + if (error < 0) + return error; + path_len = git_buf_len(idx_path); + if (path_len <= strlen(".pack") || git__suffixcmp(git_buf_cstr(idx_path), ".pack") != 0) + return git_odb__error_notfound("packfile does not end in .pack", NULL, 0); + path_len -= strlen(".pack"); + error = git_buf_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx")); + if (error < 0) + return error; + + return 0; +} + +static int pack_backend__writemidx(git_odb_backend *_backend) +{ + struct pack_backend *backend; + git_midx_writer *w = NULL; + struct git_pack_file *p; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(_backend); + + backend = (struct pack_backend *)_backend; + + error = git_midx_writer_new(&w, backend->pack_folder); + if (error < 0) + return error; + + git_vector_foreach(&backend->midx_packs, i, p) { + git_buf idx_path = GIT_BUF_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_buf_cstr(&idx_path)); + git_buf_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + git_vector_foreach(&backend->packs, i, p) { + git_buf idx_path = GIT_BUF_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_buf_cstr(&idx_path)); + git_buf_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + + /* + * Invalidate the previous midx before writing the new one. + */ + error = remove_multi_pack_index(backend); + if (error < 0) + goto cleanup; + error = git_midx_writer_commit(w); + if (error < 0) + goto cleanup; + error = refresh_multi_pack_index(backend); + +cleanup: + git_midx_writer_free(w); + return error; +} + static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; @@ -792,6 +865,7 @@ static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) backend->parent.refresh = &pack_backend__refresh; backend->parent.foreach = &pack_backend__foreach; backend->parent.writepack = &pack_backend__writepack; + backend->parent.writemidx = &pack_backend__writemidx; backend->parent.freshen = &pack_backend__freshen; backend->parent.free = &pack_backend__free; diff --git a/src/pack.c b/src/pack.c index 5d284ca9e..94b1ecd9d 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1368,6 +1368,73 @@ int git_pack_foreach_entry( return error; } +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data) +{ + const unsigned char *index; + off64_t current_offset; + const git_oid *current_oid; + uint32_t i; + int error = 0; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for git_pack_foreach_entry_offset"); + + index = p->index_map.data; + if (index == NULL) { + if ((error = pack_index_open_locked(p)) < 0) + goto cleanup; + + GIT_ASSERT(p->index_map.data); + index = p->index_map.data; + } + + if (p->index_version > 1) + index += 8; + + index += 4 * 256; + + /* all offsets should have been validated by pack_index_check_locked */ + if (p->index_version > 1) { + const unsigned char *offsets = index + 24 * p->num_objects; + const unsigned char *large_offset_ptr; + const unsigned char *large_offsets = index + 28 * p->num_objects; + const unsigned char *large_offsets_end = ((const unsigned char *)p->index_map.data) + p->index_map.len - 20; + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(offsets + 4 * i)); + if (current_offset & 0x80000000) { + large_offset_ptr = large_offsets + (current_offset & 0x7fffffff) * 8; + if (large_offset_ptr >= large_offsets_end) { + error = packfile_error("invalid large offset"); + goto cleanup; + } + current_offset = (((off64_t)ntohl(*((uint32_t *)(large_offset_ptr + 0)))) << 32) | + ntohl(*((uint32_t *)(large_offset_ptr + 4))); + } + current_oid = (const git_oid *)(index + 20 * i); + if ((error = cb(current_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } else { + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(index + 24 * i)); + current_oid = (const git_oid *)(index + 24 * i + 4); + if ((error = cb(current_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } + +cleanup: + git_mutex_unlock(&p->lock); + return error; +} + int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, unsigned hi, const unsigned char *oid_prefix) { diff --git a/src/pack.h b/src/pack.h index 1d077240d..bf279c6b6 100644 --- a/src/pack.h +++ b/src/pack.h @@ -20,6 +20,14 @@ #include "oidmap.h" #include "zstream.h" +/** + * Function type for callbacks from git_pack_foreach_entry_offset. + */ +typedef int git_pack_foreach_entry_offset_cb( + const git_oid *id, + off64_t offset, + void *payload); + #define GIT_PACK_FILE_MODE 0444 #define PACK_SIGNATURE 0x5041434b /* "PACK" */ @@ -176,5 +184,16 @@ int git_pack_foreach_entry( struct git_pack_file *p, git_odb_foreach_cb cb, void *data); +/** + * Similar to git_pack_foreach_entry, but: + * - It also provides the offset of the object within the + * packfile. + * - It does not sort the objects in any order. + * - It retains the lock while invoking the callback. + */ +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data); #endif diff --git a/src/repository.c b/src/repository.c index 692f71861..9ed46bf4d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1093,8 +1093,7 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo) if (!error) { GIT_REFCOUNT_OWN(config, repo); - config = git_atomic_compare_and_swap(&repo->_config, NULL, config); - if (config != NULL) { + if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) { GIT_REFCOUNT_OWN(config, NULL); git_config_free(config); } @@ -1164,8 +1163,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) return error; } - odb = git_atomic_compare_and_swap(&repo->_odb, NULL, odb); - if (odb != NULL) { + if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) { GIT_REFCOUNT_OWN(odb, NULL); git_odb_free(odb); } @@ -1209,8 +1207,7 @@ int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) if (!error) { GIT_REFCOUNT_OWN(refdb, repo); - refdb = git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb); - if (refdb != NULL) { + if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) { GIT_REFCOUNT_OWN(refdb, NULL); git_refdb_free(refdb); } @@ -1257,8 +1254,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) if (!error) { GIT_REFCOUNT_OWN(index, repo); - index = git_atomic_compare_and_swap(&repo->_index, NULL, index); - if (index != NULL) { + if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) { GIT_REFCOUNT_OWN(index, NULL); git_index_free(index); } diff --git a/src/thread.h b/src/thread.h index b4f869243..4b091c0a2 100644 --- a/src/thread.h +++ b/src/thread.h @@ -74,6 +74,9 @@ typedef git_atomic32 git_atomic_ssize; # include "unix/pthread.h" #endif +/* + * Atomically sets the contents of *a to be val. + */ GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) { #if defined(GIT_WIN32) @@ -87,6 +90,10 @@ GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) #endif } +/* + * Atomically increments the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) { #if defined(GIT_WIN32) @@ -100,10 +107,14 @@ GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) #endif } +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) { #if defined(GIT_WIN32) - return InterlockedExchangeAdd(&a->val, addend); + return InterlockedAdd(&a->val, addend); #elif defined(GIT_BUILTIN_ATOMIC) return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); #elif defined(GIT_BUILTIN_SYNC) @@ -113,6 +124,10 @@ GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) #endif } +/* + * Atomically decrements the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) { #if defined(GIT_WIN32) @@ -126,6 +141,10 @@ GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) #endif } +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) { #if defined(GIT_WIN32) @@ -143,16 +162,13 @@ GIT_INLINE(void *) git_atomic__compare_and_swap( void * volatile *ptr, void *oldval, void *newval) { #if defined(GIT_WIN32) - volatile void *foundval; - foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); - return (foundval == oldval) ? oldval : newval; + return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); #elif defined(GIT_BUILTIN_ATOMIC) - bool success = __atomic_compare_exchange(ptr, &oldval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); - return success ? oldval : newval; + void *foundval = oldval; + __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return foundval; #elif defined(GIT_BUILTIN_SYNC) - volatile void *foundval; - foundval = __sync_val_compare_and_swap(ptr, oldval, newval); - return (foundval == oldval) ? oldval : newval; + return __sync_val_compare_and_swap(ptr, oldval, newval); #else # error "Unsupported architecture for atomic operations" #endif @@ -164,11 +180,11 @@ GIT_INLINE(volatile void *) git_atomic__swap( #if defined(GIT_WIN32) return InterlockedExchangePointer(ptr, newval); #elif defined(GIT_BUILTIN_ATOMIC) - void * volatile foundval; + void * volatile foundval = NULL; __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); return foundval; #elif defined(GIT_BUILTIN_SYNC) - return __sync_lock_test_and_set(ptr, newval); + return (volatile void *)__sync_lock_test_and_set(ptr, newval); #else # error "Unsupported architecture for atomic operations" #endif @@ -178,9 +194,7 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) { #if defined(GIT_WIN32) void *newval = NULL, *oldval = NULL; - volatile void *foundval = NULL; - foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); - return foundval; + return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); #elif defined(GIT_BUILTIN_ATOMIC) return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); #elif defined(GIT_BUILTIN_SYNC) @@ -192,10 +206,14 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) #ifdef GIT_ARCH_64 +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) { #if defined(GIT_WIN32) - return InterlockedExchangeAdd64(&a->val, addend); + return InterlockedAdd64(&a->val, addend); #elif defined(GIT_BUILTIN_ATOMIC) return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); #elif defined(GIT_BUILTIN_SYNC) @@ -205,6 +223,9 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #endif } +/* + * Atomically sets the contents of *a to be val. + */ GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) { #if defined(GIT_WIN32) @@ -218,6 +239,10 @@ GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) #endif } +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) { #if defined(GIT_WIN32) @@ -297,11 +322,10 @@ GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) GIT_INLINE(void *) git_atomic__compare_and_swap( void * volatile *ptr, void *oldval, void *newval) { - if (*ptr == oldval) + void *foundval = *ptr; + if (foundval == oldval) *ptr = newval; - else - oldval = newval; - return oldval; + return foundval; } GIT_INLINE(volatile void *) git_atomic__swap( @@ -339,17 +363,50 @@ GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) #endif -/* Atomically replace oldval with newval - * @return oldval if it was replaced or newval if it was not +/* + * Atomically replace the contents of *ptr (if they are equal to oldval) with + * newval. ptr must point to a pointer or a value that is the same size as a + * pointer. This is semantically compatible with: + * + * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ + * ({ \ + * void *foundval = *ptr; \ + * if (foundval == oldval) \ + * *ptr = newval; \ + * foundval; \ + * }) + * + * @return the original contents of *ptr. */ -#define git_atomic_compare_and_swap(P,O,N) \ - git_atomic__compare_and_swap((void * volatile *)P, O, N) +#define git_atomic_compare_and_swap(ptr, oldval, newval) \ + git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) -#define git_atomic_swap(ptr, val) \ - (void *)git_atomic__swap((void * volatile *)&ptr, val) +/* + * Atomically replace the contents of v with newval. v must be the same size as + * a pointer. This is semantically compatible with: + * + * #define git_atomic_swap(v, newval) \ + * ({ \ + * volatile void *old = v; \ + * v = newval; \ + * old; \ + * }) + * + * @return the original contents of v. + */ +#define git_atomic_swap(v, newval) \ + (void *)git_atomic__swap((void * volatile *)&(v), newval) -#define git_atomic_load(ptr) \ - (void *)git_atomic__load((void * volatile *)&ptr) +/* + * Atomically reads the contents of v. v must be the same size as a pointer. + * This is semantically compatible with: + * + * #define git_atomic_load(v) v + * + * @return the contents of v. + */ +#define git_atomic_load(v) \ + (void *)git_atomic__load((void * volatile *)&(v)) #if defined(GIT_THREADS) diff --git a/src/transports/auth.c b/src/transports/auth.c index 4aa3df021..51763e359 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -18,7 +18,7 @@ static int basic_next_token( { git_credential_userpass_plaintext *cred; git_buf raw = GIT_BUF_INIT; - int error = -1; + int error = GIT_EAUTH; GIT_UNUSED(ctx); diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c index c538dbbca..31469933e 100644 --- a/src/transports/auth_negotiate.c +++ b/src/transports/auth_negotiate.c @@ -267,7 +267,7 @@ static int negotiate_init_context( if (!ctx->oid) { git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); - return -1; + return GIT_EAUTH; } git_buf_puts(&ctx->target, "HTTP@"); diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c index b5110616b..742db75b3 100644 --- a/src/transports/auth_ntlm.c +++ b/src/transports/auth_ntlm.c @@ -85,7 +85,7 @@ static int ntlm_next_token( git_buf input_buf = GIT_BUF_INIT; const unsigned char *msg; size_t challenge_len, msg_len; - int error = -1; + int error = GIT_EAUTH; GIT_ASSERT_ARG(buf); GIT_ASSERT_ARG(ctx); diff --git a/src/transports/http.c b/src/transports/http.c index 9871be5ad..691bceb75 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -162,7 +162,7 @@ static int handle_auth( if (error > 0) { git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); - error = -1; + error = GIT_EAUTH; } if (!error) @@ -179,7 +179,7 @@ GIT_INLINE(int) handle_remote_auth( if (response->server_auth_credtypes == 0) { git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); - return -1; + return GIT_EAUTH; } /* Otherwise, prompt for credentials. */ @@ -201,7 +201,7 @@ GIT_INLINE(int) handle_proxy_auth( if (response->proxy_auth_credtypes == 0) { git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); - return -1; + return GIT_EAUTH; } /* Otherwise, prompt for credentials. */ @@ -259,7 +259,7 @@ static int handle_response( } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED || response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure"); - return -1; + return GIT_EAUTH; } if (response->status != GIT_HTTP_STATUS_OK) { @@ -416,7 +416,7 @@ static int http_stream_read( if (stream->state == HTTP_STATE_SENDING_REQUEST) { git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - error = -1; + error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */ goto done; } @@ -554,7 +554,7 @@ static int http_stream_write( if (stream->state == HTTP_STATE_NONE) { git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - error = -1; + error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */ goto done; } diff --git a/src/transports/httpclient.c b/src/transports/httpclient.c index ba86184df..4612b4334 100644 --- a/src/transports/httpclient.c +++ b/src/transports/httpclient.c @@ -597,6 +597,7 @@ static int apply_credentials( free_auth_context(server); } else if (!token.size) { git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge"); + error = GIT_EAUTH; error = -1; goto done; } diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 71c37e758..efa77a798 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -461,13 +461,13 @@ static int request_creds(git_credential **out, ssh_subtransport *t, const char * if (no_callback) { git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); - return -1; + return GIT_EAUTH; } if (!(cred->credtype & auth_methods)) { cred->free(cred); - git_error_set(GIT_ERROR_SSH, "callback returned unsupported credentials type"); - return -1; + git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); + return GIT_EAUTH; } *out = cred; @@ -840,7 +840,7 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ if (list == NULL && !libssh2_userauth_authenticated(session)) { ssh_error(session, "Failed to retrieve list of SSH authentication methods"); - return -1; + return GIT_EAUTH; } ptr = list; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index d21c50651..ea2195a99 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -154,7 +154,7 @@ static int apply_userpass_credentials(HINTERNET request, DWORD target, int mecha native_scheme = WINHTTP_AUTH_SCHEME_BASIC; } else { git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); - error = -1; + error = GIT_EAUTH; goto done; } @@ -193,7 +193,7 @@ static int apply_default_credentials(HINTERNET request, DWORD target, int mechan native_scheme = WINHTTP_AUTH_SCHEME_NTLM; } else { git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); - return -1; + return GIT_EAUTH; } /* @@ -616,7 +616,7 @@ static int parse_unauthorized_response( */ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes"); - return -1; + return GIT_EAUTH; } if (WINHTTP_AUTH_SCHEME_NTLM & supported) { @@ -1040,7 +1040,7 @@ replay: /* Enforce a reasonable cap on the number of replays */ if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - return -1; + return GIT_ERROR; /* not GIT_EAUTH because the exact cause is not clear */ } /* Connect if necessary */ diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index f8d9abe55..3241a3eb7 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1636,3 +1636,49 @@ void test_checkout_tree__no_index_refresh(void) modify_index_and_checkout_tree(&opts); assert_status_entrycount(g_repo, 0); } + +void test_checkout_tree__dry_run(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + checkout_counts ct; + + /* first let's get things into a known state - by checkout out the HEAD */ + + assert_on_branch(g_repo, "master"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_path_isdir("testrepo/a")); + + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* now checkout branch but with dry run enabled */ + + memset(&ct, 0, sizeof(ct)); + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DRY_RUN; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &ct; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + assert_on_branch(g_repo, "dir"); + + /* these normally would have been created and updated, but with + * DRY_RUN they will be unchanged. + */ + cl_assert(!git_path_isdir("testrepo/a")); + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* check that notify callback was invoked */ + cl_assert_equal_i(ct.n_updates, 2); + + git_object_free(obj); +} diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c index d4da3d3af..ec12fee18 100644 --- a/tests/clone/nonetwork.c +++ b/tests/clone/nonetwork.c @@ -172,7 +172,7 @@ void test_clone_nonetwork__can_checkout_given_branch(void) cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD")); cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head)); - cl_assert_equal_s("refs/remotes/origin/test", git_reference_symbolic_target(remote_head)); + cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head)); git_reference_free(remote_head); } diff --git a/tests/config/read.c b/tests/config/read.c index 109908750..badf5118e 100644 --- a/tests/config/read.c +++ b/tests/config/read.c @@ -964,7 +964,7 @@ void test_config_read__get_mapped(void) " key9 = off\n"); cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - // check parsing bool and string + /* check parsing bool and string */ cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map1, ARRAY_SIZE(_test_map1))); cl_assert_equal_i(val, MAP_TRUE); cl_git_pass(git_config_get_mapped(&val, cfg, "header.key2", _test_map1, ARRAY_SIZE(_test_map1))); @@ -986,7 +986,7 @@ void test_config_read__get_mapped(void) cl_git_fail(git_config_get_mapped(&val, cfg, "header.key7", _test_map1, ARRAY_SIZE(_test_map1))); - // check parsing int values + /* check parsing int values */ cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map2, ARRAY_SIZE(_test_map2))); cl_git_pass(git_config_get_int32(&known_good, cfg, "header.key1")); cl_assert_equal_i(val, known_good); diff --git a/tests/pack/midx.c b/tests/pack/midx.c index 0d7efbef1..d5b91d28b 100644 --- a/tests/pack/midx.c +++ b/tests/pack/midx.c @@ -1,7 +1,9 @@ #include "clar_libgit2.h" #include <git2.h> +#include <git2/sys/midx.h> +#include "futils.h" #include "midx.h" void test_pack_midx__parse(void) @@ -44,3 +46,65 @@ void test_pack_midx__lookup(void) git_commit_free(commit); git_repository_free(repo); } + +void test_pack_midx__writer(void) +{ + git_repository *repo; + git_midx_writer *w = NULL; + git_buf midx = GIT_BUF_INIT, expected_midx = GIT_BUF_INIT, path = GIT_BUF_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/pack")); + cl_git_pass(git_midx_writer_new(&w, git_buf_cstr(&path))); + + cl_git_pass(git_midx_writer_add(w, "pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx")); + cl_git_pass(git_midx_writer_add(w, "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx")); + cl_git_pass(git_midx_writer_add(w, "pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx")); + + cl_git_pass(git_midx_writer_dump(&midx, w)); + cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/pack/multi-pack-index")); + cl_git_pass(git_futils_readbuffer(&expected_midx, git_buf_cstr(&path))); + + cl_assert_equal_i(git_buf_len(&midx), git_buf_len(&expected_midx)); + cl_assert_equal_strn(git_buf_cstr(&midx), git_buf_cstr(&expected_midx), git_buf_len(&midx)); + + git_buf_dispose(&midx); + git_buf_dispose(&expected_midx); + git_buf_dispose(&path); + git_midx_writer_free(w); + git_repository_free(repo); +} + +void test_pack_midx__odb_create(void) +{ + git_repository *repo; + git_odb *odb; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_buf midx = GIT_BUF_INIT, expected_midx = GIT_BUF_INIT, midx_path = GIT_BUF_INIT; + struct stat st; + + opts.bare = true; + opts.local = GIT_CLONE_LOCAL; + cl_git_pass(git_clone(&repo, cl_fixture("testrepo/.gitted"), "./clone.git", &opts)); + cl_git_pass(git_buf_joinpath(&midx_path, git_repository_path(repo), "objects/pack/multi-pack-index")); + cl_git_fail(p_stat(git_buf_cstr(&midx_path), &st)); + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_git_pass(git_odb_write_multi_pack_index(odb)); + git_odb_free(odb); + + cl_git_pass(p_stat(git_buf_cstr(&midx_path), &st)); + + cl_git_pass(git_futils_readbuffer(&expected_midx, cl_fixture("testrepo.git/objects/pack/multi-pack-index"))); + cl_git_pass(git_futils_readbuffer(&midx, git_buf_cstr(&midx_path))); + cl_assert_equal_i(git_buf_len(&midx), git_buf_len(&expected_midx)); + cl_assert_equal_strn(git_buf_cstr(&midx), git_buf_cstr(&expected_midx), git_buf_len(&midx)); + + git_repository_free(repo); + git_buf_dispose(&midx); + git_buf_dispose(&midx_path); + git_buf_dispose(&expected_midx); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); +}
\ No newline at end of file diff --git a/tests/remote/fetch.c b/tests/remote/fetch.c index 392801e0b..7e825ca16 100644 --- a/tests/remote/fetch.c +++ b/tests/remote/fetch.c @@ -72,7 +72,7 @@ void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, .strings = &refspec_strs, }; - // create two commits in repo 1 and a reference to them + /* create two commits in repo 1 and a reference to them */ { git_oid empty_tree_id; git_tree *empty_tree; @@ -92,7 +92,7 @@ void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, git_treebuilder_free(tb); } - // fetch the reference via the remote + /* fetch the reference via the remote */ { git_remote *remote; @@ -103,7 +103,7 @@ void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, git_remote_free(remote); } - // assert that repo2 references the second commit + /* assert that repo2 references the second commit */ { const git_oid *target; git_reference *ref; @@ -113,7 +113,7 @@ void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, git_reference_free(ref); } - // set the reference in repo1 to point to the older commit + /* set the reference in repo1 to point to the older commit */ { git_reference *ref; git_reference *ref2; @@ -124,7 +124,7 @@ void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, git_reference_free(ref2); } - // fetch the reference again + /* fetch the reference again */ { git_remote *remote; @@ -144,7 +144,7 @@ void test_remote_fetch__dont_update_refs_if_not_descendant_and_not_force(void) { do_time_travelling_fetch(&commit1id, &commit2id, false); - // assert that the reference in repo2 has not changed + /* assert that the reference in repo2 has not changed */ cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); target = git_reference_target(ref); cl_assert_equal_b(git_oid_cmp(target, &commit2id), 0); @@ -160,7 +160,7 @@ void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) { do_time_travelling_fetch(&commit1id, &commit2id, true); - // assert that the reference in repo2 has changed + /* assert that the reference in repo2 has changed */ cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); target = git_reference_target(ref); cl_assert_equal_b(git_oid_cmp(target, &commit1id), 0); diff --git a/tests/threads/atomic.c b/tests/threads/atomic.c new file mode 100644 index 000000000..4d04a777a --- /dev/null +++ b/tests/threads/atomic.c @@ -0,0 +1,125 @@ +#include "clar_libgit2.h" + +void test_threads_atomic__atomic32_set(void) +{ + git_atomic32 v = {0}; + git_atomic32_set(&v, 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_get(void) +{ + git_atomic32 v = {1}; + cl_assert_equal_i(git_atomic32_get(&v), 1); +} + +void test_threads_atomic__atomic32_inc(void) +{ + git_atomic32 v = {0}; + cl_assert_equal_i(git_atomic32_inc(&v), 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_add(void) +{ + git_atomic32 v = {0}; + cl_assert_equal_i(git_atomic32_add(&v, 1), 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_dec(void) +{ + git_atomic32 v = {1}; + cl_assert_equal_i(git_atomic32_dec(&v), 0); + cl_assert_equal_i(v.val, 0); +} + +void test_threads_atomic__atomic64_set(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {0}; + git_atomic64_set(&v, 1); + cl_assert_equal_i(v.val, 1); +#endif +} + +void test_threads_atomic__atomic64_get(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {1}; + cl_assert_equal_i(git_atomic64_get(&v), 1); +#endif +} + +void test_threads_atomic__atomic64_add(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {0}; + cl_assert_equal_i(git_atomic64_add(&v, 1), 1); + cl_assert_equal_i(v.val, 1); +#endif +} + +void test_threads_atomic__cas_pointer(void) +{ + int *value = NULL; + int newvalue1 = 1, newvalue2 = 2; + + /* value is updated */ + cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue1), NULL); + cl_assert_equal_p(value, &newvalue1); + + /* value is not updated */ + cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue2), &newvalue1); + cl_assert_equal_p(value, &newvalue1); +} + +void test_threads_atomic__cas_intptr(void) +{ + intptr_t value = 0; + intptr_t oldvalue; + intptr_t newvalue; + + /* value is updated */ + oldvalue = 0; + newvalue = 1; + cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 0); + cl_assert_equal_i(value, 1); + + /* value is not updated */ + oldvalue = 0; + newvalue = 2; + cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 1); + cl_assert_equal_i(value, 1); +} + +void test_threads_atomic__swap(void) +{ + int *value = NULL; + int newvalue = 1; + + cl_assert_equal_p(git_atomic_swap(value, &newvalue), NULL); + cl_assert_equal_p(value, &newvalue); + + cl_assert_equal_p(git_atomic_swap(value, NULL), &newvalue); + cl_assert_equal_p(value, NULL); +} + +void test_threads_atomic__load_ptr(void) +{ + int value = 1; + int *ptr = &value; + cl_assert_equal_p(git_atomic_load(ptr), &value); +} + +void test_threads_atomic__load_intptr(void) +{ + intptr_t value = 1; + cl_assert_equal_i((intptr_t)git_atomic_load(value), 1); +} |