Merge branch 'pr/4228'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d5bf21f..ca52db7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -238,6 +238,7 @@
 
 IF (WIN32 AND WINHTTP)
 	ADD_DEFINITIONS(-DGIT_WINHTTP)
+	ADD_DEFINITIONS(-DGIT_HTTPS)
 
 	# Since MinGW does not come with headers or an import library for winhttp,
 	# we have to include a private header and generate our own import library
@@ -546,11 +547,13 @@
 
 IF (SECURITY_FOUND)
   ADD_DEFINITIONS(-DGIT_SECURE_TRANSPORT)
+  ADD_DEFINITIONS(-DGIT_HTTPS)
   INCLUDE_DIRECTORIES(${SECURITY_INCLUDE_DIR})
 ENDIF ()
 
 IF (OPENSSL_FOUND)
   ADD_DEFINITIONS(-DGIT_OPENSSL)
+  ADD_DEFINITIONS(-DGIT_HTTPS)
   INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
   SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES})
 ENDIF()
diff --git a/README.md b/README.md
index 472261c..1bbd371 100644
--- a/README.md
+++ b/README.md
@@ -43,10 +43,9 @@
 What It Can Do
 ==============
 
-The goal of this library is to allow its users the ability to handle Git data in
-their applications from their programming language of choice, as is used in
-production for many applications including the GitHub.com site, in Plastic SCM
-and also powering Microsoft's Visual Studio tools for Git.
+libgit2 provides you with the ability to manage Git repositories in the
+programming language of your choice.  It's used in production to power many
+applications including GitHub.com, Plastic SCM and Visual Studio Team Services.
 
 It does not aim to replace the git tool or its user-facing commands. Some APIs
 resemble the plumbing commands as those align closely with the concepts of the
diff --git a/examples/network/common.c b/examples/network/common.c
index d123eed..1a81a10 100644
--- a/examples/network/common.c
+++ b/examples/network/common.c
@@ -1,5 +1,7 @@
 #include "common.h"
 #include <stdio.h>
+#include <string.h>
+#include <errno.h>
 
 /* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/
  * with permission of the original author, Martin Pool.
@@ -20,15 +22,27 @@
 		unsigned int UNUSED(allowed_types),
 		void * UNUSED(payload))
 {
-	char username[128] = {0};
-	char password[128] = {0};
+	char *username = NULL, *password = NULL;
+	int error;
 
 	printf("Username: ");
-	scanf("%s", username);
+	if (getline(&username, NULL, stdin) < 0) {
+		fprintf(stderr, "Unable to read username: %s", strerror(errno));
+		return -1;
+	}
 
 	/* Yup. Right there on your terminal. Careful where you copy/paste output. */
 	printf("Password: ");
-	scanf("%s", password);
+	if (getline(&password, NULL, stdin) < 0) {
+		fprintf(stderr, "Unable to read password: %s", strerror(errno));
+		free(username);
+		return -1;
+	}
 
-	return git_cred_userpass_plaintext_new(out, username, password);
+	error = git_cred_userpass_plaintext_new(out, username, password);
+
+	free(username);
+	free(password);
+
+	return error;
 }
diff --git a/include/git2/common.h b/include/git2/common.h
index 6d20920..d83e8c3 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -182,6 +182,7 @@
 	GIT_OPT_ENABLE_SYNCHRONOUS_OBJECT_CREATION,
 	GIT_OPT_GET_WINDOWS_SHAREMODE,
 	GIT_OPT_SET_WINDOWS_SHAREMODE,
+	GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
 } git_libgit2_opt_t;
 
 /**
@@ -337,6 +338,13 @@
  *		> is written to permanent storage, not simply cached.  This
  *		> defaults to disabled.
  *
+ *	 opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, int enabled)
+ *
+ *		> Enable strict verification of object hashsums when reading
+ *		> objects from disk. This may impact performance due to an
+ *		> additional checksum calculation on each object. This defaults
+ *		> to enabled.
+ *
  * @param option Option key
  * @param ... value to set the option
  * @return 0 on success, <0 on failure
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 71bff0f..6f55802 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -54,6 +54,7 @@
 	GIT_PASSTHROUGH     = -30,	/**< Internal only */
 	GIT_ITEROVER        = -31,	/**< Signals end of iteration with iterator */
 	GIT_RETRY           = -32,	/**< Internal only */
+	GIT_EMISMATCH       = -33,	/**< Hashsum mismatch in object */
 } git_error_code;
 
 /**
diff --git a/include/git2/remote.h b/include/git2/remote.h
index 244794e..e9e4e5b 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -715,8 +715,8 @@
  * Peform all the steps from a push.
  *
  * @param remote the remote to push to
- * @param refspecs the refspecs to use for pushing. If none are
- * passed, the configured refspecs will be used
+ * @param refspecs the refspecs to use for pushing. If NULL or an empty
+ *                 array, the configured refspecs will be used
  * @param opts options to use for this push
  */
 GIT_EXTERN(int) git_remote_push(git_remote *remote,
diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h
index 60e38b2..a395de5 100644
--- a/include/git2/sys/transport.h
+++ b/include/git2/sys/transport.h
@@ -241,6 +241,16 @@
  */
 GIT_EXTERN(int) git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods);
 
+/**
+ * Get a copy of the proxy options
+ *
+ * The url is copied and must be freed by the caller.
+ *
+ * @param out options struct to fill
+ * @param transport the transport to extract the data from.
+ */
+GIT_EXTERN(int) git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport);
+
 /*
  *** End of base transport interface ***
  *** Begin interface for subtransports for the smart transport ***
diff --git a/src/attr_file.c b/src/attr_file.c
index 0bb761d..e30ea5e 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -395,9 +395,13 @@
 	if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
 		bool samename;
 
-		/* for attribute checks or root ignore checks, fail match */
+		/*
+		 * for attribute checks or checks at the root of this match's
+		 * containing_dir (or root of the repository if no containing_dir),
+		 * do not match.
+		 */
 		if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
-			path->basename == path->path)
+			path->basename == relpath)
 			return false;
 
 		flags |= FNM_LEADING_DIR;
diff --git a/src/attrcache.c b/src/attrcache.c
index 4df14ee..5416189 100644
--- a/src/attrcache.c
+++ b/src/attrcache.c
@@ -290,14 +290,16 @@
 		const char *cfgval = entry->value;
 
 		/* expand leading ~/ as needed */
-		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
-			!git_sysdir_find_global_file(&buf, &cfgval[2]))
-			*out = git_buf_detach(&buf);
-		else if (cfgval)
+		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') {
+			if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2])))
+				*out = git_buf_detach(&buf);
+		} else if (cfgval) {
 			*out = git__strdup(cfgval);
+		}
 	}
-	else if (!git_sysdir_find_xdg_file(&buf, fallback))
+	else if (!git_sysdir_find_xdg_file(&buf, fallback)) {
 		*out = git_buf_detach(&buf);
+	}
 
 	git_config_entry_free(entry);
 	git_buf_free(&buf);
diff --git a/src/branch.c b/src/branch.c
index 7a83b83..fe4955a 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -130,14 +130,16 @@
 static int branch_equals(git_repository *repo, const char *path, void *payload)
 {
 	git_reference *branch = (git_reference *) payload;
-	git_reference *head;
-	int equal;
+	git_reference *head = NULL;
+	int equal = 0;
 
 	if (git_reference__read_head(&head, repo, path) < 0 ||
-	    git_reference_type(head) != GIT_REF_SYMBOLIC)
-		return 0;
+		git_reference_type(head) != GIT_REF_SYMBOLIC)
+		goto done;
 
 	equal = !git__strcmp(head->target.symbolic, branch->name);
+
+done:
 	git_reference_free(head);
 	return equal;
 }
diff --git a/src/config.c b/src/config.c
index cbcea2e..169a628 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1358,9 +1358,6 @@
 
 int git_config_parse_path(git_buf *out, const char *value)
 {
-	int error = 0;
-	const git_buf *home;
-
 	assert(out && value);
 
 	git_buf_sanitize(out);
@@ -1371,16 +1368,7 @@
 			return -1;
 		}
 
-		if ((error = git_sysdir_get(&home, GIT_SYSDIR_GLOBAL)) < 0)
-			return error;
-
-		git_buf_sets(out, home->ptr);
-		git_buf_puts(out, value + 1);
-
-		if (git_buf_oom(out))
-			return -1;
-
-		return 0;
+		return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL);
 	}
 
 	return git_buf_sets(out, value);
diff --git a/src/config_file.c b/src/config_file.c
index 7df43c8..2302d33 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -1256,7 +1256,7 @@
 {
 	/* From the user's home */
 	if (path[0] == '~' && path[1] == '/')
-		return git_sysdir_find_global_file(out, &path[1]);
+		return git_sysdir_expand_global_file(out, &path[1]);
 
 	return git_path_join_unrooted(out, path, dir, NULL);
 }
diff --git a/src/global.c b/src/global.c
index e2ad8fe..afa57e1 100644
--- a/src/global.c
+++ b/src/global.c
@@ -22,7 +22,7 @@
 
 git_mutex git__mwindow_mutex;
 
-#define MAX_SHUTDOWN_CB 8
+#define MAX_SHUTDOWN_CB 9
 
 static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
 static git_atomic git__n_shutdown_callbacks;
diff --git a/src/odb.c b/src/odb.c
index cf321f5..b66324f 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -31,6 +31,8 @@
 
 #define GIT_ALTERNATES_MAX_DEPTH 5
 
+bool git_odb__strict_hash_verification = true;
+
 typedef struct
 {
 	git_odb_backend *backend;
@@ -998,7 +1000,9 @@
 	size_t i;
 	git_rawobj raw;
 	git_odb_object *object;
+	git_oid hashed;
 	bool found = false;
+	int error = 0;
 
 	if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0)
 		found = true;
@@ -1011,7 +1015,7 @@
 			continue;
 
 		if (b->read != NULL) {
-			int error = b->read(&raw.data, &raw.len, &raw.type, b, id);
+			error = b->read(&raw.data, &raw.len, &raw.type, b, id);
 			if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND)
 				continue;
 
@@ -1025,12 +1029,26 @@
 	if (!found)
 		return GIT_ENOTFOUND;
 
+	if (git_odb__strict_hash_verification) {
+		if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0)
+			goto out;
+
+		if (!git_oid_equal(id, &hashed)) {
+			error = git_odb__error_mismatch(id, &hashed);
+			goto out;
+		}
+	}
+
 	giterr_clear();
 	if ((object = odb_object__alloc(id, &raw)) == NULL)
-		return -1;
+		goto out;
 
 	*out = git_cache_store_raw(odb_cache(db), object);
-	return 0;
+
+out:
+	if (error)
+		git__free(raw.data);
+	return error;
 }
 
 int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
@@ -1081,9 +1099,9 @@
 		const git_oid *key, size_t len, bool only_refreshed)
 {
 	size_t i;
-	int error = GIT_ENOTFOUND;
+	int error = 0;
 	git_oid found_full_oid = {{0}};
-	git_rawobj raw;
+	git_rawobj raw = {0};
 	void *data = NULL;
 	bool found = false;
 	git_odb_object *object;
@@ -1102,14 +1120,22 @@
 				continue;
 
 			if (error)
-				return error;
+				goto out;
 
 			git__free(data);
 			data = raw.data;
 
 			if (found && git_oid__cmp(&full_oid, &found_full_oid)) {
-				git__free(raw.data);
-				return git_odb__error_ambiguous("multiple matches for prefix");
+				git_buf buf = GIT_BUF_INIT;
+
+				git_buf_printf(&buf, "multiple matches for prefix: %s",
+					git_oid_tostr_s(&full_oid));
+				git_buf_printf(&buf, " %s",
+					git_oid_tostr_s(&found_full_oid));
+
+				error = git_odb__error_ambiguous(buf.ptr);
+				git_buf_free(&buf);
+				goto out;
 			}
 
 			found_full_oid = full_oid;
@@ -1120,11 +1146,28 @@
 	if (!found)
 		return GIT_ENOTFOUND;
 
+	if (git_odb__strict_hash_verification) {
+		git_oid hash;
+
+		if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0)
+			goto out;
+
+		if (!git_oid_equal(&found_full_oid, &hash)) {
+			error = git_odb__error_mismatch(&found_full_oid, &hash);
+			goto out;
+		}
+	}
+
 	if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL)
-		return -1;
+		goto out;
 
 	*out = git_cache_store_raw(odb_cache(db), object);
-	return 0;
+
+out:
+	if (error)
+		git__free(raw.data);
+
+	return error;
 }
 
 int git_odb_read_prefix(
@@ -1283,9 +1326,9 @@
 {
 	giterr_set(GITERR_ODB,
 		"cannot %s - "
-		"Invalid length. %"PRIuZ" was expected. The "
-		"total size of the received chunks amounts to %"PRIuZ".",
-		action, stream->declared_size, stream->received_bytes);		
+		"Invalid length. %"PRIdZ" was expected. The "
+		"total size of the received chunks amounts to %"PRIdZ".",
+		action, stream->declared_size, stream->received_bytes);
 
 	return -1;
 }
@@ -1411,6 +1454,19 @@
 	return 0;
 }
 
+int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual)
+{
+	char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1];
+
+	git_oid_tostr(expected_oid, sizeof(expected_oid), expected);
+	git_oid_tostr(actual_oid, sizeof(actual_oid), actual);
+
+	giterr_set(GITERR_ODB, "object hash mismatch - expected %s but got %s",
+		expected_oid, actual_oid);
+
+	return GIT_EMISMATCH;
+}
+
 int git_odb__error_notfound(
 	const char *message, const git_oid *oid, size_t oid_len)
 {
diff --git a/src/odb.h b/src/odb.h
index 4f548bb..61d687a 100644
--- a/src/odb.h
+++ b/src/odb.h
@@ -20,6 +20,8 @@
 #define GIT_OBJECT_DIR_MODE 0777
 #define GIT_OBJECT_FILE_MODE 0444
 
+extern bool git_odb__strict_hash_verification;
+
 /* DO NOT EXPORT */
 typedef struct {
 	void *data;			/**< Raw, decompressed object data. */
@@ -96,6 +98,12 @@
  */
 int git_odb__hashlink(git_oid *out, const char *path);
 
+/**
+ * Generate a GIT_EMISMATCH error for the ODB.
+ */
+int git_odb__error_mismatch(
+	const git_oid *expected, const git_oid *actual);
+
 /*
  * Generate a GIT_ENOTFOUND error for the ODB.
  */
diff --git a/src/odb_loose.c b/src/odb_loose.c
index e14af4f..a97ac25 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -205,6 +205,11 @@
 	return inflate(s, 0);
 }
 
+static void abort_inflate(z_stream *s)
+{
+	inflateEnd(s);
+}
+
 static int finish_inflate(z_stream *s)
 {
 	int status = Z_OK;
@@ -367,6 +372,7 @@
 		(used = get_object_header(&hdr, head)) == 0 ||
 		!git_object_typeisloose(hdr.type))
 	{
+		abort_inflate(&zs);
 		giterr_set(GITERR_ODB, "failed to inflate disk object");
 		return -1;
 	}
diff --git a/src/refs.c b/src/refs.c
index 31410b7..632a529 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -277,8 +277,8 @@
 	}
 
 out:
-	free(name);
-	git_buf_clear(&reference);
+	git__free(name);
+	git_buf_free(&reference);
 
 	return error;
 }
diff --git a/src/remote.c b/src/remote.c
index d3132f7..bd8b3cf 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -192,7 +192,7 @@
 static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
 {
 	git_remote *remote;
-	git_config *config = NULL;
+	git_config *config_ro = NULL, *config_rw;
 	git_buf canonical_url = GIT_BUF_INIT;
 	git_buf var = GIT_BUF_INIT;
 	int error = -1;
@@ -200,7 +200,7 @@
 	/* name is optional */
 	assert(out && repo && url);
 
-	if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+	if ((error = git_repository_config_snapshot(&config_ro, repo)) < 0)
 		return error;
 
 	remote = git__calloc(1, sizeof(git_remote));
@@ -212,7 +212,8 @@
 		(error = canonicalize_url(&canonical_url, url)) < 0)
 		goto on_error;
 
-	remote->url = apply_insteadof(repo->_config, canonical_url.ptr, GIT_DIRECTION_FETCH);
+	remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
+	GITERR_CHECK_ALLOC(remote->url);
 
 	if (name != NULL) {
 		remote->name = git__strdup(name);
@@ -221,7 +222,8 @@
 		if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0)
 			goto on_error;
 
-		if ((error = git_config_set_string(config, var.ptr, canonical_url.ptr)) < 0)
+		if ((error = git_repository_config__weakptr(&config_rw, repo)) < 0 ||
+			(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)
 			goto on_error;
 	}
 
@@ -233,10 +235,7 @@
 		if (name && (error = write_add_refspec(repo, name, fetch, true)) < 0)
 			goto on_error;
 
-		if ((error = git_repository_config_snapshot(&config, repo)) < 0)
-			goto on_error;
-
-		if ((error = lookup_remote_prune_config(remote, config, name)) < 0)
+		if ((error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
 			goto on_error;
 
 		/* Move the data over to where the matching functions can find them */
@@ -260,7 +259,7 @@
 	if (error)
 		git_remote_free(remote);
 
-	git_config_free(config);
+	git_config_free(config_ro);
 	git_buf_free(&canonical_url);
 	git_buf_free(&var);
 	return error;
@@ -2412,7 +2411,7 @@
 		proxy = &opts->proxy_opts;
 	}
 
-	assert(remote && refspecs);
+	assert(remote);
 
 	if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0)
 		return error;
diff --git a/src/repository.c b/src/repository.c
index 707b7b7..d0a38cc 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -422,10 +422,10 @@
 }
 
 static int find_repo(
-	git_buf *repo_path,
-	git_buf *parent_path,
-	git_buf *link_path,
-	git_buf *common_path,
+	git_buf *gitdir_path,
+	git_buf *workdir_path,
+	git_buf *gitlink_path,
+	git_buf *commondir_path,
 	const char *start_path,
 	uint32_t flags,
 	const char *ceiling_dirs)
@@ -440,7 +440,7 @@
 	bool in_dot_git;
 	size_t ceiling_offset = 0;
 
-	git_buf_free(repo_path);
+	git_buf_clear(gitdir_path);
 
 	error = git_path_prettify(&path, start_path, NULL);
 	if (error < 0)
@@ -482,13 +482,13 @@
 			if (S_ISDIR(st.st_mode)) {
 				if (valid_repository_path(&path, &common_link)) {
 					git_path_to_dir(&path);
-					git_buf_set(repo_path, path.ptr, path.size);
+					git_buf_set(gitdir_path, path.ptr, path.size);
 
-					if (link_path)
-						git_buf_attach(link_path,
+					if (gitlink_path)
+						git_buf_attach(gitlink_path,
 							git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0);
-					if (common_path)
-						git_buf_swap(&common_link, common_path);
+					if (commondir_path)
+						git_buf_swap(&common_link, commondir_path);
 
 					break;
 				}
@@ -498,12 +498,12 @@
 				if (error < 0)
 					break;
 				if (valid_repository_path(&repo_link, &common_link)) {
-					git_buf_swap(repo_path, &repo_link);
+					git_buf_swap(gitdir_path, &repo_link);
 
-					if (link_path)
-						error = git_buf_put(link_path, path.ptr, path.size);
-					if (common_path)
-						git_buf_swap(&common_link, common_path);
+					if (gitlink_path)
+						error = git_buf_put(gitlink_path, path.ptr, path.size);
+					if (commondir_path)
+						git_buf_swap(&common_link, commondir_path);
 				}
 				break;
 			}
@@ -529,20 +529,20 @@
 			break;
 	}
 
-	if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
-		if (!git_buf_len(repo_path))
-			git_buf_clear(parent_path);
+	if (!error && workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
+		if (!git_buf_len(gitdir_path))
+			git_buf_clear(workdir_path);
 		else {
-			git_path_dirname_r(parent_path, path.ptr);
-			git_path_to_dir(parent_path);
+			git_path_dirname_r(workdir_path, path.ptr);
+			git_path_to_dir(workdir_path);
 		}
-		if (git_buf_oom(parent_path))
+		if (git_buf_oom(workdir_path))
 			return -1;
 	}
 
 	/* If we didn't find the repository, and we don't have any other error
 	 * to report, report that. */
-	if (!git_buf_len(repo_path) && !error) {
+	if (!git_buf_len(gitdir_path) && !error) {
 		giterr_set(GITERR_REPOSITORY,
 			"could not find repository from '%s'", start_path);
 		error = GIT_ENOTFOUND;
@@ -758,6 +758,29 @@
 	return error;
 }
 
+static int repo_is_worktree(unsigned *out, const git_repository *repo)
+{
+	git_buf gitdir_link = GIT_BUF_INIT;
+	int error;
+
+	/* Worktrees cannot have the same commondir and gitdir */
+	if (repo->commondir && repo->gitdir
+	    && !strcmp(repo->commondir, repo->gitdir)) {
+		*out = 0;
+		return 0;
+	}
+
+	if ((error = git_buf_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0)
+		return -1;
+
+	/* A 'gitdir' file inside a git directory is currently
+	 * only used when the repository is a working tree. */
+	*out = !!git_path_exists(gitdir_link.ptr);
+
+	git_buf_free(&gitdir_link);
+	return error;
+}
+
 int git_repository_open_ext(
 	git_repository **repo_ptr,
 	const char *start_path,
@@ -765,8 +788,9 @@
 	const char *ceiling_dirs)
 {
 	int error;
-	git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
-		link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
+	unsigned is_worktree;
+	git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT,
+		gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT;
 	git_repository *repo;
 	git_config *config = NULL;
 
@@ -777,7 +801,7 @@
 		*repo_ptr = NULL;
 
 	error = find_repo(
-		&path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs);
+		&gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs);
 
 	if (error < 0 || !repo_ptr)
 		return error;
@@ -785,24 +809,21 @@
 	repo = repository_alloc();
 	GITERR_CHECK_ALLOC(repo);
 
-	repo->gitdir = git_buf_detach(&path);
+	repo->gitdir = git_buf_detach(&gitdir);
 	GITERR_CHECK_ALLOC(repo->gitdir);
 
-	if (link_path.size) {
-		repo->gitlink = git_buf_detach(&link_path);
+	if (gitlink.size) {
+		repo->gitlink = git_buf_detach(&gitlink);
 		GITERR_CHECK_ALLOC(repo->gitlink);
 	}
-	if (common_path.size) {
-		repo->commondir = git_buf_detach(&common_path);
+	if (commondir.size) {
+		repo->commondir = git_buf_detach(&commondir);
 		GITERR_CHECK_ALLOC(repo->commondir);
 	}
 
-	if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0)
+	if ((error = repo_is_worktree(&is_worktree, repo)) < 0)
 		goto cleanup;
-	/* A 'gitdir' file inside a git directory is currently
-	 * only used when the repository is a working tree. */
-	if (git_path_exists(path.ptr))
-		repo->is_worktree = 1;
+	repo->is_worktree = is_worktree;
 
 	/*
 	 * We'd like to have the config, but git doesn't particularly
@@ -822,13 +843,13 @@
 
 		if (config &&
 		    ((error = load_config_data(repo, config)) < 0 ||
-		     (error = load_workdir(repo, config, &parent)) < 0))
+		     (error = load_workdir(repo, config, &workdir)) < 0))
 			goto cleanup;
 	}
 
 cleanup:
-	git_buf_free(&path);
-	git_buf_free(&parent);
+	git_buf_free(&gitdir);
+	git_buf_free(&workdir);
 	git_config_free(config);
 
 	if (error < 0)
@@ -2132,7 +2153,8 @@
 out:
 	if (error)
 		git_reference_free(head);
-	git_buf_clear(&path);
+
+	git_buf_free(&path);
 
 	return error;
 }
@@ -2531,7 +2553,9 @@
 
 	git_buf_puts(out, " to ");
 
-	if (git_reference__is_branch(new))
+	if (git_reference__is_branch(new) ||
+		git_reference__is_tag(new) ||
+		git_reference__is_remote(new))
 		git_buf_puts(out, git_reference__shorthand(new));
 	else
 		git_buf_puts(out, new);
@@ -2542,6 +2566,41 @@
 	return 0;
 }
 
+static int detach(git_repository *repo, const git_oid *id, const char *new)
+{
+	int error;
+	git_buf log_message = GIT_BUF_INIT;
+	git_object *object = NULL, *peeled = NULL;
+	git_reference *new_head = NULL, *current = NULL;
+
+	assert(repo && id);
+
+	if ((error = git_reference_lookup(&current, repo, GIT_HEAD_FILE)) < 0)
+		return error;
+
+	if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0)
+		goto cleanup;
+
+	if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)
+		goto cleanup;
+
+	if (new == NULL)
+		new = git_oid_tostr_s(git_object_id(peeled));
+
+	if ((error = checkout_message(&log_message, current, new)) < 0)
+		goto cleanup;
+
+	error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message));
+
+cleanup:
+	git_buf_free(&log_message);
+	git_object_free(object);
+	git_object_free(peeled);
+	git_reference_free(current);
+	git_reference_free(new_head);
+	return error;
+}
+
 int git_repository_set_head(
 	git_repository* repo,
 	const char* refname)
@@ -2575,7 +2634,8 @@
 			error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE,
 					git_reference_name(ref), true, git_buf_cstr(&log_message));
 		} else {
-			error = git_repository_set_head_detached(repo, git_reference_target(ref));
+			error = detach(repo, git_reference_target(ref),
+				git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL);
 		}
 	} else if (git_reference__is_branch(refname)) {
 		error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname,
@@ -2590,41 +2650,6 @@
 	return error;
 }
 
-static int detach(git_repository *repo, const git_oid *id, const char *from)
-{
-	int error;
-	git_buf log_message = GIT_BUF_INIT;
-	git_object *object = NULL, *peeled = NULL;
-	git_reference *new_head = NULL, *current = NULL;
-
-	assert(repo && id);
-
-	if ((error = git_reference_lookup(&current, repo, GIT_HEAD_FILE)) < 0)
-		return error;
-
-	if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0)
-		goto cleanup;
-
-	if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)
-		goto cleanup;
-
-	if (from == NULL)
-		from = git_oid_tostr_s(git_object_id(peeled));
-
-	if ((error = checkout_message(&log_message, current, from)) < 0)
-		goto cleanup;
-
-	error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message));
-
-cleanup:
-	git_buf_free(&log_message);
-	git_object_free(object);
-	git_object_free(peeled);
-	git_reference_free(current);
-	git_reference_free(new_head);
-	return error;
-}
-
 int git_repository_set_head_detached(
 	git_repository* repo,
 	const git_oid* commitish)
diff --git a/src/revparse.c b/src/revparse.c
index d5511b4..fd6bd1e 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -892,6 +892,17 @@
 		const char *rstr;
 		revspec->flags = GIT_REVPARSE_RANGE;
 
+		/*
+		 * Following git.git, don't allow '..' because it makes command line
+		 * arguments which can be either paths or revisions ambiguous when the
+		 * path is almost certainly intended. The empty range '...' is still
+		 * allowed.
+		 */
+		if (!git__strcmp(spec, "..")) {
+			giterr_set(GITERR_INVALID, "Invalid pattern '..'");
+			return GIT_EINVALIDSPEC;
+		}
+
 		lstr = git__substrdup(spec, dotdot - spec);
 		rstr = dotdot + 2;
 		if (dotdot[2] == '.') {
@@ -899,9 +910,17 @@
 			rstr++;
 		}
 
-		error = git_revparse_single(&revspec->from, repo, lstr);
-		if (!error)
-			error = git_revparse_single(&revspec->to, repo, rstr);
+		error = git_revparse_single(
+			&revspec->from,
+			repo,
+			*lstr == '\0' ? "HEAD" : lstr);
+
+		if (!error) {
+			error = git_revparse_single(
+				&revspec->to,
+				repo,
+				*rstr == '\0' ? "HEAD" : rstr);
+		}
 
 		git__free((void*)lstr);
 	} else {
diff --git a/src/settings.c b/src/settings.c
index 07ac16a..25c5aae 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -15,6 +15,7 @@
 #include "cache.h"
 #include "global.h"
 #include "object.h"
+#include "odb.h"
 #include "refs.h"
 #include "transports/smart.h"
 
@@ -31,7 +32,7 @@
 #ifdef GIT_THREADS
 		| GIT_FEATURE_THREADS
 #endif
-#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
+#ifdef GIT_HTTPS
 		| GIT_FEATURE_HTTPS
 #endif
 #if defined(GIT_SSH)
@@ -243,6 +244,10 @@
 #endif
 		break;
 
+	case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION:
+		git_odb__strict_hash_verification = (va_arg(ap, int) != 0);
+		break;
+
 	default:
 		giterr_set(GITERR_INVALID, "invalid option key");
 		error = -1;
diff --git a/src/signature.c b/src/signature.c
index e792a52..a56b8a2 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -228,8 +228,11 @@
 		const char *time_start = email_end + 2;
 		const char *time_end;
 
-		if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)
+		if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) {
+			git__free(sig->name);
+			git__free(sig->email);
 			return signature_error("invalid Unix timestamp");
+		}
 
 		/* do we have a timezone? */
 		if (time_end + 1 < buffer_end) {
diff --git a/src/sysdir.c b/src/sysdir.c
index ed11221..9312a7e 100644
--- a/src/sysdir.c
+++ b/src/sysdir.c
@@ -275,3 +275,14 @@
 		path, NULL, GIT_SYSDIR_TEMPLATE, "template");
 }
 
+int git_sysdir_expand_global_file(git_buf *path, const char *filename)
+{
+	int error;
+
+	if ((error = git_sysdir_find_global_file(path, NULL)) == 0) {
+		if (filename)
+			error = git_buf_joinpath(path, path->ptr, filename);
+	}
+
+	return error;
+}
diff --git a/src/sysdir.h b/src/sysdir.h
index 1187898..79f2381 100644
--- a/src/sysdir.h
+++ b/src/sysdir.h
@@ -55,6 +55,18 @@
  */
 extern int git_sysdir_find_template_dir(git_buf *path);
 
+/**
+ * Expand the name of a "global" file (i.e. one in a user's home
+ * directory).  Unlike `find_global_file` (above), this makes no
+ * attempt to check for the existence of the file, and is useful if
+ * you want the full path regardless of existence.
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file in the home directory
+ * @return 0 on success or -1 on error
+ */
+extern int git_sysdir_expand_global_file(git_buf *path, const char *filename);
+
 typedef enum {
 	GIT_SYSDIR_SYSTEM = 0,
 	GIT_SYSDIR_GLOBAL = 1,
diff --git a/src/transports/http.c b/src/transports/http.c
index 949e857..cb4a6d0 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -575,6 +575,9 @@
 		if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
 			return error;
 
+		opts.credentials = t->owner->proxy.credentials;
+		opts.certificate_check = t->owner->proxy.certificate_check;
+		opts.payload = t->owner->proxy.payload;
 		opts.type = GIT_PROXY_SPECIFIED;
 		opts.url = url;
 		error = git_stream_set_proxy(t->io, &opts);
diff --git a/src/transports/smart.c b/src/transports/smart.c
index e4aa26d..a96fdf6 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -472,6 +472,12 @@
 	return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload);
 }
 
+int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport)
+{
+	transport_smart *t = (transport_smart *) transport;
+	return git_proxy_options_dup(out, &t->proxy);
+}
+
 int git_transport_smart(git_transport **out, git_remote *owner, void *param)
 {
 	transport_smart *t;
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index 44d02e5..4c55e3f 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -9,6 +9,7 @@
 #include <libssh2.h>
 #endif
 
+#include "global.h"
 #include "git2.h"
 #include "buffer.h"
 #include "netops.h"
@@ -893,11 +894,22 @@
 #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) {
+		giterr_set(GITERR_SSH, "unable to initialize libssh2");
+		return -1;
+	}
 
-	libssh2_init(0);
+	git__on_shutdown(shutdown_ssh);
 	return 0;
 
 #else
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index 3a19389..a089ee4 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -291,3 +291,15 @@
 	assert_is_ignored(true, "symlink");
 	assert_is_ignored(true, "lala/../symlink");
 }
+
+void test_attr_ignore__test(void)
+{
+	cl_git_rewritefile("attr/.gitignore",
+		"/*/\n"
+		"!/src\n");
+	assert_is_ignored(false, "src/foo.c");
+	assert_is_ignored(false, "src/foo/foo.c");
+	assert_is_ignored(false, "README.md");
+	assert_is_ignored(true, "dist/foo.o");
+	assert_is_ignored(true, "bin/foo");
+}
diff --git a/tests/clone/local.c b/tests/clone/local.c
index 91a0a1c..7f54d05 100644
--- a/tests/clone/local.c
+++ b/tests/clone/local.c
@@ -16,6 +16,7 @@
 	return git_buf_printf(buf, "file://%s/%s", host, path);
 }
 
+#ifdef GIT_WIN32
 static int git_style_unc_path(git_buf *buf, const char *host, const char *path)
 {
 	git_buf_clear(buf);
@@ -49,6 +50,7 @@
 
 	return 0;
 }
+#endif
 
 void test_clone_local__should_clone_local(void)
 {
diff --git a/tests/config/include.c b/tests/config/include.c
index 882b89b..0a07c9b 100644
--- a/tests/config/include.c
+++ b/tests/config/include.c
@@ -108,6 +108,26 @@
 	git_config_free(cfg);
 }
 
+void test_config_include__missing_homedir(void)
+{
+	git_config *cfg;
+	git_buf buf = GIT_BUF_INIT;
+
+	cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config")));
+	cl_git_mkfile("including", "[include]\npath = ~/.nonexistentfile\n[foo]\nbar = baz");
+
+	giterr_clear();
+	cl_git_pass(git_config_open_ondisk(&cfg, "including"));
+	cl_assert(giterr_last() == NULL);
+	cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar"));
+	cl_assert_equal_s("baz", git_buf_cstr(&buf));
+
+	git_buf_free(&buf);
+	git_config_free(cfg);
+
+	cl_sandbox_set_search_path_defaults();
+}
+
 #define replicate10(s) s s s s s s s s s s
 void test_config_include__depth2(void)
 {
diff --git a/tests/core/features.c b/tests/core/features.c
index cf5e190..7b28cc0 100644
--- a/tests/core/features.c
+++ b/tests/core/features.c
@@ -17,7 +17,9 @@
 	cl_assert((caps & GIT_FEATURE_THREADS) == 0);
 #endif
 
+#ifdef GIT_HTTPS
 	cl_assert((caps & GIT_FEATURE_HTTPS) != 0);
+#endif
 
 #if defined(GIT_SSH)
 	cl_assert((caps & GIT_FEATURE_SSH) != 0);
diff --git a/tests/object/lookup.c b/tests/object/lookup.c
index cfa6d46..544f32b 100644
--- a/tests/object/lookup.c
+++ b/tests/object/lookup.c
@@ -6,13 +6,12 @@
 
 void test_object_lookup__initialize(void)
 {
-   cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+	g_repo = cl_git_sandbox_init("testrepo.git");
 }
 
 void test_object_lookup__cleanup(void)
 {
-	git_repository_free(g_repo);
-	g_repo = NULL;
+	cl_git_sandbox_cleanup();
 }
 
 void test_object_lookup__lookup_wrong_type_returns_enotfound(void)
@@ -63,3 +62,61 @@
 		GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
 }
 
+void test_object_lookup__lookup_corrupt_object_returns_error(void)
+{
+	const char *commit = "8e73b769e97678d684b809b163bebdae2911720f",
+	      *file = "objects/8e/73b769e97678d684b809b163bebdae2911720f";
+	git_buf path = GIT_BUF_INIT, contents = GIT_BUF_INIT;
+	git_oid oid;
+	git_object *object;
+	size_t i;
+
+	cl_git_pass(git_oid_fromstr(&oid, commit));
+	cl_git_pass(git_buf_joinpath(&path, git_repository_path(g_repo), file));
+	cl_git_pass(git_futils_readbuffer(&contents, path.ptr));
+
+	/* Corrupt and try to read the object */
+	for (i = 0; i < contents.size; i++) {
+		contents.ptr[i] ^= 0x1;
+		cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644));
+		cl_git_fail(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
+		contents.ptr[i] ^= 0x1;
+	}
+
+	/* Restore original content and assert we can read the object */
+	cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644));
+	cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
+
+	git_object_free(object);
+	git_buf_free(&path);
+	git_buf_free(&contents);
+}
+
+void test_object_lookup__lookup_object_with_wrong_hash_returns_error(void)
+{
+	const char *oldloose = "objects/8e/73b769e97678d684b809b163bebdae2911720f",
+	      *newloose = "objects/8e/73b769e97678d684b809b163bebdae2911720e",
+	      *commit = "8e73b769e97678d684b809b163bebdae2911720e";
+	git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT;
+	git_object *object;
+	git_oid oid;
+
+	cl_git_pass(git_oid_fromstr(&oid, commit));
+
+	/* Copy object to another location with wrong hash */
+	cl_git_pass(git_buf_joinpath(&oldpath, git_repository_path(g_repo), oldloose));
+	cl_git_pass(git_buf_joinpath(&newpath, git_repository_path(g_repo), newloose));
+	cl_git_pass(git_futils_cp(oldpath.ptr, newpath.ptr, 0644));
+
+	/* Verify that lookup fails due to a hashsum mismatch */
+	cl_git_fail_with(GIT_EMISMATCH, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
+
+	/* Disable verification and try again */
+	cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0));
+	cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
+	cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1));
+
+	git_object_free(object);
+	git_buf_free(&oldpath);
+	git_buf_free(&newpath);
+}
diff --git a/tests/odb/backend/nonrefreshing.c b/tests/odb/backend/nonrefreshing.c
index b435294..f12ac74 100644
--- a/tests/odb/backend/nonrefreshing.c
+++ b/tests/odb/backend/nonrefreshing.c
@@ -17,6 +17,9 @@
 static fake_backend *_fake;
 static git_oid _oid;
 
+#define HASH "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+#define EMPTY_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
+
 static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid)
 {
 	fake_backend *fake;
@@ -78,7 +81,6 @@
 {
 	fake_backend *fake;
 
-	GIT_UNUSED(out_oid);
 	GIT_UNUSED(buffer_p);
 	GIT_UNUSED(len_p);
 	GIT_UNUSED(type_p);
@@ -89,6 +91,7 @@
 
 	fake->read_prefix_calls++;
 
+	git_oid_cpy(out_oid, &_oid);
 	*len_p = 0;
 	*buffer_p = NULL;
 	*type_p = GIT_OBJ_BLOB;
@@ -130,7 +133,7 @@
 	return 0;
 }
 
-static void setup_repository_and_backend(git_error_code error_code)
+static void setup_repository_and_backend(git_error_code error_code, const char *hash)
 {
 	git_odb *odb = NULL;
 	git_odb_backend *backend = NULL;
@@ -144,7 +147,7 @@
 
 	_fake = (fake_backend *)backend;
 
-	cl_git_pass(git_oid_fromstr(&_oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+	cl_git_pass(git_oid_fromstr(&_oid, hash));
 }
 
 void test_odb_backend_nonrefreshing__cleanup(void)
@@ -156,7 +159,7 @@
 {
 	git_odb *odb;
 
-	setup_repository_and_backend(GIT_ENOTFOUND);
+	setup_repository_and_backend(GIT_ENOTFOUND, HASH);
 
 	cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
 	cl_assert_equal_b(false, git_odb_exists(odb, &_oid));
@@ -168,7 +171,7 @@
 {
 	git_object *obj;
 
-	setup_repository_and_backend(GIT_ENOTFOUND);
+	setup_repository_and_backend(GIT_ENOTFOUND, HASH);
 
 	cl_git_fail_with(
 		git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY),
@@ -181,7 +184,7 @@
 {
 	git_object *obj;
 
-	setup_repository_and_backend(GIT_ENOTFOUND);
+	setup_repository_and_backend(GIT_ENOTFOUND, HASH);
 
 	cl_git_fail_with(
 		git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY),
@@ -196,7 +199,7 @@
 	size_t len;
 	git_otype type;
 
-	setup_repository_and_backend(GIT_ENOTFOUND);
+	setup_repository_and_backend(GIT_ENOTFOUND, HASH);
 
 	cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
 
@@ -211,7 +214,7 @@
 {
 	git_odb *odb;
 
-	setup_repository_and_backend(GIT_OK);
+	setup_repository_and_backend(GIT_OK, HASH);
 
 	cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
 	cl_assert_equal_b(true, git_odb_exists(odb, &_oid));
@@ -223,7 +226,7 @@
 {
 	git_object *obj;
 
-	setup_repository_and_backend(GIT_OK);
+	setup_repository_and_backend(GIT_OK, EMPTY_HASH);
 
 	cl_git_pass(git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY));
 
@@ -236,7 +239,7 @@
 {
 	git_object *obj;
 
-	setup_repository_and_backend(GIT_OK);
+	setup_repository_and_backend(GIT_OK, EMPTY_HASH);
 
 	cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY));
 
@@ -251,7 +254,7 @@
 	size_t len;
 	git_otype type;
 
-	setup_repository_and_backend(GIT_OK);
+	setup_repository_and_backend(GIT_OK, HASH);
 
 	cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
 
@@ -264,7 +267,7 @@
 {
 	git_object *obj;
 
-	setup_repository_and_backend(GIT_ENOTFOUND);
+	setup_repository_and_backend(GIT_ENOTFOUND, HASH);
 
 	cl_git_fail_with(
 		git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
diff --git a/tests/online/badssl.c b/tests/online/badssl.c
index aa4c24d..6524fcd 100644
--- a/tests/online/badssl.c
+++ b/tests/online/badssl.c
@@ -4,7 +4,7 @@
 
 static git_repository *g_repo;
 
-#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT)
+#ifdef GIT_HTTPS
 static bool g_has_ssl = true;
 #else
 static bool g_has_ssl = false;
diff --git a/tests/refs/revparse.c b/tests/refs/revparse.c
index c22c304..459188c 100644
--- a/tests/refs/revparse.c
+++ b/tests/refs/revparse.c
@@ -122,6 +122,14 @@
 	test_id_inrepo(spec, expected_left, expected_right, expected_flags, g_repo);
 }
 
+static void test_invalid_revspec(const char* invalid_spec)
+{
+	git_revspec revspec;
+
+	cl_assert_equal_i(
+		GIT_EINVALIDSPEC, git_revparse(&revspec, g_repo, invalid_spec));
+}
+
 void test_refs_revparse__initialize(void)
 {
 	cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
@@ -749,6 +757,33 @@
 		"4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
 		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
 		GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+
+	test_id("HEAD~3..",
+		"4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		GIT_REVPARSE_RANGE);
+
+	test_id("HEAD~3...",
+		"4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+
+	test_id("..HEAD~3",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		"4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+		GIT_REVPARSE_RANGE);
+
+	test_id("...HEAD~3",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		"4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+		GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+
+	test_id("...",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		"a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+		GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+
+	test_invalid_revspec("..");
 }
 
 void test_refs_revparse__ext_retrieves_both_the_reference_and_its_target(void)
diff --git a/tests/repo/discover.c b/tests/repo/discover.c
index abb7bd1..eadd055 100644
--- a/tests/repo/discover.c
+++ b/tests/repo/discover.c
@@ -33,7 +33,7 @@
 	git_buf_attach(&resolved, p_realpath(expected_path, NULL), 0);
 	cl_assert(resolved.size > 0);
 	cl_git_pass(git_path_to_dir(&resolved));
-	cl_git_pass(git_repository_discover(&found_path, start_path, 0, ceiling_dirs));
+	cl_git_pass(git_repository_discover(&found_path, start_path, 1, ceiling_dirs));
 
 	cl_assert_equal_s(found_path.ptr, resolved.ptr);
 
diff --git a/tests/repo/head.c b/tests/repo/head.c
index 31c2287..d021160 100644
--- a/tests/repo/head.c
+++ b/tests/repo/head.c
@@ -261,15 +261,19 @@
 	cl_git_pass(git_revparse_single(&tag, repo, "tags/test"));
 	cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag)));
 	cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked"));
+	cl_git_pass(git_repository_set_head(repo, "refs/tags/test"));
+	cl_git_pass(git_repository_set_head(repo, "refs/remotes/test/master"));
 
-	test_reflog(repo, 2, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from master to haacked");
-	test_reflog(repo, 1, NULL, "tags/test^{commit}", "foo@example.com", "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d");
-	test_reflog(repo, 0, "tags/test^{commit}", "refs/heads/haacked", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked");
+	test_reflog(repo, 4, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from master to haacked");
+	test_reflog(repo, 3, NULL, "tags/test^{commit}", "foo@example.com", "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d");
+	test_reflog(repo, 2, "tags/test^{commit}", "refs/heads/haacked", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked");
+	test_reflog(repo, 1, "refs/heads/haacked", "tags/test^{commit}", "foo@example.com", "checkout: moving from haacked to test");
+	test_reflog(repo, 0, "tags/test^{commit}", "refs/remotes/test/master", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master");
 
 	cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "haacked~0"));
 	cl_git_pass(git_repository_set_head_detached_from_annotated(repo, annotated));
 
-	test_reflog(repo, 0, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from haacked to haacked~0");
+	test_reflog(repo, 0, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0");
 
 	git_annotated_commit_free(annotated);
 	git_object_free(tag);
diff --git a/tests/repo/open.c b/tests/repo/open.c
index 6114ad2..3239b6f 100644
--- a/tests/repo/open.c
+++ b/tests/repo/open.c
@@ -398,7 +398,8 @@
 	cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2"));
 
 	cl_git_pass(git_repository_open_ext(
-		&barerepo, "alternate/subdir/sub2", GIT_REPOSITORY_OPEN_BARE, NULL));
+		&barerepo, "alternate/subdir/sub2",
+		GIT_REPOSITORY_OPEN_BARE|GIT_REPOSITORY_OPEN_CROSS_FS, NULL));
 	cl_assert(git_repository_is_bare(barerepo));
 	git_repository_free(barerepo);
 }
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index 251de39..23384fb 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -1077,3 +1077,81 @@
     cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config"));
     cl_assert_equal_i(0, ignored);
 }
+
+void test_status_ignore__ignore_all_toplevel_dirs_include_files(void)
+{
+	static const char *test_files[] = {
+		"empty_standard_repo/README.md",
+		"empty_standard_repo/src/main.c",
+		"empty_standard_repo/src/foo/foo.c",
+		"empty_standard_repo/dist/foo.o",
+		"empty_standard_repo/dist/main.o",
+		NULL
+	};
+
+	make_test_data("empty_standard_repo", test_files);
+	cl_git_mkfile(
+		"empty_standard_repo/.gitignore",
+		"/*/\n"
+		"!/src\n");
+
+	assert_is_ignored("dist/foo.o");
+	assert_is_ignored("dist/main.o");
+
+	refute_is_ignored("README.md");
+	refute_is_ignored("src/foo.c");
+	refute_is_ignored("src/foo/foo.c");
+}
+
+void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void)
+{
+	static const char *test_files[] = {
+		"empty_standard_repo/project/README.md",
+		"empty_standard_repo/project/src/main.c",
+		"empty_standard_repo/project/src/foo/foo.c",
+		"empty_standard_repo/project/dist/foo.o",
+		"empty_standard_repo/project/dist/main.o",
+		NULL
+	};
+
+	make_test_data("empty_standard_repo", test_files);
+	cl_git_mkfile(
+		"empty_standard_repo/project/.gitignore",
+		"/*/\n"
+		"!/src\n");
+
+	assert_is_ignored("project/dist/foo.o");
+	assert_is_ignored("project/dist/main.o");
+
+	refute_is_ignored("project/src/foo.c");
+	refute_is_ignored("project/src/foo/foo.c");
+	refute_is_ignored("project/README.md");
+}
+
+void test_status_ignore__subdir_ignore_everything_except_certain_files(void)
+{
+	static const char *test_files[] = {
+		"empty_standard_repo/project/README.md",
+		"empty_standard_repo/project/some_file",
+		"empty_standard_repo/project/src/main.c",
+		"empty_standard_repo/project/src/foo/foo.c",
+		"empty_standard_repo/project/dist/foo.o",
+		"empty_standard_repo/project/dist/main.o",
+		NULL
+	};
+
+	make_test_data("empty_standard_repo", test_files);
+	cl_git_mkfile(
+		"empty_standard_repo/project/.gitignore",
+		"/*\n"
+		"!/src\n"
+		"!README.md\n");
+
+	assert_is_ignored("project/some_file");
+	assert_is_ignored("project/dist/foo.o");
+	assert_is_ignored("project/dist/main.o");
+
+	refute_is_ignored("project/README.md");
+	refute_is_ignored("project/src/foo.c");
+	refute_is_ignored("project/src/foo/foo.c");
+}
diff --git a/tests/submodule/open.c b/tests/submodule/open.c
new file mode 100644
index 0000000..0ef01ec
--- /dev/null
+++ b/tests/submodule/open.c
@@ -0,0 +1,90 @@
+#include "clar_libgit2.h"
+#include "submodule_helpers.h"
+#include "path.h"
+
+static git_repository *g_parent;
+static git_repository *g_child;
+static git_submodule *g_module;
+
+void test_submodule_open__initialize(void)
+{
+	g_parent = setup_fixture_submod2();
+}
+
+void test_submodule_open__cleanup(void)
+{
+	git_submodule_free(g_module);
+	git_repository_free(g_child);
+	cl_git_sandbox_cleanup();
+	g_parent = NULL;
+	g_child = NULL;
+	g_module = NULL;
+}
+
+static void assert_sm_valid(git_repository *parent, git_repository *child, const char *sm_name)
+{
+	git_buf expected = GIT_BUF_INIT, actual = GIT_BUF_INIT;
+
+	/* assert working directory */
+	cl_git_pass(git_buf_joinpath(&expected, git_repository_workdir(parent), sm_name));
+	cl_git_pass(git_path_prettify_dir(&expected, expected.ptr, NULL));
+	cl_git_pass(git_buf_sets(&actual, git_repository_workdir(child)));
+	cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
+	cl_assert_equal_s(expected.ptr, actual.ptr);
+
+	git_buf_clear(&expected);
+	git_buf_clear(&actual);
+
+	/* assert common directory */
+	cl_git_pass(git_buf_joinpath(&expected, git_repository_commondir(parent), "modules"));
+	cl_git_pass(git_buf_joinpath(&expected, expected.ptr, sm_name));
+	cl_git_pass(git_path_prettify_dir(&expected, expected.ptr, NULL));
+	cl_git_pass(git_buf_sets(&actual, git_repository_commondir(child)));
+	cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
+	cl_assert_equal_s(expected.ptr, actual.ptr);
+
+	/* assert git directory */
+	cl_git_pass(git_buf_sets(&actual, git_repository_path(child)));
+	cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
+	cl_assert_equal_s(expected.ptr, actual.ptr);
+
+	git_buf_free(&expected);
+	git_buf_free(&actual);
+}
+
+void test_submodule_open__opening_via_lookup_succeeds(void)
+{
+	cl_git_pass(git_submodule_lookup(&g_module, g_parent, "sm_unchanged"));
+	cl_git_pass(git_submodule_open(&g_child, g_module));
+	assert_sm_valid(g_parent, g_child, "sm_unchanged");
+}
+
+void test_submodule_open__direct_open_succeeds(void)
+{
+	git_buf path = GIT_BUF_INIT;
+
+	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged"));
+	cl_git_pass(git_repository_open(&g_child, path.ptr));
+	assert_sm_valid(g_parent, g_child, "sm_unchanged");
+
+	git_buf_free(&path);
+}
+
+void test_submodule_open__direct_open_succeeds_for_broken_sm_with_gitdir(void)
+{
+	git_buf path = GIT_BUF_INIT;
+
+	/*
+	 * This is actually not a valid submodule, but we
+	 * encountered at least one occasion where the gitdir
+	 * file existed inside of a submodule's gitdir. As we are
+	 * now able to open these submodules correctly, we still
+	 * add a test for this.
+	 */
+	cl_git_mkfile("submod2/.git/modules/sm_unchanged/gitdir", ".git");
+	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged"));
+	cl_git_pass(git_repository_open(&g_child, path.ptr));
+	assert_sm_valid(g_parent, g_child, "sm_unchanged");
+
+	git_buf_free(&path);
+}
diff --git a/tests/threads/basic.c b/tests/threads/basic.c
index a9310bb..af60490 100644
--- a/tests/threads/basic.c
+++ b/tests/threads/basic.c
@@ -54,12 +54,6 @@
 {
 	return param;
 }
-
-static void *exit_abruptly(void *param)
-{
-	git_thread_exit(param);
-	return NULL;
-}
 #endif
 
 void test_threads_basic__exit(void)