Merge pull request #4239 from roblg/toplevel-dir-ignore-fix

Fix issue with directory glob ignore in subdirectories
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/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/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/odb.c b/src/odb.c
index 3090cca..b66324f 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -1002,7 +1002,7 @@
 	git_odb_object *object;
 	git_oid hashed;
 	bool found = false;
-	int error;
+	int error = 0;
 
 	if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0)
 		found = true;
@@ -1099,7 +1099,7 @@
 		const git_oid *key, size_t len, bool only_refreshed)
 {
 	size_t i;
-	int error;
+	int error = 0;
 	git_oid found_full_oid = {{0}};
 	git_rawobj raw = {0};
 	void *data = NULL;
@@ -1326,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;
 }
diff --git a/src/repository.c b/src/repository.c
index f8d19eb..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)
@@ -2532,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);
@@ -2543,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)
@@ -2576,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,
@@ -2591,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/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/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/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/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/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)