"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: initial sha256 support in the network protocol
To:
Omar Polo <op@omarpolo.com>
Cc:
gameoftrees@openbsd.org
Date:
Mon, 9 Feb 2026 18:00:20 +0100

Download raw body.

Thread
On Sat, Feb 07, 2026 at 06:04:18PM +0100, Omar Polo wrote:
> The "big" diff finally!
> 
> In the end I haven't managed to split it in smaller pieces since both
> fetch and send changes depends on the changes I'm doing to gitproto.
> Hopefully, it's not too big to review ;-)
> 
> There's some leftovers/spoilers on how I was planning to handle clone.
> The idea would be to pass -1 for the protocol, and have fetch tell us
> what the server supports.  It's still a bit tricky because we have to
> change the fetch_progress callback and reinitialize some state after
> the capabilities are parsed.  This is left for later.

This makes 'got clone' work for me on top of your diff.

I am running out of time for today so I cannot tidy this up right now.
I hope this allows you to make progress towards getting tests to pass.


M  cvg/cvg.c                                |  19+   2-
M  got/got.c                                |  17+   2-
M  include/got_fetch.h                      |   5+   2-
M  include/got_repository.h                 |   9+   0-
M  lib/fetch.c                              |  28+  19-
M  lib/gitproto.c                           |  11+  11-
M  lib/got_lib_gitproto.h                   |   3+   0-
M  lib/got_lib_privsep.h                    |   1+   0-
M  lib/got_lib_repository.h                 |   1+   0-
M  lib/privsep.c                            |   8+   2-
M  lib/repository.c                         |  19+  11-
M  lib/repository_init.c                    |  65+  17-
M  lib/worktree_cvg.c                       |   2+   0-
M  libexec/got-fetch-pack/got-fetch-pack.c  |  32+   1-

14 files changed, 220 insertions(+), 67 deletions(-)

commit - 3a30796e10c8ba3088bedf613d9e911264cebfad
commit + db093745dd4861787944b61f3dca257620255914
blob - 8bc47fb2cbcc88f89ff5dc2e9eb2cf7445f26c88
blob + db25d3304e991eac1c7422383a0559ca74c1a134
--- cvg/cvg.c
+++ cvg/cvg.c
@@ -1009,6 +1009,7 @@ struct got_fetch_progress_arg {
 		const char *git_url;
 		int fetch_all_branches;
 		int mirror_references;
+		int expected_algo;
 	} config_info;
 };
 
@@ -1022,6 +1023,7 @@ create_config_files(const char *proto, const char *hos
 
 static const struct got_error *
 fetch_progress(void *arg, const char *message, off_t packfile_size,
+    struct got_object_id *pack_hash,
     int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved)
 {
 	const struct got_error *err = NULL;
@@ -1038,6 +1040,14 @@ fetch_progress(void *arg, const char *message, off_t p
 	 */
 	if (a->create_configs && !a->configs_created &&
 	    !RB_EMPTY(a->config_info.symrefs)) {
+		if (a->config_info.expected_algo == -1 &&
+		    pack_hash->algo == GOT_HASH_SHA256) {
+			err = got_repo_init_gitconfig(a->repo,
+			    pack_hash->algo);
+			if (err)
+				return err;
+		}
+
 		err = create_config_files(a->config_info.proto,
 		    a->config_info.host, a->config_info.port,
 		    a->config_info.remote_repo_path,
@@ -1049,6 +1059,7 @@ fetch_progress(void *arg, const char *message, off_t p
 		    a->config_info.wanted_refs, a->repo);
 		if (err)
 			return err;
+
 		a->configs_created = 1;
 	}
 
@@ -1721,10 +1732,11 @@ cmd_clone(int argc, char *argv[])
 	fpa.config_info.git_url = git_url;
 	fpa.config_info.fetch_all_branches = fetch_all_branches;
 	fpa.config_info.mirror_references = mirror_references;
+	fpa.config_info.expected_algo = -1;
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs,
 	    GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references,
 	    fetch_all_branches, &wanted_branches, &wanted_refs,
-	    list_refs_only, verbosity, fetchfd, repo, NULL, NULL, bflag,
+	    list_refs_only, verbosity, -1, fetchfd, repo, NULL, NULL, bflag,
 	    fetch_progress, &fpa);
 	if (error)
 		goto done;
@@ -2678,6 +2690,7 @@ cmd_update(int argc, char *argv[])
 	int delete_remote = 0;
 	int replace_tags = 0;
 	int *pack_fds = NULL;
+	int expected_algo;
 	const char *remote_head = NULL, *worktree_branch = NULL;
 	struct got_object_id *commit_id = NULL;
 	char *commit_id_str = NULL;
@@ -2902,6 +2915,8 @@ cmd_update(int argc, char *argv[])
 	if (strncmp(refname, "refs/heads/", 11) == 0)
 		worktree_branch = refname;
 
+	expected_algo = got_repo_get_object_format(repo);
+
 	fpa.last_scaled_size[0] = '\0';
 	fpa.last_p_indexed = -1;
 	fpa.last_p_resolved = -1;
@@ -2910,10 +2925,12 @@ cmd_update(int argc, char *argv[])
 	fpa.create_configs = 0;
 	fpa.configs_created = 0;
 	memset(&fpa.config_info, 0, sizeof(fpa.config_info));
+	fpa.config_info.expected_algo = expected_algo;
 
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name,
 	    remote->mirror_references, 0, &wanted_branches, &wanted_refs,
-	    0, verbosity, fetchfd, repo, worktree_branch, remote_head,
+	    0, verbosity, expected_algo,
+	    fetchfd, repo, worktree_branch, remote_head,
 	    0, fetch_progress, &fpa);
 	if (error)
 		goto done;
blob - 487b4865e729be9a32fd1157ab96adf4e609986e
blob + a53709f8fc2cca37ac4090e5a082fdad48899dcf
--- got/got.c
+++ got/got.c
@@ -1098,6 +1098,7 @@ struct got_fetch_progress_arg {
 		const char *git_url;
 		int fetch_all_branches;
 		int mirror_references;
+		int expected_algo;
 	} config_info;
 };
 
@@ -1111,6 +1112,7 @@ create_config_files(const char *proto, const char *hos
 
 static const struct got_error *
 fetch_progress(void *arg, const char *message, off_t packfile_size,
+    struct got_object_id *pack_hash,
     int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved)
 {
 	const struct got_error *err = NULL;
@@ -1127,6 +1129,14 @@ fetch_progress(void *arg, const char *message, off_t p
 	 */
 	if (a->create_configs && !a->configs_created &&
 	    !RB_EMPTY(a->config_info.symrefs)) {
+		if (a->config_info.expected_algo == -1 &&
+		    pack_hash->algo == GOT_HASH_SHA256) {
+			err = got_repo_init_gitconfig(a->repo,
+			    pack_hash->algo);
+			if (err)
+				return err;
+		}
+
 		err = create_config_files(a->config_info.proto,
 		    a->config_info.host, a->config_info.port,
 		    a->config_info.remote_repo_path,
@@ -1138,6 +1148,7 @@ fetch_progress(void *arg, const char *message, off_t p
 		    a->config_info.wanted_refs, a->repo);
 		if (err)
 			return err;
+
 		a->configs_created = 1;
 	}
 
@@ -1820,10 +1831,11 @@ cmd_clone(int argc, char *argv[])
 	fpa.config_info.git_url = git_url;
 	fpa.config_info.fetch_all_branches = fetch_all_branches;
 	fpa.config_info.mirror_references = mirror_references;
+	fpa.config_info.expected_algo = -1;
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs,
 	    GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references,
 	    fetch_all_branches, &wanted_branches, &wanted_refs,
-	    list_refs_only, verbosity, fetchfd, repo, NULL, NULL, bflag,
+	    list_refs_only, verbosity, -1, fetchfd, repo, NULL, NULL, bflag,
 	    fetch_progress, &fpa);
 	if (error)
 		goto done;
@@ -2762,11 +2774,14 @@ cmd_fetch(int argc, char *argv[])
 	fpa.repo = repo;
 	fpa.create_configs = 0;
 	fpa.configs_created = 0;
+	fpa.config_info.expected_algo = got_repo_get_object_format(repo);
 	memset(&fpa.config_info, 0, sizeof(fpa.config_info));
 
+
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name,
 	    remote->mirror_references, fetch_all_branches, &wanted_branches,
-	    &wanted_refs, list_refs_only, verbosity, fetchfd, repo,
+	    &wanted_refs, list_refs_only, verbosity,
+	    got_repo_get_object_format(repo), fetchfd, repo,
 	    worktree_branch, remote_head, have_bflag, fetch_progress, &fpa);
 	if (error)
 		goto done;
blob - 4e7f538f523c582bd0b2b8be1e97182731a9ec7e
blob + 514769c17e48227a0ef18ab5759fca1cde588c37
--- include/got_fetch.h
+++ include/got_fetch.h
@@ -37,16 +37,19 @@ const struct got_error *got_fetch_connect(pid_t *, int
 
 /* A callback function which gets invoked with progress information to print. */
 typedef const struct got_error *(*got_fetch_progress_cb)(void *,
-    const char *, off_t, int, int, int, int);
+    const char *, off_t, struct got_object_id *, int, int, int, int);
 
 /*
  * Attempt to fetch a packfile from a server. This pack file will contain
  * objects which that are not yet contained in the provided repository.
  * Return the hash of the packfile (in form of an object ID) and lists of
  * references and symbolic references learned from the server.
+ * The expected object ID format is either from got_repo_get_object_format(),
+ * or can be passed as -1 to discover the server's object iD hash format
+ * when cloning a fresh repository.
  */
 const struct got_error *got_fetch_pack(struct got_object_id **,
 	struct got_pathlist_head *, struct got_pathlist_head *, const char *,
 	int, int, struct got_pathlist_head *, struct got_pathlist_head *,
-	int, int, int, struct got_repository *, const char *, const char *,
+	int, int, int, int, struct got_repository *, const char *, const char *,
 	int, got_fetch_progress_cb, void *);
blob - 89dabf01804eecca3d8273fcc04ca27c031c0f5e
blob + 7703401a56a83065fab1b59a58a52079860f8bff
--- include/got_repository.h
+++ include/got_repository.h
@@ -158,6 +158,15 @@ const struct got_error *got_repo_map_path(char **, str
 const struct got_error *got_repo_init(const char *, const char *,
     enum got_hash_algorithm);
 
+/*
+ * Create a new config file for git in the specified repository,
+ * resetting parameters such as the object Id hash algorithm.
+ * Should only be used if the repository is freshly cloned and has
+ * wrong setings in the existing configuration file.
+ */
+const struct got_error *got_repo_init_gitconfig(struct got_repository *,
+    enum got_hash_algorithm);
+
 /* Attempt to find a unique object ID for a given ID string prefix. */
 const struct got_error *got_repo_match_object_id_prefix(struct got_object_id **,
     const char *, int, struct got_repository *);
blob - b78727982872932c8a61d31ef72455d7c93c4b63
blob + ef20ad90080e0a5bfb77b37b0d2dd3144a0d28c5
--- lib/fetch.c
+++ lib/fetch.c
@@ -111,7 +111,8 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
     int mirror_references, int fetch_all_branches,
     struct got_pathlist_head *wanted_branches,
     struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity,
-    int fetchfd, struct got_repository *repo, const char *worktree_refname,
+    int expected_algo, int fetchfd, struct got_repository *repo,
+    const char *worktree_refname,
     const char *remote_head, int no_head, got_fetch_progress_cb progress_cb,
     void *progress_arg)
 {
@@ -135,8 +136,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 	uint32_t nobj = 0;
 	char *path;
 	char *progress = NULL;
-	enum got_hash_algorithm algo = GOT_HASH_SHA1;
-	size_t idlen;
+	size_t idlen = 0;
 
 	*pack_hash = NULL;
 	memset(&fetchibuf, 0, sizeof(fetchibuf));
@@ -155,13 +155,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 			return got_error_path(refname, GOT_ERR_FETCH_BAD_REF);
 	}
 
-	/* for listing refs we don't actually care about the algorithm */
 	if (!list_refs_only)
-		algo = got_repo_get_object_format(repo);
-
-	idlen = got_hash_digest_length(algo);
-
-	if (!list_refs_only)
 		repo_path = got_repo_get_path_git_dir(repo);
 
 	for (i = 0; i < nitems(tmpfds); i++)
@@ -285,8 +279,8 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 		err = got_error_from_errno("dup");
 		goto done;
 	}
-	err = got_privsep_send_fetch_req(&fetchibuf, nfetchfd, algo, &have_refs,
-	    fetch_all_branches, wanted_branches, wanted_refs,
+	err = got_privsep_send_fetch_req(&fetchibuf, nfetchfd, expected_algo,
+	    &have_refs, fetch_all_branches, wanted_branches, wanted_refs,
 	    list_refs_only, worktree_refname, remote_head, no_head, verbosity);
 	if (err != NULL)
 		goto done;
@@ -325,6 +319,26 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 		    &packfile_size_cur, *pack_hash, &fetchibuf);
 		if (err != NULL)
 			goto done;
+
+		/* Hash algorithm must now be known. */
+		switch ((*pack_hash)->algo) {
+		case GOT_HASH_SHA1:
+		case GOT_HASH_SHA256:
+			idlen = got_hash_digest_length((*pack_hash)->algo);
+			break;
+		default:
+			err = got_error_fmt(GOT_ERR_OBJECT_FORMAT,
+			    "could not determine object hash algorithm "
+			    "used by server");
+			goto done;
+		}
+
+		if (expected_algo != -1 &&
+		    (*pack_hash)->algo != expected_algo) {
+			err = got_error(GOT_ERR_OBJECT_FORMAT);
+			goto done;
+		}
+
 		/* Don't report size progress for an empty pack file. */
 		if (packfile_size_cur <= ssizeof(pack_hdr) + idlen)
 			packfile_size_cur = 0;
@@ -361,7 +375,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 					goto done;
 				}
 				err = progress_cb(progress_arg, s,
-				    packfile_size_cur, 0, 0, 0, 0);
+				    packfile_size_cur, *pack_hash, 0, 0, 0, 0);
 				free(s);
 				if (err)
 					break;
@@ -377,7 +391,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 				goto done;
 		} else if (!done && packfile_size_cur != packfile_size) {
 			err = progress_cb(progress_arg, NULL,
-			    packfile_size_cur, 0, 0, 0, 0);
+			    packfile_size_cur, *pack_hash, 0, 0, 0, 0);
 			if (err)
 				break;
 			packfile_size = packfile_size_cur;
@@ -397,11 +411,6 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 		goto done;
 	}
 
-	if ((*pack_hash)->algo != algo) {
-		err = got_error(GOT_ERR_OBJECT_FORMAT);
-		goto done;
-	}
-
 	/* If zero data was fetched without error we are already up-to-date. */
 	if (packfile_size == 0) {
 		free(*pack_hash);
@@ -513,7 +522,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 			goto done;
 		if (nobj_indexed != 0) {
 			err = progress_cb(progress_arg, NULL,
-			    packfile_size, nobj_total,
+			    packfile_size, *pack_hash, nobj_total,
 			    nobj_indexed, nobj_loose, nobj_resolved);
 			if (err)
 				break;
blob - f63af5151c343acd415e881e2ef503a4471825b0
blob + 7fae1f6aa9fecd6d20e4425f3c5e98240cc8e904
--- lib/gitproto.c
+++ lib/gitproto.c
@@ -232,7 +232,7 @@ got_gitproto_parse_ref_update_line(char **old_id_str, 
 
 static const struct got_error *
 match_capability(char **my_capabilities, const char *capa,
-    const struct got_capability *mycapa, enum got_hash_algorithm *algo)
+    const struct got_capability *mycapa)
 {
 	char *equalsign;
 	char *s;
@@ -241,15 +241,6 @@ match_capability(char **my_capabilities, const char *c
 	if (equalsign) {
 		if (strncmp(capa, mycapa->key, equalsign - capa) != 0)
 			return NULL;
-
-		if (strcmp(mycapa->key, "object-format") == 0) {
-			/* require an exact match on object-format value */
-			if (strcmp(equalsign + 1, mycapa->value) != 0)
-				return NULL;
-
-			if (strcmp(mycapa->value, "sha256") == 0)
-				*algo = GOT_HASH_SHA256;
-		}
 	} else {
 		if (strcmp(capa, mycapa->key) != 0)
 			return NULL;
@@ -331,9 +322,18 @@ got_gitproto_match_capabilities(char **common_capabili
 			continue;
 		}
 
+		if (equalsign != NULL &&
+		    strncmp(capa, GOT_CAPA_OBJECT_FORMAT,
+		    equalsign - capa) == 0) {
+			if (strcmp(equalsign + 1,
+			    GOT_CAPA_OBJECT_FORMAT_SHA256) == 0)
+				*algo = GOT_HASH_SHA256;
+			continue;
+		}
+
 		for (i = 0; i < ncapa; i++) {
 			err = match_capability(common_capabilities,
-			    capa, &my_capabilities[i], algo);
+			    capa, &my_capabilities[i]);
 			if (err)
 				break;
 		}
blob - 6128c320387d0fc02ee3b1edf54f6d59de13bb86
blob + a1521e5e216928e5fa0a439146c9a9aed8187231
--- lib/got_lib_gitproto.h
+++ lib/got_lib_gitproto.h
@@ -23,6 +23,9 @@
 #define GOT_CAPA_NO_THIN		"no-thin"
 #define GOT_CAPA_OBJECT_FORMAT		"object-format"
 
+#define GOT_CAPA_OBJECT_FORMAT_SHA1	"sha1"
+#define GOT_CAPA_OBJECT_FORMAT_SHA256	"sha256"
+
 #define GOT_SIDEBAND_PACKFILE_DATA	1
 #define GOT_SIDEBAND_PROGRESS_INFO	2
 #define GOT_SIDEBAND_ERROR_INFO		3
blob - 6d1e35b9cd6137e782e25a9f4f3a66450cb77a7e
blob + 7af185dfaa8a64ffa50fb31b054197de566cdc2c
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -120,6 +120,7 @@ enum got_imsg_type {
 	GOT_IMSG_FETCH_SYMREFS,
 	GOT_IMSG_FETCH_REF,
 	GOT_IMSG_FETCH_SERVER_PROGRESS,
+	GOT_IMSG_FETCH_OBJECT_FORMAT,
 	GOT_IMSG_FETCH_DOWNLOAD_PROGRESS,
 	GOT_IMSG_FETCH_DONE,
 	GOT_IMSG_IDXPACK_REQUEST,
blob - 825c284a4e4d1d2d2a5c1a4d62d6ba968820390a
blob + acd75ae28f46fb7fc4e959f7e6e146cb84cadfde
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -187,3 +187,4 @@ void got_repo_temp_fds_put(int, struct got_repository 
 
 const struct got_error *got_repo_find_object_id(struct got_object_id *,
     struct got_repository *);
+void got_repo_free_gitconfig(struct got_repository *);
blob - 8f1d693232a9d8b268ee215889b827c3ea255cce
blob + 4a1a7a694b548d7263ffc7c0a9a288c158de4ff5
--- lib/privsep.c
+++ lib/privsep.c
@@ -714,7 +714,8 @@ got_privsep_send_fetch_outfd(struct imsgbuf *ibuf, int
 const struct got_error *
 got_privsep_recv_fetch_progress(int *done, struct got_object_id **id,
     char **refname, struct got_pathlist_head *symrefs, char **server_progress,
-    off_t *packfile_size, struct got_object_id *pack_hash, struct imsgbuf *ibuf)
+    off_t *packfile_size, struct got_object_id *pack_hash,
+    struct imsgbuf *ibuf)
 {
 	const struct got_error *err = NULL;
 	struct imsg imsg;
@@ -730,7 +731,6 @@ got_privsep_recv_fetch_progress(int *done, struct got_
 	*refname = NULL;
 	*server_progress = NULL;
 	*packfile_size = 0;
-	memset(pack_hash, 0, sizeof(*pack_hash));
 
 	err = got_privsep_recv_imsg(&imsg, ibuf, 0);
 	if (err)
@@ -838,6 +838,12 @@ got_privsep_recv_fetch_progress(int *done, struct got_
 		}
 		memcpy(packfile_size, imsg.data, sizeof(*packfile_size));
 		break;
+	case GOT_IMSG_FETCH_OBJECT_FORMAT:
+		if (imsg_get_data(&imsg, pack_hash, sizeof(*pack_hash)) == -1) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			break;
+		}
+		break;
 	case GOT_IMSG_FETCH_DONE:
 		if (imsg_get_data(&imsg, pack_hash, sizeof(*pack_hash)) == -1) {
 			err = got_error(GOT_ERR_PRIVSEP_MSG);
blob - 14731673ee55811e65071b506239d1af5c93077a
blob + 5d954c5b18dc87b456b4f3fb1e85a55226c82c41
--- lib/repository.c
+++ lib/repository.c
@@ -844,6 +844,24 @@ done:
 	return err;
 }
 
+void
+got_repo_free_gitconfig(struct got_repository *repo)
+{
+	size_t i;
+
+	free(repo->gitconfig_author_name);
+	free(repo->gitconfig_author_email);
+	for (i = 0; i < repo->ngitconfig_remotes; i++)
+		got_repo_free_remote_repo_data(&repo->gitconfig_remotes[i]);
+	free(repo->gitconfig_remotes);
+	for (i = 0; i < repo->nextensions; i++) {
+		free(repo->extnames[i]);
+		free(repo->extvals[i]);
+	}
+	free(repo->extnames);
+	free(repo->extvals);
+}
+
 const struct got_error *
 got_repo_close(struct got_repository *repo)
 {
@@ -903,17 +921,7 @@ got_repo_close(struct got_repository *repo)
 
 	if (repo->gotconfig)
 		got_gotconfig_free(repo->gotconfig);
-	free(repo->gitconfig_author_name);
-	free(repo->gitconfig_author_email);
-	for (i = 0; i < repo->ngitconfig_remotes; i++)
-		got_repo_free_remote_repo_data(&repo->gitconfig_remotes[i]);
-	free(repo->gitconfig_remotes);
-	for (i = 0; i < repo->nextensions; i++) {
-		free(repo->extnames[i]);
-		free(repo->extvals[i]);
-	}
-	free(repo->extnames);
-	free(repo->extvals);
+	got_repo_free_gitconfig(repo);
 
 	got_pathlist_free(&repo->packidx_paths, GOT_PATHLIST_FREE_PATH);
 	free(repo);
blob - 226d9754daca5178d153ef58e08e9cb96d5b157a
blob + 398f19626c435567029d24b7cee6ce595fe25920
--- lib/repository_init.c
+++ lib/repository_init.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <sha1.h>
 #include <sha2.h>
+#include <unistd.h>
 
 #include "got_error.h"
 #include "got_path.h"
@@ -42,6 +43,28 @@
 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
 #endif
 
+static const struct got_error *
+init_gitconfig(const char *gitconfig_path, enum got_hash_algorithm algo)
+{
+	const char *gitconfig_sha1 = "[core]\n"
+	    "\trepositoryformatversion = 0\n"
+	    "\tfilemode = true\n"
+	    "\tbare = true\n";
+	const char *gitconfig_sha256 = "[core]\n"
+	    "\trepositoryformatversion = 1\n"
+	    "\tfilemode = true\n"
+	    "\tbare = true\n"
+	    "[extensions]\n"
+	    "\tobjectformat = sha256\n";
+	const char *gitconfig = gitconfig_sha1;
+
+	if (algo == GOT_HASH_SHA256)
+		gitconfig = gitconfig_sha256;
+
+	return got_path_create_file(gitconfig_path, gitconfig);
+}
+
+
 const struct got_error *
 got_repo_init(const char *repo_path, const char *head_name,
     enum got_hash_algorithm algo)
@@ -55,23 +78,9 @@ got_repo_init(const char *repo_path, const char *head_
 	const char *description_str = "Unnamed repository; "
 	    "edit this file 'description' to name the repository.";
 	const char *headref = "ref: refs/heads/";
-	const char *gitconfig_sha1 = "[core]\n"
-	    "\trepositoryformatversion = 0\n"
-	    "\tfilemode = true\n"
-	    "\tbare = true\n";
-	const char *gitconfig_sha256 = "[core]\n"
-	    "\trepositoryformatversion = 1\n"
-	    "\tfilemode = true\n"
-	    "\tbare = true\n"
-	    "[extensions]\n"
-	    "\tobjectformat = sha256\n";
-	const char *gitconfig = gitconfig_sha1;
 	char *headref_str, *path;
 	size_t i;
 
-	if (algo == GOT_HASH_SHA256)
-		gitconfig = gitconfig_sha256;
-
 	if (!got_path_dir_is_empty(repo_path))
 		return got_error(GOT_ERR_DIR_NOT_EMPTY);
 
@@ -107,10 +116,49 @@ got_repo_init(const char *repo_path, const char *head_
 
 	if (asprintf(&path, "%s/%s", repo_path, "config") == -1)
 		return got_error_from_errno("asprintf");
-	err = got_path_create_file(path, gitconfig);
+
+	err = init_gitconfig(path, algo);
 	free(path);
+	return err;
+}
+
+const struct got_error *
+got_repo_init_gitconfig(struct got_repository *repo,
+    enum got_hash_algorithm algo)
+{
+	const struct got_error *err;
+	char *path;
+
+	path = got_repo_get_path_gitconfig(repo);
+	if (path == NULL)
+		return got_error_from_errno("got_repo_get_path_gitconfig");
+
+	if (unlink(path) == -1) {
+		err = got_error_from_errno2("unlink", path);
+		goto done;
+	}
+
+	err = init_gitconfig(path, algo);
 	if (err)
-		return err;
+		goto done;
+	
+	got_repo_free_gitconfig(repo);
 
-	return NULL;
+	repo->algo = algo;
+
+	if (getenv("GOT_IGNORE_GITCONFIG") != NULL) {
+		err = got_repo_read_gitconfig(
+		    &repo->gitconfig_repository_format_version,
+		    &repo->gitconfig_author_name, &repo->gitconfig_author_email,
+		    &repo->gitconfig_remotes, &repo->ngitconfig_remotes,
+		    &repo->gitconfig_owner, &repo->extnames, &repo->extvals,
+		    &repo->nextensions, path);
+		if (err)
+			goto done;
+	} else if (algo == GOT_HASH_SHA256)
+	    repo->gitconfig_repository_format_version = 1;
+done:
+	free(path);
+	return err;
+
 }
blob - 5b9b1c532c0d687ea6a7630ba195c953fd2add73
blob + 04ac785139414457c03c21d8bbedf5c7df84c027
--- lib/worktree_cvg.c
+++ lib/worktree_cvg.c
@@ -2596,6 +2596,7 @@ struct got_fetch_progress_arg {
 
 static const struct got_error *
 fetch_progress(void *arg, const char *message, off_t packfile_size,
+    struct got_object_id *pack_hash,
     int nobj_total, int nobj_indexed, int nobj_loose, int nobj_resolved)
 {
 	struct got_fetch_progress_arg *a = arg;
@@ -2805,6 +2806,7 @@ fetch_updated_remote(const char *proto, const char *ho
 
 	err = got_fetch_pack(&pack_hash, &learned_refs, &symrefs,
 	    remote->name, 1, 0, &wanted_branches, &wanted_refs, 0, verbosity,
+	    got_repo_get_object_format(repo),
 	    fetchfd, repo, head_refname, NULL, 0, fetch_progress, &fpa);
 	if (err)
 		goto done;
blob - ef7f20aedafd9c0d532e4b464943c35f2f88b582
blob + cb16d011377640803090d492c6e35ff56a102a15
--- libexec/got-fetch-pack/got-fetch-pack.c
+++ libexec/got-fetch-pack/got-fetch-pack.c
@@ -274,6 +274,15 @@ send_fetch_symrefs(struct imsgbuf *ibuf, struct got_pa
 }
 
 static const struct got_error *
+send_object_format(struct imsgbuf *ibuf, struct got_object_id *pack_hash)
+{
+	if (imsg_compose(ibuf, GOT_IMSG_FETCH_OBJECT_FORMAT, 0, 0, -1,
+	    pack_hash, sizeof(*pack_hash)) == -1)
+		return got_error_from_errno("imsg_compose FETCH_OBJECT_FORMAT");
+	return got_privsep_flush_imsg(ibuf);
+}
+
+static const struct got_error *
 send_fetch_ref(struct imsgbuf *ibuf, struct got_object_id *refid,
     const char *refname)
 {
@@ -420,13 +429,29 @@ fetch_pack(int fd, int packfd, int expected_algo,
 				    getprogname(), server_capabilities);
 			err = got_gitproto_match_capabilities(&my_capabilities,
 			    &symrefs, server_capabilities,
-			    got_capabilities, nitems(got_capabilities), &algo);
+			    got_capabilities, nitems(got_capabilities),
+			    &algo);
 			if (err)
 				goto done;
 			if (expected_algo != -1 && algo != expected_algo) {
 				err = got_error(GOT_ERR_OBJECT_FORMAT);
 				goto done;
 			}
+
+			if (algo == GOT_HASH_SHA256) {
+				char *s;
+
+				if (asprintf(&s, "%s%s%s=%s", my_capabilities,
+				    my_capabilities[0] != '\0' ? " " : "",
+				    GOT_CAPA_OBJECT_FORMAT,
+				    GOT_CAPA_OBJECT_FORMAT_SHA256) == -1) {
+					err = got_error_from_errno("asprintf");
+					goto done;
+				}
+				free(my_capabilities);
+				my_capabilities = s;
+			}
+
 			if (chattygot)
 				fprintf(stderr, "%s: my capabilities:%s\n",
 				    getprogname(), my_capabilities != NULL ?
@@ -434,6 +459,12 @@ fetch_pack(int fd, int packfd, int expected_algo,
 			err = send_fetch_symrefs(ibuf, &symrefs);
 			if (err)
 				goto done;
+
+			pack_hash->algo = algo;
+			err = send_object_format(ibuf, pack_hash);
+			if (err)
+				goto done;
+
 			is_firstpkt = 0;
 			if (!fetch_all_branches) {
 				RB_FOREACH(pe, got_pathlist_head, &symrefs) {