{"thread":{"id":"53856","subject":"Empty bundles are not useless","startedAt":"2020-07-14T13:59:05Z","lastAt":"2020-07-14T21:47:30Z","messageCount":2,"participants":["R. Lemos","Johannes Schindelin"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"401548","messageId":"CAM6_cxzzWJQpw9akM=JZAnZ0LrPBCPX+q1da8_24vOJexCKeHA@mail.gmail.com","threadId":"53856","inReplyTo":null,"subject":"Empty bundles are not useless","fromName":"R. Lemos","fromEmail":"rslemos@gmail.com","sentAt":"2020-07-14T13:58:50Z","receivedAt":"2020-07-14T13:59:05Z","isPatch":false,"sender":{"key":"rslemos@gmail.com","avatar":null},"body":"When working disconnected (sending bundles back and forth) it is\nuseful to make bundles like this:\n\n$ git bundle create /tmp/my.bundle master ^disconnected/master\n\nThis way I pack all new commits since the last updated from\n`disconnected` remote.\n\nBut if my local copy is up-to-date, git bundle refuses to create an\nempty bundle, since there are no commits to send.\n\nHowever it would be nice to at least send the information that my\nlocal branch `master` is up-to-date. That is, the bundle would just\nconvey that my local `master` points to a given commit (even though\nthis commit is already on the remote site). This way, when I upload\nthis bundle to the remote site, then bring back changes made there,\nthe remote site will know which commits I already have.\n\nThanks.\nRodrigo Lemos\n"},{"id":"401567","messageId":"nycvar.QRO.7.76.6.2007142331160.52@tvgsbejvaqbjf.bet","threadId":"53856","inReplyTo":"CAM6_cxzzWJQpw9akM=JZAnZ0LrPBCPX+q1da8_24vOJexCKeHA@mail.gmail.com","subject":"Re: Empty bundles are not useless","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-07-14T21:47:25Z","receivedAt":"2020-07-14T21:47:30Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Rodrigo,\n\nOn Tue, 14 Jul 2020, R. Lemos wrote:\n\n> When working disconnected (sending bundles back and forth) it is\n> useful to make bundles like this:\n>\n> $ git bundle create /tmp/my.bundle master ^disconnected/master\n>\n> This way I pack all new commits since the last updated from\n> `disconnected` remote.\n>\n> But if my local copy is up-to-date, git bundle refuses to create an\n> empty bundle, since there are no commits to send.\n>\n> However it would be nice to at least send the information that my\n> local branch `master` is up-to-date. That is, the bundle would just\n> convey that my local `master` points to a given commit (even though\n> this commit is already on the remote site). This way, when I upload\n> this bundle to the remote site, then bring back changes made there,\n> the remote site will know which commits I already have.\n\nI am sympathetic to your use case (I certainly had wished for something\nlike this in the past), but it is not quite as trivial as you might think:\nany bundle lists its \"wants\" and \"haves\" first, i.e. the commits that it\nexpects to be present and the tip commits it provides, respectively. And\nherein lies the rub: in your case, the tip commit would technically belong\nto both \"wants\" and \"haves\", and that cannot be represented by the simple\n`git rev-list --boundary --pretty=oneline <rev-list-args>` call that\n`compute_and_write_prerequisites()` in `bundle.c` performs in order to\ngenerate that wants/haves list.\n\nYou could of course implement something in `write_bundle_refs()` that\n(guarded by a new option, so as to maintain backwards-compatibility) would\nlist commits marked as `UNINTERESTING` both as \"wants\" and \"haves\", but\nthat might become a bit tricky e.g. handling commit ranges like\n`HEAD~2..HEAD` (where `HEAD~2` would be marked `UNINTERESTING`).\n\nSide note: if it is not clear to you what I mean by `UNINTERESTING`,\nplease read the excellent tutorial at:\nhttps://github.com/git/git/blob/v2.28.0-rc0/Documentation/MyFirstObjectWalk.txt\n\nOnce you worked out how to write out such commits both as prerequisites as\nwell as refs in the bundle, the rest should be relatively easy: a new test\ncase in `t/t5607-clone-bundle.sh` to verify that empty bundles can be\ncreated (using a new command-line option that allows precisely that) and\nsomething like this (plus the corresponding docs in\n`Documentation/git-bundle.txt`):\n\n-- snip --\ndiff --git a/bundle.c b/bundle.c\nindex 99439e07a106..74f2759a2068 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -11,6 +11,7 @@\n #include \"run-command.h\"\n #include \"refs.h\"\n #include \"argv-array.h\"\n+#include \"parse-options.h\"\n\n static const char bundle_signature[] = \"# v2 git bundle\\n\";\n\n@@ -436,6 +437,21 @@ int create_bundle(struct repository *r, const char *path,\n \tint bundle_to_stdout;\n \tint ref_count = 0;\n \tstruct rev_info revs;\n+\tint allow_empty_bundle = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"allow-empty-bundle\",\n+\t\t\t &allow_empty_bundle,\n+\t\t\t N_(\"allow an empty bundle to be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char * const create_usage[] = {\n+\t\tN_(\"git bundle create [<options>] <file> <git-rev-list args>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, options, create_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n\n \tbundle_to_stdout = !strcmp(path, \"-\");\n \tif (bundle_to_stdout)\n@@ -465,7 +481,7 @@ int create_bundle(struct repository *r, const char *path,\n \tobject_array_remove_duplicates(&revs.pending);\n\n \tref_count = write_bundle_refs(bundle_fd, &revs);\n-\tif (!ref_count)\n+\tif (!ref_count && !allow_empty_bundle)\n \t\tdie(_(\"Refusing to create empty bundle.\"));\n \telse if (ref_count < 0)\n \t\tgoto err;\n-- snap --\n\nHopefully this gets you started with \"scratching your own itch\".\n\nCiao,\nJohannes\n"}]}