Merge pull request #3996 from pks-t/pks/curl-lastsocket-deprecation

curl_stream: use CURLINFO_ACTIVESOCKET if curl is recent enough
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
new file mode 100644
index 0000000..1e432ae
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE
@@ -0,0 +1,14 @@
+You are opening a _bug report_ against the libgit2 project.  If you have a
+question about an API or usage, please ask on StackOverflow:
+http://stackoverflow.com/questions/tagged/libgit2.  Please fill out the
+reproduction steps (below) and delete this introductory paragraph.  Thanks!
+
+### Reproduction steps
+
+### Expected behavior
+
+### Actual behavior
+
+### Version of libgit2 (release number or SHA1)
+
+### Operating system(s) tested
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dae86de..6307365 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,14 +17,37 @@
 
 * Improve the performance of the revwalk and bring us closer to git's code.
 
+* The reference db has improved support for concurrency and returns `GIT_ELOCKED`
+  when an operation could not be performed due to locking.
+
+* Nanosecond resolution is now activated by default, following git's change to
+  do this.
+
+* We now restrict the set of ciphers we let OpenSSL use by default.
+
+* Users can now register their own merge drivers for use with `.gitattributes`.
+  The library also gained built-in support for the union merge driver.
+
+* The default for creating references is now to validate that the object does
+  exist.
+
+* Add `git_proxy_options` which is used by the different networking
+  implementations to let the caller specify the proxy settings instead of
+  relying on the environment variables.
+
 ### API additions
 
 * You can now get the user-agent used by libgit2 using the
   `GIT_OPT_GET_USER_AGENT` option with `git_libgit2_opts()`.
   It is the counterpart to `GIT_OPT_SET_USER_AGENT`.
 
+* The `GIT_OPT_SET_SSL_CIPHERS` option for `git_libgit2_opts()` lets you specify
+  a custom list of ciphers to use for OpenSSL.
+
 * `git_commit_create_buffer()` creates a commit and writes it into a
-  user-provided buffer instead of writing it into the object db.
+  user-provided buffer instead of writing it into the object db. Combine it with
+  `git_commit_create_with_signature()` in order to create a commit with a
+  cryptographic signature.
 
 * `git_blob_create_fromstream()` and
   `git_blob_create_fromstream_commit()` allow you to create a blob by
@@ -50,12 +73,48 @@
       `git_repository_open_ext` with this flag will error out if either
       `$GIT_WORK_TREE` or `$GIT_COMMON_DIR` is set.
 
-* `git_diff_from_buffer` can create a `git_diff` object from the contents
+* `git_diff_from_buffer()` can create a `git_diff` object from the contents
   of a git-style patch file.
 
 * `git_index_version()` and `git_index_set_version()` to get and set
   the index version
 
+* `git_odb_expand_ids()` lets you check for the existence of multiple
+  objects at once.
+
+* The new `git_blob_dup()`, `git_commit_dup()`, `git_tag_dup()` and
+  `git_tree_dup()` functions provide type-specific wrappers for
+  `git_object_dup()` to reduce noise and increase type safety for callers.
+
+* `git_reference_dup()` lets you duplicate a reference to aid in ownership
+  management and cleanup.
+
+* `git_signature_from_buffer()` lets you create a signature from a string in the
+  format that appear in objects.
+
+* `git_tree_create_updated()` lets you create a tree based on another one
+  together with a list of updates. For the covered update cases, it's more
+  efficient than the `git_index` route.
+
+* `git_apply_patch()` applies hunks from a `git_patch` to a buffer.
+
+* `git_diff_to_buf()` lets you print an entire diff directory to a buffer,
+  similar to how `git_patch_to_buf()` works.
+
+* `git_proxy_init_options()` is added to initialize a `git_proxy_options`
+  structure at run-time.
+
+* `git_merge_driver_register()`, `git_merge_driver_unregister()` let you
+  register and unregister a custom merge driver to be used when `.gitattributes`
+  specifies it.
+
+* `git_merge_driver_lookup()` can be used to look up a merge driver by name.
+
+* `git_merge_driver_source_repo()`, `git_merge_driver_source_ancestor()`,
+  `git_merge_driver_source_ours()`, `git_merge_driver_source_theirs()`,
+  `git_merge_driver_source_file_options()` added as accessors to
+  `git_merge_driver_source`.
+
 ### API removals
 
 * `git_blob_create_fromchunks()` has been removed in favour of
@@ -80,6 +139,8 @@
   If this is `NULL`, then it will not be called and the `exists` function
   will be used instead.
 
+* `git_remote_connect()` now accepts proxy options.
+
 v0.24
 -------
 
diff --git a/THREADING.md b/THREADING.md
index e9be8b9..430bca8 100644
--- a/THREADING.md
+++ b/THREADING.md
@@ -62,29 +62,34 @@
 General Case
 ------------
 
-By default we use libcurl, which has its own ![recommendations for
-thread safety](http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading).
+If it's available, by default we use libcurl to provide HTTP tunneling support,
+which may be linked against a number of cryptographic libraries and has its
+own
+[recommendations for thread safety](https://curl.haxx.se/libcurl/c/threadsafe.html).
 
-If libcurl was not found or was disabled, libgit2 uses OpenSSL to be
-able to use HTTPS as a transport. This library is made to be
-thread-implementation agnostic, and the users of the library must set
-which locking function it should use. This means that libgit2 cannot
-know what to set as the user of libgit2 may use OpenSSL independently
-and the locking settings must survive libgit2 shutting down.
+If there are no alternative TLS implementations (currently only
+SecureTransport), libgit2 uses OpenSSL in order to use HTTPS as a transport.
+OpenSSL is thread-safe starting at version 1.1.0. If your copy of libgit2 is
+linked against that version, you do not need to take any further steps.
 
-Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used
-by libssh2 depending on the configuration.  If OpenSSL is used both by
-libgit2 and libssh2, you only need to set up threading for OpenSSL once.
+Older versions of OpenSSL are made to be thread-implementation agnostic, and the
+users of the library must set which locking function it should use. libgit2
+cannot know what to set as the user of libgit2 may also be using OpenSSL independently and
+the locking settings must then live outside the lifetime of libgit2.
 
-libgit2 does provide a last-resort convenience function
+Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used by
+libssh2 or libcurl depending on the configuration. If OpenSSL is used by
+more than one library, you only need to set up threading for OpenSSL once.
+
+If libgit2 is linked against OpenSSL, it provides a last-resort convenience function
 `git_openssl_set_locking()` (available in `sys/openssl.h`) to use the
-platform-native mutex mechanisms to perform the locking, which you may
-rely on if you do not want to use OpenSSL outside of libgit2, or you
-know that libgit2 will outlive the rest of the operations. It is not
+platform-native mutex mechanisms to perform the locking, which you can use
+if you do not want to use OpenSSL outside of libgit2, or you
+know that libgit2 will outlive the rest of the operations. It is then not
 safe to use OpenSSL multi-threaded after libgit2's shutdown function
 has been called.  Note `git_openssl_set_locking()` only works if
 libgit2 uses OpenSSL directly - if OpenSSL is only used as a dependency
-of libssh2 as described above, `git_openssl_set_locking()` is a no-op.
+of libssh2 or libcurl as described above, `git_openssl_set_locking()` is a no-op.
 
 If your programming language offers a package/bindings for OpenSSL,
 you should very strongly prefer to use that in order to set up
@@ -96,9 +101,6 @@
 on threading for more details, and http://trac.libssh2.org/wiki/MultiThreading
 for a specific example of providing the threading callbacks.
 
-Be also aware that libgit2 does not always link against OpenSSL
-if there are alternatives provided by the system.
-
 libssh2 may be linked against OpenSSL or libgcrypt. If it uses OpenSSL,
 see the above paragraphs. If it uses libgcrypt, then you need to
 set up its locking before using it multi-threaded. libgit2 has no
diff --git a/include/git2/proxy.h b/include/git2/proxy.h
index dcd6156..194cbb6 100644
--- a/include/git2/proxy.h
+++ b/include/git2/proxy.h
@@ -19,7 +19,7 @@
 	/**
 	 * Do not attempt to connect through a proxy
 	 *
-	 * If built against lbicurl, it itself may attempt to connect
+	 * If built against libcurl, it itself may attempt to connect
 	 * to a proxy if the environment variables specify it.
 	 */
 	GIT_PROXY_NONE,
diff --git a/src/apply.c b/src/apply.c
index f701724..6359342 100644
--- a/src/apply.c
+++ b/src/apply.c
@@ -173,7 +173,7 @@
 		git_diff_line *line = git_array_get(patch->lines, linenum);
 
 		if (!line) {
-			error = apply_err("Preimage does not contain line %d", linenum);
+			error = apply_err("Preimage does not contain line %"PRIuZ, linenum);
 			goto done;
 		}
 
diff --git a/src/checkout.c b/src/checkout.c
index b3427fb..6295091 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -1966,7 +1966,7 @@
 	if (i == INT_MAX) {
 		git_buf_truncate(path, path_len);
 
-		giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path);
+		giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path->ptr);
 		return GIT_EEXISTS;
 	}
 
@@ -2469,7 +2469,7 @@
 			data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3;
 		else {
 			giterr_set(GITERR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'",
-				conflict_style);
+				conflict_style->value);
 			error = -1;
 			git_config_entry_free(conflict_style);
 			goto cleanup;
diff --git a/src/common.h b/src/common.h
index 51fb918..f12cc98 100644
--- a/src/common.h
+++ b/src/common.h
@@ -103,7 +103,8 @@
 /**
  * Set the error message for this thread, formatting as needed.
  */
-void giterr_set(int error_class, const char *string, ...);
+
+void giterr_set(int error_class, const char *string, ...) GIT_FORMAT_PRINTF(2, 3);
 
 /**
  * Set the error message for a regex failure, using the internal regex
diff --git a/src/fetchhead.c b/src/fetchhead.c
index a95ea4c..3d16c21 100644
--- a/src/fetchhead.c
+++ b/src/fetchhead.c
@@ -149,7 +149,7 @@
 
 	if (!*line) {
 		giterr_set(GITERR_FETCHHEAD,
-			"Empty line in FETCH_HEAD line %d", line_num);
+			"Empty line in FETCH_HEAD line %"PRIuZ, line_num);
 		return -1;
 	}
 
@@ -163,7 +163,7 @@
 
 	if (strlen(oid_str) != GIT_OID_HEXSZ) {
 		giterr_set(GITERR_FETCHHEAD,
-			"Invalid object ID in FETCH_HEAD line %d", line_num);
+			"Invalid object ID in FETCH_HEAD line %"PRIuZ, line_num);
 		return -1;
 	}
 
@@ -171,7 +171,7 @@
 		const git_error *oid_err = giterr_last();
 		const char *err_msg = oid_err ? oid_err->message : "Invalid object ID";
 
-		giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d",
+		giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ,
 			err_msg, line_num);
 		return -1;
 	}
@@ -180,7 +180,7 @@
 	if (*line) {
 		if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
 			giterr_set(GITERR_FETCHHEAD,
-				"Invalid description data in FETCH_HEAD line %d", line_num);
+				"Invalid description data in FETCH_HEAD line %"PRIuZ, line_num);
 			return -1;
 		}
 
@@ -190,13 +190,13 @@
 			*is_merge = 0;
 		else {
 			giterr_set(GITERR_FETCHHEAD,
-				"Invalid for-merge entry in FETCH_HEAD line %d", line_num);
+				"Invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num);
 			return -1;
 		}
 
 		if ((desc = line) == NULL) {
 			giterr_set(GITERR_FETCHHEAD,
-				"Invalid description in FETCH_HEAD line %d", line_num);
+				"Invalid description in FETCH_HEAD line %"PRIuZ, line_num);
 			return -1;
 		}
 
@@ -213,7 +213,7 @@
 			if ((desc = strstr(name, "' ")) == NULL ||
 				git__prefixcmp(desc, "' of ") != 0) {
 				giterr_set(GITERR_FETCHHEAD,
-					"Invalid description in FETCH_HEAD line %d", line_num);
+					"Invalid description in FETCH_HEAD line %"PRIuZ, line_num);
 				return -1;
 			}
 
@@ -277,7 +277,7 @@
 	}
 
 	if (*buffer) {
-		giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1);
+		giterr_set(GITERR_FETCHHEAD, "No EOL at line %"PRIuZ, line_num+1);
 		error = -1;
 		goto done;
 	}
diff --git a/src/fileops.c b/src/fileops.c
index fcc0301..a82202c 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -72,8 +72,16 @@
 		O_EXCL | O_BINARY | O_CLOEXEC, mode);
 
 	if (fd < 0) {
+		int error = errno;
 		giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
-		return errno == EEXIST ? GIT_ELOCKED : -1;
+		switch (error) {
+		case EEXIST:
+			return GIT_ELOCKED;
+		case ENOENT:
+			return GIT_ENOTFOUND;
+		default:
+			return -1;
+		}
 	}
 
 	return fd;
diff --git a/src/fileops.h b/src/fileops.h
index 54e3bd4..65c96a6 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -45,12 +45,12 @@
 extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode);
 
 /**
- * Create an open a process-locked file
+ * Create and open a process-locked file
  */
 extern int git_futils_creat_locked(const char *path, const mode_t mode);
 
 /**
- * Create an open a process-locked file, while
+ * Create and open a process-locked file, while
  * also creating all the folders in its path
  */
 extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode);
diff --git a/src/iterator.c b/src/iterator.c
index 598c69c..8fc62c0 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1311,7 +1311,7 @@
 
 	if (iter->frames.size == FILESYSTEM_MAX_DEPTH) {
 		giterr_set(GITERR_REPOSITORY,
-			"directory nesting too deep (%d)", iter->frames.size);
+			"directory nesting too deep (%"PRIuZ")", iter->frames.size);
 		return -1;
 	}
 
diff --git a/src/merge.c b/src/merge.c
index 6934aa7..1142917 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -591,7 +591,7 @@
 	}
 
 	if (*buffer) {
-		giterr_set(GITERR_MERGE, "No EOL at line %d", line_num);
+		giterr_set(GITERR_MERGE, "No EOL at line %"PRIuZ, line_num);
 		error = -1;
 		goto cleanup;
 	}
diff --git a/src/odb.c b/src/odb.c
index acf4dea..7b194c7 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -1400,7 +1400,7 @@
 		char oid_str[GIT_OID_HEXSZ + 1];
 		git_oid_tostr(oid_str, oid_len+1, oid);
 		giterr_set(GITERR_ODB, "Object not found - %s (%.*s)",
-			message, oid_len, oid_str);
+			message, (int) oid_len, oid_str);
 	} else
 		giterr_set(GITERR_ODB, "Object not found - %s", message);
 
diff --git a/src/patch_parse.c b/src/patch_parse.c
index 5ee09ee..7a4fe9f 100644
--- a/src/patch_parse.c
+++ b/src/patch_parse.c
@@ -176,7 +176,7 @@
 	int ret;
 
 	if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]))
-		return parse_err("invalid file mode at line %d", ctx->line_num);
+		return parse_err("invalid file mode at line %"PRIuZ, ctx->line_num);
 
 	if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0)
 		return ret;
@@ -205,7 +205,7 @@
 
 	if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ ||
 		git_oid_fromstrn(oid, ctx->line, len) < 0)
-		return parse_err("invalid hex formatted object id at line %d",
+		return parse_err("invalid hex formatted object id at line %"PRIuZ,
 			ctx->line_num);
 
 	parse_advance_chars(ctx, len);
@@ -350,7 +350,7 @@
 	git_patch_parsed *patch, git_patch_parse_ctx *ctx)
 {
 	if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0)
-		return parse_err("invalid similarity percentage at line %d",
+		return parse_err("invalid similarity percentage at line %"PRIuZ,
 			ctx->line_num);
 
 	return 0;
@@ -362,7 +362,7 @@
 	uint16_t dissimilarity;
 
 	if (parse_header_percent(&dissimilarity, ctx) < 0)
-		return parse_err("invalid similarity percentage at line %d",
+		return parse_err("invalid similarity percentage at line %"PRIuZ,
 			ctx->line_num);
 
 	patch->base.delta->similarity = 100 - dissimilarity;
@@ -406,15 +406,15 @@
 
 	/* Parse the diff --git line */
 	if (parse_advance_expected_str(ctx, "diff --git ") < 0)
-		return parse_err("corrupt git diff header at line %d", ctx->line_num);
+		return parse_err("corrupt git diff header at line %"PRIuZ, ctx->line_num);
 
 	if (parse_header_path(&patch->header_old_path, ctx) < 0)
-		return parse_err("corrupt old path in git diff header at line %d",
+		return parse_err("corrupt old path in git diff header at line %"PRIuZ,
 			ctx->line_num);
 
 	if (parse_advance_ws(ctx) < 0 ||
 		parse_header_path(&patch->header_new_path, ctx) < 0)
-		return parse_err("corrupt new path in git diff header at line %d",
+		return parse_err("corrupt new path in git diff header at line %"PRIuZ,
 			ctx->line_num);
 
 	/* Parse remaining header lines */
@@ -447,7 +447,7 @@
 			parse_advance_expected_str(ctx, "\n");
 
 			if (ctx->line_len > 0) {
-				error = parse_err("trailing data at line %d", ctx->line_num);
+				error = parse_err("trailing data at line %"PRIuZ, ctx->line_num);
 				goto done;
 			}
 
@@ -456,7 +456,7 @@
 		}
 		
 		if (!found) {
-			error = parse_err("invalid patch header at line %d",
+			error = parse_err("invalid patch header at line %"PRIuZ,
 				ctx->line_num);
 			goto done;
 		}
@@ -536,7 +536,7 @@
 
 	hunk->hunk.header_len = ctx->line - header_start;
 	if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1))
-		return parse_err("oversized patch hunk header at line %d",
+		return parse_err("oversized patch hunk header at line %"PRIuZ,
 			ctx->line_num);
 
 	memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len);
@@ -545,7 +545,7 @@
 	return 0;
 
 fail:
-	giterr_set(GITERR_PATCH, "invalid patch hunk header at line %d",
+	giterr_set(GITERR_PATCH, "invalid patch hunk header at line %"PRIuZ,
 		ctx->line_num);
 	return -1;
 }
@@ -570,7 +570,7 @@
 		int prefix = 1;
 
 		if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') {
-			error = parse_err("invalid patch instruction at line %d",
+			error = parse_err("invalid patch instruction at line %"PRIuZ,
 				ctx->line_num);
 			goto done;
 		}
@@ -596,7 +596,7 @@
 			break;
 
 		default:
-			error = parse_err("invalid patch hunk at line %d", ctx->line_num);
+			error = parse_err("invalid patch hunk at line %"PRIuZ, ctx->line_num);
 			goto done;
 		}
 
@@ -672,7 +672,7 @@
 				continue;
 			}
 
-			error = parse_err("invalid hunk header outside patch at line %d",
+			error = parse_err("invalid hunk header outside patch at line %"PRIuZ,
 				line_num);
 			goto done;
 		}
@@ -715,12 +715,12 @@
 		parse_advance_chars(ctx, 6);
 	} else {
 		error = parse_err(
-			"unknown binary delta type at line %d", ctx->line_num);
+			"unknown binary delta type at line %"PRIuZ, ctx->line_num);
 		goto done;
 	}
 
 	if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) {
-		error = parse_err("invalid binary size at line %d", ctx->line_num);
+		error = parse_err("invalid binary size at line %"PRIuZ, ctx->line_num);
 		goto done;
 	}
 
@@ -736,7 +736,7 @@
 			decoded_len = c - 'a' + (('z' - 'a') + 1) + 1;
 
 		if (!decoded_len) {
-			error = parse_err("invalid binary length at line %d", ctx->line_num);
+			error = parse_err("invalid binary length at line %"PRIuZ, ctx->line_num);
 			goto done;
 		}
 
@@ -745,7 +745,7 @@
 		encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
 
 		if (encoded_len > ctx->line_len - 1) {
-			error = parse_err("truncated binary data at line %d", ctx->line_num);
+			error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num);
 			goto done;
 		}
 
@@ -754,14 +754,14 @@
 			goto done;
 
 		if (decoded.size - decoded_orig != decoded_len) {
-			error = parse_err("truncated binary data at line %d", ctx->line_num);
+			error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num);
 			goto done;
 		}
 
 		parse_advance_chars(ctx, encoded_len);
 
 		if (parse_advance_nl(ctx) < 0) {
-			error = parse_err("trailing data at line %d", ctx->line_num);
+			error = parse_err("trailing data at line %"PRIuZ, ctx->line_num);
 			goto done;
 		}
 	}
@@ -785,7 +785,7 @@
 
 	if (parse_advance_expected_str(ctx, "GIT binary patch") < 0 ||
 		parse_advance_nl(ctx) < 0)
-		return parse_err("corrupt git binary header at line %d", ctx->line_num);
+		return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num);
 
 	/* parse old->new binary diff */
 	if ((error = parse_patch_binary_side(
@@ -793,7 +793,7 @@
 		return error;
 
 	if (parse_advance_nl(ctx) < 0)
-		return parse_err("corrupt git binary separator at line %d",
+		return parse_err("corrupt git binary separator at line %"PRIuZ,
 			ctx->line_num);
 
 	/* parse new->old binary diff */
@@ -802,7 +802,7 @@
 		return error;
 
 	if (parse_advance_nl(ctx) < 0)
-		return parse_err("corrupt git binary patch separator at line %d",
+		return parse_err("corrupt git binary patch separator at line %"PRIuZ,
 			ctx->line_num);
 
 	patch->base.binary.contains_data = 1;
@@ -820,7 +820,7 @@
 		parse_advance_expected_str(ctx, patch->header_new_path) < 0 ||
 		parse_advance_expected_str(ctx, " differ") < 0 ||
 		parse_advance_nl(ctx) < 0)
-		return parse_err("corrupt git binary header at line %d", ctx->line_num);
+		return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num);
 
 	patch->base.binary.contains_data = 0;
 	patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
@@ -912,7 +912,7 @@
 
 	if (remain_len || !*path)
 		return parse_err(
-			"header filename does not contain %d path components",
+			"header filename does not contain %"PRIuZ" path components",
 			prefix_len);
 
 done:
diff --git a/src/path.c b/src/path.c
index e5f04a5..2b1a962 100644
--- a/src/path.c
+++ b/src/path.c
@@ -644,6 +644,10 @@
 		giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
 		return GIT_EEXISTS;
 
+	case EACCES:
+		giterr_set(GITERR_OS, "Failed %s - '%s' is locked", action, path);
+		return GIT_ELOCKED;
+
 	default:
 		giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
 		return -1;
@@ -1347,7 +1351,7 @@
 				return GIT_ITEROVER;
 
 			giterr_set(GITERR_OS,
-				"Could not read directory '%s'", diriter->path);
+				"Could not read directory '%s'", diriter->path.ptr);
 			return -1;
 		}
 	} while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
diff --git a/src/refdb.c b/src/refdb.c
index debba12..85c8489 100644
--- a/src/refdb.c
+++ b/src/refdb.c
@@ -125,13 +125,15 @@
 
 int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
 {
+	int error;
+
 	if (!db->backend || !db->backend->iterator) {
 		giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators");
 		return -1;
 	}
 
-	if (db->backend->iterator(out, db->backend, glob) < 0)
-		return -1;
+	if ((error = db->backend->iterator(out, db->backend, glob)) < 0)
+		return error;
 
 	GIT_REFCOUNT_INC(db);
 	(*out)->db = db;
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index f978038..558d060 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -326,12 +326,13 @@
 {
 	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
 	git_buf ref_path = GIT_BUF_INIT;
+	int error;
 
 	assert(backend);
 
-	if (packed_reload(backend) < 0 ||
-		git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
-		return -1;
+	if ((error = packed_reload(backend)) < 0 ||
+		(error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
+		return error;
 
 	*exists = git_path_isfile(ref_path.ptr) ||
 		(git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
@@ -409,8 +410,8 @@
 	int error = 0;
 	struct packref *entry;
 
-	if (packed_reload(backend) < 0)
-		return -1;
+	if ((error = packed_reload(backend)) < 0)
+		return error;
 
 	if (git_sortedcache_rlock(backend->refcache) < 0)
 		return -1;
@@ -615,13 +616,14 @@
 static int refdb_fs_backend__iterator(
 	git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
 {
+	int error;
 	refdb_fs_iter *iter;
 	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
 
 	assert(backend);
 
-	if (packed_reload(backend) < 0)
-		return -1;
+	if ((error = packed_reload(backend)) < 0)
+		return error;
 
 	iter = git__calloc(1, sizeof(refdb_fs_iter));
 	GITERR_CHECK_ALLOC(iter);
@@ -674,16 +676,18 @@
 	int force)
 {
 	size_t i;
+	int error;
 
-	if (packed_reload(backend) < 0)
-		return -1;
+	if ((error = packed_reload(backend)) < 0)
+		return error;
 
 	if (!force) {
 		int exists;
 
-		if (refdb_fs_backend__exists(
-				&exists, (git_refdb_backend *)backend, new_ref) < 0)
-			return -1;
+		if ((error = refdb_fs_backend__exists(
+			&exists, (git_refdb_backend *)backend, new_ref)) < 0) {
+			return error;
+		}
 
 		if (exists) {
 			giterr_set(GITERR_REFERENCE,
@@ -901,40 +905,62 @@
 static int packed_remove_loose(refdb_fs_backend *backend)
 {
 	size_t i;
-	git_buf full_path = GIT_BUF_INIT;
-	int failed = 0;
+	git_filebuf lock = GIT_FILEBUF_INIT;
+	git_buf ref_content = GIT_BUF_INIT;
+	int error = 0;
 
 	/* backend->refcache is already locked when this is called */
 
 	for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
 		struct packref *ref = git_sortedcache_entry(backend->refcache, i);
+		git_oid current_id;
 
 		if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
 			continue;
 
-		if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
-			return -1; /* critical; do not try to recover on oom */
+		git_filebuf_cleanup(&lock);
 
-		if (git_path_exists(full_path.ptr) && p_unlink(full_path.ptr) < 0) {
-			if (failed)
-				continue;
+		/* We need to stop anybody from updating the ref while we try to do a safe delete */
+		error = loose_lock(&lock, backend, ref->name);
+		/* If someone else is updating it, let them do it */
+		if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
+			continue;
 
-			giterr_set(GITERR_REFERENCE,
-				"Failed to remove loose reference '%s' after packing: %s",
-				full_path.ptr, strerror(errno));
-			failed = 1;
+		if (error < 0) {
+			git_buf_free(&ref_content);
+			giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
+			return error;
 		}
 
+		error = git_futils_readbuffer(&ref_content, lock.path_original);
+		/* Someone else beat us to cleaning up the ref, let's simply continue */
+		if (error == GIT_ENOTFOUND)
+			continue;
+
+		/* This became a symref between us packing and trying to delete it, so ignore it */
+		if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF))
+			continue;
+
+		/* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
+		if (loose_parse_oid(&current_id, lock.path_original, &ref_content) < 0)
+			continue;
+
+		/* If the ref moved since we packed it, we must not delete it */
+		if (!git_oid_equal(&current_id, &ref->oid))
+			continue;
+
 		/*
 		 * if we fail to remove a single file, this is *not* good,
 		 * but we should keep going and remove as many as possible.
-		 * After we've removed as many files as possible, we return
-		 * the error code anyway.
+		 * If we fail to remove, the ref is still in the old state, so
+		 * we haven't lost information.
 		 */
+		p_unlink(lock.path_original);
 	}
 
-	git_buf_free(&full_path);
-	return failed ? -1 : 0;
+	git_buf_free(&ref_content);
+	git_filebuf_cleanup(&lock);
+	return 0;
 }
 
 /*
@@ -944,41 +970,42 @@
 {
 	git_sortedcache *refcache = backend->refcache;
 	git_filebuf pack_file = GIT_FILEBUF_INIT;
+	int error;
 	size_t i;
 
 	/* lock the cache to updates while we do this */
-	if (git_sortedcache_wlock(refcache) < 0)
-		return -1;
+	if ((error = git_sortedcache_wlock(refcache)) < 0)
+		return error;
 
 	/* Open the file! */
-	if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE) < 0)
+	if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0)
 		goto fail;
 
 	/* Packfiles have a header... apparently
 	 * This is in fact not required, but we might as well print it
 	 * just for kicks */
-	if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
+	if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0)
 		goto fail;
 
 	for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
 		struct packref *ref = git_sortedcache_entry(refcache, i);
 		assert(ref);
 
-		if (packed_find_peel(backend, ref) < 0)
+		if ((error = packed_find_peel(backend, ref)) < 0)
 			goto fail;
 
-		if (packed_write_ref(ref, &pack_file) < 0)
+		if ((error = packed_write_ref(ref, &pack_file)) < 0)
 			goto fail;
 	}
 
 	/* if we've written all the references properly, we can commit
 	 * the packfile to make the changes effective */
-	if (git_filebuf_commit(&pack_file) < 0)
+	if ((error = git_filebuf_commit(&pack_file)) < 0)
 		goto fail;
 
 	/* when and only when the packfile has been properly written,
 	 * we can go ahead and remove the loose refs */
-	if (packed_remove_loose(backend) < 0)
+	if ((error = packed_remove_loose(backend)) < 0)
 		goto fail;
 
 	git_sortedcache_updated(refcache);
@@ -991,7 +1018,7 @@
 	git_filebuf_cleanup(&pack_file);
 	git_sortedcache_wunlock(refcache);
 
-	return -1;
+	return error;
 }
 
 static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
@@ -1143,8 +1170,7 @@
 
 	assert(backend);
 
-	error = reference_path_available(backend, ref->name, NULL, force);
-	if (error < 0)
+	if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
 		return error;
 
 	/* We need to perform the reflog append and old value check under the ref's lock */
@@ -1260,15 +1286,14 @@
 	if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
 		return -1;
 
-	if (git_path_isfile(loose_path.ptr)) {
-		error = p_unlink(loose_path.ptr);
-		loose_deleted = 1;
-	}
 
-	git_buf_free(&loose_path);
-
-	if (error != 0)
+	error = p_unlink(loose_path.ptr);
+	if (error < 0 && errno == ENOENT)
+		error = 0;
+	else if (error < 0)
 		goto cleanup;
+	else if (error == 0)
+		loose_deleted = 1;
 
 	if ((error = packed_reload(backend)) < 0)
 		goto cleanup;
@@ -1291,6 +1316,7 @@
 	error = packed_write(backend);
 
 cleanup:
+	git_buf_free(&loose_path);
 	git_filebuf_cleanup(file);
 
 	return error;
@@ -1362,14 +1388,15 @@
 
 static int refdb_fs_backend__compress(git_refdb_backend *_backend)
 {
+	int error;
 	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
 
 	assert(backend);
 
-	if (packed_reload(backend) < 0 || /* load the existing packfile */
-		packed_loadloose(backend) < 0 || /* add all the loose refs */
-		packed_write(backend) < 0) /* write back to disk */
-		return -1;
+	if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */
+	    (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */
+	    (error = packed_write(backend)) < 0) /* write back to disk */
+		return error;
 
 	return 0;
 }
@@ -1789,9 +1816,10 @@
 	 * there maybe an obsolete/unused directory (or directory hierarchy) in the way.
 	 */
 	if (git_path_isdir(git_buf_cstr(&path))) {
-		if ((git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0))
-			error = -1;
-		else if (git_path_isdir(git_buf_cstr(&path))) {
+		if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
+			if (error == GIT_ENOTFOUND)
+				error = 0;
+		} else if (git_path_isdir(git_buf_cstr(&path))) {
 			giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
 				ref->name);
 			error = GIT_EDIRECTORY;
diff --git a/src/repository.c b/src/repository.c
index cf3d18a..5c44423 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -410,7 +410,7 @@
 					break;
 				}
 			}
-			else if (S_ISREG(st.st_mode)) {
+			else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) {
 				error = read_gitfile(&repo_link, path.ptr);
 				if (error < 0)
 					break;
@@ -613,9 +613,10 @@
 		git_repository_set_odb(repo, odb);
 
 	error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES");
-	if (error == GIT_ENOTFOUND)
+	if (error == GIT_ENOTFOUND) {
 		giterr_clear();
-	else if (error < 0)
+		error = 0;
+	} else if (error < 0)
 		goto error;
         else {
 		const char *end;
@@ -638,9 +639,11 @@
 		}
 	}
 
-	error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf));
-	if (error < 0)
-		goto error;
+	if (git_buf_len(&namespace_buf)) {
+		error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf));
+		if (error < 0)
+			goto error;
+	}
 
 	git_repository_set_index(repo, index);
 
diff --git a/src/sortedcache.c b/src/sortedcache.c
index 5c2a167..ed4199b 100644
--- a/src/sortedcache.c
+++ b/src/sortedcache.c
@@ -200,6 +200,7 @@
 int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf)
 {
 	int error, fd;
+	struct stat st;
 
 	if ((error = git_sortedcache_wlock(sc)) < 0)
 		return error;
@@ -207,19 +208,26 @@
 	if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
 		goto unlock;
 
-	if (!git__is_sizet(sc->stamp.size)) {
-		giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
-		error = -1;
-		goto unlock;
-	}
-
 	if ((fd = git_futils_open_ro(sc->path)) < 0) {
 		error = fd;
 		goto unlock;
 	}
 
+	if (p_fstat(fd, &st) < 0) {
+		giterr_set(GITERR_OS, "failed to stat file");
+		error = -1;
+		goto unlock;
+	}
+
+	if (!git__is_sizet(st.st_size)) {
+		giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
+		error = -1;
+		(void)p_close(fd);
+		goto unlock;
+	}
+
 	if (buf)
-		error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size);
+		error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
 
 	(void)p_close(fd);
 
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 3448fa7..c1e4124 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -50,7 +50,7 @@
 			if ((recvd = gitno_recv(buf)) < 0)
 				return recvd;
 
-			if (recvd == 0 && !flush) {
+			if (recvd == 0) {
 				giterr_set(GITERR_NET, "early EOF");
 				return GIT_EEOF;
 			}
@@ -222,8 +222,12 @@
 		if (error < 0 && error != GIT_EBUFS)
 			return error;
 
-		if ((ret = gitno_recv(buf)) < 0)
+		if ((ret = gitno_recv(buf)) < 0) {
 			return ret;
+		} else if (ret == 0) {
+			giterr_set(GITERR_NET, "early EOF");
+			return GIT_EEOF;
+		}
 	} while (error);
 
 	gitno_consume(buf, line_end);
diff --git a/src/tree.c b/src/tree.c
index 6008a95..9655ad7 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -917,7 +917,7 @@
 
 	if (entry == NULL) {
 		giterr_set(GITERR_TREE,
-			   "the path '%.*s' does not exist in the given tree", filename_len, path);
+			   "the path '%.*s' does not exist in the given tree", (int) filename_len, path);
 		return GIT_ENOTFOUND;
 	}
 
@@ -927,7 +927,7 @@
 		 * then this entry *must* be a tree */
 		if (!git_tree_entry__is_tree(entry)) {
 			giterr_set(GITERR_TREE,
-				   "the path '%.*s' exists but is not a tree", filename_len, path);
+				   "the path '%.*s' exists but is not a tree", (int) filename_len, path);
 			return GIT_ENOTFOUND;
 		}
 
@@ -1164,8 +1164,8 @@
 		goto cleanup;
 
 	for (i = 0; i < nupdates; i++) {
-		const git_tree_update *last_update = i == 0 ? NULL : &updates[i-1];
-		const git_tree_update *update = &updates[i];
+		const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1);
+		const git_tree_update *update = git_vector_get(&entries, i);
 		size_t common_prefix = 0, steps_up, j;
 		const char *path;
 
@@ -1200,6 +1200,9 @@
 
 			last = git_array_last(stack);
 			entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL;
+			if (!entry)
+				entry = treebuilder_get(last->bld, component.ptr);
+
 			if (entry && git_tree_entry_type(entry) != GIT_OBJ_TREE) {
 				giterr_set(GITERR_TREE, "D/F conflict when updating tree");
 				error = -1;
diff --git a/tests/object/tree/update.c b/tests/object/tree/update.c
index 54c4335..b76e861 100644
--- a/tests/object/tree/update.c
+++ b/tests/object/tree/update.c
@@ -196,6 +196,63 @@
 	git_tree_free(base_tree);
 }
 
+void test_object_tree_update__add_blobs_unsorted(void)
+{
+	git_oid tree_index_id, tree_updater_id, base_id;
+	git_tree *base_tree;
+	git_index *idx;
+	git_index_entry entry = { {0} };
+	int i;
+	const char *paths[] = {
+		"some/deep/path",
+		"a/path/elsewhere",
+		"some/other/path",
+	};
+
+	git_tree_update updates[] = {
+		{ GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]},
+		{ GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]},
+		{ GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]},
+	};
+
+	cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b"));
+
+	entry.mode = GIT_FILEMODE_BLOB;
+	cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92"));
+
+	for (i = 0; i < 3; i++) {
+		cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92"));
+	}
+
+	for (i = 0; i < 2; i++) {
+		int j;
+
+		/* Create it with an index */
+		cl_git_pass(git_index_new(&idx));
+
+		base_tree = NULL;
+		if (i == 1) {
+			cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id));
+			cl_git_pass(git_index_read_tree(idx, base_tree));
+		}
+
+		for (j = 0; j < 3; j++) {
+			entry.path = paths[j];
+			cl_git_pass(git_index_add(idx, &entry));
+		}
+
+		cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo));
+		git_index_free(idx);
+
+		/* Perform the same operations via the tree updater */
+		cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates));
+
+		cl_assert_equal_oid(&tree_index_id, &tree_updater_id);
+	}
+
+	git_tree_free(base_tree);
+}
+
 void test_object_tree_update__add_conflict(void)
 {
 	int i;
diff --git a/tests/repo/discover.c b/tests/repo/discover.c
index 358daee..48aa275 100644
--- a/tests/repo/discover.c
+++ b/tests/repo/discover.c
@@ -9,6 +9,7 @@
 
 #define SUB_REPOSITORY_FOLDER_NAME "sub_repo"
 #define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME
+#define SUB_REPOSITORY_GITDIR SUB_REPOSITORY_FOLDER "/.git"
 #define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub"
 #define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub"
 #define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub"
@@ -24,20 +25,26 @@
 #define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo"
 
 static void ensure_repository_discover(const char *start_path,
-                                       const char *ceiling_dirs,
-				       git_buf *expected_path)
+				       const char *ceiling_dirs,
+				       const char *expected_path)
 {
-	git_buf found_path = GIT_BUF_INIT;
+	git_buf found_path = GIT_BUF_INIT, resolved = GIT_BUF_INIT;
+
+	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));
-	//across_fs is always 0 as we can't automate the filesystem change tests
-	cl_assert_equal_s(found_path.ptr, expected_path->ptr);
+
+	cl_assert_equal_s(found_path.ptr, resolved.ptr);
+
+	git_buf_free(&resolved);
 	git_buf_free(&found_path);
 }
 
 static void write_file(const char *path, const char *content)
 {
 	git_file file;
-   int error;
+	int error;
 
 	if (git_path_exists(path)) {
 		cl_git_pass(p_unlink(path));
@@ -68,42 +75,30 @@
 	cl_assert(git_buf_oom(ceiling_dirs) == 0);
 }
 
-void test_repo_discover__0(void)
+static git_buf discovered;
+static git_buf ceiling_dirs;
+
+void test_repo_discover__initialize(void)
 {
-	// test discover
 	git_repository *repo;
-	git_buf ceiling_dirs_buf = GIT_BUF_INIT, repository_path = GIT_BUF_INIT,
-		sub_repository_path = GIT_BUF_INIT, found_path = GIT_BUF_INIT;
-	const char *ceiling_dirs;
 	const mode_t mode = 0777;
-
 	git_futils_mkdir_r(DISCOVER_FOLDER, mode);
-	append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER);
-	ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
 
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs));
+	git_buf_init(&discovered, 0);
+	git_buf_init(&ceiling_dirs, 0);
+	append_ceiling_dir(&ceiling_dirs, TEMP_REPO_FOLDER);
 
 	cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1));
-	cl_git_pass(git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs));
 	git_repository_free(repo);
 
 	cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0));
 	cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode));
-	cl_git_pass(git_repository_discover(&sub_repository_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
-
 	cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode));
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, &sub_repository_path);
 
 	cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode));
 	write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT);
 	write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT);
 	write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../");
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path);
 
 	cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode));
 	write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:");
@@ -113,41 +108,94 @@
 	write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n");
 	cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode));
 	write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist");
-	cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs));
-	cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs));
-	cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs));
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs));
 
-	append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER_SUB);
-	ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
+	git_repository_free(repo);
+}
+
+void test_repo_discover__cleanup(void)
+{
+	git_buf_free(&discovered);
+	git_buf_free(&ceiling_dirs);
+	cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_repo_discover__discovering_repo_with_exact_path_succeeds(void)
+{
+	cl_git_pass(git_repository_discover(&discovered, DISCOVER_FOLDER, 0, ceiling_dirs.ptr));
+	cl_git_pass(git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs.ptr));
+}
+
+void test_repo_discover__discovering_nonexistent_dir_fails(void)
+{
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, DISCOVER_FOLDER "-nonexistent", 0, NULL));
+}
+
+void test_repo_discover__discovering_repo_with_subdirectory_succeeds(void)
+{
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+}
+
+void test_repo_discover__discovering_repository_with_alternative_gitdir_succeeds(void)
+{
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER);
+}
+
+void test_repo_discover__discovering_repository_with_malformed_alternative_gitdir_fails(void)
+{
+	cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs.ptr));
+	cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs.ptr));
+	cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs.ptr));
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs.ptr));
+}
+
+void test_repo_discover__discovering_repository_with_ceiling(void)
+{
+	append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER_SUB);
 
 	/* this must pass as ceiling_directories cannot prevent the current
 	 * working directory to be checked */
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
 
-	append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER);
-	ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
-
-	//this must pass as ceiling_directories cannot predent the current
-	//working directory to be checked
-	ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path);
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs));
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
-	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
-
-	//.gitfile redirection should not be affected by ceiling directories
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path);
-	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path);
-
-	cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES));
-	git_repository_free(repo);
-	git_buf_free(&ceiling_dirs_buf);
-	git_buf_free(&repository_path);
-	git_buf_free(&sub_repository_path);
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr));
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr));
 }
 
+void test_repo_discover__other_ceiling(void)
+{
+	append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER);
+
+	/* this must pass as ceiling_directories cannot predent the current
+	 * working directory to be checked */
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs.ptr));
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr));
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr));
+}
+
+void test_repo_discover__ceiling_should_not_affect_gitdir_redirection(void)
+{
+	append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER);
+
+	/* gitfile redirection should not be affected by ceiling directories */
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+	ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER);
+}
+
+void test_repo_discover__discovery_starting_at_file_succeeds(void)
+{
+	int fd;
+
+	cl_assert((fd = p_creat(SUB_REPOSITORY_FOLDER "/file", 0600)) >= 0);
+	cl_assert(p_close(fd) == 0);
+
+	ensure_repository_discover(SUB_REPOSITORY_FOLDER "/file", ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR);
+}
diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c
index f869bcb..5484b71 100644
--- a/tests/threads/refdb.c
+++ b/tests/threads/refdb.c
@@ -18,14 +18,27 @@
 
 #define REPEAT 20
 #define THREADS 20
+/* Number of references to create or delete in each thread */
+#define NREFS 10
+
+struct th_data {
+	int id;
+	const char *path;
+};
 
 static void *iterate_refs(void *arg)
 {
+	struct th_data *data = (struct th_data *) arg;
 	git_reference_iterator *i;
 	git_reference *ref;
-	int count = 0;
+	int count = 0, error;
+	git_repository *repo;
 
-	cl_git_pass(git_reference_iterator_new(&i, g_repo));
+	cl_git_pass(git_repository_open(&repo, data->path));
+	do {
+		error = git_reference_iterator_new(&i, repo);
+	} while (error == GIT_ELOCKED);
+	cl_git_pass(error);
 
 	for (count = 0; !git_reference_next(&ref, i); ++count) {
 		cl_assert(ref != NULL);
@@ -37,112 +50,91 @@
 
 	git_reference_iterator_free(i);
 
+	git_repository_free(repo);
 	giterr_clear();
 	return arg;
 }
 
-void test_threads_refdb__iterator(void)
-{
-	int r, t;
-	git_thread th[THREADS];
-	int id[THREADS];
-	git_oid head;
-	git_reference *ref;
-	char name[128];
-	git_refdb *refdb;
-
-	g_repo = cl_git_sandbox_init("testrepo2");
-
-	cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
-
-	/* make a bunch of references */
-
-	for (r = 0; r < 200; ++r) {
-		p_snprintf(name, sizeof(name), "refs/heads/direct-%03d", r);
-		cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL));
-		git_reference_free(ref);
-	}
-
-	cl_git_pass(git_repository_refdb(&refdb, g_repo));
-	cl_git_pass(git_refdb_compress(refdb));
-	git_refdb_free(refdb);
-
-	g_expected = 206;
-
-	for (r = 0; r < REPEAT; ++r) {
-		g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */
-
-		for (t = 0; t < THREADS; ++t) {
-			id[t] = t;
-#ifdef GIT_THREADS
-			cl_git_pass(git_thread_create(&th[t], iterate_refs, &id[t]));
-#else
-			th[t] = t;
-			iterate_refs(&id[t]);
-#endif
-		}
-
-#ifdef GIT_THREADS
-		for (t = 0; t < THREADS; ++t) {
-			cl_git_pass(git_thread_join(&th[t], NULL));
-		}
-#endif
-
-		memset(th, 0, sizeof(th));
-	}
-}
-
 static void *create_refs(void *arg)
 {
-	int *id = arg, i;
+	int i, error;
+	struct th_data *data = (struct th_data *) arg;
 	git_oid head;
 	char name[128];
-	git_reference *ref[10];
+	git_reference *ref[NREFS];
+	git_repository *repo;
 
-	cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
+	cl_git_pass(git_repository_open(&repo, data->path));
 
-	for (i = 0; i < 10; ++i) {
-		p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", *id, i);
-		cl_git_pass(git_reference_create(&ref[i], g_repo, name, &head, 0, NULL));
+	do {
+		error = git_reference_name_to_id(&head, repo, "HEAD");
+	} while (error == GIT_ELOCKED);
+	cl_git_pass(error);
 
-		if (i == 5) {
+	for (i = 0; i < NREFS; ++i) {
+		p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i);
+		do {
+			error = git_reference_create(&ref[i], repo, name, &head, 0, NULL);
+		} while (error == GIT_ELOCKED);
+		cl_git_pass(error);
+
+		if (i == NREFS/2) {
 			git_refdb *refdb;
-			cl_git_pass(git_repository_refdb(&refdb, g_repo));
-			cl_git_pass(git_refdb_compress(refdb));
+			cl_git_pass(git_repository_refdb(&refdb, repo));
+			do {
+				error = git_refdb_compress(refdb);
+			} while (error == GIT_ELOCKED);
 			git_refdb_free(refdb);
 		}
 	}
 
-	for (i = 0; i < 10; ++i)
+	for (i = 0; i < NREFS; ++i)
 		git_reference_free(ref[i]);
 
+	git_repository_free(repo);
+
 	giterr_clear();
 	return arg;
 }
 
 static void *delete_refs(void *arg)
 {
-	int *id = arg, i;
+	int i, error;
+	struct th_data *data = (struct th_data *) arg;
 	git_reference *ref;
 	char name[128];
+	git_repository *repo;
 
-	for (i = 0; i < 10; ++i) {
+	cl_git_pass(git_repository_open(&repo, data->path));
+
+	for (i = 0; i < NREFS; ++i) {
 		p_snprintf(
-			name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i);
+			name, sizeof(name), "refs/heads/thread-%03d-%02d", (data->id) & ~0x3, i);
 
-		if (!git_reference_lookup(&ref, g_repo, name)) {
-			cl_git_pass(git_reference_delete(ref));
+		if (!git_reference_lookup(&ref, repo, name)) {
+			do {
+				error = git_reference_delete(ref);
+			} while (error == GIT_ELOCKED);
+			/* Sometimes we race with other deleter threads */
+			if (error == GIT_ENOTFOUND)
+				error = 0;
+
+			cl_git_pass(error);
 			git_reference_free(ref);
 		}
 
-		if (i == 5) {
+		if (i == NREFS/2) {
 			git_refdb *refdb;
-			cl_git_pass(git_repository_refdb(&refdb, g_repo));
-			cl_git_pass(git_refdb_compress(refdb));
+			cl_git_pass(git_repository_refdb(&refdb, repo));
+			do {
+				error = git_refdb_compress(refdb);
+			} while (error == GIT_ELOCKED);
+			cl_git_pass(error);
 			git_refdb_free(refdb);
 		}
 	}
 
+	git_repository_free(repo);
 	giterr_clear();
 	return arg;
 }
@@ -150,7 +142,7 @@
 void test_threads_refdb__edit_while_iterate(void)
 {
 	int r, t;
-	int id[THREADS];
+	struct th_data th_data[THREADS];
 	git_oid head;
 	git_reference *ref;
 	char name[128];
@@ -189,29 +181,26 @@
 		default: fn = iterate_refs; break;
 		}
 
-		id[t] = t;
+		th_data[t].id = t;
+		th_data[t].path = git_repository_path(g_repo);
 
-		/* It appears with all reflog writing changes, etc., that this
-		 * test has started to fail quite frequently, so let's disable it
-		 * for now by just running on a single thread...
-		 */
-/* #ifdef GIT_THREADS */
-/*		cl_git_pass(git_thread_create(&th[t], fn, &id[t])); */
-/* #else */
-		fn(&id[t]);
-/* #endif */
+#ifdef GIT_THREADS
+		cl_git_pass(git_thread_create(&th[t], fn, &th_data[t]));
+#else
+		fn(&th_data[t]);
+#endif
 	}
 
 #ifdef GIT_THREADS
-/*	for (t = 0; t < THREADS; ++t) { */
-/*		cl_git_pass(git_thread_join(th[t], NULL)); */
-/*	} */
+	for (t = 0; t < THREADS; ++t) {
+		cl_git_pass(git_thread_join(&th[t], NULL));
+	}
 
 	memset(th, 0, sizeof(th));
 
 	for (t = 0; t < THREADS; ++t) {
-		id[t] = t;
-		cl_git_pass(git_thread_create(&th[t], iterate_refs, &id[t]));
+		th_data[t].id = t;
+		cl_git_pass(git_thread_create(&th[t], iterate_refs, &th_data[t]));
 	}
 
 	for (t = 0; t < THREADS; ++t) {