summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/cli/CMakeLists.txt3
-rw-r--r--src/cli/cli.h20
-rw-r--r--src/cli/cmd.c2
-rw-r--r--src/cli/cmd.h2
-rw-r--r--src/cli/cmd_cat_file.c9
-rw-r--r--src/cli/cmd_clone.c6
-rw-r--r--src/cli/cmd_config.c242
-rw-r--r--src/cli/cmd_hash_object.c9
-rw-r--r--src/cli/cmd_help.c6
-rw-r--r--src/cli/cmd_index_pack.c114
-rw-r--r--src/cli/common.c126
-rw-r--r--src/cli/common.h66
-rw-r--r--src/cli/error.h2
-rw-r--r--src/cli/main.c58
-rw-r--r--src/cli/opt.c32
-rw-r--r--src/cli/opt.h24
-rw-r--r--src/cli/opt_usage.c2
-rw-r--r--src/cli/progress.c53
-rw-r--r--src/cli/progress.h12
-rw-r--r--src/cli/unix/sighandler.c3
-rw-r--r--src/cli/win32/precompiled.h2
-rw-r--r--src/cli/win32/sighandler.c2
-rw-r--r--src/libgit2/CMakeLists.txt6
-rw-r--r--src/libgit2/attr.c6
-rw-r--r--src/libgit2/blame.c27
-rw-r--r--src/libgit2/clone.c67
-rw-r--r--src/libgit2/commit.c99
-rw-r--r--src/libgit2/commit.h2
-rw-r--r--src/libgit2/config.c633
-rw-r--r--src/libgit2/config.h13
-rw-r--r--src/libgit2/config_backend.h9
-rw-r--r--src/libgit2/config_entries.c237
-rw-r--r--src/libgit2/config_entries.h24
-rw-r--r--src/libgit2/config_file.c182
-rw-r--r--src/libgit2/config_list.c288
-rw-r--r--src/libgit2/config_list.h32
-rw-r--r--src/libgit2/config_mem.c232
-rw-r--r--src/libgit2/config_parse.c19
-rw-r--r--src/libgit2/config_snapshot.c50
-rw-r--r--src/libgit2/diff_print.c51
-rw-r--r--src/libgit2/diff_tform.c38
-rw-r--r--src/libgit2/errors.c293
-rw-r--r--src/libgit2/fetch.c8
-rw-r--r--src/libgit2/filter.c6
-rw-r--r--src/libgit2/git2.rc2
-rw-r--r--src/libgit2/index.c18
-rw-r--r--src/libgit2/iterator.c14
-rw-r--r--src/libgit2/libgit2.c404
-rw-r--r--src/libgit2/libgit2.h15
-rw-r--r--src/libgit2/merge.c7
-rw-r--r--src/libgit2/midx.c4
-rw-r--r--src/libgit2/notes.c4
-rw-r--r--src/libgit2/oid.c37
-rw-r--r--src/libgit2/oid.h2
-rw-r--r--src/libgit2/pack.c7
-rw-r--r--src/libgit2/patch_parse.c4
-rw-r--r--src/libgit2/path.c2
-rw-r--r--src/libgit2/push.c45
-rw-r--r--src/libgit2/push.h1
-rw-r--r--src/libgit2/rebase.c9
-rw-r--r--src/libgit2/refdb_fs.c186
-rw-r--r--src/libgit2/refs.c6
-rw-r--r--src/libgit2/refs.h6
-rw-r--r--src/libgit2/remote.c74
-rw-r--r--src/libgit2/repository.c224
-rw-r--r--src/libgit2/repository.h4
-rw-r--r--src/libgit2/revparse.c4
-rw-r--r--src/libgit2/settings.c456
-rw-r--r--src/libgit2/settings.h8
-rw-r--r--src/libgit2/signature.c1
-rw-r--r--src/libgit2/stash.c4
-rw-r--r--src/libgit2/streams/mbedtls.c110
-rw-r--r--src/libgit2/streams/openssl.c4
-rw-r--r--src/libgit2/submodule.c4
-rw-r--r--src/libgit2/threadstate.c97
-rw-r--r--src/libgit2/threadstate.h22
-rw-r--r--src/libgit2/trailer.c12
-rw-r--r--src/libgit2/transaction.c17
-rw-r--r--src/libgit2/transaction.h5
-rw-r--r--src/libgit2/transport.c3
-rw-r--r--src/libgit2/transports/credential.c2
-rw-r--r--src/libgit2/transports/http.c3
-rw-r--r--src/libgit2/transports/http.h10
-rw-r--r--src/libgit2/transports/httpclient.c178
-rw-r--r--src/libgit2/transports/httpparser.c126
-rw-r--r--src/libgit2/transports/httpparser.h99
-rw-r--r--src/libgit2/transports/local.c7
-rw-r--r--src/libgit2/transports/smart.c16
-rw-r--r--src/libgit2/transports/smart.h6
-rw-r--r--src/libgit2/transports/smart_pkt.c4
-rw-r--r--src/libgit2/transports/smart_protocol.c54
-rw-r--r--src/libgit2/transports/ssh.c1144
-rw-r--r--src/libgit2/transports/ssh.h14
-rw-r--r--src/libgit2/transports/ssh_exec.c346
-rw-r--r--src/libgit2/transports/ssh_exec.h26
-rw-r--r--src/libgit2/transports/ssh_libssh2.c1124
-rw-r--r--src/libgit2/transports/ssh_libssh2.h28
-rw-r--r--src/libgit2/transports/winhttp.c42
-rw-r--r--src/libgit2/tree.c2
-rw-r--r--src/libgit2/worktree.c31
-rw-r--r--src/util/array.h35
-rw-r--r--src/util/ctype_compat.h70
-rw-r--r--src/util/date.c26
-rw-r--r--src/util/errors.c401
-rw-r--r--src/util/errors.h (renamed from src/libgit2/errors.h)38
-rw-r--r--src/util/fs_path.c19
-rw-r--r--src/util/fs_path.h23
-rw-r--r--src/util/futils.h2
-rw-r--r--src/util/git2_features.h.in8
-rw-r--r--src/util/git2_util.h2
-rw-r--r--src/util/integer.h4
-rw-r--r--src/util/net.c5
-rw-r--r--src/util/process.h222
-rw-r--r--src/util/rand.c8
-rw-r--r--src/util/regexp.c2
-rw-r--r--src/util/str.c4
-rw-r--r--src/util/strlist.c108
-rw-r--r--src/util/strlist.h36
-rw-r--r--src/util/unix/posix.h2
-rw-r--r--src/util/unix/process.c629
-rw-r--r--src/util/util.c4
-rw-r--r--src/util/util.h34
-rw-r--r--src/util/win32/posix_w32.c34
-rw-r--r--src/util/win32/process.c506
125 files changed, 7162 insertions, 3266 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8525acd..ed3f4a5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -135,7 +135,8 @@ endif()
# platform libraries
if(WIN32)
- list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32)
+ list(APPEND LIBGIT2_SYSTEM_LIBS "ws2_32" "secur32")
+ list(APPEND LIBGIT2_PC_LIBS "-lws2_32" "-lsecur32")
endif()
if(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
@@ -183,7 +184,7 @@ add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix")
# iconv
if(USE_ICONV)
- find_package(Iconv)
+ find_package(IntlIconv)
endif()
if(ICONV_FOUND)
set(GIT_USE_ICONV 1)
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index 84b6c19..97797e3 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -4,7 +4,8 @@ set(CLI_INCLUDES
"${libgit2_SOURCE_DIR}/src/util"
"${libgit2_SOURCE_DIR}/src/cli"
"${libgit2_SOURCE_DIR}/include"
- "${LIBGIT2_DEPENDENCY_INCLUDES}")
+ "${LIBGIT2_DEPENDENCY_INCLUDES}"
+ "${LIBGIT2_SYSTEM_INCLUDES}")
if(WIN32 AND NOT CYGWIN)
file(GLOB CLI_SRC_OS win32/*.c)
diff --git a/src/cli/cli.h b/src/cli/cli.h
deleted file mode 100644
index 7dede67..0000000
--- a/src/cli/cli.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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 CLI_cli_h__
-#define CLI_cli_h__
-
-#define PROGRAM_NAME "git2"
-
-#include "git2_util.h"
-
-#include "error.h"
-#include "opt.h"
-#include "opt_usage.h"
-#include "sighandler.h"
-
-#endif /* CLI_cli_h__ */
diff --git a/src/cli/cmd.c b/src/cli/cmd.c
index 2a7e71c..0b1fafb 100644
--- a/src/cli/cmd.c
+++ b/src/cli/cmd.c
@@ -5,7 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
const cli_cmd_spec *cli_cmd_spec_byname(const char *name)
diff --git a/src/cli/cmd.h b/src/cli/cmd.h
index 8b1a1b3..bd88122 100644
--- a/src/cli/cmd.h
+++ b/src/cli/cmd.h
@@ -27,7 +27,9 @@ extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name);
/* Commands */
extern int cmd_cat_file(int argc, char **argv);
extern int cmd_clone(int argc, char **argv);
+extern int cmd_config(int argc, char **argv);
extern int cmd_hash_object(int argc, char **argv);
extern int cmd_help(int argc, char **argv);
+extern int cmd_index_pack(int argc, char **argv);
#endif /* CLI_cmd_h__ */
diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c
index fb53a72..90ee603 100644
--- a/src/cli/cmd_cat_file.c
+++ b/src/cli/cmd_cat_file.c
@@ -6,7 +6,7 @@
*/
#include <git2.h>
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
#define COMMAND_NAME "cat-file"
@@ -24,9 +24,7 @@ static int display = DISPLAY_CONTENT;
static char *type_name, *object_spec;
static const cli_opt_spec opts[] = {
- { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
- CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
- "display help about the " COMMAND_NAME " command" },
+ CLI_COMMON_OPT,
{ CLI_OPT_TYPE_SWITCH, NULL, 't', &display, DISPLAY_TYPE,
CLI_OPT_USAGE_REQUIRED, NULL, "display the type of the object" },
@@ -139,6 +137,7 @@ static int print_pretty(git_object *object)
int cmd_cat_file(int argc, char **argv)
{
+ cli_repository_open_options open_opts = { argv + 1, argc - 1};
git_repository *repo = NULL;
git_object *object = NULL;
git_object_t type;
@@ -153,7 +152,7 @@ int cmd_cat_file(int argc, char **argv)
return 0;
}
- if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0)
+ if (cli_repository_open(&repo, &open_opts) < 0)
return cli_error_git();
if ((giterr = git_revparse_single(&object, repo, object_spec)) < 0) {
diff --git a/src/cli/cmd_clone.c b/src/cli/cmd_clone.c
index e477625..7d9736f 100644
--- a/src/cli/cmd_clone.c
+++ b/src/cli/cmd_clone.c
@@ -7,7 +7,7 @@
#include <stdio.h>
#include <git2.h>
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
#include "error.h"
#include "sighandler.h"
@@ -24,9 +24,7 @@ static bool local_path_exists;
static cli_progress progress = CLI_PROGRESS_INIT;
static const cli_opt_spec opts[] = {
- { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
- CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
- "display help about the " COMMAND_NAME " command" },
+ CLI_COMMON_OPT,
{ CLI_OPT_TYPE_SWITCH, "quiet", 'q', &quiet, 1,
CLI_OPT_USAGE_DEFAULT, NULL, "display the type of the object" },
diff --git a/src/cli/cmd_config.c b/src/cli/cmd_config.c
new file mode 100644
index 0000000..6b9d373
--- /dev/null
+++ b/src/cli/cmd_config.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <git2.h>
+
+#include "common.h"
+#include "cmd.h"
+
+#define COMMAND_NAME "config"
+
+typedef enum {
+ ACTION_NONE = 0,
+ ACTION_GET,
+ ACTION_ADD,
+ ACTION_REPLACE_ALL,
+ ACTION_LIST
+} action_t;
+
+static action_t action = ACTION_NONE;
+static int show_origin;
+static int show_scope;
+static int show_help;
+static int null_separator;
+static int config_level;
+static char *config_filename;
+static char *name, *value, *value_pattern;
+
+static const cli_opt_spec opts[] = {
+ CLI_COMMON_OPT, \
+
+ { CLI_OPT_TYPE_SWITCH, "null", 'z', &null_separator, 1,
+ 0, NULL, "use NUL as a separator" },
+
+ { CLI_OPT_TYPE_SWITCH, "system", 0, &config_level, GIT_CONFIG_LEVEL_SYSTEM,
+ 0, NULL, "read/write to system configuration" },
+ { CLI_OPT_TYPE_SWITCH, "global", 0, &config_level, GIT_CONFIG_LEVEL_GLOBAL,
+ CLI_OPT_USAGE_CHOICE, NULL, "read/write to global configuration" },
+ { CLI_OPT_TYPE_SWITCH, "local", 0, &config_level, GIT_CONFIG_LEVEL_LOCAL,
+ CLI_OPT_USAGE_CHOICE, NULL, "read/write to local configuration" },
+ { CLI_OPT_TYPE_VALUE, "file", 0, &config_filename, 0,
+ CLI_OPT_USAGE_CHOICE, "filename", "read/write to specified configuration file" },
+
+ { CLI_OPT_TYPE_SWITCH, "get", 0, &action, ACTION_GET,
+ CLI_OPT_USAGE_REQUIRED, NULL, "get a configuration value" },
+ { CLI_OPT_TYPE_SWITCH, "add", 0, &action, ACTION_ADD,
+ CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value" },
+ { CLI_OPT_TYPE_SWITCH, "replace-all", 0, &action, ACTION_REPLACE_ALL,
+ CLI_OPT_USAGE_CHOICE, NULL, "add a configuration value, replacing any old values" },
+ { CLI_OPT_TYPE_SWITCH, "list", 'l', &action, ACTION_LIST,
+ CLI_OPT_USAGE_CHOICE | CLI_OPT_USAGE_SHOW_LONG,
+ NULL, "list all configuration entries" },
+ { CLI_OPT_TYPE_SWITCH, "show-origin", 0, &show_origin, 1,
+ 0, NULL, "show origin of configuration" },
+ { CLI_OPT_TYPE_SWITCH, "show-scope", 0, &show_scope, 1,
+ 0, NULL, "show scope of configuration" },
+ { CLI_OPT_TYPE_ARG, "name", 0, &name, 0,
+ 0, "name", "name of configuration entry" },
+ { CLI_OPT_TYPE_ARG, "value", 0, &value, 0,
+ 0, "value", "value of configuration entry" },
+ { CLI_OPT_TYPE_ARG, "regexp", 0, &value_pattern, 0,
+ 0, "regexp", "regular expression of values to replace" },
+ { 0 },
+};
+
+static void print_help(void)
+{
+ cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
+ printf("\n");
+
+ printf("Query and set configuration options.\n");
+ printf("\n");
+
+ printf("Options:\n");
+
+ cli_opt_help_fprint(stdout, opts);
+}
+
+static int get_config(git_config *config)
+{
+ git_buf value = GIT_BUF_INIT;
+ char sep = null_separator ? '\0' : '\n';
+ int error;
+
+ error = git_config_get_string_buf(&value, config, name);
+
+ if (error && error != GIT_ENOTFOUND)
+ return cli_error_git();
+
+ else if (error == GIT_ENOTFOUND)
+ return 1;
+
+ printf("%s%c", value.ptr, sep);
+ return 0;
+}
+
+static int add_config(git_config *config)
+{
+ if (git_config_set_multivar(config, name, "$^", value) < 0)
+ return cli_error_git();
+
+ return 0;
+}
+
+static int replace_all_config(git_config *config)
+{
+ if (git_config_set_multivar(config, name, value_pattern ? value_pattern : ".*", value) < 0)
+ return cli_error_git();
+
+ return 0;
+}
+
+static const char *level_name(git_config_level_t level)
+{
+ switch (level) {
+ case GIT_CONFIG_LEVEL_PROGRAMDATA:
+ return "programdata";
+ case GIT_CONFIG_LEVEL_SYSTEM:
+ return "system";
+ case GIT_CONFIG_LEVEL_XDG:
+ return "global";
+ case GIT_CONFIG_LEVEL_GLOBAL:
+ return "global";
+ case GIT_CONFIG_LEVEL_LOCAL:
+ return "local";
+ case GIT_CONFIG_LEVEL_APP:
+ return "command";
+ default:
+ return "unknown";
+ }
+}
+
+static int list_config(git_config *config)
+{
+ git_config_iterator *iterator;
+ git_config_entry *entry;
+ char data_separator = null_separator ? '\0' : '\t';
+ char kv_separator = null_separator ? '\n' : '=';
+ char entry_separator = null_separator ? '\0' : '\n';
+ int error;
+
+ if (git_config_iterator_new(&iterator, config) < 0)
+ return cli_error_git();
+
+ while ((error = git_config_next(&entry, iterator)) == 0) {
+ if (show_scope)
+ printf("%s%c",
+ level_name(entry->level),
+ data_separator);
+
+ if (show_origin)
+ printf("%s%s%s%c",
+ entry->backend_type ? entry->backend_type : "",
+ entry->backend_type && entry->origin_path ? ":" : "",
+ entry->origin_path ? entry->origin_path : "",
+ data_separator);
+
+ printf("%s%c%s%c", entry->name, kv_separator, entry->value,
+ entry_separator);
+ }
+
+ if (error != GIT_ITEROVER)
+ return cli_error_git();
+
+ git_config_iterator_free(iterator);
+ return 0;
+}
+
+int cmd_config(int argc, char **argv)
+{
+ git_repository *repo = NULL;
+ git_config *config = NULL;
+ cli_repository_open_options open_opts = { argv + 1, argc - 1};
+ cli_opt invalid_opt;
+ int ret = 0;
+
+ if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
+ return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);
+
+ if (show_help) {
+ print_help();
+ return 0;
+ }
+
+ if (config_filename) {
+ if (git_config_new(&config) < 0 ||
+ git_config_add_file_ondisk(config, config_filename,
+ GIT_CONFIG_LEVEL_APP, NULL, 0) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+ } else {
+ if (cli_repository_open(&repo, &open_opts) < 0 ||
+ git_repository_config(&config, repo) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+
+ if (config_level &&
+ git_config_open_level(&config, config, config_level) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+ }
+
+ switch (action) {
+ case ACTION_ADD:
+ if (!name || !value || value_pattern)
+ ret = cli_error_usage("%s --add requires two arguments", COMMAND_NAME);
+ else
+ ret = add_config(config);
+ break;
+ case ACTION_REPLACE_ALL:
+ if (!name || !value)
+ ret = cli_error_usage("%s --replace-all requires two or three arguments", COMMAND_NAME);
+ else
+ ret = replace_all_config(config);
+ break;
+ case ACTION_GET:
+ if (!name)
+ ret = cli_error_usage("%s --get requires an argument", COMMAND_NAME);
+ else
+ ret = get_config(config);
+ break;
+ case ACTION_LIST:
+ if (name)
+ ret = cli_error_usage("%s --list does not take an argument", COMMAND_NAME);
+ else
+ ret = list_config(config);
+ break;
+ default:
+ ret = cli_error_usage("unknown action");
+ }
+
+done:
+ git_config_free(config);
+ git_repository_free(repo);
+ return ret;
+}
diff --git a/src/cli/cmd_hash_object.c b/src/cli/cmd_hash_object.c
index 93b980d..741debb 100644
--- a/src/cli/cmd_hash_object.c
+++ b/src/cli/cmd_hash_object.c
@@ -6,7 +6,7 @@
*/
#include <git2.h>
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
#include "futils.h"
@@ -19,9 +19,7 @@ static int write_object, read_stdin, literally;
static char **filenames;
static const cli_opt_spec opts[] = {
- { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
- CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
- "display help about the " COMMAND_NAME " command" },
+ CLI_COMMON_OPT,
{ CLI_OPT_TYPE_VALUE, NULL, 't', &type_name, 0,
CLI_OPT_USAGE_DEFAULT, "type", "the type of object to hash (default: \"blob\")" },
@@ -92,6 +90,7 @@ static int hash_buf(
int cmd_hash_object(int argc, char **argv)
{
+ cli_repository_open_options open_opts = { argv + 1, argc - 1};
git_repository *repo = NULL;
git_odb *odb = NULL;
git_oid_t oid_type;
@@ -113,7 +112,7 @@ int cmd_hash_object(int argc, char **argv)
return cli_error_usage("invalid object type '%s'", type_name);
if (write_object &&
- (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0 ||
+ (cli_repository_open(&repo, &open_opts) < 0 ||
git_repository_odb(&odb, repo) < 0)) {
ret = cli_error_git();
goto done;
diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c
index 7ee9822..5e877e0 100644
--- a/src/cli/cmd_help.c
+++ b/src/cli/cmd_help.c
@@ -7,7 +7,7 @@
#include <stdio.h>
#include <git2.h>
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
#define COMMAND_NAME "help"
@@ -16,8 +16,8 @@ static char *command;
static int show_help;
static const cli_opt_spec opts[] = {
- { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
- CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" },
+ CLI_COMMON_OPT,
+
{ CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" },
{ 0 },
diff --git a/src/cli/cmd_index_pack.c b/src/cli/cmd_index_pack.c
new file mode 100644
index 0000000..09685c5
--- /dev/null
+++ b/src/cli/cmd_index_pack.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <git2.h>
+#include "common.h"
+#include "cmd.h"
+#include "progress.h"
+
+#define COMMAND_NAME "index-pack"
+
+#define BUFFER_SIZE (1024 * 1024)
+
+static int show_help, verbose, read_stdin;
+static char *filename;
+static cli_progress progress = CLI_PROGRESS_INIT;
+
+static const cli_opt_spec opts[] = {
+ { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
+ CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL,
+ "display help about the " COMMAND_NAME " command" },
+
+ { CLI_OPT_TYPE_SWITCH, "verbose", 'v', &verbose, 1,
+ CLI_OPT_USAGE_DEFAULT, NULL, "display progress output" },
+
+ { CLI_OPT_TYPE_LITERAL },
+
+ { CLI_OPT_TYPE_SWITCH, "stdin", 0, &read_stdin, 1,
+ CLI_OPT_USAGE_REQUIRED, NULL, "read from stdin" },
+ { CLI_OPT_TYPE_ARG, "pack-file", 0, &filename, 0,
+ CLI_OPT_USAGE_CHOICE, "pack-file", "packfile path" },
+
+ { 0 },
+};
+
+static void print_help(void)
+{
+ cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts);
+ printf("\n");
+
+ printf("Indexes a packfile and writes the index to disk.\n");
+ printf("\n");
+
+ printf("Options:\n");
+
+ cli_opt_help_fprint(stdout, opts);
+}
+
+int cmd_index_pack(int argc, char **argv)
+{
+ cli_opt invalid_opt;
+ git_indexer *idx = NULL;
+ git_indexer_options idx_opts = GIT_INDEXER_OPTIONS_INIT;
+ git_indexer_progress stats = {0};
+ char buf[BUFFER_SIZE];
+ ssize_t read_len;
+ int fd, ret;
+
+ if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU))
+ return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt);
+
+ if (show_help) {
+ print_help();
+ return 0;
+ }
+
+ if (verbose) {
+ idx_opts.progress_cb = cli_progress_indexer;
+ idx_opts.progress_cb_payload = &progress;
+ }
+
+ if (read_stdin) {
+ fd = fileno(stdin);
+ } else if ((fd = p_open(filename, O_RDONLY)) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+
+#ifdef GIT_EXPERIMENTAL_SHA256
+ ret = git_indexer_new(&idx, ".", GIT_OID_SHA1, &idx_opts);
+#else
+ ret = git_indexer_new(&idx, ".", 0, NULL, &idx_opts);
+#endif
+
+ if (ret < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+
+ while ((read_len = p_read(fd, buf, sizeof(buf))) > 0) {
+ if (git_indexer_append(idx, buf, (size_t)read_len, &stats) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+ }
+
+ if (git_indexer_commit(idx, &stats) < 0) {
+ ret = cli_error_git();
+ goto done;
+ }
+
+ cli_progress_finish(&progress);
+
+done:
+ if (!read_stdin && fd >= 0)
+ p_close(fd);
+
+ cli_progress_dispose(&progress);
+ git_indexer_free(idx);
+ return ret;
+}
diff --git a/src/cli/common.c b/src/cli/common.c
new file mode 100644
index 0000000..60b0358
--- /dev/null
+++ b/src/cli/common.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <git2.h>
+#include <git2/sys/config.h>
+
+#include "git2_util.h"
+#include "vector.h"
+
+#include "common.h"
+#include "error.h"
+
+static int parse_option(cli_opt *opt, void *data)
+{
+ git_str kv = GIT_STR_INIT, env = GIT_STR_INIT;
+ git_vector *cmdline_config = data;
+ int error = 0;
+
+ if (opt->spec && opt->spec->alias == 'c') {
+ if (git_str_puts(&kv, opt->value) < 0) {
+ error = cli_error_git();
+ goto done;
+ }
+ }
+
+ else if (opt->spec && !strcmp(opt->spec->name, "config-env")) {
+ char *val = strchr(opt->value, '=');
+
+ if (val == NULL || *(val + 1) == '\0') {
+ error = cli_error("invalid config format: '%s'", opt->value);
+ goto done;
+ }
+
+ if (git_str_put(&kv, opt->value, (val - opt->value)) < 0) {
+ error = cli_error_git();
+ goto done;
+ }
+
+ val++;
+
+ if ((error = git__getenv(&env, val)) == GIT_ENOTFOUND) {
+ error = cli_error("missing environment variable '%s' for configuration '%s'", val, kv.ptr);
+ goto done;
+ } else if (error) {
+ error = cli_error_git();
+ goto done;
+ }
+
+ if (git_str_putc(&kv, '=') < 0 ||
+ git_str_puts(&kv, env.ptr) < 0) {
+ error = cli_error_git();
+ goto done;
+ }
+ }
+
+ if (kv.size > 0 &&
+ git_vector_insert(cmdline_config, git_str_detach(&kv)) < 0)
+ error = cli_error_git();
+
+done:
+ git_str_dispose(&env);
+ git_str_dispose(&kv);
+ return error;
+}
+
+static int parse_common_options(
+ git_repository *repo,
+ cli_repository_open_options *opts)
+{
+ cli_opt_spec common_opts[] = {
+ { CLI_COMMON_OPT_CONFIG },
+ { CLI_COMMON_OPT_CONFIG_ENV },
+ { 0 }
+ };
+ git_config_backend_memory_options config_opts =
+ GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT;
+ git_vector cmdline = GIT_VECTOR_INIT;
+ git_config *config = NULL;
+ git_config_backend *backend = NULL;
+ int error = 0;
+
+ config_opts.backend_type = "command line";
+
+ if ((error = cli_opt_foreach(common_opts, opts->args,
+ opts->args_len, CLI_OPT_PARSE_GNU, parse_option,
+ &cmdline)) < 0)
+ goto done;
+
+ if (git_vector_length(&cmdline) == 0)
+ goto done;
+
+ if (git_repository_config(&config, repo) < 0 ||
+ git_config_backend_from_values(&backend,
+ (const char **)cmdline.contents, cmdline.length,
+ &config_opts) < 0 ||
+ git_config_add_backend(config, backend, GIT_CONFIG_LEVEL_APP,
+ repo, 0) < 0)
+ error = cli_error_git();
+
+done:
+ if (error && backend)
+ backend->free(backend);
+ git_config_free(config);
+ git_vector_free_deep(&cmdline);
+ return error;
+}
+
+int cli_repository_open(
+ git_repository **out,
+ cli_repository_open_options *opts)
+{
+ git_repository *repo;
+
+ if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0)
+ return -1;
+
+ if (opts && parse_common_options(repo, opts) < 0)
+ return -1;
+
+ *out = repo;
+ return 0;
+}
diff --git a/src/cli/common.h b/src/cli/common.h
new file mode 100644
index 0000000..3aed8ad
--- /dev/null
+++ b/src/cli/common.h
@@ -0,0 +1,66 @@
+/*
+ * 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 CLI_common_h__
+#define CLI_common_h__
+
+#define PROGRAM_NAME "git2"
+
+#include "git2_util.h"
+
+#include "error.h"
+#include "opt.h"
+#include "opt_usage.h"
+
+/*
+ * Common command arguments.
+ */
+
+#define CLI_COMMON_OPT_HELP \
+ CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, \
+ CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING
+#define CLI_COMMON_OPT_CONFIG \
+ CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, \
+ CLI_OPT_USAGE_HIDDEN
+#define CLI_COMMON_OPT_CONFIG_ENV \
+ CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, \
+ CLI_OPT_USAGE_HIDDEN
+
+#define CLI_COMMON_OPT \
+ { CLI_COMMON_OPT_HELP }, \
+ { CLI_COMMON_OPT_CONFIG }, \
+ { CLI_COMMON_OPT_CONFIG_ENV }
+
+typedef struct {
+ char **args;
+ int args_len;
+} cli_repository_open_options;
+
+extern int cli_repository_open(
+ git_repository **out,
+ cli_repository_open_options *opts);
+
+/*
+ * Common command arguments.
+ */
+
+#define CLI_COMMON_OPT_HELP \
+ CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, \
+ CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING
+#define CLI_COMMON_OPT_CONFIG \
+ CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0, \
+ CLI_OPT_USAGE_HIDDEN
+#define CLI_COMMON_OPT_CONFIG_ENV \
+ CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0, \
+ CLI_OPT_USAGE_HIDDEN
+
+#define CLI_COMMON_OPT \
+ { CLI_COMMON_OPT_HELP }, \
+ { CLI_COMMON_OPT_CONFIG }, \
+ { CLI_COMMON_OPT_CONFIG_ENV }
+
+#endif /* CLI_common_h__ */
diff --git a/src/cli/error.h b/src/cli/error.h
index cce7a54..abf8a51 100644
--- a/src/cli/error.h
+++ b/src/cli/error.h
@@ -8,7 +8,7 @@
#ifndef CLI_error_h__
#define CLI_error_h__
-#include "cli.h"
+#include "common.h"
#include <stdio.h>
#define CLI_EXIT_OK 0
diff --git a/src/cli/main.c b/src/cli/main.c
index cbfc50e..c7a6fcf 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -7,7 +7,7 @@
#include <stdio.h>
#include <git2.h>
-#include "cli.h"
+#include "common.h"
#include "cmd.h"
static int show_help = 0;
@@ -16,8 +16,12 @@ static char *command = NULL;
static char **args = NULL;
const cli_opt_spec cli_common_opts[] = {
- { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
- CLI_OPT_USAGE_DEFAULT, NULL, "display help information" },
+ { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1,
+ CLI_OPT_USAGE_DEFAULT, NULL, "display help information" },
+ { CLI_OPT_TYPE_VALUE, NULL, 'c', NULL, 0,
+ CLI_OPT_USAGE_DEFAULT, "key=value", "add configuration value" },
+ { CLI_OPT_TYPE_VALUE, "config-env", 0, NULL, 0,
+ CLI_OPT_USAGE_DEFAULT, "key=value", "set configuration value to environment variable" },
{ CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1,
CLI_OPT_USAGE_DEFAULT, NULL, "display the version" },
{ CLI_OPT_TYPE_ARG, "command", 0, &command, 0,
@@ -30,19 +34,40 @@ const cli_opt_spec cli_common_opts[] = {
const cli_cmd_spec cli_cmds[] = {
{ "cat-file", cmd_cat_file, "Display an object in the repository" },
{ "clone", cmd_clone, "Clone a repository into a new directory" },
+ { "config", cmd_config, "View or set configuration values " },
{ "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" },
{ "help", cmd_help, "Display help information" },
+ { "index-pack", cmd_index_pack, "Create an index for a packfile" },
{ NULL }
};
+/*
+ * Reorder the argv as it was given, since git has the notion of global
+ * options (like `--help` or `-c key=val`) that we want to pass to the
+ * subcommand, and that can appear early in the arguments, before the
+ * command name. Put the command-name in argv[1] to allow easier parsing.
+ */
+static void reorder_args(char **argv, size_t first)
+{
+ char *tmp;
+ size_t i;
+
+ if (first == 1)
+ return;
+
+ tmp = argv[first];
+
+ for (i = first; i > 1; i--)
+ argv[i] = argv[i - 1];
+
+ argv[1] = tmp;
+}
+
int main(int argc, char **argv)
{
const cli_cmd_spec *cmd;
cli_opt_parser optparser;
cli_opt opt;
- char *help_args[3] = { NULL };
- int help_args_len;
- int args_len = 0;
int ret = 0;
if (git_libgit2_init() < 0) {
@@ -66,8 +91,7 @@ int main(int argc, char **argv)
* remaining arguments as args for the command itself.
*/
if (command) {
- args = &argv[optparser.idx];
- args_len = (int)(argc - optparser.idx);
+ reorder_args(argv, optparser.idx);
break;
}
}
@@ -77,19 +101,9 @@ int main(int argc, char **argv)
goto done;
}
- /*
- * If `--help <command>` is specified, delegate to that command's
- * `--help` option. If no command is specified, run the `help`
- * command. Do this by updating the args to emulate that behavior.
- */
- if (!command || show_help) {
- help_args[0] = command ? (char *)command : "help";
- help_args[1] = command ? "--help" : NULL;
- help_args_len = command ? 2 : 1;
-
- command = help_args[0];
- args = help_args;
- args_len = help_args_len;
+ if (!command) {
+ ret = cmd_help(argc, argv);
+ goto done;
}
if ((cmd = cli_cmd_spec_byname(command)) == NULL) {
@@ -98,7 +112,7 @@ int main(int argc, char **argv)
goto done;
}
- ret = cmd->fn(args_len, args);
+ ret = cmd->fn(argc - 1, &argv[1]);
done:
git_libgit2_shutdown();
diff --git a/src/cli/opt.c b/src/cli/opt.c
index 62a3430..9242e22 100644
--- a/src/cli/opt.c
+++ b/src/cli/opt.c
@@ -10,7 +10,7 @@
* This file was produced by using the `rename.pl` script included with
* adopt. The command-line specified was:
*
- * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
+ * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
*/
#include <stdlib.h>
@@ -19,7 +19,11 @@
#include <limits.h>
#include <assert.h>
-#include "cli.h"
+#if defined(__sun) || defined(__illumos__)
+# include <alloca.h>
+#endif
+
+#include "common.h"
#include "opt.h"
#ifdef _WIN32
@@ -73,7 +77,7 @@ GIT_INLINE(const cli_opt_spec *) spec_for_long(
/* Handle --option=value arguments */
if (spec->type == CLI_OPT_TYPE_VALUE &&
- eql &&
+ spec->name && eql &&
strncmp(arg, spec->name, eql_pos) == 0 &&
spec->name[eql_pos] == '\0') {
*has_value = 1;
@@ -575,6 +579,28 @@ cli_opt_status_t cli_opt_parse(
return validate_required(opt, specs, given_specs);
}
+int cli_opt_foreach(
+ const cli_opt_spec specs[],
+ char **args,
+ size_t args_len,
+ unsigned int flags,
+ int (*callback)(cli_opt *, void *),
+ void *callback_data)
+{
+ cli_opt_parser parser;
+ cli_opt opt;
+ int ret;
+
+ cli_opt_parser_init(&parser, specs, args, args_len, flags);
+
+ while (cli_opt_parser_next(&opt, &parser)) {
+ if ((ret = callback(&opt, callback_data)) != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int spec_name_fprint(FILE *file, const cli_opt_spec *spec)
{
int error;
diff --git a/src/cli/opt.h b/src/cli/opt.h
index 6c1d460..226f74d 100644
--- a/src/cli/opt.h
+++ b/src/cli/opt.h
@@ -10,7 +10,7 @@
* This file was produced by using the `rename.pl` script included with
* adopt. The command-line specified was:
*
- * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
+ * ./rename.pl cli_opt --filename=opt --include=common.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
*/
#ifndef CLI_opt_h__
@@ -275,8 +275,8 @@ typedef struct cli_opt_parser {
size_t arg_idx;
size_t in_args;
size_t in_short;
- int needs_sort : 1,
- in_literal : 1;
+ unsigned int needs_sort : 1,
+ in_literal : 1;
} cli_opt_parser;
/**
@@ -301,6 +301,24 @@ cli_opt_status_t cli_opt_parse(
unsigned int flags);
/**
+ * Quickly executes the given callback for each argument.
+ *
+ * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed
+ * @param args The arguments that will be parsed
+ * @param args_len The length of arguments to be parsed
+ * @param flags The `cli_opt_flag_t flags for parsing
+ * @param callback The callback to invoke for each specified option
+ * @param callback_data Data to be provided to the callback
+ */
+int cli_opt_foreach(
+ const cli_opt_spec specs[],
+ char **args,
+ size_t args_len,
+ unsigned int flags,
+ int (*callback)(cli_opt *, void *),
+ void *callback_data);
+
+/**
* Initializes a parser that parses the given arguments according to the
* given specifications.
*
diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c
index 478b416..8374f51 100644
--- a/src/cli/opt_usage.c
+++ b/src/cli/opt_usage.c
@@ -5,7 +5,7 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "cli.h"
+#include "common.h"
#include "str.h"
static int print_spec_name(git_str *out, const cli_opt_spec *spec)
diff --git a/src/cli/progress.c b/src/cli/progress.c
index ddfbafb..d975b09 100644
--- a/src/cli/progress.c
+++ b/src/cli/progress.c
@@ -242,7 +242,21 @@ static int fetch_receiving(
done ? ", done." : "");
}
-static int fetch_resolving(
+static int indexer_indexing(
+ cli_progress *progress,
+ const git_indexer_progress *stats)
+{
+ bool done = (stats->received_objects == stats->total_objects);
+
+ return progress_printf(progress, false,
+ "Indexing objects: %3d%% (%d/%d)%s\r",
+ percent(stats->received_objects, stats->total_objects),
+ stats->received_objects,
+ stats->total_objects,
+ done ? ", done." : "");
+}
+
+static int indexer_resolving(
cli_progress *progress,
const git_indexer_progress *stats)
{
@@ -283,7 +297,42 @@ int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload
/* fall through */
case CLI_PROGRESS_RESOLVING:
- error = fetch_resolving(progress, stats);
+ error = indexer_resolving(progress, stats);
+ break;
+
+ default:
+ /* should not be reached */
+ GIT_ASSERT(!"unexpected progress state");
+ }
+
+ return error;
+}
+
+int cli_progress_indexer(
+ const git_indexer_progress *stats,
+ void *payload)
+{
+ cli_progress *progress = (cli_progress *)payload;
+ int error = 0;
+
+ switch (progress->action) {
+ case CLI_PROGRESS_NONE:
+ progress->action = CLI_PROGRESS_INDEXING;
+ /* fall through */
+
+ case CLI_PROGRESS_INDEXING:
+ if ((error = indexer_indexing(progress, stats)) < 0)
+ break;
+
+ if (stats->indexed_deltas == stats->total_deltas)
+ break;
+
+ progress_complete(progress);
+ progress->action = CLI_PROGRESS_RESOLVING;
+ /* fall through */
+
+ case CLI_PROGRESS_RESOLVING:
+ error = indexer_resolving(progress, stats);
break;
default:
diff --git a/src/cli/progress.h b/src/cli/progress.h
index 886fef8..f08d68f 100644
--- a/src/cli/progress.h
+++ b/src/cli/progress.h
@@ -22,6 +22,7 @@
typedef enum {
CLI_PROGRESS_NONE,
CLI_PROGRESS_RECEIVING,
+ CLI_PROGRESS_INDEXING,
CLI_PROGRESS_RESOLVING,
CLI_PROGRESS_CHECKING_OUT
} cli_progress_t;
@@ -75,6 +76,17 @@ extern int cli_progress_fetch_transfer(
void *payload);
/**
+ * Prints indexer progress to the console. Suitable for a
+ * `progress_cb` callback for `git_indexer_options`.
+ *
+ * @param stats The indexer stats
+ * @param payload A pointer to the cli_progress
+ */
+extern int cli_progress_indexer(
+ const git_indexer_progress *stats,
+ void *payload);
+
+/**
* Prints checkout progress to the console. Suitable for a
* `progress_cb` callback for `git_checkout_options`.
*
diff --git a/src/cli/unix/sighandler.c b/src/cli/unix/sighandler.c
index 6b4982d..05ac867 100644
--- a/src/cli/unix/sighandler.c
+++ b/src/cli/unix/sighandler.c
@@ -8,7 +8,8 @@
#include <stdint.h>
#include <signal.h>
#include "git2_util.h"
-#include "cli.h"
+#include "common.h"
+#include "sighandler.h"
static void (*interrupt_handler)(void) = NULL;
diff --git a/src/cli/win32/precompiled.h b/src/cli/win32/precompiled.h
index b0309b8..031370e 100644
--- a/src/cli/win32/precompiled.h
+++ b/src/cli/win32/precompiled.h
@@ -1,3 +1,3 @@
#include <git2.h>
-#include "cli.h"
+#include "common.h"
diff --git a/src/cli/win32/sighandler.c b/src/cli/win32/sighandler.c
index cc0b646..05a67fb 100644
--- a/src/cli/win32/sighandler.c
+++ b/src/cli/win32/sighandler.c
@@ -8,7 +8,7 @@
#include "git2_util.h"
#include <windows.h>
-#include "cli.h"
+#include "sighandler.h"
static void (*interrupt_handler)(void) = NULL;
diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt
index 876a703..bc7cb5b 100644
--- a/src/libgit2/CMakeLists.txt
+++ b/src/libgit2/CMakeLists.txt
@@ -65,12 +65,6 @@ set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJE
set_target_properties(libgit2package PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set_target_properties(libgit2package PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
-# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240)
-# Win64+MSVC+static libs = linker error
-if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS)
- set_target_properties(libgit2package PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64")
-endif()
-
ide_split_sources(libgit2package)
if(SONAME)
diff --git a/src/libgit2/attr.c b/src/libgit2/attr.c
index 1623b1d..1db90b5 100644
--- a/src/libgit2/attr.c
+++ b/src/libgit2/attr.c
@@ -424,9 +424,13 @@ static int attr_setup(
goto out;
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
- (error = preload_attr_source(repo, attr_session, &index_source)) < 0)
+ (error = preload_attr_source(repo, attr_session, &index_source)) < 0) {
+ if (error != GIT_ENOTFOUND)
goto out;
+ error = 0;
+ }
+
if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) &&
(error = preload_attr_source(repo, attr_session, &head_source)) < 0)
goto out;
diff --git a/src/libgit2/blame.c b/src/libgit2/blame.c
index d93dd5e..2ed7d20 100644
--- a/src/libgit2/blame.c
+++ b/src/libgit2/blame.c
@@ -117,12 +117,12 @@ static git_blame_hunk *dup_hunk(git_blame_hunk *hunk, git_blame *blame)
static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
{
size_t i;
-
- if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) {
- for (; i < v->length; i++) {
- git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
- hunk->final_start_line_number += shift_by;
+ for (i = 0; i < v->length; i++) {
+ git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
+ if(hunk->final_start_line_number < start_line){
+ continue;
}
+ hunk->final_start_line_number += shift_by;
}
}
@@ -444,21 +444,20 @@ static int buffer_hunk_cb(
GIT_UNUSED(delta);
- wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
+ wedge_line = (hunk->new_start >= hunk->old_start || hunk->old_lines==0) ? hunk->new_start : hunk->old_start;
blame->current_diff_line = wedge_line;
-
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
if (!blame->current_hunk) {
/* Line added at the end of the file */
blame->current_hunk = new_hunk(wedge_line, 0, wedge_line,
blame->path, blame);
+ blame->current_diff_line++;
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
-
git_vector_insert(&blame->hunks, blame->current_hunk);
} else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
/* If this hunk doesn't start between existing hunks, split a hunk up so it does */
blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
- wedge_line - blame->current_hunk->orig_start_line_number, true,
+ wedge_line - blame->current_hunk->final_start_line_number, true,
blame);
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
}
@@ -484,13 +483,12 @@ static int buffer_line_cb(
hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
/* Append to the current buffer-blame hunk */
blame->current_hunk->lines_in_hunk++;
- shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
+ shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
} else {
/* Create a new buffer-blame hunk with this line */
shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path, blame);
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
-
git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
}
blame->current_diff_line++;
@@ -498,15 +496,16 @@ static int buffer_line_cb(
if (line->origin == GIT_DIFF_LINE_DELETION) {
/* Trim the line from the current hunk; remove it if it's now empty */
- size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
+ size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk;
if (--(blame->current_hunk->lines_in_hunk) == 0) {
size_t i;
- shift_base--;
+ size_t i_next;
if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
git_vector_remove(&blame->hunks, i);
free_hunk(blame->current_hunk);
- blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i);
+ i_next = min( i , blame->hunks.length -1);
+ blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i_next);
}
}
shift_hunks_by(&blame->hunks, shift_base, -1);
diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c
index fca0ca0..d62c77a 100644
--- a/src/libgit2/clone.c
+++ b/src/libgit2/clone.c
@@ -22,6 +22,7 @@
#include "fs_path.h"
#include "repository.h"
#include "odb.h"
+#include "net.h"
static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link);
@@ -336,8 +337,9 @@ static int create_and_configure_origin(
git_remote_create_cb remote_create = options->remote_cb;
void *payload = options->remote_cb_payload;
- /* If the path exists and is a dir, the url should be the absolute path */
- if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) {
+ /* If the path is local and exists it should be the absolute path. */
+ if (!git_net_str_is_url(url) && git_fs_path_root(url) < 0 &&
+ git_fs_path_exists(url)) {
if (p_realpath(url, buf) == NULL)
return -1;
@@ -360,25 +362,29 @@ on_error:
return error;
}
-static bool should_checkout(
+static int should_checkout(
+ bool *out,
git_repository *repo,
bool is_bare,
const git_checkout_options *opts)
{
- if (is_bare)
- return false;
+ int error;
- if (!opts)
- return false;
+ if (!opts || is_bare || opts->checkout_strategy == GIT_CHECKOUT_NONE) {
+ *out = 0;
+ return 0;
+ }
- if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
- return false;
+ if ((error = git_repository_head_unborn(repo)) < 0)
+ return error;
- return !git_repository_head_unborn(repo);
+ *out = !error;
+ return 0;
}
static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message)
{
+ bool checkout;
int error;
if (branch)
@@ -387,7 +393,13 @@ static int checkout_branch(git_repository *repo, git_remote *remote, const git_c
else
error = update_head_to_remote(repo, remote, reflog_message);
- if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
+ if (error < 0)
+ return error;
+
+ if ((error = should_checkout(&checkout, repo, git_repository_is_bare(repo), co_opts)) < 0)
+ return error;
+
+ if (checkout)
error = git_checkout_head(repo, co_opts);
return error;
@@ -458,26 +470,25 @@ cleanup:
int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local)
{
git_str fromurl = GIT_STR_INIT;
- const char *path = url_or_path;
- bool is_url, is_local;
+ bool is_local;
if (local == GIT_CLONE_NO_LOCAL)
return 0;
- if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) {
- if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) {
- is_local = -1;
- goto done;
- }
+ if (git_net_str_is_url(url_or_path)) {
+ /* If GIT_CLONE_LOCAL_AUTO is specified, any url should be treated as remote */
+ if (local == GIT_CLONE_LOCAL_AUTO ||
+ !git_fs_path_is_local_file_url(url_or_path))
+ return 0;
- path = fromurl.ptr;
+ if (git_fs_path_fromurl(&fromurl, url_or_path) == 0)
+ is_local = git_fs_path_isdir(git_str_cstr(&fromurl));
+ else
+ is_local = -1;
+ git_str_dispose(&fromurl);
+ } else {
+ is_local = git_fs_path_isdir(url_or_path);
}
-
- is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) &&
- git_fs_path_isdir(path);
-
-done:
- git_str_dispose(&fromurl);
return is_local;
}
@@ -542,15 +553,15 @@ static int git__clone(
}
if (error != 0) {
- git_error_state last_error = {0};
- git_error_state_capture(&last_error, error);
+ git_error *last_error;
+ git_error_save(&last_error);
git_repository_free(repo);
repo = NULL;
(void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
- git_error_state_restore(&last_error);
+ git_error_restore(last_error);
}
*out = repo;
diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c
index f7be73a..47f6fed 100644
--- a/src/libgit2/commit.c
+++ b/src/libgit2/commit.c
@@ -281,7 +281,7 @@ int git_commit_create_from_ids(
typedef struct {
size_t total;
- const git_commit **parents;
+ git_commit * const *parents;
git_repository *repo;
} commit_parent_data;
@@ -307,7 +307,7 @@ int git_commit_create(
const char *message,
const git_tree *tree,
size_t parent_count,
- const git_commit *parents[])
+ git_commit * const parents[])
{
commit_parent_data data = { parent_count, parents, repo };
@@ -945,7 +945,7 @@ int git_commit_create_buffer(
const char *message,
const git_tree *tree,
size_t parent_count,
- const git_commit *parents[])
+ git_commit * const parents[])
{
GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo,
author, committer, message_encoding, message,
@@ -961,7 +961,7 @@ int git_commit__create_buffer(
const char *message,
const git_tree *tree,
size_t parent_count,
- const git_commit *parents[])
+ git_commit * const parents[])
{
int error;
commit_parent_data data = { parent_count, parents, repo };
@@ -1086,6 +1086,82 @@ cleanup:
return error;
}
+int git_commit_create_from_stage(
+ git_oid *out,
+ git_repository *repo,
+ const char *message,
+ const git_commit_create_options *given_opts)
+{
+ git_commit_create_options opts = GIT_COMMIT_CREATE_OPTIONS_INIT;
+ git_signature *default_signature = NULL;
+ const git_signature *author, *committer;
+ git_index *index = NULL;
+ git_diff *diff = NULL;
+ git_oid tree_id;
+ git_tree *head_tree = NULL, *tree = NULL;
+ git_commitarray parents = { 0 };
+ int error = -1;
+
+ GIT_ASSERT_ARG(out && repo);
+
+ if (given_opts)
+ memcpy(&opts, given_opts, sizeof(git_commit_create_options));
+
+ author = opts.author;
+ committer = opts.committer;
+
+ if (!author || !committer) {
+ if (git_signature_default(&default_signature, repo) < 0)
+ goto done;
+
+ if (!author)
+ author = default_signature;
+
+ if (!committer)
+ committer = default_signature;
+ }
+
+ if (git_repository_index(&index, repo) < 0)
+ goto done;
+
+ if (!opts.allow_empty_commit) {
+ error = git_repository_head_tree(&head_tree, repo);
+
+ if (error && error != GIT_EUNBORNBRANCH)
+ goto done;
+
+ error = -1;
+
+ if (git_diff_tree_to_index(&diff, repo, head_tree, index, NULL) < 0)
+ goto done;
+
+ if (git_diff_num_deltas(diff) == 0) {
+ git_error_set(GIT_ERROR_REPOSITORY,
+ "no changes are staged for commit");
+ error = GIT_EUNCHANGED;
+ goto done;
+ }
+ }
+
+ if (git_index_write_tree(&tree_id, index) < 0 ||
+ git_tree_lookup(&tree, repo, &tree_id) < 0 ||
+ git_repository_commit_parents(&parents, repo) < 0)
+ goto done;
+
+ error = git_commit_create(out, repo, "HEAD", author, committer,
+ opts.message_encoding, message,
+ tree, parents.count, parents.commits);
+
+done:
+ git_commitarray_dispose(&parents);
+ git_signature_free(default_signature);
+ git_tree_free(tree);
+ git_tree_free(head_tree);
+ git_diff_free(diff);
+ git_index_free(index);
+ return error;
+}
+
int git_commit_committer_with_mailmap(
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
{
@@ -1097,3 +1173,18 @@ int git_commit_author_with_mailmap(
{
return git_mailmap_resolve_signature(out, mailmap, commit->author);
}
+
+void git_commitarray_dispose(git_commitarray *array)
+{
+ size_t i;
+
+ if (array == NULL)
+ return;
+
+ for (i = 0; i < array->count; i++)
+ git_commit_free(array->commits[i]);
+
+ git__free((git_commit **)array->commits);
+
+ memset(array, 0, sizeof(*array));
+}
diff --git a/src/libgit2/commit.h b/src/libgit2/commit.h
index c25fee3..53128ba 100644
--- a/src/libgit2/commit.h
+++ b/src/libgit2/commit.h
@@ -64,7 +64,7 @@ int git_commit__create_buffer(
const char *message,
const git_tree *tree,
size_t parent_count,
- const git_commit *parents[]);
+ git_commit * const parents[]);
int git_commit__parse(
void *commit,
diff --git a/src/libgit2/config.c b/src/libgit2/config.c
index 23a8f9f..1e4e175 100644
--- a/src/libgit2/config.c
+++ b/src/libgit2/config.c
@@ -22,6 +22,32 @@
#include <ctype.h>
+/*
+ * A refcounted instance of a config_backend that can be shared across
+ * a configuration instance, any snapshots, and individual configuration
+ * levels (from `git_config_open_level`).
+ */
+typedef struct {
+ git_refcount rc;
+ git_config_backend *backend;
+} backend_instance;
+
+/*
+ * An entry in the readers or writers vector in the configuration.
+ * This is kept separate from the refcounted instance so that different
+ * views of the configuration can have different notions of levels or
+ * write orders.
+ *
+ * (eg, a standard configuration has a priority ordering of writers, a
+ * snapshot has *no* writers, and an individual level has a single
+ * writer.)
+ */
+typedef struct {
+ backend_instance *instance;
+ git_config_level_t level;
+ int write_order;
+} backend_entry;
+
void git_config_entry_free(git_config_entry *entry)
{
if (!entry)
@@ -30,75 +56,75 @@ void git_config_entry_free(git_config_entry *entry)
entry->free(entry);
}
-typedef struct {
- git_refcount rc;
-
- git_config_backend *backend;
- git_config_level_t level;
-} backend_internal;
-
-static void backend_internal_free(backend_internal *internal)
+static void backend_instance_free(backend_instance *instance)
{
git_config_backend *backend;
- backend = internal->backend;
+ backend = instance->backend;
backend->free(backend);
- git__free(internal);
+ git__free(instance);
}
-static void config_free(git_config *cfg)
+static void config_free(git_config *config)
{
size_t i;
- backend_internal *internal;
+ backend_entry *entry;
- for (i = 0; i < cfg->backends.length; ++i) {
- internal = git_vector_get(&cfg->backends, i);
- GIT_REFCOUNT_DEC(internal, backend_internal_free);
+ git_vector_foreach(&config->readers, i, entry) {
+ GIT_REFCOUNT_DEC(entry->instance, backend_instance_free);
+ git__free(entry);
}
- git_vector_free(&cfg->backends);
-
- git__memzero(cfg, sizeof(*cfg));
- git__free(cfg);
+ git_vector_free(&config->readers);
+ git_vector_free(&config->writers);
+ git__free(config);
}
-void git_config_free(git_config *cfg)
+void git_config_free(git_config *config)
{
- if (cfg == NULL)
+ if (config == NULL)
return;
- GIT_REFCOUNT_DEC(cfg, config_free);
+ GIT_REFCOUNT_DEC(config, config_free);
}
-static int config_backend_cmp(const void *a, const void *b)
+static int reader_cmp(const void *_a, const void *_b)
{
- const backend_internal *bk_a = (const backend_internal *)(a);
- const backend_internal *bk_b = (const backend_internal *)(b);
+ const backend_entry *a = _a;
+ const backend_entry *b = _b;
- return bk_b->level - bk_a->level;
+ return b->level - a->level;
}
-int git_config_new(git_config **out)
+static int writer_cmp(const void *_a, const void *_b)
{
- git_config *cfg;
+ const backend_entry *a = _a;
+ const backend_entry *b = _b;
- cfg = git__malloc(sizeof(git_config));
- GIT_ERROR_CHECK_ALLOC(cfg);
+ return b->write_order - a->write_order;
+}
- memset(cfg, 0x0, sizeof(git_config));
+int git_config_new(git_config **out)
+{
+ git_config *config;
+
+ config = git__calloc(1, sizeof(git_config));
+ GIT_ERROR_CHECK_ALLOC(config);
- if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) {
- git__free(cfg);
+ if (git_vector_init(&config->readers, 8, reader_cmp) < 0 ||
+ git_vector_init(&config->writers, 8, writer_cmp) < 0) {
+ config_free(config);
return -1;
}
- *out = cfg;
- GIT_REFCOUNT_INC(cfg);
+ GIT_REFCOUNT_INC(config);
+
+ *out = config;
return 0;
}
int git_config_add_file_ondisk(
- git_config *cfg,
+ git_config *config,
const char *path,
git_config_level_t level,
const git_repository *repo,
@@ -108,7 +134,7 @@ int git_config_add_file_ondisk(
struct stat st;
int res;
- GIT_ASSERT_ARG(cfg);
+ GIT_ASSERT_ARG(config);
GIT_ASSERT_ARG(path);
res = p_stat(path, &st);
@@ -120,7 +146,7 @@ int git_config_add_file_ondisk(
if (git_config_backend_from_file(&file, path) < 0)
return -1;
- if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) {
+ if ((res = git_config_add_backend(config, file, level, repo, force)) < 0) {
/*
* free manually; the file is not owned by the config
* instance yet and will not be freed on cleanup
@@ -154,7 +180,7 @@ int git_config_snapshot(git_config **out, git_config *in)
{
int error = 0;
size_t i;
- backend_internal *internal;
+ backend_entry *entry;
git_config *config;
*out = NULL;
@@ -162,18 +188,20 @@ int git_config_snapshot(git_config **out, git_config *in)
if (git_config_new(&config) < 0)
return -1;
- git_vector_foreach(&in->backends, i, internal) {
+ git_vector_foreach(&in->readers, i, entry) {
git_config_backend *b;
- if ((error = internal->backend->snapshot(&b, internal->backend)) < 0)
+ if ((error = entry->instance->backend->snapshot(&b, entry->instance->backend)) < 0)
break;
- if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) {
+ if ((error = git_config_add_backend(config, b, entry->level, NULL, 0)) < 0) {
b->free(b);
break;
}
}
+ git_config_set_writeorder(config, NULL, 0);
+
if (error < 0)
git_config_free(config);
else
@@ -183,141 +211,162 @@ int git_config_snapshot(git_config **out, git_config *in)
}
static int find_backend_by_level(
- backend_internal **out,
- const git_config *cfg,
+ backend_instance **out,
+ const git_config *config,
git_config_level_t level)
{
- int pos = -1;
- backend_internal *internal;
+ backend_entry *entry, *found = NULL;
size_t i;
- /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend
- * which has the highest level. As config backends are stored in a vector
- * sorted by decreasing order of level, getting the backend at position 0
- * will do the job.
+ /*
+ * when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the
+ * config backend which has the highest level. As config backends
+ * are stored in a vector sorted by decreasing order of level,
+ * getting the backend at position 0 will do the job.
*/
if (level == GIT_CONFIG_HIGHEST_LEVEL) {
- pos = 0;
+ found = git_vector_get(&config->readers, 0);
} else {
- git_vector_foreach(&cfg->backends, i, internal) {
- if (internal->level == level)
- pos = (int)i;
+ git_vector_foreach(&config->readers, i, entry) {
+ if (entry->level == level) {
+ found = entry;
+ break;
+ }
}
}
- if (pos == -1) {
+ if (!found) {
git_error_set(GIT_ERROR_CONFIG,
- "no configuration exists for the given level '%i'", (int)level);
+ "no configuration exists for the given level '%d'", level);
return GIT_ENOTFOUND;
}
- *out = git_vector_get(&cfg->backends, pos);
-
+ *out = found->instance;
return 0;
}
-static int duplicate_level(void **old_raw, void *new_raw)
+static int duplicate_level(void **_old, void *_new)
{
- backend_internal **old = (backend_internal **)old_raw;
+ backend_entry **old = (backend_entry **)_old;
- GIT_UNUSED(new_raw);
+ GIT_UNUSED(_new);
- git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level);
+ git_error_set(GIT_ERROR_CONFIG, "configuration at level %d already exists", (*old)->level);
return GIT_EEXISTS;
}
static void try_remove_existing_backend(
- git_config *cfg,
+ git_config *config,
git_config_level_t level)
{
- int pos = -1;
- backend_internal *internal;
+ backend_entry *entry, *found = NULL;
size_t i;
- git_vector_foreach(&cfg->backends, i, internal) {
- if (internal->level == level)
- pos = (int)i;
+ git_vector_foreach(&config->readers, i, entry) {
+ if (entry->level == level) {
+ git_vector_remove(&config->readers, i);
+ found = entry;
+ break;
+ }
}
- if (pos == -1)
+ if (!found)
return;
- internal = git_vector_get(&cfg->backends, pos);
-
- if (git_vector_remove(&cfg->backends, pos) < 0)
- return;
+ git_vector_foreach(&config->writers, i, entry) {
+ if (entry->level == level) {
+ git_vector_remove(&config->writers, i);
+ break;
+ }
+ }
- GIT_REFCOUNT_DEC(internal, backend_internal_free);
+ GIT_REFCOUNT_DEC(found->instance, backend_instance_free);
+ git__free(found);
}
-static int git_config__add_internal(
- git_config *cfg,
- backend_internal *internal,
+static int git_config__add_instance(
+ git_config *config,
+ backend_instance *instance,
git_config_level_t level,
int force)
{
+ backend_entry *entry;
int result;
/* delete existing config backend for level if it exists */
if (force)
- try_remove_existing_backend(cfg, level);
+ try_remove_existing_backend(config, level);
- if ((result = git_vector_insert_sorted(&cfg->backends,
- internal, &duplicate_level)) < 0)
- return result;
+ entry = git__malloc(sizeof(backend_entry));
+ GIT_ERROR_CHECK_ALLOC(entry);
- git_vector_sort(&cfg->backends);
- internal->backend->cfg = cfg;
+ entry->instance = instance;
+ entry->level = level;
+ entry->write_order = level;
- GIT_REFCOUNT_INC(internal);
+ if ((result = git_vector_insert_sorted(&config->readers,
+ entry, &duplicate_level)) < 0 ||
+ (result = git_vector_insert_sorted(&config->writers,
+ entry, NULL)) < 0) {
+ git__free(entry);
+ return result;
+ }
+
+ GIT_REFCOUNT_INC(entry->instance);
return 0;
}
-int git_config_open_global(git_config **cfg_out, git_config *cfg)
+int git_config_open_global(git_config **out, git_config *config)
{
- if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG))
+ int error;
+
+ error = git_config_open_level(out, config, GIT_CONFIG_LEVEL_XDG);
+
+ if (error == 0)
return 0;
+ else if (error != GIT_ENOTFOUND)
+ return error;
- return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL);
+ return git_config_open_level(out, config, GIT_CONFIG_LEVEL_GLOBAL);
}
int git_config_open_level(
- git_config **cfg_out,
- const git_config *cfg_parent,
+ git_config **out,
+ const git_config *parent,
git_config_level_t level)
{
- git_config *cfg;
- backend_internal *internal;
+ git_config *config;
+ backend_instance *instance;
int res;
- if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0)
+ if ((res = find_backend_by_level(&instance, parent, level)) < 0)
return res;
- if ((res = git_config_new(&cfg)) < 0)
+ if ((res = git_config_new(&config)) < 0)
return res;
- if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) {
- git_config_free(cfg);
+ if ((res = git_config__add_instance(config, instance, level, true)) < 0) {
+ git_config_free(config);
return res;
}
- *cfg_out = cfg;
+ *out = config;
return 0;
}
int git_config_add_backend(
- git_config *cfg,
+ git_config *config,
git_config_backend *backend,
git_config_level_t level,
const git_repository *repo,
int force)
{
- backend_internal *internal;
+ backend_instance *instance;
int result;
- GIT_ASSERT_ARG(cfg);
+ GIT_ASSERT_ARG(config);
GIT_ASSERT_ARG(backend);
GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
@@ -325,22 +374,50 @@ int git_config_add_backend(
if ((result = backend->open(backend, level, repo)) < 0)
return result;
- internal = git__malloc(sizeof(backend_internal));
- GIT_ERROR_CHECK_ALLOC(internal);
+ instance = git__calloc(1, sizeof(backend_instance));
+ GIT_ERROR_CHECK_ALLOC(instance);
- memset(internal, 0x0, sizeof(backend_internal));
+ instance->backend = backend;
+ instance->backend->cfg = config;
- internal->backend = backend;
- internal->level = level;
-
- if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) {
- git__free(internal);
+ if ((result = git_config__add_instance(config, instance, level, force)) < 0) {
+ git__free(instance);
return result;
}
return 0;
}
+int git_config_set_writeorder(
+ git_config *config,
+ git_config_level_t *levels,
+ size_t len)
+{
+ backend_entry *entry;
+ size_t i, j;
+
+ GIT_ASSERT(len < INT_MAX);
+
+ git_vector_foreach(&config->readers, i, entry) {
+ bool found = false;
+
+ for (j = 0; j < len; j++) {
+ if (levels[j] == entry->level) {
+ entry->write_order = (int)j;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ entry->write_order = -1;
+ }
+
+ git_vector_sort(&config->writers);
+
+ return 0;
+}
+
/*
* Loop over all the variables
*/
@@ -348,37 +425,20 @@ int git_config_add_backend(
typedef struct {
git_config_iterator parent;
git_config_iterator *current;
- const git_config *cfg;
+ const git_config *config;
git_regexp regex;
size_t i;
} all_iter;
-static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
-{
- backend_internal *internal;
-
- for (; i > 0; --i) {
- internal = git_vector_get(&cfg->backends, i - 1);
- if (!internal || !internal->backend)
- continue;
-
- *out = i;
- return 0;
- }
-
- return -1;
-}
-
-static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
+static int all_iter_next(git_config_entry **out, git_config_iterator *_iter)
{
all_iter *iter = (all_iter *) _iter;
- backend_internal *internal;
+ backend_entry *entry;
git_config_backend *backend;
- size_t i;
int error = 0;
if (iter->current != NULL &&
- (error = iter->current->next(entry, iter->current)) == 0) {
+ (error = iter->current->next(out, iter->current)) == 0) {
return 0;
}
@@ -386,12 +446,14 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
return error;
do {
- if (find_next_backend(&i, iter->cfg, iter->i) < 0)
+ if (iter->i == 0)
return GIT_ITEROVER;
- internal = git_vector_get(&iter->cfg->backends, i - 1);
- backend = internal->backend;
- iter->i = i - 1;
+ entry = git_vector_get(&iter->config->readers, iter->i - 1);
+ GIT_ASSERT(entry && entry->instance && entry->instance->backend);
+
+ backend = entry->instance->backend;
+ iter->i--;
if (iter->current)
iter->current->free(iter->current);
@@ -404,7 +466,7 @@ static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
if (error < 0)
return error;
- error = iter->current->next(entry, iter->current);
+ error = iter->current->next(out, iter->current);
/* If this backend is empty, then keep going */
if (error == GIT_ITEROVER)
continue;
@@ -423,7 +485,7 @@ static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_it
/*
* We use the "normal" function to grab the next one across
- * backends and then apply the regex
+ * readers and then apply the regex
*/
while ((error = all_iter_next(entry, _iter)) == 0) {
/* skip non-matching keys if regexp was provided */
@@ -455,7 +517,7 @@ static void all_iter_glob_free(git_config_iterator *_iter)
all_iter_free(_iter);
}
-int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
+int git_config_iterator_new(git_config_iterator **out, const git_config *config)
{
all_iter *iter;
@@ -465,21 +527,21 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
iter->parent.free = all_iter_free;
iter->parent.next = all_iter_next;
- iter->i = cfg->backends.length;
- iter->cfg = cfg;
+ iter->i = config->readers.length;
+ iter->config = config;
*out = (git_config_iterator *) iter;
return 0;
}
-int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp)
+int git_config_iterator_glob_new(git_config_iterator **out, const git_config *config, const char *regexp)
{
all_iter *iter;
int result;
if (regexp == NULL)
- return git_config_iterator_new(out, cfg);
+ return git_config_iterator_new(out, config);
iter = git__calloc(1, sizeof(all_iter));
GIT_ERROR_CHECK_ALLOC(iter);
@@ -491,8 +553,8 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf
iter->parent.next = all_iter_glob_next;
iter->parent.free = all_iter_glob_free;
- iter->i = cfg->backends.length;
- iter->cfg = cfg;
+ iter->i = config->readers.length;
+ iter->config = config;
*out = (git_config_iterator *) iter;
@@ -500,9 +562,9 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf
}
int git_config_foreach(
- const git_config *cfg, git_config_foreach_cb cb, void *payload)
+ const git_config *config, git_config_foreach_cb cb, void *payload)
{
- return git_config_foreach_match(cfg, NULL, cb, payload);
+ return git_config_foreach_match(config, NULL, cb, payload);
}
int git_config_backend_foreach_match(
@@ -548,7 +610,7 @@ int git_config_backend_foreach_match(
}
int git_config_foreach_match(
- const git_config *cfg,
+ const git_config *config,
const char *regexp,
git_config_foreach_cb cb,
void *payload)
@@ -557,7 +619,7 @@ int git_config_foreach_match(
git_config_iterator *iter;
git_config_entry *entry;
- if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0)
+ if ((error = git_config_iterator_glob_new(&iter, config, regexp)) < 0)
return error;
while (!(error = git_config_next(&entry, iter))) {
@@ -579,72 +641,59 @@ int git_config_foreach_match(
* Setters
**************/
-typedef enum {
- BACKEND_USE_SET,
- BACKEND_USE_DELETE
-} backend_use;
-
-static const char *uses[] = {
- "set",
- "delete"
-};
-
-static int get_backend_for_use(git_config_backend **out,
- git_config *cfg, const char *name, backend_use use)
-{
+ static backend_instance *get_writer_instance(git_config *config)
+ {
+ backend_entry *entry;
size_t i;
- backend_internal *backend;
- *out = NULL;
+ git_vector_foreach(&config->writers, i, entry) {
+ if (entry->instance->backend->readonly)
+ continue;
- if (git_vector_length(&cfg->backends) == 0) {
- git_error_set(GIT_ERROR_CONFIG,
- "cannot %s value for '%s' when no config backends exist",
- uses[use], name);
- return GIT_ENOTFOUND;
- }
+ if (entry->write_order < 0)
+ continue;
- git_vector_foreach(&cfg->backends, i, backend) {
- if (!backend->backend->readonly) {
- *out = backend->backend;
- return 0;
- }
+ return entry->instance;
}
- git_error_set(GIT_ERROR_CONFIG,
- "cannot %s value for '%s' when all config backends are readonly",
- uses[use], name);
- return GIT_ENOTFOUND;
+ return NULL;
+ }
+
+static git_config_backend *get_writer(git_config *config)
+{
+ backend_instance *instance = get_writer_instance(config);
+
+ return instance ? instance->backend : NULL;
}
-int git_config_delete_entry(git_config *cfg, const char *name)
+int git_config_delete_entry(git_config *config, const char *name)
{
git_config_backend *backend;
- if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
- return GIT_ENOTFOUND;
+ if ((backend = get_writer(config)) == NULL)
+ return GIT_EREADONLY;
return backend->del(backend, name);
}
-int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
+int git_config_set_int64(git_config *config, const char *name, int64_t value)
{
char str_value[32]; /* All numbers should fit in here */
p_snprintf(str_value, sizeof(str_value), "%" PRId64, value);
- return git_config_set_string(cfg, name, str_value);
+ return git_config_set_string(config, name, str_value);
}
-int git_config_set_int32(git_config *cfg, const char *name, int32_t value)
+int git_config_set_int32(git_config *config, const char *name, int32_t value)
{
- return git_config_set_int64(cfg, name, (int64_t)value);
+ return git_config_set_int64(config, name, (int64_t)value);
}
-int git_config_set_bool(git_config *cfg, const char *name, int value)
+int git_config_set_bool(git_config *config, const char *name, int value)
{
- return git_config_set_string(cfg, name, value ? "true" : "false");
+ return git_config_set_string(config, name, value ? "true" : "false");
}
-int git_config_set_string(git_config *cfg, const char *name, const char *value)
+int git_config_set_string(git_config *config, const char *name, const char *value)
{
int error;
git_config_backend *backend;
@@ -654,13 +703,15 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
return -1;
}
- if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0)
- return GIT_ENOTFOUND;
+ if ((backend = get_writer(config)) == NULL) {
+ git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name);
+ return GIT_EREADONLY;
+ }
error = backend->set(backend, name, value);
- if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
- git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+ if (!error && GIT_REFCOUNT_OWNER(config) != NULL)
+ git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(config));
return error;
}
@@ -714,16 +765,17 @@ enum {
static int get_entry(
git_config_entry **out,
- const git_config *cfg,
+ const git_config *config,
const char *name,
bool normalize_name,
int want_errors)
{
+ backend_entry *entry;
+ git_config_backend *backend;
int res = GIT_ENOTFOUND;
const char *key = name;
char *normalized = NULL;
size_t i;
- backend_internal *internal;
*out = NULL;
@@ -734,11 +786,12 @@ static int get_entry(
}
res = GIT_ENOTFOUND;
- git_vector_foreach(&cfg->backends, i, internal) {
- if (!internal || !internal->backend)
- continue;
+ git_vector_foreach(&config->readers, i, entry) {
+ GIT_ASSERT(entry->instance && entry->instance->backend);
+
+ backend = entry->instance->backend;
+ res = backend->get(backend, key, out);
- res = internal->backend->get(internal->backend, key, out);
if (res != GIT_ENOTFOUND)
break;
}
@@ -746,9 +799,9 @@ static int get_entry(
git__free(normalized);
cleanup:
- if (res == GIT_ENOTFOUND)
+ if (res == GIT_ENOTFOUND) {
res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name);
- else if (res && (want_errors == GET_NO_ERRORS)) {
+ } else if (res && (want_errors == GET_NO_ERRORS)) {
git_error_clear();
res = 0;
}
@@ -757,24 +810,24 @@ cleanup:
}
int git_config_get_entry(
- git_config_entry **out, const git_config *cfg, const char *name)
+ git_config_entry **out, const git_config *config, const char *name)
{
- return get_entry(out, cfg, name, true, GET_ALL_ERRORS);
+ return get_entry(out, config, name, true, GET_ALL_ERRORS);
}
int git_config__lookup_entry(
git_config_entry **out,
- const git_config *cfg,
+ const git_config *config,
const char *key,
bool no_errors)
{
return get_entry(
- out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING);
+ out, config, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING);
}
int git_config_get_mapped(
int *out,
- const git_config *cfg,
+ const git_config *config,
const char *name,
const git_configmap *maps,
size_t map_n)
@@ -782,7 +835,7 @@ int git_config_get_mapped(
git_config_entry *entry;
int ret;
- if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0)
return ret;
ret = git_config_lookup_map_value(out, maps, map_n, entry->value);
@@ -791,12 +844,12 @@ int git_config_get_mapped(
return ret;
}
-int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name)
+int git_config_get_int64(int64_t *out, const git_config *config, const char *name)
{
git_config_entry *entry;
int ret;
- if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0)
return ret;
ret = git_config_parse_int64(out, entry->value);
@@ -805,12 +858,12 @@ int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name)
return ret;
}
-int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name)
+int git_config_get_int32(int32_t *out, const git_config *config, const char *name)
{
git_config_entry *entry;
int ret;
- if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0)
return ret;
ret = git_config_parse_int32(out, entry->value);
@@ -819,12 +872,12 @@ int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name)
return ret;
}
-int git_config_get_bool(int *out, const git_config *cfg, const char *name)
+int git_config_get_bool(int *out, const git_config *config, const char *name)
{
git_config_entry *entry;
int ret;
- if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ if ((ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0)
return ret;
ret = git_config_parse_bool(out, entry->value);
@@ -833,16 +886,15 @@ int git_config_get_bool(int *out, const git_config *cfg, const char *name)
return ret;
}
-static int is_readonly(const git_config *cfg)
+static int is_readonly(const git_config *config)
{
+ backend_entry *entry;
size_t i;
- backend_internal *internal;
- git_vector_foreach(&cfg->backends, i, internal) {
- if (!internal || !internal->backend)
- continue;
+ git_vector_foreach(&config->writers, i, entry) {
+ GIT_ASSERT(entry->instance && entry->instance->backend);
- if (!internal->backend->readonly)
+ if (!entry->instance->backend->readonly)
return 0;
}
@@ -873,21 +925,21 @@ int git_config_parse_path(git_buf *out, const char *value)
int git_config_get_path(
git_buf *out,
- const git_config *cfg,
+ const git_config *config,
const char *name)
{
- GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name);
+ GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, config, name);
}
int git_config__get_path(
git_str *out,
- const git_config *cfg,
+ const git_config *config,
const char *name)
{
git_config_entry *entry;
int error;
- if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ if ((error = get_entry(&entry, config, name, true, GET_ALL_ERRORS)) < 0)
return error;
error = git_config__parse_path(out, entry->value);
@@ -897,17 +949,17 @@ int git_config__get_path(
}
int git_config_get_string(
- const char **out, const git_config *cfg, const char *name)
+ const char **out, const git_config *config, const char *name)
{
git_config_entry *entry;
int ret;
- if (!is_readonly(cfg)) {
+ if (!is_readonly(config)) {
git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object");
return -1;
}
- ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS);
+ ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS);
*out = !ret ? (entry->value ? entry->value : "") : NULL;
git_config_entry_free(entry);
@@ -916,22 +968,22 @@ int git_config_get_string(
}
int git_config_get_string_buf(
- git_buf *out, const git_config *cfg, const char *name)
+ git_buf *out, const git_config *config, const char *name)
{
- GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name);
+ GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, config, name);
}
int git_config__get_string_buf(
- git_str *out, const git_config *cfg, const char *name)
+ git_str *out, const git_config *config, const char *name)
{
git_config_entry *entry;
int ret;
const char *str;
GIT_ASSERT_ARG(out);
- GIT_ASSERT_ARG(cfg);
+ GIT_ASSERT_ARG(config);
- ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS);
+ ret = get_entry(&entry, config, name, true, GET_ALL_ERRORS);
str = !ret ? (entry->value ? entry->value : "") : NULL;
if (str)
@@ -943,12 +995,12 @@ int git_config__get_string_buf(
}
char *git_config__get_string_force(
- const git_config *cfg, const char *key, const char *fallback_value)
+ const git_config *config, const char *key, const char *fallback_value)
{
git_config_entry *entry;
char *ret;
- get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+ get_entry(&entry, config, key, false, GET_NO_ERRORS);
ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL;
git_config_entry_free(entry);
@@ -956,12 +1008,12 @@ char *git_config__get_string_force(
}
int git_config__get_bool_force(
- const git_config *cfg, const char *key, int fallback_value)
+ const git_config *config, const char *key, int fallback_value)
{
int val = fallback_value;
git_config_entry *entry;
- get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+ get_entry(&entry, config, key, false, GET_NO_ERRORS);
if (entry && git_config_parse_bool(&val, entry->value) < 0)
git_error_clear();
@@ -971,12 +1023,12 @@ int git_config__get_bool_force(
}
int git_config__get_int_force(
- const git_config *cfg, const char *key, int fallback_value)
+ const git_config *config, const char *key, int fallback_value)
{
int32_t val = (int32_t)fallback_value;
git_config_entry *entry;
- get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+ get_entry(&entry, config, key, false, GET_NO_ERRORS);
if (entry && git_config_parse_int32(&val, entry->value) < 0)
git_error_clear();
@@ -986,14 +1038,14 @@ int git_config__get_int_force(
}
int git_config_get_multivar_foreach(
- const git_config *cfg, const char *name, const char *regexp,
+ const git_config *config, const char *name, const char *regexp,
git_config_foreach_cb cb, void *payload)
{
int err, found;
git_config_iterator *iter;
git_config_entry *entry;
- if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0)
+ if ((err = git_config_multivar_iterator_new(&iter, config, name, regexp)) < 0)
return err;
found = 0;
@@ -1055,13 +1107,13 @@ static void multivar_iter_free(git_config_iterator *_iter)
git__free(iter);
}
-int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp)
+int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *config, const char *name, const char *regexp)
{
multivar_iter *iter = NULL;
git_config_iterator *inner = NULL;
int error;
- if ((error = git_config_iterator_new(&inner, cfg)) < 0)
+ if ((error = git_config_iterator_new(&inner, config)) < 0)
return error;
iter = git__calloc(1, sizeof(multivar_iter));
@@ -1092,22 +1144,24 @@ on_error:
return error;
}
-int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
+int git_config_set_multivar(git_config *config, const char *name, const char *regexp, const char *value)
{
git_config_backend *backend;
- if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
- return GIT_ENOTFOUND;
+ if ((backend = get_writer(config)) == NULL) {
+ git_error_set(GIT_ERROR_CONFIG, "cannot set '%s': the configuration is read-only", name);
+ return GIT_EREADONLY;
+ }
return backend->set_multivar(backend, name, regexp, value);
}
-int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp)
+int git_config_delete_multivar(git_config *config, const char *name, const char *regexp)
{
git_config_backend *backend;
- if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0)
- return GIT_ENOTFOUND;
+ if ((backend = get_writer(config)) == NULL)
+ return GIT_EREADONLY;
return backend->del_multivar(backend, name, regexp);
}
@@ -1218,79 +1272,77 @@ int git_config__global_location(git_str *buf)
int git_config_open_default(git_config **out)
{
int error;
- git_config *cfg = NULL;
+ git_config *config = NULL;
git_str buf = GIT_STR_INIT;
- if ((error = git_config_new(&cfg)) < 0)
+ if ((error = git_config_new(&config)) < 0)
return error;
if (!git_config__find_global(&buf) ||
!git_config__global_location(&buf)) {
- error = git_config_add_file_ondisk(cfg, buf.ptr,
+ error = git_config_add_file_ondisk(config, buf.ptr,
GIT_CONFIG_LEVEL_GLOBAL, NULL, 0);
}
if (!error && !git_config__find_xdg(&buf))
- error = git_config_add_file_ondisk(cfg, buf.ptr,
+ error = git_config_add_file_ondisk(config, buf.ptr,
GIT_CONFIG_LEVEL_XDG, NULL, 0);
if (!error && !git_config__find_system(&buf))
- error = git_config_add_file_ondisk(cfg, buf.ptr,
+ error = git_config_add_file_ondisk(config, buf.ptr,
GIT_CONFIG_LEVEL_SYSTEM, NULL, 0);
if (!error && !git_config__find_programdata(&buf))
- error = git_config_add_file_ondisk(cfg, buf.ptr,
+ error = git_config_add_file_ondisk(config, buf.ptr,
GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0);
git_str_dispose(&buf);
if (error) {
- git_config_free(cfg);
- cfg = NULL;
+ git_config_free(config);
+ config = NULL;
}
- *out = cfg;
+ *out = config;
return error;
}
-int git_config_lock(git_transaction **out, git_config *cfg)
+int git_config_lock(git_transaction **out, git_config *config)
{
+ backend_instance *instance;
int error;
- git_config_backend *backend;
- backend_internal *internal;
- GIT_ASSERT_ARG(cfg);
+ GIT_ASSERT_ARG(config);
- internal = git_vector_get(&cfg->backends, 0);
- if (!internal || !internal->backend) {
- git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends");
- return -1;
+ if ((instance = get_writer_instance(config)) == NULL) {
+ git_error_set(GIT_ERROR_CONFIG, "cannot lock: the configuration is read-only");
+ return GIT_EREADONLY;
}
- backend = internal->backend;
- if ((error = backend->lock(backend)) < 0)
+ if ((error = instance->backend->lock(instance->backend)) < 0 ||
+ (error = git_transaction_config_new(out, config, instance)) < 0)
return error;
- return git_transaction_config_new(out, cfg);
+ GIT_REFCOUNT_INC(instance);
+ return 0;
}
-int git_config_unlock(git_config *cfg, int commit)
+int git_config_unlock(
+ git_config *config,
+ void *data,
+ int commit)
{
- git_config_backend *backend;
- backend_internal *internal;
-
- GIT_ASSERT_ARG(cfg);
+ backend_instance *instance = data;
+ int error;
- internal = git_vector_get(&cfg->backends, 0);
- if (!internal || !internal->backend) {
- git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends");
- return -1;
- }
+ GIT_ASSERT_ARG(config && data);
+ GIT_UNUSED(config);
- backend = internal->backend;
+ error = instance->backend->unlock(instance->backend, commit);
+ GIT_REFCOUNT_DEC(instance, backend_instance_free);
- return backend->unlock(backend, commit);
+ return error;
}
/***********
@@ -1447,7 +1499,7 @@ static int normalize_section(char *start, char *end)
for (scan = start; *scan; ++scan) {
if (end && scan >= end)
break;
- if (isalnum(*scan))
+ if (git__isalnum(*scan))
*scan = (char)git__tolower(*scan);
else if (*scan != '-' || scan == start)
return GIT_EINVALIDSPEC;
@@ -1509,19 +1561,32 @@ static int rename_config_entries_cb(
int error = 0;
struct rename_data *data = (struct rename_data *)payload;
size_t base_len = git_str_len(data->name);
+ git_str value = GIT_STR_INIT;
+
+ if (base_len > 0) {
+ if ((error = git_str_puts(data->name,
+ entry->name + data->old_len)) < 0 ||
+ (error = git_config_set_multivar(
+ data->config, git_str_cstr(data->name), "^$",
+ entry->value)) < 0)
+ goto cleanup;
+ }
- if (base_len > 0 &&
- !(error = git_str_puts(data->name, entry->name + data->old_len)))
- {
- error = git_config_set_string(
- data->config, git_str_cstr(data->name), entry->value);
+ git_str_putc(&value, '^');
+ git_str_puts_escape_regex(&value, entry->value);
+ git_str_putc(&value, '$');
- git_str_truncate(data->name, base_len);
+ if (git_str_oom(&value)) {
+ error = -1;
+ goto cleanup;
}
- if (!error)
- error = git_config_delete_entry(data->config, entry->name);
+ error = git_config_delete_multivar(
+ data->config, entry->name, git_str_cstr(&value));
+ cleanup:
+ git_str_truncate(data->name, base_len);
+ git_str_dispose(&value);
return error;
}
diff --git a/src/libgit2/config.h b/src/libgit2/config.h
index 01b84b1..5003cbf 100644
--- a/src/libgit2/config.h
+++ b/src/libgit2/config.h
@@ -24,7 +24,8 @@
struct git_config {
git_refcount rc;
- git_vector backends;
+ git_vector readers;
+ git_vector writers;
};
extern int git_config__global_location(git_str *buf);
@@ -94,17 +95,21 @@ int git_config_lookup_map_enum(git_configmap_t *type_out,
size_t map_n, int enum_val);
/**
- * Unlock the backend with the highest priority
+ * Unlock the given backend that was previously locked.
*
* Unlocking will allow other writers to update the configuration
* file. Optionally, any changes performed since the lock will be
* applied to the configuration.
*
- * @param cfg the configuration
+ * @param config the config instance
+ * @param data the config data passed to git_transaction_new
* @param commit boolean which indicates whether to commit any changes
* done since locking
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit);
+GIT_EXTERN(int) git_config_unlock(
+ git_config *config,
+ void *data,
+ int commit);
#endif
diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h
index dbb1905..37d25ab 100644
--- a/src/libgit2/config_backend.h
+++ b/src/libgit2/config_backend.h
@@ -37,15 +37,6 @@ extern int git_config_backend_from_file(git_config_backend **out, const char *pa
*/
extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source);
-/**
- * Create an in-memory configuration file backend
- *
- * @param out the new backend
- * @param cfg the configuration that is to be parsed
- * @param len the length of the string pointed to by `cfg`
- */
-extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len);
-
GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo)
{
return cfg->open(cfg, level, repo);
diff --git a/src/libgit2/config_entries.c b/src/libgit2/config_entries.c
deleted file mode 100644
index 66aae09..0000000
--- a/src/libgit2/config_entries.c
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#include "config_entries.h"
-
-typedef struct config_entry_list {
- struct config_entry_list *next;
- struct config_entry_list *last;
- git_config_entry *entry;
-} config_entry_list;
-
-typedef struct {
- git_config_entry *entry;
- bool multivar;
-} config_entry_map_head;
-
-typedef struct config_entries_iterator {
- git_config_iterator parent;
- git_config_entries *entries;
- config_entry_list *head;
-} config_entries_iterator;
-
-struct git_config_entries {
- git_refcount rc;
- git_strmap *map;
- config_entry_list *list;
-};
-
-int git_config_entries_new(git_config_entries **out)
-{
- git_config_entries *entries;
- int error;
-
- entries = git__calloc(1, sizeof(git_config_entries));
- GIT_ERROR_CHECK_ALLOC(entries);
- GIT_REFCOUNT_INC(entries);
-
- if ((error = git_strmap_new(&entries->map)) < 0)
- git__free(entries);
- else
- *out = entries;
-
- return error;
-}
-
-int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry)
-{
- git_config_entry *duplicated;
- int error;
-
- duplicated = git__calloc(1, sizeof(git_config_entry));
- GIT_ERROR_CHECK_ALLOC(duplicated);
-
- duplicated->name = git__strdup(entry->name);
- GIT_ERROR_CHECK_ALLOC(duplicated->name);
-
- if (entry->value) {
- duplicated->value = git__strdup(entry->value);
- GIT_ERROR_CHECK_ALLOC(duplicated->value);
- }
- duplicated->level = entry->level;
- duplicated->include_depth = entry->include_depth;
-
- if ((error = git_config_entries_append(entries, duplicated)) < 0)
- goto out;
-
-out:
- if (error && duplicated) {
- git__free((char *) duplicated->name);
- git__free((char *) duplicated->value);
- git__free(duplicated);
- }
- return error;
-}
-
-int git_config_entries_dup(git_config_entries **out, git_config_entries *entries)
-{
- git_config_entries *result = NULL;
- config_entry_list *head;
- int error;
-
- if ((error = git_config_entries_new(&result)) < 0)
- goto out;
-
- for (head = entries->list; head; head = head->next)
- if ((git_config_entries_dup_entry(result, head->entry)) < 0)
- goto out;
-
- *out = result;
- result = NULL;
-
-out:
- git_config_entries_free(result);
- return error;
-}
-
-void git_config_entries_incref(git_config_entries *entries)
-{
- GIT_REFCOUNT_INC(entries);
-}
-
-static void config_entries_free(git_config_entries *entries)
-{
- config_entry_list *list = NULL, *next;
- config_entry_map_head *head;
-
- git_strmap_foreach_value(entries->map, head,
- git__free((char *) head->entry->name); git__free(head)
- );
- git_strmap_free(entries->map);
-
- list = entries->list;
- while (list != NULL) {
- next = list->next;
- git__free((char *) list->entry->value);
- git__free(list->entry);
- git__free(list);
- list = next;
- }
-
- git__free(entries);
-}
-
-void git_config_entries_free(git_config_entries *entries)
-{
- if (entries)
- GIT_REFCOUNT_DEC(entries, config_entries_free);
-}
-
-int git_config_entries_append(git_config_entries *entries, git_config_entry *entry)
-{
- config_entry_list *list_head;
- config_entry_map_head *map_head;
-
- if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) {
- map_head->multivar = true;
- /*
- * This is a micro-optimization for configuration files
- * with a lot of same keys. As for multivars the entry's
- * key will be the same for all entries, we can just free
- * all except the first entry's name and just re-use it.
- */
- git__free((char *) entry->name);
- entry->name = map_head->entry->name;
- } else {
- map_head = git__calloc(1, sizeof(*map_head));
- if ((git_strmap_set(entries->map, entry->name, map_head)) < 0)
- return -1;
- }
- map_head->entry = entry;
-
- list_head = git__calloc(1, sizeof(config_entry_list));
- GIT_ERROR_CHECK_ALLOC(list_head);
- list_head->entry = entry;
-
- if (entries->list)
- entries->list->last->next = list_head;
- else
- entries->list = list_head;
- entries->list->last = list_head;
-
- return 0;
-}
-
-int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key)
-{
- config_entry_map_head *entry;
- if ((entry = git_strmap_get(entries->map, key)) == NULL)
- return GIT_ENOTFOUND;
- *out = entry->entry;
- return 0;
-}
-
-int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key)
-{
- config_entry_map_head *entry;
-
- if ((entry = git_strmap_get(entries->map, key)) == NULL)
- return GIT_ENOTFOUND;
-
- if (entry->multivar) {
- git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar");
- return -1;
- }
-
- if (entry->entry->include_depth) {
- git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included");
- return -1;
- }
-
- *out = entry->entry;
-
- return 0;
-}
-
-static void config_iterator_free(git_config_iterator *iter)
-{
- config_entries_iterator *it = (config_entries_iterator *) iter;
- git_config_entries_free(it->entries);
- git__free(it);
-}
-
-static int config_iterator_next(
- git_config_entry **entry,
- git_config_iterator *iter)
-{
- config_entries_iterator *it = (config_entries_iterator *) iter;
-
- if (!it->head)
- return GIT_ITEROVER;
-
- *entry = it->head->entry;
- it->head = it->head->next;
-
- return 0;
-}
-
-int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries)
-{
- config_entries_iterator *it;
-
- it = git__calloc(1, sizeof(config_entries_iterator));
- GIT_ERROR_CHECK_ALLOC(it);
- it->parent.next = config_iterator_next;
- it->parent.free = config_iterator_free;
- it->head = entries->list;
- it->entries = entries;
-
- git_config_entries_incref(entries);
- *out = &it->parent;
-
- return 0;
-}
diff --git a/src/libgit2/config_entries.h b/src/libgit2/config_entries.h
deleted file mode 100644
index 832379e..0000000
--- a/src/libgit2/config_entries.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#include "common.h"
-
-#include "git2/sys/config.h"
-#include "config.h"
-
-typedef struct git_config_entries git_config_entries;
-
-int git_config_entries_new(git_config_entries **out);
-int git_config_entries_dup(git_config_entries **out, git_config_entries *entries);
-int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry);
-void git_config_entries_incref(git_config_entries *entries);
-void git_config_entries_free(git_config_entries *entries);
-/* Add or append the new config option */
-int git_config_entries_append(git_config_entries *entries, git_config_entry *entry);
-int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key);
-int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key);
-int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries);
diff --git a/src/libgit2/config_file.c b/src/libgit2/config_file.c
index 716924d..340e856 100644
--- a/src/libgit2/config_file.c
+++ b/src/libgit2/config_file.c
@@ -13,7 +13,7 @@
#include "array.h"
#include "str.h"
#include "config_backend.h"
-#include "config_entries.h"
+#include "config_list.h"
#include "config_parse.h"
#include "filebuf.h"
#include "regexp.h"
@@ -24,6 +24,8 @@
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
+#define CONFIG_FILE_TYPE "file"
+
typedef struct config_file {
git_futils_filestamp stamp;
unsigned char checksum[GIT_HASH_SHA256_SIZE];
@@ -34,7 +36,7 @@ typedef struct config_file {
typedef struct {
git_config_backend parent;
git_mutex values_mutex;
- git_config_entries *entries;
+ git_config_list *config_list;
const git_repository *repo;
git_config_level_t level;
@@ -50,13 +52,13 @@ typedef struct {
typedef struct {
const git_repository *repo;
config_file *file;
- git_config_entries *entries;
+ git_config_list *config_list;
git_config_level_t level;
unsigned int depth;
} config_file_parse_data;
-static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth);
-static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen);
+static int config_file_read(git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, int depth);
+static int config_file_read_buffer(git_config_list *config_list, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen);
static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value);
static char *escape_value(const char *ptr);
@@ -65,7 +67,7 @@ static char *escape_value(const char *ptr);
* refcount. This is its own function to make sure we use the mutex to
* avoid the map pointer from changing under us.
*/
-static int config_file_entries_take(git_config_entries **out, config_file_backend *b)
+static int config_file_take_list(git_config_list **out, config_file_backend *b)
{
int error;
@@ -74,8 +76,8 @@ static int config_file_entries_take(git_config_entries **out, config_file_backen
return error;
}
- git_config_entries_incref(b->entries);
- *out = b->entries;
+ git_config_list_incref(b->config_list);
+ *out = b->config_list;
git_mutex_unlock(&b->values_mutex);
@@ -106,7 +108,7 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c
b->level = level;
b->repo = repo;
- if ((res = git_config_entries_new(&b->entries)) < 0)
+ if ((res = git_config_list_new(&b->config_list)) < 0)
return res;
if (!git_fs_path_exists(b->file.path))
@@ -121,9 +123,9 @@ static int config_file_open(git_config_backend *cfg, git_config_level_t level, c
if (p_access(b->file.path, R_OK) < 0)
return GIT_ENOTFOUND;
- if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) {
- git_config_entries_free(b->entries);
- b->entries = NULL;
+ if (res < 0 || (res = config_file_read(b->config_list, repo, &b->file, level, 0)) < 0) {
+ git_config_list_free(b->config_list);
+ b->config_list = NULL;
}
return res;
@@ -175,10 +177,10 @@ static void config_file_clear_includes(config_file_backend *cfg)
git_array_clear(cfg->file.includes);
}
-static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries)
+static int config_file_set_entries(git_config_backend *cfg, git_config_list *config_list)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *old = NULL;
+ git_config_list *old = NULL;
int error;
if (b->parent.readonly) {
@@ -191,40 +193,40 @@ static int config_file_set_entries(git_config_backend *cfg, git_config_entries *
goto out;
}
- old = b->entries;
- b->entries = entries;
+ old = b->config_list;
+ b->config_list = config_list;
git_mutex_unlock(&b->values_mutex);
out:
- git_config_entries_free(old);
+ git_config_list_free(old);
return error;
}
static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries = NULL;
+ git_config_list *config_list = NULL;
int error;
config_file_clear_includes(b);
- if ((error = git_config_entries_new(&entries)) < 0 ||
- (error = config_file_read_buffer(entries, b->repo, &b->file,
+ if ((error = git_config_list_new(&config_list)) < 0 ||
+ (error = config_file_read_buffer(config_list, b->repo, &b->file,
b->level, 0, buf, buflen)) < 0 ||
- (error = config_file_set_entries(cfg, entries)) < 0)
+ (error = config_file_set_entries(cfg, config_list)) < 0)
goto out;
- entries = NULL;
+ config_list = NULL;
out:
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
return error;
}
static int config_file_refresh(git_config_backend *cfg)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries = NULL;
+ git_config_list *config_list = NULL;
int error, modified;
if (cfg->readonly)
@@ -238,14 +240,14 @@ static int config_file_refresh(git_config_backend *cfg)
config_file_clear_includes(b);
- if ((error = git_config_entries_new(&entries)) < 0 ||
- (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 ||
- (error = config_file_set_entries(cfg, entries)) < 0)
+ if ((error = git_config_list_new(&config_list)) < 0 ||
+ (error = config_file_read(config_list, b->repo, &b->file, b->level, 0)) < 0 ||
+ (error = config_file_set_entries(cfg, config_list)) < 0)
goto out;
- entries = NULL;
+ config_list = NULL;
out:
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
return (error == GIT_ENOTFOUND) ? 0 : error;
}
@@ -258,7 +260,7 @@ static void config_file_free(git_config_backend *_backend)
return;
config_file_clear(&backend->file);
- git_config_entries_free(backend->entries);
+ git_config_list_free(backend->config_list);
git_mutex_free(&backend->values_mutex);
git__free(backend);
}
@@ -268,19 +270,19 @@ static int config_file_iterator(
struct git_config_backend *backend)
{
config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent);
- git_config_entries *dupped = NULL, *entries = NULL;
+ git_config_list *dupped = NULL, *config_list = NULL;
int error;
if ((error = config_file_refresh(backend)) < 0 ||
- (error = config_file_entries_take(&entries, b)) < 0 ||
- (error = git_config_entries_dup(&dupped, entries)) < 0 ||
- (error = git_config_entries_iterator_new(iter, dupped)) < 0)
+ (error = config_file_take_list(&config_list, b)) < 0 ||
+ (error = git_config_list_dup(&dupped, config_list)) < 0 ||
+ (error = git_config_list_iterator_new(iter, dupped)) < 0)
goto out;
out:
- /* Let iterator delete duplicated entries when it's done */
- git_config_entries_free(entries);
- git_config_entries_free(dupped);
+ /* Let iterator delete duplicated config_list when it's done */
+ git_config_list_free(config_list);
+ git_config_list_free(dupped);
return error;
}
@@ -292,24 +294,24 @@ static int config_file_snapshot(git_config_backend **out, git_config_backend *ba
static int config_file_set(git_config_backend *cfg, const char *name, const char *value)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries;
- git_config_entry *existing;
+ git_config_list *config_list;
+ git_config_list_entry *existing;
char *key, *esc_value = NULL;
int error;
if ((error = git_config__normalize_name(name, &key)) < 0)
return error;
- if ((error = config_file_entries_take(&entries, b)) < 0)
+ if ((error = config_file_take_list(&config_list, b)) < 0)
return error;
/* Check whether we'd be modifying an included or multivar key */
- if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) {
+ if ((error = git_config_list_get_unique(&existing, config_list, key)) < 0) {
if (error != GIT_ENOTFOUND)
goto out;
error = 0;
- } else if ((!existing->value && !value) ||
- (existing->value && value && !strcmp(existing->value, value))) {
+ } else if ((!existing->base.value && !value) ||
+ (existing->base.value && value && !strcmp(existing->base.value, value))) {
/* don't update if old and new values already match */
error = 0;
goto out;
@@ -325,43 +327,34 @@ static int config_file_set(git_config_backend *cfg, const char *name, const char
goto out;
out:
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
git__free(esc_value);
git__free(key);
return error;
}
-/* release the map containing the entry as an equivalent to freeing it */
-static void config_file_entry_free(git_config_entry *entry)
-{
- git_config_entries *entries = (git_config_entries *) entry->payload;
- git_config_entries_free(entries);
-}
-
/*
* Internal function that actually gets the value in string form
*/
static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out)
{
config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries = NULL;
- git_config_entry *entry;
+ git_config_list *config_list = NULL;
+ git_config_list_entry *entry;
int error = 0;
if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0))
return error;
- if ((error = config_file_entries_take(&entries, h)) < 0)
+ if ((error = config_file_take_list(&config_list, h)) < 0)
return error;
- if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
- git_config_entries_free(entries);
+ if ((error = (git_config_list_get(&entry, config_list, key))) < 0) {
+ git_config_list_free(config_list);
return error;
}
- entry->free = config_file_entry_free;
- entry->payload = entries;
- *out = entry;
+ *out = &entry->base;
return 0;
}
@@ -396,29 +389,29 @@ out:
static int config_file_delete(git_config_backend *cfg, const char *name)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries = NULL;
- git_config_entry *entry;
+ git_config_list *config_list = NULL;
+ git_config_list_entry *entry;
char *key = NULL;
int error;
if ((error = git_config__normalize_name(name, &key)) < 0)
goto out;
- if ((error = config_file_entries_take(&entries, b)) < 0)
+ if ((error = config_file_take_list(&config_list, b)) < 0)
goto out;
/* Check whether we'd be modifying an included or multivar key */
- if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) {
+ if ((error = git_config_list_get_unique(&entry, config_list, key)) < 0) {
if (error == GIT_ENOTFOUND)
git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
goto out;
}
- if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0)
+ if ((error = config_file_write(b, name, entry->base.name, NULL, NULL)) < 0)
goto out;
out:
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
git__free(key);
return error;
}
@@ -426,8 +419,8 @@ out:
static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
- git_config_entries *entries = NULL;
- git_config_entry *entry = NULL;
+ git_config_list *config_list = NULL;
+ git_config_list_entry *entry = NULL;
git_regexp preg = GIT_REGEX_INIT;
char *key = NULL;
int result;
@@ -435,10 +428,10 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name
if ((result = git_config__normalize_name(name, &key)) < 0)
goto out;
- if ((result = config_file_entries_take(&entries, b)) < 0)
+ if ((result = config_file_take_list(&config_list, b)) < 0)
goto out;
- if ((result = git_config_entries_get(&entry, entries, key)) < 0) {
+ if ((result = git_config_list_get(&entry, config_list, key)) < 0) {
if (result == GIT_ENOTFOUND)
git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
goto out;
@@ -451,7 +444,7 @@ static int config_file_delete_multivar(git_config_backend *cfg, const char *name
goto out;
out:
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
git__free(key);
git_regexp_dispose(&preg);
return result;
@@ -591,7 +584,7 @@ static int parse_include(config_file_parse_data *parse_data, const char *file)
git_array_init(include->includes);
include->path = git_str_detach(&path);
- result = config_file_read(parse_data->entries, parse_data->repo, include,
+ result = config_file_read(parse_data->config_list, parse_data->repo, include,
parse_data->level, parse_data->depth+1);
if (result == GIT_ENOTFOUND) {
@@ -776,7 +769,7 @@ static int read_on_variable(
{
config_file_parse_data *parse_data = (config_file_parse_data *)data;
git_str buf = GIT_STR_INIT;
- git_config_entry *entry;
+ git_config_list_entry *entry;
const char *c;
int result = 0;
@@ -799,30 +792,45 @@ static int read_on_variable(
if (git_str_oom(&buf))
return -1;
- entry = git__calloc(1, sizeof(git_config_entry));
+ entry = git__calloc(1, sizeof(git_config_list_entry));
GIT_ERROR_CHECK_ALLOC(entry);
- entry->name = git_str_detach(&buf);
- entry->value = var_value ? git__strdup(var_value) : NULL;
- entry->level = parse_data->level;
- entry->include_depth = parse_data->depth;
- if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
+ entry->base.name = git_str_detach(&buf);
+ GIT_ERROR_CHECK_ALLOC(entry->base.name);
+
+ if (var_value) {
+ entry->base.value = git__strdup(var_value);
+ GIT_ERROR_CHECK_ALLOC(entry->base.value);
+ }
+
+ entry->base.backend_type = git_config_list_add_string(parse_data->config_list, CONFIG_FILE_TYPE);
+ GIT_ERROR_CHECK_ALLOC(entry->base.backend_type);
+
+ entry->base.origin_path = git_config_list_add_string(parse_data->config_list, parse_data->file->path);
+ GIT_ERROR_CHECK_ALLOC(entry->base.origin_path);
+
+ entry->base.level = parse_data->level;
+ entry->base.include_depth = parse_data->depth;
+ entry->base.free = git_config_list_entry_free;
+ entry->config_list = parse_data->config_list;
+
+ if ((result = git_config_list_append(parse_data->config_list, entry)) < 0)
return result;
result = 0;
/* Add or append the new config option */
- if (!git__strcmp(entry->name, "include.path"))
- result = parse_include(parse_data, entry->value);
- else if (!git__prefixcmp(entry->name, "includeif.") &&
- !git__suffixcmp(entry->name, ".path"))
- result = parse_conditional_include(parse_data, entry->name, entry->value);
+ if (!git__strcmp(entry->base.name, "include.path"))
+ result = parse_include(parse_data, entry->base.value);
+ else if (!git__prefixcmp(entry->base.name, "includeif.") &&
+ !git__suffixcmp(entry->base.name, ".path"))
+ result = parse_conditional_include(parse_data, entry->base.name, entry->base.value);
return result;
}
static int config_file_read_buffer(
- git_config_entries *entries,
+ git_config_list *config_list,
const git_repository *repo,
config_file *file,
git_config_level_t level,
@@ -851,7 +859,7 @@ static int config_file_read_buffer(
parse_data.repo = repo;
parse_data.file = file;
- parse_data.entries = entries;
+ parse_data.config_list = config_list;
parse_data.level = level;
parse_data.depth = depth;
@@ -862,7 +870,7 @@ out:
}
static int config_file_read(
- git_config_entries *entries,
+ git_config_list *config_list,
const git_repository *repo,
config_file *file,
git_config_level_t level,
@@ -884,7 +892,7 @@ static int config_file_read(
if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA256)) < 0)
goto out;
- if ((error = config_file_read_buffer(entries, repo, file, level, depth,
+ if ((error = config_file_read_buffer(config_list, repo, file, level, depth,
contents.ptr, contents.size)) < 0)
goto out;
diff --git a/src/libgit2/config_list.c b/src/libgit2/config_list.c
new file mode 100644
index 0000000..0b7a4f3
--- /dev/null
+++ b/src/libgit2/config_list.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "config_list.h"
+
+typedef struct config_entry_list {
+ struct config_entry_list *next;
+ struct config_entry_list *last;
+ git_config_list_entry *entry;
+} config_entry_list;
+
+typedef struct {
+ git_config_list_entry *entry;
+ bool multivar;
+} config_entry_map_head;
+
+typedef struct config_list_iterator {
+ git_config_iterator parent;
+ git_config_list *list;
+ config_entry_list *head;
+} config_list_iterator;
+
+struct git_config_list {
+ git_refcount rc;
+
+ /* Interned strings - paths to config files or backend types */
+ git_strmap *strings;
+
+ /* Config entries */
+ git_strmap *map;
+ config_entry_list *entries;
+};
+
+int git_config_list_new(git_config_list **out)
+{
+ git_config_list *config_list;
+
+ config_list = git__calloc(1, sizeof(git_config_list));
+ GIT_ERROR_CHECK_ALLOC(config_list);
+ GIT_REFCOUNT_INC(config_list);
+
+ if (git_strmap_new(&config_list->strings) < 0 ||
+ git_strmap_new(&config_list->map) < 0) {
+ git_strmap_free(config_list->strings);
+ git_strmap_free(config_list->map);
+ git__free(config_list);
+
+ return -1;
+ }
+
+ *out = config_list;
+ return 0;
+}
+
+int git_config_list_dup_entry(git_config_list *config_list, const git_config_entry *entry)
+{
+ git_config_list_entry *duplicated;
+ int error;
+
+ duplicated = git__calloc(1, sizeof(git_config_list_entry));
+ GIT_ERROR_CHECK_ALLOC(duplicated);
+
+ duplicated->base.name = git__strdup(entry->name);
+ GIT_ERROR_CHECK_ALLOC(duplicated->base.name);
+
+ if (entry->value) {
+ duplicated->base.value = git__strdup(entry->value);
+ GIT_ERROR_CHECK_ALLOC(duplicated->base.value);
+ }
+
+ duplicated->base.backend_type = git_config_list_add_string(config_list, entry->backend_type);
+ GIT_ERROR_CHECK_ALLOC(duplicated->base.backend_type);
+
+ if (entry->origin_path) {
+ duplicated->base.origin_path = git_config_list_add_string(config_list, entry->origin_path);
+ GIT_ERROR_CHECK_ALLOC(duplicated->base.origin_path);
+ }
+
+ duplicated->base.level = entry->level;
+ duplicated->base.include_depth = entry->include_depth;
+ duplicated->base.free = git_config_list_entry_free;
+ duplicated->config_list = config_list;
+
+ if ((error = git_config_list_append(config_list, duplicated)) < 0)
+ goto out;
+
+out:
+ if (error && duplicated) {
+ git__free((char *) duplicated->base.name);
+ git__free((char *) duplicated->base.value);
+ git__free(duplicated);
+ }
+ return error;
+}
+
+int git_config_list_dup(git_config_list **out, git_config_list *config_list)
+{
+ git_config_list *result = NULL;
+ config_entry_list *head;
+ int error;
+
+ if ((error = git_config_list_new(&result)) < 0)
+ goto out;
+
+ for (head = config_list->entries; head; head = head->next)
+ if ((git_config_list_dup_entry(result, &head->entry->base)) < 0)
+ goto out;
+
+ *out = result;
+ result = NULL;
+
+out:
+ git_config_list_free(result);
+ return error;
+}
+
+void git_config_list_incref(git_config_list *config_list)
+{
+ GIT_REFCOUNT_INC(config_list);
+}
+
+static void config_list_free(git_config_list *config_list)
+{
+ config_entry_list *entry_list = NULL, *next;
+ config_entry_map_head *head;
+ char *str;
+
+ git_strmap_foreach_value(config_list->strings, str, {
+ git__free(str);
+ });
+ git_strmap_free(config_list->strings);
+
+ git_strmap_foreach_value(config_list->map, head, {
+ git__free((char *) head->entry->base.name);
+ git__free(head);
+ });
+ git_strmap_free(config_list->map);
+
+ entry_list = config_list->entries;
+ while (entry_list != NULL) {
+ next = entry_list->next;
+ git__free((char *) entry_list->entry->base.value);
+ git__free(entry_list->entry);
+ git__free(entry_list);
+ entry_list = next;
+ }
+
+ git__free(config_list);
+}
+
+void git_config_list_free(git_config_list *config_list)
+{
+ if (config_list)
+ GIT_REFCOUNT_DEC(config_list, config_list_free);
+}
+
+int git_config_list_append(git_config_list *config_list, git_config_list_entry *entry)
+{
+ config_entry_list *list_head;
+ config_entry_map_head *map_head;
+
+ if ((map_head = git_strmap_get(config_list->map, entry->base.name)) != NULL) {
+ map_head->multivar = true;
+ /*
+ * This is a micro-optimization for configuration files
+ * with a lot of same keys. As for multivars the entry's
+ * key will be the same for all list, we can just free
+ * all except the first entry's name and just re-use it.
+ */
+ git__free((char *) entry->base.name);
+ entry->base.name = map_head->entry->base.name;
+ } else {
+ map_head = git__calloc(1, sizeof(*map_head));
+ if ((git_strmap_set(config_list->map, entry->base.name, map_head)) < 0)
+ return -1;
+ }
+ map_head->entry = entry;
+
+ list_head = git__calloc(1, sizeof(config_entry_list));
+ GIT_ERROR_CHECK_ALLOC(list_head);
+ list_head->entry = entry;
+
+ if (config_list->entries)
+ config_list->entries->last->next = list_head;
+ else
+ config_list->entries = list_head;
+ config_list->entries->last = list_head;
+
+ return 0;
+}
+
+int git_config_list_get(git_config_list_entry **out, git_config_list *config_list, const char *key)
+{
+ config_entry_map_head *entry;
+
+ if ((entry = git_strmap_get(config_list->map, key)) == NULL)
+ return GIT_ENOTFOUND;
+
+ *out = entry->entry;
+ return 0;
+}
+
+int git_config_list_get_unique(git_config_list_entry **out, git_config_list *config_list, const char *key)
+{
+ config_entry_map_head *entry;
+
+ if ((entry = git_strmap_get(config_list->map, key)) == NULL)
+ return GIT_ENOTFOUND;
+
+ if (entry->multivar) {
+ git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar");
+ return -1;
+ }
+
+ if (entry->entry->base.include_depth) {
+ git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included");
+ return -1;
+ }
+
+ *out = entry->entry;
+ return 0;
+}
+
+static void config_iterator_free(git_config_iterator *iter)
+{
+ config_list_iterator *it = (config_list_iterator *) iter;
+ git_config_list_free(it->list);
+ git__free(it);
+}
+
+static int config_iterator_next(
+ git_config_entry **entry,
+ git_config_iterator *iter)
+{
+ config_list_iterator *it = (config_list_iterator *) iter;
+
+ if (!it->head)
+ return GIT_ITEROVER;
+
+ *entry = &it->head->entry->base;
+ it->head = it->head->next;
+
+ return 0;
+}
+
+int git_config_list_iterator_new(git_config_iterator **out, git_config_list *config_list)
+{
+ config_list_iterator *it;
+
+ it = git__calloc(1, sizeof(config_list_iterator));
+ GIT_ERROR_CHECK_ALLOC(it);
+ it->parent.next = config_iterator_next;
+ it->parent.free = config_iterator_free;
+ it->head = config_list->entries;
+ it->list = config_list;
+
+ git_config_list_incref(config_list);
+ *out = &it->parent;
+
+ return 0;
+}
+
+/* release the map containing the entry as an equivalent to freeing it */
+void git_config_list_entry_free(git_config_entry *e)
+{
+ git_config_list_entry *entry = (git_config_list_entry *)e;
+ git_config_list_free(entry->config_list);
+}
+
+const char *git_config_list_add_string(
+ git_config_list *config_list,
+ const char *str)
+{
+ const char *s;
+
+ if ((s = git_strmap_get(config_list->strings, str)) != NULL)
+ return s;
+
+ if ((s = git__strdup(str)) == NULL ||
+ git_strmap_set(config_list->strings, s, (void *)s) < 0)
+ return NULL;
+
+ return s;
+}
diff --git a/src/libgit2/config_list.h b/src/libgit2/config_list.h
new file mode 100644
index 0000000..023bca1
--- /dev/null
+++ b/src/libgit2/config_list.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+
+#include "git2/sys/config.h"
+#include "config.h"
+
+typedef struct git_config_list git_config_list;
+
+typedef struct {
+ git_config_entry base;
+ git_config_list *config_list;
+} git_config_list_entry;
+
+int git_config_list_new(git_config_list **out);
+int git_config_list_dup(git_config_list **out, git_config_list *list);
+int git_config_list_dup_entry(git_config_list *list, const git_config_entry *entry);
+void git_config_list_incref(git_config_list *list);
+void git_config_list_free(git_config_list *list);
+/* Add or append the new config option */
+int git_config_list_append(git_config_list *list, git_config_list_entry *entry);
+int git_config_list_get(git_config_list_entry **out, git_config_list *list, const char *key);
+int git_config_list_get_unique(git_config_list_entry **out, git_config_list *list, const char *key);
+int git_config_list_iterator_new(git_config_iterator **out, git_config_list *list);
+const char *git_config_list_add_string(git_config_list *list, const char *str);
+
+void git_config_list_entry_free(git_config_entry *entry);
diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c
index 560229c..406aa83 100644
--- a/src/libgit2/config_mem.c
+++ b/src/libgit2/config_mem.c
@@ -9,16 +9,29 @@
#include "config_backend.h"
#include "config_parse.h"
-#include "config_entries.h"
+#include "config_list.h"
+#include "strlist.h"
typedef struct {
git_config_backend parent;
- git_config_entries *entries;
+
+ char *backend_type;
+ char *origin_path;
+
+ git_config_list *config_list;
+
+ /* Configuration data in the config file format */
git_str cfg;
+
+ /* Array of key=value pairs */
+ char **values;
+ size_t values_len;
} config_memory_backend;
typedef struct {
- git_config_entries *entries;
+ const char *backend_type;
+ const char *origin_path;
+ git_config_list *config_list;
git_config_level_t level;
} config_memory_parse_data;
@@ -39,7 +52,7 @@ static int read_variable_cb(
{
config_memory_parse_data *parse_data = (config_memory_parse_data *) payload;
git_str buf = GIT_STR_INIT;
- git_config_entry *entry;
+ git_config_list_entry *entry;
const char *c;
int result;
@@ -62,35 +75,46 @@ static int read_variable_cb(
if (git_str_oom(&buf))
return -1;
- entry = git__calloc(1, sizeof(git_config_entry));
+ entry = git__calloc(1, sizeof(git_config_list_entry));
GIT_ERROR_CHECK_ALLOC(entry);
- entry->name = git_str_detach(&buf);
- entry->value = var_value ? git__strdup(var_value) : NULL;
- entry->level = parse_data->level;
- entry->include_depth = 0;
-
- if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
+ entry->base.name = git_str_detach(&buf);
+ entry->base.value = var_value ? git__strdup(var_value) : NULL;
+ entry->base.level = parse_data->level;
+ entry->base.include_depth = 0;
+ entry->base.backend_type = parse_data->backend_type;
+ entry->base.origin_path = parse_data->origin_path;
+ entry->base.free = git_config_list_entry_free;
+ entry->config_list = parse_data->config_list;
+
+ if ((result = git_config_list_append(parse_data->config_list, entry)) < 0)
return result;
return result;
}
-static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo)
+static int parse_config(
+ config_memory_backend *memory_backend,
+ git_config_level_t level)
{
- config_memory_backend *memory_backend = (config_memory_backend *) backend;
git_config_parser parser = GIT_PARSE_CTX_INIT;
config_memory_parse_data parse_data;
int error;
- GIT_UNUSED(repo);
-
- if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr,
- memory_backend->cfg.size)) < 0)
+ if ((error = git_config_parser_init(&parser, "in-memory",
+ memory_backend->cfg.ptr, memory_backend->cfg.size)) < 0)
goto out;
- parse_data.entries = memory_backend->entries;
+
+ parse_data.backend_type = git_config_list_add_string(
+ memory_backend->config_list, memory_backend->backend_type);
+ parse_data.origin_path = memory_backend->origin_path ?
+ git_config_list_add_string(memory_backend->config_list,
+ memory_backend->origin_path) :
+ NULL;
+ parse_data.config_list = memory_backend->config_list;
parse_data.level = level;
- if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0)
+ if ((error = git_config_parse(&parser, NULL, read_variable_cb,
+ NULL, NULL, &parse_data)) < 0)
goto out;
out:
@@ -98,10 +122,85 @@ out:
return error;
}
+static int parse_values(
+ config_memory_backend *memory_backend,
+ git_config_level_t level)
+{
+ git_config_list_entry *entry;
+ const char *eql, *backend_type, *origin_path;
+ size_t name_len, i;
+
+ backend_type = git_config_list_add_string(
+ memory_backend->config_list, memory_backend->backend_type);
+ GIT_ERROR_CHECK_ALLOC(backend_type);
+
+ origin_path = memory_backend->origin_path ?
+ git_config_list_add_string(memory_backend->config_list,
+ memory_backend->origin_path) :
+ NULL;
+
+ for (i = 0; i < memory_backend->values_len; i++) {
+ eql = strchr(memory_backend->values[i], '=');
+ name_len = eql - memory_backend->values[i];
+
+ if (name_len == 0) {
+ git_error_set(GIT_ERROR_CONFIG, "empty config key");
+ return -1;
+ }
+
+ entry = git__calloc(1, sizeof(git_config_list_entry));
+ GIT_ERROR_CHECK_ALLOC(entry);
+
+ entry->base.name = git__strndup(memory_backend->values[i], name_len);
+ GIT_ERROR_CHECK_ALLOC(entry->base.name);
+
+ if (eql) {
+ entry->base.value = git__strdup(eql + 1);
+ GIT_ERROR_CHECK_ALLOC(entry->base.value);
+ }
+
+ entry->base.level = level;
+ entry->base.include_depth = 0;
+ entry->base.backend_type = backend_type;
+ entry->base.origin_path = origin_path;
+ entry->base.free = git_config_list_entry_free;
+ entry->config_list = memory_backend->config_list;
+
+ if (git_config_list_append(memory_backend->config_list, entry) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo)
+{
+ config_memory_backend *memory_backend = (config_memory_backend *) backend;
+
+ GIT_UNUSED(repo);
+
+ if (memory_backend->cfg.size > 0 &&
+ parse_config(memory_backend, level) < 0)
+ return -1;
+
+ if (memory_backend->values_len > 0 &&
+ parse_values(memory_backend, level) < 0)
+ return -1;
+
+ return 0;
+}
+
static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out)
{
config_memory_backend *memory_backend = (config_memory_backend *) backend;
- return git_config_entries_get(out, memory_backend->entries, key);
+ git_config_list_entry *entry;
+ int error;
+
+ if ((error = git_config_list_get(&entry, memory_backend->config_list, key)) != 0)
+ return error;
+
+ *out = &entry->base;
+ return 0;
}
static int config_memory_iterator(
@@ -109,18 +208,18 @@ static int config_memory_iterator(
git_config_backend *backend)
{
config_memory_backend *memory_backend = (config_memory_backend *) backend;
- git_config_entries *entries;
+ git_config_list *config_list;
int error;
- if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0)
+ if ((error = git_config_list_dup(&config_list, memory_backend->config_list)) < 0)
goto out;
- if ((error = git_config_entries_iterator_new(iter, entries)) < 0)
+ if ((error = git_config_list_iterator_new(iter, config_list)) < 0)
goto out;
out:
- /* Let iterator delete duplicated entries when it's done */
- git_config_entries_free(entries);
+ /* Let iterator delete duplicated config_list when it's done */
+ git_config_list_free(config_list);
return error;
}
@@ -177,28 +276,24 @@ static void config_memory_free(git_config_backend *_backend)
if (backend == NULL)
return;
- git_config_entries_free(backend->entries);
+ git__free(backend->origin_path);
+ git__free(backend->backend_type);
+ git_config_list_free(backend->config_list);
+ git_strlist_free(backend->values, backend->values_len);
git_str_dispose(&backend->cfg);
git__free(backend);
}
-int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len)
+static config_memory_backend *config_backend_new(
+ git_config_backend_memory_options *opts)
{
config_memory_backend *backend;
- backend = git__calloc(1, sizeof(config_memory_backend));
- GIT_ERROR_CHECK_ALLOC(backend);
+ if ((backend = git__calloc(1, sizeof(config_memory_backend))) == NULL)
+ return NULL;
- if (git_config_entries_new(&backend->entries) < 0) {
- git__free(backend);
- return -1;
- }
-
- if (git_str_set(&backend->cfg, cfg, len) < 0) {
- git_config_entries_free(backend->entries);
- git__free(backend);
- return -1;
- }
+ if (git_config_list_new(&backend->config_list) < 0)
+ goto on_error;
backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
backend->parent.readonly = 1;
@@ -214,7 +309,66 @@ int git_config_backend_from_string(git_config_backend **out, const char *cfg, si
backend->parent.snapshot = git_config_backend_snapshot;
backend->parent.free = config_memory_free;
+ backend->backend_type = git__strdup(opts && opts->backend_type ?
+ opts->backend_type : "in-memory");
+
+ if (backend->backend_type == NULL)
+ goto on_error;
+
+ if (opts && opts->origin_path &&
+ (backend->origin_path = git__strdup(opts->origin_path)) == NULL)
+ goto on_error;
+
+ return backend;
+
+on_error:
+ git_config_list_free(backend->config_list);
+ git__free(backend->origin_path);
+ git__free(backend->backend_type);
+ git__free(backend);
+ return NULL;
+}
+
+int git_config_backend_from_string(
+ git_config_backend **out,
+ const char *cfg,
+ size_t len,
+ git_config_backend_memory_options *opts)
+{
+ config_memory_backend *backend;
+
+ if ((backend = config_backend_new(opts)) == NULL)
+ return -1;
+
+ if (git_str_set(&backend->cfg, cfg, len) < 0) {
+ git_config_list_free(backend->config_list);
+ git__free(backend);
+ return -1;
+ }
+
*out = (git_config_backend *)backend;
+ return 0;
+}
+
+int git_config_backend_from_values(
+ git_config_backend **out,
+ const char **values,
+ size_t len,
+ git_config_backend_memory_options *opts)
+{
+ config_memory_backend *backend;
+ if ((backend = config_backend_new(opts)) == NULL)
+ return -1;
+
+ if (git_strlist_copy(&backend->values, values, len) < 0) {
+ git_config_list_free(backend->config_list);
+ git__free(backend);
+ return -1;
+ }
+
+ backend->values_len = len;
+
+ *out = (git_config_backend *)backend;
return 0;
}
diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c
index 0693136..7f933db 100644
--- a/src/libgit2/config_parse.c
+++ b/src/libgit2/config_parse.c
@@ -25,9 +25,9 @@ static void set_parse_error(git_config_parser *reader, int col, const char *erro
}
-GIT_INLINE(int) config_keychar(int c)
+GIT_INLINE(int) config_keychar(char c)
{
- return isalnum(c) || c == '-';
+ return git__isalnum(c) || c == '-';
}
static int strip_comments(char *line, int in_quotes)
@@ -158,9 +158,10 @@ end_error:
static int parse_section_header(git_config_parser *reader, char **section_out)
{
char *name, *name_end;
- int name_length, c, pos;
+ int name_length, pos;
int result;
char *line;
+ char c;
size_t line_len;
git_parse_advance_ws(&reader->ctx);
@@ -279,8 +280,7 @@ static int skip_bom(git_parse_ctx *parser)
*/
/* '\"' -> '"' etc */
-static int unescape_line(
- char **out, bool *is_multi, const char *ptr, int quote_count)
+static int unescape_line(char **out, bool *is_multi, const char *ptr, int *quote_count)
{
char *str, *fixed, *esc;
size_t ptr_len = strlen(ptr), alloc_len;
@@ -296,7 +296,8 @@ static int unescape_line(
while (*ptr != '\0') {
if (*ptr == '"') {
- quote_count++;
+ if (quote_count)
+ (*quote_count)++;
} else if (*ptr != '\\') {
*fixed++ = *ptr;
} else {
@@ -358,7 +359,7 @@ static int parse_multiline_variable(git_config_parser *reader, git_str *value, i
goto next;
if ((error = unescape_line(&proc_line, &multiline,
- line, in_quotes)) < 0)
+ line, &in_quotes)) < 0)
goto out;
/* Add this line to the multiline var */
@@ -382,7 +383,7 @@ out:
GIT_INLINE(bool) is_namechar(char c)
{
- return isalnum(c) || c == '-';
+ return git__isalnum(c) || c == '-';
}
static int parse_name(
@@ -445,7 +446,7 @@ static int parse_variable(git_config_parser *reader, char **var_name, char **var
while (git__isspace(value_start[0]))
value_start++;
- if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0)
+ if ((error = unescape_line(&value, &multiline, value_start, NULL)) < 0)
goto out;
if (multiline) {
diff --git a/src/libgit2/config_snapshot.c b/src/libgit2/config_snapshot.c
index e295d2f..d8b8733 100644
--- a/src/libgit2/config_snapshot.c
+++ b/src/libgit2/config_snapshot.c
@@ -8,12 +8,12 @@
#include "config_backend.h"
#include "config.h"
-#include "config_entries.h"
+#include "config_list.h"
typedef struct {
git_config_backend parent;
git_mutex values_mutex;
- git_config_entries *entries;
+ git_config_list *config_list;
git_config_backend *source;
} config_snapshot_backend;
@@ -28,31 +28,24 @@ static int config_snapshot_iterator(
struct git_config_backend *backend)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent);
- git_config_entries *entries = NULL;
+ git_config_list *config_list = NULL;
int error;
- if ((error = git_config_entries_dup(&entries, b->entries)) < 0 ||
- (error = git_config_entries_iterator_new(iter, entries)) < 0)
+ if ((error = git_config_list_dup(&config_list, b->config_list)) < 0 ||
+ (error = git_config_list_iterator_new(iter, config_list)) < 0)
goto out;
out:
- /* Let iterator delete duplicated entries when it's done */
- git_config_entries_free(entries);
+ /* Let iterator delete duplicated config_list when it's done */
+ git_config_list_free(config_list);
return error;
}
-/* release the map containing the entry as an equivalent to freeing it */
-static void config_snapshot_entry_free(git_config_entry *entry)
-{
- git_config_entries *entries = (git_config_entries *) entry->payload;
- git_config_entries_free(entries);
-}
-
static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent);
- git_config_entries *entries = NULL;
- git_config_entry *entry;
+ git_config_list *config_list = NULL;
+ git_config_list_entry *entry;
int error = 0;
if (git_mutex_lock(&b->values_mutex) < 0) {
@@ -60,19 +53,16 @@ static int config_snapshot_get(git_config_backend *cfg, const char *key, git_con
return -1;
}
- entries = b->entries;
- git_config_entries_incref(entries);
+ config_list = b->config_list;
+ git_config_list_incref(config_list);
git_mutex_unlock(&b->values_mutex);
- if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
- git_config_entries_free(entries);
+ if ((error = (git_config_list_get(&entry, config_list, key))) < 0) {
+ git_config_list_free(config_list);
return error;
}
- entry->free = config_snapshot_entry_free;
- entry->payload = entries;
- *out = entry;
-
+ *out = &entry->base;
return 0;
}
@@ -135,7 +125,7 @@ static void config_snapshot_free(git_config_backend *_backend)
if (backend == NULL)
return;
- git_config_entries_free(backend->entries);
+ git_config_list_free(backend->config_list);
git_mutex_free(&backend->values_mutex);
git__free(backend);
}
@@ -143,7 +133,7 @@ static void config_snapshot_free(git_config_backend *_backend)
static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent);
- git_config_entries *entries = NULL;
+ git_config_list *config_list = NULL;
git_config_iterator *it = NULL;
git_config_entry *entry;
int error;
@@ -152,12 +142,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve
GIT_UNUSED(level);
GIT_UNUSED(repo);
- if ((error = git_config_entries_new(&entries)) < 0 ||
+ if ((error = git_config_list_new(&config_list)) < 0 ||
(error = b->source->iterator(&it, b->source)) < 0)
goto out;
while ((error = git_config_next(&entry, it)) == 0)
- if ((error = git_config_entries_dup_entry(entries, entry)) < 0)
+ if ((error = git_config_list_dup_entry(config_list, entry)) < 0)
goto out;
if (error < 0) {
@@ -166,12 +156,12 @@ static int config_snapshot_open(git_config_backend *cfg, git_config_level_t leve
error = 0;
}
- b->entries = entries;
+ b->config_list = config_list;
out:
git_config_iterator_free(it);
if (error)
- git_config_entries_free(entries);
+ git_config_list_free(config_list);
return error;
}
diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c
index 32c9368..daeefca 100644
--- a/src/libgit2/diff_print.c
+++ b/src/libgit2/diff_print.c
@@ -29,6 +29,7 @@ typedef struct {
const char *new_prefix;
uint32_t flags;
int id_strlen;
+ unsigned int sent_file_header;
git_oid_t oid_type;
int (*strcomp)(const char *, const char *);
@@ -579,6 +580,30 @@ static int diff_print_patch_file_binary(
return error;
}
+GIT_INLINE(int) should_force_header(const git_diff_delta *delta)
+{
+ if (delta->old_file.mode != delta->new_file.mode)
+ return 1;
+
+ if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED)
+ return 1;
+
+ return 0;
+}
+
+GIT_INLINE(int) flush_file_header(const git_diff_delta *delta, diff_print_info *pi)
+{
+ if (pi->sent_file_header)
+ return 0;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_str_cstr(pi->buf);
+ pi->line.content_len = git_str_len(pi->buf);
+ pi->sent_file_header = 1;
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
@@ -609,15 +634,22 @@ static int diff_print_patch_file(
(pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
return 0;
+ pi->sent_file_header = 0;
+
if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx,
id_strlen, print_index)) < 0)
return error;
- pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
- pi->line.content = git_str_cstr(pi->buf);
- pi->line.content_len = git_str_len(pi->buf);
+ /*
+ * pi->buf now contains the file header data. Go ahead and send it
+ * if there's useful data in there, like similarity. Otherwise, we
+ * should queue it to send when we see the first hunk. This prevents
+ * us from sending a header when all hunks were ignored.
+ */
+ if (should_force_header(delta) && (error = flush_file_header(delta, pi)) < 0)
+ return error;
- return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+ return 0;
}
static int diff_print_patch_binary(
@@ -632,6 +664,9 @@ static int diff_print_patch_binary(
pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
int error;
+ if ((error = flush_file_header(delta, pi)) < 0)
+ return error;
+
git_str_clear(pi->buf);
if ((error = diff_print_patch_file_binary(
@@ -651,10 +686,14 @@ static int diff_print_patch_hunk(
void *data)
{
diff_print_info *pi = data;
+ int error;
if (S_ISDIR(d->new_file.mode))
return 0;
+ if ((error = flush_file_header(d, pi)) < 0)
+ return error;
+
pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
pi->line.content = h->header;
pi->line.content_len = h->header_len;
@@ -669,10 +708,14 @@ static int diff_print_patch_line(
void *data)
{
diff_print_info *pi = data;
+ int error;
if (S_ISDIR(delta->new_file.mode))
return 0;
+ if ((error = flush_file_header(delta, pi)) < 0)
+ return error;
+
return pi->print_cb(delta, hunk, line, pi->payload);
}
diff --git a/src/libgit2/diff_tform.c b/src/libgit2/diff_tform.c
index 4a156c7..9fa3cef 100644
--- a/src/libgit2/diff_tform.c
+++ b/src/libgit2/diff_tform.c
@@ -653,6 +653,23 @@ static int calc_self_similarity(
return 0;
}
+static void handle_non_blob(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that are blobs */
+ if (GIT_MODE_ISBLOB(delta->old_file.mode))
+ return;
+
+ /* honor "remove unmodified" flag for non-blobs (eg submodules) */
+ if (delta->status == GIT_DELTA_UNMODIFIED &&
+ FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED))
+ delta->flags |= GIT_DIFF_FLAG__TO_DELETE;
+}
+
static bool is_rename_target(
git_diff *diff,
const git_diff_find_options *opts,
@@ -810,7 +827,8 @@ int git_diff_find_similar(
git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
size_t num_deltas, num_srcs = 0, num_tgts = 0;
size_t tried_srcs = 0, tried_tgts = 0;
- size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
+ size_t num_rewrites = 0, num_updates = 0, num_bumped = 0,
+ num_to_delete = 0;
size_t sigcache_size;
void **sigcache = NULL; /* cache of similarity metric file signatures */
diff_find_match *tgt2src = NULL;
@@ -844,6 +862,8 @@ int git_diff_find_similar(
* mark them for splitting if break-rewrites is enabled
*/
git_vector_foreach(&diff->deltas, t, tgt) {
+ handle_non_blob(diff, &opts, t);
+
if (is_rename_source(diff, &opts, t, sigcache))
++num_srcs;
@@ -852,11 +872,14 @@ int git_diff_find_similar(
if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
num_rewrites++;
+
+ if ((tgt->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ num_to_delete++;
}
- /* if there are no candidate srcs or tgts, we're done */
+ /* If there are no candidate srcs or tgts, no need to find matches */
if (!num_srcs || !num_tgts)
- goto cleanup;
+ goto split_and_delete;
src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
GIT_ERROR_CHECK_ALLOC(src2tgt);
@@ -1093,15 +1116,20 @@ find_best_matches:
}
}
+split_and_delete:
/*
* Actually split and delete entries as needed
*/
- if (num_rewrites > 0 || num_updates > 0)
+ if (num_rewrites > 0 || num_updates > 0 || num_to_delete > 0) {
+ size_t apply_len = diff->deltas.length -
+ num_rewrites - num_to_delete;
+
error = apply_splits_and_deletes(
- diff, diff->deltas.length - num_rewrites,
+ diff, apply_len,
FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) &&
!FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY));
+ }
cleanup:
git__free(tgt2src);
diff --git a/src/libgit2/errors.c b/src/libgit2/errors.c
deleted file mode 100644
index 2e58948..0000000
--- a/src/libgit2/errors.c
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#include "common.h"
-
-#include "threadstate.h"
-#include "posix.h"
-#include "str.h"
-#include "libgit2.h"
-
-/********************************************
- * New error handling
- ********************************************/
-
-static git_error oom_error = {
- "Out of memory",
- GIT_ERROR_NOMEMORY
-};
-
-static git_error uninitialized_error = {
- "libgit2 has not been initialized; you must call git_libgit2_init",
- GIT_ERROR_INVALID
-};
-
-static git_error tlsdata_error = {
- "thread-local data initialization failure",
- GIT_ERROR
-};
-
-static void set_error_from_buffer(int error_class)
-{
- git_threadstate *threadstate = git_threadstate_get();
- git_error *error;
- git_str *buf;
-
- if (!threadstate)
- return;
-
- error = &threadstate->error_t;
- buf = &threadstate->error_buf;
-
- error->message = buf->ptr;
- error->klass = error_class;
-
- threadstate->last_error = error;
-}
-
-static void set_error(int error_class, char *string)
-{
- git_threadstate *threadstate = git_threadstate_get();
- git_str *buf;
-
- if (!threadstate)
- return;
-
- buf = &threadstate->error_buf;
-
- git_str_clear(buf);
-
- if (string) {
- git_str_puts(buf, string);
- git__free(string);
- }
-
- set_error_from_buffer(error_class);
-}
-
-void git_error_set_oom(void)
-{
- git_threadstate *threadstate = git_threadstate_get();
-
- if (!threadstate)
- return;
-
- threadstate->last_error = &oom_error;
-}
-
-void git_error_set(int error_class, const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- git_error_vset(error_class, fmt, ap);
- va_end(ap);
-}
-
-void git_error_vset(int error_class, const char *fmt, va_list ap)
-{
-#ifdef GIT_WIN32
- DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0;
-#endif
-
- git_threadstate *threadstate = git_threadstate_get();
- int error_code = (error_class == GIT_ERROR_OS) ? errno : 0;
- git_str *buf;
-
- if (!threadstate)
- return;
-
- buf = &threadstate->error_buf;
-
- git_str_clear(buf);
-
- if (fmt) {
- git_str_vprintf(buf, fmt, ap);
- if (error_class == GIT_ERROR_OS)
- git_str_PUTS(buf, ": ");
- }
-
- if (error_class == GIT_ERROR_OS) {
-#ifdef GIT_WIN32
- char *win32_error = git_win32_get_error_message(win32_error_code);
- if (win32_error) {
- git_str_puts(buf, win32_error);
- git__free(win32_error);
-
- SetLastError(0);
- }
- else
-#endif
- if (error_code)
- git_str_puts(buf, strerror(error_code));
-
- if (error_code)
- errno = 0;
- }
-
- if (!git_str_oom(buf))
- set_error_from_buffer(error_class);
-}
-
-int git_error_set_str(int error_class, const char *string)
-{
- git_threadstate *threadstate = git_threadstate_get();
- git_str *buf;
-
- GIT_ASSERT_ARG(string);
-
- if (!threadstate)
- return -1;
-
- buf = &threadstate->error_buf;
-
- git_str_clear(buf);
- git_str_puts(buf, string);
-
- if (git_str_oom(buf))
- return -1;
-
- set_error_from_buffer(error_class);
- return 0;
-}
-
-void git_error_clear(void)
-{
- git_threadstate *threadstate = git_threadstate_get();
-
- if (!threadstate)
- return;
-
- if (threadstate->last_error != NULL) {
- set_error(0, NULL);
- threadstate->last_error = NULL;
- }
-
- errno = 0;
-#ifdef GIT_WIN32
- SetLastError(0);
-#endif
-}
-
-const git_error *git_error_last(void)
-{
- git_threadstate *threadstate;
-
- /* If the library is not initialized, return a static error. */
- if (!git_libgit2_init_count())
- return &uninitialized_error;
-
- if ((threadstate = git_threadstate_get()) == NULL)
- return &tlsdata_error;
-
- return threadstate->last_error;
-}
-
-int git_error_state_capture(git_error_state *state, int error_code)
-{
- git_threadstate *threadstate = git_threadstate_get();
- git_error *error;
- git_str *error_buf;
-
- if (!threadstate)
- return -1;
-
- error = threadstate->last_error;
- error_buf = &threadstate->error_buf;
-
- memset(state, 0, sizeof(git_error_state));
-
- if (!error_code)
- return 0;
-
- state->error_code = error_code;
- state->oom = (error == &oom_error);
-
- if (error) {
- state->error_msg.klass = error->klass;
-
- if (state->oom)
- state->error_msg.message = oom_error.message;
- else
- state->error_msg.message = git_str_detach(error_buf);
- }
-
- git_error_clear();
- return error_code;
-}
-
-int git_error_state_restore(git_error_state *state)
-{
- int ret = 0;
-
- git_error_clear();
-
- if (state && state->error_msg.message) {
- if (state->oom)
- git_error_set_oom();
- else
- set_error(state->error_msg.klass, state->error_msg.message);
-
- ret = state->error_code;
- memset(state, 0, sizeof(git_error_state));
- }
-
- return ret;
-}
-
-void git_error_state_free(git_error_state *state)
-{
- if (!state)
- return;
-
- if (!state->oom)
- git__free(state->error_msg.message);
-
- memset(state, 0, sizeof(git_error_state));
-}
-
-int git_error_system_last(void)
-{
-#ifdef GIT_WIN32
- return GetLastError();
-#else
- return errno;
-#endif
-}
-
-void git_error_system_set(int code)
-{
-#ifdef GIT_WIN32
- SetLastError(code);
-#else
- errno = code;
-#endif
-}
-
-/* Deprecated error values and functions */
-
-#ifndef GIT_DEPRECATE_HARD
-const git_error *giterr_last(void)
-{
- return git_error_last();
-}
-
-void giterr_clear(void)
-{
- git_error_clear();
-}
-
-void giterr_set_str(int error_class, const char *string)
-{
- git_error_set_str(error_class, string);
-}
-
-void giterr_set_oom(void)
-{
- git_error_set_oom();
-}
-#endif
diff --git a/src/libgit2/fetch.c b/src/libgit2/fetch.c
index d74abb4..8e2660f 100644
--- a/src/libgit2/fetch.c
+++ b/src/libgit2/fetch.c
@@ -60,9 +60,11 @@ static int mark_local(git_remote *remote)
git_vector_foreach(&remote->refs, i, head) {
/* If we have the object, mark it so we don't ask for it.
- However if we are unshallowing, we need to ask for it
- even though the head exists locally. */
- if (remote->nego.depth != INT_MAX && git_odb_exists(odb, &head->oid))
+ However if we are unshallowing or changing history
+ depth, we need to ask for it even though the head
+ exists locally. */
+ if (remote->nego.depth == GIT_FETCH_DEPTH_FULL &&
+ git_odb_exists(odb, &head->oid))
head->local = 1;
else
remote->need_pack = 1;
diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c
index 80a3cae..fdfc409 100644
--- a/src/libgit2/filter.c
+++ b/src/libgit2/filter.c
@@ -908,7 +908,7 @@ static int buffered_stream_close(git_writestream *s)
{
struct buffered_stream *buffered_stream = (struct buffered_stream *)s;
git_str *writebuf;
- git_error_state error_state = {0};
+ git_error *last_error;
int error;
GIT_ASSERT_ARG(buffered_stream);
@@ -946,9 +946,9 @@ static int buffered_stream_close(git_writestream *s)
} else {
/* close stream before erroring out taking care
* to preserve the original error */
- git_error_state_capture(&error_state, error);
+ git_error_save(&last_error);
buffered_stream->target->close(buffered_stream->target);
- git_error_state_restore(&error_state);
+ git_error_restore(last_error);
return error;
}
diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc
index d273afd..b94ecaf 100644
--- a/src/libgit2/git2.rc
+++ b/src/libgit2/git2.rc
@@ -10,7 +10,7 @@
#endif
#ifndef LIBGIT2_COMMENTS
-# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/"
+# define LIBGIT2_COMMENTS "For more information visit https://libgit2.org/"
#endif
#ifdef __GNUC__
diff --git a/src/libgit2/index.c b/src/libgit2/index.c
index ccb3823..670fbc5 100644
--- a/src/libgit2/index.c
+++ b/src/libgit2/index.c
@@ -1612,15 +1612,17 @@ int git_index_add_bypath(git_index *index, const char *path)
if (ret == GIT_EDIRECTORY) {
git_submodule *sm;
- git_error_state err;
+ git_error *last_error;
- git_error_state_capture(&err, ret);
+ git_error_save(&last_error);
ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path);
- if (ret == GIT_ENOTFOUND)
- return git_error_state_restore(&err);
+ if (ret == GIT_ENOTFOUND) {
+ git_error_restore(last_error);
+ return GIT_EDIRECTORY;
+ }
- git_error_state_free(&err);
+ git_error_free(last_error);
/*
* EEXISTS means that there is a repository at that path, but it's not known
@@ -2758,6 +2760,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
unsigned int i;
struct index_header header = { 0 };
unsigned char checksum[GIT_HASH_MAX_SIZE];
+ unsigned char zero_checksum[GIT_HASH_MAX_SIZE] = { 0 };
size_t checksum_size = git_hash_size(git_oid_algorithm(index->oid_type));
const char *last = NULL;
const char *empty = "";
@@ -2847,8 +2850,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
/*
* SHA-1 or SHA-256 (depending on the repository's object format)
* over the content of the index file before this checksum.
+ * Note: checksum may be 0 if the index was written by a client
+ * where index.skipHash was set to true.
*/
- if (memcmp(checksum, buffer, checksum_size) != 0) {
+ if (memcmp(zero_checksum, buffer, checksum_size) != 0 &&
+ memcmp(checksum, buffer, checksum_size) != 0) {
error = index_error_invalid(
"calculated checksum does not match expected");
goto done;
diff --git a/src/libgit2/iterator.c b/src/libgit2/iterator.c
index 95ded10..bef9c60 100644
--- a/src/libgit2/iterator.c
+++ b/src/libgit2/iterator.c
@@ -26,9 +26,10 @@
#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT)
#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS)
-
static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
{
+ int (*vector_cmp)(const void *a, const void *b);
+
if (ignore_case)
iter->flags |= GIT_ITERATOR_IGNORE_CASE;
else
@@ -39,7 +40,9 @@ static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp;
iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch;
- git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
+ vector_cmp = ignore_case ? git__strcasecmp_cb : git__strcmp_cb;
+
+ git_vector_set_cmp(&iter->pathlist, vector_cmp);
}
static int iterator_range_init(
@@ -299,6 +302,7 @@ typedef enum {
static iterator_pathlist_search_t iterator_pathlist_search(
git_iterator *iter, const char *path, size_t path_len)
{
+ int (*vector_cmp)(const void *a, const void *b);
const char *p;
size_t idx;
int error;
@@ -308,8 +312,10 @@ static iterator_pathlist_search_t iterator_pathlist_search(
git_vector_sort(&iter->pathlist);
- error = git_vector_bsearch2(&idx, &iter->pathlist,
- (git_vector_cmp)iter->strcomp, path);
+ vector_cmp = (iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0 ?
+ git__strcasecmp_cb : git__strcmp_cb;
+
+ error = git_vector_bsearch2(&idx, &iter->pathlist, vector_cmp, path);
/* the given path was found in the pathlist. since the pathlist only
* matches directories when they're suffixed with a '/', analyze the
diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c
index ce28714..1b6f1a1 100644
--- a/src/libgit2/libgit2.c
+++ b/src/libgit2/libgit2.c
@@ -5,67 +5,32 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "libgit2.h"
-
#include <git2.h>
#include "alloc.h"
#include "buf.h"
-#include "cache.h"
#include "common.h"
#include "filter.h"
-#include "grafts.h"
#include "hash.h"
-#include "index.h"
#include "merge_driver.h"
#include "pool.h"
#include "mwindow.h"
-#include "object.h"
-#include "odb.h"
+#include "oid.h"
#include "rand.h"
-#include "refs.h"
#include "runtime.h"
+#include "settings.h"
#include "sysdir.h"
#include "thread.h"
-#include "threadstate.h"
#include "git2/global.h"
#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/socket.h"
-#include "transports/smart.h"
-#include "transports/http.h"
-#include "transports/ssh.h"
+#include "transports/ssh_libssh2.h"
#ifdef GIT_WIN32
# include "win32/w32_leakcheck.h"
#endif
-/* Declarations for tuneable settings */
-extern size_t git_mwindow__window_size;
-extern size_t git_mwindow__mapped_limit;
-extern size_t git_mwindow__file_limit;
-extern size_t git_indexer__max_objects;
-extern bool git_disable_pack_keep_file_checks;
-extern int git_odb__packed_priority;
-extern int git_odb__loose_priority;
-extern int git_socket_stream__connect_timeout;
-extern int git_socket_stream__timeout;
-
-char *git__user_agent;
-char *git__ssl_ciphers;
-
-static void libgit2_settings_global_shutdown(void)
-{
- git__free(git__user_agent);
- git__free(git__ssl_ciphers);
- git_repository__free_extensions();
-}
-
-static int git_libgit2_settings_global_init(void)
-{
- return git_runtime_shutdown_register(libgit2_settings_global_shutdown);
-}
-
int git_libgit2_init(void)
{
static git_runtime_init_fn init_fns[] = {
@@ -73,31 +38,27 @@ int git_libgit2_init(void)
git_win32_leakcheck_global_init,
#endif
git_allocator_global_init,
- git_threadstate_global_init,
+ git_error_global_init,
git_threads_global_init,
+ git_oid_global_init,
git_rand_global_init,
git_hash_global_init,
git_sysdir_global_init,
git_filter_global_init,
git_merge_driver_global_init,
- git_transport_ssh_global_init,
+ git_transport_ssh_libssh2_global_init,
git_stream_registry_global_init,
git_socket_stream_global_init,
git_openssl_stream_global_init,
git_mbedtls_stream_global_init,
git_mwindow_global_init,
git_pool_global_init,
- git_libgit2_settings_global_init
+ git_settings_global_init
};
return git_runtime_init(init_fns, ARRAY_SIZE(init_fns));
}
-int git_libgit2_init_count(void)
-{
- return git_runtime_init_count();
-}
-
int git_libgit2_shutdown(void)
{
return git_runtime_shutdown();
@@ -126,358 +87,11 @@ int git_libgit2_features(void)
#ifdef GIT_HTTPS
| GIT_FEATURE_HTTPS
#endif
-#if defined(GIT_SSH)
+#ifdef GIT_SSH
| GIT_FEATURE_SSH
#endif
-#if defined(GIT_USE_NSEC)
+#ifdef GIT_USE_NSEC
| GIT_FEATURE_NSEC
#endif
;
}
-
-static int config_level_to_sysdir(int *out, int config_level)
-{
- switch (config_level) {
- case GIT_CONFIG_LEVEL_SYSTEM:
- *out = GIT_SYSDIR_SYSTEM;
- return 0;
- case GIT_CONFIG_LEVEL_XDG:
- *out = GIT_SYSDIR_XDG;
- return 0;
- case GIT_CONFIG_LEVEL_GLOBAL:
- *out = GIT_SYSDIR_GLOBAL;
- return 0;
- case GIT_CONFIG_LEVEL_PROGRAMDATA:
- *out = GIT_SYSDIR_PROGRAMDATA;
- return 0;
- default:
- break;
- }
-
- git_error_set(
- GIT_ERROR_INVALID, "invalid config path selector %d", config_level);
- return -1;
-}
-
-const char *git_libgit2__user_agent(void)
-{
- return git__user_agent;
-}
-
-const char *git_libgit2__ssl_ciphers(void)
-{
- return git__ssl_ciphers;
-}
-
-int git_libgit2_opts(int key, ...)
-{
- int error = 0;
- va_list ap;
-
- va_start(ap, key);
-
- switch (key) {
- case GIT_OPT_SET_MWINDOW_SIZE:
- git_mwindow__window_size = va_arg(ap, size_t);
- break;
-
- case GIT_OPT_GET_MWINDOW_SIZE:
- *(va_arg(ap, size_t *)) = git_mwindow__window_size;
- break;
-
- case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
- git_mwindow__mapped_limit = va_arg(ap, size_t);
- break;
-
- case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
- *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
- break;
-
- case GIT_OPT_SET_MWINDOW_FILE_LIMIT:
- git_mwindow__file_limit = va_arg(ap, size_t);
- break;
-
- case GIT_OPT_GET_MWINDOW_FILE_LIMIT:
- *(va_arg(ap, size_t *)) = git_mwindow__file_limit;
- break;
-
- case GIT_OPT_GET_SEARCH_PATH:
- {
- int sysdir = va_arg(ap, int);
- git_buf *out = va_arg(ap, git_buf *);
- git_str str = GIT_STR_INIT;
- const git_str *tmp;
- int level;
-
- if ((error = git_buf_tostr(&str, out)) < 0 ||
- (error = config_level_to_sysdir(&level, sysdir)) < 0 ||
- (error = git_sysdir_get(&tmp, level)) < 0 ||
- (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
- break;
-
- error = git_buf_fromstr(out, &str);
- }
- break;
-
- case GIT_OPT_SET_SEARCH_PATH:
- {
- int level;
-
- if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0)
- error = git_sysdir_set(level, va_arg(ap, const char *));
- }
- break;
-
- case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
- {
- git_object_t type = (git_object_t)va_arg(ap, int);
- size_t size = va_arg(ap, size_t);
- error = git_cache_set_max_object_size(type, size);
- break;
- }
-
- case GIT_OPT_SET_CACHE_MAX_SIZE:
- git_cache__max_storage = va_arg(ap, ssize_t);
- break;
-
- case GIT_OPT_ENABLE_CACHING:
- git_cache__enabled = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_GET_CACHED_MEMORY:
- *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
- *(va_arg(ap, ssize_t *)) = git_cache__max_storage;
- break;
-
- case GIT_OPT_GET_TEMPLATE_PATH:
- {
- git_buf *out = va_arg(ap, git_buf *);
- git_str str = GIT_STR_INIT;
- const git_str *tmp;
-
- if ((error = git_buf_tostr(&str, out)) < 0 ||
- (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 ||
- (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
- break;
-
- error = git_buf_fromstr(out, &str);
- }
- break;
-
- case GIT_OPT_SET_TEMPLATE_PATH:
- error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *));
- break;
-
- case GIT_OPT_SET_SSL_CERT_LOCATIONS:
-#ifdef GIT_OPENSSL
- {
- const char *file = va_arg(ap, const char *);
- const char *path = va_arg(ap, const char *);
- error = git_openssl__set_cert_location(file, path);
- }
-#elif defined(GIT_MBEDTLS)
- {
- const char *file = va_arg(ap, const char *);
- const char *path = va_arg(ap, const char *);
- error = git_mbedtls__set_cert_location(file, path);
- }
-#else
- git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations");
- error = -1;
-#endif
- break;
- case GIT_OPT_SET_USER_AGENT:
- git__free(git__user_agent);
- git__user_agent = git__strdup(va_arg(ap, const char *));
- if (!git__user_agent) {
- git_error_set_oom();
- error = -1;
- }
-
- break;
-
- case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION:
- git_object__strict_input_validation = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION:
- git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_SET_SSL_CIPHERS:
-#if (GIT_OPENSSL || GIT_MBEDTLS)
- {
- git__free(git__ssl_ciphers);
- git__ssl_ciphers = git__strdup(va_arg(ap, const char *));
- if (!git__ssl_ciphers) {
- git_error_set_oom();
- error = -1;
- }
- }
-#else
- git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers");
- error = -1;
-#endif
- break;
-
- case GIT_OPT_GET_USER_AGENT:
- {
- git_buf *out = va_arg(ap, git_buf *);
- git_str str = GIT_STR_INIT;
-
- if ((error = git_buf_tostr(&str, out)) < 0 ||
- (error = git_str_puts(&str, git__user_agent)) < 0)
- break;
-
- error = git_buf_fromstr(out, &str);
- }
- break;
-
- case GIT_OPT_ENABLE_OFS_DELTA:
- git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_ENABLE_FSYNC_GITDIR:
- git_repository__fsync_gitdir = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_GET_WINDOWS_SHAREMODE:
-#ifdef GIT_WIN32
- *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode;
-#endif
- break;
-
- case GIT_OPT_SET_WINDOWS_SHAREMODE:
-#ifdef GIT_WIN32
- git_win32__createfile_sharemode = va_arg(ap, unsigned long);
-#endif
- break;
-
- case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION:
- git_odb__strict_hash_verification = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_SET_ALLOCATOR:
- error = git_allocator_setup(va_arg(ap, git_allocator *));
- break;
-
- case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY:
- git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_SET_PACK_MAX_OBJECTS:
- git_indexer__max_objects = va_arg(ap, size_t);
- break;
-
- case GIT_OPT_GET_PACK_MAX_OBJECTS:
- *(va_arg(ap, size_t *)) = git_indexer__max_objects;
- break;
-
- case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS:
- git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE:
- git_http__expect_continue = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_SET_ODB_PACKED_PRIORITY:
- git_odb__packed_priority = va_arg(ap, int);
- break;
-
- case GIT_OPT_SET_ODB_LOOSE_PRIORITY:
- git_odb__loose_priority = va_arg(ap, int);
- break;
-
- case GIT_OPT_SET_EXTENSIONS:
- {
- const char **extensions = va_arg(ap, const char **);
- size_t len = va_arg(ap, size_t);
- error = git_repository__set_extensions(extensions, len);
- }
- break;
-
- case GIT_OPT_GET_EXTENSIONS:
- {
- git_strarray *out = va_arg(ap, git_strarray *);
- char **extensions;
- size_t len;
-
- if ((error = git_repository__extensions(&extensions, &len)) < 0)
- break;
-
- out->strings = extensions;
- out->count = len;
- }
- break;
-
- case GIT_OPT_GET_OWNER_VALIDATION:
- *(va_arg(ap, int *)) = git_repository__validate_ownership;
- break;
-
- case GIT_OPT_SET_OWNER_VALIDATION:
- git_repository__validate_ownership = (va_arg(ap, int) != 0);
- break;
-
- case GIT_OPT_GET_HOMEDIR:
- {
- git_buf *out = va_arg(ap, git_buf *);
- git_str str = GIT_STR_INIT;
- const git_str *tmp;
-
- if ((error = git_buf_tostr(&str, out)) < 0 ||
- (error = git_sysdir_get(&tmp, GIT_SYSDIR_HOME)) < 0 ||
- (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
- break;
-
- error = git_buf_fromstr(out, &str);
- }
- break;
-
- case GIT_OPT_SET_HOMEDIR:
- error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *));
- break;
-
- case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT:
- *(va_arg(ap, int *)) = git_socket_stream__connect_timeout;
- break;
-
- case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT:
- {
- int timeout = va_arg(ap, int);
-
- if (timeout < 0) {
- git_error_set(GIT_ERROR_INVALID, "invalid connect timeout");
- error = -1;
- } else {
- git_socket_stream__connect_timeout = timeout;
- }
- }
- break;
-
- case GIT_OPT_GET_SERVER_TIMEOUT:
- *(va_arg(ap, int *)) = git_socket_stream__timeout;
- break;
-
- case GIT_OPT_SET_SERVER_TIMEOUT:
- {
- int timeout = va_arg(ap, int);
-
- if (timeout < 0) {
- git_error_set(GIT_ERROR_INVALID, "invalid timeout");
- error = -1;
- } else {
- git_socket_stream__timeout = timeout;
- }
- }
- break;
-
- default:
- git_error_set(GIT_ERROR_INVALID, "invalid option key");
- error = -1;
- }
-
- va_end(ap);
-
- return error;
-}
diff --git a/src/libgit2/libgit2.h b/src/libgit2/libgit2.h
deleted file mode 100644
index a898367..0000000
--- a/src/libgit2/libgit2.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * 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_libgit2_h__
-#define INCLUDE_libgit2_h__
-
-extern int git_libgit2_init_count(void);
-
-extern const char *git_libgit2__user_agent(void);
-extern const char *git_libgit2__ssl_ciphers(void);
-
-#endif
diff --git a/src/libgit2/merge.c b/src/libgit2/merge.c
index 0114e4b..21e5ef6 100644
--- a/src/libgit2/merge.c
+++ b/src/libgit2/merge.c
@@ -1225,6 +1225,13 @@ static int merge_diff_mark_similarity_exact(
if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry))
continue;
+ /*
+ * Ignore empty files because it has always the same blob sha1
+ * and will lead to incorrect matches between all entries.
+ */
+ if (git_oid_equal(&conflict_src->ancestor_entry.id, &git_oid__empty_blob_sha1))
+ continue;
+
if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) {
error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i);
if (error < 0)
diff --git a/src/libgit2/midx.c b/src/libgit2/midx.c
index d73a1da..71bbb1d 100644
--- a/src/libgit2/midx.c
+++ b/src/libgit2/midx.c
@@ -703,9 +703,9 @@ static int midx_write(
hash_cb_data.ctx = &ctx;
oid_size = git_oid_size(w->oid_type);
-
- GIT_ASSERT((checksum_type = git_oid_algorithm(w->oid_type)));
+ checksum_type = git_oid_algorithm(w->oid_type);
checksum_size = git_hash_size(checksum_type);
+ GIT_ASSERT(oid_size && checksum_type && checksum_size);
if ((error = git_hash_ctx_init(&ctx, checksum_type)) < 0)
return error;
diff --git a/src/libgit2/notes.c b/src/libgit2/notes.c
index 13ca382..2dd1945 100644
--- a/src/libgit2/notes.c
+++ b/src/libgit2/notes.c
@@ -303,7 +303,7 @@ static int note_write(
error = git_commit_create(&oid, repo, notes_ref, author, committer,
NULL, GIT_NOTES_DEFAULT_MSG_ADD,
- tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
+ tree, *parents == NULL ? 0 : 1, parents);
if (notes_commit_out)
git_oid_cpy(notes_commit_out, &oid);
@@ -394,7 +394,7 @@ static int note_remove(
NULL, GIT_NOTES_DEFAULT_MSG_RM,
tree_after_removal,
*parents == NULL ? 0 : 1,
- (const git_commit **) parents);
+ parents);
if (error < 0)
goto cleanup;
diff --git a/src/libgit2/oid.c b/src/libgit2/oid.c
index 631a566..2bb7a6f 100644
--- a/src/libgit2/oid.c
+++ b/src/libgit2/oid.c
@@ -9,7 +9,7 @@
#include "git2/oid.h"
#include "repository.h"
-#include "threadstate.h"
+#include "runtime.h"
#include <string.h>
#include <limits.h>
@@ -153,15 +153,42 @@ int git_oid_pathfmt(char *str, const git_oid *oid)
return 0;
}
+static git_tlsdata_key thread_str_key;
+
+static void GIT_SYSTEM_CALL thread_str_free(void *s)
+{
+ char *str = (char *)s;
+ git__free(str);
+}
+
+static void thread_str_global_shutdown(void)
+{
+ char *str = git_tlsdata_get(thread_str_key);
+ git_tlsdata_set(thread_str_key, NULL);
+
+ git__free(str);
+ git_tlsdata_dispose(thread_str_key);
+}
+
+int git_oid_global_init(void)
+{
+ if (git_tlsdata_init(&thread_str_key, thread_str_free) != 0)
+ return -1;
+
+ return git_runtime_shutdown_register(thread_str_global_shutdown);
+}
+
char *git_oid_tostr_s(const git_oid *oid)
{
- git_threadstate *threadstate = git_threadstate_get();
char *str;
- if (!threadstate)
- return NULL;
+ if ((str = git_tlsdata_get(thread_str_key)) == NULL) {
+ if ((str = git__malloc(GIT_OID_MAX_HEXSIZE + 1)) == NULL)
+ return NULL;
+
+ git_tlsdata_set(thread_str_key, str);
+ }
- str = threadstate->oid_fmt;
git_oid_nfmt(str, git_oid_hexsize(git_oid_type(oid)) + 1, oid);
return str;
}
diff --git a/src/libgit2/oid.h b/src/libgit2/oid.h
index 7b6b09d..f25a899 100644
--- a/src/libgit2/oid.h
+++ b/src/libgit2/oid.h
@@ -270,4 +270,6 @@ int git_oid__fromstrn(
int git_oid__fromraw(git_oid *out, const unsigned char *raw, git_oid_t type);
+int git_oid_global_init(void);
+
#endif
diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c
index eff7398..1ff0eb0 100644
--- a/src/libgit2/pack.c
+++ b/src/libgit2/pack.c
@@ -1499,6 +1499,7 @@ static int pack_entry_find_offset(
size_t len)
{
const uint32_t *level1_ofs;
+ size_t ofs_delta = 0;
const unsigned char *index;
unsigned hi, lo, stride;
int pos, found = 0;
@@ -1524,9 +1525,15 @@ static int pack_entry_find_offset(
if (p->index_version > 1) {
level1_ofs += 2;
+ ofs_delta = 2;
index += 8;
}
+ if ((size_t)short_oid->id[0] + ofs_delta >= p->index_map.len) {
+ git_error_set(GIT_ERROR_INTERNAL, "internal error: p->short_oid->[0] out of bounds");
+ goto cleanup;
+ }
+
index += 4 * 256;
hi = ntohl(level1_ofs[(int)short_oid->id[0]]);
lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1]));
diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c
index c069155..04f2a58 100644
--- a/src/libgit2/patch_parse.c
+++ b/src/libgit2/patch_parse.c
@@ -562,9 +562,9 @@ fail:
static int eof_for_origin(int origin) {
if (origin == GIT_DIFF_LINE_ADDITION)
- return GIT_DIFF_LINE_ADD_EOFNL;
- if (origin == GIT_DIFF_LINE_DELETION)
return GIT_DIFF_LINE_DEL_EOFNL;
+ if (origin == GIT_DIFF_LINE_DELETION)
+ return GIT_DIFF_LINE_ADD_EOFNL;
return GIT_DIFF_LINE_CONTEXT_EOFNL;
}
diff --git a/src/libgit2/path.c b/src/libgit2/path.c
index a19340e..4b584fb 100644
--- a/src/libgit2/path.c
+++ b/src/libgit2/path.c
@@ -202,7 +202,7 @@ GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *
{
size_t count = 0;
- while (len > 0 && tolower(*str) == tolower(*prefix)) {
+ while (len > 0 && git__tolower(*str) == git__tolower(*prefix)) {
count++;
str++;
prefix++;
diff --git a/src/libgit2/push.c b/src/libgit2/push.c
index 8b47abc..e065858 100644
--- a/src/libgit2/push.c
+++ b/src/libgit2/push.c
@@ -68,6 +68,14 @@ int git_push_new(git_push **out, git_remote *remote, const git_push_options *opt
return -1;
}
+ if (git_vector_init(&p->remote_push_options, 0, git__strcmp_cb) < 0) {
+ git_vector_free(&p->status);
+ git_vector_free(&p->specs);
+ git_vector_free(&p->updates);
+ git__free(p);
+ return -1;
+ }
+
*out = p;
return 0;
}
@@ -444,10 +452,21 @@ static int do_push(git_push *push)
if ((error = calculate_work(push)) < 0)
goto on_error;
- if (callbacks && callbacks->push_negotiation &&
- (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents,
- push->updates.length, callbacks->payload)) < 0)
- goto on_error;
+ if (callbacks && callbacks->push_negotiation) {
+ git_error_clear();
+
+ error = callbacks->push_negotiation(
+ (const git_push_update **) push->updates.contents,
+ push->updates.length, callbacks->payload);
+
+ if (error < 0) {
+ git_error_set_after_callback_function(error,
+ "push_negotiation");
+ goto on_error;
+ }
+
+ error = 0;
+ }
if ((error = queue_objects(push)) < 0 ||
(error = transport->push(transport, push)) < 0)
@@ -479,12 +498,24 @@ static int filter_refs(git_remote *remote)
int git_push_finish(git_push *push)
{
int error;
+ unsigned int remote_caps;
if (!git_remote_connected(push->remote)) {
git_error_set(GIT_ERROR_NET, "remote is disconnected");
return -1;
}
+ if ((error = git_remote_capabilities(&remote_caps, push->remote)) < 0) {
+ git_error_set(GIT_ERROR_INVALID, "remote capabilities not available");
+ return -1;
+ }
+
+ if (git_vector_length(&push->remote_push_options) > 0 &&
+ !(remote_caps & GIT_REMOTE_CAPABILITY_PUSH_OPTIONS)) {
+ git_error_set(GIT_ERROR_INVALID, "push-options not supported by remote");
+ return -1;
+ }
+
if ((error = filter_refs(push->remote)) < 0 ||
(error = do_push(push)) < 0)
return error;
@@ -528,6 +559,7 @@ void git_push_free(git_push *push)
push_spec *spec;
push_status *status;
git_push_update *update;
+ char *option;
unsigned int i;
if (push == NULL)
@@ -550,6 +582,11 @@ void git_push_free(git_push *push)
}
git_vector_free(&push->updates);
+ git_vector_foreach(&push->remote_push_options, i, option) {
+ git__free(option);
+ }
+ git_vector_free(&push->remote_push_options);
+
git__free(push);
}
diff --git a/src/libgit2/push.h b/src/libgit2/push.h
index fc72e84..40a1823 100644
--- a/src/libgit2/push.h
+++ b/src/libgit2/push.h
@@ -34,6 +34,7 @@ struct git_push {
git_vector specs;
git_vector updates;
bool report_status;
+ git_vector remote_push_options;
/* report-status */
bool unpack_ok;
diff --git a/src/libgit2/rebase.c b/src/libgit2/rebase.c
index 77e442e..2fce3e7 100644
--- a/src/libgit2/rebase.c
+++ b/src/libgit2/rebase.c
@@ -952,7 +952,7 @@ static int create_signed(
const char *message,
git_tree *tree,
size_t parent_count,
- const git_commit **parents)
+ git_commit * const *parents)
{
git_str commit_content = GIT_STR_INIT;
git_buf commit_signature = { NULL, 0, 0 },
@@ -1040,8 +1040,7 @@ static int rebase_commit__create(
if (rebase->options.commit_create_cb) {
error = rebase->options.commit_create_cb(&commit_id,
author, committer, message_encoding, message,
- tree, 1, (const git_commit **)&parent_commit,
- rebase->options.payload);
+ tree, 1, &parent_commit, rebase->options.payload);
git_error_set_after_callback_function(error,
"commit_create_cb");
@@ -1050,14 +1049,14 @@ static int rebase_commit__create(
else if (rebase->options.signing_cb) {
error = create_signed(&commit_id, rebase, author,
committer, message_encoding, message, tree,
- 1, (const git_commit **)&parent_commit);
+ 1, &parent_commit);
}
#endif
if (error == GIT_PASSTHROUGH)
error = git_commit_create(&commit_id, rebase->repo, NULL,
author, committer, message_encoding, message,
- tree, 1, (const git_commit **)&parent_commit);
+ tree, 1, &parent_commit);
if (error)
goto done;
diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c
index e34a714..9a5c38e 100644
--- a/src/libgit2/refdb_fs.c
+++ b/src/libgit2/refdb_fs.c
@@ -62,8 +62,8 @@ typedef struct refdb_fs_backend {
git_oid_t oid_type;
- int fsync : 1,
- sorted : 1;
+ unsigned int fsync : 1,
+ sorted : 1;
int peeling_mode;
git_iterator_flag_t iterator_flags;
uint32_t direach_flags;
@@ -410,7 +410,9 @@ static const char *loose_parse_symbolic(git_str *file_content)
static bool is_per_worktree_ref(const char *ref_name)
{
return git__prefixcmp(ref_name, "refs/") != 0 ||
- git__prefixcmp(ref_name, "refs/bisect/") == 0;
+ git__prefixcmp(ref_name, "refs/bisect/") == 0 ||
+ git__prefixcmp(ref_name, "refs/worktree/") == 0 ||
+ git__prefixcmp(ref_name, "refs/rewritten/") == 0;
}
static int loose_lookup(
@@ -805,83 +807,149 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
git__free(iter);
}
-static int iter_load_loose_paths(
- refdb_fs_backend *backend,
- refdb_fs_iter *iter)
+struct iter_load_context {
+ refdb_fs_backend *backend;
+ refdb_fs_iter *iter;
+
+ /*
+ * If we have a glob with a prefix (eg `refs/heads/ *`) then we can
+ * optimize our prefix to avoid walking refs that we know won't
+ * match. This is that prefix.
+ */
+ const char *ref_prefix;
+ size_t ref_prefix_len;
+
+ /* Temporary variables to avoid unnecessary allocations */
+ git_str ref_name;
+ git_str path;
+};
+
+static void iter_load_optimize_prefix(struct iter_load_context *ctx)
{
- int error = 0;
- git_str path = GIT_STR_INIT;
- git_iterator *fsit = NULL;
- git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry = NULL;
- const char *ref_prefix = GIT_REFS_DIR;
- size_t ref_prefix_len = strlen(ref_prefix);
+ const char *pos, *last_sep = NULL;
- if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
- return 0;
+ if (!ctx->iter->glob)
+ return;
- fsit_opts.flags = backend->iterator_flags;
- fsit_opts.oid_type = backend->oid_type;
-
- if (iter->glob) {
- const char *last_sep = NULL;
- const char *pos;
- for (pos = iter->glob; *pos; ++pos) {
- switch (*pos) {
- case '?':
- case '*':
- case '[':
- case '\\':
- break;
- case '/':
- last_sep = pos;
- /* FALLTHROUGH */
- default:
- continue;
- }
+ for (pos = ctx->iter->glob; *pos; pos++) {
+ switch (*pos) {
+ case '?':
+ case '*':
+ case '[':
+ case '\\':
break;
+ case '/':
+ last_sep = pos;
+ /* FALLTHROUGH */
+ default:
+ continue;
}
- if (last_sep) {
- ref_prefix = iter->glob;
- ref_prefix_len = (last_sep - ref_prefix) + 1;
- }
+ break;
}
- if ((error = git_str_puts(&path, backend->commonpath)) < 0 ||
- (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) {
- git_str_dispose(&path);
- return error;
+ if (last_sep) {
+ ctx->ref_prefix = ctx->iter->glob;
+ ctx->ref_prefix_len = (last_sep - ctx->ref_prefix) + 1;
}
+}
+
+static int iter_load_paths(
+ struct iter_load_context *ctx,
+ const char *root_path,
+ bool worktree)
+{
+ git_iterator *fsit = NULL;
+ git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int error = 0;
+
+ fsit_opts.flags = ctx->backend->iterator_flags;
+
+ git_str_clear(&ctx->path);
+ git_str_puts(&ctx->path, root_path);
+ git_str_put(&ctx->path, ctx->ref_prefix, ctx->ref_prefix_len);
+
+ fsit_opts.flags = ctx->backend->iterator_flags;
+ fsit_opts.oid_type = ctx->backend->oid_type;
+
+ if ((error = git_iterator_for_filesystem(&fsit, ctx->path.ptr, &fsit_opts)) < 0) {
+ /*
+ * Subdirectories - either glob provided or per-worktree refs - need
+ * not exist.
+ */
+ if ((worktree || ctx->iter->glob) && error == GIT_ENOTFOUND)
+ error = 0;
- if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
- git_str_dispose(&path);
- return (iter->glob && error == GIT_ENOTFOUND)? 0 : error;
+ goto done;
}
- error = git_str_sets(&path, ref_prefix);
+ git_str_clear(&ctx->ref_name);
+ git_str_put(&ctx->ref_name, ctx->ref_prefix, ctx->ref_prefix_len);
- while (!error && !git_iterator_advance(&entry, fsit)) {
- const char *ref_name;
+ while (git_iterator_advance(&entry, fsit) == 0) {
char *ref_dup;
- git_str_truncate(&path, ref_prefix_len);
- git_str_puts(&path, entry->path);
- ref_name = git_str_cstr(&path);
+ git_str_truncate(&ctx->ref_name, ctx->ref_prefix_len);
+ git_str_puts(&ctx->ref_name, entry->path);
- if (git__suffixcmp(ref_name, ".lock") == 0 ||
- (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
+ if (worktree) {
+ if (!is_per_worktree_ref(ctx->ref_name.ptr))
+ continue;
+ } else {
+ if (git_repository_is_worktree(ctx->backend->repo) &&
+ is_per_worktree_ref(ctx->ref_name.ptr))
+ continue;
+ }
+
+ if (git__suffixcmp(ctx->ref_name.ptr, ".lock") == 0)
continue;
- ref_dup = git_pool_strdup(&iter->pool, ref_name);
- if (!ref_dup)
- error = -1;
- else
- error = git_vector_insert(&iter->loose, ref_dup);
+ if (ctx->iter->glob && wildmatch(ctx->iter->glob, ctx->ref_name.ptr, 0))
+ continue;
+
+ ref_dup = git_pool_strdup(&ctx->iter->pool, ctx->ref_name.ptr);
+ GIT_ERROR_CHECK_ALLOC(ref_dup);
+
+ if ((error = git_vector_insert(&ctx->iter->loose, ref_dup)) < 0)
+ goto done;
}
+done:
git_iterator_free(fsit);
- git_str_dispose(&path);
+ return error;
+}
+#define iter_load_context_init(b, i) { b, i, GIT_REFS_DIR, CONST_STRLEN(GIT_REFS_DIR) }
+#define iter_load_context_dispose(ctx) do { \
+ git_str_dispose(&((ctx)->path)); \
+ git_str_dispose(&((ctx)->ref_name)); \
+} while(0)
+
+static int iter_load_loose_paths(
+ refdb_fs_backend *backend,
+ refdb_fs_iter *iter)
+{
+ struct iter_load_context ctx = iter_load_context_init(backend, iter);
+
+ int error = 0;
+
+ if (!backend->commonpath)
+ return 0;
+
+ iter_load_optimize_prefix(&ctx);
+
+ if ((error = iter_load_paths(&ctx,
+ backend->commonpath, false)) < 0)
+ goto done;
+
+ if (git_repository_is_worktree(backend->repo)) {
+ if ((error = iter_load_paths(&ctx,
+ backend->gitpath, true)) < 0)
+ goto done;
+ }
+
+done:
+ iter_load_context_dispose(&ctx);
return error;
}
diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c
index 5e2fe97..c1ed04d 100644
--- a/src/libgit2/refs.c
+++ b/src/libgit2/refs.c
@@ -1080,6 +1080,12 @@ int git_reference_cmp(
return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
}
+int git_reference__cmp_cb(const void *a, const void *b)
+{
+ return git_reference_cmp(
+ (const git_reference *)a, (const git_reference *)b);
+}
+
/*
* Starting with the reference given by `ref_name`, follows symbolic
* references until a direct reference is found and updated the OID
diff --git a/src/libgit2/refs.h b/src/libgit2/refs.h
index cb888bf..588af82 100644
--- a/src/libgit2/refs.h
+++ b/src/libgit2/refs.h
@@ -92,6 +92,12 @@ int git_reference__is_tag(const char *ref_name);
int git_reference__is_note(const char *ref_name);
const char *git_reference__shorthand(const char *name);
+/*
+ * A `git_reference_cmp` wrapper suitable for passing to generic
+ * comparators, like `vector_cmp` / `tsort` / etc.
+ */
+int git_reference__cmp_cb(const void *a, const void *b);
+
/**
* Lookup a reference by name and try to resolve to an OID.
*
diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c
index fee2a7f..8b486ea 100644
--- a/src/libgit2/remote.c
+++ b/src/libgit2/remote.c
@@ -1339,7 +1339,11 @@ int git_remote_download(
if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0)
return error;
- return git_remote__download(remote, refspecs, opts);
+ error = git_remote__download(remote, refspecs, opts);
+
+ git_remote_connect_options_dispose(&connect_opts);
+
+ return error;
}
int git_remote_fetch(
@@ -1348,13 +1352,14 @@ int git_remote_fetch(
const git_fetch_options *opts,
const char *reflog_message)
{
- int error, update_fetchhead = 1;
git_remote_autotag_option_t tagopt = remote->download_tags;
bool prune = false;
git_str reflog_msg_buf = GIT_STR_INIT;
git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT;
unsigned int capabilities;
git_oid_t oid_type;
+ unsigned int update_flags = GIT_REMOTE_UPDATE_FETCHHEAD;
+ int error;
GIT_ASSERT_ARG(remote);
@@ -1371,7 +1376,7 @@ int git_remote_fetch(
return error;
if (opts) {
- update_fetchhead = opts->update_fetchhead;
+ update_flags = opts->update_fetchhead;
tagopt = opts->download_tags;
}
@@ -1398,8 +1403,14 @@ int git_remote_fetch(
}
/* Create "remote/foo" branches for all remote branches */
- error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf));
+ error = git_remote_update_tips(remote,
+ &connect_opts.callbacks,
+ update_flags,
+ tagopt,
+ git_str_cstr(&reflog_msg_buf));
+
git_str_dispose(&reflog_msg_buf);
+
if (error < 0)
goto done;
@@ -1774,6 +1785,7 @@ static int update_one_tip(
git_refspec *spec,
git_remote_head *head,
git_refspec *tagspec,
+ unsigned int update_flags,
git_remote_autotag_option_t tagopt,
const char *log_message,
const git_remote_callbacks *callbacks)
@@ -1781,7 +1793,7 @@ static int update_one_tip(
git_odb *odb;
git_str refname = GIT_STR_INIT;
git_reference *ref = NULL;
- bool autotag = false;
+ bool autotag = false, updated = false;
git_oid old;
int valid;
int error;
@@ -1855,21 +1867,21 @@ static int update_one_tip(
goto done;
}
- if (!git_oid__cmp(&old, &head->oid))
- goto done;
+ if ((updated = !git_oid_equal(&old, &head->oid))) {
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
+ log_message);
- /* In autotag mode, don't overwrite any locally-existing tags */
- error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
- log_message);
-
- if (error < 0) {
- if (error == GIT_EEXISTS)
- error = 0;
+ if (error < 0) {
+ if (error == GIT_EEXISTS)
+ error = 0;
- goto done;
+ goto done;
+ }
}
if (callbacks && callbacks->update_tips != NULL &&
+ (updated || (update_flags & GIT_REMOTE_UPDATE_REPORT_UNCHANGED)) &&
(error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0)
git_error_set_after_callback_function(error, "git_remote_fetch");
@@ -1882,7 +1894,7 @@ done:
static int update_tips_for_spec(
git_remote *remote,
const git_remote_callbacks *callbacks,
- int update_fetchhead,
+ unsigned int update_flags,
git_remote_autotag_option_t tagopt,
git_refspec *spec,
git_vector *refs,
@@ -1905,7 +1917,10 @@ static int update_tips_for_spec(
/* Update tips based on the remote heads */
git_vector_foreach(refs, i, head) {
- if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0)
+ if (update_one_tip(&update_heads,
+ remote, spec, head, &tagspec,
+ update_flags, tagopt, log_message,
+ callbacks) < 0)
goto on_error;
}
@@ -1927,7 +1942,7 @@ static int update_tips_for_spec(
goto on_error;
}
- if (update_fetchhead &&
+ if ((update_flags & GIT_REMOTE_UPDATE_FETCHHEAD) &&
(error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0)
goto on_error;
@@ -2058,11 +2073,11 @@ static int truncate_fetch_head(const char *gitdir)
}
int git_remote_update_tips(
- git_remote *remote,
- const git_remote_callbacks *callbacks,
- int update_fetchhead,
- git_remote_autotag_option_t download_tags,
- const char *reflog_message)
+ git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ unsigned int update_flags,
+ git_remote_autotag_option_t download_tags,
+ const char *reflog_message)
{
git_refspec *spec, tagspec;
git_vector refs = GIT_VECTOR_INIT;
@@ -2091,7 +2106,7 @@ int git_remote_update_tips(
goto out;
if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
- if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0)
+ if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, &tagspec, &refs, reflog_message)) < 0)
goto out;
}
@@ -2099,7 +2114,7 @@ int git_remote_update_tips(
if (spec->push)
continue;
- if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0)
+ if ((error = update_tips_for_spec(remote, callbacks, update_flags, tagopt, spec, &refs, reflog_message)) < 0)
goto out;
}
@@ -2967,6 +2982,15 @@ int git_remote_upload(
}
}
+ if (opts && opts->remote_push_options.count > 0)
+ for (i = 0; i < opts->remote_push_options.count; ++i) {
+ char *optstr = git__strdup(opts->remote_push_options.strings[i]);
+ GIT_ERROR_CHECK_ALLOC(optstr);
+
+ if ((error = git_vector_insert(&push->remote_push_options, optstr)) < 0)
+ goto cleanup;
+ }
+
if ((error = git_push_finish(push)) < 0)
goto cleanup;
diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c
index 05ece6e..8e449a6 100644
--- a/src/libgit2/repository.c
+++ b/src/libgit2/repository.c
@@ -62,7 +62,8 @@ static const struct {
{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true },
{ GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true },
{ GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true },
- { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true }
+ { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true },
+ { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", false }
};
static int check_repositoryformatversion(int *version, git_config *config);
@@ -328,7 +329,7 @@ on_error:
return NULL;
}
-int git_repository_new(git_repository **out)
+int git_repository__new(git_repository **out, git_oid_t oid_type)
{
git_repository *repo;
@@ -337,10 +338,23 @@ int git_repository_new(git_repository **out)
repo->is_bare = 1;
repo->is_worktree = 0;
+ repo->oid_type = oid_type;
return 0;
}
+#ifdef GIT_EXPERIMENTAL_SHA256
+int git_repository_new(git_repository **out, git_oid_t oid_type)
+{
+ return git_repository__new(out, oid_type);
+}
+#else
+int git_repository_new(git_repository** out)
+{
+ return git_repository__new(out, GIT_OID_SHA1);
+}
+#endif
+
static int load_config_data(git_repository *repo, const git_config *config)
{
int is_bare;
@@ -545,25 +559,39 @@ typedef struct {
static int validate_ownership_cb(const git_config_entry *entry, void *payload)
{
validate_ownership_data *data = payload;
+ const char *test_path;
if (strcmp(entry->value, "") == 0) {
*data->is_safe = false;
} else if (strcmp(entry->value, "*") == 0) {
*data->is_safe = true;
} else {
- const char *test_path = entry->value;
+ if (git_str_sets(&data->tmp, entry->value) < 0)
+ return -1;
+
+ if (!git_fs_path_is_root(data->tmp.ptr)) {
+ /* Input must not have trailing backslash. */
+ if (!data->tmp.size ||
+ data->tmp.ptr[data->tmp.size - 1] == '/')
+ return 0;
+
+ if (git_fs_path_to_dir(&data->tmp) < 0)
+ return -1;
+ }
+
+ test_path = data->tmp.ptr;
-#ifdef GIT_WIN32
/*
- * Git for Windows does some truly bizarre things with
- * paths that start with a forward slash; and expects you
- * to escape that with `%(prefix)`. This syntax generally
- * means to add the prefix that Git was installed to -- eg
- * `/usr/local` -- unless it's an absolute path, in which
- * case the leading `%(prefix)/` is just removed. And Git
- * for Windows expects you to use this syntax for absolute
- * Unix-style paths (in "Git Bash" or Windows Subsystem for
- * Linux).
+ * Git - and especially, Git for Windows - does some
+ * truly bizarre things with paths that start with a
+ * forward slash; and expects you to escape that with
+ * `%(prefix)`. This syntax generally means to add the
+ * prefix that Git was installed to (eg `/usr/local`)
+ * unless it's an absolute path, in which case the
+ * leading `%(prefix)/` is just removed. And Git for
+ * Windows expects you to use this syntax for absolute
+ * Unix-style paths (in "Git Bash" or Windows Subsystem
+ * for Linux).
*
* Worse, the behavior used to be that a leading `/` was
* not absolute. It would indicate that Git for Windows
@@ -578,13 +606,8 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload)
*/
if (strncmp(test_path, "%(prefix)//", strlen("%(prefix)//")) == 0)
test_path += strlen("%(prefix)/");
- else if (strncmp(test_path, "//", 2) == 0 &&
- strncmp(test_path, "//wsl.localhost/", strlen("//wsl.localhost/")) != 0)
- test_path++;
-#endif
- if (git_fs_path_prettify_dir(&data->tmp, test_path, NULL) == 0 &&
- strcmp(data->tmp.ptr, data->repo_path) == 0)
+ if (strcmp(test_path, data->repo_path) == 0)
*data->is_safe = true;
}
@@ -681,9 +704,12 @@ static int validate_ownership(git_repository *repo)
goto done;
if (!is_safe) {
+ size_t path_len = git_fs_path_is_root(path) ?
+ strlen(path) : git_fs_path_dirlen(path);
+
git_error_set(GIT_ERROR_CONFIG,
- "repository path '%s' is not owned by current user",
- path);
+ "repository path '%.*s' is not owned by current user",
+ (int)min(path_len, INT_MAX), path);
error = GIT_EOWNER;
}
@@ -852,8 +878,30 @@ static int load_grafts(git_repository *repo)
git_str path = GIT_STR_INIT;
int error;
- if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
- (error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 ||
+ /* refresh if they've both been opened previously */
+ if (repo->grafts && repo->shallow_grafts) {
+ if ((error = git_grafts_refresh(repo->grafts)) < 0 ||
+ (error = git_grafts_refresh(repo->shallow_grafts)) < 0)
+ return error;
+ }
+
+ /* resolve info path, which may not be found for inmemory repository */
+ if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ /* create empty/inmemory grafts for inmemory repository */
+ if (!repo->grafts && (error = git_grafts_new(&repo->grafts, repo->oid_type)) < 0)
+ return error;
+
+ if (!repo->shallow_grafts && (error = git_grafts_new(&repo->shallow_grafts, repo->oid_type)) < 0)
+ return error;
+
+ return 0;
+ }
+
+ /* load grafts from disk */
+ if ((error = git_str_joinpath(&path, path.ptr, "grafts")) < 0 ||
(error = git_grafts_open_or_refresh(&repo->grafts, path.ptr, repo->oid_type)) < 0)
goto error;
@@ -1226,6 +1274,24 @@ int git_repository_discover(
return error;
}
+static int has_config_worktree(bool *out, git_config *cfg)
+{
+ int worktreeconfig = 0, error;
+
+ *out = false;
+
+ error = git_config_get_bool(&worktreeconfig, cfg, "extensions.worktreeconfig");
+
+ if (error == 0)
+ *out = worktreeconfig;
+ else if (error == GIT_ENOTFOUND)
+ *out = false;
+ else
+ return error;
+
+ return 0;
+}
+
static int load_config(
git_config **out,
git_repository *repo,
@@ -1234,9 +1300,11 @@ static int load_config(
const char *system_config_path,
const char *programdata_path)
{
- int error;
git_str config_path = GIT_STR_INIT;
git_config *cfg = NULL;
+ git_config_level_t write_order;
+ bool has_worktree;
+ int error;
GIT_ASSERT_ARG(out);
@@ -1250,6 +1318,14 @@ static int load_config(
if (error && error != GIT_ENOTFOUND)
goto on_error;
+ if ((error = has_config_worktree(&has_worktree, cfg)) == 0 &&
+ has_worktree &&
+ (error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0)
+ error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0);
+
+ if (error && error != GIT_ENOTFOUND)
+ goto on_error;
+
git_str_dispose(&config_path);
}
@@ -1279,6 +1355,11 @@ static int load_config(
git_error_clear(); /* clear any lingering ENOTFOUND errors */
+ write_order = GIT_CONFIG_LEVEL_LOCAL;
+
+ if ((error = git_config_set_writeorder(cfg, &write_order, 1)) < 0)
+ goto on_error;
+
*out = cfg;
return 0;
@@ -1798,7 +1879,8 @@ static int check_repositoryformatversion(int *version, git_config *config)
static const char *builtin_extensions[] = {
"noop",
- "objectformat"
+ "objectformat",
+ "worktreeconfig",
};
static git_vector user_extensions = { 0, git__strcmp_cb };
@@ -2630,6 +2712,8 @@ static int repo_init_directories(
if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0)
return -1;
+ git_fs_path_mkposix(repo_path->ptr);
+
has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0);
if (has_dotgit)
opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT;
@@ -3197,14 +3281,18 @@ int git_repository_set_workdir(
if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0)
return -1;
- if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0)
+ if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) {
+ git_str_dispose(&path);
return 0;
+ }
if (update_gitlink) {
git_config *config;
- if (git_repository_config__weakptr(&config, repo) < 0)
+ if (git_repository_config__weakptr(&config, repo) < 0) {
+ git_str_dispose(&path);
return -1;
+ }
error = repo_write_gitlink(path.ptr, git_repository_path(repo), false);
@@ -3226,6 +3314,7 @@ int git_repository_set_workdir(
git__free(old_workdir);
}
+ git_str_dispose(&path);
return error;
}
@@ -3268,6 +3357,25 @@ int git_repository_set_bare(git_repository *repo)
return 0;
}
+int git_repository_head_commit(git_commit **commit, git_repository *repo)
+{
+ git_reference *head;
+ git_object *obj;
+ int error;
+
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ if ((error = git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)) < 0)
+ goto cleanup;
+
+ *commit = (git_commit *)obj;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
int git_repository_head_tree(git_tree **tree, git_repository *repo)
{
git_reference *head;
@@ -3839,3 +3947,65 @@ git_oid_t git_repository_oid_type(git_repository *repo)
{
return repo ? repo->oid_type : 0;
}
+
+struct mergehead_data {
+ git_repository *repo;
+ git_vector *parents;
+};
+
+static int insert_mergehead(const git_oid *oid, void *payload)
+{
+ git_commit *commit;
+ struct mergehead_data *data = (struct mergehead_data *)payload;
+
+ if (git_commit_lookup(&commit, data->repo, oid) < 0)
+ return -1;
+
+ return git_vector_insert(data->parents, commit);
+}
+
+int git_repository_commit_parents(git_commitarray *out, git_repository *repo)
+{
+ git_commit *first_parent = NULL, *commit;
+ git_reference *head_ref = NULL;
+ git_vector parents = GIT_VECTOR_INIT;
+ struct mergehead_data data;
+ size_t i;
+ int error;
+
+ GIT_ASSERT_ARG(out && repo);
+
+ out->count = 0;
+ out->commits = NULL;
+
+ error = git_revparse_ext((git_object **)&first_parent, &head_ref, repo, "HEAD");
+
+ if (error != 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ goto done;
+ }
+
+ if ((error = git_vector_insert(&parents, first_parent)) < 0)
+ goto done;
+
+ data.repo = repo;
+ data.parents = &parents;
+
+ error = git_repository_mergehead_foreach(repo, insert_mergehead, &data);
+
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ else if (error != 0)
+ goto done;
+
+ out->commits = (git_commit **)git_vector_detach(&out->count, NULL, &parents);
+
+done:
+ git_vector_foreach(&parents, i, commit)
+ git__free(commit);
+
+ git_reference_free(head_ref);
+ return error;
+}
diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h
index 6d2b64c..f45a359 100644
--- a/src/libgit2/repository.h
+++ b/src/libgit2/repository.h
@@ -173,6 +173,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
return repo->attrcache;
}
+int git_repository_head_commit(git_commit **commit, git_repository *repo);
int git_repository_head_tree(git_tree **tree, git_repository *repo);
int git_repository_create_head(const char *git_dir, const char *ref_name);
@@ -280,4 +281,7 @@ int git_repository__set_objectformat(
git_repository *repo,
git_oid_t oid_type);
+/* SHA256-aware internal functions */
+int git_repository__new(git_repository **out, git_oid_t oid_type);
+
#endif
diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c
index 06d92f8..9083e7a 100644
--- a/src/libgit2/revparse.c
+++ b/src/libgit2/revparse.c
@@ -816,7 +816,7 @@ static int revparse(
if (temp_object != NULL)
base_rev = temp_object;
break;
- } else if (spec[pos+1] == '\0') {
+ } else if (spec[pos + 1] == '\0' && !pos) {
spec = "HEAD";
identifier_len = 4;
parsed = true;
@@ -935,7 +935,7 @@ int git_revparse(
* allowed.
*/
if (!git__strcmp(spec, "..")) {
- git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'");
+ git_error_set(GIT_ERROR_INVALID, "invalid pattern '..'");
return GIT_EINVALIDSPEC;
}
diff --git a/src/libgit2/settings.c b/src/libgit2/settings.c
new file mode 100644
index 0000000..4a41830
--- /dev/null
+++ b/src/libgit2/settings.c
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "settings.h"
+
+#include <git2.h>
+#include "alloc.h"
+#include "buf.h"
+#include "cache.h"
+#include "common.h"
+#include "filter.h"
+#include "grafts.h"
+#include "hash.h"
+#include "index.h"
+#include "merge_driver.h"
+#include "pool.h"
+#include "mwindow.h"
+#include "object.h"
+#include "odb.h"
+#include "rand.h"
+#include "refs.h"
+#include "runtime.h"
+#include "sysdir.h"
+#include "thread.h"
+#include "git2/global.h"
+#include "streams/registry.h"
+#include "streams/mbedtls.h"
+#include "streams/openssl.h"
+#include "streams/socket.h"
+#include "transports/smart.h"
+#include "transports/http.h"
+#include "transports/ssh_libssh2.h"
+
+#ifdef GIT_WIN32
+# include "win32/w32_leakcheck.h"
+#endif
+
+/* Declarations for tuneable settings */
+extern size_t git_mwindow__window_size;
+extern size_t git_mwindow__mapped_limit;
+extern size_t git_mwindow__file_limit;
+extern size_t git_indexer__max_objects;
+extern bool git_disable_pack_keep_file_checks;
+extern int git_odb__packed_priority;
+extern int git_odb__loose_priority;
+extern int git_socket_stream__connect_timeout;
+extern int git_socket_stream__timeout;
+
+char *git__user_agent;
+char *git__user_agent_product;
+char *git__ssl_ciphers;
+
+static void settings_global_shutdown(void)
+{
+ git__free(git__user_agent);
+ git__free(git__user_agent_product);
+
+ git__free(git__ssl_ciphers);
+ git_repository__free_extensions();
+}
+
+int git_settings_global_init(void)
+{
+ return git_runtime_shutdown_register(settings_global_shutdown);
+}
+
+static int config_level_to_sysdir(int *out, int config_level)
+{
+ switch (config_level) {
+ case GIT_CONFIG_LEVEL_SYSTEM:
+ *out = GIT_SYSDIR_SYSTEM;
+ return 0;
+ case GIT_CONFIG_LEVEL_XDG:
+ *out = GIT_SYSDIR_XDG;
+ return 0;
+ case GIT_CONFIG_LEVEL_GLOBAL:
+ *out = GIT_SYSDIR_GLOBAL;
+ return 0;
+ case GIT_CONFIG_LEVEL_PROGRAMDATA:
+ *out = GIT_SYSDIR_PROGRAMDATA;
+ return 0;
+ default:
+ break;
+ }
+
+ git_error_set(
+ GIT_ERROR_INVALID, "invalid config path selector %d", config_level);
+ return -1;
+}
+
+const char *git_settings__user_agent_product(void)
+{
+ return git__user_agent_product ? git__user_agent_product :
+ "git/2.0";
+}
+
+const char *git_settings__user_agent(void)
+{
+ return git__user_agent ? git__user_agent :
+ "libgit2 " LIBGIT2_VERSION;
+}
+
+int git_libgit2_opts(int key, ...)
+{
+ int error = 0;
+ va_list ap;
+
+ va_start(ap, key);
+
+ switch (key) {
+ case GIT_OPT_SET_MWINDOW_SIZE:
+ git_mwindow__window_size = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_SIZE:
+ *(va_arg(ap, size_t *)) = git_mwindow__window_size;
+ break;
+
+ case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
+ git_mwindow__mapped_limit = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
+ *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
+ break;
+
+ case GIT_OPT_SET_MWINDOW_FILE_LIMIT:
+ git_mwindow__file_limit = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_FILE_LIMIT:
+ *(va_arg(ap, size_t *)) = git_mwindow__file_limit;
+ break;
+
+ case GIT_OPT_GET_SEARCH_PATH:
+ {
+ int sysdir = va_arg(ap, int);
+ git_buf *out = va_arg(ap, git_buf *);
+ git_str str = GIT_STR_INIT;
+ const git_str *tmp;
+ int level;
+
+ if ((error = git_buf_tostr(&str, out)) < 0 ||
+ (error = config_level_to_sysdir(&level, sysdir)) < 0 ||
+ (error = git_sysdir_get(&tmp, level)) < 0 ||
+ (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
+ break;
+
+ error = git_buf_fromstr(out, &str);
+ }
+ break;
+
+ case GIT_OPT_SET_SEARCH_PATH:
+ {
+ int level;
+
+ if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0)
+ error = git_sysdir_set(level, va_arg(ap, const char *));
+ }
+ break;
+
+ case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
+ {
+ git_object_t type = (git_object_t)va_arg(ap, int);
+ size_t size = va_arg(ap, size_t);
+ error = git_cache_set_max_object_size(type, size);
+ break;
+ }
+
+ case GIT_OPT_SET_CACHE_MAX_SIZE:
+ git_cache__max_storage = va_arg(ap, ssize_t);
+ break;
+
+ case GIT_OPT_ENABLE_CACHING:
+ git_cache__enabled = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_GET_CACHED_MEMORY:
+ *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
+ *(va_arg(ap, ssize_t *)) = git_cache__max_storage;
+ break;
+
+ case GIT_OPT_GET_TEMPLATE_PATH:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ git_str str = GIT_STR_INIT;
+ const git_str *tmp;
+
+ if ((error = git_buf_tostr(&str, out)) < 0 ||
+ (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 ||
+ (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
+ break;
+
+ error = git_buf_fromstr(out, &str);
+ }
+ break;
+
+ case GIT_OPT_SET_TEMPLATE_PATH:
+ error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_SET_SSL_CERT_LOCATIONS:
+#ifdef GIT_OPENSSL
+ {
+ const char *file = va_arg(ap, const char *);
+ const char *path = va_arg(ap, const char *);
+ error = git_openssl__set_cert_location(file, path);
+ }
+#elif defined(GIT_MBEDTLS)
+ {
+ const char *file = va_arg(ap, const char *);
+ const char *path = va_arg(ap, const char *);
+ error = git_mbedtls__set_cert_location(file, path);
+ }
+#else
+ git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations");
+ error = -1;
+#endif
+ break;
+
+ case GIT_OPT_SET_USER_AGENT:
+ {
+ const char *new_agent = va_arg(ap, const char *);
+
+ git__free(git__user_agent);
+
+ if (new_agent) {
+ git__user_agent= git__strdup(new_agent);
+
+ if (!git__user_agent)
+ error = -1;
+ } else {
+ git__user_agent = NULL;
+ }
+ }
+ break;
+
+ case GIT_OPT_GET_USER_AGENT:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ git_str str = GIT_STR_INIT;
+
+ if ((error = git_buf_tostr(&str, out)) < 0 ||
+ (error = git_str_puts(&str, git_settings__user_agent())) < 0)
+ break;
+
+ error = git_buf_fromstr(out, &str);
+ }
+ break;
+
+ case GIT_OPT_SET_USER_AGENT_PRODUCT:
+ {
+ const char *new_agent = va_arg(ap, const char *);
+
+ git__free(git__user_agent_product);
+
+ if (new_agent) {
+ git__user_agent_product = git__strdup(new_agent);
+
+ if (!git__user_agent_product)
+ error = -1;
+ } else {
+ git__user_agent_product = NULL;
+ }
+ }
+ break;
+
+ case GIT_OPT_GET_USER_AGENT_PRODUCT:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ git_str str = GIT_STR_INIT;
+
+ if ((error = git_buf_tostr(&str, out)) < 0 ||
+ (error = git_str_puts(&str, git_settings__user_agent_product())) < 0)
+ break;
+
+ error = git_buf_fromstr(out, &str);
+ }
+ break;
+
+ case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION:
+ git_object__strict_input_validation = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION:
+ git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_SET_SSL_CIPHERS:
+#if (GIT_OPENSSL || GIT_MBEDTLS)
+ {
+ git__free(git__ssl_ciphers);
+ git__ssl_ciphers = git__strdup(va_arg(ap, const char *));
+ if (!git__ssl_ciphers) {
+ git_error_set_oom();
+ error = -1;
+ }
+ }
+#else
+ git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers");
+ error = -1;
+#endif
+ break;
+
+ case GIT_OPT_ENABLE_OFS_DELTA:
+ git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_ENABLE_FSYNC_GITDIR:
+ git_repository__fsync_gitdir = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_GET_WINDOWS_SHAREMODE:
+#ifdef GIT_WIN32
+ *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode;
+#endif
+ break;
+
+ case GIT_OPT_SET_WINDOWS_SHAREMODE:
+#ifdef GIT_WIN32
+ git_win32__createfile_sharemode = va_arg(ap, unsigned long);
+#endif
+ break;
+
+ case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION:
+ git_odb__strict_hash_verification = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_SET_ALLOCATOR:
+ error = git_allocator_setup(va_arg(ap, git_allocator *));
+ break;
+
+ case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY:
+ git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_SET_PACK_MAX_OBJECTS:
+ git_indexer__max_objects = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_PACK_MAX_OBJECTS:
+ *(va_arg(ap, size_t *)) = git_indexer__max_objects;
+ break;
+
+ case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS:
+ git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE:
+ git_http__expect_continue = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_SET_ODB_PACKED_PRIORITY:
+ git_odb__packed_priority = va_arg(ap, int);
+ break;
+
+ case GIT_OPT_SET_ODB_LOOSE_PRIORITY:
+ git_odb__loose_priority = va_arg(ap, int);
+ break;
+
+ case GIT_OPT_SET_EXTENSIONS:
+ {
+ const char **extensions = va_arg(ap, const char **);
+ size_t len = va_arg(ap, size_t);
+ error = git_repository__set_extensions(extensions, len);
+ }
+ break;
+
+ case GIT_OPT_GET_EXTENSIONS:
+ {
+ git_strarray *out = va_arg(ap, git_strarray *);
+ char **extensions;
+ size_t len;
+
+ if ((error = git_repository__extensions(&extensions, &len)) < 0)
+ break;
+
+ out->strings = extensions;
+ out->count = len;
+ }
+ break;
+
+ case GIT_OPT_GET_OWNER_VALIDATION:
+ *(va_arg(ap, int *)) = git_repository__validate_ownership;
+ break;
+
+ case GIT_OPT_SET_OWNER_VALIDATION:
+ git_repository__validate_ownership = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_GET_HOMEDIR:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ git_str str = GIT_STR_INIT;
+ const git_str *tmp;
+
+ if ((error = git_buf_tostr(&str, out)) < 0 ||
+ (error = git_sysdir_get(&tmp, GIT_SYSDIR_HOME)) < 0 ||
+ (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0)
+ break;
+
+ error = git_buf_fromstr(out, &str);
+ }
+ break;
+
+ case GIT_OPT_SET_HOMEDIR:
+ error = git_sysdir_set(GIT_SYSDIR_HOME, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_GET_SERVER_CONNECT_TIMEOUT:
+ *(va_arg(ap, int *)) = git_socket_stream__connect_timeout;
+ break;
+
+ case GIT_OPT_SET_SERVER_CONNECT_TIMEOUT:
+ {
+ int timeout = va_arg(ap, int);
+
+ if (timeout < 0) {
+ git_error_set(GIT_ERROR_INVALID, "invalid connect timeout");
+ error = -1;
+ } else {
+ git_socket_stream__connect_timeout = timeout;
+ }
+ }
+ break;
+
+ case GIT_OPT_GET_SERVER_TIMEOUT:
+ *(va_arg(ap, int *)) = git_socket_stream__timeout;
+ break;
+
+ case GIT_OPT_SET_SERVER_TIMEOUT:
+ {
+ int timeout = va_arg(ap, int);
+
+ if (timeout < 0) {
+ git_error_set(GIT_ERROR_INVALID, "invalid timeout");
+ error = -1;
+ } else {
+ git_socket_stream__timeout = timeout;
+ }
+ }
+ break;
+
+ default:
+ git_error_set(GIT_ERROR_INVALID, "invalid option key");
+ error = -1;
+ }
+
+ va_end(ap);
+
+ return error;
+}
diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h
index dc42ce9..2929366 100644
--- a/src/libgit2/settings.h
+++ b/src/libgit2/settings.h
@@ -4,8 +4,12 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
+#ifndef INCLUDE_settings_h__
+#define INCLUDE_settings_h__
extern int git_settings_global_init(void);
-extern const char *git_libgit2__user_agent(void);
-extern const char *git_libgit2__ssl_ciphers(void);
+extern const char *git_settings__user_agent(void);
+extern const char *git_settings__user_agent_product(void);
+
+#endif
diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c
index 5d6ab57..12d2b5f 100644
--- a/src/libgit2/signature.c
+++ b/src/libgit2/signature.c
@@ -43,7 +43,6 @@ static bool contains_angle_brackets(const char *input)
static bool is_crud(unsigned char c)
{
return c <= 32 ||
- c == '.' ||
c == ',' ||
c == ':' ||
c == ';' ||
diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c
index b49e95c..a0a72de 100644
--- a/src/libgit2/stash.c
+++ b/src/libgit2/stash.c
@@ -124,7 +124,7 @@ static int commit_index(
git_index *index,
const git_signature *stasher,
const char *message,
- const git_commit *parent)
+ git_commit *parent)
{
git_tree *i_tree = NULL;
git_oid i_commit_oid;
@@ -423,7 +423,7 @@ static int build_stash_commit_from_tree(
git_commit *u_commit,
const git_tree *tree)
{
- const git_commit *parents[] = { NULL, NULL, NULL };
+ git_commit *parents[] = { NULL, NULL, NULL };
parents[0] = b_commit;
parents[1] = i_commit;
diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c
index 49aa76c..1b27807 100644
--- a/src/libgit2/streams/mbedtls.c
+++ b/src/libgit2/streams/mbedtls.c
@@ -32,7 +32,6 @@
# endif
#endif
-#include <mbedtls/config.h>
#include <mbedtls/ssl.h>
#include <mbedtls/error.h>
#include <mbedtls/entropy.h>
@@ -43,9 +42,15 @@
#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA"
#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30
-static mbedtls_ssl_config *git__ssl_conf;
static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT];
-static mbedtls_entropy_context *mbedtls_entropy;
+
+static bool initialized = false;
+static mbedtls_ssl_config mbedtls_config;
+static mbedtls_ctr_drbg_context mbedtls_rng;
+static mbedtls_entropy_context mbedtls_entropy;
+
+static bool has_ca_chain = false;
+static mbedtls_x509_crt mbedtls_ca_chain;
/**
* This function aims to clean-up the SSL context which
@@ -53,19 +58,16 @@ static mbedtls_entropy_context *mbedtls_entropy;
*/
static void shutdown_ssl(void)
{
- if (git__ssl_conf) {
- mbedtls_x509_crt_free(git__ssl_conf->ca_chain);
- git__free(git__ssl_conf->ca_chain);
- mbedtls_ctr_drbg_free(git__ssl_conf->p_rng);
- git__free(git__ssl_conf->p_rng);
- mbedtls_ssl_config_free(git__ssl_conf);
- git__free(git__ssl_conf);
- git__ssl_conf = NULL;
+ if (has_ca_chain) {
+ mbedtls_x509_crt_free(&mbedtls_ca_chain);
+ has_ca_chain = false;
}
- if (mbedtls_entropy) {
- mbedtls_entropy_free(mbedtls_entropy);
- git__free(mbedtls_entropy);
- mbedtls_entropy = NULL;
+
+ if (initialized) {
+ mbedtls_ctr_drbg_free(&mbedtls_rng);
+ mbedtls_ssl_config_free(&mbedtls_config);
+ mbedtls_entropy_free(&mbedtls_entropy);
+ initialized = false;
}
}
@@ -74,32 +76,33 @@ int git_mbedtls_stream_global_init(void)
int loaded = 0;
char *crtpath = GIT_DEFAULT_CERT_LOCATION;
struct stat statbuf;
- mbedtls_ctr_drbg_context *ctr_drbg = NULL;
size_t ciphers_known = 0;
char *cipher_name = NULL;
char *cipher_string = NULL;
char *cipher_string_tmp = NULL;
- git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config));
- GIT_ERROR_CHECK_ALLOC(git__ssl_conf);
+ mbedtls_ssl_config_init(&mbedtls_config);
+ mbedtls_entropy_init(&mbedtls_entropy);
+ mbedtls_ctr_drbg_init(&mbedtls_rng);
- mbedtls_ssl_config_init(git__ssl_conf);
- if (mbedtls_ssl_config_defaults(git__ssl_conf,
- MBEDTLS_SSL_IS_CLIENT,
- MBEDTLS_SSL_TRANSPORT_STREAM,
- MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
+ if (mbedtls_ssl_config_defaults(&mbedtls_config,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS");
goto cleanup;
}
- /* configure TLSv1 */
- mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0);
+ /* configure TLSv1.1 */
+#ifdef MBEDTLS_SSL_MINOR_VERSION_2
+ mbedtls_ssl_conf_min_version(&mbedtls_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_2);
+#endif
/* verify_server_cert is responsible for making the check.
* OPTIONAL because REQUIRED drops the certificate as soon as the check
* is made, so we can never see the certificate and override it. */
- mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
+ mbedtls_ssl_conf_authmode(&mbedtls_config, MBEDTLS_SSL_VERIFY_OPTIONAL);
/* set the list of allowed ciphersuites */
ciphers_known = 0;
@@ -123,42 +126,33 @@ int git_mbedtls_stream_global_init(void)
git_error_set(GIT_ERROR_SSL, "no cipher could be enabled");
goto cleanup;
}
- mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list);
+ mbedtls_ssl_conf_ciphersuites(&mbedtls_config, ciphers_list);
/* Seeding the random number generator */
- mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context));
- GIT_ERROR_CHECK_ALLOC(mbedtls_entropy);
-
- mbedtls_entropy_init(mbedtls_entropy);
-
- ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context));
- GIT_ERROR_CHECK_ALLOC(ctr_drbg);
- mbedtls_ctr_drbg_init(ctr_drbg);
-
- if (mbedtls_ctr_drbg_seed(ctr_drbg,
- mbedtls_entropy_func,
- mbedtls_entropy, NULL, 0) != 0) {
+ if (mbedtls_ctr_drbg_seed(&mbedtls_rng, mbedtls_entropy_func,
+ &mbedtls_entropy, NULL, 0) != 0) {
git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool");
goto cleanup;
}
- mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg);
+ mbedtls_ssl_conf_rng(&mbedtls_config, mbedtls_ctr_drbg_random, &mbedtls_rng);
/* load default certificates */
if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0);
+
if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0);
+ initialized = true;
+
return git_runtime_shutdown_register(shutdown_ssl);
cleanup:
- mbedtls_ctr_drbg_free(ctr_drbg);
- git__free(ctr_drbg);
- mbedtls_ssl_config_free(git__ssl_conf);
- git__free(git__ssl_conf);
- git__ssl_conf = NULL;
+ mbedtls_ctr_drbg_free(&mbedtls_rng);
+ mbedtls_ssl_config_free(&mbedtls_config);
+ mbedtls_entropy_free(&mbedtls_entropy);
return -1;
}
@@ -192,7 +186,7 @@ static int ssl_set_error(mbedtls_ssl_context *ssl, int error)
break;
case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:
- git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf);
+ git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, mbedtls_ssl_get_verify_result(ssl), errbuf);
ret = GIT_ECERTIFICATE;
break;
@@ -374,7 +368,7 @@ static int mbedtls_stream_wrap(
st->ssl = git__malloc(sizeof(mbedtls_ssl_context));
GIT_ERROR_CHECK_ALLOC(st->ssl);
mbedtls_ssl_init(st->ssl);
- if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) {
+ if (mbedtls_ssl_setup(st->ssl, &mbedtls_config)) {
git_error_set(GIT_ERROR_SSL, "failed to create ssl object");
error = -1;
goto out_err;
@@ -441,30 +435,30 @@ int git_mbedtls__set_cert_location(const char *file, const char *path)
{
int ret = 0;
char errbuf[512];
- mbedtls_x509_crt *cacert;
GIT_ASSERT_ARG(file || path);
- cacert = git__malloc(sizeof(mbedtls_x509_crt));
- GIT_ERROR_CHECK_ALLOC(cacert);
+ if (has_ca_chain)
+ mbedtls_x509_crt_free(&mbedtls_ca_chain);
+
+ mbedtls_x509_crt_init(&mbedtls_ca_chain);
- mbedtls_x509_crt_init(cacert);
if (file)
- ret = mbedtls_x509_crt_parse_file(cacert, file);
+ ret = mbedtls_x509_crt_parse_file(&mbedtls_ca_chain, file);
+
if (ret >= 0 && path)
- ret = mbedtls_x509_crt_parse_path(cacert, path);
+ ret = mbedtls_x509_crt_parse_path(&mbedtls_ca_chain, path);
+
/* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */
if (ret < 0) {
- mbedtls_x509_crt_free(cacert);
- git__free(cacert);
+ mbedtls_x509_crt_free(&mbedtls_ca_chain);
mbedtls_strerror( ret, errbuf, 512 );
git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf);
return -1;
}
- mbedtls_x509_crt_free(git__ssl_conf->ca_chain);
- git__free(git__ssl_conf->ca_chain);
- mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL);
+ mbedtls_ssl_conf_ca_chain(&mbedtls_config, &mbedtls_ca_chain, NULL);
+ has_ca_chain = true;
return 0;
}
diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c
index 9db911e..7cb8f7f 100644
--- a/src/libgit2/streams/openssl.c
+++ b/src/libgit2/streams/openssl.c
@@ -36,6 +36,8 @@
# include <openssl/bio.h>
#endif
+extern char *git__ssl_ciphers;
+
SSL_CTX *git__ssl_ctx;
#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA"
@@ -105,7 +107,7 @@ static void git_openssl_free(void *mem)
static int openssl_init(void)
{
long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
- const char *ciphers = git_libgit2__ssl_ciphers();
+ const char *ciphers = git__ssl_ciphers;
#ifdef VALGRIND
static bool allocators_initialized = false;
#endif
diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c
index 95ea84f..830d41c 100644
--- a/src/libgit2/submodule.c
+++ b/src/libgit2/submodule.c
@@ -196,7 +196,7 @@ static void free_submodule_names(git_strmap *names)
*/
static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg)
{
- const char *key = "submodule\\..*\\.path";
+ const char *key = "^submodule\\..*\\.path$";
git_config_iterator *iter = NULL;
git_config_entry *entry;
git_str buf = GIT_STR_INIT;
@@ -332,7 +332,7 @@ int git_submodule__lookup_with_cache(
/* If it's not configured or we're looking by path */
if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
git_config_backend *mods;
- const char *pattern = "submodule\\..*\\.path";
+ const char *pattern = "^submodule\\..*\\.path$";
git_str path = GIT_STR_INIT;
fbp_data data = { NULL, NULL };
diff --git a/src/libgit2/threadstate.c b/src/libgit2/threadstate.c
deleted file mode 100644
index ed9bb9b..0000000
--- a/src/libgit2/threadstate.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#include "threadstate.h"
-#include "runtime.h"
-
-/**
- * Handle the thread-local state
- *
- * `git_threadstate_global_init` will be called as part
- * of `git_libgit2_init` (which itself must be called
- * before calling any other function in the library).
- *
- * This function allocates a TLS index to store the per-
- * thread state.
- *
- * Any internal method that requires thread-local state
- * will then call `git_threadstate_get()` which returns a
- * pointer to the thread-local state structure; this
- * structure is lazily allocated on each thread.
- *
- * This mechanism will register a shutdown handler
- * (`git_threadstate_global_shutdown`) which will free the
- * TLS index. This shutdown handler will be called by
- * `git_libgit2_shutdown`.
- */
-
-static git_tlsdata_key tls_key;
-
-static void threadstate_dispose(git_threadstate *threadstate)
-{
- if (!threadstate)
- return;
-
- if (threadstate->error_t.message != git_str__initstr)
- git__free(threadstate->error_t.message);
- threadstate->error_t.message = NULL;
-}
-
-static void GIT_SYSTEM_CALL threadstate_free(void *threadstate)
-{
- threadstate_dispose(threadstate);
- git__free(threadstate);
-}
-
-static void git_threadstate_global_shutdown(void)
-{
- git_threadstate *threadstate;
-
- threadstate = git_tlsdata_get(tls_key);
- git_tlsdata_set(tls_key, NULL);
-
- threadstate_dispose(threadstate);
- git__free(threadstate);
-
- git_tlsdata_dispose(tls_key);
-}
-
-int git_threadstate_global_init(void)
-{
- if (git_tlsdata_init(&tls_key, &threadstate_free) != 0)
- return -1;
-
- return git_runtime_shutdown_register(git_threadstate_global_shutdown);
-}
-
-git_threadstate *git_threadstate_get(void)
-{
- git_threadstate *threadstate;
-
- if ((threadstate = git_tlsdata_get(tls_key)) != NULL)
- return threadstate;
-
- /*
- * Avoid git__malloc here, since if it fails, it sets an error
- * message, which requires thread state, which would allocate
- * here, which would fail, which would set an error message...
- */
-
- if ((threadstate = git__allocator.gmalloc(sizeof(git_threadstate),
- __FILE__, __LINE__)) == NULL)
- return NULL;
-
- memset(threadstate, 0, sizeof(git_threadstate));
-
- if (git_str_init(&threadstate->error_buf, 0) < 0) {
- git__allocator.gfree(threadstate);
- return NULL;
- }
-
- git_tlsdata_set(tls_key, threadstate);
- return threadstate;
-}
diff --git a/src/libgit2/threadstate.h b/src/libgit2/threadstate.h
deleted file mode 100644
index 6ef0419..0000000
--- a/src/libgit2/threadstate.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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_threadstate_h__
-#define INCLUDE_threadstate_h__
-
-#include "common.h"
-
-typedef struct {
- git_error *last_error;
- git_error error_t;
- git_str error_buf;
- char oid_fmt[GIT_OID_MAX_HEXSIZE+1];
-} git_threadstate;
-
-extern int git_threadstate_global_init(void);
-extern git_threadstate *git_threadstate_get(void);
-
-#endif
diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c
index 4761c99..c7579fb 100644
--- a/src/libgit2/trailer.c
+++ b/src/libgit2/trailer.c
@@ -24,7 +24,7 @@ static const char *const git_generated_prefixes[] = {
static int is_blank_line(const char *str)
{
const char *s = str;
- while (*s && *s != '\n' && isspace(*s))
+ while (*s && *s != '\n' && git__isspace(*s))
s++;
return !*s || *s == '\n';
}
@@ -93,7 +93,7 @@ static bool find_separator(size_t *out, const char *line, const char *separators
return true;
}
- if (!whitespace_found && (isalnum(*c) || *c == '-'))
+ if (!whitespace_found && (git__isalnum(*c) || *c == '-'))
continue;
if (c != line && (*c == ' ' || *c == '\t')) {
whitespace_found = 1;
@@ -158,7 +158,7 @@ static size_t find_patch_start(const char *str)
const char *s;
for (s = str; *s; s = next_line(s)) {
- if (git__prefixcmp(s, "---") == 0)
+ if (git__prefixcmp(s, "---") == 0 && git__isspace(s[3]))
return s - str;
}
@@ -233,12 +233,12 @@ static size_t find_trailer_start(const char *buf, size_t len)
}
find_separator(&separator_pos, bol, TRAILER_SEPARATORS);
- if (separator_pos >= 1 && !isspace(bol[0])) {
+ if (separator_pos >= 1 && !git__isspace(bol[0])) {
trailer_lines++;
possible_continuation_lines = 0;
if (recognized_prefix)
continue;
- } else if (isspace(bol[0]))
+ } else if (git__isspace(bol[0]))
possible_continuation_lines++;
else {
non_trailer_lines++;
@@ -323,7 +323,7 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes
goto ret;
}
- if (isalnum(*ptr) || *ptr == '-') {
+ if (git__isalnum(*ptr) || *ptr == '-') {
/* legal key character */
NEXT(S_KEY);
}
diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c
index ccffa99..9634161 100644
--- a/src/libgit2/transaction.c
+++ b/src/libgit2/transaction.c
@@ -49,12 +49,16 @@ struct git_transaction {
git_repository *repo;
git_refdb *db;
git_config *cfg;
+ void *cfg_data;
git_strmap *locks;
git_pool pool;
};
-int git_transaction_config_new(git_transaction **out, git_config *cfg)
+int git_transaction_config_new(
+ git_transaction **out,
+ git_config *cfg,
+ void *data)
{
git_transaction *tx;
@@ -66,6 +70,8 @@ int git_transaction_config_new(git_transaction **out, git_config *cfg)
tx->type = TRANSACTION_CONFIG;
tx->cfg = cfg;
+ tx->cfg_data = data;
+
*out = tx;
return 0;
}
@@ -333,8 +339,9 @@ int git_transaction_commit(git_transaction *tx)
GIT_ASSERT_ARG(tx);
if (tx->type == TRANSACTION_CONFIG) {
- error = git_config_unlock(tx->cfg, true);
+ error = git_config_unlock(tx->cfg, tx->cfg_data, true);
tx->cfg = NULL;
+ tx->cfg_data = NULL;
return error;
}
@@ -369,10 +376,8 @@ void git_transaction_free(git_transaction *tx)
return;
if (tx->type == TRANSACTION_CONFIG) {
- if (tx->cfg) {
- git_config_unlock(tx->cfg, false);
- git_config_free(tx->cfg);
- }
+ if (tx->cfg)
+ git_config_unlock(tx->cfg, tx->cfg_data, false);
git__free(tx);
return;
diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h
index 780c068..cb26017 100644
--- a/src/libgit2/transaction.h
+++ b/src/libgit2/transaction.h
@@ -9,6 +9,9 @@
#include "common.h"
-int git_transaction_config_new(git_transaction **out, git_config *cfg);
+int git_transaction_config_new(
+ git_transaction **out,
+ git_config *cfg,
+ void *data);
#endif
diff --git a/src/libgit2/transport.c b/src/libgit2/transport.c
index 640ccac..c61d0a6 100644
--- a/src/libgit2/transport.c
+++ b/src/libgit2/transport.c
@@ -22,6 +22,7 @@ typedef struct transport_definition {
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL };
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL };
+
#ifdef GIT_SSH
static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL };
#endif
@@ -33,11 +34,13 @@ static transport_definition transports[] = {
{ "http://", git_transport_smart, &http_subtransport_definition },
{ "https://", git_transport_smart, &http_subtransport_definition },
{ "file://", git_transport_local, NULL },
+
#ifdef GIT_SSH
{ "ssh://", git_transport_smart, &ssh_subtransport_definition },
{ "ssh+git://", git_transport_smart, &ssh_subtransport_definition },
{ "git+ssh://", git_transport_smart, &ssh_subtransport_definition },
#endif
+
{ NULL, 0, 0 }
};
diff --git a/src/libgit2/transports/credential.c b/src/libgit2/transports/credential.c
index 6e00b02..b47bd63 100644
--- a/src/libgit2/transports/credential.c
+++ b/src/libgit2/transports/credential.c
@@ -204,7 +204,7 @@ int git_credential_ssh_key_memory_new(
const char *privatekey,
const char *passphrase)
{
-#ifdef GIT_SSH_MEMORY_CREDENTIALS
+#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS
return git_credential_ssh_key_type_new(
cred,
username,
diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c
index 8437674..ea81995 100644
--- a/src/libgit2/transports/http.c
+++ b/src/libgit2/transports/http.c
@@ -9,7 +9,6 @@
#ifndef GIT_WINHTTP
-#include "http_parser.h"
#include "net.h"
#include "remote.h"
#include "smart.h"
@@ -334,7 +333,7 @@ static int lookup_proxy(
return 0;
}
- if (!proxy ||
+ if (!proxy || !*proxy ||
(error = git_net_url_parse_http(&transport->proxy.url, proxy)) < 0)
goto done;
diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h
index 8e8e722..7410202 100644
--- a/src/libgit2/transports/http.h
+++ b/src/libgit2/transports/http.h
@@ -15,14 +15,4 @@
extern bool git_http__expect_continue;
-GIT_INLINE(int) git_http__user_agent(git_str *buf)
-{
- const char *ua = git_libgit2__user_agent();
-
- if (!ua)
- ua = "libgit2 " LIBGIT2_VERSION;
-
- return git_str_printf(buf, "git/2.0 (%s)", ua);
-}
-
#endif
diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c
index a20b594..a0c4002 100644
--- a/src/libgit2/transports/httpclient.c
+++ b/src/libgit2/transports/httpclient.c
@@ -7,7 +7,7 @@
#include "common.h"
#include "git2.h"
-#include "http_parser.h"
+
#include "vector.h"
#include "trace.h"
#include "httpclient.h"
@@ -21,6 +21,7 @@
#include "streams/socket.h"
#include "streams/tls.h"
#include "auth.h"
+#include "httpparser.h"
static git_http_auth_scheme auth_schemes[] = {
{ GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate },
@@ -108,7 +109,7 @@ struct git_http_client {
git_http_server_t current_server;
http_client_state state;
- http_parser parser;
+ git_http_parser parser;
git_http_server server;
git_http_server proxy;
@@ -154,7 +155,7 @@ void git_http_response_dispose(git_http_response *response)
memset(response, 0, sizeof(git_http_response));
}
-static int on_header_complete(http_parser *parser)
+static int on_header_complete(git_http_parser *parser)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
git_http_client *client = ctx->client;
@@ -219,7 +220,7 @@ static int on_header_complete(http_parser *parser)
return 0;
}
-static int on_header_field(http_parser *parser, const char *str, size_t len)
+static int on_header_field(git_http_parser *parser, const char *str, size_t len)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
@@ -254,7 +255,7 @@ static int on_header_field(http_parser *parser, const char *str, size_t len)
return 0;
}
-static int on_header_value(http_parser *parser, const char *str, size_t len)
+static int on_header_value(git_http_parser *parser, const char *str, size_t len)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
@@ -342,7 +343,7 @@ static int resend_needed(git_http_client *client, git_http_response *response)
return 0;
}
-static int on_headers_complete(http_parser *parser)
+static int on_headers_complete(git_http_parser *parser)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
@@ -364,8 +365,8 @@ static int on_headers_complete(http_parser *parser)
return ctx->parse_status = PARSE_STATUS_ERROR;
}
- ctx->response->status = parser->status_code;
- ctx->client->keepalive = http_should_keep_alive(parser);
+ ctx->response->status = git_http_parser_status_code(parser);
+ ctx->client->keepalive = git_http_parser_keep_alive(parser);
/* Prepare for authentication */
collect_authinfo(&ctx->response->server_auth_schemetypes,
@@ -378,18 +379,15 @@ static int on_headers_complete(http_parser *parser)
ctx->response->resend_credentials = resend_needed(ctx->client,
ctx->response);
- /* Stop parsing. */
- http_parser_pause(parser, 1);
-
if (ctx->response->content_type || ctx->response->chunked)
ctx->client->state = READING_BODY;
else
ctx->client->state = DONE;
- return 0;
+ return git_http_parser_pause(parser);
}
-static int on_body(http_parser *parser, const char *buf, size_t len)
+static int on_body(git_http_parser *parser, const char *buf, size_t len)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
size_t max_len;
@@ -411,7 +409,7 @@ static int on_body(http_parser *parser, const char *buf, size_t len)
return 0;
}
-static int on_message_complete(http_parser *parser)
+static int on_message_complete(git_http_parser *parser)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
@@ -651,6 +649,30 @@ static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port)
return git_str_oom(buf) ? -1 : 0;
}
+static int append_user_agent(git_str *buf)
+{
+ const char *product = git_settings__user_agent_product();
+ const char *comment = git_settings__user_agent();
+
+ GIT_ASSERT(product && comment);
+
+ if (!*product)
+ return 0;
+
+ git_str_puts(buf, "User-Agent: ");
+ git_str_puts(buf, product);
+
+ if (*comment) {
+ git_str_puts(buf, " (");
+ git_str_puts(buf, comment);
+ git_str_puts(buf, ")");
+ }
+
+ git_str_puts(buf, "\r\n");
+
+ return git_str_oom(buf) ? -1 : 0;
+}
+
static int generate_connect_request(
git_http_client *client,
git_http_request *request)
@@ -665,9 +687,7 @@ static int generate_connect_request(
puts_host_and_port(buf, &client->server.url, true);
git_str_puts(buf, " HTTP/1.1\r\n");
- git_str_puts(buf, "User-Agent: ");
- git_http__user_agent(buf);
- git_str_puts(buf, "\r\n");
+ append_user_agent(buf);
git_str_puts(buf, "Host: ");
puts_host_and_port(buf, &client->server.url, true);
@@ -711,9 +731,7 @@ static int generate_request(
git_str_puts(buf, " HTTP/1.1\r\n");
- git_str_puts(buf, "User-Agent: ");
- git_http__user_agent(buf);
- git_str_puts(buf, "\r\n");
+ append_user_agent(buf);
git_str_puts(buf, "Host: ");
puts_host_and_port(buf, request->url, false);
@@ -768,25 +786,37 @@ static int check_certificate(
void *cert_cb_payload)
{
git_cert *cert;
- git_error_state last_error = {0};
+ git_error *last_error;
int error;
if ((error = git_stream_certificate(&cert, stream)) < 0)
return error;
- git_error_state_capture(&last_error, GIT_ECERTIFICATE);
+ /*
+ * Allow callers to set an error - but save ours and clear
+ * it, so that we can detect if they set one and restore it
+ * if we need to.
+ */
+ git_error_save(&last_error);
+ git_error_clear();
error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
- if (error == GIT_PASSTHROUGH && !is_valid)
- return git_error_state_restore(&last_error);
- else if (error == GIT_PASSTHROUGH)
- error = 0;
- else if (error && !git_error_last())
- git_error_set(GIT_ERROR_HTTP,
- "user rejected certificate for %s", url->host);
+ if (error == GIT_PASSTHROUGH) {
+ error = is_valid ? 0 : -1;
- git_error_state_free(&last_error);
+ if (error) {
+ git_error_restore(last_error);
+ last_error = NULL;
+ }
+ } else if (error) {
+ if (!git_error_exists())
+ git_error_set(GIT_ERROR_HTTP,
+ "user rejected certificate for %s",
+ url->host);
+ }
+
+ git_error_free(last_error);
return error;
}
@@ -864,9 +894,29 @@ GIT_INLINE(int) server_setup_from_url(
return 0;
}
+static bool parser_settings_initialized;
+static git_http_parser_settings parser_settings;
+
+GIT_INLINE(git_http_parser_settings *) http_client_parser_settings(void)
+{
+ if (!parser_settings_initialized) {
+ parser_settings.on_header_field = on_header_field;
+ parser_settings.on_header_value = on_header_value;
+ parser_settings.on_headers_complete = on_headers_complete;
+ parser_settings.on_body = on_body;
+ parser_settings.on_message_complete = on_message_complete;
+
+ parser_settings_initialized = true;
+ }
+
+ return &parser_settings;
+}
+
static void reset_parser(git_http_client *client)
{
- http_parser_init(&client->parser, HTTP_RESPONSE);
+ git_http_parser_init(&client->parser,
+ GIT_HTTP_PARSER_RESPONSE,
+ http_client_parser_settings());
}
static int setup_hosts(
@@ -1109,27 +1159,9 @@ GIT_INLINE(int) client_read(git_http_client *client)
return (int)read_len;
}
-static bool parser_settings_initialized;
-static http_parser_settings parser_settings;
-
-GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
-{
- if (!parser_settings_initialized) {
- parser_settings.on_header_field = on_header_field;
- parser_settings.on_header_value = on_header_value;
- parser_settings.on_headers_complete = on_headers_complete;
- parser_settings.on_body = on_body;
- parser_settings.on_message_complete = on_message_complete;
-
- parser_settings_initialized = true;
- }
-
- return &parser_settings;
-}
-
GIT_INLINE(int) client_read_and_parse(git_http_client *client)
{
- http_parser *parser = &client->parser;
+ git_http_parser *parser = &client->parser;
http_parser_context *ctx = (http_parser_context *) parser->data;
unsigned char http_errno;
int read_len;
@@ -1143,11 +1175,10 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client)
if (!client->read_buf.size && (read_len = client_read(client)) < 0)
return read_len;
- parsed_len = http_parser_execute(parser,
- http_client_parser_settings(),
+ parsed_len = git_http_parser_execute(parser,
client->read_buf.ptr,
client->read_buf.size);
- http_errno = client->parser.http_errno;
+ http_errno = git_http_parser_errno(parser);
if (parsed_len > INT_MAX) {
git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse");
@@ -1166,26 +1197,29 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client)
* (This can happen in response to an expect/continue request,
* where the server gives you a 100 and 200 simultaneously.)
*/
- if (http_errno == HPE_PAUSED) {
+ if (http_errno == GIT_HTTP_PARSER_PAUSED) {
+ size_t additional_size;
+
+ git_http_parser_resume(parser);
+
/*
- * http-parser has a "feature" where it will not deliver the
- * final byte when paused in a callback. Consume that byte.
- * https://github.com/nodejs/http-parser/issues/97
+ * http-parser has a "feature" where it will not deliver
+ * the final byte when paused in a callback. Consume
+ * that byte.
*/
- GIT_ASSERT(client->read_buf.size > parsed_len);
+ if ((additional_size = git_http_parser_remain_after_pause(parser)) > 0) {
+ GIT_ASSERT((client->read_buf.size - parsed_len) >= additional_size);
- http_parser_pause(parser, 0);
-
- parsed_len += http_parser_execute(parser,
- http_client_parser_settings(),
- client->read_buf.ptr + parsed_len,
- 1);
+ parsed_len += git_http_parser_execute(parser,
+ client->read_buf.ptr + parsed_len,
+ additional_size);
+ }
}
/* Most failures will be reported in http_errno */
- else if (parser->http_errno != HPE_OK) {
+ else if (git_http_parser_errno(parser) != GIT_HTTP_PARSER_OK) {
git_error_set(GIT_ERROR_HTTP, "http parser error: %s",
- http_errno_description(http_errno));
+ git_http_parser_errmsg(parser, http_errno));
return -1;
}
@@ -1193,7 +1227,7 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client)
else if (parsed_len != client->read_buf.size) {
git_error_set(GIT_ERROR_HTTP,
"http parser did not consume entire buffer: %s",
- http_errno_description(http_errno));
+ git_http_parser_errmsg(parser, http_errno));
return -1;
}
@@ -1232,7 +1266,7 @@ static void complete_response_body(git_http_client *client)
/* If there was an error, just close the connection. */
if (client_read_and_parse(client) < 0 ||
- parser_context.error != HPE_OK ||
+ parser_context.error != GIT_HTTP_PARSER_OK ||
(parser_context.parse_status != PARSE_STATUS_OK &&
parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
git_error_clear();
@@ -1240,6 +1274,7 @@ static void complete_response_body(git_http_client *client)
}
done:
+ client->parser.data = NULL;
git_str_clear(&client->read_buf);
}
@@ -1429,6 +1464,7 @@ int git_http_client_read_response(
done:
git_str_dispose(&parser_context.parse_header_name);
git_str_dispose(&parser_context.parse_header_value);
+ client->parser.data = NULL;
return error;
}
@@ -1484,6 +1520,8 @@ done:
if (error < 0)
client->connected = 0;
+ client->parser.data = NULL;
+
return error;
}
@@ -1506,7 +1544,7 @@ int git_http_client_skip_body(git_http_client *client)
do {
error = client_read_and_parse(client);
- if (parser_context.error != HPE_OK ||
+ if (parser_context.error != GIT_HTTP_PARSER_OK ||
(parser_context.parse_status != PARSE_STATUS_OK &&
parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
git_error_set(GIT_ERROR_HTTP,
@@ -1518,6 +1556,8 @@ int git_http_client_skip_body(git_http_client *client)
if (error < 0)
client->connected = 0;
+ client->parser.data = NULL;
+
return error;
}
diff --git a/src/libgit2/transports/httpparser.c b/src/libgit2/transports/httpparser.c
new file mode 100644
index 0000000..50ba6d2
--- /dev/null
+++ b/src/libgit2/transports/httpparser.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "httpparser.h"
+
+#include <string.h>
+
+#if defined(GIT_HTTPPARSER_HTTPPARSER)
+
+#include "http_parser.h"
+
+static int on_message_begin(http_parser *p)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_message_begin(parser);
+}
+
+static int on_url(http_parser *p, const char *str, size_t len)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_url(parser, str, len);
+}
+
+static int on_header_field(http_parser *p, const char *str, size_t len)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_header_field(parser, str, len);
+}
+
+static int on_header_value(http_parser *p, const char *str, size_t len)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_header_value(parser, str, len);
+}
+
+static int on_headers_complete(http_parser *p)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_headers_complete(parser);
+}
+
+static int on_body(http_parser *p, const char *buf, size_t len)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_body(parser, buf, len);
+}
+
+static int on_message_complete(http_parser *p)
+{
+ git_http_parser *parser = (git_http_parser *)p;
+ return parser->settings.on_message_complete(parser);
+}
+
+void git_http_parser_init(
+ git_http_parser *parser,
+ git_http_parser_t type,
+ git_http_parser_settings *settings)
+{
+ http_parser_init(&parser->parser, (enum http_parser_type)type);
+ memcpy(&parser->settings, settings, sizeof(git_http_parser_settings));
+}
+
+size_t git_http_parser_execute(
+ git_http_parser *parser,
+ const char *data,
+ size_t len)
+{
+ struct http_parser_settings settings_proxy;
+
+ settings_proxy.on_message_begin = parser->settings.on_message_begin ? on_message_begin : NULL;
+ settings_proxy.on_url = parser->settings.on_url ? on_url : NULL;
+ settings_proxy.on_header_field = parser->settings.on_header_field ? on_header_field : NULL;
+ settings_proxy.on_header_value = parser->settings.on_header_value ? on_header_value : NULL;
+ settings_proxy.on_headers_complete = parser->settings.on_headers_complete ? on_headers_complete : NULL;
+ settings_proxy.on_body = parser->settings.on_body ? on_body : NULL;
+ settings_proxy.on_message_complete = parser->settings.on_message_complete ? on_message_complete : NULL;
+
+ return http_parser_execute(&parser->parser, &settings_proxy, data, len);
+}
+
+#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN)
+
+# include <llhttp.h>
+
+size_t git_http_parser_execute(
+ git_http_parser *parser,
+ const char* data,
+ size_t len)
+{
+ llhttp_errno_t error;
+ size_t parsed_len;
+
+ /*
+ * Unlike http_parser, which returns the number of parsed
+ * bytes in the _execute() call, llhttp returns an error
+ * code.
+ */
+
+ if (data == NULL || len == 0)
+ error = llhttp_finish(parser);
+ else
+ error = llhttp_execute(parser, data, len);
+
+ parsed_len = len;
+
+ /*
+ * Adjust number of parsed bytes in case of error.
+ */
+ if (error != HPE_OK) {
+ parsed_len = llhttp_get_error_pos(parser) - data;
+
+ /* This isn't a real pause, just a way to stop parsing early. */
+ if (error == HPE_PAUSED_UPGRADE)
+ llhttp_resume_after_upgrade(parser);
+ }
+
+ return parsed_len;
+}
+
+#else
+# error unknown http-parser
+#endif
diff --git a/src/libgit2/transports/httpparser.h b/src/libgit2/transports/httpparser.h
new file mode 100644
index 0000000..1fe0dcf
--- /dev/null
+++ b/src/libgit2/transports/httpparser.h
@@ -0,0 +1,99 @@
+/*
+ * 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_transports_httpparser_h__
+#define INCLUDE_transports_httpparser_h__
+
+#include "git2_util.h"
+
+#if defined(GIT_HTTPPARSER_HTTPPARSER)
+
+# include <http_parser.h>
+
+typedef enum {
+ GIT_HTTP_PARSER_OK = HPE_OK,
+ GIT_HTTP_PARSER_PAUSED = HPE_PAUSED,
+} git_http_parser_error_t;
+
+typedef enum {
+ GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST,
+ GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE,
+} git_http_parser_t;
+
+typedef struct git_http_parser git_http_parser;
+
+typedef struct {
+ int (*on_message_begin)(git_http_parser *);
+ int (*on_url)(git_http_parser *, const char *, size_t);
+ int (*on_header_field)(git_http_parser *, const char *, size_t);
+ int (*on_header_value)(git_http_parser *, const char *, size_t);
+ int (*on_headers_complete)(git_http_parser *);
+ int (*on_body)(git_http_parser *, const char *, size_t);
+ int (*on_message_complete)(git_http_parser *);
+} git_http_parser_settings;
+
+struct git_http_parser {
+ http_parser parser;
+ git_http_parser_settings settings;
+ void *data;
+};
+
+void git_http_parser_init(
+ git_http_parser *parser,
+ git_http_parser_t type,
+ git_http_parser_settings *settings);
+
+size_t git_http_parser_execute(
+ git_http_parser *parser,
+ const char *data,
+ size_t len);
+
+# define git_http_parser_status_code(parser) parser->parser.status_code
+# define git_http_parser_keep_alive(parser) http_should_keep_alive(&parser->parser)
+# define git_http_parser_pause(parser) (http_parser_pause(&parser->parser, 1), 0)
+# define git_http_parser_resume(parser) http_parser_pause(&parser->parser, 0)
+# define git_http_parser_remain_after_pause(parser) 1
+# define git_http_parser_errno(parser) parser->parser.http_errno
+# define git_http_parser_errmsg(parser, errno) http_errno_description(errno)
+
+#elif defined(GIT_HTTPPARSER_LLHTTP) || defined(GIT_HTTPPARSER_BUILTIN)
+
+# include <llhttp.h>
+
+typedef enum {
+ GIT_HTTP_PARSER_OK = HPE_OK,
+ GIT_HTTP_PARSER_PAUSED = HPE_PAUSED,
+} git_http_parser_error_t;
+
+typedef enum {
+ GIT_HTTP_PARSER_REQUEST = HTTP_REQUEST,
+ GIT_HTTP_PARSER_RESPONSE = HTTP_RESPONSE,
+} git_http_parser_t;
+
+typedef llhttp_t git_http_parser;
+typedef llhttp_settings_t git_http_parser_settings;
+
+# define git_http_parser_init(parser, direction, settings) llhttp_init(parser, (llhttp_type_t)direction, settings)
+
+size_t git_http_parser_execute(
+ git_http_parser *parser,
+ const char *data,
+ size_t len);
+
+# define git_http_parser_status_code(parser) parser->status_code
+# define git_http_parser_keep_alive(parser) llhttp_should_keep_alive(parser)
+# define git_http_parser_pause(parser) (llhttp_pause(parser), GIT_HTTP_PARSER_PAUSED)
+# define git_http_parser_resume(parser) llhttp_resume(parser)
+# define git_http_parser_remain_after_pause(parser) 0
+# define git_http_parser_errno(parser) parser->error
+# define git_http_parser_errmsg(parser, errno) llhttp_get_error_reason(parser)
+
+#else
+# error unknown http-parser
+#endif
+
+#endif
diff --git a/src/libgit2/transports/local.c b/src/libgit2/transports/local.c
index 64c21af..68ff1c1 100644
--- a/src/libgit2/transports/local.c
+++ b/src/libgit2/transports/local.c
@@ -303,6 +303,11 @@ static int local_negotiate_fetch(
GIT_UNUSED(wants);
+ if (wants->depth) {
+ git_error_set(GIT_ERROR_NET, "shallow fetch is not supported by the local transport");
+ return GIT_ENOTSUPPORTED;
+ }
+
/* Fill in the loids */
git_vector_foreach(&t->refs, i, rhead) {
git_object *obj;
@@ -453,7 +458,7 @@ static int local_push(
default:
last = git_error_last();
- if (last && last->message)
+ if (last->klass != GIT_ERROR_NONE)
status->msg = git__strdup(last->message);
else
status->msg = git__strdup("Unspecified error encountered");
diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c
index 5372728..be0cb7b 100644
--- a/src/libgit2/transports/smart.c
+++ b/src/libgit2/transports/smart.c
@@ -249,6 +249,9 @@ static int git_smart__capabilities(unsigned int *capabilities, git_transport *tr
*capabilities = 0;
+ if (t->caps.push_options)
+ *capabilities |= GIT_REMOTE_CAPABILITY_PUSH_OPTIONS;
+
if (t->caps.want_tip_sha1)
*capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID;
@@ -370,17 +373,27 @@ static int git_smart__close(git_transport *transport)
git_vector *common = &t->common;
unsigned int i;
git_pkt *p;
+ git_smart_service_t service;
int ret;
git_smart_subtransport_stream *stream;
const char flush[] = "0000";
+ if (t->direction == GIT_DIRECTION_FETCH) {
+ service = GIT_SERVICE_UPLOADPACK;
+ } else if (t->direction == GIT_DIRECTION_PUSH) {
+ service = GIT_SERVICE_RECEIVEPACK;
+ } else {
+ git_error_set(GIT_ERROR_NET, "invalid direction");
+ return -1;
+ }
+
/*
* If we're still connected at this point and not using RPC,
* we should say goodbye by sending a flush, or git-daemon
* will complain that we disconnected unexpectedly.
*/
if (t->connected && !t->rpc &&
- !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
+ !t->wrapped->action(&stream, t->wrapped, t->url, service)) {
t->current_stream->write(t->current_stream, flush, 4);
}
@@ -513,7 +526,6 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param)
definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
git_vector_free(&t->refs);
git_vector_free(&t->heads);
- t->wrapped->free(t->wrapped);
git__free(t);
return -1;
}
diff --git a/src/libgit2/transports/smart.h b/src/libgit2/transports/smart.h
index 52c7553..c987d93 100644
--- a/src/libgit2/transports/smart.h
+++ b/src/libgit2/transports/smart.h
@@ -38,6 +38,7 @@
#define GIT_CAP_SHALLOW "shallow"
#define GIT_CAP_OBJECT_FORMAT "object-format="
#define GIT_CAP_AGENT "agent="
+#define GIT_CAP_PUSH_OPTIONS "push-options"
extern bool git_smart__ofs_delta_enabled;
@@ -146,7 +147,8 @@ typedef struct transport_smart_caps {
thin_pack:1,
want_tip_sha1:1,
want_reachable_sha1:1,
- shallow:1;
+ shallow:1,
+ push_options:1;
char *object_format;
char *agent;
} transport_smart_caps;
@@ -203,7 +205,7 @@ int git_smart__update_heads(transport_smart *t, git_vector *symrefs);
/* smart_pkt.c */
typedef struct {
git_oid_t oid_type;
- int seen_capabilities: 1;
+ unsigned int seen_capabilities: 1;
} git_pkt_parse_data;
int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen, git_pkt_parse_data *data);
diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c
index 3307acf..7ea8676 100644
--- a/src/libgit2/transports/smart_pkt.c
+++ b/src/libgit2/transports/smart_pkt.c
@@ -536,10 +536,10 @@ static int parse_len(size_t *out, const char *line, size_t linelen)
num[PKT_LEN_SIZE] = '\0';
for (i = 0; i < PKT_LEN_SIZE; ++i) {
- if (!isxdigit(num[i])) {
+ if (!git__isxdigit(num[i])) {
/* Make sure there are no special characters before passing to error message */
for (k = 0; k < PKT_LEN_SIZE; ++k) {
- if(!isprint(num[k])) {
+ if(!git__isprint(num[k])) {
num[k] = '.';
}
}
diff --git a/src/libgit2/transports/smart_protocol.c b/src/libgit2/transports/smart_protocol.c
index c9c422d..df1c190 100644
--- a/src/libgit2/transports/smart_protocol.c
+++ b/src/libgit2/transports/smart_protocol.c
@@ -59,7 +59,7 @@ int git_smart__store_refs(transport_smart *t, int flushes)
return recvd;
if (recvd == 0) {
- git_error_set(GIT_ERROR_NET, "early EOF");
+ git_error_set(GIT_ERROR_NET, "could not read refs from remote repository");
return GIT_EEOF;
}
@@ -194,6 +194,12 @@ int git_smart__detect_caps(
continue;
}
+ if (!git__prefixcmp(ptr, GIT_CAP_PUSH_OPTIONS)) {
+ caps->common = caps->push_options = 1;
+ ptr += strlen(GIT_CAP_PUSH_OPTIONS);
+ continue;
+ }
+
if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) {
caps->common = caps->thin_pack = 1;
ptr += strlen(GIT_CAP_THIN_PACK);
@@ -285,7 +291,7 @@ static int recv_pkt(
if ((ret = git_smart__recv(t)) < 0) {
return ret;
} else if (ret == 0) {
- git_error_set(GIT_ERROR_NET, "early EOF");
+ git_error_set(GIT_ERROR_NET, "could not read from remote repository");
return GIT_EEOF;
}
} while (error);
@@ -778,33 +784,54 @@ done:
static int gen_pktline(git_str *buf, git_push *push)
{
push_spec *spec;
+ char *option;
size_t i, len;
- char old_id[GIT_OID_SHA1_HEXSIZE+1], new_id[GIT_OID_SHA1_HEXSIZE+1];
-
- old_id[GIT_OID_SHA1_HEXSIZE] = '\0'; new_id[GIT_OID_SHA1_HEXSIZE] = '\0';
+ char old_id[GIT_OID_MAX_HEXSIZE + 1], new_id[GIT_OID_MAX_HEXSIZE + 1];
+ size_t old_id_len, new_id_len;
git_vector_foreach(&push->specs, i, spec) {
- len = 2*GIT_OID_SHA1_HEXSIZE + 7 + strlen(spec->refspec.dst);
+ len = strlen(spec->refspec.dst) + 7;
if (i == 0) {
- ++len; /* '\0' */
+ /* Need a leading \0 */
+ ++len;
+
if (push->report_status)
len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+
+ if (git_vector_length(&push->remote_push_options) > 0)
+ len += strlen(GIT_CAP_PUSH_OPTIONS) + 1;
+
len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
}
+ old_id_len = git_oid_hexsize(git_oid_type(&spec->roid));
+ new_id_len = git_oid_hexsize(git_oid_type(&spec->loid));
+
+ len += (old_id_len + new_id_len);
+
git_oid_fmt(old_id, &spec->roid);
+ old_id[old_id_len] = '\0';
+
git_oid_fmt(new_id, &spec->loid);
+ new_id[new_id_len] = '\0';
- git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst);
+ git_str_printf(buf, "%04"PRIxZ"%.*s %.*s %s", len,
+ (int)old_id_len, old_id, (int)new_id_len, new_id,
+ spec->refspec.dst);
if (i == 0) {
git_str_putc(buf, '\0');
+
/* Core git always starts their capabilities string with a space */
if (push->report_status) {
git_str_putc(buf, ' ');
git_str_printf(buf, GIT_CAP_REPORT_STATUS);
}
+ if (git_vector_length(&push->remote_push_options) > 0) {
+ git_str_putc(buf, ' ');
+ git_str_printf(buf, GIT_CAP_PUSH_OPTIONS);
+ }
git_str_putc(buf, ' ');
git_str_printf(buf, GIT_CAP_SIDE_BAND_64K);
}
@@ -812,6 +839,13 @@ static int gen_pktline(git_str *buf, git_push *push)
git_str_putc(buf, '\n');
}
+ if (git_vector_length(&push->remote_push_options) > 0) {
+ git_str_printf(buf, "0000");
+ git_vector_foreach(&push->remote_push_options, i, option) {
+ git_str_printf(buf, "%04"PRIxZ"%s", strlen(option) + 4 , option);
+ }
+ }
+
git_str_puts(buf, "0000");
return git_str_oom(buf) ? -1 : 0;
}
@@ -940,7 +974,7 @@ static int parse_report(transport_smart *transport, git_push *push)
}
if (recvd == 0) {
- git_error_set(GIT_ERROR_NET, "early EOF");
+ git_error_set(GIT_ERROR_NET, "could not read report from remote repository");
error = GIT_EEOF;
goto done;
}
@@ -1157,7 +1191,7 @@ int git_smart__push(git_transport *transport, git_push *push)
#ifdef PUSH_DEBUG
{
git_remote_head *head;
- char hex[GIT_OID_SHA1_HEXSIZE+1]; hex[GIT_OID_SHA1_HEXSIZE] = '\0';
+ char hex[GIT_OID_MAX_HEXSIZE+1], hex[GIT_OID_MAX_HEXSIZE] = '\0';
git_vector_foreach(&push->remote->refs, i, head) {
git_oid_fmt(hex, &head->oid);
diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c
index de63d45..3f3a127 100644
--- a/src/libgit2/transports/ssh.c
+++ b/src/libgit2/transports/ssh.c
@@ -5,1090 +5,67 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "ssh.h"
+#include "ssh_exec.h"
+#include "ssh_libssh2.h"
-#ifdef GIT_SSH
-#include <libssh2.h>
-#endif
-
-#include "runtime.h"
-#include "net.h"
-#include "smart.h"
-#include "streams/socket.h"
-#include "sysdir.h"
-
-#include "git2/credential.h"
-#include "git2/sys/credential.h"
-
-#ifdef GIT_SSH
-
-#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
-
-static const char cmd_uploadpack[] = "git-upload-pack";
-static const char cmd_receivepack[] = "git-receive-pack";
-
-typedef struct {
- git_smart_subtransport_stream parent;
- git_stream *io;
- LIBSSH2_SESSION *session;
- LIBSSH2_CHANNEL *channel;
- const char *cmd;
- git_net_url url;
- unsigned sent_command : 1;
-} ssh_stream;
-
-typedef struct {
- git_smart_subtransport parent;
- transport_smart *owner;
- ssh_stream *current_stream;
- git_credential *cred;
- char *cmd_uploadpack;
- char *cmd_receivepack;
-} ssh_subtransport;
-
-static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
-
-static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
-{
- char *ssherr;
- libssh2_session_last_error(session, &ssherr, NULL, 0);
-
- git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr);
-}
-
-/*
- * Create a git protocol request.
- *
- * For example: git-upload-pack '/libgit2/libgit2'
- */
-static int gen_proto(git_str *request, const char *cmd, git_net_url *url)
-{
- const char *repo;
-
- repo = url->path;
-
- if (repo && repo[0] == '/' && repo[1] == '~')
- repo++;
-
- if (!repo || !repo[0]) {
- git_error_set(GIT_ERROR_NET, "malformed git protocol URL");
- return -1;
- }
-
- git_str_puts(request, cmd);
- git_str_puts(request, " '");
- git_str_puts(request, repo);
- git_str_puts(request, "'");
-
- if (git_str_oom(request))
- return -1;
-
- return 0;
-}
-
-static int send_command(ssh_stream *s)
-{
- int error;
- git_str request = GIT_STR_INIT;
-
- error = gen_proto(&request, s->cmd, &s->url);
- if (error < 0)
- goto cleanup;
-
- error = libssh2_channel_exec(s->channel, request.ptr);
- if (error < LIBSSH2_ERROR_NONE) {
- ssh_error(s->session, "SSH could not execute request");
- goto cleanup;
- }
-
- s->sent_command = 1;
-
-cleanup:
- git_str_dispose(&request);
- return error;
-}
-
-static int ssh_stream_read(
- git_smart_subtransport_stream *stream,
- char *buffer,
- size_t buf_size,
- size_t *bytes_read)
-{
- int rc;
- ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
-
- *bytes_read = 0;
-
- if (!s->sent_command && send_command(s) < 0)
- return -1;
-
- if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
- ssh_error(s->session, "SSH could not read data");
- return -1;
- }
-
- /*
- * If we can't get anything out of stdout, it's typically a
- * not-found error, so read from stderr and signal EOF on
- * stderr.
- */
- if (rc == 0) {
- if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) {
- git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer);
- return GIT_EEOF;
- } else if (rc < LIBSSH2_ERROR_NONE) {
- ssh_error(s->session, "SSH could not read stderr");
- return -1;
- }
- }
-
-
- *bytes_read = rc;
-
- return 0;
-}
-
-static int ssh_stream_write(
- git_smart_subtransport_stream *stream,
- const char *buffer,
- size_t len)
-{
- ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
- size_t off = 0;
- ssize_t ret = 0;
-
- if (!s->sent_command && send_command(s) < 0)
- return -1;
-
- do {
- ret = libssh2_channel_write(s->channel, buffer + off, len - off);
- if (ret < 0)
- break;
-
- off += ret;
-
- } while (off < len);
-
- if (ret < 0) {
- ssh_error(s->session, "SSH could not write data");
- return -1;
- }
-
- return 0;
-}
-
-static void ssh_stream_free(git_smart_subtransport_stream *stream)
-{
- ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
- ssh_subtransport *t;
-
- if (!stream)
- return;
-
- t = OWNING_SUBTRANSPORT(s);
- t->current_stream = NULL;
-
- if (s->channel) {
- libssh2_channel_close(s->channel);
- libssh2_channel_free(s->channel);
- s->channel = NULL;
- }
-
- if (s->session) {
- libssh2_session_disconnect(s->session, "closing transport");
- libssh2_session_free(s->session);
- s->session = NULL;
- }
-
- if (s->io) {
- git_stream_close(s->io);
- git_stream_free(s->io);
- s->io = NULL;
- }
-
- git_net_url_dispose(&s->url);
- git__free(s);
-}
-
-static int ssh_stream_alloc(
- ssh_subtransport *t,
- const char *cmd,
- git_smart_subtransport_stream **stream)
-{
- ssh_stream *s;
-
- GIT_ASSERT_ARG(stream);
-
- s = git__calloc(sizeof(ssh_stream), 1);
- GIT_ERROR_CHECK_ALLOC(s);
-
- s->parent.subtransport = &t->parent;
- s->parent.read = ssh_stream_read;
- s->parent.write = ssh_stream_write;
- s->parent.free = ssh_stream_free;
-
- s->cmd = cmd;
-
- *stream = &s->parent;
- return 0;
-}
-
-static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) {
- int rc = LIBSSH2_ERROR_NONE;
-
- struct libssh2_agent_publickey *curr, *prev = NULL;
-
- LIBSSH2_AGENT *agent = libssh2_agent_init(session);
-
- if (agent == NULL)
- return -1;
-
- rc = libssh2_agent_connect(agent);
-
- if (rc != LIBSSH2_ERROR_NONE) {
- rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
- goto shutdown;
- }
-
- rc = libssh2_agent_list_identities(agent);
-
- if (rc != LIBSSH2_ERROR_NONE)
- goto shutdown;
-
- while (1) {
- rc = libssh2_agent_get_identity(agent, &curr, prev);
-
- if (rc < 0)
- goto shutdown;
+#include "transports/smart.h"
- /* rc is set to 1 whenever the ssh agent ran out of keys to check.
- * Set the error code to authentication failure rather than erroring
- * out with an untranslatable error code.
- */
- if (rc == 1) {
- rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
- goto shutdown;
- }
-
- rc = libssh2_agent_userauth(agent, c->username, curr);
-
- if (rc == 0)
- break;
-
- prev = curr;
- }
-
-shutdown:
-
- if (rc != LIBSSH2_ERROR_NONE)
- ssh_error(session, "error authenticating");
-
- libssh2_agent_disconnect(agent);
- libssh2_agent_free(agent);
-
- return rc;
-}
-
-static int _git_ssh_authenticate_session(
- LIBSSH2_SESSION *session,
- git_credential *cred)
-{
- int rc;
-
- do {
- git_error_clear();
- switch (cred->credtype) {
- case GIT_CREDENTIAL_USERPASS_PLAINTEXT: {
- git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred;
- rc = libssh2_userauth_password(session, c->username, c->password);
- break;
- }
- case GIT_CREDENTIAL_SSH_KEY: {
- git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
-
- if (c->privatekey)
- rc = libssh2_userauth_publickey_fromfile(
- session, c->username, c->publickey,
- c->privatekey, c->passphrase);
- else
- rc = ssh_agent_auth(session, c);
-
- break;
- }
- case GIT_CREDENTIAL_SSH_CUSTOM: {
- git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred;
-
- rc = libssh2_userauth_publickey(
- session, c->username, (const unsigned char *)c->publickey,
- c->publickey_len, c->sign_callback, &c->payload);
- break;
- }
- case GIT_CREDENTIAL_SSH_INTERACTIVE: {
- void **abstract = libssh2_session_abstract(session);
- git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred;
-
- /* ideally, we should be able to set this by calling
- * libssh2_session_init_ex() instead of libssh2_session_init().
- * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
- * allows you to pass the `abstract` as part of the call, whereas
- * libssh2_userauth_keyboard_interactive() does not!
- *
- * The only way to set the `abstract` pointer is by calling
- * libssh2_session_abstract(), which will replace the existing
- * pointer as is done below. This is safe for now (at time of writing),
- * but may not be valid in future.
- */
- *abstract = c->payload;
-
- rc = libssh2_userauth_keyboard_interactive(
- session, c->username, c->prompt_callback);
- break;
- }
-#ifdef GIT_SSH_MEMORY_CREDENTIALS
- case GIT_CREDENTIAL_SSH_MEMORY: {
- git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
-
- GIT_ASSERT(c->username);
- GIT_ASSERT(c->privatekey);
-
- rc = libssh2_userauth_publickey_frommemory(
- session,
- c->username,
- strlen(c->username),
- c->publickey,
- c->publickey ? strlen(c->publickey) : 0,
- c->privatekey,
- strlen(c->privatekey),
- c->passphrase);
- break;
- }
-#endif
- default:
- rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
- }
- } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
-
- if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED ||
- rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED ||
- rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED)
- return GIT_EAUTH;
-
- if (rc != LIBSSH2_ERROR_NONE) {
- if (!git_error_last())
- ssh_error(session, "Failed to authenticate SSH session");
- return -1;
- }
-
- return 0;
-}
-
-static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods)
-{
- int error, no_callback = 0;
- git_credential *cred = NULL;
-
- if (!t->owner->connect_opts.callbacks.credentials) {
- no_callback = 1;
- } else {
- error = t->owner->connect_opts.callbacks.credentials(
- &cred,
- t->owner->url,
- user,
- auth_methods,
- t->owner->connect_opts.callbacks.payload);
-
- if (error == GIT_PASSTHROUGH) {
- no_callback = 1;
- } else if (error < 0) {
- return error;
- } else if (!cred) {
- git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials");
- return -1;
- }
- }
-
- if (no_callback) {
- git_error_set(GIT_ERROR_SSH, "authentication required but no callback set");
- return GIT_EAUTH;
- }
-
- if (!(cred->credtype & auth_methods)) {
- cred->free(cred);
- git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type");
- return GIT_EAUTH;
- }
-
- *out = cred;
-
- return 0;
-}
-
-#define SSH_DIR ".ssh"
-#define KNOWN_HOSTS_FILE "known_hosts"
-
-/*
- * Load the known_hosts file.
- *
- * Returns success but leaves the output NULL if we couldn't find the file.
- */
-static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session)
-{
- git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT;
- LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
- int error;
-
- GIT_ASSERT_ARG(hosts);
-
- if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 ||
- (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0)
- goto out;
-
- if ((known_hosts = libssh2_knownhost_init(session)) == NULL) {
- ssh_error(session, "error initializing known hosts");
- error = -1;
- goto out;
- }
-
- /*
- * Try to read the file and consider not finding it as not trusting the
- * host rather than an error.
- */
- error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH);
- if (error == LIBSSH2_ERROR_FILE)
- error = 0;
- if (error < 0)
- ssh_error(session, "error reading known_hosts");
-
-out:
- *hosts = known_hosts;
-
- git_str_dispose(&sshdir);
- git_str_dispose(&path);
-
- return error;
-}
-
-static void add_hostkey_pref_if_avail(
- LIBSSH2_KNOWNHOSTS *known_hosts,
- const char *hostname,
- int port,
- git_str *prefs,
- int type,
- const char *type_name)
-{
- struct libssh2_knownhost *host = NULL;
- const char key = '\0';
- int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type;
- int error;
-
- error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host);
- if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
- if (git_str_len(prefs) > 0) {
- git_str_putc(prefs, ',');
- }
- git_str_puts(prefs, type_name);
- }
-}
-
-/*
- * We figure out what kind of key we want to ask the remote for by trying to
- * look it up with a nonsense key and using that mismatch to figure out what key
- * we do have stored for the host.
- *
- * Populates prefs with the string to pass to libssh2_session_method_pref.
- */
-static void find_hostkey_preference(
- LIBSSH2_KNOWNHOSTS *known_hosts,
- const char *hostname,
- int port,
- git_str *prefs)
-{
- /*
- * The order here is important as it indicates the priority of what will
- * be preferred.
- */
-#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
- add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519");
-#endif
-#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
- add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256");
- add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384");
- add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521");
-#endif
- add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa");
-}
-
-static int _git_ssh_session_create(
- LIBSSH2_SESSION **session,
- LIBSSH2_KNOWNHOSTS **hosts,
- const char *hostname,
- int port,
- git_stream *io)
-{
- git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
- LIBSSH2_SESSION *s;
- LIBSSH2_KNOWNHOSTS *known_hosts;
- git_str prefs = GIT_STR_INIT;
- int rc = 0;
-
- GIT_ASSERT_ARG(session);
- GIT_ASSERT_ARG(hosts);
-
- s = libssh2_session_init();
- if (!s) {
- git_error_set(GIT_ERROR_NET, "failed to initialize SSH session");
- return -1;
- }
-
- if ((rc = load_known_hosts(&known_hosts, s)) < 0) {
- ssh_error(s, "error loading known_hosts");
- libssh2_session_free(s);
- return -1;
- }
-
- find_hostkey_preference(known_hosts, hostname, port, &prefs);
- if (git_str_len(&prefs) > 0) {
- do {
- rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs));
- } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
- if (rc != LIBSSH2_ERROR_NONE) {
- ssh_error(s, "failed to set hostkey preference");
- goto on_error;
- }
- }
- git_str_dispose(&prefs);
-
- do {
- rc = libssh2_session_handshake(s, socket->s);
- } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
-
- if (rc != LIBSSH2_ERROR_NONE) {
- ssh_error(s, "failed to start SSH session");
- goto on_error;
- }
-
- libssh2_session_set_blocking(s, 1);
-
- *session = s;
- *hosts = known_hosts;
-
- return 0;
-
-on_error:
- libssh2_knownhost_free(known_hosts);
- libssh2_session_free(s);
- return -1;
-}
-
-
-/*
- * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on
- * the type of key that libssh2_session_hostkey returns.
- */
-static int fingerprint_type_mask(int keytype)
-{
- int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW;
- return mask;
-
- switch (keytype) {
- case LIBSSH2_HOSTKEY_TYPE_RSA:
- mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA;
- break;
- case LIBSSH2_HOSTKEY_TYPE_DSS:
- mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS;
- break;
-#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
- case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
- mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
- break;
- case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
- mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
- break;
- case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
- mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
- break;
-#endif
-#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
- case LIBSSH2_HOSTKEY_TYPE_ED25519:
- mask |= LIBSSH2_KNOWNHOST_KEY_ED25519;
- break;
-#endif
- }
-
- return mask;
-}
-
-/*
- * Check the host against the user's known_hosts file.
- *
- * Returns 1/0 for valid/''not-valid or <0 for an error
- */
-static int check_against_known_hosts(
- LIBSSH2_SESSION *session,
- LIBSSH2_KNOWNHOSTS *known_hosts,
- const char *hostname,
- int port,
- const char *key,
- size_t key_len,
- int key_type)
-{
- int check, typemask, ret = 0;
- struct libssh2_knownhost *host = NULL;
-
- if (known_hosts == NULL)
- return 0;
-
- typemask = fingerprint_type_mask(key_type);
- check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host);
- if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) {
- ssh_error(session, "error checking for known host");
- return -1;
- }
-
- ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0;
-
- return ret;
-}
-
-/*
- * Perform the check for the session's certificate against known hosts if
- * possible and then ask the user if they have a callback.
- *
- * Returns 1/0 for valid/not-valid or <0 for an error
- */
-static int check_certificate(
- LIBSSH2_SESSION *session,
- LIBSSH2_KNOWNHOSTS *known_hosts,
- git_transport_certificate_check_cb check_cb,
- void *check_cb_payload,
- const char *host,
- int port)
-{
- git_cert_hostkey cert = {{ 0 }};
- const char *key;
- size_t cert_len;
- int cert_type, cert_valid = 0, error = 0;
-
- if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) {
- ssh_error(session, "failed to retrieve hostkey");
- return -1;
- }
-
- if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0)
- return -1;
-
- cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
- if (key != NULL) {
- cert.type |= GIT_CERT_SSH_RAW;
- cert.hostkey = key;
- cert.hostkey_len = cert_len;
- switch (cert_type) {
- case LIBSSH2_HOSTKEY_TYPE_RSA:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA;
- break;
- case LIBSSH2_HOSTKEY_TYPE_DSS:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS;
- break;
-
-#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
- case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256;
- break;
- case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384;
- break;
- case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521;
- break;
-#endif
-
-#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
- case LIBSSH2_HOSTKEY_TYPE_ED25519:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519;
- break;
-#endif
- default:
- cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN;
- }
- }
-
-#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
- key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
- if (key != NULL) {
- cert.type |= GIT_CERT_SSH_SHA256;
- memcpy(&cert.hash_sha256, key, 32);
- }
-#endif
-
- key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
- if (key != NULL) {
- cert.type |= GIT_CERT_SSH_SHA1;
- memcpy(&cert.hash_sha1, key, 20);
- }
-
- key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
- if (key != NULL) {
- cert.type |= GIT_CERT_SSH_MD5;
- memcpy(&cert.hash_md5, key, 16);
- }
-
- if (cert.type == 0) {
- git_error_set(GIT_ERROR_SSH, "unable to get the host key");
- return -1;
- }
-
- git_error_clear();
- error = 0;
- if (!cert_valid) {
- git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey");
- error = GIT_ECERTIFICATE;
- }
-
- if (check_cb != NULL) {
- git_cert_hostkey *cert_ptr = &cert;
- git_error_state previous_error = {0};
-
- git_error_state_capture(&previous_error, error);
- error = check_cb((git_cert *) cert_ptr, cert_valid, host, check_cb_payload);
- if (error == GIT_PASSTHROUGH) {
- error = git_error_state_restore(&previous_error);
- } else if (error < 0 && !git_error_last()) {
- git_error_set(GIT_ERROR_NET, "unknown remote host key");
- }
-
- git_error_state_free(&previous_error);
- }
-
- return error;
-}
-
-#define SSH_DEFAULT_PORT "22"
-
-static int _git_ssh_setup_conn(
- ssh_subtransport *t,
- const char *url,
- const char *cmd,
- git_smart_subtransport_stream **stream)
-{
- int auth_methods, error = 0, port;
- ssh_stream *s;
- git_credential *cred = NULL;
- LIBSSH2_SESSION *session=NULL;
- LIBSSH2_CHANNEL *channel=NULL;
- LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
-
- t->current_stream = NULL;
-
- *stream = NULL;
- if (ssh_stream_alloc(t, cmd, stream) < 0)
- return -1;
-
- s = (ssh_stream *)*stream;
- s->session = NULL;
- s->channel = NULL;
-
- if ((error = git_net_url_parse_standard_or_scp(&s->url, url)) < 0 ||
- (error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 ||
- (error = git_stream_connect(s->io)) < 0)
- goto done;
-
- /*
- * Try to parse the port as a number, if we can't then fall back to
- * default. It would be nice if we could get the port that was resolved
- * as part of the stream connection, but that's not something that's
- * exposed.
- */
- if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0) {
- git_error_set(GIT_ERROR_NET, "invalid port to ssh: %s", s->url.port);
- error = -1;
- goto done;
- }
-
- if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0)
- goto done;
-
- if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0)
- goto done;
-
- /* we need the username to ask for auth methods */
- if (!s->url.username) {
- if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0)
- goto done;
-
- s->url.username = git__strdup(((git_credential_username *) cred)->username);
- cred->free(cred);
- cred = NULL;
- if (!s->url.username)
- goto done;
- } else if (s->url.username && s->url.password) {
- if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0)
- goto done;
- }
-
- if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
- goto done;
-
- error = GIT_EAUTH;
- /* if we already have something to try */
- if (cred && auth_methods & cred->credtype)
- error = _git_ssh_authenticate_session(session, cred);
-
- while (error == GIT_EAUTH) {
- if (cred) {
- cred->free(cred);
- cred = NULL;
- }
-
- if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0)
- goto done;
-
- if (strcmp(s->url.username, git_credential_get_username(cred))) {
- git_error_set(GIT_ERROR_SSH, "username does not match previous request");
- error = -1;
- goto done;
- }
-
- error = _git_ssh_authenticate_session(session, cred);
-
- if (error == GIT_EAUTH) {
- /* refresh auth methods */
- if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
- goto done;
- else
- error = GIT_EAUTH;
- }
- }
-
- if (error < 0)
- goto done;
-
- channel = libssh2_channel_open_session(session);
- if (!channel) {
- error = -1;
- ssh_error(session, "Failed to open SSH channel");
- goto done;
- }
-
- libssh2_channel_set_blocking(channel, 1);
-
- s->session = session;
- s->channel = channel;
-
- t->current_stream = s;
-
-done:
- if (known_hosts)
- libssh2_knownhost_free(known_hosts);
-
- if (error < 0) {
- ssh_stream_free(*stream);
-
- if (session)
- libssh2_session_free(session);
- }
-
- if (cred)
- cred->free(cred);
-
- return error;
-}
-
-static int ssh_uploadpack_ls(
- ssh_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
-{
- const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
-
- return _git_ssh_setup_conn(t, url, cmd, stream);
-}
-
-static int ssh_uploadpack(
- ssh_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
-{
- GIT_UNUSED(url);
-
- if (t->current_stream) {
- *stream = &t->current_stream->parent;
- return 0;
- }
-
- git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK");
- return -1;
-}
-
-static int ssh_receivepack_ls(
- ssh_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
-{
- const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
-
-
- return _git_ssh_setup_conn(t, url, cmd, stream);
-}
-
-static int ssh_receivepack(
- ssh_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
-{
- GIT_UNUSED(url);
-
- if (t->current_stream) {
- *stream = &t->current_stream->parent;
- return 0;
- }
-
- git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK");
- return -1;
-}
-
-static int _ssh_action(
- git_smart_subtransport_stream **stream,
- git_smart_subtransport *subtransport,
- const char *url,
- git_smart_service_t action)
-{
- ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
-
- switch (action) {
- case GIT_SERVICE_UPLOADPACK_LS:
- return ssh_uploadpack_ls(t, url, stream);
-
- case GIT_SERVICE_UPLOADPACK:
- return ssh_uploadpack(t, url, stream);
-
- case GIT_SERVICE_RECEIVEPACK_LS:
- return ssh_receivepack_ls(t, url, stream);
-
- case GIT_SERVICE_RECEIVEPACK:
- return ssh_receivepack(t, url, stream);
- }
+int git_smart_subtransport_ssh(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *param)
+{
+#ifdef GIT_SSH_LIBSSH2
+ return git_smart_subtransport_ssh_libssh2(out, owner, param);
+#elif GIT_SSH_EXEC
+ return git_smart_subtransport_ssh_exec(out, owner, param);
+#else
+ GIT_UNUSED(out);
+ GIT_UNUSED(owner);
+ GIT_UNUSED(param);
- *stream = NULL;
+ git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport; library was built without SSH support");
return -1;
-}
-
-static int _ssh_close(git_smart_subtransport *subtransport)
-{
- ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
-
- GIT_ASSERT(!t->current_stream);
-
- GIT_UNUSED(t);
-
- return 0;
-}
-
-static void _ssh_free(git_smart_subtransport *subtransport)
-{
- ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
-
- git__free(t->cmd_uploadpack);
- git__free(t->cmd_receivepack);
- git__free(t);
-}
-
-#define SSH_AUTH_PUBLICKEY "publickey"
-#define SSH_AUTH_PASSWORD "password"
-#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
-
-static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
-{
- const char *list, *ptr;
-
- *out = 0;
-
- list = libssh2_userauth_list(session, username, strlen(username));
-
- /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
- if (list == NULL && !libssh2_userauth_authenticated(session)) {
- ssh_error(session, "remote rejected authentication");
- return GIT_EAUTH;
- }
-
- ptr = list;
- while (ptr) {
- if (*ptr == ',')
- ptr++;
-
- if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
- *out |= GIT_CREDENTIAL_SSH_KEY;
- *out |= GIT_CREDENTIAL_SSH_CUSTOM;
-#ifdef GIT_SSH_MEMORY_CREDENTIALS
- *out |= GIT_CREDENTIAL_SSH_MEMORY;
#endif
- ptr += strlen(SSH_AUTH_PUBLICKEY);
- continue;
- }
-
- if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
- *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
- ptr += strlen(SSH_AUTH_PASSWORD);
- continue;
- }
-
- if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
- *out |= GIT_CREDENTIAL_SSH_INTERACTIVE;
- ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
- continue;
- }
-
- /* Skip it if we don't know it */
- ptr = strchr(ptr, ',');
- }
-
- return 0;
}
-#endif
-int git_smart_subtransport_ssh(
- git_smart_subtransport **out, git_transport *owner, void *param)
+static int transport_set_paths(git_transport *t, git_strarray *paths)
{
-#ifdef GIT_SSH
- ssh_subtransport *t;
+ transport_smart *smart = (transport_smart *)t;
- GIT_ASSERT_ARG(out);
-
- GIT_UNUSED(param);
-
- t = git__calloc(sizeof(ssh_subtransport), 1);
- GIT_ERROR_CHECK_ALLOC(t);
-
- t->owner = (transport_smart *)owner;
- t->parent.action = _ssh_action;
- t->parent.close = _ssh_close;
- t->parent.free = _ssh_free;
-
- *out = (git_smart_subtransport *) t;
- return 0;
+#ifdef GIT_SSH_LIBSSH2
+ return git_smart_subtransport_ssh_libssh2_set_paths(
+ (git_smart_subtransport *)smart->wrapped,
+ paths->strings[0],
+ paths->strings[1]);
+#elif GIT_SSH_EXEC
+ return git_smart_subtransport_ssh_exec_set_paths(
+ (git_smart_subtransport *)smart->wrapped,
+ paths->strings[0],
+ paths->strings[1]);
#else
- GIT_UNUSED(owner);
- GIT_UNUSED(param);
-
- GIT_ASSERT_ARG(out);
- *out = NULL;
+ GIT_UNUSED(t);
+ GIT_UNUSED(smart);
+ GIT_UNUSED(paths);
- git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support");
+ GIT_ASSERT(!"cannot create SSH library; library was built without SSH support");
return -1;
#endif
}
-int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
+int git_transport_ssh_with_paths(
+ git_transport **out,
+ git_remote *owner,
+ void *payload)
{
-#ifdef GIT_SSH
git_strarray *paths = (git_strarray *) payload;
git_transport *transport;
- transport_smart *smart;
- ssh_subtransport *t;
int error;
+
git_smart_subtransport_definition ssh_definition = {
git_smart_subtransport_ssh,
0, /* no RPC */
- NULL,
+ NULL
};
if (paths->count != 2) {
@@ -1099,49 +76,10 @@ int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *p
if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
return error;
- smart = (transport_smart *) transport;
- t = (ssh_subtransport *) smart->wrapped;
-
- t->cmd_uploadpack = git__strdup(paths->strings[0]);
- GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack);
- t->cmd_receivepack = git__strdup(paths->strings[1]);
- GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack);
+ if ((error = transport_set_paths(transport, paths)) < 0)
+ return error;
*out = transport;
return 0;
-#else
- GIT_UNUSED(owner);
- GIT_UNUSED(payload);
-
- GIT_ASSERT_ARG(out);
- *out = NULL;
-
- git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support");
- return -1;
-#endif
-}
-
-#ifdef GIT_SSH
-static void shutdown_ssh(void)
-{
- libssh2_exit();
}
-#endif
-
-int git_transport_ssh_global_init(void)
-{
-#ifdef GIT_SSH
- if (libssh2_init(0) < 0) {
- git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2");
- return -1;
- }
-
- return git_runtime_shutdown_register(shutdown_ssh);
-#else
-
- /* Nothing to initialize */
- return 0;
-
-#endif
-}
diff --git a/src/libgit2/transports/ssh.h b/src/libgit2/transports/ssh.h
deleted file mode 100644
index d3e741f..0000000
--- a/src/libgit2/transports/ssh.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * 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_transports_ssh_h__
-#define INCLUDE_transports_ssh_h__
-
-#include "common.h"
-
-int git_transport_ssh_global_init(void);
-
-#endif
diff --git a/src/libgit2/transports/ssh_exec.c b/src/libgit2/transports/ssh_exec.c
new file mode 100644
index 0000000..a09c1db
--- /dev/null
+++ b/src/libgit2/transports/ssh_exec.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "ssh_exec.h"
+
+#ifdef GIT_SSH_EXEC
+
+#include "common.h"
+
+#include "config.h"
+#include "net.h"
+#include "path.h"
+#include "futils.h"
+#include "process.h"
+#include "transports/smart.h"
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+} ssh_exec_subtransport_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+
+ ssh_exec_subtransport_stream *current_stream;
+
+ char *cmd_uploadpack;
+ char *cmd_receivepack;
+
+ git_smart_service_t action;
+ git_process *process;
+} ssh_exec_subtransport;
+
+static int ssh_exec_subtransport_stream_read(
+ git_smart_subtransport_stream *s,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ ssh_exec_subtransport *transport;
+ ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
+ ssize_t ret;
+
+ GIT_ASSERT_ARG(stream);
+ GIT_ASSERT(stream->parent.subtransport);
+
+ transport = (ssh_exec_subtransport *)stream->parent.subtransport;
+
+ if ((ret = git_process_read(transport->process, buffer, buf_size)) < 0) {
+ return (int)ret;
+ }
+
+ *bytes_read = (size_t)ret;
+ return 0;
+}
+
+static int ssh_exec_subtransport_stream_write(
+ git_smart_subtransport_stream *s,
+ const char *buffer,
+ size_t len)
+{
+ ssh_exec_subtransport *transport;
+ ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
+ ssize_t ret;
+
+ GIT_ASSERT(stream && stream->parent.subtransport);
+
+ transport = (ssh_exec_subtransport *)stream->parent.subtransport;
+
+ while (len > 0) {
+ if ((ret = git_process_write(transport->process, buffer, len)) < 0)
+ return (int)ret;
+
+ len -= ret;
+ }
+
+ return 0;
+}
+
+static void ssh_exec_subtransport_stream_free(git_smart_subtransport_stream *s)
+{
+ ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s;
+
+ git__free(stream);
+}
+
+static int ssh_exec_subtransport_stream_init(
+ ssh_exec_subtransport_stream **out,
+ ssh_exec_subtransport *transport)
+{
+ GIT_ASSERT_ARG(out);
+
+ *out = git__calloc(sizeof(ssh_exec_subtransport_stream), 1);
+ GIT_ERROR_CHECK_ALLOC(*out);
+
+ (*out)->parent.subtransport = &transport->parent;
+ (*out)->parent.read = ssh_exec_subtransport_stream_read;
+ (*out)->parent.write = ssh_exec_subtransport_stream_write;
+ (*out)->parent.free = ssh_exec_subtransport_stream_free;
+
+ return 0;
+}
+
+GIT_INLINE(int) ensure_transport_state(
+ ssh_exec_subtransport *transport,
+ git_smart_service_t expected,
+ git_smart_service_t next)
+{
+ if (transport->action != expected && transport->action != next) {
+ git_error_set(GIT_ERROR_NET, "invalid transport state");
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_ssh_cmdline(
+ git_str *out,
+ ssh_exec_subtransport *transport,
+ git_net_url *url,
+ const char *command)
+{
+ git_remote *remote = ((transport_smart *)transport->owner)->owner;
+ git_repository *repo = remote->repo;
+ git_config *cfg;
+ git_str ssh_cmd = GIT_STR_INIT;
+ const char *default_ssh_cmd = "ssh";
+ int error;
+
+ /*
+ * Safety check: like git, we forbid paths that look like an
+ * option as that could lead to injection to ssh that can make
+ * us do unexpected things
+ */
+ if (git_process__is_cmdline_option(url->username)) {
+ git_error_set(GIT_ERROR_NET, "cannot ssh: username '%s' is ambiguous with command-line option", url->username);
+ return -1;
+ } else if (git_process__is_cmdline_option(url->host)) {
+ git_error_set(GIT_ERROR_NET, "cannot ssh: host '%s' is ambiguous with command-line option", url->host);
+ return -1;
+ } else if (git_process__is_cmdline_option(url->path)) {
+ git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", url->path);
+ return -1;
+ }
+
+ if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
+ return error;
+
+ if ((error = git__getenv(&ssh_cmd, "GIT_SSH")) == 0)
+ ;
+ else if (error != GIT_ENOTFOUND)
+ goto done;
+ else if ((error = git_config__get_string_buf(&ssh_cmd, cfg, "core.sshcommand")) < 0 && error != GIT_ENOTFOUND)
+ goto done;
+
+ error = git_str_printf(out, "%s -p %s \"%s%s%s\" \"%s\" \"%s\"",
+ ssh_cmd.size > 0 ? ssh_cmd.ptr : default_ssh_cmd,
+ url->port,
+ url->username ? url->username : "",
+ url->username ? "@" : "",
+ url->host,
+ command,
+ url->path);
+
+done:
+ git_str_dispose(&ssh_cmd);
+ git_config_free(cfg);
+ return error;
+}
+
+static int start_ssh(
+ ssh_exec_subtransport *transport,
+ git_smart_service_t action,
+ const char *sshpath)
+{
+ const char *env[] = { "GIT_DIR=" };
+
+ git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT;
+ git_net_url url = GIT_NET_URL_INIT;
+ git_str ssh_cmdline = GIT_STR_INIT;
+ const char *command;
+ int error;
+
+ process_opts.capture_in = 1;
+ process_opts.capture_out = 1;
+ process_opts.capture_err = 0;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ command = transport->cmd_uploadpack ?
+ transport->cmd_uploadpack : "git-upload-pack";
+ break;
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ command = transport->cmd_receivepack ?
+ transport->cmd_receivepack : "git-receive-pack";
+ break;
+ default:
+ git_error_set(GIT_ERROR_NET, "invalid action");
+ error = -1;
+ goto done;
+ }
+
+ if (git_net_str_is_url(sshpath))
+ error = git_net_url_parse(&url, sshpath);
+ else
+ error = git_net_url_parse_scp(&url, sshpath);
+
+ if (error < 0)
+ goto done;
+
+ if ((error = get_ssh_cmdline(&ssh_cmdline, transport, &url, command)) < 0)
+ goto done;
+
+ if ((error = git_process_new_from_cmdline(&transport->process,
+ ssh_cmdline.ptr, env, ARRAY_SIZE(env), &process_opts)) < 0 ||
+ (error = git_process_start(transport->process)) < 0) {
+ git_process_free(transport->process);
+ transport->process = NULL;
+ goto done;
+ }
+
+done:
+ git_str_dispose(&ssh_cmdline);
+ git_net_url_dispose(&url);
+ return error;
+}
+
+static int ssh_exec_subtransport_action(
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *t,
+ const char *sshpath,
+ git_smart_service_t action)
+{
+ ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
+ ssh_exec_subtransport_stream *stream = NULL;
+ git_smart_service_t expected;
+ int error;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ if ((error = ensure_transport_state(transport, 0, 0)) < 0 ||
+ (error = ssh_exec_subtransport_stream_init(&stream, transport)) < 0 ||
+ (error = start_ssh(transport, action, sshpath)) < 0)
+ goto on_error;
+
+ transport->current_stream = stream;
+ break;
+
+ case GIT_SERVICE_UPLOADPACK:
+ case GIT_SERVICE_RECEIVEPACK:
+ expected = (action == GIT_SERVICE_UPLOADPACK) ?
+ GIT_SERVICE_UPLOADPACK_LS : GIT_SERVICE_RECEIVEPACK_LS;
+
+ if ((error = ensure_transport_state(transport, expected, action)) < 0)
+ goto on_error;
+
+ break;
+
+ default:
+ git_error_set(GIT_ERROR_INVALID, "invalid service request");
+ goto on_error;
+ }
+
+ transport->action = action;
+ *out = &transport->current_stream->parent;
+
+ return 0;
+
+on_error:
+ if (stream != NULL)
+ ssh_exec_subtransport_stream_free(&stream->parent);
+
+ return -1;
+}
+
+static int ssh_exec_subtransport_close(git_smart_subtransport *t)
+{
+ ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
+
+ if (transport->process) {
+ git_process_close(transport->process);
+ git_process_free(transport->process);
+ transport->process = NULL;
+ }
+
+ transport->action = 0;
+
+ return 0;
+}
+
+static void ssh_exec_subtransport_free(git_smart_subtransport *t)
+{
+ ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t;
+
+ git__free(transport->cmd_uploadpack);
+ git__free(transport->cmd_receivepack);
+ git__free(transport);
+}
+
+int git_smart_subtransport_ssh_exec(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *payload)
+{
+ ssh_exec_subtransport *transport;
+
+ GIT_UNUSED(payload);
+
+ transport = git__calloc(sizeof(ssh_exec_subtransport), 1);
+ GIT_ERROR_CHECK_ALLOC(transport);
+
+ transport->owner = owner;
+ transport->parent.action = ssh_exec_subtransport_action;
+ transport->parent.close = ssh_exec_subtransport_close;
+ transport->parent.free = ssh_exec_subtransport_free;
+
+ *out = (git_smart_subtransport *) transport;
+ return 0;
+}
+
+int git_smart_subtransport_ssh_exec_set_paths(
+ git_smart_subtransport *subtransport,
+ const char *cmd_uploadpack,
+ const char *cmd_receivepack)
+{
+ ssh_exec_subtransport *t = (ssh_exec_subtransport *)subtransport;
+
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
+
+ t->cmd_uploadpack = git__strdup(cmd_uploadpack);
+ GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack);
+
+ t->cmd_receivepack = git__strdup(cmd_receivepack);
+ GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack);
+
+ return 0;
+}
+
+#endif
diff --git a/src/libgit2/transports/ssh_exec.h b/src/libgit2/transports/ssh_exec.h
new file mode 100644
index 0000000..4bcba06
--- /dev/null
+++ b/src/libgit2/transports/ssh_exec.h
@@ -0,0 +1,26 @@
+/*
+ * 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_transports_ssh_exec_h__
+#define INCLUDE_transports_ssh_exec_h__
+
+#include "common.h"
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "git2/sys/transport.h"
+
+int git_smart_subtransport_ssh_exec(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *param);
+
+int git_smart_subtransport_ssh_exec_set_paths(
+ git_smart_subtransport *subtransport,
+ const char *cmd_uploadpack,
+ const char *cmd_receivepack);
+
+#endif
diff --git a/src/libgit2/transports/ssh_libssh2.c b/src/libgit2/transports/ssh_libssh2.c
new file mode 100644
index 0000000..1993ffe
--- /dev/null
+++ b/src/libgit2/transports/ssh_libssh2.c
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "ssh_libssh2.h"
+
+#ifdef GIT_SSH_LIBSSH2
+
+#include <libssh2.h>
+
+#include "runtime.h"
+#include "net.h"
+#include "smart.h"
+#include "process.h"
+#include "streams/socket.h"
+#include "sysdir.h"
+
+#include "git2/credential.h"
+#include "git2/sys/credential.h"
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+extern int git_socket_stream__timeout;
+
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ git_stream *io;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ const char *cmd;
+ git_net_url url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ ssh_stream *current_stream;
+ git_credential *cred;
+ char *cmd_uploadpack;
+ char *cmd_receivepack;
+} ssh_subtransport;
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
+
+static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
+{
+ char *ssherr;
+ libssh2_session_last_error(session, &ssherr, NULL, 0);
+
+ git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr);
+}
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: git-upload-pack '/libgit2/libgit2'
+ */
+static int gen_proto(git_str *request, const char *cmd, git_net_url *url)
+{
+ const char *repo;
+
+ repo = url->path;
+
+ if (repo && repo[0] == '/' && repo[1] == '~')
+ repo++;
+
+ if (!repo || !repo[0]) {
+ git_error_set(GIT_ERROR_NET, "malformed git protocol URL");
+ return -1;
+ }
+
+ git_str_puts(request, cmd);
+ git_str_puts(request, " '");
+ git_str_puts(request, repo);
+ git_str_puts(request, "'");
+
+ if (git_str_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_str request = GIT_STR_INIT;
+
+ error = gen_proto(&request, s->cmd, &s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = libssh2_channel_exec(s->channel, request.ptr);
+ if (error < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not execute request");
+ goto cleanup;
+ }
+
+ s->sent_command = 1;
+
+cleanup:
+ git_str_dispose(&request);
+ return error;
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int rc;
+ ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not read data");
+ return -1;
+ }
+
+ /*
+ * If we can't get anything out of stdout, it's typically a
+ * not-found error, so read from stderr and signal EOF on
+ * stderr.
+ */
+ if (rc == 0) {
+ if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) {
+ git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer);
+ return GIT_EEOF;
+ } else if (rc < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not read stderr");
+ return -1;
+ }
+ }
+
+
+ *bytes_read = rc;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
+ size_t off = 0;
+ ssize_t ret = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ do {
+ ret = libssh2_channel_write(s->channel, buffer + off, len - off);
+ if (ret < 0)
+ break;
+
+ off += ret;
+
+ } while (off < len);
+
+ if (ret < 0) {
+ ssh_error(s->session, "SSH could not write data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
+ ssh_subtransport *t;
+
+ if (!stream)
+ return;
+
+ t = OWNING_SUBTRANSPORT(s);
+ t->current_stream = NULL;
+
+ if (s->channel) {
+ libssh2_channel_close(s->channel);
+ libssh2_channel_free(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->session) {
+ libssh2_session_disconnect(s->session, "closing transport");
+ libssh2_session_free(s->session);
+ s->session = NULL;
+ }
+
+ if (s->io) {
+ git_stream_close(s->io);
+ git_stream_free(s->io);
+ s->io = NULL;
+ }
+
+ git_net_url_dispose(&s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ ssh_subtransport *t,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ ssh_stream *s;
+
+ GIT_ASSERT_ARG(stream);
+
+ s = git__calloc(sizeof(ssh_stream), 1);
+ GIT_ERROR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = ssh_stream_read;
+ s->parent.write = ssh_stream_write;
+ s->parent.free = ssh_stream_free;
+
+ s->cmd = cmd;
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) {
+ int rc = LIBSSH2_ERROR_NONE;
+
+ struct libssh2_agent_publickey *curr, *prev = NULL;
+
+ LIBSSH2_AGENT *agent = libssh2_agent_init(session);
+
+ if (agent == NULL)
+ return -1;
+
+ rc = libssh2_agent_connect(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ goto shutdown;
+ }
+
+ rc = libssh2_agent_list_identities(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ goto shutdown;
+
+ while (1) {
+ rc = libssh2_agent_get_identity(agent, &curr, prev);
+
+ if (rc < 0)
+ goto shutdown;
+
+ /* rc is set to 1 whenever the ssh agent ran out of keys to check.
+ * Set the error code to authentication failure rather than erroring
+ * out with an untranslatable error code.
+ */
+ if (rc == 1) {
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ goto shutdown;
+ }
+
+ rc = libssh2_agent_userauth(agent, c->username, curr);
+
+ if (rc == 0)
+ break;
+
+ prev = curr;
+ }
+
+shutdown:
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ ssh_error(session, "error authenticating");
+
+ libssh2_agent_disconnect(agent);
+ libssh2_agent_free(agent);
+
+ return rc;
+}
+
+static int _git_ssh_authenticate_session(
+ LIBSSH2_SESSION *session,
+ git_credential *cred)
+{
+ int rc;
+
+ do {
+ git_error_clear();
+ switch (cred->credtype) {
+ case GIT_CREDENTIAL_USERPASS_PLAINTEXT: {
+ git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred;
+ rc = libssh2_userauth_password(session, c->username, c->password);
+ break;
+ }
+ case GIT_CREDENTIAL_SSH_KEY: {
+ git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
+
+ if (c->privatekey)
+ rc = libssh2_userauth_publickey_fromfile(
+ session, c->username, c->publickey,
+ c->privatekey, c->passphrase);
+ else
+ rc = ssh_agent_auth(session, c);
+
+ break;
+ }
+ case GIT_CREDENTIAL_SSH_CUSTOM: {
+ git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred;
+
+ rc = libssh2_userauth_publickey(
+ session, c->username, (const unsigned char *)c->publickey,
+ c->publickey_len, c->sign_callback, &c->payload);
+ break;
+ }
+ case GIT_CREDENTIAL_SSH_INTERACTIVE: {
+ void **abstract = libssh2_session_abstract(session);
+ git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred;
+
+ /* ideally, we should be able to set this by calling
+ * libssh2_session_init_ex() instead of libssh2_session_init().
+ * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
+ * allows you to pass the `abstract` as part of the call, whereas
+ * libssh2_userauth_keyboard_interactive() does not!
+ *
+ * The only way to set the `abstract` pointer is by calling
+ * libssh2_session_abstract(), which will replace the existing
+ * pointer as is done below. This is safe for now (at time of writing),
+ * but may not be valid in future.
+ */
+ *abstract = c->payload;
+
+ rc = libssh2_userauth_keyboard_interactive(
+ session, c->username, c->prompt_callback);
+ break;
+ }
+#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS
+ case GIT_CREDENTIAL_SSH_MEMORY: {
+ git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
+
+ GIT_ASSERT(c->username);
+ GIT_ASSERT(c->privatekey);
+
+ rc = libssh2_userauth_publickey_frommemory(
+ session,
+ c->username,
+ strlen(c->username),
+ c->publickey,
+ c->publickey ? strlen(c->publickey) : 0,
+ c->privatekey,
+ strlen(c->privatekey),
+ c->passphrase);
+ break;
+ }
+#endif
+ default:
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ }
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED ||
+ rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED ||
+ rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED)
+ return GIT_EAUTH;
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ if (git_error_last()->klass == GIT_ERROR_NONE)
+ ssh_error(session, "failed to authenticate SSH session");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods)
+{
+ int error, no_callback = 0;
+ git_credential *cred = NULL;
+
+ if (!t->owner->connect_opts.callbacks.credentials) {
+ no_callback = 1;
+ } else {
+ error = t->owner->connect_opts.callbacks.credentials(
+ &cred,
+ t->owner->url,
+ user,
+ auth_methods,
+ t->owner->connect_opts.callbacks.payload);
+
+ if (error == GIT_PASSTHROUGH) {
+ no_callback = 1;
+ } else if (error < 0) {
+ return error;
+ } else if (!cred) {
+ git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials");
+ return -1;
+ }
+ }
+
+ if (no_callback) {
+ git_error_set(GIT_ERROR_SSH, "authentication required but no callback set");
+ return GIT_EAUTH;
+ }
+
+ if (!(cred->credtype & auth_methods)) {
+ cred->free(cred);
+ git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type");
+ return GIT_EAUTH;
+ }
+
+ *out = cred;
+
+ return 0;
+}
+
+#define SSH_DIR ".ssh"
+#define KNOWN_HOSTS_FILE "known_hosts"
+
+/*
+ * Load the known_hosts file.
+ *
+ * Returns success but leaves the output NULL if we couldn't find the file.
+ */
+static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session)
+{
+ git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT;
+ LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
+ int error;
+
+ GIT_ASSERT_ARG(hosts);
+
+ if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 ||
+ (error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0)
+ goto out;
+
+ if ((known_hosts = libssh2_knownhost_init(session)) == NULL) {
+ ssh_error(session, "error initializing known hosts");
+ error = -1;
+ goto out;
+ }
+
+ /*
+ * Try to read the file and consider not finding it as not trusting the
+ * host rather than an error.
+ */
+ error = libssh2_knownhost_readfile(known_hosts, git_str_cstr(&path), LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+ if (error == LIBSSH2_ERROR_FILE)
+ error = 0;
+ if (error < 0)
+ ssh_error(session, "error reading known_hosts");
+
+out:
+ *hosts = known_hosts;
+
+ git_str_dispose(&sshdir);
+ git_str_dispose(&path);
+
+ return error;
+}
+
+static void add_hostkey_pref_if_avail(
+ LIBSSH2_KNOWNHOSTS *known_hosts,
+ const char *hostname,
+ int port,
+ git_str *prefs,
+ int type,
+ const char *type_name)
+{
+ struct libssh2_knownhost *host = NULL;
+ const char key = '\0';
+ int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type;
+ int error;
+
+ error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host);
+ if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
+ if (git_str_len(prefs) > 0) {
+ git_str_putc(prefs, ',');
+ }
+ git_str_puts(prefs, type_name);
+ }
+}
+
+/*
+ * We figure out what kind of key we want to ask the remote for by trying to
+ * look it up with a nonsense key and using that mismatch to figure out what key
+ * we do have stored for the host.
+ *
+ * Populates prefs with the string to pass to libssh2_session_method_pref.
+ */
+static void find_hostkey_preference(
+ LIBSSH2_KNOWNHOSTS *known_hosts,
+ const char *hostname,
+ int port,
+ git_str *prefs)
+{
+ /*
+ * The order here is important as it indicates the priority of what will
+ * be preferred.
+ */
+#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
+ add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519");
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
+ add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256");
+ add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384");
+ add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521");
+#endif
+ add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa");
+}
+
+static int _git_ssh_session_create(
+ LIBSSH2_SESSION **session,
+ LIBSSH2_KNOWNHOSTS **hosts,
+ const char *hostname,
+ int port,
+ git_stream *io)
+{
+ git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
+ LIBSSH2_SESSION *s;
+ LIBSSH2_KNOWNHOSTS *known_hosts;
+ git_str prefs = GIT_STR_INIT;
+ int rc = 0;
+
+ GIT_ASSERT_ARG(session);
+ GIT_ASSERT_ARG(hosts);
+
+ s = libssh2_session_init();
+ if (!s) {
+ git_error_set(GIT_ERROR_NET, "failed to initialize SSH session");
+ return -1;
+ }
+
+ if (git_socket_stream__timeout > 0) {
+ libssh2_session_set_timeout(s, git_socket_stream__timeout);
+ }
+
+ if ((rc = load_known_hosts(&known_hosts, s)) < 0) {
+ ssh_error(s, "error loading known_hosts");
+ libssh2_session_free(s);
+ return -1;
+ }
+
+ find_hostkey_preference(known_hosts, hostname, port, &prefs);
+ if (git_str_len(&prefs) > 0) {
+ do {
+ rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs));
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+ if (rc != LIBSSH2_ERROR_NONE) {
+ ssh_error(s, "failed to set hostkey preference");
+ goto on_error;
+ }
+ }
+ git_str_dispose(&prefs);
+
+ do {
+ rc = libssh2_session_handshake(s, socket->s);
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ ssh_error(s, "failed to start SSH session");
+ goto on_error;
+ }
+
+ libssh2_session_set_blocking(s, 1);
+
+ *session = s;
+ *hosts = known_hosts;
+
+ return 0;
+
+on_error:
+ libssh2_knownhost_free(known_hosts);
+ libssh2_session_free(s);
+ return -1;
+}
+
+
+/*
+ * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on
+ * the type of key that libssh2_session_hostkey returns.
+ */
+static int fingerprint_type_mask(int keytype)
+{
+ int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW;
+ return mask;
+
+ switch (keytype) {
+ case LIBSSH2_HOSTKEY_TYPE_RSA:
+ mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA;
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_DSS:
+ mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS;
+ break;
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
+ case LIBSSH2_HOSTKEY_TYPE_ED25519:
+ mask |= LIBSSH2_KNOWNHOST_KEY_ED25519;
+ break;
+#endif
+ }
+
+ return mask;
+}
+
+/*
+ * Check the host against the user's known_hosts file.
+ *
+ * Returns 1/0 for valid/''not-valid or <0 for an error
+ */
+static int check_against_known_hosts(
+ LIBSSH2_SESSION *session,
+ LIBSSH2_KNOWNHOSTS *known_hosts,
+ const char *hostname,
+ int port,
+ const char *key,
+ size_t key_len,
+ int key_type)
+{
+ int check, typemask, ret = 0;
+ struct libssh2_knownhost *host = NULL;
+
+ if (known_hosts == NULL)
+ return 0;
+
+ typemask = fingerprint_type_mask(key_type);
+ check = libssh2_knownhost_checkp(known_hosts, hostname, port, key, key_len, typemask, &host);
+ if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE) {
+ ssh_error(session, "error checking for known host");
+ return -1;
+ }
+
+ ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0;
+
+ return ret;
+}
+
+/*
+ * Perform the check for the session's certificate against known hosts if
+ * possible and then ask the user if they have a callback.
+ *
+ * Returns 1/0 for valid/not-valid or <0 for an error
+ */
+static int check_certificate(
+ LIBSSH2_SESSION *session,
+ LIBSSH2_KNOWNHOSTS *known_hosts,
+ git_transport_certificate_check_cb check_cb,
+ void *check_cb_payload,
+ const char *host,
+ int port)
+{
+ git_cert_hostkey cert = {{ 0 }};
+ const char *key;
+ size_t cert_len;
+ int cert_type, cert_valid = 0, error = GIT_ECERTIFICATE;
+
+ if ((key = libssh2_session_hostkey(session, &cert_len, &cert_type)) == NULL) {
+ ssh_error(session, "failed to retrieve hostkey");
+ return -1;
+ }
+
+ if ((cert_valid = check_against_known_hosts(session, known_hosts, host, port, key, cert_len, cert_type)) < 0)
+ return -1;
+
+ cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_RAW;
+ cert.hostkey = key;
+ cert.hostkey_len = cert_len;
+ switch (cert_type) {
+ case LIBSSH2_HOSTKEY_TYPE_RSA:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA;
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_DSS:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS;
+ break;
+
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256;
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521;
+ break;
+#endif
+
+#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
+ case LIBSSH2_HOSTKEY_TYPE_ED25519:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519;
+ break;
+#endif
+ default:
+ cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN;
+ }
+ }
+
+#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_SHA256;
+ memcpy(&cert.hash_sha256, key, 32);
+ }
+#endif
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_SHA1;
+ memcpy(&cert.hash_sha1, key, 20);
+ }
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_MD5;
+ memcpy(&cert.hash_md5, key, 16);
+ }
+
+ if (cert.type == 0) {
+ git_error_set(GIT_ERROR_SSH, "unable to get the host key");
+ return -1;
+ }
+
+ if (check_cb != NULL) {
+ git_cert_hostkey *cert_ptr = &cert;
+
+ error = check_cb((git_cert *)cert_ptr, cert_valid, host,
+ check_cb_payload);
+
+ if (error == 0)
+ cert_valid = 1;
+ else if (error != GIT_PASSTHROUGH)
+ cert_valid = 0;
+ }
+
+ if (!cert_valid) {
+ git_error_set(GIT_ERROR_SSH, "invalid or unknown remote ssh hostkey");
+ return (error == GIT_PASSTHROUGH) ? GIT_ECERTIFICATE : error;
+ }
+
+ return 0;
+}
+
+#define SSH_DEFAULT_PORT "22"
+
+static int _git_ssh_setup_conn(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ int auth_methods, error = 0, port;
+ ssh_stream *s;
+ git_credential *cred = NULL;
+ LIBSSH2_SESSION *session=NULL;
+ LIBSSH2_CHANNEL *channel=NULL;
+ LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
+
+ t->current_stream = NULL;
+
+ *stream = NULL;
+ if (ssh_stream_alloc(t, cmd, stream) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+ s->session = NULL;
+ s->channel = NULL;
+
+ if (git_net_str_is_url(url))
+ error = git_net_url_parse(&s->url, url);
+ else
+ error = git_net_url_parse_scp(&s->url, url);
+
+ if (error < 0)
+ goto done;
+
+ /* Safety check: like git, we forbid paths that look like an option as
+ * that could lead to injection on the remote side */
+ if (git_process__is_cmdline_option(s->url.path)) {
+ git_error_set(GIT_ERROR_NET, "cannot ssh: path '%s' is ambiguous with command-line option", s->url.path);
+ error = -1;
+ goto done;
+ }
+
+
+ if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 ||
+ (error = git_stream_connect(s->io)) < 0)
+ goto done;
+
+ /*
+ * Try to parse the port as a number, if we can't then fall back to
+ * default. It would be nice if we could get the port that was resolved
+ * as part of the stream connection, but that's not something that's
+ * exposed.
+ */
+ if (git__strntol32(&port, s->url.port, strlen(s->url.port), NULL, 10) < 0)
+ port = -1;
+
+ if ((error = _git_ssh_session_create(&session, &known_hosts, s->url.host, port, s->io)) < 0)
+ goto done;
+
+ if ((error = check_certificate(session, known_hosts, t->owner->connect_opts.callbacks.certificate_check, t->owner->connect_opts.callbacks.payload, s->url.host, port)) < 0)
+ goto done;
+
+ /* we need the username to ask for auth methods */
+ if (!s->url.username) {
+ if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0)
+ goto done;
+
+ s->url.username = git__strdup(((git_credential_username *) cred)->username);
+ cred->free(cred);
+ cred = NULL;
+ if (!s->url.username)
+ goto done;
+ } else if (s->url.username && s->url.password) {
+ if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0)
+ goto done;
+ }
+
+ if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
+ goto done;
+
+ error = GIT_EAUTH;
+ /* if we already have something to try */
+ if (cred && auth_methods & cred->credtype)
+ error = _git_ssh_authenticate_session(session, cred);
+
+ while (error == GIT_EAUTH) {
+ if (cred) {
+ cred->free(cred);
+ cred = NULL;
+ }
+
+ if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0)
+ goto done;
+
+ if (strcmp(s->url.username, git_credential_get_username(cred))) {
+ git_error_set(GIT_ERROR_SSH, "username does not match previous request");
+ error = -1;
+ goto done;
+ }
+
+ error = _git_ssh_authenticate_session(session, cred);
+
+ if (error == GIT_EAUTH) {
+ /* refresh auth methods */
+ if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
+ goto done;
+ else
+ error = GIT_EAUTH;
+ }
+ }
+
+ if (error < 0)
+ goto done;
+
+ channel = libssh2_channel_open_session(session);
+ if (!channel) {
+ error = -1;
+ ssh_error(session, "Failed to open SSH channel");
+ goto done;
+ }
+
+ libssh2_channel_set_blocking(channel, 1);
+
+ s->session = session;
+ s->channel = channel;
+
+ t->current_stream = s;
+
+done:
+ if (known_hosts)
+ libssh2_knownhost_free(known_hosts);
+
+ if (error < 0) {
+ ssh_stream_free(*stream);
+
+ if (session)
+ libssh2_session_free(session);
+ }
+
+ if (cred)
+ cred->free(cred);
+
+ return error;
+}
+
+static int ssh_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
+}
+
+static int ssh_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int ssh_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
+
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
+}
+
+static int ssh_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _ssh_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return ssh_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return ssh_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _ssh_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
+
+ GIT_ASSERT(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _ssh_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
+
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
+ git__free(t);
+}
+
+#define SSH_AUTH_PUBLICKEY "publickey"
+#define SSH_AUTH_PASSWORD "password"
+#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
+{
+ const char *list, *ptr;
+
+ *out = 0;
+
+ list = libssh2_userauth_list(session, username, strlen(username));
+
+ /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
+ if (list == NULL && !libssh2_userauth_authenticated(session)) {
+ ssh_error(session, "remote rejected authentication");
+ return GIT_EAUTH;
+ }
+
+ ptr = list;
+ while (ptr) {
+ if (*ptr == ',')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
+ *out |= GIT_CREDENTIAL_SSH_KEY;
+ *out |= GIT_CREDENTIAL_SSH_CUSTOM;
+#ifdef GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS
+ *out |= GIT_CREDENTIAL_SSH_MEMORY;
+#endif
+ ptr += strlen(SSH_AUTH_PUBLICKEY);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
+ *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
+ ptr += strlen(SSH_AUTH_PASSWORD);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
+ *out |= GIT_CREDENTIAL_SSH_INTERACTIVE;
+ ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
+ continue;
+ }
+
+ /* Skip it if we don't know it */
+ ptr = strchr(ptr, ',');
+ }
+
+ return 0;
+}
+
+int git_smart_subtransport_ssh_libssh2(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *param)
+{
+ ssh_subtransport *t;
+
+ GIT_ASSERT_ARG(out);
+
+ GIT_UNUSED(param);
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GIT_ERROR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = _ssh_action;
+ t->parent.close = _ssh_close;
+ t->parent.free = _ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+int git_smart_subtransport_ssh_libssh2_set_paths(
+ git_smart_subtransport *subtransport,
+ const char *cmd_uploadpack,
+ const char *cmd_receivepack)
+{
+ ssh_subtransport *t = (ssh_subtransport *)subtransport;
+
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
+
+ t->cmd_uploadpack = git__strdup(cmd_uploadpack);
+ GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack);
+
+ t->cmd_receivepack = git__strdup(cmd_receivepack);
+ GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack);
+
+ return 0;
+}
+
+static void shutdown_libssh2(void)
+{
+ libssh2_exit();
+}
+
+int git_transport_ssh_libssh2_global_init(void)
+{
+ if (libssh2_init(0) < 0) {
+ git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2");
+ return -1;
+ }
+
+ return git_runtime_shutdown_register(shutdown_libssh2);
+}
+
+#else /* GIT_SSH */
+
+int git_transport_ssh_libssh2_global_init(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/src/libgit2/transports/ssh_libssh2.h b/src/libgit2/transports/ssh_libssh2.h
new file mode 100644
index 0000000..3f8cc2a
--- /dev/null
+++ b/src/libgit2/transports/ssh_libssh2.h
@@ -0,0 +1,28 @@
+/*
+ * 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_transports_libssh2_h__
+#define INCLUDE_transports_libssh2_h__
+
+#include "common.h"
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "git2/sys/transport.h"
+
+int git_transport_ssh_libssh2_global_init(void);
+
+int git_smart_subtransport_ssh_libssh2(
+ git_smart_subtransport **out,
+ git_transport *owner,
+ void *param);
+
+int git_smart_subtransport_ssh_libssh2_set_paths(
+ git_smart_subtransport *subtransport,
+ const char *cmd_uploadpack,
+ const char *cmd_receivepack);
+
+#endif
diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c
index ae572c5..b83ef99 100644
--- a/src/libgit2/transports/winhttp.c
+++ b/src/libgit2/transports/winhttp.c
@@ -293,7 +293,7 @@ static int certificate_check(winhttp_stream *s, int valid)
/* If there is no override, we should fail if WinHTTP doesn't think it's fine */
if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) {
- if (!git_error_last())
+ if (git_error_last()->klass == GIT_ERROR_NONE)
git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure");
return GIT_ECERTIFICATE;
@@ -317,7 +317,7 @@ static int certificate_check(winhttp_stream *s, int valid)
if (error == GIT_PASSTHROUGH)
error = valid ? 0 : GIT_ECERTIFICATE;
- if (error < 0 && !git_error_last())
+ if (error < 0 && git_error_last()->klass == GIT_ERROR_NONE)
git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check");
return error;
@@ -436,7 +436,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
GIT_ERROR_CHECK_ALLOC(proxy_url);
}
- if (proxy_url) {
+ if (proxy_url && *proxy_url) {
git_str processed_url = GIT_STR_INIT;
WINHTTP_PROXY_INFO proxy_info;
wchar_t *proxy_wide;
@@ -746,6 +746,33 @@ static void CALLBACK winhttp_status(
}
}
+static int user_agent(bool *exists, git_str *out)
+{
+ const char *product = git_settings__user_agent_product();
+ const char *comment = git_settings__user_agent();
+
+ GIT_ASSERT(product && comment);
+
+ if (!*product) {
+ *exists = false;
+ return 0;
+ }
+
+ git_str_puts(out, product);
+
+ if (*comment) {
+ git_str_puts(out, " (");
+ git_str_puts(out, comment);
+ git_str_puts(out, ")");
+ }
+
+ if (git_str_oom(out))
+ return -1;
+
+ *exists = true;
+ return 0;
+}
+
static int winhttp_connect(
winhttp_subtransport *t)
{
@@ -757,6 +784,7 @@ static int winhttp_connect(
int error = -1;
int default_timeout = TIMEOUT_INFINITE;
int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+ bool has_ua = true;
DWORD protocols =
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
@@ -787,11 +815,11 @@ static int winhttp_connect(
goto on_error;
}
-
- if (git_http__user_agent(&ua) < 0)
+ if (user_agent(&has_ua, &ua) < 0)
goto on_error;
- if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
+ if (has_ua &&
+ git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
goto on_error;
}
@@ -933,7 +961,7 @@ static int send_request(winhttp_stream *s, size_t len, bool chunked)
(!request_failed && s->status_sending_request_reached)) {
git_error_clear();
if ((error = certificate_check(s, cert_valid)) < 0) {
- if (!git_error_last())
+ if (git_error_last()->klass == GIT_ERROR_NONE)
git_error_set(GIT_ERROR_OS, "user cancelled certificate check");
return error;
diff --git a/src/libgit2/tree.c b/src/libgit2/tree.c
index 236a87f..18278d3 100644
--- a/src/libgit2/tree.c
+++ b/src/libgit2/tree.c
@@ -381,7 +381,7 @@ static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len,
if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0)
return error;
- if (mode < 0 || mode > UINT16_MAX)
+ if (mode < 0 || (uint32_t)mode > UINT16_MAX)
return -1;
*mode_out = mode;
diff --git a/src/libgit2/worktree.c b/src/libgit2/worktree.c
index a878634..00ff9e7 100644
--- a/src/libgit2/worktree.c
+++ b/src/libgit2/worktree.c
@@ -335,11 +335,21 @@ int git_worktree_add(git_worktree **out, git_repository *repo,
goto out;
}
- if (git_branch_is_checked_out(wtopts.ref)) {
- git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out");
- err = -1;
+ if ((err = git_reference_dup(&ref, wtopts.ref)) < 0)
goto out;
- }
+ } else if (wtopts.checkout_existing && git_branch_lookup(&ref, repo, name, GIT_BRANCH_LOCAL) == 0) {
+ /* Do nothing */
+ } else if ((err = git_repository_head(&head, repo)) < 0 ||
+ (err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0 ||
+ (err = git_branch_create(&ref, repo, name, commit, false)) < 0) {
+ goto out;
+ }
+
+ if (git_branch_is_checked_out(ref)) {
+ git_error_set(GIT_ERROR_WORKTREE, "reference %s is already checked out",
+ git_reference_name(ref));
+ err = -1;
+ goto out;
}
/* Create gitdir directory ".git/worktrees/<name>" */
@@ -392,19 +402,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo,
|| (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0)
goto out;
- /* Set up worktree reference */
- if (wtopts.ref) {
- if ((err = git_reference_dup(&ref, wtopts.ref)) < 0)
- goto out;
- } else {
- if ((err = git_repository_head(&head, repo)) < 0)
- goto out;
- if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
- goto out;
- if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
- goto out;
- }
-
/* Set worktree's HEAD */
if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0)
goto out;
diff --git a/src/util/array.h b/src/util/array.h
index 633d598..515e6e3 100644
--- a/src/util/array.h
+++ b/src/util/array.h
@@ -41,39 +41,40 @@
#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr)
-
-typedef git_array_t(char) git_array_generic_t;
-
-/* use a generic array for growth, return 0 on success */
-GIT_INLINE(int) git_array_grow(void *_a, size_t item_size)
+GIT_INLINE(void *) git_array__alloc(void *arr, size_t *size, size_t *asize, size_t item_size)
{
- volatile git_array_generic_t *a = _a;
size_t new_size;
- char *new_array;
+ void *new_array;
+
+ if (*size < *asize)
+ return arr;
- if (a->size < 8) {
+ if (*size < 8) {
new_size = 8;
} else {
- if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3))
+ if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, *asize, 3))
goto on_oom;
+
new_size /= 2;
}
- if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL)
+ if ((new_array = git__reallocarray(arr, new_size, item_size)) == NULL)
goto on_oom;
- a->ptr = new_array;
- a->asize = new_size;
- return 0;
+ *asize = new_size;
+
+ return new_array;
on_oom:
- git_array_clear(*a);
- return -1;
+ git__free(arr);
+ *size = 0;
+ *asize = 0;
+ return NULL;
}
#define git_array_alloc(a) \
- (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \
- &(a).ptr[(a).size++] : (void *)NULL)
+ (((a).size < (a).asize || \
+ ((a).ptr = git_array__alloc((a).ptr, &(a).size, &(a).asize, sizeof(*(a).ptr))) != NULL) ? &(a).ptr[(a).size++] : (void *)NULL)
#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL)
diff --git a/src/util/ctype_compat.h b/src/util/ctype_compat.h
new file mode 100644
index 0000000..462c8a1
--- /dev/null
+++ b/src/util/ctype_compat.h
@@ -0,0 +1,70 @@
+/*
+ * 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_ctype_compat_h__
+#define INCLUDE_ctype_compat_h__
+
+/*
+ * The Microsoft C runtime (MSVCRT) may take a heavy lock on the
+ * locale in order to figure out how the `ctype` functions work.
+ * This is deeply slow. Provide our own to avoid that.
+ */
+
+#ifdef GIT_WIN32
+
+GIT_INLINE(int) git__tolower(int c)
+{
+ return (c >= 'A' && c <= 'Z') ? (c + 32) : c;
+}
+
+GIT_INLINE(int) git__toupper(int c)
+{
+ return (c >= 'a' && c <= 'z') ? (c - 32) : c;
+}
+
+GIT_INLINE(bool) git__isalpha(int c)
+{
+ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+}
+
+GIT_INLINE(bool) git__isdigit(int c)
+{
+ return (c >= '0' && c <= '9');
+}
+
+GIT_INLINE(bool) git__isalnum(int c)
+{
+ return git__isalpha(c) || git__isdigit(c);
+}
+
+GIT_INLINE(bool) git__isspace(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');
+}
+
+GIT_INLINE(bool) git__isxdigit(int c)
+{
+ return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+GIT_INLINE(bool) git__isprint(int c)
+{
+ return (c >= ' ' && c <= '~');
+}
+
+#else
+# define git__tolower(a) tolower((unsigned char)(a))
+# define git__toupper(a) toupper((unsigned char)(a))
+
+# define git__isalpha(a) (!!isalpha((unsigned char)(a)))
+# define git__isdigit(a) (!!isdigit((unsigned char)(a)))
+# define git__isalnum(a) (!!isalnum((unsigned char)(a)))
+# define git__isspace(a) (!!isspace((unsigned char)(a)))
+# define git__isxdigit(a) (!!isxdigit((unsigned char)(a)))
+# define git__isprint(a) (!!isprint((unsigned char)(a)))
+#endif
+
+#endif
diff --git a/src/util/date.c b/src/util/date.c
index 4d757e2..872cb81 100644
--- a/src/util/date.c
+++ b/src/util/date.c
@@ -129,9 +129,9 @@ static size_t match_string(const char *date, const char *str)
for (i = 0; *date; date++, str++, i++) {
if (*date == *str)
continue;
- if (toupper(*date) == toupper(*str))
+ if (git__toupper(*date) == git__toupper(*str))
continue;
- if (!isalnum(*date))
+ if (!git__isalnum(*date))
break;
return 0;
}
@@ -143,7 +143,7 @@ static int skip_alpha(const char *date)
int i = 0;
do {
i++;
- } while (isalpha(date[i]));
+ } while (git__isalpha(date[i]));
return i;
}
@@ -251,7 +251,7 @@ static size_t match_multi_number(unsigned long num, char c, const char *date, ch
num2 = strtol(end+1, &end, 10);
num3 = -1;
- if (*end == c && isdigit(end[1]))
+ if (*end == c && git__isdigit(end[1]))
num3 = strtol(end+1, &end, 10);
/* Time? Date? */
@@ -349,7 +349,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_
case '.':
case '/':
case '-':
- if (isdigit(end[1])) {
+ if (git__isdigit(end[1])) {
size_t match = match_multi_number(num, *end, date, end, tm);
if (match)
return match;
@@ -364,7 +364,7 @@ static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_
n = 0;
do {
n++;
- } while (isdigit(date[n]));
+ } while (git__isdigit(date[n]));
/* Four-digit year or a timezone? */
if (n == 4) {
@@ -514,11 +514,11 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset
if (!c || c == '\n')
break;
- if (isalpha(c))
+ if (git__isalpha(c))
match = match_alpha(date, &tm, offset);
- else if (isdigit(c))
+ else if (git__isdigit(c))
match = match_digit(date, &tm, offset, &tm_gmt);
- else if ((c == '-' || c == '+') && isdigit(date[1]))
+ else if ((c == '-' || c == '+') && git__isdigit(date[1]))
match = match_tz(date, offset);
if (!match) {
@@ -682,7 +682,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
const char *end = date;
int i;
- while (isalpha(*++end))
+ while (git__isalpha(*++end))
/* scan to non-alpha */;
for (i = 0; i < 12; i++) {
@@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
case '.':
case '/':
case '-':
- if (isdigit(end[1])) {
+ if (git__isdigit(end[1])) {
size_t match = match_multi_number(number, *end, date, end, tm);
if (match)
return date + match;
@@ -843,13 +843,13 @@ static git_time_t approxidate_str(const char *date,
if (!c)
break;
date++;
- if (isdigit(c)) {
+ if (git__isdigit(c)) {
pending_number(&tm, &number);
date = approxidate_digit(date-1, &tm, &number);
touched = 1;
continue;
}
- if (isalpha(c))
+ if (git__isalpha(c))
date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
}
pending_number(&tm, &number);
diff --git a/src/util/errors.c b/src/util/errors.c
new file mode 100644
index 0000000..feed6a8
--- /dev/null
+++ b/src/util/errors.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2_util.h"
+
+#include "errors.h"
+#include "posix.h"
+#include "str.h"
+#include "runtime.h"
+
+/*
+ * Some static error data that is used when we're out of memory, TLS
+ * has not been setup, or TLS has failed.
+ */
+
+static git_error oom_error = {
+ "Out of memory",
+ GIT_ERROR_NOMEMORY
+};
+
+static git_error uninitialized_error = {
+ "library has not been initialized",
+ GIT_ERROR_INVALID
+};
+
+static git_error tlsdata_error = {
+ "thread-local data initialization failure",
+ GIT_ERROR_THREAD
+};
+
+static git_error no_error = {
+ "no error",
+ GIT_ERROR_NONE
+};
+
+#define IS_STATIC_ERROR(err) \
+ ((err) == &oom_error || (err) == &uninitialized_error || \
+ (err) == &tlsdata_error || (err) == &no_error)
+
+/* Per-thread error state (TLS) */
+
+static git_tlsdata_key tls_key;
+
+struct error_threadstate {
+ /* The error message buffer. */
+ git_str message;
+
+ /* Error information, set by `git_error_set` and friends. */
+ git_error error;
+
+ /*
+ * The last error to occur; points to the error member of this
+ * struct _or_ a static error.
+ */
+ git_error *last;
+};
+
+static void threadstate_dispose(struct error_threadstate *threadstate)
+{
+ if (!threadstate)
+ return;
+
+ git_str_dispose(&threadstate->message);
+}
+
+static struct error_threadstate *threadstate_get(void)
+{
+ struct error_threadstate *threadstate;
+
+ if ((threadstate = git_tlsdata_get(tls_key)) != NULL)
+ return threadstate;
+
+ /*
+ * Avoid git__malloc here, since if it fails, it sets an error
+ * message, which requires thread state, which would allocate
+ * here, which would fail, which would set an error message...
+ */
+
+ if ((threadstate = git__allocator.gmalloc(
+ sizeof(struct error_threadstate),
+ __FILE__, __LINE__)) == NULL)
+ return NULL;
+
+ memset(threadstate, 0, sizeof(struct error_threadstate));
+
+ if (git_str_init(&threadstate->message, 0) < 0) {
+ git__allocator.gfree(threadstate);
+ return NULL;
+ }
+
+ git_tlsdata_set(tls_key, threadstate);
+ return threadstate;
+}
+
+static void GIT_SYSTEM_CALL threadstate_free(void *threadstate)
+{
+ threadstate_dispose(threadstate);
+ git__free(threadstate);
+}
+
+static void git_error_global_shutdown(void)
+{
+ struct error_threadstate *threadstate;
+
+ threadstate = git_tlsdata_get(tls_key);
+ git_tlsdata_set(tls_key, NULL);
+
+ threadstate_dispose(threadstate);
+ git__free(threadstate);
+
+ git_tlsdata_dispose(tls_key);
+}
+
+int git_error_global_init(void)
+{
+ if (git_tlsdata_init(&tls_key, &threadstate_free) != 0)
+ return -1;
+
+ return git_runtime_shutdown_register(git_error_global_shutdown);
+}
+
+static void set_error_from_buffer(int error_class)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+ git_error *error;
+ git_str *buf;
+
+ if (!threadstate)
+ return;
+
+ error = &threadstate->error;
+ buf = &threadstate->message;
+
+ error->message = buf->ptr;
+ error->klass = error_class;
+
+ threadstate->last = error;
+}
+
+static void set_error(int error_class, char *string)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+ git_str *buf;
+
+ if (!threadstate)
+ return;
+
+ buf = &threadstate->message;
+
+ git_str_clear(buf);
+
+ if (string)
+ git_str_puts(buf, string);
+
+ if (!git_str_oom(buf))
+ set_error_from_buffer(error_class);
+}
+
+void git_error_set_oom(void)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+
+ if (!threadstate)
+ return;
+
+ threadstate->last = &oom_error;
+}
+
+void git_error_set(int error_class, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ git_error_vset(error_class, fmt, ap);
+ va_end(ap);
+}
+
+void git_error_vset(int error_class, const char *fmt, va_list ap)
+{
+#ifdef GIT_WIN32
+ DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0;
+#endif
+
+ struct error_threadstate *threadstate = threadstate_get();
+ int error_code = (error_class == GIT_ERROR_OS) ? errno : 0;
+ git_str *buf;
+
+ if (!threadstate)
+ return;
+
+ buf = &threadstate->message;
+
+ git_str_clear(buf);
+
+ if (fmt) {
+ git_str_vprintf(buf, fmt, ap);
+ if (error_class == GIT_ERROR_OS)
+ git_str_PUTS(buf, ": ");
+ }
+
+ if (error_class == GIT_ERROR_OS) {
+#ifdef GIT_WIN32
+ char *win32_error = git_win32_get_error_message(win32_error_code);
+ if (win32_error) {
+ git_str_puts(buf, win32_error);
+ git__free(win32_error);
+
+ SetLastError(0);
+ }
+ else
+#endif
+ if (error_code)
+ git_str_puts(buf, strerror(error_code));
+
+ if (error_code)
+ errno = 0;
+ }
+
+ if (!git_str_oom(buf))
+ set_error_from_buffer(error_class);
+}
+
+int git_error_set_str(int error_class, const char *string)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+ git_str *buf;
+
+ GIT_ASSERT_ARG(string);
+
+ if (!threadstate)
+ return -1;
+
+ buf = &threadstate->message;
+
+ git_str_clear(buf);
+ git_str_puts(buf, string);
+
+ if (git_str_oom(buf))
+ return -1;
+
+ set_error_from_buffer(error_class);
+ return 0;
+}
+
+void git_error_clear(void)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+
+ if (!threadstate)
+ return;
+
+ if (threadstate->last != NULL) {
+ set_error(0, NULL);
+ threadstate->last = NULL;
+ }
+
+ errno = 0;
+#ifdef GIT_WIN32
+ SetLastError(0);
+#endif
+}
+
+bool git_error_exists(void)
+{
+ struct error_threadstate *threadstate;
+
+ if ((threadstate = threadstate_get()) == NULL)
+ return true;
+
+ return threadstate->last != NULL;
+}
+
+const git_error *git_error_last(void)
+{
+ struct error_threadstate *threadstate;
+
+ /* If the library is not initialized, return a static error. */
+ if (!git_runtime_init_count())
+ return &uninitialized_error;
+
+ if ((threadstate = threadstate_get()) == NULL)
+ return &tlsdata_error;
+
+ if (!threadstate->last)
+ return &no_error;
+
+ return threadstate->last;
+}
+
+int git_error_save(git_error **out)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+ git_error *error, *dup;
+
+ if (!threadstate) {
+ *out = &tlsdata_error;
+ return -1;
+ }
+
+ error = threadstate->last;
+
+ if (!error || error == &no_error) {
+ *out = &no_error;
+ return 0;
+ } else if (IS_STATIC_ERROR(error)) {
+ *out = error;
+ return 0;
+ }
+
+ if ((dup = git__malloc(sizeof(git_error))) == NULL) {
+ *out = &oom_error;
+ return -1;
+ }
+
+ dup->klass = error->klass;
+ dup->message = git__strdup(error->message);
+
+ if (!dup->message) {
+ *out = &oom_error;
+ return -1;
+ }
+
+ *out = dup;
+ return 0;
+}
+
+int git_error_restore(git_error *error)
+{
+ struct error_threadstate *threadstate = threadstate_get();
+
+ GIT_ASSERT_ARG(error);
+
+ if (IS_STATIC_ERROR(error) && threadstate)
+ threadstate->last = error;
+ else
+ set_error(error->klass, error->message);
+
+ git_error_free(error);
+ return 0;
+}
+
+void git_error_free(git_error *error)
+{
+ if (!error)
+ return;
+
+ if (IS_STATIC_ERROR(error))
+ return;
+
+ git__free(error->message);
+ git__free(error);
+}
+
+int git_error_system_last(void)
+{
+#ifdef GIT_WIN32
+ return GetLastError();
+#else
+ return errno;
+#endif
+}
+
+void git_error_system_set(int code)
+{
+#ifdef GIT_WIN32
+ SetLastError(code);
+#else
+ errno = code;
+#endif
+}
+
+/* Deprecated error values and functions */
+
+#ifndef GIT_DEPRECATE_HARD
+
+#include "git2/deprecated.h"
+
+const git_error *giterr_last(void)
+{
+ return git_error_last();
+}
+
+void giterr_clear(void)
+{
+ git_error_clear();
+}
+
+void giterr_set_str(int error_class, const char *string)
+{
+ git_error_set_str(error_class, string);
+}
+
+void giterr_set_oom(void)
+{
+ git_error_set_oom();
+}
+#endif
diff --git a/src/libgit2/errors.h b/src/util/errors.h
index 772c7ba..8d58775 100644
--- a/src/libgit2/errors.h
+++ b/src/util/errors.h
@@ -8,7 +8,11 @@
#ifndef INCLUDE_errors_h__
#define INCLUDE_errors_h__
-#include "common.h"
+#include "git2_util.h"
+#include "git2/sys/errors.h"
+
+/* Initialize the error thread-state. */
+int git_error_global_init(void);
/*
* `vprintf`-style formatting for the error message for this thread.
@@ -16,6 +20,11 @@
void git_error_vset(int error_class, const char *fmt, va_list ap);
/**
+ * Determines whether an error exists.
+ */
+bool git_error_exists(void);
+
+/**
* Set error message for user callback if needed.
*
* If the error code in non-zero and no error message is set, this
@@ -27,9 +36,8 @@ GIT_INLINE(int) git_error_set_after_callback_function(
int error_code, const char *action)
{
if (error_code) {
- const git_error *e = git_error_last();
- if (!e || !e->message)
- git_error_set(e ? e->klass : GIT_ERROR_CALLBACK,
+ if (!git_error_exists())
+ git_error_set(GIT_ERROR_CALLBACK,
"%s callback returned %d", action, error_code);
}
return error_code;
@@ -54,27 +62,23 @@ int git_error_system_last(void);
void git_error_system_set(int code);
/**
- * Structure to preserve libgit2 error state
- */
-typedef struct {
- int error_code;
- unsigned int oom : 1;
- git_error error_msg;
-} git_error_state;
-
-/**
* Capture current error state to restore later, returning error code.
* If `error_code` is zero, this does not clear the current error state.
* You must either restore this error state, or free it.
+ *
+ * This function returns 0 on success, or -1 on failure. If the function
+ * fails, the `out` structure is set to the failure error message and
+ * the normal system error message is not updated.
*/
-extern int git_error_state_capture(git_error_state *state, int error_code);
+extern int git_error_save(git_error **out);
/**
- * Restore error state to a previous value, returning saved error code.
+ * Restore thread error state to the given value. The given value is
+ * freed and `git_error_free` need not be called on it.
*/
-extern int git_error_state_restore(git_error_state *state);
+extern int git_error_restore(git_error *error);
/** Free an error state. */
-extern void git_error_state_free(git_error_state *state);
+extern void git_error_free(git_error *error);
#endif
diff --git a/src/util/fs_path.c b/src/util/fs_path.c
index e03fcf7..9d5c99e 100644
--- a/src/util/fs_path.c
+++ b/src/util/fs_path.c
@@ -419,6 +419,16 @@ int git_fs_path_to_dir(git_str *path)
return git_str_oom(path) ? -1 : 0;
}
+size_t git_fs_path_dirlen(const char *path)
+{
+ size_t len = strlen(path);
+
+ while (len > 1 && path[len - 1] == '/')
+ len--;
+
+ return len;
+}
+
void git_fs_path_string_to_dir(char *path, size_t size)
{
size_t end = strlen(path);
@@ -1938,12 +1948,13 @@ static int sudo_uid_lookup(uid_t *out)
{
git_str uid_str = GIT_STR_INIT;
int64_t uid;
- int error;
+ int error = -1;
- if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 &&
- (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 &&
- uid == (int64_t)((uid_t)uid)) {
+ if (git__getenv(&uid_str, "SUDO_UID") == 0 &&
+ git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10) == 0 &&
+ uid == (int64_t)((uid_t)uid)) {
*out = (uid_t)uid;
+ error = 0;
}
git_str_dispose(&uid_str);
diff --git a/src/util/fs_path.h b/src/util/fs_path.h
index e5ca673..43f7951 100644
--- a/src/util/fs_path.h
+++ b/src/util/fs_path.h
@@ -87,6 +87,29 @@ extern int git_fs_path_to_dir(git_str *path);
extern void git_fs_path_string_to_dir(char *path, size_t size);
/**
+ * Provides the length of the given path string with no trailing
+ * slashes.
+ */
+size_t git_fs_path_dirlen(const char *path);
+
+/**
+ * Returns nonzero if the given path is a filesystem root; on Windows, this
+ * means a drive letter (eg `A:/`, `C:\`). On POSIX this is `/`.
+ */
+GIT_INLINE(int) git_fs_path_is_root(const char *name)
+{
+#ifdef GIT_WIN32
+ if (((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')) &&
+ name[1] == ':' &&
+ (name[2] == '/' || name[2] == '\\') &&
+ name[3] == '\0')
+ return 1;
+#endif
+
+ return (name[0] == '/' && name[1] == '\0');
+}
+
+/**
* Taken from git.git; returns nonzero if the given path is "." or "..".
*/
GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name)
diff --git a/src/util/futils.h b/src/util/futils.h
index 3f207af..53bcc55 100644
--- a/src/util/futils.h
+++ b/src/util/futils.h
@@ -25,7 +25,7 @@ extern int git_futils_readbuffer(git_str *obj, const char *path);
extern int git_futils_readbuffer_updated(
git_str *obj,
const char *path,
- unsigned char checksum[GIT_HASH_SHA1_SIZE],
+ unsigned char checksum[GIT_HASH_SHA256_SIZE],
int *updated);
extern int git_futils_readbuffer_fd_full(git_str *obj, git_file fd);
extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len);
diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in
index a84ea89..52b7328 100644
--- a/src/util/git2_features.h.in
+++ b/src/util/git2_features.h.in
@@ -30,7 +30,9 @@
#cmakedefine GIT_QSORT_MSC
#cmakedefine GIT_SSH 1
-#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
+#cmakedefine GIT_SSH_EXEC 1
+#cmakedefine GIT_SSH_LIBSSH2 1
+#cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_GSSAPI 1
@@ -44,6 +46,10 @@
#cmakedefine GIT_MBEDTLS 1
#cmakedefine GIT_SCHANNEL 1
+#cmakedefine GIT_HTTPPARSER_HTTPPARSER 1
+#cmakedefine GIT_HTTPPARSER_LLHTTP 1
+#cmakedefine GIT_HTTPPARSER_BUILTIN 1
+
#cmakedefine GIT_SHA1_COLLISIONDETECT 1
#cmakedefine GIT_SHA1_WIN32 1
#cmakedefine GIT_SHA1_COMMON_CRYPTO 1
diff --git a/src/util/git2_util.h b/src/util/git2_util.h
index c62dc24..5bf0981 100644
--- a/src/util/git2_util.h
+++ b/src/util/git2_util.h
@@ -12,6 +12,7 @@
#endif
#include "git2/common.h"
+#include "git2/sys/errors.h"
#include "cc-compat.h"
typedef struct git_str git_str;
@@ -164,5 +165,6 @@ typedef struct git_str git_str;
if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; }
#include "util.h"
+#include "ctype_compat.h"
#endif
diff --git a/src/util/integer.h b/src/util/integer.h
index 6327717..a9e416c 100644
--- a/src/util/integer.h
+++ b/src/util/integer.h
@@ -89,7 +89,9 @@ GIT_INLINE(int) git__is_int(int64_t p)
/* Use Microsoft's safe integer handling functions where available */
#elif defined(_MSC_VER)
-# define ENABLE_INTSAFE_SIGNED_FUNCTIONS
+# if !defined(ENABLE_INTSAFE_SIGNED_FUNCTIONS)
+# define ENABLE_INTSAFE_SIGNED_FUNCTIONS
+# endif
# include <intsafe.h>
# define git__add_sizet_overflow(out, one, two) \
diff --git a/src/util/net.c b/src/util/net.c
index afd52ce..dede784 100644
--- a/src/util/net.c
+++ b/src/util/net.c
@@ -11,7 +11,6 @@
#include "posix.h"
#include "str.h"
-#include "http_parser.h"
#include "runtime.h"
#define DEFAULT_PORT_HTTP "80"
@@ -22,7 +21,7 @@
#define GIT_NET_URL_PARSER_INIT { 0 }
typedef struct {
- int hierarchical : 1;
+ unsigned int hierarchical : 1;
const char *scheme;
const char *user;
@@ -657,7 +656,7 @@ static bool has_at(const char *str)
int git_net_url_parse_scp(git_net_url *url, const char *given)
{
const char *default_port = default_port_for_scheme("ssh");
- const char *c, *user, *host, *port, *path = NULL;
+ const char *c, *user, *host, *port = NULL, *path = NULL;
size_t user_len = 0, host_len = 0, port_len = 0;
unsigned short bracket = 0;
diff --git a/src/util/process.h b/src/util/process.h
new file mode 100644
index 0000000..3ada669
--- /dev/null
+++ b/src/util/process.h
@@ -0,0 +1,222 @@
+/*
+ * 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_process_h__
+#define INCLUDE_process_h__
+
+typedef struct git_process git_process;
+
+typedef struct {
+ unsigned int capture_in : 1,
+ capture_out : 1,
+ capture_err : 1,
+ exclude_env : 1;
+
+ char *cwd;
+} git_process_options;
+
+typedef enum {
+ GIT_PROCESS_STATUS_NONE,
+ GIT_PROCESS_STATUS_NORMAL,
+ GIT_PROCESS_STATUS_ERROR
+} git_process_result_status;
+
+#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE }
+
+typedef struct {
+ git_process_result_status status;
+ int exitcode;
+ int signal;
+} git_process_result;
+
+#define GIT_PROCESS_OPTIONS_INIT { 0 }
+
+#ifdef GIT_WIN32
+# define p_pid_t DWORD
+#else
+# define p_pid_t pid_t
+#endif
+
+/**
+ * Create a new process. The command to run should be specified as the
+ * element of the `arg` array, execv-style. This should be the full path
+ * to the command to run, the PATH is not obeyed.
+ *
+ * This function will add the given environment variables (in `env`)
+ * to the current environment. Operations on environment variables
+ * are not thread safe, so you may not modify the environment during
+ * this call. You can avoid this by setting `exclude_env` in the
+ * options and providing the entire environment yourself.
+ *
+ * @param out location to store the process
+ * @param args the command (with arguments) to run
+ * @param args_len the length of the args array
+ * @param env environment variables to add (or NULL)
+ * @param env_len the length of the env len
+ * @param opts the options for creating the process
+ * @return 0 or an error code
+ */
+extern int git_process_new(
+ git_process **out,
+ const char **args,
+ size_t args_len,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts);
+
+/**
+ * Create a new process. The command to run should be specified as the
+ * `cmdline` option - which is the full text of the command line as it
+ * would be specified or run by a user. The command to run will be
+ * looked up in the PATH.
+ *
+ * On Unix, this will be executed by the system's shell (`/bin/sh`)
+ * and may contain _Bourne-style_ shell quoting rules. On Windows,
+ * this will be passed to `CreateProcess`, and similarly, may
+ * contain _Windows-style_ shell quoting rules.
+ *
+ * This function will add the given environment variables (in `env`)
+ * to the current environment. Operations on environment variables
+ * are not thread safe, so you may not modify the environment during
+ * this call. You can avoid this by setting `exclude_env` in the
+ * options and providing the entire environment yourself.
+ */
+extern int git_process_new_from_cmdline(
+ git_process **out,
+ const char *cmdline,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts);
+
+#ifdef GIT_WIN32
+
+extern int git_process__appname(
+ git_str *out,
+ const char *cmdline);
+
+/* Windows path parsing is tricky; this helper function is for testing. */
+extern int git_process__cmdline(
+ git_str *out,
+ const char **in,
+ size_t in_len);
+
+#endif
+
+/*
+ * Whether the given string looks like a command line option (starts
+ * with a dash). This is useful for examining strings that will become
+ * cmdline arguments to ensure that they are not erroneously treated
+ * as an option. For example, arguments to `ssh`.
+ */
+GIT_INLINE(bool) git_process__is_cmdline_option(const char *str)
+{
+ return (str && str[0] == '-');
+}
+
+/**
+ * Start the process.
+ *
+ * @param process the process to start
+ * @return 0 or an error code
+ */
+extern int git_process_start(git_process *process);
+
+/**
+ * Returns the process id of the process.
+ *
+ * @param out pointer to a pid_t to store the process id
+ * @param process the process to query
+ * @return 0 or an error code
+ */
+extern int git_process_id(p_pid_t *out, git_process *process);
+
+/**
+ * Read from the process's stdout. The process must have been created with
+ * `capture_out` set to true.
+ *
+ * @param process the process to read from
+ * @param buf the buf to read into
+ * @param count maximum number of bytes to read
+ * @return number of bytes read or an error code
+ */
+extern ssize_t git_process_read(git_process *process, void *buf, size_t count);
+
+/**
+ * Read from the process's stderr. The process must have been created with
+ * `capture_err` set to true.
+ *
+ * @param process the process to read from
+ * @param buf the buf to read into
+ * @param count maximum number of bytes to read
+ * @return number of bytes read or an error code
+ */
+extern ssize_t git_process_read_err(git_process *process, void *buf, size_t count);
+
+/**
+ * Write to the process's stdin. The process must have been created with
+ * `capture_in` set to true.
+ *
+ * @param process the process to write to
+ * @param buf the buf to write
+ * @param count maximum number of bytes to write
+ * @return number of bytes written or an error code
+ */
+extern ssize_t git_process_write(git_process *process, const void *buf, size_t count);
+
+/**
+ * Wait for the process to finish.
+ *
+ * @param result the result of the process or NULL
+ * @param process the process to wait on
+ */
+extern int git_process_wait(git_process_result *result, git_process *process);
+
+/**
+ * Close the input pipe from the child.
+ *
+ * @param process the process to close the pipe on
+ */
+extern int git_process_close_in(git_process *process);
+
+/**
+ * Close the output pipe from the child.
+ *
+ * @param process the process to close the pipe on
+ */
+extern int git_process_close_out(git_process *process);
+
+/**
+ * Close the error pipe from the child.
+ *
+ * @param process the process to close the pipe on
+ */
+extern int git_process_close_err(git_process *process);
+
+/**
+ * Close all resources that are used by the process. This does not
+ * wait for the process to complete.
+ *
+ * @parma process the process to close
+ */
+extern int git_process_close(git_process *process);
+
+/**
+ * Place a human-readable error message in the given git buffer.
+ *
+ * @param msg the buffer to store the message
+ * @param result the process result that produced an error
+ */
+extern int git_process_result_msg(git_str *msg, git_process_result *result);
+
+/**
+ * Free a process structure
+ *
+ * @param process the process to free
+ */
+extern void git_process_free(git_process *process);
+
+#endif
diff --git a/src/util/rand.c b/src/util/rand.c
index 2ed0605..2b137a5 100644
--- a/src/util/rand.c
+++ b/src/util/rand.c
@@ -10,10 +10,6 @@ See <http://creativecommons.org/publicdomain/zero/1.0/>. */
#include "rand.h"
#include "runtime.h"
-#if defined(GIT_RAND_GETENTROPY)
-# include <sys/random.h>
-#endif
-
#if defined(GIT_WIN32)
# include <wincrypt.h>
#endif
@@ -80,10 +76,10 @@ GIT_INLINE(int) getseed(uint64_t *seed)
GIT_INLINE(int) getseed(uint64_t *seed)
{
struct timeval tv;
- double loadavg[3];
int fd;
# if defined(GIT_RAND_GETLOADAVG)
+ double loadavg[3];
bits convert;
# endif
@@ -129,8 +125,6 @@ GIT_INLINE(int) getseed(uint64_t *seed)
convert.f = loadavg[0]; *seed ^= (convert.d >> 36);
convert.f = loadavg[1]; *seed ^= (convert.d);
convert.f = loadavg[2]; *seed ^= (convert.d >> 16);
-# else
- GIT_UNUSED(loadavg[0]);
# endif
*seed ^= git_time_monotonic();
diff --git a/src/util/regexp.c b/src/util/regexp.c
index 0870088..eb45822 100644
--- a/src/util/regexp.c
+++ b/src/util/regexp.c
@@ -125,7 +125,7 @@ int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches,
if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) {
git_error_set_oom();
- goto out;
+ return -1;
}
if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string),
diff --git a/src/util/str.c b/src/util/str.c
index 0d405bf..0b07c81 100644
--- a/src/util/str.c
+++ b/src/util/str.c
@@ -485,8 +485,8 @@ int git_str_decode_percent(
for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) {
if (str[str_pos] == '%' &&
str_len > str_pos + 2 &&
- isxdigit(str[str_pos + 1]) &&
- isxdigit(str[str_pos + 2])) {
+ git__isxdigit(str[str_pos + 1]) &&
+ git__isxdigit(str[str_pos + 2])) {
buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
HEX_DECODE(str[str_pos + 2]);
str_pos += 2;
diff --git a/src/util/strlist.c b/src/util/strlist.c
new file mode 100644
index 0000000..df5640c
--- /dev/null
+++ b/src/util/strlist.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stdio.h>
+
+#include "git2_util.h"
+#include "vector.h"
+#include "strlist.h"
+
+int git_strlist_copy(char ***out, const char **in, size_t len)
+{
+ char **dup;
+ size_t i;
+
+ dup = git__calloc(len, sizeof(char *));
+ GIT_ERROR_CHECK_ALLOC(dup);
+
+ for (i = 0; i < len; i++) {
+ dup[i] = git__strdup(in[i]);
+ GIT_ERROR_CHECK_ALLOC(dup[i]);
+ }
+
+ *out = dup;
+ return 0;
+}
+
+int git_strlist_copy_with_null(char ***out, const char **in, size_t len)
+{
+ char **dup;
+ size_t new_len, i;
+
+ GIT_ERROR_CHECK_ALLOC_ADD(&new_len, len, 1);
+
+ dup = git__calloc(new_len, sizeof(char *));
+ GIT_ERROR_CHECK_ALLOC(dup);
+
+ for (i = 0; i < len; i++) {
+ dup[i] = git__strdup(in[i]);
+ GIT_ERROR_CHECK_ALLOC(dup[i]);
+ }
+
+ *out = dup;
+ return 0;
+}
+
+bool git_strlist_contains_prefix(
+ const char **strings,
+ size_t len,
+ const char *str,
+ size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (strncmp(strings[i], str, n) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+bool git_strlist_contains_key(
+ const char **strings,
+ size_t len,
+ const char *key,
+ char delimiter)
+{
+ const char *c;
+
+ for (c = key; *c; c++) {
+ if (*c == delimiter)
+ break;
+ }
+
+ return *c ?
+ git_strlist_contains_prefix(strings, len, key, (c - key)) :
+ false;
+}
+
+void git_strlist_free(char **strings, size_t len)
+{
+ size_t i;
+
+ if (!strings)
+ return;
+
+ for (i = 0; i < len; i++)
+ git__free(strings[i]);
+
+ git__free(strings);
+}
+
+void git_strlist_free_with_null(char **strings)
+{
+ char **s;
+
+ if (!strings)
+ return;
+
+ for (s = strings; *s; s++)
+ git__free(*s);
+
+ git__free(strings);
+}
diff --git a/src/util/strlist.h b/src/util/strlist.h
new file mode 100644
index 0000000..68fbf8f
--- /dev/null
+++ b/src/util/strlist.h
@@ -0,0 +1,36 @@
+/*
+ * 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_runtime_h__
+#define INCLUDE_runtime_h__
+
+#include "git2_util.h"
+
+extern int git_strlist_copy(char ***out, const char **in, size_t len);
+
+extern int git_strlist_copy_with_null(
+ char ***out,
+ const char **in,
+ size_t len);
+
+extern bool git_strlist_contains_prefix(
+ const char **strings,
+ size_t len,
+ const char *str,
+ size_t n);
+
+extern bool git_strlist_contains_key(
+ const char **strings,
+ size_t len,
+ const char *key,
+ char delimiter);
+
+extern void git_strlist_free(char **strings, size_t len);
+
+extern void git_strlist_free_with_null(char **strings);
+
+#endif
diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h
index 778477e..60f27d3 100644
--- a/src/util/unix/posix.h
+++ b/src/util/unix/posix.h
@@ -54,8 +54,6 @@ GIT_INLINE(int) p_fsync(int fd)
#define p_send(s,b,l,f) send(s,b,l,f)
#define p_inet_pton(a, b, c) inet_pton(a, b, c)
-#define p_strcasecmp(s1, s2) strcasecmp(s1, s2)
-#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c)
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
#define p_snprintf snprintf
#define p_chdir(p) chdir(p)
diff --git a/src/util/unix/process.c b/src/util/unix/process.c
new file mode 100644
index 0000000..68c0384
--- /dev/null
+++ b/src/util/unix/process.c
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stdio.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <git2.h>
+
+#include "git2_util.h"
+#include "vector.h"
+#include "process.h"
+#include "strlist.h"
+
+#ifdef __APPLE__
+ #include <crt_externs.h>
+ #define environ (*_NSGetEnviron())
+#else
+ extern char **environ;
+#endif
+
+struct git_process {
+ char **args;
+ char **env;
+
+ char *cwd;
+
+ unsigned int capture_in : 1,
+ capture_out : 1,
+ capture_err : 1;
+
+ pid_t pid;
+
+ int child_in;
+ int child_out;
+ int child_err;
+ git_process_result_status status;
+};
+
+GIT_INLINE(bool) is_delete_env(const char *env)
+{
+ char *c = strchr(env, '=');
+
+ if (c == NULL)
+ return false;
+
+ return *(c+1) == '\0';
+}
+
+static int merge_env(
+ char ***out,
+ const char **env,
+ size_t env_len,
+ bool exclude_env)
+{
+ git_vector merged = GIT_VECTOR_INIT;
+ char **kv, *dup;
+ size_t max, cnt;
+ int error = 0;
+
+ for (max = env_len, kv = environ; !exclude_env && *kv; kv++)
+ max++;
+
+ if ((error = git_vector_init(&merged, max, NULL)) < 0)
+ goto on_error;
+
+ for (cnt = 0; env && cnt < env_len; cnt++) {
+ if (is_delete_env(env[cnt]))
+ continue;
+
+ dup = git__strdup(env[cnt]);
+ GIT_ERROR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(&merged, dup)) < 0)
+ goto on_error;
+ }
+
+ if (!exclude_env) {
+ for (kv = environ; *kv; kv++) {
+ if (env && git_strlist_contains_key(env, env_len, *kv, '='))
+ continue;
+
+ dup = git__strdup(*kv);
+ GIT_ERROR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(&merged, dup)) < 0)
+ goto on_error;
+ }
+ }
+
+ if (merged.length == 0) {
+ *out = NULL;
+ error = 0;
+ goto on_error;
+ }
+
+ git_vector_insert(&merged, NULL);
+
+ *out = (char **)merged.contents;
+
+ return 0;
+
+on_error:
+ git_vector_free_deep(&merged);
+ return error;
+}
+
+int git_process_new(
+ git_process **out,
+ const char **args,
+ size_t args_len,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts)
+{
+ git_process *process;
+
+ GIT_ASSERT_ARG(out && args && args_len > 0);
+
+ *out = NULL;
+
+ process = git__calloc(sizeof(git_process), 1);
+ GIT_ERROR_CHECK_ALLOC(process);
+
+ if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 ||
+ merge_env(&process->env, env, env_len, opts ? opts->exclude_env : false) < 0) {
+ git_process_free(process);
+ return -1;
+ }
+
+ if (opts) {
+ process->capture_in = opts->capture_in;
+ process->capture_out = opts->capture_out;
+ process->capture_err = opts->capture_err;
+
+ if (opts->cwd) {
+ process->cwd = git__strdup(opts->cwd);
+ GIT_ERROR_CHECK_ALLOC(process->cwd);
+ }
+ }
+
+ process->child_in = -1;
+ process->child_out = -1;
+ process->child_err = -1;
+ process->status = -1;
+
+ *out = process;
+ return 0;
+}
+
+extern int git_process_new_from_cmdline(
+ git_process **out,
+ const char *cmdline,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts)
+{
+ const char *args[] = { "/bin/sh", "-c", cmdline };
+
+ return git_process_new(out,
+ args, ARRAY_SIZE(args), env, env_len, opts);
+}
+
+#define CLOSE_FD(fd) \
+ if (fd >= 0) { \
+ close(fd); \
+ fd = -1; \
+ }
+
+static int try_read_status(size_t *out, int fd, void *buf, size_t len)
+{
+ size_t read_len = 0;
+ int ret = -1;
+
+ while (ret && read_len < len) {
+ ret = read(fd, buf + read_len, len - read_len);
+
+ if (ret < 0 && errno != EAGAIN && errno != EINTR) {
+ git_error_set(GIT_ERROR_OS, "could not read child status");
+ return -1;
+ }
+
+ read_len += ret;
+ }
+
+ *out = read_len;
+ return 0;
+}
+
+
+static int read_status(int fd)
+{
+ size_t status_len = sizeof(int) * 3, read_len = 0;
+ char buffer[status_len], fn[128];
+ int error, fn_error, os_error, fn_len = 0;
+
+ if ((error = try_read_status(&read_len, fd, buffer, status_len)) < 0)
+ return error;
+
+ /* Immediate EOF indicates the exec succeeded. */
+ if (read_len == 0)
+ return 0;
+
+ if (read_len < status_len) {
+ git_error_set(GIT_ERROR_INVALID, "child status truncated");
+ return -1;
+ }
+
+ memcpy(&fn_error, &buffer[0], sizeof(int));
+ memcpy(&os_error, &buffer[sizeof(int)], sizeof(int));
+ memcpy(&fn_len, &buffer[sizeof(int) * 2], sizeof(int));
+
+ if (fn_len > 0) {
+ fn_len = min(fn_len, (int)(ARRAY_SIZE(fn) - 1));
+
+ if ((error = try_read_status(&read_len, fd, fn, fn_len)) < 0)
+ return error;
+
+ fn[fn_len] = '\0';
+ } else {
+ fn[0] = '\0';
+ }
+
+ if (fn_error) {
+ errno = os_error;
+ git_error_set(GIT_ERROR_OS, "could not %s", fn[0] ? fn : "(unknown)");
+ }
+
+ return fn_error;
+}
+
+static bool try_write_status(int fd, const void *buf, size_t len)
+{
+ size_t write_len;
+ int ret;
+
+ for (write_len = 0; write_len < len; ) {
+ ret = write(fd, buf + write_len, len - write_len);
+
+ if (ret <= 0)
+ break;
+
+ write_len += ret;
+ }
+
+ return (len == write_len);
+}
+
+static void write_status(int fd, const char *fn, int error, int os_error)
+{
+ size_t status_len = sizeof(int) * 3, fn_len;
+ char buffer[status_len];
+
+ fn_len = strlen(fn);
+
+ if (fn_len > INT_MAX)
+ fn_len = INT_MAX;
+
+ memcpy(&buffer[0], &error, sizeof(int));
+ memcpy(&buffer[sizeof(int)], &os_error, sizeof(int));
+ memcpy(&buffer[sizeof(int) * 2], &fn_len, sizeof(int));
+
+ /* Do our best effort to write all the status. */
+ if (!try_write_status(fd, buffer, status_len))
+ return;
+
+ if (fn_len)
+ try_write_status(fd, fn, fn_len);
+}
+
+int git_process_start(git_process *process)
+{
+ int in[2] = { -1, -1 }, out[2] = { -1, -1 },
+ err[2] = { -1, -1 }, status[2] = { -1, -1 };
+ int fdflags, state, error;
+ pid_t pid;
+
+ /* Set up the pipes to read from/write to the process */
+ if ((process->capture_in && pipe(in) < 0) ||
+ (process->capture_out && pipe(out) < 0) ||
+ (process->capture_err && pipe(err) < 0)) {
+ git_error_set(GIT_ERROR_OS, "could not create pipe");
+ goto on_error;
+ }
+
+ /* Set up a self-pipe for status from the forked process. */
+ if (pipe(status) < 0 ||
+ (fdflags = fcntl(status[1], F_GETFD)) < 0 ||
+ fcntl(status[1], F_SETFD, fdflags | FD_CLOEXEC) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not create pipe");
+ goto on_error;
+ }
+
+ switch (pid = fork()) {
+ case -1:
+ git_error_set(GIT_ERROR_OS, "could not fork");
+ goto on_error;
+
+ /* Child: start the process. */
+ case 0:
+ /* Close the opposing side of the pipes */
+ CLOSE_FD(status[0]);
+
+ if (process->capture_in) {
+ CLOSE_FD(in[1]);
+ dup2(in[0], STDIN_FILENO);
+ }
+
+ if (process->capture_out) {
+ CLOSE_FD(out[0]);
+ dup2(out[1], STDOUT_FILENO);
+ }
+
+ if (process->capture_err) {
+ CLOSE_FD(err[0]);
+ dup2(err[1], STDERR_FILENO);
+ }
+
+ if (process->cwd && (error = chdir(process->cwd)) < 0) {
+ write_status(status[1], "chdir", error, errno);
+ exit(0);
+ }
+
+ /*
+ * Exec the process and write the results back if the
+ * call fails. If it succeeds, we'll close the status
+ * pipe (via CLOEXEC) and the parent will know.
+ */
+ error = execve(process->args[0],
+ process->args,
+ process->env);
+
+ write_status(status[1], "execve", error, errno);
+ exit(0);
+
+ /* Parent: make sure the child process exec'd correctly. */
+ default:
+ /* Close the opposing side of the pipes */
+ CLOSE_FD(status[1]);
+
+ if (process->capture_in) {
+ CLOSE_FD(in[0]);
+ process->child_in = in[1];
+ }
+
+ if (process->capture_out) {
+ CLOSE_FD(out[1]);
+ process->child_out = out[0];
+ }
+
+ if (process->capture_err) {
+ CLOSE_FD(err[1]);
+ process->child_err = err[0];
+ }
+
+ /* Try to read the status */
+ process->status = status[0];
+ if ((error = read_status(status[0])) < 0) {
+ waitpid(process->pid, &state, 0);
+ goto on_error;
+ }
+
+ process->pid = pid;
+ return 0;
+ }
+
+on_error:
+ CLOSE_FD(in[0]); CLOSE_FD(in[1]);
+ CLOSE_FD(out[0]); CLOSE_FD(out[1]);
+ CLOSE_FD(err[0]); CLOSE_FD(err[1]);
+ CLOSE_FD(status[0]); CLOSE_FD(status[1]);
+ return -1;
+}
+
+int git_process_id(p_pid_t *out, git_process *process)
+{
+ GIT_ASSERT(out && process);
+
+ if (!process->pid) {
+ git_error_set(GIT_ERROR_INVALID, "process not running");
+ return -1;
+ }
+
+ *out = process->pid;
+ return 0;
+}
+
+static ssize_t process_read(int fd, void *buf, size_t count)
+{
+ ssize_t ret;
+
+ if (count > SSIZE_MAX)
+ count = SSIZE_MAX;
+
+ if ((ret = read(fd, buf, count)) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not read from child process");
+ return -1;
+ }
+
+ return ret;
+}
+
+ssize_t git_process_read(git_process *process, void *buf, size_t count)
+{
+ GIT_ASSERT_ARG(process);
+ GIT_ASSERT(process->capture_out);
+
+ return process_read(process->child_out, buf, count);
+}
+
+ssize_t git_process_read_err(git_process *process, void *buf, size_t count)
+{
+ GIT_ASSERT_ARG(process);
+ GIT_ASSERT(process->capture_err);
+
+ return process_read(process->child_err, buf, count);
+}
+
+#ifdef GIT_THREADS
+
+# define signal_state sigset_t
+
+/*
+ * Since signal-handling is process-wide, we cannot simply use
+ * SIG_IGN to avoid SIGPIPE. Instead: http://www.microhowto.info:80/howto/ignore_sigpipe_without_affecting_other_threads_in_a_process.html
+ */
+
+GIT_INLINE(int) disable_signals(sigset_t *saved_mask)
+{
+ sigset_t sigpipe_mask;
+
+ sigemptyset(&sigpipe_mask);
+ sigaddset(&sigpipe_mask, SIGPIPE);
+
+ if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, saved_mask) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not configure signal mask");
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) restore_signals(sigset_t *saved_mask)
+{
+ sigset_t sigpipe_mask, pending;
+ int signal;
+
+ sigemptyset(&sigpipe_mask);
+ sigaddset(&sigpipe_mask, SIGPIPE);
+
+ if (sigpending(&pending) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not examine pending signals");
+ return -1;
+ }
+
+ if (sigismember(&pending, SIGPIPE) == 1 &&
+ sigwait(&sigpipe_mask, &signal) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not wait for (blocking) signal delivery");
+ return -1;
+ }
+
+ if (pthread_sigmask(SIG_SETMASK, saved_mask, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not configure signal mask");
+ return -1;
+ }
+
+ return 0;
+}
+
+#else
+
+# define signal_state struct sigaction
+
+GIT_INLINE(int) disable_signals(struct sigaction *saved_handler)
+{
+ struct sigaction ign_handler = { 0 };
+
+ ign_handler.sa_handler = SIG_IGN;
+
+ if (sigaction(SIGPIPE, &ign_handler, saved_handler) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not configure signal handler");
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) restore_signals(struct sigaction *saved_handler)
+{
+ if (sigaction(SIGPIPE, saved_handler, NULL) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not configure signal handler");
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+ssize_t git_process_write(git_process *process, const void *buf, size_t count)
+{
+ signal_state saved_signal;
+ ssize_t ret;
+
+ GIT_ASSERT_ARG(process);
+ GIT_ASSERT(process->capture_in);
+
+ if (count > SSIZE_MAX)
+ count = SSIZE_MAX;
+
+ if (disable_signals(&saved_signal) < 0)
+ return -1;
+
+ if ((ret = write(process->child_in, buf, count)) < 0)
+ git_error_set(GIT_ERROR_OS, "could not write to child process");
+
+ if (restore_signals(&saved_signal) < 0)
+ return -1;
+
+ return (ret < 0) ? -1 : ret;
+}
+
+int git_process_close_in(git_process *process)
+{
+ if (!process->capture_in) {
+ git_error_set(GIT_ERROR_INVALID, "input is not open");
+ return -1;
+ }
+
+ CLOSE_FD(process->child_in);
+ return 0;
+}
+
+int git_process_close_out(git_process *process)
+{
+ if (!process->capture_out) {
+ git_error_set(GIT_ERROR_INVALID, "output is not open");
+ return -1;
+ }
+
+ CLOSE_FD(process->child_out);
+ return 0;
+}
+
+int git_process_close_err(git_process *process)
+{
+ if (!process->capture_err) {
+ git_error_set(GIT_ERROR_INVALID, "error is not open");
+ return -1;
+ }
+
+ CLOSE_FD(process->child_err);
+ return 0;
+}
+
+int git_process_close(git_process *process)
+{
+ CLOSE_FD(process->child_in);
+ CLOSE_FD(process->child_out);
+ CLOSE_FD(process->child_err);
+
+ return 0;
+}
+
+int git_process_wait(git_process_result *result, git_process *process)
+{
+ int state;
+
+ if (result)
+ memset(result, 0, sizeof(git_process_result));
+
+ if (!process->pid) {
+ git_error_set(GIT_ERROR_INVALID, "process is stopped");
+ return -1;
+ }
+
+ if (waitpid(process->pid, &state, 0) < 0) {
+ git_error_set(GIT_ERROR_OS, "could not wait for child");
+ return -1;
+ }
+
+ process->pid = 0;
+
+ if (result) {
+ if (WIFEXITED(state)) {
+ result->status = GIT_PROCESS_STATUS_NORMAL;
+ result->exitcode = WEXITSTATUS(state);
+ } else if (WIFSIGNALED(state)) {
+ result->status = GIT_PROCESS_STATUS_ERROR;
+ result->signal = WTERMSIG(state);
+ } else {
+ result->status = GIT_PROCESS_STATUS_ERROR;
+ }
+ }
+
+ return 0;
+}
+
+int git_process_result_msg(git_str *out, git_process_result *result)
+{
+ if (result->status == GIT_PROCESS_STATUS_NONE) {
+ return git_str_puts(out, "process not started");
+ } else if (result->status == GIT_PROCESS_STATUS_NORMAL) {
+ return git_str_printf(out, "process exited with code %d",
+ result->exitcode);
+ } else if (result->signal) {
+ return git_str_printf(out, "process exited on signal %d",
+ result->signal);
+ }
+
+ return git_str_puts(out, "unknown error");
+}
+
+void git_process_free(git_process *process)
+{
+ if (!process)
+ return;
+
+ if (process->pid)
+ git_process_close(process);
+
+ git__free(process->cwd);
+ git_strlist_free_with_null(process->args);
+ git_strlist_free_with_null(process->env);
+ git__free(process);
+}
diff --git a/src/util/util.c b/src/util/util.c
index c8e8303..e86bcee 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -623,12 +623,12 @@ int git__bsearch_r(
*/
int git__strcmp_cb(const void *a, const void *b)
{
- return strcmp((const char *)a, (const char *)b);
+ return git__strcmp((const char *)a, (const char *)b);
}
int git__strcasecmp_cb(const void *a, const void *b)
{
- return strcasecmp((const char *)a, (const char *)b);
+ return git__strcasecmp((const char *)a, (const char *)b);
}
int git__parse_bool(int *out, const char *value)
diff --git a/src/util/util.h b/src/util/util.h
index 7f178b1..2ed0051 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -83,15 +83,6 @@ extern char *git__strsep(char **end, const char *sep);
extern void git__strntolower(char *str, size_t len);
extern void git__strtolower(char *str);
-#ifdef GIT_WIN32
-GIT_INLINE(int) git__tolower(int c)
-{
- return (c >= 'A' && c <= 'Z') ? (c + 32) : c;
-}
-#else
-# define git__tolower(a) tolower(a)
-#endif
-
extern size_t git__linenlen(const char *buffer, size_t buffer_len);
GIT_INLINE(const char *) git__next_line(const char *s)
@@ -249,26 +240,6 @@ GIT_INLINE(size_t) git__size_t_powerof2(size_t v)
return git__size_t_bitmask(v) + 1;
}
-GIT_INLINE(bool) git__isupper(int c)
-{
- return (c >= 'A' && c <= 'Z');
-}
-
-GIT_INLINE(bool) git__isalpha(int c)
-{
- return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
-}
-
-GIT_INLINE(bool) git__isdigit(int c)
-{
- return (c >= '0' && c <= '9');
-}
-
-GIT_INLINE(bool) git__isspace(int c)
-{
- return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');
-}
-
GIT_INLINE(bool) git__isspace_nonlf(int c)
{
return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v');
@@ -279,11 +250,6 @@ GIT_INLINE(bool) git__iswildcard(int c)
return (c == '*' || c == '?' || c == '[');
}
-GIT_INLINE(bool) git__isxdigit(int c)
-{
- return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
/*
* Parse a string value as a boolean, just like Core Git does.
*
diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c
index 3fec469..ace2320 100644
--- a/src/util/win32/posix_w32.c
+++ b/src/util/win32/posix_w32.c
@@ -787,13 +787,19 @@ int p_rmdir(const char *path)
char *p_realpath(const char *orig_path, char *buffer)
{
git_win32_path orig_path_w, buffer_w;
+ DWORD long_len;
if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
return NULL;
- /* Note that if the path provided is a relative path, then the current directory
+ /*
+ * POSIX realpath performs two functions: first, it turns relative
+ * paths into absolute paths. For this, we need GetFullPathName.
+ *
+ * Note that if the path provided is a relative path, then the current directory
* is used to resolve the path -- which is a concurrency issue because the current
- * directory is a process-wide variable. */
+ * directory is a process-wide variable.
+ */
if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
errno = ENAMETOOLONG;
@@ -803,9 +809,26 @@ char *p_realpath(const char *orig_path, char *buffer)
return NULL;
}
- /* The path must exist. */
- if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
- errno = ENOENT;
+ /*
+ * Then, the path is canonicalized. eg, on macOS,
+ * "/TMP" -> "/private/tmp". For this, we need GetLongPathName.
+ */
+ if ((long_len = GetLongPathNameW(buffer_w, buffer_w, GIT_WIN_PATH_UTF16)) == 0) {
+ DWORD error = GetLastError();
+
+ if (error == ERROR_FILE_NOT_FOUND ||
+ error == ERROR_PATH_NOT_FOUND)
+ errno = ENOENT;
+ else if (error == ERROR_ACCESS_DENIED)
+ errno = EPERM;
+ else
+ errno = EINVAL;
+
+ return NULL;
+ }
+
+ if (long_len > GIT_WIN_PATH_UTF16) {
+ errno = ENAMETOOLONG;
return NULL;
}
@@ -821,7 +844,6 @@ char *p_realpath(const char *orig_path, char *buffer)
return NULL;
git_fs_path_mkposix(buffer);
-
return buffer;
}
diff --git a/src/util/win32/process.c b/src/util/win32/process.c
new file mode 100644
index 0000000..bb52245
--- /dev/null
+++ b/src/util/win32/process.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <stdio.h>
+#include <git2.h>
+
+#include "git2_util.h"
+#include "process.h"
+#include "strlist.h"
+
+#ifndef DWORD_MAX
+# define DWORD_MAX INT32_MAX
+#endif
+
+#define ENV_MAX 32767
+
+struct git_process {
+ wchar_t *appname;
+ wchar_t *cmdline;
+ wchar_t *env;
+
+ wchar_t *cwd;
+
+ unsigned int capture_in : 1,
+ capture_out : 1,
+ capture_err : 1;
+
+ PROCESS_INFORMATION process_info;
+
+ HANDLE child_in;
+ HANDLE child_out;
+ HANDLE child_err;
+
+ git_process_result_status status;
+};
+
+/*
+ * Windows processes have a single command-line that is split by the
+ * invoked application into arguments (instead of an array of
+ * command-line arguments). This command-line is split by space or
+ * tab delimiters, unless that whitespace is within a double quote.
+ * Literal double-quotes themselves can be escaped by a backslash,
+ * but only when not within double quotes. Literal backslashes can
+ * be escaped by a backslash.
+ *
+ * Effectively, this means that instead of thinking about quoting
+ * individual strings, think about double quotes as an escaping
+ * mechanism for whitespace.
+ *
+ * In other words (using ` as a string boundary):
+ * [ `foo`, `bar` ] => `foo bar`
+ * [ `foo bar` ] => `foo" "bar`
+ * [ `foo bar`, `foo bar` ] => `foo" "bar foo" "bar`
+ * [ `foo "bar" foo` ] => `foo" "\"bar\"" "foo`
+ */
+int git_process__cmdline(
+ git_str *out,
+ const char **in,
+ size_t in_len)
+{
+ bool quoted = false;
+ const char *c;
+ size_t i;
+
+ for (i = 0; i < in_len; i++) {
+ /* Arguments are delimited by an unquoted space */
+ if (i)
+ git_str_putc(out, ' ');
+
+ for (c = in[i]; *c; c++) {
+ /* Start or stop quoting spaces within an argument */
+ if ((*c == ' ' || *c == '\t') && !quoted) {
+ git_str_putc(out, '"');
+ quoted = true;
+ } else if (*c != ' ' && *c != '\t' && quoted) {
+ git_str_putc(out, '"');
+ quoted = false;
+ }
+
+ /* Escape double-quotes and backslashes */
+ if (*c == '"' || *c == '\\')
+ git_str_putc(out, '\\');
+
+ git_str_putc(out, *c);
+ }
+ }
+
+ return git_str_oom(out) ? -1 : 0;
+}
+
+GIT_INLINE(bool) is_delete_env(const char *env)
+{
+ char *c = strchr(env, '=');
+
+ if (c == NULL)
+ return false;
+
+ return *(c+1) == '\0';
+}
+
+static int merge_env(wchar_t **out, const char **in, size_t in_len, bool exclude_env)
+{
+ git_str merged = GIT_STR_INIT;
+ wchar_t *in16 = NULL, *env = NULL, *e;
+ char *e8 = NULL;
+ size_t e_len;
+ int ret = 0;
+ size_t i;
+
+ *out = NULL;
+
+ in16 = git__malloc(ENV_MAX * sizeof(wchar_t));
+ GIT_ERROR_CHECK_ALLOC(in16);
+
+ e8 = git__malloc(ENV_MAX);
+ GIT_ERROR_CHECK_ALLOC(e8);
+
+ for (i = 0; in && i < in_len; i++) {
+ if (is_delete_env(in[i]))
+ continue;
+
+ if ((ret = git_utf8_to_16(in16, ENV_MAX, in[i])) < 0)
+ goto done;
+
+ git_str_put(&merged, (const char *)in16, ret * 2);
+ git_str_put(&merged, "\0\0", 2);
+ }
+
+ if (!exclude_env) {
+ env = GetEnvironmentStringsW();
+
+ for (e = env; *e; e += (e_len + 1)) {
+ e_len = wcslen(e);
+
+ if ((ret = git_utf8_from_16(e8, ENV_MAX, e)) < 0)
+ goto done;
+
+ if (git_strlist_contains_key(in, in_len, e8, '='))
+ continue;
+
+ git_str_put(&merged, (const char *)e, e_len * 2);
+ git_str_put(&merged, "\0\0", 2);
+ }
+ }
+
+ git_str_put(&merged, "\0\0", 2);
+
+ *out = (wchar_t *)git_str_detach(&merged);
+
+done:
+ if (env)
+ FreeEnvironmentStringsW(env);
+
+ git_str_dispose(&merged);
+ git__free(e8);
+ git__free(in16);
+
+ return ret < 0 ? -1 : 0;
+}
+
+static int process_new(
+ git_process **out,
+ const char *appname,
+ const char *cmdline,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts)
+{
+ git_process *process;
+ int error = 0;
+
+ *out = NULL;
+
+ process = git__calloc(1, sizeof(git_process));
+ GIT_ERROR_CHECK_ALLOC(process);
+
+ if (appname &&
+ git_utf8_to_16_alloc(&process->appname, appname) < 0) {
+ error = -1;
+ goto done;
+ }
+
+ if (git_utf8_to_16_alloc(&process->cmdline, cmdline) < 0) {
+ error = -1;
+ goto done;
+ }
+
+ if (opts && opts->cwd &&
+ git_utf8_to_16_alloc(&process->cwd, opts->cwd) < 0) {
+ error = -1;
+ goto done;
+ }
+
+ if (env && (error = merge_env(&process->env, env, env_len, opts && opts->exclude_env) < 0))
+ goto done;
+
+ if (opts) {
+ process->capture_in = opts->capture_in;
+ process->capture_out = opts->capture_out;
+ process->capture_err = opts->capture_err;
+ }
+
+done:
+ if (error)
+ git_process_free(process);
+ else
+ *out = process;
+
+ return error;
+}
+
+int git_process_new_from_cmdline(
+ git_process **out,
+ const char *cmdline,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts)
+{
+ GIT_ASSERT_ARG(out && cmdline);
+
+ return process_new(out, NULL, cmdline, env, env_len, opts);
+}
+
+int git_process_new(
+ git_process **out,
+ const char **args,
+ size_t args_len,
+ const char **env,
+ size_t env_len,
+ git_process_options *opts)
+{
+ git_str cmdline = GIT_STR_INIT;
+ int error;
+
+ GIT_ASSERT_ARG(out && args && args_len > 0);
+
+ if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0)
+ goto done;
+
+ error = process_new(out, args[0], cmdline.ptr, env, env_len, opts);
+
+done:
+ git_str_dispose(&cmdline);
+ return error;
+}
+
+#define CLOSE_HANDLE(h) do { if ((h) != NULL) CloseHandle(h); } while(0)
+
+int git_process_start(git_process *process)
+{
+ STARTUPINFOW startup_info;
+ SECURITY_ATTRIBUTES security_attrs;
+ DWORD flags = CREATE_UNICODE_ENVIRONMENT;
+ HANDLE in[2] = { NULL, NULL },
+ out[2] = { NULL, NULL },
+ err[2] = { NULL, NULL };
+
+ memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES));
+ security_attrs.bInheritHandle = TRUE;
+
+ memset(&startup_info, 0, sizeof(STARTUPINFOW));
+ startup_info.cb = sizeof(STARTUPINFOW);
+ startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+ if (process->capture_in) {
+ if (!CreatePipe(&in[0], &in[1], &security_attrs, 0) ||
+ !SetHandleInformation(in[1], HANDLE_FLAG_INHERIT, 0)) {
+ git_error_set(GIT_ERROR_OS, "could not create pipe");
+ goto on_error;
+ }
+
+ startup_info.hStdInput = in[0];
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+ }
+
+ if (process->capture_out) {
+ if (!CreatePipe(&out[0], &out[1], &security_attrs, 0) ||
+ !SetHandleInformation(out[0], HANDLE_FLAG_INHERIT, 0)) {
+ git_error_set(GIT_ERROR_OS, "could not create pipe");
+ goto on_error;
+ }
+
+ startup_info.hStdOutput = out[1];
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+ }
+
+ if (process->capture_err) {
+ if (!CreatePipe(&err[0], &err[1], &security_attrs, 0) ||
+ !SetHandleInformation(err[0], HANDLE_FLAG_INHERIT, 0)) {
+ git_error_set(GIT_ERROR_OS, "could not create pipe");
+ goto on_error;
+ }
+
+ startup_info.hStdError = err[1];
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+ }
+
+ memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
+
+ if (!CreateProcessW(process->appname, process->cmdline,
+ NULL, NULL, TRUE, flags, process->env,
+ process->cwd,
+ &startup_info,
+ &process->process_info)) {
+ git_error_set(GIT_ERROR_OS, "could not create process");
+ goto on_error;
+ }
+
+ CLOSE_HANDLE(in[0]); process->child_in = in[1];
+ CLOSE_HANDLE(out[1]); process->child_out = out[0];
+ CLOSE_HANDLE(err[1]); process->child_err = err[0];
+
+ return 0;
+
+on_error:
+ CLOSE_HANDLE(in[0]); CLOSE_HANDLE(in[1]);
+ CLOSE_HANDLE(out[0]); CLOSE_HANDLE(out[1]);
+ CLOSE_HANDLE(err[0]); CLOSE_HANDLE(err[1]);
+ return -1;
+}
+
+int git_process_id(p_pid_t *out, git_process *process)
+{
+ GIT_ASSERT(out && process);
+
+ if (!process->process_info.dwProcessId) {
+ git_error_set(GIT_ERROR_INVALID, "process not running");
+ return -1;
+ }
+
+ *out = process->process_info.dwProcessId;
+ return 0;
+}
+
+ssize_t git_process_read(git_process *process, void *buf, size_t count)
+{
+ DWORD ret;
+
+ if (count > DWORD_MAX)
+ count = DWORD_MAX;
+ if (count > SSIZE_MAX)
+ count = SSIZE_MAX;
+
+ if (!ReadFile(process->child_out, buf, (DWORD)count, &ret, NULL)) {
+ if (GetLastError() == ERROR_BROKEN_PIPE)
+ return 0;
+
+ git_error_set(GIT_ERROR_OS, "could not read");
+ return -1;
+ }
+
+ return ret;
+}
+
+ssize_t git_process_write(git_process *process, const void *buf, size_t count)
+{
+ DWORD ret;
+
+ if (count > DWORD_MAX)
+ count = DWORD_MAX;
+ if (count > SSIZE_MAX)
+ count = SSIZE_MAX;
+
+ if (!WriteFile(process->child_in, buf, (DWORD)count, &ret, NULL)) {
+ git_error_set(GIT_ERROR_OS, "could not write");
+ return -1;
+ }
+
+ return ret;
+}
+
+int git_process_close_in(git_process *process)
+{
+ if (!process->capture_in) {
+ git_error_set(GIT_ERROR_INVALID, "input is not open");
+ return -1;
+ }
+
+ if (process->child_in) {
+ CloseHandle(process->child_in);
+ process->child_in = NULL;
+ }
+
+ return 0;
+}
+
+int git_process_close_out(git_process *process)
+{
+ if (!process->capture_out) {
+ git_error_set(GIT_ERROR_INVALID, "output is not open");
+ return -1;
+ }
+
+ if (process->child_out) {
+ CloseHandle(process->child_out);
+ process->child_out = NULL;
+ }
+
+ return 0;
+}
+
+int git_process_close_err(git_process *process)
+{
+ if (!process->capture_err) {
+ git_error_set(GIT_ERROR_INVALID, "error is not open");
+ return -1;
+ }
+
+ if (process->child_err) {
+ CloseHandle(process->child_err);
+ process->child_err = NULL;
+ }
+
+ return 0;
+}
+
+int git_process_close(git_process *process)
+{
+ if (process->child_in) {
+ CloseHandle(process->child_in);
+ process->child_in = NULL;
+ }
+
+ if (process->child_out) {
+ CloseHandle(process->child_out);
+ process->child_out = NULL;
+ }
+
+ if (process->child_err) {
+ CloseHandle(process->child_err);
+ process->child_err = NULL;
+ }
+
+ CloseHandle(process->process_info.hProcess);
+ process->process_info.hProcess = NULL;
+
+ CloseHandle(process->process_info.hThread);
+ process->process_info.hThread = NULL;
+
+ return 0;
+}
+
+int git_process_wait(git_process_result *result, git_process *process)
+{
+ DWORD exitcode;
+
+ if (result)
+ memset(result, 0, sizeof(git_process_result));
+
+ if (!process->process_info.dwProcessId) {
+ git_error_set(GIT_ERROR_INVALID, "process is stopped");
+ return -1;
+ }
+
+ if (WaitForSingleObject(process->process_info.hProcess, INFINITE) == WAIT_FAILED) {
+ git_error_set(GIT_ERROR_OS, "could not wait for process");
+ return -1;
+ }
+
+ if (!GetExitCodeProcess(process->process_info.hProcess, &exitcode)) {
+ git_error_set(GIT_ERROR_OS, "could not get process exit code");
+ return -1;
+ }
+
+ result->status = GIT_PROCESS_STATUS_NORMAL;
+ result->exitcode = exitcode;
+
+ memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
+ return 0;
+}
+
+int git_process_result_msg(git_str *out, git_process_result *result)
+{
+ if (result->status == GIT_PROCESS_STATUS_NONE) {
+ return git_str_puts(out, "process not started");
+ } else if (result->status == GIT_PROCESS_STATUS_NORMAL) {
+ return git_str_printf(out, "process exited with code %d",
+ result->exitcode);
+ } else if (result->signal) {
+ return git_str_printf(out, "process exited on signal %d",
+ result->signal);
+ }
+
+ return git_str_puts(out, "unknown error");
+}
+
+void git_process_free(git_process *process)
+{
+ if (!process)
+ return;
+
+ if (process->process_info.hProcess)
+ git_process_close(process);
+
+ git__free(process->env);
+ git__free(process->cwd);
+ git__free(process->cmdline);
+ git__free(process->appname);
+ git__free(process);
+}