{"thread":{"id":"46861","subject":"[PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","startedAt":"2017-09-29T20:12:23Z","lastAt":"2017-10-12T15:45:17Z","messageCount":28,"participants":["Jonathan Tan","Johannes Schindelin","Junio C Hamano","Christian Couder","Jeff Hostetler"],"isPatch":true,"patchVersion":1,"patchTotal":18},"messages":[{"id":"329192","messageId":"cover.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":null,"subject":"[PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:36Z","receivedAt":"2017-09-29T20:12:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"These patches are also available online:\nhttps://github.com/jonathantanmy/git/commits/partialclone3\n\n(I've announced it in another e-mail, but am now sending the patches to the\nmailing list too.)\n\nHere's an update of my work so far. Notable features:\n - These 18 patches allow a user to clone with --blob-max-bytes=<bytes>,\n   creating a partial clone that is automatically configured to lazily\n   fetch missing objects from the origin. The local repo also has fsck\n   working offline, and GC working (albeit only on locally created\n   objects).\n - Cloning and fetching is currently only able to exclude blobs by a\n   size threshold, but the local repository is already capable of\n   fetching missing objects of any type. For example, if a repository\n   with missing trees or commits is generated by any tool (for example,\n   a future version of Git), current Git with my patches will still be\n   able to operate on them, automatically fetching those missing trees\n   and commits when needed.\n - Missing blobs are fetched all at once during checkout.\n\nJeff Hostetler has sent out some object-filtering patches [1] that is a\nsuperset of the object-filtering functionality that I have (in the\npack-objects patches). I have gone for the minimal approach here, but if\nhis patches are merged, I'll update my patch set to use those.\n\n[1] https://public-inbox.org/git/20170922203017.53986-6-git@jeffhostetler.com/\n\nDemo\n====\n\nObtain a repository.\n\n    $ make prefix=$HOME/local install\n    $ cd $HOME/tmp\n    $ git clone https://github.com/git/git\n\nMake it advertise the new feature and allow requests for arbitrary blobs.\n\n    $ git -C git config uploadpack.advertiseblobmaxbytes 1\n    $ git -C git config uploadpack.allowanysha1inwant 1\n\nPerform the partial clone and check that it is indeed smaller. Specify\n\"file://\" in order to test the partial clone mechanism. (If not, Git will\nperform a local clone, which unselectively copies every object.)\n\n    $ git clone --blob-max-bytes=0 \"file://$(pwd)/git\" git2\n    $ git clone \"file://$(pwd)/git\" git3\n    $ du -sh git2 git3\n    85M\tgit2\n    130M\tgit3\n\nObserve that the new repo is automatically configured to fetch missing objects\nfrom the original repo. Subsequent fetches will also be partial.\n\n    $ cat git2/.git/config\n    [core]\n    \trepositoryformatversion = 1\n    \tfilemode = true\n    \tbare = false\n    \tlogallrefupdates = true\n    [remote \"origin\"]\n    \turl = [snip]\n    \tfetch = +refs/heads/*:refs/remotes/origin/*\n    \tblobmaxbytes = 0\n    [extensions]\n    \tpartialclone = origin\n    [branch \"master\"]\n    \tremote = origin\n    \tmerge = refs/heads/master\n\nDesign\n======\n\nLocal repository layout\n-----------------------\n\nA repository declares its dependence on a *promisor remote* (a remote that\ndeclares that it can serve certain objects when requested) by a repository\nextension \"partialclone\". `extensions.partialclone` must be set to the name of\nthe remote (\"origin\" in the demo above).\n\nA packfile can be annotated as originating from the promisor remote by the\nexistence of a \"(packfile name).promisor\" file with arbitrary contents (similar\nto the \".keep\" file). Whenever a promisor remote sends an object, it declares\nthat it can serve every object directly or indirectly referenced by the sent\nobject.\n\nA promisor packfile is a packfile annotated with the \".promisor\" file. A\npromisor object is an object that the promisor remote is known to be able to\nserve, because it is an object in a promisor packfile or directly referred to by\none.\n\n(In the future, we might need to add \".promisor\" support to loose objects.)\n\nConnectivity check and gc\n-------------------------\n\nThe object walk done by the connectivity check (as used by fsck and fetch) stops\nat all promisor objects.\n\nThe object walk done by gc also stops at all promisor objects. Only non-promisor\npackfiles are deleted (if pack deletion is requested); promisor packfiles are\nleft alone. This maintains the distinction between promisor packfiles and\nnon-promisor packfiles. (In the future, we might need to do something more\nsophisticated with promisor packfiles.)\n\nFetching of missing objects\n---------------------------\n\nWhen `sha1_object_info_extended()` (or similar) is invoked, it will\nautomatically attempt to fetch a missing object from the promisor remote if that\nobject is not in the local repository. For efficiency, no check is made as to\nwhether that object is known to be a promisor object or not.\n\nThis automatic fetching can be toggled on and off by the `fetch_if_missing`\nglobal variable, and it is on by default.\n\nThe actual fetch is done through the fetch-pack/upload-pack protocol. Right now,\nthis uses the fact that upload-pack allows blob and tree \"want\"s, and this\nincurs the overhead of the unnecessary ref advertisement. I hope that protocol\nv2 will allow us to declare that blob and tree \"want\"s are allowed, and allow\nthe client to declare that it does not want the ref advertisement. All packfiles\ndownloaded in this way are annotated with \".promisor\".\n\nFetching with `git fetch`\n-------------------------\n\nThe fetch-pack/upload-pack protocol has also been extended to support omission\nof blobs above a certain size. The client only allows this when fetching from\nthe promisor remote, and will annotate any packs received from the promisor\nremote with \".promisor\".\n\nJonathan Tan (18):\n  fsck: introduce partialclone extension\n  fsck: support refs pointing to promisor objects\n  fsck: support referenced promisor objects\n  fsck: support promisor objects as CLI argument\n  index-pack: refactor writing of .keep files\n  introduce fetch-object: fetch one promisor object\n  sha1_file: support lazily fetching missing objects\n  rev-list: support termination at promisor objects\n  gc: do not repack promisor packfiles\n  pack-objects: rename want_.* to ignore_.*\n  pack-objects: support --blob-max-bytes\n  fetch-pack: support excluding large blobs\n  fetch: refactor calculation of remote list\n  fetch: support excluding large blobs\n  clone: support excluding large blobs\n  clone: configure blobmaxbytes in created repos\n  unpack-trees: batch fetching of missing blobs\n  fetch-pack: restore save_commit_buffer after use\n\n Documentation/git-pack-objects.txt                |  12 +-\n Documentation/technical/pack-protocol.txt         |   9 +\n Documentation/technical/protocol-capabilities.txt |   7 +\n Documentation/technical/repository-version.txt    |  12 +\n Makefile                                          |   1 +\n builtin/cat-file.c                                |   2 +\n builtin/clone.c                                   |  24 +-\n builtin/fetch-pack.c                              |  21 ++\n builtin/fetch.c                                   |  36 ++-\n builtin/fsck.c                                    |  26 +-\n builtin/gc.c                                      |   3 +\n builtin/index-pack.c                              | 113 ++++---\n builtin/pack-objects.c                            |  97 ++++--\n builtin/prune.c                                   |   7 +\n builtin/repack.c                                  |   7 +-\n builtin/rev-list.c                                |  13 +\n cache.h                                           |  13 +-\n connected.c                                       |   1 +\n environment.c                                     |   1 +\n fetch-object.c                                    |  45 +++\n fetch-object.h                                    |  11 +\n fetch-pack.c                                      |  23 +-\n fetch-pack.h                                      |   3 +\n list-objects.c                                    |  16 +-\n object.c                                          |   2 +-\n packfile.c                                        |  77 ++++-\n packfile.h                                        |  13 +\n remote-curl.c                                     |  21 +-\n remote.c                                          |   2 +\n remote.h                                          |   2 +\n revision.c                                        |  33 ++-\n revision.h                                        |   5 +-\n setup.c                                           |   7 +-\n sha1_file.c                                       |  38 ++-\n t/t0410-partial-clone.sh                          | 343 ++++++++++++++++++++++\n t/t5300-pack-object.sh                            |  45 +++\n t/t5500-fetch-pack.sh                             | 115 ++++++++\n t/t5601-clone.sh                                  | 101 +++++++\n t/test-lib-functions.sh                           |  12 +\n transport-helper.c                                |   4 +\n transport.c                                       |  18 ++\n transport.h                                       |  12 +\n unpack-trees.c                                    |  22 ++\n upload-pack.c                                     |  16 +-\n 44 files changed, 1278 insertions(+), 113 deletions(-)\n create mode 100644 fetch-object.c\n create mode 100644 fetch-object.h\n create mode 100755 t/t0410-partial-clone.sh\n\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329193","messageId":"a8c2d9c11c8bfba214cbd69f8c82697d384c0b55.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 01/18] fsck: introduce partialclone extension","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:37Z","receivedAt":"2017-09-29T20:12:25Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, Git does not support repos with very large numbers of objects\nor repos that wish to minimize manipulation of certain blobs (for\nexample, because they are very large) very well, even if the user\noperates mostly on part of the repo, because Git is designed on the\nassumption that every referenced object is available somewhere in the\nrepo storage. In such an arrangement, the full set of objects is usually\navailable in remote storage, ready to be lazily downloaded.\n\nIntroduce the ability to have missing objects in a repo.  This\nfunctionality is guarded behind a new repository extension option\n`extensions.partialClone`. See the update to\nDocumentation/technical/repository-version.txt in this patch for more\ninformation.\n\nTeach fsck about the new state of affairs. In this commit, teach fsck\nthat missing promisor objects referenced from the reflog are not an\nerror case; in future commits, fsck will be taught about other cases.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/technical/repository-version.txt | 12 ++++\n builtin/fsck.c                                 |  2 +-\n cache.h                                        |  5 +-\n environment.c                                  |  1 +\n packfile.c                                     | 77 +++++++++++++++++++++++-\n packfile.h                                     | 13 +++++\n setup.c                                        |  7 ++-\n t/t0410-partial-clone.sh                       | 81 ++++++++++++++++++++++++++\n 8 files changed, 192 insertions(+), 6 deletions(-)\n create mode 100755 t/t0410-partial-clone.sh\n\ndiff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt\nindex 00ad37986..074ccb9e0 100644\n--- a/Documentation/technical/repository-version.txt\n+++ b/Documentation/technical/repository-version.txt\n@@ -86,3 +86,15 @@ for testing format-1 compatibility.\n When the config key `extensions.preciousObjects` is set to `true`,\n objects in the repository MUST NOT be deleted (e.g., by `git-prune` or\n `git repack -d`).\n+\n+`partialClone`\n+~~~~~~~~~~~~~~\n+\n+When the config key `extensions.partialClone` is set, a remote is\n+designated as a \"promisor remote\". Objects referenced by packed objects\n+obtained from that promisor remote do not need to be in the local repo.\n+Instead, the promisor remote promises that all such objects can be\n+fetched from it in the future, and Git will fetch those objects when\n+needed.\n+\n+The value of this key is the name of the aforementioned promisor remote.\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 1e4c471b4..97d1e621e 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -403,7 +403,7 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,\n \t\t\t\t\txstrfmt(\"%s@{%\"PRItime\"}\", refname, timestamp));\n \t\t\tobj->flags |= USED;\n \t\t\tmark_object_reachable(obj);\n-\t\t} else {\n+\t\t} else if (!is_promisor_object(oid)) {\n \t\t\terror(\"%s: invalid reflog entry %s\", refname, oid_to_hex(oid));\n \t\t\terrors_found |= ERROR_REACHABLE;\n \t\t}\ndiff --git a/cache.h b/cache.h\nindex a916bc79e..437206d06 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -853,10 +853,12 @@ extern int grafts_replace_parents;\n #define GIT_REPO_VERSION 0\n #define GIT_REPO_VERSION_READ 1\n extern int repository_format_precious_objects;\n+extern char *repository_format_partial_clone;\n \n struct repository_format {\n \tint version;\n \tint precious_objects;\n+\tchar *partial_clone;\n \tint is_bare;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\n@@ -1578,7 +1580,8 @@ extern struct packed_git {\n \tunsigned pack_local:1,\n \t\t pack_keep:1,\n \t\t freshened:1,\n-\t\t do_not_close:1;\n+\t\t do_not_close:1,\n+\t\t pack_promisor:1;\n \tunsigned char sha1[20];\n \tstruct revindex_entry *revindex;\n \t/* something like \".git/objects/pack/xxxxx.pack\" */\ndiff --git a/environment.c b/environment.c\nindex f1f934b6f..624385fb2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -27,6 +27,7 @@ int warn_ambiguous_refs = 1;\n int warn_on_object_refname_ambiguity = 1;\n int ref_paranoia = -1;\n int repository_format_precious_objects;\n+char *repository_format_partial_clone;\n const char *git_commit_encoding;\n const char *git_log_output_encoding;\n const char *apply_default_whitespace;\ndiff --git a/packfile.c b/packfile.c\nindex f86fa051c..88f424320 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -8,6 +8,11 @@\n #include \"list.h\"\n #include \"streaming.h\"\n #include \"sha1-lookup.h\"\n+#include \"commit.h\"\n+#include \"object.h\"\n+#include \"tag.h\"\n+#include \"tree-walk.h\"\n+#include \"tree.h\"\n \n char *odb_pack_name(struct strbuf *buf,\n \t\t    const unsigned char *sha1,\n@@ -638,10 +643,10 @@ struct packed_git *add_packed_git(const char *path, size_t path_len, int local)\n \t\treturn NULL;\n \n \t/*\n-\t * \".pack\" is long enough to hold any suffix we're adding (and\n+\t * \".promisor\" is long enough to hold any suffix we're adding (and\n \t * the use xsnprintf double-checks that)\n \t */\n-\talloc = st_add3(path_len, strlen(\".pack\"), 1);\n+\talloc = st_add3(path_len, strlen(\".promisor\"), 1);\n \tp = alloc_packed_git(alloc);\n \tmemcpy(p->pack_name, path, path_len);\n \n@@ -649,6 +654,10 @@ struct packed_git *add_packed_git(const char *path, size_t path_len, int local)\n \tif (!access(p->pack_name, F_OK))\n \t\tp->pack_keep = 1;\n \n+\txsnprintf(p->pack_name + path_len, alloc - path_len, \".promisor\");\n+\tif (!access(p->pack_name, F_OK))\n+\t\tp->pack_promisor = 1;\n+\n \txsnprintf(p->pack_name + path_len, alloc - path_len, \".pack\");\n \tif (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {\n \t\tfree(p);\n@@ -776,7 +785,8 @@ static void prepare_packed_git_one(char *objdir, int local)\n \t\tif (ends_with(de->d_name, \".idx\") ||\n \t\t    ends_with(de->d_name, \".pack\") ||\n \t\t    ends_with(de->d_name, \".bitmap\") ||\n-\t\t    ends_with(de->d_name, \".keep\"))\n+\t\t    ends_with(de->d_name, \".keep\") ||\n+\t\t    ends_with(de->d_name, \".promisor\"))\n \t\t\tstring_list_append(&garbage, path.buf);\n \t\telse\n \t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path.buf);\n@@ -1884,6 +1894,9 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)\n \tfor (p = packed_git; p; p = p->next) {\n \t\tif ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n \t\t\tcontinue;\n+\t\tif ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n \t\tif (open_pack_index(p)) {\n \t\t\tpack_errors = 1;\n \t\t\tcontinue;\n@@ -1894,3 +1907,61 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)\n \t}\n \treturn r ? r : pack_errors;\n }\n+\n+static int add_promisor_object(const struct object_id *oid,\n+\t\t\t       struct packed_git *pack,\n+\t\t\t       uint32_t pos,\n+\t\t\t       void *set_)\n+{\n+\tstruct oidset *set = set_;\n+\tstruct object *obj = parse_object(oid);\n+\tif (!obj)\n+\t\treturn 1;\n+\n+\toidset_insert(set, oid);\n+\n+\t/*\n+\t * If this is a tree, commit, or tag, the objects it refers\n+\t * to are also promisor objects. (Blobs refer to no objects.)\n+\t */\n+\tif (obj->type == OBJ_TREE) {\n+\t\tstruct tree *tree = (struct tree *)obj;\n+\t\tstruct tree_desc desc;\n+\t\tstruct name_entry entry;\n+\t\tif (init_tree_desc_gently(&desc, tree->buffer, tree->size))\n+\t\t\t/*\n+\t\t\t * Error messages are given when packs are\n+\t\t\t * verified, so do not print any here.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\twhile (tree_entry_gently(&desc, &entry))\n+\t\t\toidset_insert(set, entry.oid);\n+\t} else if (obj->type == OBJ_COMMIT) {\n+\t\tstruct commit *commit = (struct commit *) obj;\n+\t\tstruct commit_list *parents = commit->parents;\n+\n+\t\toidset_insert(set, &commit->tree->object.oid);\n+\t\tfor (; parents; parents = parents->next)\n+\t\t\toidset_insert(set, &parents->item->object.oid);\n+\t} else if (obj->type == OBJ_TAG) {\n+\t\tstruct tag *tag = (struct tag *) obj;\n+\t\toidset_insert(set, &tag->tagged->oid);\n+\t}\n+\treturn 0;\n+}\n+\n+int is_promisor_object(const struct object_id *oid)\n+{\n+\tstatic struct oidset promisor_objects;\n+\tstatic int promisor_objects_prepared;\n+\n+\tif (!promisor_objects_prepared) {\n+\t\tif (repository_format_partial_clone) {\n+\t\t\tfor_each_packed_object(add_promisor_object,\n+\t\t\t\t\t       &promisor_objects,\n+\t\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t}\n+\t\tpromisor_objects_prepared = 1;\n+\t}\n+\treturn oidset_contains(&promisor_objects, oid);\n+}\ndiff --git a/packfile.h b/packfile.h\nindex 0cdeb54dc..a7fca598d 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -1,6 +1,8 @@\n #ifndef PACKFILE_H\n #define PACKFILE_H\n \n+#include \"oidset.h\"\n+\n /*\n  * Generate the filename to be used for a pack file with checksum \"sha1\" and\n  * extension \"ext\". The result is written into the strbuf \"buf\", overwriting\n@@ -124,6 +126,11 @@ extern int has_sha1_pack(const unsigned char *sha1);\n \n extern int has_pack_index(const unsigned char *sha1);\n \n+/*\n+ * Only iterate over packs obtained from the promisor remote.\n+ */\n+#define FOR_EACH_OBJECT_PROMISOR_ONLY 2\n+\n /*\n  * Iterate over packed objects in both the local\n  * repository and any alternates repositories (unless the\n@@ -135,4 +142,10 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n extern int for_each_packed_object(each_packed_object_fn, void *, unsigned flags);\n \n+/*\n+ * Return 1 if an object in a promisor packfile is or refers to the given\n+ * object, 0 otherwise.\n+ */\n+extern int is_promisor_object(const struct object_id *oid);\n+\n #endif\ndiff --git a/setup.c b/setup.c\nindex 6d8380acd..35c9da806 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -420,7 +420,11 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t\t;\n \t\telse if (!strcmp(ext, \"preciousobjects\"))\n \t\t\tdata->precious_objects = git_config_bool(var, value);\n-\t\telse\n+\t\telse if (!strcmp(ext, \"partialclone\")) {\n+\t\t\tif (!value)\n+\t\t\t\treturn config_error_nonbool(var);\n+\t\t\tdata->partial_clone = xstrdup(value);\n+\t\t} else\n \t\t\tstring_list_append(&data->unknown_extensions, ext);\n \t} else if (strcmp(var, \"core.bare\") == 0) {\n \t\tdata->is_bare = git_config_bool(var, value);\n@@ -463,6 +467,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t}\n \n \trepository_format_precious_objects = candidate.precious_objects;\n+\trepository_format_partial_clone = candidate.partial_clone;\n \tstring_list_clear(&candidate.unknown_extensions, 0);\n \tif (!has_common) {\n \t\tif (candidate.is_bare != -1) {\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nnew file mode 100755\nindex 000000000..3ddb3b98f\n--- /dev/null\n+++ b/t/t0410-partial-clone.sh\n@@ -0,0 +1,81 @@\n+#!/bin/sh\n+\n+test_description='partial clone'\n+\n+. ./test-lib.sh\n+\n+delete_object () {\n+\trm $1/.git/objects/$(echo $2 | sed -e 's|^..|&/|')\n+}\n+\n+pack_as_from_promisor () {\n+\tHASH=$(git -C repo pack-objects .git/objects/pack/pack) &&\n+\t>repo/.git/objects/pack/pack-$HASH.promisor\n+}\n+\n+test_expect_success 'missing reflog object, but promised by a commit, passes fsck' '\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tA=$(git -C repo commit-tree -m a HEAD^{tree}) &&\n+\tC=$(git -C repo commit-tree -m c -p $A HEAD^{tree}) &&\n+\n+\t# Reference $A only from reflog, and delete it\n+\tgit -C repo branch my_branch \"$A\" &&\n+\tgit -C repo branch -f my_branch my_commit &&\n+\tdelete_object repo \"$A\" &&\n+\n+\t# State that we got $C, which refers to $A, from promisor\n+\tprintf \"$C\\n\" | pack_as_from_promisor &&\n+\n+\t# Normally, it fails\n+\ttest_must_fail git -C repo fsck &&\n+\n+\t# But with the extension, it succeeds\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo fsck\n+'\n+\n+test_expect_success 'missing reflog object, but promised by a tag, passes fsck' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tA=$(git -C repo commit-tree -m a HEAD^{tree}) &&\n+\tgit -C repo tag -a -m d my_tag_name $A &&\n+\tT=$(git -C repo rev-parse my_tag_name) &&\n+\tgit -C repo tag -d my_tag_name &&\n+\n+\t# Reference $A only from reflog, and delete it\n+\tgit -C repo branch my_branch \"$A\" &&\n+\tgit -C repo branch -f my_branch my_commit &&\n+\tdelete_object repo \"$A\" &&\n+\n+\t# State that we got $T, which refers to $A, from promisor\n+\tprintf \"$T\\n\" | pack_as_from_promisor &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo fsck\n+'\n+\n+test_expect_success 'missing reflog object alone fails fsck, even with extension set' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tA=$(git -C repo commit-tree -m a HEAD^{tree}) &&\n+\tB=$(git -C repo commit-tree -m b HEAD^{tree}) &&\n+\n+\t# Reference $A only from reflog, and delete it\n+\tgit -C repo branch my_branch \"$A\" &&\n+\tgit -C repo branch -f my_branch my_commit &&\n+\tdelete_object repo \"$A\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\ttest_must_fail git -C repo fsck\n+'\n+\n+test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329194","messageId":"d21a3364d56827794cade1bb506acad5b3cc405e.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 04/18] fsck: support promisor objects as CLI argument","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:40Z","receivedAt":"2017-09-29T20:12:27Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach fsck to not treat missing promisor objects provided on the CLI as\nan error when extensions.partialclone is set.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/fsck.c           |  2 ++\n t/t0410-partial-clone.sh | 13 +++++++++++++\n 2 files changed, 15 insertions(+)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 4492a4fab..f6cb4d755 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -755,6 +755,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \t\t\tstruct object *obj = lookup_object(oid.hash);\n \n \t\t\tif (!obj || !(obj->flags & HAS_OBJ)) {\n+\t\t\t\tif (is_promisor_object(&oid))\n+\t\t\t\t\tcontinue;\n \t\t\t\terror(\"%s: object missing\", oid_to_hex(&oid));\n \t\t\t\terrors_found |= ERROR_OBJECT;\n \t\t\t\tcontinue;\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex 4f9931f9b..e96f436b0 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -125,4 +125,17 @@ test_expect_success 'missing object, but promised, passes fsck' '\n \tgit -C repo fsck\n '\n \n+test_expect_success 'missing CLI object, but promised, passes fsck' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tA=$(git -C repo commit-tree -m a HEAD^{tree}) &&\n+\tpromise_and_delete \"$A\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo fsck \"$A\"\n+'\n+\n test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329195","messageId":"94b52786a5b652749f1109e2baee482161a8cc28.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 06/18] introduce fetch-object: fetch one promisor object","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:42Z","receivedAt":"2017-09-29T20:12:28Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Introduce fetch-object, providing the ability to fetch one object from a\npromisor remote.\n\nThis uses fetch-pack. To do this, the transport mechanism has been\nupdated with 2 flags, \"from-promisor\" to indicate that the resulting\npack comes from a promisor remote (and thus should be annotated as such\nby index-pack), and \"no-haves\" to suppress the sending of \"have\" lines.\n\nThis will be tested in a subsequent commit.\n\nNEEDSWORK: update this when we have more information about protocol v2,\nwhich should allow a way to suppress the ref advertisement and\nofficially allow any object type to be \"want\"-ed.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Makefile             |  1 +\n builtin/fetch-pack.c |  8 ++++++++\n builtin/index-pack.c | 16 +++++++++++++---\n fetch-object.c       | 23 +++++++++++++++++++++++\n fetch-object.h       |  6 ++++++\n fetch-pack.c         |  8 ++++++--\n fetch-pack.h         |  2 ++\n remote-curl.c        | 14 +++++++++++++-\n transport.c          |  8 ++++++++\n transport.h          |  8 ++++++++\n 10 files changed, 88 insertions(+), 6 deletions(-)\n create mode 100644 fetch-object.c\n create mode 100644 fetch-object.h\n\ndiff --git a/Makefile b/Makefile\nindex ed5960e6b..4303ef6f8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -789,6 +789,7 @@ LIB_OBJS += ewah/ewah_bitmap.o\n LIB_OBJS += ewah/ewah_io.o\n LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec_cmd.o\n+LIB_OBJS += fetch-object.o\n LIB_OBJS += fetch-pack.o\n LIB_OBJS += fsck.o\n LIB_OBJS += gettext.o\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 366b9d13f..9f303cf98 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -143,6 +143,14 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \t\t\targs.update_shallow = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(\"--from-promisor\", arg)) {\n+\t\t\targs.from_promisor = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(\"--no-haves\", arg)) {\n+\t\t\targs.no_haves = 1;\n+\t\t\tcontinue;\n+\t\t}\n \t\tusage(fetch_pack_usage);\n \t}\n \tif (deepen_not.nr)\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 7ad170590..14ebb2f17 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1429,14 +1429,16 @@ static void write_special_file(const char *suffix, const char *msg,\n \t\tif (close(fd) != 0)\n \t\t\tdie_errno(_(\"cannot close written %s file '%s'\"),\n \t\t\t\t  suffix, filename);\n-\t\t*report = suffix;\n+\t\tif (report)\n+\t\t\t*report = suffix;\n \t}\n \tstrbuf_release(&name_buf);\n }\n \n static void final(const char *final_pack_name, const char *curr_pack_name,\n \t\t  const char *final_index_name, const char *curr_index_name,\n-\t\t  const char *keep_msg, unsigned char *sha1)\n+\t\t  const char *keep_msg, const char *promisor_msg,\n+\t\t  unsigned char *sha1)\n {\n \tconst char *report = \"pack\";\n \tstruct strbuf pack_name = STRBUF_INIT;\n@@ -1455,6 +1457,9 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tif (keep_msg)\n \t\twrite_special_file(\"keep\", keep_msg, final_pack_name, sha1,\n \t\t\t\t   &report);\n+\tif (promisor_msg)\n+\t\twrite_special_file(\"promisor\", promisor_msg, final_pack_name,\n+\t\t\t\t   sha1, NULL);\n \n \tif (final_pack_name != curr_pack_name) {\n \t\tif (!final_pack_name)\n@@ -1644,6 +1649,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \tconst char *curr_index;\n \tconst char *index_name = NULL, *pack_name = NULL;\n \tconst char *keep_msg = NULL;\n+\tconst char *promisor_msg = NULL;\n \tstruct strbuf index_name_buf = STRBUF_INIT;\n \tstruct pack_idx_entry **idx_objects;\n \tstruct pack_idx_option opts;\n@@ -1693,6 +1699,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\tkeep_msg = \"\";\n \t\t\t} else if (starts_with(arg, \"--keep=\")) {\n \t\t\t\tkeep_msg = arg + 7;\n+\t\t\t} else if (!strcmp(arg, \"--promisor\")) {\n+\t\t\t\tpromisor_msg = \"\";\n+\t\t\t} else if (starts_with(arg, \"--promisor=\")) {\n+\t\t\t\tpromisor_msg = arg + strlen(\"--promisor=\");\n \t\t\t} else if (starts_with(arg, \"--threads=\")) {\n \t\t\t\tchar *end;\n \t\t\t\tnr_threads = strtoul(arg+10, &end, 0);\n@@ -1803,7 +1813,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \tif (!verify)\n \t\tfinal(pack_name, curr_pack,\n \t\t      index_name, curr_index,\n-\t\t      keep_msg,\n+\t\t      keep_msg, promisor_msg,\n \t\t      pack_sha1);\n \telse\n \t\tclose(input_fd);\ndiff --git a/fetch-object.c b/fetch-object.c\nnew file mode 100644\nindex 000000000..f89dbba75\n--- /dev/null\n+++ b/fetch-object.c\n@@ -0,0 +1,23 @@\n+#include \"cache.h\"\n+#include \"packfile.h\"\n+#include \"pkt-line.h\"\n+#include \"strbuf.h\"\n+#include \"transport.h\"\n+\n+void fetch_object(const char *remote_name, const unsigned char *sha1)\n+{\n+\tstruct remote *remote;\n+\tstruct transport *transport;\n+\tstruct ref *ref;\n+\n+\tremote = remote_get(remote_name);\n+\tif (!remote->url[0])\n+\t\tdie(_(\"Remote with no URL\"));\n+\ttransport = transport_get(remote, remote->url[0]);\n+\n+\tref = alloc_ref(sha1_to_hex(sha1));\n+\thashcpy(ref->old_oid.hash, sha1);\n+\ttransport_set_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n+\ttransport_set_option(transport, TRANS_OPT_NO_HAVES, \"1\");\n+\ttransport_fetch_refs(transport, ref);\n+}\ndiff --git a/fetch-object.h b/fetch-object.h\nnew file mode 100644\nindex 000000000..f371300c8\n--- /dev/null\n+++ b/fetch-object.h\n@@ -0,0 +1,6 @@\n+#ifndef FETCH_OBJECT_H\n+#define FETCH_OBJECT_H\n+\n+extern void fetch_object(const char *remote_name, const unsigned char *sha1);\n+\n+#endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 105506e9a..d376c4ef1 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -450,6 +450,8 @@ static int find_common(struct fetch_pack_args *args,\n \n \tflushes = 0;\n \tretval = -1;\n+\tif (args->no_haves)\n+\t\tgoto done;\n \twhile ((oid = get_rev())) {\n \t\tpacket_buf_write(&req_buf, \"have %s\\n\", oid_to_hex(oid));\n \t\tprint_verbose(args, \"have %s\", oid_to_hex(oid));\n@@ -832,7 +834,7 @@ static int get_pack(struct fetch_pack_args *args,\n \t\targv_array_push(&cmd.args, alternate_shallow_file);\n \t}\n \n-\tif (do_keep) {\n+\tif (do_keep || args->from_promisor) {\n \t\tif (pack_lockfile)\n \t\t\tcmd.out = -1;\n \t\tcmd_name = \"index-pack\";\n@@ -842,7 +844,7 @@ static int get_pack(struct fetch_pack_args *args,\n \t\t\targv_array_push(&cmd.args, \"-v\");\n \t\tif (args->use_thin_pack)\n \t\t\targv_array_push(&cmd.args, \"--fix-thin\");\n-\t\tif (args->lock_pack || unpack_limit) {\n+\t\tif (do_keep && (args->lock_pack || unpack_limit)) {\n \t\t\tchar hostname[HOST_NAME_MAX + 1];\n \t\t\tif (xgethostname(hostname, sizeof(hostname)))\n \t\t\t\txsnprintf(hostname, sizeof(hostname), \"localhost\");\n@@ -852,6 +854,8 @@ static int get_pack(struct fetch_pack_args *args,\n \t\t}\n \t\tif (args->check_self_contained_and_connected)\n \t\t\targv_array_push(&cmd.args, \"--check-self-contained-and-connected\");\n+\t\tif (args->from_promisor)\n+\t\t\targv_array_push(&cmd.args, \"--promisor\");\n \t}\n \telse {\n \t\tcmd_name = \"unpack-objects\";\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b6aeb43a8..84904c348 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -29,6 +29,8 @@ struct fetch_pack_args {\n \tunsigned cloning:1;\n \tunsigned update_shallow:1;\n \tunsigned deepen:1;\n+\tunsigned from_promisor:1;\n+\tunsigned no_haves:1;\n };\n \n /*\ndiff --git a/remote-curl.c b/remote-curl.c\nindex 0053b0954..34a81b8d3 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -33,7 +33,9 @@ struct options {\n \t\tthin : 1,\n \t\t/* One of the SEND_PACK_PUSH_CERT_* constants. */\n \t\tpush_cert : 2,\n-\t\tdeepen_relative : 1;\n+\t\tdeepen_relative : 1,\n+\t\tfrom_promisor : 1,\n+\t\tno_haves : 1;\n };\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n@@ -157,6 +159,12 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n #endif /* LIBCURL_VERSION_NUM >= 0x070a08 */\n+\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t\toptions.from_promisor = 1;\n+\t\treturn 0;\n+\t} else if (!strcmp(name, \"no-haves\")) {\n+\t\toptions.no_haves = 1;\n+\t\treturn 0;\n \t} else {\n \t\treturn 1 /* unsupported */;\n \t}\n@@ -822,6 +830,10 @@ static int fetch_git(struct discovery *heads,\n \t\t\t\t options.deepen_not.items[i].string);\n \tif (options.deepen_relative && options.depth)\n \t\targv_array_push(&args, \"--deepen-relative\");\n+\tif (options.from_promisor)\n+\t\targv_array_push(&args, \"--from-promisor\");\n+\tif (options.no_haves)\n+\t\targv_array_push(&args, \"--no-haves\");\n \targv_array_push(&args, url.buf);\n \n \tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.c b/transport.c\nindex d75ff0514..fb46e33bf 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -161,6 +161,12 @@ static int set_git_option(struct git_transport_options *opts,\n \t} else if (!strcmp(name, TRANS_OPT_DEEPEN_RELATIVE)) {\n \t\topts->deepen_relative = !!value;\n \t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_FROM_PROMISOR)) {\n+\t\topts->from_promisor = !!value;\n+\t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_NO_HAVES)) {\n+\t\topts->no_haves = !!value;\n+\t\treturn 0;\n \t}\n \treturn 1;\n }\n@@ -229,6 +235,8 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\tdata->options.check_self_contained_and_connected;\n \targs.cloning = transport->cloning;\n \targs.update_shallow = data->options.update_shallow;\n+\targs.from_promisor = data->options.from_promisor;\n+\targs.no_haves = data->options.no_haves;\n \n \tif (!data->got_remote_heads) {\n \t\tconnect_setup(transport, 0);\ndiff --git a/transport.h b/transport.h\nindex bc5571574..67428f6df 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -15,6 +15,8 @@ struct git_transport_options {\n \tunsigned self_contained_and_connected : 1;\n \tunsigned update_shallow : 1;\n \tunsigned deepen_relative : 1;\n+\tunsigned from_promisor : 1;\n+\tunsigned no_haves : 1;\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -210,6 +212,12 @@ void transport_check_allowed(const char *type);\n /* Send push certificates */\n #define TRANS_OPT_PUSH_CERT \"pushcert\"\n \n+/* Indicate that these objects are being fetched by a promisor */\n+#define TRANS_OPT_FROM_PROMISOR \"from-promisor\"\n+\n+/* Do not send \"have\" lines */\n+#define TRANS_OPT_NO_HAVES \"no-haves\"\n+\n /**\n  * Returns 0 if the option was used, non-zero otherwise. Prints a\n  * message to stderr if the option is not used.\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329196","messageId":"5a9242024013345d7a3b0f63580360cdc8cc1c43.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 07/18] sha1_file: support lazily fetching missing objects","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:43Z","receivedAt":"2017-09-29T20:12:32Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach sha1_file to fetch objects from the remote configured in\nextensions.partialClone whenever an object is requested but missing.\n\nThe fetching of objects can be suppressed through a global variable.\nThis is used by fsck and index-pack.\n\nHowever, by default, such fetching is not suppressed. This is meant as a\ntemporary measure to ensure that all Git commands work in such a\nsituation. Future patches will update some commands to either tolerate\nmissing objects (without fetching them) or be more efficient in fetching\nthem.\n\nIn order to determine the code changes in sha1_file.c necessary, I\ninvestigated the following:\n (1) functions in sha1_file.c that take in a hash, without the user\n     regarding how the object is stored (loose or packed)\n (2) functions in packfile.c (because I need to check callers that know\n     about the loose/packed distinction and operate on both differently,\n     and ensure that they can handle the concept of objects that are\n     neither loose nor packed)\n\n(1) is handled by the modification to sha1_object_info_extended().\n\nFor (2), I looked at for_each_packed_object and others.  For\nfor_each_packed_object, the callers either already work or are fixed in\nthis patch:\n - reachable - only to find recent objects\n - builtin/fsck - already knows about missing objects\n - builtin/cat-file - warning message added in this commit\n\nCallers of the other functions do not need to be changed:\n - parse_pack_index\n   - http - indirectly from http_get_info_packs\n   - find_pack_entry_one\n     - this searches a single pack that is provided as an argument; the\n       caller already knows (through other means) that the sought object\n       is in a specific pack\n - find_sha1_pack\n   - fast-import - appears to be an optimization to not store a file if\n     it is already in a pack\n   - http-walker - to search through a struct alt_base\n   - http-push - to search through remote packs\n - has_sha1_pack\n   - builtin/fsck - already knows about promisor objects\n   - builtin/count-objects - informational purposes only (check if loose\n     object is also packed)\n   - builtin/prune-packed - check if object to be pruned is packed (if\n     not, don't prune it)\n   - revision - used to exclude packed objects if requested by user\n   - diff - just for optimization\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/cat-file.c       |  2 ++\n builtin/fetch-pack.c     |  2 ++\n builtin/fsck.c           |  3 +++\n builtin/index-pack.c     |  6 ++++++\n cache.h                  |  8 ++++++++\n fetch-object.c           |  3 +++\n sha1_file.c              | 38 ++++++++++++++++++++++++------------\n t/t0410-partial-clone.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++++\n 8 files changed, 100 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 4ccbfaac3..7aa1159ce 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -474,6 +474,8 @@ static int batch_objects(struct batch_options *opt)\n \n \t\tfor_each_loose_object(batch_loose_object, &sa, 0);\n \t\tfor_each_packed_object(batch_packed_object, &sa, 0);\n+\t\tif (repository_format_partial_clone)\n+\t\t\twarning(\"This repository has extensions.partialClone set. Some objects may not be loaded.\");\n \n \t\tcb.opt = opt;\n \t\tcb.expand = &data;\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 9f303cf98..9a7ebf6e9 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -53,6 +53,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tstruct oid_array shallow = OID_ARRAY_INIT;\n \tstruct string_list deepen_not = STRING_LIST_INIT_DUP;\n \n+\tfetch_if_missing = 0;\n+\n \tpacket_trace_identity(\"fetch-pack\");\n \n \tmemset(&args, 0, sizeof(args));\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex f6cb4d755..155ca663b 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -683,6 +683,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \tint i;\n \tstruct alternate_object_database *alt;\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\tfetch_if_missing = 0;\n+\n \terrors_found = 0;\n \tcheck_replace_refs = 0;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 14ebb2f17..440558046 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1657,6 +1657,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \tunsigned foreign_nr = 1;\t/* zero is a \"good\" value, assume bad */\n \tint report_end_of_input = 0;\n \n+\t/*\n+\t * index-pack never needs to fetch missing objects, since it only\n+\t * accesses the repo to do hash collision checks\n+\t */\n+\tfetch_if_missing = 0;\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(index_pack_usage);\n \ndiff --git a/cache.h b/cache.h\nindex 437206d06..58d3f8986 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1721,6 +1721,14 @@ struct object_info {\n #define OBJECT_INFO_QUICK 8\n extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);\n \n+/*\n+ * Set this to 0 to prevent sha1_object_info_extended() from fetching missing\n+ * blobs. This has a difference only if extensions.partialClone is set.\n+ *\n+ * Its default value is 1.\n+ */\n+extern int fetch_if_missing;\n+\n /* Dumb servers support */\n extern int update_server_info(int);\n \ndiff --git a/fetch-object.c b/fetch-object.c\nindex f89dbba75..369b61c0e 100644\n--- a/fetch-object.c\n+++ b/fetch-object.c\n@@ -9,7 +9,9 @@ void fetch_object(const char *remote_name, const unsigned char *sha1)\n \tstruct remote *remote;\n \tstruct transport *transport;\n \tstruct ref *ref;\n+\tint original_fetch_if_missing = fetch_if_missing;\n \n+\tfetch_if_missing = 0;\n \tremote = remote_get(remote_name);\n \tif (!remote->url[0])\n \t\tdie(_(\"Remote with no URL\"));\n@@ -20,4 +22,5 @@ void fetch_object(const char *remote_name, const unsigned char *sha1)\n \ttransport_set_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n \ttransport_set_option(transport, TRANS_OPT_NO_HAVES, \"1\");\n \ttransport_fetch_refs(transport, ref);\n+\tfetch_if_missing = original_fetch_if_missing;\n }\ndiff --git a/sha1_file.c b/sha1_file.c\nindex b4a67bb83..77aa0ffdf 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -29,6 +29,7 @@\n #include \"mergesort.h\"\n #include \"quote.h\"\n #include \"packfile.h\"\n+#include \"fetch-object.h\"\n \n const unsigned char null_sha1[GIT_MAX_RAWSZ];\n const struct object_id null_oid;\n@@ -1149,6 +1150,8 @@ static int sha1_loose_object_info(const unsigned char *sha1,\n \treturn (status < 0) ? status : 0;\n }\n \n+int fetch_if_missing = 1;\n+\n int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi, unsigned flags)\n {\n \tstatic struct object_info blank_oi = OBJECT_INFO_INIT;\n@@ -1157,6 +1160,7 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,\n \tconst unsigned char *real = (flags & OBJECT_INFO_LOOKUP_REPLACE) ?\n \t\t\t\t    lookup_replace_object(sha1) :\n \t\t\t\t    sha1;\n+\tint already_retried = 0;\n \n \tif (!oi)\n \t\toi = &blank_oi;\n@@ -1181,28 +1185,36 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,\n \t\t}\n \t}\n \n-\tif (!find_pack_entry(real, &e)) {\n-\t\t/* Most likely it's a loose object. */\n-\t\tif (!sha1_loose_object_info(real, oi, flags))\n-\t\t\treturn 0;\n+retry:\n+\tif (find_pack_entry(real, &e))\n+\t\tgoto found_packed;\n \n-\t\t/* Not a loose object; someone else may have just packed it. */\n-\t\tif (flags & OBJECT_INFO_QUICK) {\n-\t\t\treturn -1;\n-\t\t} else {\n-\t\t\treprepare_packed_git();\n-\t\t\tif (!find_pack_entry(real, &e))\n-\t\t\t\treturn -1;\n-\t\t}\n+\t/* Most likely it's a loose object. */\n+\tif (!sha1_loose_object_info(real, oi, flags))\n+\t\treturn 0;\n+\n+\t/* Not a loose object; someone else may have just packed it. */\n+\treprepare_packed_git();\n+\tif (find_pack_entry(real, &e))\n+\t\tgoto found_packed;\n+\n+\t/* Check if it is a missing object */\n+\tif (fetch_if_missing && repository_format_partial_clone &&\n+\t    !already_retried) {\n+\t\tfetch_object(repository_format_partial_clone, real);\n+\t\talready_retried = 1;\n+\t\tgoto retry;\n \t}\n \n+\treturn -1;\n+\n+found_packed:\n \tif (oi == &blank_oi)\n \t\t/*\n \t\t * We know that the caller doesn't actually need the\n \t\t * information below, so return early.\n \t\t */\n \t\treturn 0;\n-\n \trtype = packed_object_info(e.p, e.offset, oi);\n \tif (rtype < 0) {\n \t\tmark_bad_packed_object(e.p, real);\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex e96f436b0..8a90f6ab3 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -138,4 +138,55 @@ test_expect_success 'missing CLI object, but promised, passes fsck' '\n \tgit -C repo fsck \"$A\"\n '\n \n+test_expect_success 'fetching of missing objects' '\n+\trm -rf repo &&\n+\ttest_create_repo server &&\n+\ttest_commit -C server foo &&\n+\tgit -C server repack -a -d --write-bitmap-index &&\n+\n+\tgit clone \"file://$(pwd)/server\" repo &&\n+\tHASH=$(git -C repo rev-parse foo) &&\n+\trm -rf repo/.git/objects/* &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"origin\" &&\n+\tgit -C repo cat-file -p \"$HASH\" &&\n+\n+\t# Ensure that the .promisor file is written, and check that its\n+\t# associated packfile contains the object\n+\tls repo/.git/objects/pack/pack-*.promisor >promisorlist &&\n+\ttest_line_count = 1 promisorlist &&\n+\tIDX=$(cat promisorlist | sed \"s/promisor$/idx/\") &&\n+\tgit verify-pack --verbose \"$IDX\" | grep \"$HASH\"\n+'\n+\n+LIB_HTTPD_PORT=12345  # default port, 410, cannot be used as non-root\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'fetching of missing objects from an HTTP server' '\n+\trm -rf repo &&\n+\tSERVER=\"$HTTPD_DOCUMENT_ROOT_PATH/server\" &&\n+\ttest_create_repo \"$SERVER\" &&\n+\ttest_commit -C \"$SERVER\" foo &&\n+\tgit -C \"$SERVER\" repack -a -d --write-bitmap-index &&\n+\n+\tgit clone $HTTPD_URL/smart/server repo &&\n+\tHASH=$(git -C repo rev-parse foo) &&\n+\trm -rf repo/.git/objects/* &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"origin\" &&\n+\tgit -C repo cat-file -p \"$HASH\" &&\n+\n+\t# Ensure that the .promisor file is written, and check that its\n+\t# associated packfile contains the object\n+\tls repo/.git/objects/pack/pack-*.promisor >promisorlist &&\n+\ttest_line_count = 1 promisorlist &&\n+\tIDX=$(cat promisorlist | sed \"s/promisor$/idx/\") &&\n+\tgit verify-pack --verbose \"$IDX\" | grep \"$HASH\"\n+'\n+\n+stop_httpd\n+\n test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329197","messageId":"bfad85445506615386ca49c2129bc27c4156c568.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 08/18] rev-list: support termination at promisor objects","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:44Z","receivedAt":"2017-09-29T20:12:35Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach rev-list to support termination of an object traversal at any\nobject from a promisor remote (whether one that the local repo also has,\nor one that the local repo knows about because it has another promisor\nobject that references it).\n\nThis will be used subsequently in gc and in the connectivity check used\nby fetch.\n\nFor efficiency, if an object is referenced by a promisor object, and is\nin the local repo only as a non-promisor object, object traversal will\nnot stop there. This is to avoid building the list of promisor object\nreferences.\n\n(In list-objects.c, the case where obj is NULL in process_blob() and\nprocess_tree() do not need to be changed because those happen only when\nthere is a conflict between the expected type and the existing object.\nIf the object doesn't exist, an object will be synthesized, which is\nfine.)\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/rev-list.c       |  13 ++++++\n list-objects.c           |  16 +++++++-\n object.c                 |   2 +-\n revision.c               |  33 +++++++++++++++-\n revision.h               |   5 ++-\n t/t0410-partial-clone.sh | 101 +++++++++++++++++++++++++++++++++++++++++++++++\n 6 files changed, 165 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex c1c74d4a7..bba800cb9 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -12,6 +12,7 @@\n #include \"bisect.h\"\n #include \"progress.h\"\n #include \"reflog-walk.h\"\n+#include \"packfile.h\"\n \n static const char rev_list_usage[] =\n \"git rev-list [OPTION] <commit-id>... [ -- paths... ]\\n\"\n@@ -287,6 +288,18 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)\n \tinit_revisions(&revs, prefix);\n \trevs.abbrev = DEFAULT_ABBREV;\n \trevs.commit_format = CMIT_FMT_UNSPECIFIED;\n+\n+\t/*\n+\t * Scan the argument list before invoking setup_revisions(), so that we\n+\t * know if fetch_if_missing needs to be set to 0.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--exclude-promisor-objects\")) {\n+\t\t\tfetch_if_missing = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n \targc = setup_revisions(argc, argv, &revs, NULL);\n \n \tmemset(&info, 0, sizeof(info));\ndiff --git a/list-objects.c b/list-objects.c\nindex b3931fa43..8a98192dd 100644\n--- a/list-objects.c\n+++ b/list-objects.c\n@@ -7,6 +7,7 @@\n #include \"tree-walk.h\"\n #include \"revision.h\"\n #include \"list-objects.h\"\n+#include \"packfile.h\"\n \n static void process_blob(struct rev_info *revs,\n \t\t\t struct blob *blob,\n@@ -24,6 +25,13 @@ static void process_blob(struct rev_info *revs,\n \t\tdie(\"bad blob object\");\n \tif (obj->flags & (UNINTERESTING | SEEN))\n \t\treturn;\n+\tif (!has_object_file(&obj->oid)) {\n+\t\tif (revs->exclude_promisor_objects &&\n+\t\t    is_promisor_object(&obj->oid)) {\n+\t\t\treturn;\n+\t\t}\n+\t\t/* error message will be reported later */\n+\t}\n \tobj->flags |= SEEN;\n \n \tpathlen = path->len;\n@@ -77,6 +85,8 @@ static void process_tree(struct rev_info *revs,\n \tenum interesting match = revs->diffopt.pathspec.nr == 0 ?\n \t\tall_entries_interesting: entry_not_interesting;\n \tint baselen = base->len;\n+\tint gently = revs->ignore_missing_links ||\n+\t\t     revs->exclude_promisor_objects;\n \n \tif (!revs->tree_objects)\n \t\treturn;\n@@ -84,9 +94,13 @@ static void process_tree(struct rev_info *revs,\n \t\tdie(\"bad tree object\");\n \tif (obj->flags & (UNINTERESTING | SEEN))\n \t\treturn;\n-\tif (parse_tree_gently(tree, revs->ignore_missing_links) < 0) {\n+\tif (parse_tree_gently(tree, gently) < 0) {\n \t\tif (revs->ignore_missing_links)\n \t\t\treturn;\n+\t\tif (revs->exclude_promisor_objects &&\n+\t\t    is_promisor_object(&obj->oid)) {\n+\t\t\treturn;\n+\t\t}\n \t\tdie(\"bad tree object %s\", oid_to_hex(&obj->oid));\n \t}\n \ndiff --git a/object.c b/object.c\nindex 321d7e920..0f2490145 100644\n--- a/object.c\n+++ b/object.c\n@@ -252,7 +252,7 @@ struct object *parse_object(const struct object_id *oid)\n \tif (obj && obj->parsed)\n \t\treturn obj;\n \n-\tif ((obj && obj->type == OBJ_BLOB) ||\n+\tif ((obj && obj->type == OBJ_BLOB && has_object_file(oid)) ||\n \t    (!obj && has_object_file(oid) &&\n \t     sha1_object_info(oid->hash, NULL) == OBJ_BLOB)) {\n \t\tif (check_sha1_signature(repl, NULL, 0, NULL) < 0) {\ndiff --git a/revision.c b/revision.c\nindex f9a90d71d..f92d03bfe 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -197,6 +197,8 @@ static struct object *get_reference(struct rev_info *revs, const char *name,\n \tif (!object) {\n \t\tif (revs->ignore_missing)\n \t\t\treturn object;\n+\t\tif (revs->exclude_promisor_objects && is_promisor_object(oid))\n+\t\t\treturn NULL;\n \t\tdie(\"bad object %s\", name);\n \t}\n \tobject->flags |= flags;\n@@ -789,9 +791,17 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,\n \n \tfor (parent = commit->parents; parent; parent = parent->next) {\n \t\tstruct commit *p = parent->item;\n-\n-\t\tif (parse_commit_gently(p, revs->ignore_missing_links) < 0)\n+\t\tint gently = revs->ignore_missing_links ||\n+\t\t\t     revs->exclude_promisor_objects;\n+\t\tif (parse_commit_gently(p, gently) < 0) {\n+\t\t\tif (revs->exclude_promisor_objects &&\n+\t\t\t    is_promisor_object(&p->object.oid)) {\n+\t\t\t\tif (revs->first_parent_only)\n+\t\t\t\t\tbreak;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\treturn -1;\n+\t\t}\n \t\tif (revs->show_source && !p->util)\n \t\t\tp->util = commit->util;\n \t\tp->object.flags |= left_flag;\n@@ -2103,6 +2113,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->limited = 1;\n \t} else if (!strcmp(arg, \"--ignore-missing\")) {\n \t\trevs->ignore_missing = 1;\n+\t} else if (!strcmp(arg, \"--exclude-promisor-objects\")) {\n+\t\tif (fetch_if_missing)\n+\t\t\tdie(\"BUG: exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n+\t\trevs->exclude_promisor_objects = 1;\n \t} else {\n \t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);\n \t\tif (!opts)\n@@ -2848,6 +2862,16 @@ void reset_revision_walk(void)\n \tclear_object_flags(SEEN | ADDED | SHOWN);\n }\n \n+static int mark_uninteresting(const struct object_id *oid,\n+\t\t\t      struct packed_git *pack,\n+\t\t\t      uint32_t pos,\n+\t\t\t      void *unused)\n+{\n+\tstruct object *o = parse_object(oid);\n+\to->flags |= UNINTERESTING | SEEN;\n+\treturn 0;\n+}\n+\n int prepare_revision_walk(struct rev_info *revs)\n {\n \tint i;\n@@ -2876,6 +2900,11 @@ int prepare_revision_walk(struct rev_info *revs)\n \t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n+\tif (revs->exclude_promisor_objects) {\n+\t\tfor_each_packed_object(mark_uninteresting, NULL,\n+\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t}\n+\n \tif (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)\n \t\tcommit_list_sort_by_date(&revs->commits);\n \tif (revs->no_walk)\ndiff --git a/revision.h b/revision.h\nindex 3a3d3e2cf..341711ab7 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -121,7 +121,10 @@ struct rev_info {\n \t\t\tbisect:1,\n \t\t\tancestry_path:1,\n \t\t\tfirst_parent_only:1,\n-\t\t\tline_level_traverse:1;\n+\t\t\tline_level_traverse:1,\n+\n+\t\t\t/* for internal use only */\n+\t\t\texclude_promisor_objects:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex 8a90f6ab3..3ca6af5cd 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -160,6 +160,107 @@ test_expect_success 'fetching of missing objects' '\n \tgit verify-pack --verbose \"$IDX\" | grep \"$HASH\"\n '\n \n+test_expect_success 'rev-list stops traversal at missing and promised commit' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo foo &&\n+\ttest_commit -C repo bar &&\n+\n+\tFOO=$(git -C repo rev-parse foo) &&\n+\tpromise_and_delete \"$FOO\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo rev-list --exclude-promisor-objects --objects bar >out &&\n+\tgrep $(git -C repo rev-parse bar) out &&\n+\t! grep $FOO out\n+'\n+\n+test_expect_success 'rev-list stops traversal at missing and promised tree' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo foo &&\n+\tmkdir repo/a_dir &&\n+\techo something >repo/a_dir/something &&\n+\tgit -C repo add a_dir/something &&\n+\tgit -C repo commit -m bar &&\n+\n+\t# foo^{tree} (tree referenced from commit)\n+\tTREE=$(git -C repo rev-parse foo^{tree}) &&\n+\n+\t# a tree referenced by HEAD^{tree} (tree referenced from tree)\n+\tTREE2=$(git -C repo ls-tree HEAD^{tree} | grep \" tree \" | head -1 | cut -b13-52) &&\n+\n+\tpromise_and_delete \"$TREE\" &&\n+\tpromise_and_delete \"$TREE2\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&\n+\tgrep $(git -C repo rev-parse foo) out &&\n+\t! grep $TREE out &&\n+\tgrep $(git -C repo rev-parse HEAD) out &&\n+\t! grep $TREE2 out\n+'\n+\n+test_expect_success 'rev-list stops traversal at missing and promised blob' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\techo something >repo/something &&\n+\tgit -C repo add something &&\n+\tgit -C repo commit -m foo &&\n+\n+\tBLOB=$(git -C repo hash-object -w something) &&\n+\tpromise_and_delete \"$BLOB\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&\n+\tgrep $(git -C repo rev-parse HEAD) out &&\n+\t! grep $BLOB out\n+'\n+\n+test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo foo &&\n+\ttest_commit -C repo bar &&\n+\ttest_commit -C repo baz &&\n+\n+\tCOMMIT=$(git -C repo rev-parse foo) &&\n+\tTREE=$(git -C repo rev-parse bar^{tree}) &&\n+\tBLOB=$(git hash-object repo/baz.t) &&\n+\tprintf \"%s\\n%s\\n%s\\n\" $COMMIT $TREE $BLOB | pack_as_from_promisor &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&\n+\t! grep $COMMIT out &&\n+\t! grep $TREE out &&\n+\t! grep $BLOB out &&\n+\tgrep $(git -C repo rev-parse bar) out  # sanity check that some walking was done\n+'\n+\n+test_expect_success 'rev-list accepts missing and promised objects on command line' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo foo &&\n+\ttest_commit -C repo bar &&\n+\ttest_commit -C repo baz &&\n+\n+\tCOMMIT=$(git -C repo rev-parse foo) &&\n+\tTREE=$(git -C repo rev-parse bar^{tree}) &&\n+\tBLOB=$(git hash-object repo/baz.t) &&\n+\n+\tpromise_and_delete $COMMIT &&\n+\tpromise_and_delete $TREE &&\n+\tpromise_and_delete $BLOB &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo rev-list --exclude-promisor-objects --objects \"$COMMIT\" \"$TREE\" \"$BLOB\"\n+'\n+\n LIB_HTTPD_PORT=12345  # default port, 410, cannot be used as non-root\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n start_httpd\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329198","messageId":"9cb7d457b8c06905d1fdab25576d8e7e899c0cd4.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 02/18] fsck: support refs pointing to promisor objects","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:38Z","receivedAt":"2017-09-29T20:12:36Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach fsck to not treat refs referring to missing promisor objects as an\nerror when extensions.partialclone is set.\n\nFor the purposes of warning about no default refs, such refs are still\ntreated as legitimate refs.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/fsck.c           |  8 ++++++++\n t/t0410-partial-clone.sh | 24 ++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 97d1e621e..f1529527b 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -439,6 +439,14 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,\n \n \tobj = parse_object(oid);\n \tif (!obj) {\n+\t\tif (is_promisor_object(oid)) {\n+\t\t\t/*\n+\t\t\t * Increment default_refs anyway, because this is a\n+\t\t\t * valid ref.\n+\t\t\t */\n+\t\t\t default_refs++;\n+\t\t\t return 0;\n+\t\t}\n \t\terror(\"%s: invalid sha1 pointer %s\", refname, oid_to_hex(oid));\n \t\terrors_found |= ERROR_REACHABLE;\n \t\t/* We'll continue with the rest despite the error.. */\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex 3ddb3b98f..bf75162c1 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -13,6 +13,14 @@ pack_as_from_promisor () {\n \t>repo/.git/objects/pack/pack-$HASH.promisor\n }\n \n+promise_and_delete () {\n+\tHASH=$(git -C repo rev-parse \"$1\") &&\n+\tgit -C repo tag -a -m message my_annotated_tag \"$HASH\" &&\n+\tgit -C repo rev-parse my_annotated_tag | pack_as_from_promisor &&\n+\tgit -C repo tag -d my_annotated_tag &&\n+\tdelete_object repo \"$HASH\"\n+}\n+\n test_expect_success 'missing reflog object, but promised by a commit, passes fsck' '\n \ttest_create_repo repo &&\n \ttest_commit -C repo my_commit &&\n@@ -78,4 +86,20 @@ test_expect_success 'missing reflog object alone fails fsck, even with extension\n \ttest_must_fail git -C repo fsck\n '\n \n+test_expect_success 'missing ref object, but promised, passes fsck' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tA=$(git -C repo commit-tree -m a HEAD^{tree}) &&\n+\n+\t# Reference $A only from ref\n+\tgit -C repo branch my_branch \"$A\" &&\n+\tpromise_and_delete \"$A\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo fsck\n+'\n+\n test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329199","messageId":"2234882d889d72f7f8ed86850d9aed3c471cd5b8.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 13/18] fetch: refactor calculation of remote list","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:49Z","receivedAt":"2017-09-29T20:12:38Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Separate out the calculation of remotes to be fetched from and the\nactual fetching. This will allow us to include an additional step before\nthe actual fetching in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/fetch.c | 14 ++++++++------\n 1 file changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 225c73492..1b1f03923 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1322,7 +1322,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n-\tstruct remote *remote;\n+\tstruct remote *remote = NULL;\n \tint result = 0;\n \tstruct argv_array argv_gc_auto = ARGV_ARRAY_INIT;\n \n@@ -1367,17 +1367,14 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \t\telse if (argc > 1)\n \t\t\tdie(_(\"fetch --all does not make sense with refspecs\"));\n \t\t(void) for_each_remote(get_one_remote_for_fetch, &list);\n-\t\tresult = fetch_multiple(&list);\n \t} else if (argc == 0) {\n \t\t/* No arguments -- use default remote */\n \t\tremote = remote_get(NULL);\n-\t\tresult = fetch_one(remote, argc, argv);\n \t} else if (multiple) {\n \t\t/* All arguments are assumed to be remotes or groups */\n \t\tfor (i = 0; i < argc; i++)\n \t\t\tif (!add_remote_or_group(argv[i], &list))\n \t\t\t\tdie(_(\"No such remote or remote group: %s\"), argv[i]);\n-\t\tresult = fetch_multiple(&list);\n \t} else {\n \t\t/* Single remote or group */\n \t\t(void) add_remote_or_group(argv[0], &list);\n@@ -1385,14 +1382,19 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \t\t\t/* More than one remote */\n \t\t\tif (argc > 1)\n \t\t\t\tdie(_(\"Fetching a group and specifying refspecs does not make sense\"));\n-\t\t\tresult = fetch_multiple(&list);\n \t\t} else {\n \t\t\t/* Zero or one remotes */\n \t\t\tremote = remote_get(argv[0]);\n-\t\t\tresult = fetch_one(remote, argc-1, argv+1);\n+\t\t\targc--;\n+\t\t\targv++;\n \t\t}\n \t}\n \n+\tif (remote)\n+\t\tresult = fetch_one(remote, argc, argv);\n+\telse\n+\t\tresult = fetch_multiple(&list);\n+\n \tif (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {\n \t\tstruct argv_array options = ARGV_ARRAY_INIT;\n \n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329200","messageId":"73f9648b4c258eae1d02f91f12250ea139b72519.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 17/18] unpack-trees: batch fetching of missing blobs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:53Z","receivedAt":"2017-09-29T20:12:40Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When running checkout, first prefetch all blobs that are to be updated\nbut are missing. This means that only one pack is downloaded during such\noperations, instead of one per missing blob.\n\nThis operates only on the blob level - if a repository has a missing\ntree, they are still fetched one at a time.\n\nThis does not use the delayed checkout mechanism introduced in commit\n2841e8f (\"convert: add \"status=delayed\" to filter process protocol\",\n2017-06-30) due to significant conceptual differences - in particular,\nfor partial clones, we already know what needs to be fetched based on\nthe contents of the local repo alone, whereas for status=delayed, it is\nthe filter process that tells us what needs to be checked in the end.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n fetch-object.c   | 27 +++++++++++++++++++++++----\n fetch-object.h   |  5 +++++\n t/t5601-clone.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n unpack-trees.c   | 22 ++++++++++++++++++++++\n 4 files changed, 102 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object.c b/fetch-object.c\nindex 369b61c0e..21b4dfafc 100644\n--- a/fetch-object.c\n+++ b/fetch-object.c\n@@ -3,12 +3,12 @@\n #include \"pkt-line.h\"\n #include \"strbuf.h\"\n #include \"transport.h\"\n+#include \"fetch-object.h\"\n \n-void fetch_object(const char *remote_name, const unsigned char *sha1)\n+static void fetch_refs(const char *remote_name, struct ref *ref)\n {\n \tstruct remote *remote;\n \tstruct transport *transport;\n-\tstruct ref *ref;\n \tint original_fetch_if_missing = fetch_if_missing;\n \n \tfetch_if_missing = 0;\n@@ -17,10 +17,29 @@ void fetch_object(const char *remote_name, const unsigned char *sha1)\n \t\tdie(_(\"Remote with no URL\"));\n \ttransport = transport_get(remote, remote->url[0]);\n \n-\tref = alloc_ref(sha1_to_hex(sha1));\n-\thashcpy(ref->old_oid.hash, sha1);\n \ttransport_set_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n \ttransport_set_option(transport, TRANS_OPT_NO_HAVES, \"1\");\n \ttransport_fetch_refs(transport, ref);\n \tfetch_if_missing = original_fetch_if_missing;\n }\n+\n+void fetch_object(const char *remote_name, const unsigned char *sha1)\n+{\n+\tstruct ref *ref = alloc_ref(sha1_to_hex(sha1));\n+\thashcpy(ref->old_oid.hash, sha1);\n+\tfetch_refs(remote_name, ref);\n+}\n+\n+void fetch_objects(const char *remote_name, const struct oid_array *to_fetch)\n+{\n+\tstruct ref *ref = NULL;\n+\tint i;\n+\n+\tfor (i = 0; i < to_fetch->nr; i++) {\n+\t\tstruct ref *new_ref = alloc_ref(oid_to_hex(&to_fetch->oid[i]));\n+\t\toidcpy(&new_ref->old_oid, &to_fetch->oid[i]);\n+\t\tnew_ref->next = ref;\n+\t\tref = new_ref;\n+\t}\n+\tfetch_refs(remote_name, ref);\n+}\ndiff --git a/fetch-object.h b/fetch-object.h\nindex f371300c8..4b269d07e 100644\n--- a/fetch-object.h\n+++ b/fetch-object.h\n@@ -1,6 +1,11 @@\n #ifndef FETCH_OBJECT_H\n #define FETCH_OBJECT_H\n \n+#include \"sha1-array.h\"\n+\n extern void fetch_object(const char *remote_name, const unsigned char *sha1);\n \n+extern void fetch_objects(const char *remote_name,\n+\t\t\t  const struct oid_array *to_fetch);\n+\n #endif\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 951b1ffa8..0ca24c215 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -611,6 +611,58 @@ test_expect_success 'partial clone: warn if server does not support blob-max-byt\n \ttest_i18ngrep \"blob-max-bytes not recognized by server\" err\n '\n \n+test_expect_success 'batch missing blob request during checkout' '\n+\trm -rf server client &&\n+\n+\ttest_create_repo server &&\n+\techo a >server/a &&\n+\techo b >server/b &&\n+\tgit -C server add a b &&\n+\n+\tgit -C server commit -m x &&\n+\techo aa >server/a &&\n+\techo bb >server/b &&\n+\tgit -C server add a b &&\n+\tgit -C server commit -m x &&\n+\n+\ttest_config -C server uploadpack.advertiseblobmaxbytes 1 &&\n+\ttest_config -C server uploadpack.allowanysha1inwant 1 &&\n+\n+\tgit clone --blob-max-bytes=0 \"file://$(pwd)/server\" client &&\n+\n+\t# Ensure that there is only one negotiation by checking that there is\n+\t# only \"done\" line sent. (\"done\" marks the end of negotiation.)\n+\tGIT_TRACE_PACKET=\"$(pwd)/trace\" git -C client checkout HEAD^ &&\n+\tgrep \"git> done\" trace >done_lines &&\n+\ttest_line_count = 1 done_lines\n+'\n+\n+test_expect_success 'batch missing blob request does not inadvertently try to fetch gitlinks' '\n+\trm -rf server client &&\n+\n+\ttest_create_repo repo_for_submodule &&\n+\ttest_commit -C repo_for_submodule x &&\n+\n+\ttest_create_repo server &&\n+\techo a >server/a &&\n+\techo b >server/b &&\n+\tgit -C server add a b &&\n+\tgit -C server commit -m x &&\n+\n+\techo aa >server/a &&\n+\techo bb >server/b &&\n+\t# Also add a gitlink pointing to an arbitrary repository\n+\tgit -C server submodule add \"$(pwd)/repo_for_submodule\" c &&\n+\tgit -C server add a b c &&\n+\tgit -C server commit -m x &&\n+\n+\ttest_config -C server uploadpack.advertiseblobmaxbytes 1 &&\n+\ttest_config -C server uploadpack.allowanysha1inwant 1 &&\n+\n+\t# Make sure that it succeeds\n+\tgit clone --blob-max-bytes=0 \"file://$(pwd)/server\" client\n+'\n+\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n start_httpd\n \ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 71b70ccb1..73a1cdb43 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -14,6 +14,7 @@\n #include \"dir.h\"\n #include \"submodule.h\"\n #include \"submodule-config.h\"\n+#include \"fetch-object.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -369,6 +370,27 @@ static int check_updates(struct unpack_trees_options *o)\n \t\tload_gitmodules_file(index, &state);\n \n \tenable_delayed_checkout(&state);\n+\tif (repository_format_partial_clone && o->update && !o->dry_run) {\n+\t\t/*\n+\t\t * Prefetch the objects that are to be checked out in the loop\n+\t\t * below.\n+\t\t */\n+\t\tstruct oid_array to_fetch = OID_ARRAY_INIT;\n+\t\tint fetch_if_missing_store = fetch_if_missing;\n+\t\tfetch_if_missing = 0;\n+\t\tfor (i = 0; i < index->cache_nr; i++) {\n+\t\t\tstruct cache_entry *ce = index->cache[i];\n+\t\t\tif ((ce->ce_flags & CE_UPDATE) &&\n+\t\t\t    !S_ISGITLINK(ce->ce_mode)) {\n+\t\t\t\tif (!has_object_file(&ce->oid))\n+\t\t\t\t\toid_array_append(&to_fetch, &ce->oid);\n+\t\t\t}\n+\t\t}\n+\t\tif (to_fetch.nr)\n+\t\t\tfetch_objects(repository_format_partial_clone,\n+\t\t\t\t      &to_fetch);\n+\t\tfetch_if_missing = fetch_if_missing_store;\n+\t}\n \tfor (i = 0; i < index->cache_nr; i++) {\n \t\tstruct cache_entry *ce = index->cache[i];\n \n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329201","messageId":"9f0c6eb5aea49a5620de8e56fab2065805570f0e.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 15/18] clone: support excluding large blobs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:51Z","receivedAt":"2017-09-29T20:12:42Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach clone to support excluding large blobs through a blob-max-bytes\nparameter.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/clone.c  | 23 +++++++++++++++++++++--\n t/t5601-clone.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 70 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex dbddd98f8..4c2193dc4 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -60,6 +60,7 @@ static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;\n static int option_dissociate;\n static int max_jobs = -1;\n static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;\n+static char *blob_max_bytes;\n \n static int recurse_submodules_cb(const struct option *opt,\n \t\t\t\t const char *arg, int unset)\n@@ -135,6 +136,8 @@ static struct option builtin_clone_options[] = {\n \t\t\tTRANSPORT_FAMILY_IPV4),\n \tOPT_SET_INT('6', \"ipv6\", &family, N_(\"use IPv6 addresses only\"),\n \t\t\tTRANSPORT_FAMILY_IPV6),\n+\tOPT_STRING(0, \"blob-max-bytes\", &blob_max_bytes, N_(\"bytes\"),\n+\t\t   N_(\"do not fetch blobs above this size\")),\n \tOPT_END()\n };\n \n@@ -886,6 +889,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstruct refspec *refspec;\n \tconst char *fetch_pattern;\n \n+\tfetch_if_missing = 0;\n+\n \tpacket_trace_identity(\"clone\");\n \targc = parse_options(argc, argv, prefix, builtin_clone_options,\n \t\t\t     builtin_clone_usage, 0);\n@@ -1104,7 +1109,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport_set_option(transport, TRANS_OPT_UPLOADPACK,\n \t\t\t\t     option_upload_pack);\n \n-\tif (transport->smart_options && !deepen)\n+\tif (blob_max_bytes) {\n+\t\ttransport_set_option(transport, TRANS_OPT_BLOB_MAX_BYTES,\n+\t\t\t\t     blob_max_bytes);\n+\t\ttransport_set_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n+\t}\n+\n+\tif (transport->smart_options && !deepen && !blob_max_bytes)\n \t\ttransport->smart_options->check_self_contained_and_connected = 1;\n \n \trefs = transport_get_remote_refs(transport);\n@@ -1164,13 +1175,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \twrite_refspec_config(src_ref_prefix, our_head_points_at,\n \t\t\tremote_head_points_at, &branch_top);\n \n+\tif (blob_max_bytes) {\n+\t\tgit_config_set(\"core.repositoryformatversion\", \"1\");\n+\t\tgit_config_set(\"extensions.partialclone\", \"origin\");\n+\t\trepository_format_partial_clone = \"origin\";\n+\t}\n+\n \tif (is_local)\n \t\tclone_local(path, git_dir);\n \telse if (refs && complete_refs_before_fetch)\n \t\ttransport_fetch_refs(transport, mapped_refs);\n \n \tupdate_remote_refs(refs, mapped_refs, remote_head_points_at,\n-\t\t\t   branch_top.buf, reflog_msg.buf, transport, !is_local);\n+\t\t\t   branch_top.buf, reflog_msg.buf, transport,\n+\t\t\t   !is_local && !blob_max_bytes);\n \n \tupdate_head(our_head_points_at, remote_head, reflog_msg.buf);\n \n@@ -1191,6 +1209,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t}\n \n \tjunk_mode = JUNK_LEAVE_REPO;\n+\tfetch_if_missing = 1;\n \terr = checkout(submodule_progress);\n \n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 9c56f771b..951b1ffa8 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -571,4 +571,53 @@ test_expect_success 'GIT_TRACE_PACKFILE produces a usable pack' '\n \tgit -C replay.git index-pack -v --stdin <tmp.pack\n '\n \n+partial_clone () {\n+\tSERVER=\"$1\" &&\n+\tURL=\"$2\" &&\n+\n+\trm -rf \"$SERVER\" client &&\n+\ttest_create_repo \"$SERVER\" &&\n+\ttest_commit -C \"$SERVER\" one &&\n+\tHASH1=$(git hash-object \"$SERVER/one.t\") &&\n+\tgit -C \"$SERVER\" revert HEAD &&\n+\ttest_commit -C \"$SERVER\" two &&\n+\tHASH2=$(git hash-object \"$SERVER/two.t\") &&\n+\ttest_config -C \"$SERVER\" uploadpack.advertiseblobmaxbytes 1 &&\n+\ttest_config -C \"$SERVER\" uploadpack.allowanysha1inwant 1 &&\n+\n+\tgit clone --blob-max-bytes=0 \"$URL\" client &&\n+\n+\tgit -C client fsck &&\n+\n+\t# Ensure that unneeded blobs are not inadvertently fetched.\n+\ttest_config -C client extensions.partialclone \"not a remote\" &&\n+\ttest_must_fail git -C client cat-file -e \"$HASH1\" &&\n+\n+\t# But this blob was fetched, because clone performs an initial checkout\n+\tgit -C client cat-file -e \"$HASH2\"\n+}\n+\n+test_expect_success 'partial clone' '\n+\tpartial_clone server \"file://$(pwd)/server\"\n+'\n+\n+test_expect_success 'partial clone: warn if server does not support blob-max-bytes' '\n+\trm -rf server client &&\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\n+\tgit clone --blob-max-bytes=0 \"file://$(pwd)/server\" client 2> err &&\n+\n+\ttest_i18ngrep \"blob-max-bytes not recognized by server\" err\n+'\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'partial clone using HTTP' '\n+\tpartial_clone \"$HTTPD_DOCUMENT_ROOT_PATH/server\" \"$HTTPD_URL/smart/server\"\n+'\n+\n+stop_httpd\n+\n test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329202","messageId":"ca676ac57279ff21c23b5fe0bb3391845b166f20.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 14/18] fetch: support excluding large blobs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:50Z","receivedAt":"2017-09-29T20:12:44Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach fetch to support excluding large blobs through a blob-max-bytes\nparameter. This is only allowed for the remote configured in\nextensions.partialclone.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/fetch.c       | 22 ++++++++++++++++++++--\n connected.c           |  1 +\n remote-curl.c         |  7 +++++++\n t/t5500-fetch-pack.sh | 36 ++++++++++++++++++++++++++++++++++++\n transport-helper.c    |  4 ++++\n transport.c           | 10 ++++++++++\n transport.h           |  4 ++++\n 7 files changed, 82 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 1b1f03923..07beaf5b5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -55,6 +55,7 @@ static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND;\n static int shown_url = 0;\n static int refmap_alloc, refmap_nr;\n static const char **refmap_array;\n+static const char *blob_max_bytes;\n \n static int git_fetch_config(const char *k, const char *v, void *cb)\n {\n@@ -160,6 +161,8 @@ static struct option builtin_fetch_options[] = {\n \t\t\tTRANSPORT_FAMILY_IPV4),\n \tOPT_SET_INT('6', \"ipv6\", &family, N_(\"use IPv6 addresses only\"),\n \t\t\tTRANSPORT_FAMILY_IPV6),\n+\tOPT_STRING(0, \"blob-max-bytes\", &blob_max_bytes, N_(\"bytes\"),\n+\t\t   N_(\"do not fetch blobs above this size\")),\n \tOPT_END()\n };\n \n@@ -1044,6 +1047,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)\n \t\tset_option(transport, TRANS_OPT_DEEPEN_RELATIVE, \"yes\");\n \tif (update_shallow)\n \t\tset_option(transport, TRANS_OPT_UPDATE_SHALLOW, \"yes\");\n+\tif (blob_max_bytes) {\n+\t\tset_option(transport, TRANS_OPT_BLOB_MAX_BYTES, blob_max_bytes);\n+\t\tset_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n+\t}\n \treturn transport;\n }\n \n@@ -1328,6 +1335,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \n \tpacket_trace_identity(\"fetch\");\n \n+\tfetch_if_missing = 0;\n+\n \t/* Record the command line for the reflog */\n \tstrbuf_addstr(&default_rla, \"fetch\");\n \tfor (i = 1; i < argc; i++)\n@@ -1361,6 +1370,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tif (depth || deepen_since || deepen_not.nr)\n \t\tdeepen = 1;\n \n+\tif (blob_max_bytes && !repository_format_partial_clone)\n+\t\tdie(\"--blob-max-bytes can only be used when extensions.partialClone is set\");\n+\n \tif (all) {\n \t\tif (argc == 1)\n \t\t\tdie(_(\"fetch --all does not take a repository argument\"));\n@@ -1390,10 +1402,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (remote)\n+\tif (remote) {\n+\t\tif (blob_max_bytes &&\n+\t\t    strcmp(remote->name, repository_format_partial_clone))\n+\t\t\tdie(_(\"--blob-max-bytes can only be used with the remote configured in core.partialClone\"));\n \t\tresult = fetch_one(remote, argc, argv);\n-\telse\n+\t} else {\n+\t\tif (blob_max_bytes)\n+\t\t\tdie(_(\"--blob-max-bytes can only be used with the remote configured in core.partialClone\"));\n \t\tresult = fetch_multiple(&list);\n+\t}\n \n \tif (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {\n \t\tstruct argv_array options = ARGV_ARRAY_INIT;\ndiff --git a/connected.c b/connected.c\nindex f416b0505..a51c01d63 100644\n--- a/connected.c\n+++ b/connected.c\n@@ -56,6 +56,7 @@ int check_connected(sha1_iterate_fn fn, void *cb_data,\n \targv_array_push(&rev_list.args,\"rev-list\");\n \targv_array_push(&rev_list.args, \"--objects\");\n \targv_array_push(&rev_list.args, \"--stdin\");\n+\targv_array_push(&rev_list.args, \"--exclude-promisor-objects\");\n \targv_array_push(&rev_list.args, \"--not\");\n \targv_array_push(&rev_list.args, \"--all\");\n \targv_array_push(&rev_list.args, \"--quiet\");\ndiff --git a/remote-curl.c b/remote-curl.c\nindex 34a81b8d3..18fd184c1 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -24,6 +24,7 @@ struct options {\n \tchar *deepen_since;\n \tstruct string_list deepen_not;\n \tstruct string_list push_options;\n+\tchar *blob_max_bytes;\n \tunsigned progress : 1,\n \t\tcheck_self_contained_and_connected : 1,\n \t\tcloning : 1,\n@@ -165,6 +166,9 @@ static int set_option(const char *name, const char *value)\n \t} else if (!strcmp(name, \"no-haves\")) {\n \t\toptions.no_haves = 1;\n \t\treturn 0;\n+\t} else if (!strcmp(name, \"blob-max-bytes\")) {\n+\t\toptions.blob_max_bytes = xstrdup(value);\n+\t\treturn 0;\n \t} else {\n \t\treturn 1 /* unsupported */;\n \t}\n@@ -834,6 +838,9 @@ static int fetch_git(struct discovery *heads,\n \t\targv_array_push(&args, \"--from-promisor\");\n \tif (options.no_haves)\n \t\targv_array_push(&args, \"--no-haves\");\n+\tif (options.blob_max_bytes)\n+\t\targv_array_pushf(&args, \"--blob-max-bytes=%s\",\n+\t\t\t\t options.blob_max_bytes);\n \targv_array_push(&args, url.buf);\n \n \tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex 62e384230..b2682862f 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -782,4 +782,40 @@ test_expect_success '--blob-max-bytes has no effect if support for it is not adv\n \ttest_i18ngrep \"blob-max-bytes not recognized by server\" err\n '\n \n+fetch_blob_max_bytes () {\n+\tSERVER=\"$1\"\n+\tURL=\"$2\"\n+\n+\trm -rf \"$SERVER\" client &&\n+\ttest_create_repo \"$SERVER\" &&\n+\ttest_commit -C \"$SERVER\" one &&\n+\ttest_config -C \"$SERVER\" uploadpack.advertiseblobmaxbytes 1 &&\n+\n+\tgit clone \"$URL\" client &&\n+\ttest_config -C client extensions.partialclone origin &&\n+\n+\ttest_commit -C \"$SERVER\" two &&\n+\n+\tgit -C client fetch --blob-max-bytes=0 origin HEAD:somewhere &&\n+\n+\t# Ensure that commit is fetched, but blob is not\n+\ttest_config -C client extensions.partialclone \"arbitrary string\" &&\n+\tgit -C client cat-file -e $(git -C \"$SERVER\" rev-parse two) &&\n+\ttest_must_fail git -C client cat-file -e $(git hash-object \"$SERVER/two.t\")\n+}\n+\n+test_expect_success 'fetch with --blob-max-bytes' '\n+\tfetch_blob_max_bytes server server\n+'\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'fetch with --blob-max-bytes and HTTP' '\n+\tfetch_blob_max_bytes \"$HTTPD_DOCUMENT_ROOT_PATH/server\" \"$HTTPD_URL/smart/server\"\n+'\n+\n+stop_httpd\n+\n+\n test_done\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 42b960ff8..9c1874b70 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -672,6 +672,10 @@ static int fetch(struct transport *transport,\n \tif (data->transport_options.update_shallow)\n \t\tset_helper_option(transport, \"update-shallow\", \"true\");\n \n+\tif (data->transport_options.blob_max_bytes)\n+\t\tset_helper_option(transport, \"blob-max-bytes\",\n+\t\t\t\t  data->transport_options.blob_max_bytes);\n+\n \tif (data->fetch)\n \t\treturn fetch_with_fetch(transport, nr_heads, to_fetch);\n \ndiff --git a/transport.c b/transport.c\nindex fb46e33bf..6408af19e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -167,6 +167,9 @@ static int set_git_option(struct git_transport_options *opts,\n \t} else if (!strcmp(name, TRANS_OPT_NO_HAVES)) {\n \t\topts->no_haves = !!value;\n \t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_BLOB_MAX_BYTES)) {\n+\t\topts->blob_max_bytes = value;\n+\t\treturn 0;\n \t}\n \treturn 1;\n }\n@@ -237,6 +240,13 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.update_shallow = data->options.update_shallow;\n \targs.from_promisor = data->options.from_promisor;\n \targs.no_haves = data->options.no_haves;\n+\tif (data->options.blob_max_bytes) {\n+\t\tunsigned long *ptr = xmalloc(sizeof(*ptr));\n+\t\tif (!git_parse_ulong(data->options.blob_max_bytes, ptr))\n+\t\t\tdie(\"Invalid blob-max-bytes value: %s\",\n+\t\t\t    data->options.blob_max_bytes);\n+\t\targs.blob_max_bytes = ptr;\n+\t}\n \n \tif (!data->got_remote_heads) {\n \t\tconnect_setup(transport, 0);\ndiff --git a/transport.h b/transport.h\nindex 67428f6df..79d4442b1 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -23,6 +23,7 @@ struct git_transport_options {\n \tconst char *uploadpack;\n \tconst char *receivepack;\n \tstruct push_cas_option *cas;\n+\tconst char *blob_max_bytes;\n };\n \n enum transport_family {\n@@ -218,6 +219,9 @@ void transport_check_allowed(const char *type);\n /* Do not send \"have\" lines */\n #define TRANS_OPT_NO_HAVES \"no-haves\"\n \n+/* Exclude blobs above a certain size */\n+#define TRANS_OPT_BLOB_MAX_BYTES \"blob-max-bytes\"\n+\n /**\n  * Returns 0 if the option was used, non-zero otherwise. Prints a\n  * message to stderr if the option is not used.\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329203","messageId":"407a298b52a9e0a2ee4135fe844e35b9a14c0f7b.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 16/18] clone: configure blobmaxbytes in created repos","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:52Z","receivedAt":"2017-09-29T20:12:49Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach clone to configure blobmaxbytes in any repos that it generates\nwhen the --blob-max-bytes parameter is set. Also teach fetch to use this\nparameter.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/clone.c       |  1 +\n builtin/fetch.c       |  4 ++++\n remote.c              |  2 ++\n remote.h              |  2 ++\n t/t5500-fetch-pack.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++++-----\n 5 files changed, 67 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 4c2193dc4..58cbc8ae3 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1179,6 +1179,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set(\"core.repositoryformatversion\", \"1\");\n \t\tgit_config_set(\"extensions.partialclone\", \"origin\");\n \t\trepository_format_partial_clone = \"origin\";\n+\t\tgit_config_set(\"remote.origin.blobmaxbytes\", blob_max_bytes);\n \t}\n \n \tif (is_local)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 07beaf5b5..ace238554 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1050,6 +1050,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)\n \tif (blob_max_bytes) {\n \t\tset_option(transport, TRANS_OPT_BLOB_MAX_BYTES, blob_max_bytes);\n \t\tset_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n+\t} else if (remote->blob_max_bytes) {\n+\t\tset_option(transport, TRANS_OPT_BLOB_MAX_BYTES,\n+\t\t\t   remote->blob_max_bytes);\n+\t\tset_option(transport, TRANS_OPT_FROM_PROMISOR, \"1\");\n \t}\n \treturn transport;\n }\ndiff --git a/remote.c b/remote.c\nindex 411309006..eade3c312 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -440,6 +440,8 @@ static int handle_config(const char *key, const char *value, void *cb)\n \t\t\t\t\t key, value);\n \t} else if (!strcmp(subkey, \"vcs\")) {\n \t\treturn git_config_string(&remote->foreign_vcs, key, value);\n+\t} else if (!strcmp(subkey, \"blobmaxbytes\")) {\n+\t\treturn git_config_string(&remote->blob_max_bytes, key, value);\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex 2ecf4c8c7..3d56e62b7 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -56,6 +56,8 @@ struct remote {\n \t */\n \tchar *http_proxy;\n \tchar *http_proxy_authmethod;\n+\n+\tconst char *blob_max_bytes;\n };\n \n struct remote *remote_get(const char *name);\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex b2682862f..ee533ea32 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -782,9 +782,9 @@ test_expect_success '--blob-max-bytes has no effect if support for it is not adv\n \ttest_i18ngrep \"blob-max-bytes not recognized by server\" err\n '\n \n-fetch_blob_max_bytes () {\n-\tSERVER=\"$1\"\n-\tURL=\"$2\"\n+setup_blob_max_bytes () {\n+\tSERVER=\"$1\" &&\n+\tURL=\"$2\" &&\n \n \trm -rf \"$SERVER\" client &&\n \ttest_create_repo \"$SERVER\" &&\n@@ -794,7 +794,11 @@ fetch_blob_max_bytes () {\n \tgit clone \"$URL\" client &&\n \ttest_config -C client extensions.partialclone origin &&\n \n-\ttest_commit -C \"$SERVER\" two &&\n+\ttest_commit -C \"$SERVER\" two\n+}\n+\n+do_blob_max_bytes() {\n+\tSERVER=\"$1\" &&\n \n \tgit -C client fetch --blob-max-bytes=0 origin HEAD:somewhere &&\n \n@@ -805,14 +809,62 @@ fetch_blob_max_bytes () {\n }\n \n test_expect_success 'fetch with --blob-max-bytes' '\n-\tfetch_blob_max_bytes server server\n+\tsetup_blob_max_bytes server server &&\n+\tdo_blob_max_bytes server\n+'\n+\n+test_expect_success 'fetch respects configured blobmaxbytes' '\n+\tsetup_blob_max_bytes server server &&\n+\n+\ttest_config -C client remote.origin.blobmaxbytes 0 &&\n+\n+\tgit -C client fetch origin HEAD:somewhere &&\n+\n+\t# Ensure that commit is fetched, but blob is not\n+\ttest_config -C client extensions.partialclone \"arbitrary string\" &&\n+\tgit -C client cat-file -e $(git -C server rev-parse two) &&\n+\ttest_must_fail git -C client cat-file -e $(git hash-object server/two.t)\n+'\n+\n+test_expect_success 'pull respects configured blobmaxbytes' '\n+\tsetup_blob_max_bytes server server &&\n+\n+\t# Hide two.t from tip so that client does not load it upon the\n+\t# automatic checkout that pull performs\n+\tgit -C server rm two.t &&\n+\ttest_commit -C server three &&\n+\n+\ttest_config -C server uploadpack.allowanysha1inwant 1 &&\n+\ttest_config -C client remote.origin.blobmaxbytes 0 &&\n+\n+\tgit -C client pull origin &&\n+\n+\t# Ensure that commit is fetched, but blob is not\n+\ttest_config -C client extensions.partialclone \"arbitrary string\" &&\n+\tgit -C client cat-file -e $(git -C server rev-parse two) &&\n+\ttest_must_fail git -C client cat-file -e $(git hash-object server/two.t)\n+'\n+\n+test_expect_success 'clone configures blobmaxbytes' '\n+\trm -rf server client &&\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\ttest_commit -C server two &&\n+\ttest_config -C server uploadpack.allowanysha1inwant 1 &&\n+\n+\tgit clone --blob-max-bytes=12345 server client &&\n+\n+\t# Ensure that we can, for example, checkout HEAD^\n+\trm -rf client/.git/objects/* &&\n+\tgit -C client checkout HEAD^\n '\n \n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n start_httpd\n \n test_expect_success 'fetch with --blob-max-bytes and HTTP' '\n-\tfetch_blob_max_bytes \"$HTTPD_DOCUMENT_ROOT_PATH/server\" \"$HTTPD_URL/smart/server\"\n+\tsetup_blob_max_bytes \"$HTTPD_DOCUMENT_ROOT_PATH/server\" \"$HTTPD_URL/smart/server\" &&\n+\tdo_blob_max_bytes \"$HTTPD_DOCUMENT_ROOT_PATH/server\"\n '\n \n stop_httpd\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329204","messageId":"b88725476d9f13ba4381d85e5fe049f6ef93f621.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 18/18] fetch-pack: restore save_commit_buffer after use","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:54Z","receivedAt":"2017-09-29T20:12:51Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In fetch-pack, the global variable save_commit_buffer is set to 0, but\nnot restored to its original value after use.\n\nIn particular, if show_log() (in log-tree.c) is invoked after\nfetch_pack() in the same process, show_log() will return before printing\nout the commit message (because the invocation to\nget_cached_commit_buffer() returns NULL, because the commit buffer was\nnot saved). I discovered this when attempting to run \"git log -S\" in a\npartial clone, triggering the case where revision walking lazily loads\nmissing objects.\n\nTherefore, restore save_commit_buffer to its original value after use.\n\nAn alternative to solve the problem I had is to replace\nget_cached_commit_buffer() with get_commit_buffer(). That invocation was\nintroduced in commit a97934d (\"use get_cached_commit_buffer where\nappropriate\", 2014-06-13) to replace \"commit->buffer\" introduced in\ncommit 3131b71 (\"Add \"--show-all\" revision walker flag for debugging\",\n2008-02-13). In the latter commit, the commit author seems to be\ndeciding between not showing an unparsed commit at all and showing an\nunparsed commit without the message (which is what the commit does), and\ndid not mention parsing the unparsed commit, so I prefer to preserve the\nexisting behavior.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n fetch-pack.c | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 19b8e9322..8e6f54547 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -719,6 +719,7 @@ static int everything_local(struct fetch_pack_args *args,\n {\n \tstruct ref *ref;\n \tint retval;\n+\tint old_save_commit_buffer = save_commit_buffer;\n \ttimestamp_t cutoff = 0;\n \n \tsave_commit_buffer = 0;\n@@ -786,6 +787,9 @@ static int everything_local(struct fetch_pack_args *args,\n \t\tprint_verbose(args, _(\"already have %s (%s)\"), oid_to_hex(remote),\n \t\t\t      ref->name);\n \t}\n+\n+\tsave_commit_buffer = old_save_commit_buffer;\n+\n \treturn retval;\n }\n \n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329205","messageId":"5e5806e020a639560bca397d77045cc5286c1655.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 03/18] fsck: support referenced promisor objects","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:39Z","receivedAt":"2017-09-29T20:12:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach fsck to not treat missing promisor objects indirectly pointed to\nby refs as an error when extensions.partialclone is set.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/fsck.c           | 11 +++++++++++\n t/t0410-partial-clone.sh | 23 +++++++++++++++++++++++\n 2 files changed, 34 insertions(+)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex f1529527b..4492a4fab 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -149,6 +149,15 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt\n \tif (obj->flags & REACHABLE)\n \t\treturn 0;\n \tobj->flags |= REACHABLE;\n+\n+\tif (is_promisor_object(&obj->oid))\n+\t\t/*\n+\t\t * Further recursion does not need to be performed on this\n+\t\t * object since it is a promisor object (so it does not need to\n+\t\t * be added to \"pending\").\n+\t\t */\n+\t\treturn 0;\n+\n \tif (!(obj->flags & HAS_OBJ)) {\n \t\tif (parent && !has_object_file(&obj->oid)) {\n \t\t\tprintf(\"broken link from %7s %s\\n\",\n@@ -213,6 +222,8 @@ static void check_reachable_object(struct object *obj)\n \t * do a full fsck\n \t */\n \tif (!(obj->flags & HAS_OBJ)) {\n+\t\tif (is_promisor_object(&obj->oid))\n+\t\t\treturn;\n \t\tif (has_sha1_pack(obj->oid.hash))\n \t\t\treturn; /* it is in pack - forget about it */\n \t\tprintf(\"missing %s %s\\n\", printable_type(obj),\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex bf75162c1..4f9931f9b 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -102,4 +102,27 @@ test_expect_success 'missing ref object, but promised, passes fsck' '\n \tgit -C repo fsck\n '\n \n+test_expect_success 'missing object, but promised, passes fsck' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo 1 &&\n+\ttest_commit -C repo 2 &&\n+\ttest_commit -C repo 3 &&\n+\tgit -C repo tag -a annotated_tag -m \"annotated tag\" &&\n+\n+\tC=$(git -C repo rev-parse 1) &&\n+\tT=$(git -C repo rev-parse 2^{tree}) &&\n+\tB=$(git hash-object repo/3.t) &&\n+\tAT=$(git -C repo rev-parse annotated_tag) &&\n+\n+\tpromise_and_delete \"$C\" &&\n+\tpromise_and_delete \"$T\" &&\n+\tpromise_and_delete \"$B\" &&\n+\tpromise_and_delete \"$AT\" &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo fsck\n+'\n+\n test_done\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329206","messageId":"f211093280b422c32cc1b7034130072f35c5ed51.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 12/18] fetch-pack: support excluding large blobs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:48Z","receivedAt":"2017-09-29T20:12:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach fetch-pack and upload-pack to support excluding large blobs\nthrough a blob-max-bytes parameter.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/technical/pack-protocol.txt         |  9 ++++++++\n Documentation/technical/protocol-capabilities.txt |  7 ++++++\n builtin/fetch-pack.c                              | 11 +++++++++\n fetch-pack.c                                      | 11 +++++++++\n fetch-pack.h                                      |  1 +\n t/t5500-fetch-pack.sh                             | 27 +++++++++++++++++++++++\n upload-pack.c                                     | 16 +++++++++++++-\n 7 files changed, 81 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex ed1eae8b8..db0e1150b 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -212,6 +212,7 @@ out of what the server said it could do with the first 'want' line.\n   upload-request    =  want-list\n \t\t       *shallow-line\n \t\t       *1depth-request\n+\t\t       *1blob-max-bytes\n \t\t       flush-pkt\n \n   want-list         =  first-want\n@@ -223,10 +224,14 @@ out of what the server said it could do with the first 'want' line.\n \t\t       PKT-LINE(\"deepen-since\" SP timestamp) /\n \t\t       PKT-LINE(\"deepen-not\" SP ref)\n \n+  blob-max-bytes    =  PKT-LINE(\"blob-max-bytes\" SP magnitude)\n+\n   first-want        =  PKT-LINE(\"want\" SP obj-id SP capability-list)\n   additional-want   =  PKT-LINE(\"want\" SP obj-id)\n \n   depth             =  1*DIGIT\n+\n+  magnitude         =  1*DIGIT\n ----\n \n Clients MUST send all the obj-ids it wants from the reference\n@@ -249,6 +254,10 @@ complete those commits. Commits whose parents are not received as a\n result are defined as shallow and marked as such in the server. This\n information is sent back to the client in the next step.\n \n+The client can optionally request a partial packfile that omits blobs\n+above a certain size threshold using \"blob-max-bytes\". Files whose names\n+start with \".git\" are always included in the packfile, however.\n+\n Once all the 'want's and 'shallow's (and optional 'deepen') are\n transferred, clients MUST send a flush-pkt, to tell the server side\n that it is done sending the list.\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex 26dcc6f50..7e878fce5 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -309,3 +309,10 @@ to accept a signed push certificate, and asks the <nonce> to be\n included in the push certificate.  A send-pack client MUST NOT\n send a push-cert packet unless the receive-pack server advertises\n this capability.\n+\n+blob-max-bytes\n+--------------\n+\n+If the upload-pack server advertises this capability, fetch-pack\n+may send \"blob-max-bytes\" to request the server to omit blobs above a\n+certain size from the packfile.\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 9a7ebf6e9..116be9bf5 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -4,6 +4,7 @@\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"sha1-array.h\"\n+#include \"config.h\"\n \n static const char fetch_pack_usage[] =\n \"git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] \"\n@@ -153,6 +154,16 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \t\t\targs.no_haves = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (skip_prefix(arg, \"--blob-max-bytes=\", &arg)) {\n+\t\t\tunsigned long *ptr = xmalloc(sizeof(*ptr));\n+\t\t\tif (!git_parse_ulong(arg, ptr)) {\n+\t\t\t\terror(\"Invalid --blob-max-bytes value: %s\",\n+\t\t\t\t      arg);\n+\t\t\t\tusage(fetch_pack_usage);\n+\t\t\t}\n+\t\t\targs.blob_max_bytes = ptr;\n+\t\t\tcontinue;\n+\t\t}\n \t\tusage(fetch_pack_usage);\n \t}\n \tif (deepen_not.nr)\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex d376c4ef1..19b8e9322 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -26,6 +26,7 @@ static int prefer_ofs_delta = 1;\n static int no_done;\n static int deepen_since_ok;\n static int deepen_not_ok;\n+static int blob_max_bytes_ok;\n static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n@@ -407,6 +408,13 @@ static int find_common(struct fetch_pack_args *args,\n \t\t\tpacket_buf_write(&req_buf, \"deepen-not %s\", s->string);\n \t\t}\n \t}\n+\tif (args->blob_max_bytes) {\n+\t\tif (blob_max_bytes_ok)\n+\t\t\tpacket_buf_write(&req_buf, \"blob-max-bytes %ld\",\n+\t\t\t\t\t *args->blob_max_bytes);\n+\t\telse\n+\t\t\twarning(\"blob-max-bytes not recognized by server, ignoring\");\n+\t}\n \tpacket_buf_flush(&req_buf);\n \tstate_len = req_buf.len;\n \n@@ -983,6 +991,8 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\tdie(_(\"Server does not support --shallow-exclude\"));\n \tif (!server_supports(\"deepen-relative\") && args->deepen_relative)\n \t\tdie(_(\"Server does not support --deepen\"));\n+\tif (server_supports(\"blob-max-bytes\"))\n+\t\tblob_max_bytes_ok = 1;\n \n \tif (everything_local(args, &ref, sought, nr_sought)) {\n \t\tpacket_flush(fd[1]);\n@@ -1169,6 +1179,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,\n \t\tpacket_flush(fd[1]);\n \t\tdie(_(\"no matching remote head\"));\n \t}\n+\n \tprepare_shallow_info(&si, shallow);\n \tref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,\n \t\t\t\t&si, pack_lockfile);\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 84904c348..3743a0ab2 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -12,6 +12,7 @@ struct fetch_pack_args {\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n+\tunsigned long *blob_max_bytes;\n \tunsigned deepen_relative:1;\n \tunsigned quiet:1;\n \tunsigned keep_pack:1;\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex 80a1a3239..62e384230 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -755,4 +755,31 @@ test_expect_success 'fetching deepen' '\n \t)\n '\n \n+test_expect_success '--blob-max-bytes' '\n+\trm -rf server client &&\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\ttest_config -C server uploadpack.advertiseblobmaxbytes 1 &&\n+\n+\ttest_create_repo client &&\n+\tgit -C client fetch-pack --blob-max-bytes=0 ../server HEAD &&\n+\n+\t# Ensure that object is not inadvertently fetched\n+\ttest_must_fail git -C client cat-file -e $(git hash-object server/one.t)\n+'\n+\n+test_expect_success '--blob-max-bytes has no effect if support for it is not advertised' '\n+\trm -rf server client &&\n+\ttest_create_repo server &&\n+\ttest_commit -C server one &&\n+\n+\ttest_create_repo client &&\n+\tgit -C client fetch-pack --blob-max-bytes=0 ../server HEAD 2> err &&\n+\n+\t# Ensure that object is fetched\n+\tgit -C client cat-file -e $(git hash-object server/one.t) &&\n+\n+\ttest_i18ngrep \"blob-max-bytes not recognized by server\" err\n+'\n+\n test_done\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 7efff2fbf..484704eb6 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -63,6 +63,8 @@ static int use_sideband;\n static int advertise_refs;\n static int stateless_rpc;\n static const char *pack_objects_hook;\n+static int advertise_blob_max_bytes;\n+static char *blob_max_bytes;\n \n static void reset_timeout(void)\n {\n@@ -131,6 +133,8 @@ static void create_pack_file(void)\n \t\targv_array_push(&pack_objects.args, \"--delta-base-offset\");\n \tif (use_include_tag)\n \t\targv_array_push(&pack_objects.args, \"--include-tag\");\n+\tif (blob_max_bytes)\n+\t\targv_array_push(&pack_objects.args, blob_max_bytes);\n \n \tpack_objects.in = -1;\n \tpack_objects.out = -1;\n@@ -794,6 +798,13 @@ static void receive_needs(void)\n \t\t\tdeepen_rev_list = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (skip_prefix(line, \"blob-max-bytes \", &arg)) {\n+\t\t\tunsigned long s;\n+\t\t\tif (!git_parse_ulong(arg, &s))\n+\t\t\t\tdie(\"git upload-pack: invalid blob-max-bytes value: %s\", line);\n+\t\t\tblob_max_bytes = xstrfmt(\"--blob-max-bytes=%lu\", s);\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!skip_prefix(line, \"want \", &arg) ||\n \t\t    get_oid_hex(arg, &oid_buf))\n \t\t\tdie(\"git upload-pack: protocol error, \"\n@@ -940,7 +951,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\tstruct strbuf symref_info = STRBUF_INIT;\n \n \t\tformat_symref_info(&symref_info, cb_data);\n-\t\tpacket_write_fmt(1, \"%s %s%c%s%s%s%s%s agent=%s\\n\",\n+\t\tpacket_write_fmt(1, \"%s %s%c%s%s%s%s%s%s agent=%s\\n\",\n \t\t\t     oid_to_hex(oid), refname_nons,\n \t\t\t     0, capabilities,\n \t\t\t     (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ?\n@@ -949,6 +960,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\t\t\t     \" allow-reachable-sha1-in-want\" : \"\",\n \t\t\t     stateless_rpc ? \" no-done\" : \"\",\n \t\t\t     symref_info.buf,\n+\t\t\t     advertise_blob_max_bytes ? \" blob-max-bytes\" : \"\",\n \t\t\t     git_user_agent_sanitized());\n \t\tstrbuf_release(&symref_info);\n \t} else {\n@@ -1028,6 +1040,8 @@ static int upload_pack_config(const char *var, const char *value, void *unused)\n \t} else if (current_config_scope() != CONFIG_SCOPE_REPO) {\n \t\tif (!strcmp(\"uploadpack.packobjectshook\", var))\n \t\t\treturn git_config_string(&pack_objects_hook, var, value);\n+\t} else if (!strcmp(\"uploadpack.advertiseblobmaxbytes\", var)) {\n+\t\tadvertise_blob_max_bytes = git_config_bool(var, value);\n \t}\n \treturn parse_hide_refs_config(var, value, \"uploadpack\");\n }\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329207","messageId":"eb0b57bc917db45ae517e9cea05d94b4977b4952.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 05/18] index-pack: refactor writing of .keep files","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:41Z","receivedAt":"2017-09-29T20:12:58Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a subsequent commit, index-pack will be taught to write \".promisor\"\nfiles which are similar to the \".keep\" files it knows how to write.\nRefactor the writing of \".keep\" files, so that the implementation of\nwriting \".promisor\" files becomes easier.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/index-pack.c | 99 ++++++++++++++++++++++++++++------------------------\n 1 file changed, 53 insertions(+), 46 deletions(-)\n\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex f2be145e1..7ad170590 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1389,15 +1389,58 @@ static void fix_unresolved_deltas(struct sha1file *f)\n \tfree(sorted_by_pos);\n }\n \n+static const char *derive_filename(const char *pack_name, const char *suffix,\n+\t\t\t\t   struct strbuf *buf)\n+{\n+\tsize_t len;\n+\tif (!strip_suffix(pack_name, \".pack\", &len))\n+\t\tdie(_(\"packfile name '%s' does not end with '.pack'\"),\n+\t\t    pack_name);\n+\tstrbuf_add(buf, pack_name, len);\n+\tstrbuf_addch(buf, '.');\n+\tstrbuf_addstr(buf, suffix);\n+\treturn buf->buf;\n+}\n+\n+static void write_special_file(const char *suffix, const char *msg,\n+\t\t\t       const char *pack_name, const unsigned char *sha1,\n+\t\t\t       const char **report)\n+{\n+\tstruct strbuf name_buf = STRBUF_INIT;\n+\tconst char *filename;\n+\tint fd;\n+\tint msg_len = strlen(msg);\n+\n+\tif (pack_name)\n+\t\tfilename = derive_filename(pack_name, suffix, &name_buf);\n+\telse\n+\t\tfilename = odb_pack_name(&name_buf, sha1, suffix);\n+\n+\tfd = odb_pack_keep(filename);\n+\tif (fd < 0) {\n+\t\tif (errno != EEXIST)\n+\t\t\tdie_errno(_(\"cannot write %s file '%s'\"),\n+\t\t\t\t  suffix, filename);\n+\t} else {\n+\t\tif (msg_len > 0) {\n+\t\t\twrite_or_die(fd, msg, msg_len);\n+\t\t\twrite_or_die(fd, \"\\n\", 1);\n+\t\t}\n+\t\tif (close(fd) != 0)\n+\t\t\tdie_errno(_(\"cannot close written %s file '%s'\"),\n+\t\t\t\t  suffix, filename);\n+\t\t*report = suffix;\n+\t}\n+\tstrbuf_release(&name_buf);\n+}\n+\n static void final(const char *final_pack_name, const char *curr_pack_name,\n \t\t  const char *final_index_name, const char *curr_index_name,\n-\t\t  const char *keep_name, const char *keep_msg,\n-\t\t  unsigned char *sha1)\n+\t\t  const char *keep_msg, unsigned char *sha1)\n {\n \tconst char *report = \"pack\";\n \tstruct strbuf pack_name = STRBUF_INIT;\n \tstruct strbuf index_name = STRBUF_INIT;\n-\tstruct strbuf keep_name_buf = STRBUF_INIT;\n \tint err;\n \n \tif (!from_stdin) {\n@@ -1409,28 +1452,9 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \t\t\tdie_errno(_(\"error while closing pack file\"));\n \t}\n \n-\tif (keep_msg) {\n-\t\tint keep_fd, keep_msg_len = strlen(keep_msg);\n-\n-\t\tif (!keep_name)\n-\t\t\tkeep_name = odb_pack_name(&keep_name_buf, sha1, \"keep\");\n-\n-\t\tkeep_fd = odb_pack_keep(keep_name);\n-\t\tif (keep_fd < 0) {\n-\t\t\tif (errno != EEXIST)\n-\t\t\t\tdie_errno(_(\"cannot write keep file '%s'\"),\n-\t\t\t\t\t  keep_name);\n-\t\t} else {\n-\t\t\tif (keep_msg_len > 0) {\n-\t\t\t\twrite_or_die(keep_fd, keep_msg, keep_msg_len);\n-\t\t\t\twrite_or_die(keep_fd, \"\\n\", 1);\n-\t\t\t}\n-\t\t\tif (close(keep_fd) != 0)\n-\t\t\t\tdie_errno(_(\"cannot close written keep file '%s'\"),\n-\t\t\t\t\t  keep_name);\n-\t\t\treport = \"keep\";\n-\t\t}\n-\t}\n+\tif (keep_msg)\n+\t\twrite_special_file(\"keep\", keep_msg, final_pack_name, sha1,\n+\t\t\t\t   &report);\n \n \tif (final_pack_name != curr_pack_name) {\n \t\tif (!final_pack_name)\n@@ -1472,7 +1496,6 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \n \tstrbuf_release(&index_name);\n \tstrbuf_release(&pack_name);\n-\tstrbuf_release(&keep_name_buf);\n }\n \n static int git_index_pack_config(const char *k, const char *v, void *cb)\n@@ -1615,26 +1638,13 @@ static void show_pack_info(int stat_only)\n \t}\n }\n \n-static const char *derive_filename(const char *pack_name, const char *suffix,\n-\t\t\t\t   struct strbuf *buf)\n-{\n-\tsize_t len;\n-\tif (!strip_suffix(pack_name, \".pack\", &len))\n-\t\tdie(_(\"packfile name '%s' does not end with '.pack'\"),\n-\t\t    pack_name);\n-\tstrbuf_add(buf, pack_name, len);\n-\tstrbuf_addstr(buf, suffix);\n-\treturn buf->buf;\n-}\n-\n int cmd_index_pack(int argc, const char **argv, const char *prefix)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0;\n \tconst char *curr_index;\n \tconst char *index_name = NULL, *pack_name = NULL;\n-\tconst char *keep_name = NULL, *keep_msg = NULL;\n-\tstruct strbuf index_name_buf = STRBUF_INIT,\n-\t\t      keep_name_buf = STRBUF_INIT;\n+\tconst char *keep_msg = NULL;\n+\tstruct strbuf index_name_buf = STRBUF_INIT;\n \tstruct pack_idx_entry **idx_objects;\n \tstruct pack_idx_option opts;\n \tunsigned char pack_sha1[20];\n@@ -1745,9 +1755,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \tif (from_stdin && !startup_info->have_repository)\n \t\tdie(_(\"--stdin requires a git repository\"));\n \tif (!index_name && pack_name)\n-\t\tindex_name = derive_filename(pack_name, \".idx\", &index_name_buf);\n-\tif (keep_msg && !keep_name && pack_name)\n-\t\tkeep_name = derive_filename(pack_name, \".keep\", &keep_name_buf);\n+\t\tindex_name = derive_filename(pack_name, \"idx\", &index_name_buf);\n \n \tif (verify) {\n \t\tif (!index_name)\n@@ -1795,13 +1803,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)\n \tif (!verify)\n \t\tfinal(pack_name, curr_pack,\n \t\t      index_name, curr_index,\n-\t\t      keep_name, keep_msg,\n+\t\t      keep_msg,\n \t\t      pack_sha1);\n \telse\n \t\tclose(input_fd);\n \tfree(objects);\n \tstrbuf_release(&index_name_buf);\n-\tstrbuf_release(&keep_name_buf);\n \tif (pack_name == NULL)\n \t\tfree((void *) curr_pack);\n \tif (index_name == NULL)\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329208","messageId":"75be6a3b191753d14c5e10caf24ecf6926a7f544.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 09/18] gc: do not repack promisor packfiles","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:45Z","receivedAt":"2017-09-29T20:13:04Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach gc to stop traversal at promisor objects, and to leave promisor\npackfiles alone. This has the effect of only repacking non-promisor\npackfiles, and preserves the distinction between promisor packfiles and\nnon-promisor packfiles.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/gc.c             |  3 +++\n builtin/pack-objects.c   | 10 ++++++++++\n builtin/prune.c          |  7 +++++++\n builtin/repack.c         |  7 +++++--\n t/t0410-partial-clone.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++-\n 5 files changed, 76 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c22787ac7..7a9632bba 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -458,6 +458,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \t\t\targv_array_push(&prune, prune_expire);\n \t\t\tif (quiet)\n \t\t\t\targv_array_push(&prune, \"--no-progress\");\n+\t\t\tif (repository_format_partial_clone)\n+\t\t\t\targv_array_push(&prune,\n+\t\t\t\t\t\t\"--exclude-promisor-objects\");\n \t\t\tif (run_command_v_opt(prune.argv, RUN_GIT_CMD))\n \t\t\t\treturn error(FAILED_RUN, prune.argv[0]);\n \t\t}\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex a57b4f058..958822bf4 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -73,6 +73,8 @@ static int use_bitmap_index = -1;\n static int write_bitmap_index;\n static uint16_t write_bitmap_options;\n \n+static int exclude_promisor_objects;\n+\n static unsigned long delta_cache_size = 0;\n static unsigned long max_delta_cache_size = 256 * 1024 * 1024;\n static unsigned long cache_max_small_delta_size = 1000;\n@@ -2952,6 +2954,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"use a bitmap index if available to speed up counting objects\")),\n \t\tOPT_BOOL(0, \"write-bitmap-index\", &write_bitmap_index,\n \t\t\t N_(\"write a bitmap index together with the pack index\")),\n+\t\tOPT_BOOL(0, \"exclude-promisor-objects\", &exclude_promisor_objects,\n+\t\t\t N_(\"do not pack objects in promisor packfiles\")),\n \t\tOPT_END(),\n \t};\n \n@@ -2997,6 +3001,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\targv_array_push(&rp, \"--unpacked\");\n \t}\n \n+\tif (exclude_promisor_objects) {\n+\t\tuse_internal_rev_list = 1;\n+\t\tfetch_if_missing = 0;\n+\t\targv_array_push(&rp, \"--exclude-promisor-objects\");\n+\t}\n+\n \tif (!reuse_object)\n \t\treuse_delta = 0;\n \tif (pack_compression_level == -1)\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex cddabf26a..be34645dc 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -101,12 +101,15 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tstruct progress *progress = NULL;\n+\tint exclude_promisor_objects = 0;\n \tconst struct option options[] = {\n \t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n \t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n+\t\tOPT_BOOL(0, \"exclude-promisor-objects\", &exclude_promisor_objects,\n+\t\t\t N_(\"limit traversal to objects outside promisor packfiles\")),\n \t\tOPT_END()\n \t};\n \tchar *s;\n@@ -139,6 +142,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\tshow_progress = isatty(2);\n \tif (show_progress)\n \t\tprogress = start_delayed_progress(_(\"Checking connectivity\"), 0);\n+\tif (exclude_promisor_objects) {\n+\t\tfetch_if_missing = 0;\n+\t\trevs.exclude_promisor_objects = 1;\n+\t}\n \n \tmark_reachable_objects(&revs, 1, expire, progress);\n \tstop_progress(&progress);\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f17a68a17..f43317bb5 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -83,7 +83,8 @@ static void remove_pack_on_signal(int signo)\n \n /*\n  * Adds all packs hex strings to the fname list, which do not\n- * have a corresponding .keep file.\n+ * have a corresponding .keep or .promisor file. These packs are not to\n+ * be kept if we are going to pack everything into one file.\n  */\n static void get_non_kept_pack_filenames(struct string_list *fname_list)\n {\n@@ -101,7 +102,8 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list)\n \n \t\tfname = xmemdupz(e->d_name, len);\n \n-\t\tif (!file_exists(mkpath(\"%s/%s.keep\", packdir, fname)))\n+\t\tif (!file_exists(mkpath(\"%s/%s.keep\", packdir, fname)) &&\n+\t\t    !file_exists(mkpath(\"%s/%s.promisor\", packdir, fname)))\n \t\t\tstring_list_append_nodup(fname_list, fname);\n \t\telse\n \t\t\tfree(fname);\n@@ -232,6 +234,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)\n \targv_array_push(&cmd.args, \"--all\");\n \targv_array_push(&cmd.args, \"--reflog\");\n \targv_array_push(&cmd.args, \"--indexed-objects\");\n+\targv_array_push(&cmd.args, \"--exclude-promisor-objects\");\n \tif (window)\n \t\targv_array_pushf(&cmd.args, \"--window=%s\", window);\n \tif (window_memory)\ndiff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh\nindex 3ca6af5cd..071736c7b 100755\n--- a/t/t0410-partial-clone.sh\n+++ b/t/t0410-partial-clone.sh\n@@ -11,13 +11,15 @@ delete_object () {\n pack_as_from_promisor () {\n \tHASH=$(git -C repo pack-objects .git/objects/pack/pack) &&\n \t>repo/.git/objects/pack/pack-$HASH.promisor\n+\techo $HASH\n }\n \n promise_and_delete () {\n \tHASH=$(git -C repo rev-parse \"$1\") &&\n \tgit -C repo tag -a -m message my_annotated_tag \"$HASH\" &&\n \tgit -C repo rev-parse my_annotated_tag | pack_as_from_promisor &&\n-\tgit -C repo tag -d my_annotated_tag &&\n+\t# tag -d prints a message to stdout, so redirect it\n+\tgit -C repo tag -d my_annotated_tag >/dev/null &&\n \tdelete_object repo \"$HASH\"\n }\n \n@@ -261,6 +263,54 @@ test_expect_success 'rev-list accepts missing and promised objects on command li\n \tgit -C repo rev-list --exclude-promisor-objects --objects \"$COMMIT\" \"$TREE\" \"$BLOB\"\n '\n \n+test_expect_success 'gc does not repack promisor objects' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tTREE_HASH=$(git -C repo rev-parse HEAD^{tree}) &&\n+\tHASH=$(printf \"$TREE_HASH\\n\" | pack_as_from_promisor) &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo gc &&\n+\n+\t# Ensure that the promisor packfile still exists, and remove it\n+\ttest -e repo/.git/objects/pack/pack-$HASH.pack &&\n+\trm repo/.git/objects/pack/pack-$HASH.* &&\n+\n+\t# Ensure that the single other pack contains the commit, but not the tree\n+\tls repo/.git/objects/pack/pack-*.pack >packlist &&\n+\ttest_line_count = 1 packlist &&\n+\tgit verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&\n+\tgrep \"$(git -C repo rev-parse HEAD)\" out &&\n+\t! grep \"$TREE_HASH\" out\n+'\n+\n+test_expect_success 'gc stops traversal when a missing but promised object is reached' '\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_commit -C repo my_commit &&\n+\n+\tTREE_HASH=$(git -C repo rev-parse HEAD^{tree}) &&\n+\tHASH=$(promise_and_delete $TREE_HASH) &&\n+\n+\tgit -C repo config core.repositoryformatversion 1 &&\n+\tgit -C repo config extensions.partialclone \"arbitrary string\" &&\n+\tgit -C repo gc &&\n+\n+\t# Ensure that the promisor packfile still exists, and remove it\n+\ttest -e repo/.git/objects/pack/pack-$HASH.pack &&\n+\trm repo/.git/objects/pack/pack-$HASH.* &&\n+\n+\t# Ensure that the single other pack contains the commit, but not the tree\n+\tls repo/.git/objects/pack/pack-*.pack >packlist &&\n+\ttest_line_count = 1 packlist &&\n+\tgit verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&\n+\tgrep \"$(git -C repo rev-parse HEAD)\" out &&\n+\t! grep \"$TREE_HASH\" out\n+'\n+\n LIB_HTTPD_PORT=12345  # default port, 410, cannot be used as non-root\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n start_httpd\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329209","messageId":"2d96de7a542d82f150fb47196b21763e7156f829.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 10/18] pack-objects: rename want_.* to ignore_.*","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:46Z","receivedAt":"2017-09-29T20:13:06Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, in pack_objects, add_object_entry() distinguishes between 2\ntypes of non-preferred-base objects:\n\n (1) objects that should not be in \"to_pack\" because an option like\n     --local or --honor-pack-keep is set\n (2) objects that should be in \"to_pack\"\n\nA subsequent commit will teach pack-objects to exclude certain objects\n(specifically, blobs that are larger than a user-given threshold and are\nnot potentially a Git special file [1]), but unlike in (1) above, these\nexclusions need to be reported. So now we have 3 types of\nnon-preferred-base objects:\n\n (a) objects that should not be in \"to_pack\" and should not be reported\n     because an option like --local or --honor-pack-keep is set\n (b) objects that should not be in \"to_pack\" and should be reported\n     because they are blobs that are oversized and non-Git-special\n (c) objects that should be in \"to_pack\"\n\nadd_object_entry() will be taught to distinguish between these 3 types\nby the following:\n\n - renaming want_found_object() and want_object_in_pack() to ignore_.*\n   to make it clear that they only check for (a) (this commit)\n - adding a new function to check for (b) and using it within\n   add_object_entry() (a subsequent commit)\n\nThe alternative would be to retain the names want_found_object() and\nwant_object_in_pack() and have them handle both the cases (a) and (b),\nbut that would result in more complicated code.\n\nWe also take the opportunity to use the terminology \"preferred_base\"\ninstead of \"excluded\" in these methods. It is true that preferred bases\nare not included in the final packfile generation, but at this point in\nthe code, there is no exclusion taking place - on the contrary, if\nsomething is \"excluded\", it is in fact guaranteed to be in to_pack.\n\n[1] For the purposes of pack_objects, a blob is a Git special file if it\n    appears in a to-be-packed tree with a filename beginning with\n    \".git\".\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/pack-objects.c | 56 +++++++++++++++++++++++++-------------------------\n 1 file changed, 28 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 958822bf4..ef0b61d5f 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -949,12 +949,12 @@ static int have_duplicate_entry(const unsigned char *sha1,\n \treturn 1;\n }\n \n-static int want_found_object(int exclude, struct packed_git *p)\n+static int ignore_found_object(int preferred_base, struct packed_git *p)\n {\n-\tif (exclude)\n-\t\treturn 1;\n-\tif (incremental)\n+\tif (preferred_base)\n \t\treturn 0;\n+\tif (incremental)\n+\t\treturn 1;\n \n \t/*\n \t * When asked to do --local (do not include an object that appears in a\n@@ -972,19 +972,19 @@ static int want_found_object(int exclude, struct packed_git *p)\n \t */\n \tif (!ignore_packed_keep &&\n \t    (!local || !have_non_local_packs))\n-\t\treturn 1;\n+\t\treturn 0;\n \n \tif (local && !p->pack_local)\n-\t\treturn 0;\n+\t\treturn 1;\n \tif (ignore_packed_keep && p->pack_local && p->pack_keep)\n-\t\treturn 0;\n+\t\treturn 1;\n \n \t/* we don't know yet; keep looking for more packs */\n \treturn -1;\n }\n \n /*\n- * Check whether we want the object in the pack (e.g., we do not want\n+ * Check whether we should ignore this object (e.g., we do not want\n  * objects found in non-local stores if the \"--local\" option was used).\n  *\n  * If the caller already knows an existing pack it wants to take the object\n@@ -992,16 +992,16 @@ static int want_found_object(int exclude, struct packed_git *p)\n  * function finds if there is any pack that has the object and returns the pack\n  * and its offset in these variables.\n  */\n-static int want_object_in_pack(const unsigned char *sha1,\n-\t\t\t       int exclude,\n-\t\t\t       struct packed_git **found_pack,\n-\t\t\t       off_t *found_offset)\n+static int ignore_object(const unsigned char *sha1,\n+\t\t\t int preferred_base,\n+\t\t\t struct packed_git **found_pack,\n+\t\t\t off_t *found_offset)\n {\n \tstruct mru_entry *entry;\n-\tint want;\n+\tint ignore;\n \n-\tif (!exclude && local && has_loose_object_nonlocal(sha1))\n-\t\treturn 0;\n+\tif (!preferred_base && local && has_loose_object_nonlocal(sha1))\n+\t\treturn 1;\n \n \t/*\n \t * If we already know the pack object lives in, start checks from that\n@@ -1009,9 +1009,9 @@ static int want_object_in_pack(const unsigned char *sha1,\n \t * are present we will determine the answer right now.\n \t */\n \tif (*found_pack) {\n-\t\twant = want_found_object(exclude, *found_pack);\n-\t\tif (want != -1)\n-\t\t\treturn want;\n+\t\tignore = ignore_found_object(preferred_base, *found_pack);\n+\t\tif (ignore != -1)\n+\t\t\treturn ignore;\n \t}\n \n \tfor (entry = packed_git_mru->head; entry; entry = entry->next) {\n@@ -1030,15 +1030,15 @@ static int want_object_in_pack(const unsigned char *sha1,\n \t\t\t\t*found_offset = offset;\n \t\t\t\t*found_pack = p;\n \t\t\t}\n-\t\t\twant = want_found_object(exclude, p);\n-\t\t\tif (!exclude && want > 0)\n+\t\t\tignore = ignore_found_object(preferred_base, p);\n+\t\t\tif (!preferred_base && ignore > 0)\n \t\t\t\tmru_mark(packed_git_mru, entry);\n-\t\t\tif (want != -1)\n-\t\t\t\treturn want;\n+\t\t\tif (ignore != -1)\n+\t\t\t\treturn ignore;\n \t\t}\n \t}\n \n-\treturn 1;\n+\treturn 0;\n }\n \n static void create_object_entry(const unsigned char *sha1,\n@@ -1073,16 +1073,16 @@ static const char no_closure_warning[] = N_(\n );\n \n static int add_object_entry(const unsigned char *sha1, enum object_type type,\n-\t\t\t    const char *name, int exclude)\n+\t\t\t    const char *name, int preferred_base)\n {\n \tstruct packed_git *found_pack = NULL;\n \toff_t found_offset = 0;\n \tuint32_t index_pos;\n \n-\tif (have_duplicate_entry(sha1, exclude, &index_pos))\n+\tif (have_duplicate_entry(sha1, preferred_base, &index_pos))\n \t\treturn 0;\n \n-\tif (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset)) {\n+\tif (ignore_object(sha1, preferred_base, &found_pack, &found_offset)) {\n \t\t/* The pack is missing an object, so it will not have closure */\n \t\tif (write_bitmap_index) {\n \t\t\twarning(_(no_closure_warning));\n@@ -1092,7 +1092,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,\n \t}\n \n \tcreate_object_entry(sha1, type, pack_name_hash(name),\n-\t\t\t    exclude, name && no_try_delta(name),\n+\t\t\t    preferred_base, name && no_try_delta(name),\n \t\t\t    index_pos, found_pack, found_offset);\n \n \tdisplay_progress(progress_state, nr_result);\n@@ -1109,7 +1109,7 @@ static int add_object_entry_from_bitmap(const unsigned char *sha1,\n \tif (have_duplicate_entry(sha1, 0, &index_pos))\n \t\treturn 0;\n \n-\tif (!want_object_in_pack(sha1, 0, &pack, &offset))\n+\tif (ignore_object(sha1, 0, &pack, &offset))\n \t\treturn 0;\n \n \tcreate_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset);\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329210","messageId":"b9ea93edabc42754dc3643d6307c22a947eabaf3.1506714999.git.jonathantanmy@google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"[PATCH 11/18] pack-objects: support --blob-max-bytes","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-09-29T20:11:47Z","receivedAt":"2017-09-29T20:13:08Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"As part of an effort to improve Git support for very large repositories\nin which clients typically have only a subset of all version-controlled\nblobs, teach pack-objects to support --blob-max-bytes, packing only\nblobs not exceeding that size unless the blob corresponds to a file\nwhose name starts with \".git\". upload-pack will eventually be taught to\nuse this new parameter if needed to exclude certain blobs during a fetch\nor clone, potentially drastically reducing network consumption when\nserving these very large repositories.\n\nSince any excluded blob should not act as a delta base, I did this by\navoiding such oversized blobs from ever going into the \"to_pack\" data\nstructure, which contains both preferred bases (which do not go into the\nfinal packfile but can act as delta bases) and the objects to be packed\n(which go into the final packfile and also can act as delta bases). To\nthat end, add_object_entry() has been modified to exclude the\nappropriate non-preferred-base objects. (Preferred bases, regardless of\nsize, still enter \"to_pack\" - they are something that the client\nindicates that it has, not something that the server needs to serve, so\nno exclusion occurs.)\n\nIf bitmaps are to be used, we would not know if a blob corresponded to a\nfile whose name starts with \".git\". For this reason, when invoked with\n--blob-max-bytes, pack-objects will not use bitmaps.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-pack-objects.txt | 12 +++++++++-\n builtin/pack-objects.c             | 33 ++++++++++++++++++++++++++--\n t/t5300-pack-object.sh             | 45 ++++++++++++++++++++++++++++++++++++++\n t/test-lib-functions.sh            | 12 ++++++++++\n 4 files changed, 99 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt\nindex 473a16135..ddce2d4c1 100644\n--- a/Documentation/git-pack-objects.txt\n+++ b/Documentation/git-pack-objects.txt\n@@ -12,7 +12,8 @@ SYNOPSIS\n 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]\n \t[--no-reuse-delta] [--delta-base-offset] [--non-empty]\n \t[--local] [--incremental] [--window=<n>] [--depth=<n>]\n-\t[--revs [--unpacked | --all]] [--stdout | base-name]\n+\t[--revs [--unpacked | --all]]\n+\t[--stdout [--blob-max-bytes=<n>] | base-name]\n \t[--shallow] [--keep-true-parents] < object-list\n \n \n@@ -236,6 +237,15 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.\n \tWith this option, parents that are hidden by grafts are packed\n \tnevertheless.\n \n+--blob-max-bytes=<n>::\n+\tThis option can only be used with --stdout. If specified, a blob\n+\tlarger than this will not be packed unless a to-be-packed tree\n+\thas that blob with a filename beginning with \".git\".  The size\n+\tcan be suffixed with \"k\", \"m\", or \"g\", and may be \"0\".\n++\n+If specified, after printing the packfile, pack-objects will print, in packet\n+format (pkt-line), the names of excluded blobs and their sizes.\n+\n SEE ALSO\n --------\n linkgit:git-rev-list[1]\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex ef0b61d5f..8686b4351 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -26,6 +26,8 @@\n #include \"argv-array.h\"\n #include \"mru.h\"\n #include \"packfile.h\"\n+#include \"pkt-line.h\"\n+#include \"varint.h\"\n \n static const char *pack_usage[] = {\n \tN_(\"git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]\"),\n@@ -81,6 +83,8 @@ static unsigned long cache_max_small_delta_size = 1000;\n \n static unsigned long window_memory_limit = 0;\n \n+static long blob_max_bytes = -1;\n+\n /*\n  * stats\n  */\n@@ -1072,6 +1076,27 @@ static const char no_closure_warning[] = N_(\n \"disabling bitmap writing, as some objects are not being packed\"\n );\n \n+/*\n+ * Returns 1 if the given blob does not meet any defined blob size\n+ * limits.  Blobs that appear as a tree entry whose basename begins with\n+ * \".git\" are never considered oversized.\n+ */\n+static int oversized(const unsigned char *sha1, const char *name) {\n+\tconst char *last_slash, *basename;\n+\tunsigned long size;\n+\n+\tif (blob_max_bytes < 0)\n+\t\treturn 0;\n+\n+\tlast_slash = strrchr(name, '/');\n+\tbasename = last_slash ? last_slash + 1 : name;\n+\tif (starts_with(basename, \".git\"))\n+\t\treturn 0;\n+\n+\tsha1_object_info(sha1, &size);\n+\treturn size > blob_max_bytes;\n+}\n+\n static int add_object_entry(const unsigned char *sha1, enum object_type type,\n \t\t\t    const char *name, int preferred_base)\n {\n@@ -1082,7 +1107,8 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,\n \tif (have_duplicate_entry(sha1, preferred_base, &index_pos))\n \t\treturn 0;\n \n-\tif (ignore_object(sha1, preferred_base, &found_pack, &found_offset)) {\n+\tif (ignore_object(sha1, preferred_base, &found_pack, &found_offset) ||\n+\t    (!preferred_base && type == OBJ_BLOB && oversized(sha1, name))) {\n \t\t/* The pack is missing an object, so it will not have closure */\n \t\tif (write_bitmap_index) {\n \t\t\twarning(_(no_closure_warning));\n@@ -2956,6 +2982,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"write a bitmap index together with the pack index\")),\n \t\tOPT_BOOL(0, \"exclude-promisor-objects\", &exclude_promisor_objects,\n \t\t\t N_(\"do not pack objects in promisor packfiles\")),\n+\t\tOPT_MAGNITUDE(0, \"blob-max-bytes\", &blob_max_bytes,\n+\t\t\t      N_(\"exclude blobs larger than this\")),\n \t\tOPT_END(),\n \t};\n \n@@ -3054,7 +3082,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\tuse_bitmap_index = use_bitmap_index_default;\n \n \t/* \"hard\" reasons not to use bitmaps; these just won't work at all */\n-\tif (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow())\n+\tif (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) ||\n+\t    is_repository_shallow() || (blob_max_bytes >= 0))\n \t\tuse_bitmap_index = 0;\n \n \tif (pack_to_stdout || !rev_list_all)\ndiff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh\nindex 9c68b9925..697cc20f2 100755\n--- a/t/t5300-pack-object.sh\n+++ b/t/t5300-pack-object.sh\n@@ -457,6 +457,51 @@ test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack.\n \tgrep -F \"no threads support, ignoring pack.threads\" err\n '\n \n+lcut () {\n+\tperl -e '$/ = undef; $_ = <>; s/^.{'$1'}//s; print $_'\n+}\n+\n+test_expect_success '--blob-size-limit works with multiple excluded' '\n+\trm -rf server &&\n+\tgit init server &&\n+\tprintf a > server/a &&\n+\tprintf b > server/b &&\n+\tprintf c-very-long-file > server/c &&\n+\tprintf d-very-long-file > server/d &&\n+\tgit -C server add a b c d &&\n+\tgit -C server commit -m x &&\n+\n+\tgit -C server rev-parse HEAD >objects &&\n+\tgit -C server pack-objects --revs --stdout --blob-max-bytes=10 <objects >my.pack &&\n+\n+\t# Ensure that only the small blobs are in the packfile\n+\tgit index-pack my.pack &&\n+\tgit verify-pack -v my.idx >objectlist &&\n+\tgrep $(git hash-object server/a) objectlist &&\n+\tgrep $(git hash-object server/b) objectlist &&\n+\t! grep $(git hash-object server/c) objectlist &&\n+\t! grep $(git hash-object server/d) objectlist\n+'\n+\n+test_expect_success '--blob-size-limit never excludes special files' '\n+\trm -rf server &&\n+\tgit init server &&\n+\tprintf a-very-long-file > server/a &&\n+\tprintf a-very-long-file > server/.git-a &&\n+\tprintf b-very-long-file > server/b &&\n+\tgit -C server add a .git-a b &&\n+\tgit -C server commit -m x &&\n+\n+\tgit -C server rev-parse HEAD >objects &&\n+\tgit -C server pack-objects --revs --stdout --blob-max-bytes=10 <objects >my.pack &&\n+\n+\t# Ensure that the .git-a blob is in the packfile, despite also\n+\t# appearing as a non-.git file\n+\tgit index-pack my.pack &&\n+\tgit verify-pack -v my.idx >objectlist &&\n+\tgrep $(git hash-object server/a) objectlist\n+'\n+\n #\n # WARNING!\n #\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 1701fe2a0..07b79c7ad 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1020,3 +1020,15 @@ nongit () {\n \t\t\"$@\"\n \t)\n }\n+\n+# Converts big-endian pairs of hexadecimal digits into bytes. For example,\n+# \"printf 61620d0a | hex_pack\" results in \"ab\\r\\n\".\n+hex_pack () {\n+\tperl -e '$/ = undef; $input = <>; print pack(\"H*\", $input)'\n+}\n+\n+# Converts bytes into big-endian pairs of hexadecimal digits. For example,\n+# \"printf 'ab\\r\\n' | hex_unpack\" results in \"61620d0a\".\n+hex_unpack () {\n+\tperl -e '$/ = undef; $input = <>; print unpack(\"H2\" x length($input), $input)'\n+}\n-- \n2.14.2.822.g60be5d43e6-goog\n\n"},{"id":"329215","messageId":"alpine.DEB.2.21.1.1709292301440.40514@virtualbox","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2017-09-29T21:08:41Z","receivedAt":"2017-09-29T21:09:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Jonathan,\n\nOn Fri, 29 Sep 2017, Jonathan Tan wrote:\n\n> Jeff Hostetler has sent out some object-filtering patches [1] that is a\n> superset of the object-filtering functionality that I have (in the\n> pack-objects patches). I have gone for the minimal approach here, but if\n> his patches are merged, I'll update my patch set to use those.\n\nI wish there was a way for you to work *with* Jeff on this. It seems that\nyour aims are similar enough for that (you both need changes in the\nprotocol) yet different enough to allow for talking past each other (big\nblobs vs narrow clone).\n\nAnd I get the impression that in this instance, it slows everything down\nto build competing, large patch series rather than building on top of each\nother's work.\n\nAdditionally, I am not helping by pestering Jeff all the time about\ndifferent issues, so it is partially my fault.\n\nBut maybe there is a chance to *really* go for a minimal approach, as in\n\"incremental enough that you can share the first <N> patches\"? And even\nbetter: \"come up with those first <N> patches together\"?\n\nCiao,\nDscho\n\n\n"},{"id":"329378","messageId":"xmqqshf2p48b.fsf@gitster.mtv.corp.google.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-10-02T04:23:48Z","receivedAt":"2017-10-02T04:23:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Jeff Hostetler has sent out some object-filtering patches [1] that is a\n> superset of the object-filtering functionality that I have (in the\n> pack-objects patches). I have gone for the minimal approach here, but if\n> his patches are merged, I'll update my patch set to use those.\n>\n> [1] https://public-inbox.org/git/20170922203017.53986-6-git@jeffhostetler.com/\n\nSounds good.  Or perhaps rebasing the other way around, if we feel\nthat the \"fsck with known-missing object\" part of your series is\nwith a better done-ness than Jeff's series (which is my impression\nbut I has an obvious bias that I happened to have reviewed your\nseries with finer toothed comb before I saw Jeff's series).\n\nThanks for working well together ;-).\n"},{"id":"329528","messageId":"CAP8UFD1WiceKDX9s0hszXXKy+TOxAOvHZyr002HMYkkgbFgD7w@mail.gmail.com","threadId":"46861","inReplyTo":"cover.1506714999.git.jonathantanmy@google.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2017-10-03T06:15:33Z","receivedAt":"2017-10-03T06:15:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Sep 29, 2017 at 10:11 PM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> These patches are also available online:\n> https://github.com/jonathantanmy/git/commits/partialclone3\n>\n> (I've announced it in another e-mail, but am now sending the patches to the\n> mailing list too.)\n>\n> Here's an update of my work so far. Notable features:\n>  - These 18 patches allow a user to clone with --blob-max-bytes=<bytes>,\n>    creating a partial clone that is automatically configured to lazily\n>    fetch missing objects from the origin. The local repo also has fsck\n>    working offline, and GC working (albeit only on locally created\n>    objects).\n>  - Cloning and fetching is currently only able to exclude blobs by a\n>    size threshold, but the local repository is already capable of\n>    fetching missing objects of any type. For example, if a repository\n>    with missing trees or commits is generated by any tool (for example,\n>    a future version of Git), current Git with my patches will still be\n>    able to operate on them, automatically fetching those missing trees\n>    and commits when needed.\n>  - Missing blobs are fetched all at once during checkout.\n\nCould you give a bit more details about the use cases this is designed for?\nIt seems that when people review my work they want a lot of details\nabout the use cases, so I guess they would also be interesting in\ngetting this kind of information for your work too.\n\nCould this support users who would be interested in lazily cloning\nonly one kind of files, for example *.jpeg?\n"},{"id":"329540","messageId":"xmqq8tgsipi5.fsf@gitster.mtv.corp.google.com","threadId":"46861","inReplyTo":"CAP8UFD1WiceKDX9s0hszXXKy+TOxAOvHZyr002HMYkkgbFgD7w@mail.gmail.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-10-03T08:50:42Z","receivedAt":"2017-10-03T08:50:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> Could you give a bit more details about the use cases this is designed for?\n> It seems that when people review my work they want a lot of details\n> about the use cases, so I guess they would also be interesting in\n> getting this kind of information for your work too.\n>\n> Could this support users who would be interested in lazily cloning\n> only one kind of files, for example *.jpeg?\n\nI do not know about others, but the reason why I was not interested\nin finding out \"use cases\" is because the value of this series is\nuse-case agnostic.\n\nAt least to me, the most interesting part of the series is that it\nallows you to receive a set of objects transferred from the other\nside that lack some of objects that would otherwise be required to\nbe here for connectivity purposes, and it introduces a mechanism to\nallow object transfer layer, gc and fsck to work well together in\nthe resulting repository that deliberately lacks some objects.  The\ntransfer layer marks the objects obtained from a specific remote as\nsuch, and gc and fsck are taught not to try to follow a missing link\nor diagnose a missing link as an error, if a missing link is\nexpected using the mark the transfer layer left.\n\nand it does so in such a way that it is use-case agnostic.  The\nmechanism does not care how the objects to be omitted were chosen,\nand how the omission criteria were negotiated between the sender and\nthe receiver of the pack.\n\nI think the series comes with one filter that is size-based, but I\nview it as a technology demonstration.  It does not have to be the\nprimary use case.  IOW, I do not think the series is meant as a\ndeclaration that size-based filtering is the most important thing\nand other omission criteria are less important.\n\nYou should be able to build path based omission (i.e. narrow clone)\nor blobtype based omission.  Depending on your needs, you may want\ndifferent object omission criteria.  It is something you can build\non top.  And the work done on transfer/gc/fsck in this series does\nnot have to change to accommodate these different \"use cases\".\n\n\n"},{"id":"329575","messageId":"cb818dcf-beab-10ac-5e58-f56377af4f6a@jeffhostetler.com","threadId":"46861","inReplyTo":"xmqq8tgsipi5.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2017-10-03T14:39:17Z","receivedAt":"2017-10-03T14:39:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 10/3/2017 4:50 AM, Junio C Hamano wrote:\n> Christian Couder <christian.couder@gmail.com> writes:\n> \n>> Could you give a bit more details about the use cases this is designed for?\n>> It seems that when people review my work they want a lot of details\n>> about the use cases, so I guess they would also be interesting in\n>> getting this kind of information for your work too.\n>>\n>> Could this support users who would be interested in lazily cloning\n>> only one kind of files, for example *.jpeg?\n> \n> I do not know about others, but the reason why I was not interested\n> in finding out \"use cases\" is because the value of this series is\n> use-case agnostic.\n> \n> At least to me, the most interesting part of the series is that it\n> allows you to receive a set of objects transferred from the other\n> side that lack some of objects that would otherwise be required to\n> be here for connectivity purposes, and it introduces a mechanism to\n> allow object transfer layer, gc and fsck to work well together in\n> the resulting repository that deliberately lacks some objects.  The\n> transfer layer marks the objects obtained from a specific remote as\n> such, and gc and fsck are taught not to try to follow a missing link\n> or diagnose a missing link as an error, if a missing link is\n> expected using the mark the transfer layer left.\n> \n> and it does so in such a way that it is use-case agnostic.  The\n> mechanism does not care how the objects to be omitted were chosen,\n> and how the omission criteria were negotiated between the sender and\n> the receiver of the pack.\n> \n> I think the series comes with one filter that is size-based, but I\n> view it as a technology demonstration.  It does not have to be the\n> primary use case.  IOW, I do not think the series is meant as a\n> declaration that size-based filtering is the most important thing\n> and other omission criteria are less important.\n> \n> You should be able to build path based omission (i.e. narrow clone)\n> or blobtype based omission.  Depending on your needs, you may want\n> different object omission criteria.  It is something you can build\n> on top.  And the work done on transfer/gc/fsck in this series does\n> not have to change to accommodate these different \"use cases\".\n> \n\nAgreed.\n\nThere are lots of reasons for wanting partial clones (and we've been\nflinging lots of RFCs at each other that each seem to have a different\nbase assumption (small-blobs-only vs sparse-checkout vs <whatever>))\nand not reaching consensus or closure.\n\nThe main thing is to allow the client to use partial clone to request\na \"subset\", let the server deliver that \"subset\", and let the client\ntooling deal with an incomplete view of the repo.\n\nAs I see it there are the following major parts to partial clone:\n1. How to let git-clone (and later git-fetch) specify the desired\n    subset of objects that it wants?  (A ref-relative request.)\n2. How to let the server and git-pack-objects build that incomplete\n    packfile?\n3. How to remember in the local config that a partial clone (or\n    fetch) was used and that missing object should be expected?\n4. How to dynamically fetch individual missing objects individually?\n     (Not a ref-relative request.)\n5. How to augment the local ODB with partial clone information and\n    let git-fsck (and friends) perform limited consistency checking?\n6. Methods to bulk fetching missing objects (whether in a pre-verb\n    hook or in unpack-tree)\n7. Miscellaneous issues (e.g. fixing places that accidentally cause\n    a missing object to be fetched that don't really need it).\n\nMy proposal [1] includes a generic filtering mechanism that handles 3\ntypes of filtering and makes it easy to add other techniques as we\nsee fit.  It slips in at the list-objects / traverse_commit_list\nlevel and hides all of the details from rev-list and pack-objects.\nI have a follow on proposal [2] that extends the filtering parameter\nhandling to git-clone, git-fetch, git-fetch-pack, git-upload-pack\nand the pack protocol.  That takes care of items 1 and 2 above.\n\nJonathan's proposal [3] includes code to update the local config,\ndynamically fetch individual objects, and handle the local ODB and\nfsck consistency checking.  That takes care of items 3, 4, and 5.\n\nAs was suggested above, I think we should merge our efforts:\nusing my filtering for 1 and 2 and Jonathan's code for 3, 4, and 5.\nI would need to eliminate the \"relax\" options in favor of his\nis_promised() functionality for index-pack and similar.  And omit\nhis blob-max-bytes changes from pack-objects, the protocol and\nrelated commands.\n\nThat should be a good first step.\n\nWe both have thoughts on bulk fetching (mine in pre-verb hooks and\nhis in unpack-tree).  We don't need this immediately, but can wait\nuntil the above is working to revisit.\n\n[1] https://github.com/jeffhostetler/git/pull/3\n[2]https://github.com/jeffhostetler/git/pull/4\n[3] https://github.com/jonathantanmy/git/tree/partialclone3\n\nThoughts?\n\nThanks,\nJeff\n"},{"id":"329622","messageId":"CAGf8dgLu-TeK8KbHv-U+18O+L2TxKcGv5vFFHy38J6a_YXRfew@mail.gmail.com","threadId":"46861","inReplyTo":"cb818dcf-beab-10ac-5e58-f56377af4f6a@jeffhostetler.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2017-10-03T23:42:00Z","receivedAt":"2017-10-03T23:42:06Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"On Tue, Oct 3, 2017 at 7:39 AM, Jeff Hostetler <git@jeffhostetler.com> wrote:\n>\n> As I see it there are the following major parts to partial clone:\n> 1. How to let git-clone (and later git-fetch) specify the desired\n>    subset of objects that it wants?  (A ref-relative request.)\n> 2. How to let the server and git-pack-objects build that incomplete\n>    packfile?\n> 3. How to remember in the local config that a partial clone (or\n>    fetch) was used and that missing object should be expected?\n> 4. How to dynamically fetch individual missing objects individually?\n>     (Not a ref-relative request.)\n> 5. How to augment the local ODB with partial clone information and\n>    let git-fsck (and friends) perform limited consistency checking?\n> 6. Methods to bulk fetching missing objects (whether in a pre-verb\n>    hook or in unpack-tree)\n> 7. Miscellaneous issues (e.g. fixing places that accidentally cause\n>    a missing object to be fetched that don't really need it).\n\nThanks for the enumeration.\n\n> As was suggested above, I think we should merge our efforts:\n> using my filtering for 1 and 2 and Jonathan's code for 3, 4, and 5.\n> I would need to eliminate the \"relax\" options in favor of his\n> is_promised() functionality for index-pack and similar.  And omit\n> his blob-max-bytes changes from pack-objects, the protocol and\n> related commands.\n>\n> That should be a good first step.\n\nThis sounds good to me. Jeff Hostetler's filtering (all blobs, blobs\nby size, blobs by sparse checkout specification) is more comprehensive\nthan mine, so removing blob-max-bytes from my code is not a problem.\n\n> We both have thoughts on bulk fetching (mine in pre-verb hooks and\n> his in unpack-tree).  We don't need this immediately, but can wait\n> until the above is working to revisit.\n\nAgreed.\n"},{"id":"329685","messageId":"6ee48dca-5483-9c7f-4c10-f9ca6fba7239@jeffhostetler.com","threadId":"46861","inReplyTo":"CAGf8dgLu-TeK8KbHv-U+18O+L2TxKcGv5vFFHy38J6a_YXRfew@mail.gmail.com","subject":"Re: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches)","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2017-10-04T13:30:39Z","receivedAt":"2017-10-04T13:30:47Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 10/3/2017 7:42 PM, Jonathan Tan wrote:\n> On Tue, Oct 3, 2017 at 7:39 AM, Jeff Hostetler <git@jeffhostetler.com> wrote:\n>>\n>> As I see it there are the following major parts to partial clone:\n>> 1. How to let git-clone (and later git-fetch) specify the desired\n>>     subset of objects that it wants?  (A ref-relative request.)\n>> 2. How to let the server and git-pack-objects build that incomplete\n>>     packfile?\n>> 3. How to remember in the local config that a partial clone (or\n>>     fetch) was used and that missing object should be expected?\n>> 4. How to dynamically fetch individual missing objects individually?\n>>      (Not a ref-relative request.)\n>> 5. How to augment the local ODB with partial clone information and\n>>     let git-fsck (and friends) perform limited consistency checking?\n>> 6. Methods to bulk fetching missing objects (whether in a pre-verb\n>>     hook or in unpack-tree)\n>> 7. Miscellaneous issues (e.g. fixing places that accidentally cause\n>>     a missing object to be fetched that don't really need it).\n> \n> Thanks for the enumeration.\n> \n>> As was suggested above, I think we should merge our efforts:\n>> using my filtering for 1 and 2 and Jonathan's code for 3, 4, and 5.\n>> I would need to eliminate the \"relax\" options in favor of his\n>> is_promised() functionality for index-pack and similar.  And omit\n>> his blob-max-bytes changes from pack-objects, the protocol and\n>> related commands.\n>>\n>> That should be a good first step.\n> \n> This sounds good to me. Jeff Hostetler's filtering (all blobs, blobs\n> by size, blobs by sparse checkout specification) is more comprehensive\n> than mine, so removing blob-max-bytes from my code is not a problem.\n> \n>> We both have thoughts on bulk fetching (mine in pre-verb hooks and\n>> his in unpack-tree).  We don't need this immediately, but can wait\n>> until the above is working to revisit.\n> \n> Agreed.\n> \n\nThanks.\n\nI'll make a first pass at merging our efforts then and\npost something shortly.\n\nJeff\n\n"},{"id":"330285","messageId":"CAP8UFD2THRj7+RXmismUtUOpXQv4wM7aZsejpd_FHEOycP+ZJA@mail.gmail.com","threadId":"46861","inReplyTo":"5a9242024013345d7a3b0f63580360cdc8cc1c43.1506714999.git.jonathantanmy@google.com","subject":"Re: [PATCH 07/18] sha1_file: support lazily fetching missing objects","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2017-10-12T14:42:10Z","receivedAt":"2017-10-12T14:42:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Sep 29, 2017 at 10:11 PM, Jonathan Tan <jonathantanmy@google.com> wrote:\n\n> diff --git a/sha1_file.c b/sha1_file.c\n> index b4a67bb83..77aa0ffdf 100644\n> --- a/sha1_file.c\n> +++ b/sha1_file.c\n> @@ -29,6 +29,7 @@\n>  #include \"mergesort.h\"\n>  #include \"quote.h\"\n>  #include \"packfile.h\"\n> +#include \"fetch-object.h\"\n>\n>  const unsigned char null_sha1[GIT_MAX_RAWSZ];\n>  const struct object_id null_oid;\n> @@ -1149,6 +1150,8 @@ static int sha1_loose_object_info(const unsigned char *sha1,\n>         return (status < 0) ? status : 0;\n>  }\n>\n> +int fetch_if_missing = 1;\n> +\n>  int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi, unsigned flags)\n>  {\n>         static struct object_info blank_oi = OBJECT_INFO_INIT;\n> @@ -1157,6 +1160,7 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,\n>         const unsigned char *real = (flags & OBJECT_INFO_LOOKUP_REPLACE) ?\n>                                     lookup_replace_object(sha1) :\n>                                     sha1;\n> +       int already_retried = 0;\n>\n>         if (!oi)\n>                 oi = &blank_oi;\n> @@ -1181,28 +1185,36 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi,\n>                 }\n>         }\n>\n> -       if (!find_pack_entry(real, &e)) {\n> -               /* Most likely it's a loose object. */\n> -               if (!sha1_loose_object_info(real, oi, flags))\n> -                       return 0;\n> +retry:\n> +       if (find_pack_entry(real, &e))\n> +               goto found_packed;\n>\n> -               /* Not a loose object; someone else may have just packed it. */\n> -               if (flags & OBJECT_INFO_QUICK) {\n> -                       return -1;\n> -               } else {\n> -                       reprepare_packed_git();\n> -                       if (!find_pack_entry(real, &e))\n> -                               return -1;\n> -               }\n> +       /* Most likely it's a loose object. */\n> +       if (!sha1_loose_object_info(real, oi, flags))\n> +               return 0;\n> +\n> +       /* Not a loose object; someone else may have just packed it. */\n> +       reprepare_packed_git();\n> +       if (find_pack_entry(real, &e))\n> +               goto found_packed;\n> +\n> +       /* Check if it is a missing object */\n> +       if (fetch_if_missing && repository_format_partial_clone &&\n> +           !already_retried) {\n> +               fetch_object(repository_format_partial_clone, real);\n> +               already_retried = 1;\n> +               goto retry;\n>         }\n>\n> +       return -1;\n> +\n> +found_packed:\n>         if (oi == &blank_oi)\n>                 /*\n>                  * We know that the caller doesn't actually need the\n>                  * information below, so return early.\n>                  */\n>                 return 0;\n> -\n>         rtype = packed_object_info(e.p, e.offset, oi);\n>         if (rtype < 0) {\n>                 mark_bad_packed_object(e.p, real);\n\nInstead of adding labels and gotos, I would suggest adding a new\nfunction like the following does on top of your changes:\n\ndiff --git a/sha1_file.c b/sha1_file.c\nindex cc1aa0bd7f..02a6ed1e9b 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -1171,18 +1171,40 @@ static int sha1_loose_object_info(const\nunsigned char *sha1,\n        return (status < 0) ? status : 0;\n }\n\n+int try_find_packed_entry_or_loose_object(const unsigned char *real,\nstruct object_info *oi,\n+                                         unsigned flags, struct\npack_entry *e, int retry)\n+{\n+       if (find_pack_entry(real, e))\n+               return 1;\n+\n+       /* Most likely it's a loose object. */\n+       if (!sha1_loose_object_info(real, oi, flags))\n+               return 0;\n+\n+       /* Not a loose object; someone else may have just packed it. */\n+       reprepare_packed_git();\n+       if (find_pack_entry(real, e))\n+               return 1;\n+\n+       /* Check if it is a missing object */\n+       if (fetch_if_missing && repository_format_partial_clone && retry) {\n+               fetch_object(repository_format_partial_clone, real);\n+               return try_find_packed_entry_or_loose_object(real, oi,\nflags, e, 0);\n+       }\n+\n+       return -1;\n+}\n+\n int fetch_if_missing = 1;\n\n int sha1_object_info_extended(const unsigned char *sha1, struct\nobject_info *oi, unsigned flags)\n {\n        static struct object_info blank_oi = OBJECT_INFO_INIT;\n        struct pack_entry e;\n-       int rtype;\n+       int rtype, res;\n        const unsigned char *real = (flags & OBJECT_INFO_LOOKUP_REPLACE) ?\n                                    lookup_replace_object(sha1) :\n                                    sha1;\n-       int already_retried = 0;\n-\n        if (!oi)\n                oi = &blank_oi;\n\n@@ -1206,30 +1228,10 @@ int sha1_object_info_extended(const unsigned\nchar *sha1, struct object_info *oi,\n                }\n        }\n\n-retry:\n-       if (find_pack_entry(real, &e))\n-               goto found_packed;\n-\n-       /* Most likely it's a loose object. */\n-       if (!sha1_loose_object_info(real, oi, flags))\n-               return 0;\n-\n-       /* Not a loose object; someone else may have just packed it. */\n-       reprepare_packed_git();\n-       if (find_pack_entry(real, &e))\n-               goto found_packed;\n-\n-       /* Check if it is a missing object */\n-       if (fetch_if_missing && repository_format_partial_clone &&\n-           !already_retried) {\n-               fetch_object(repository_format_partial_clone, real);\n-               already_retried = 1;\n-               goto retry;\n-       }\n-\n-       return -1;\n+       res = try_find_packed_entry_or_loose_object(real, oi, flags, &e, 1);\n+       if (res < 1)\n+               return res;\n\n-found_packed:\n        if (oi == &blank_oi)\n                /*\n                 * We know that the caller doesn't actually need the\n"},{"id":"330289","messageId":"CAP8UFD1hivP2sRK74z-cq9+HDzD4hWcjDeQe5tHbr_LcygtQUQ@mail.gmail.com","threadId":"46861","inReplyTo":"CAP8UFD2THRj7+RXmismUtUOpXQv4wM7aZsejpd_FHEOycP+ZJA@mail.gmail.com","subject":"Re: [PATCH 07/18] sha1_file: support lazily fetching missing objects","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2017-10-12T15:45:11Z","receivedAt":"2017-10-12T15:45:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Oct 12, 2017 at 4:42 PM, Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> Instead of adding labels and gotos, I would suggest adding a new\n> function like the following does on top of your changes:\n\nSorry for the usual Gmail related problems. You can find the patch and\na further refactoring that adds an object_info_from_pack_entry()\nfunction there:\n\nhttps://github.com/chriscool/git/commits/pu-partial-clone-lazy-fetch-improved\n"}]}