* [RFC/PATCH 1/3] mailinfo: refactor commit message processing
From: Jonathan Tan @ 2016-09-16 17:37 UTC (permalink / raw)
To: git; +Cc: Jonathan Tan, peff, gitster
In-Reply-To: <cover.1474047135.git.jonathantanmy@google.com>
Within the processing of the commit message, check for a scissors line
or a patchbreak line first (before checking for in-body headers) so that
a subsequent patch modifying the processing of in-body headers would not
cause a scissors line or patchbreak line to be misidentified.
If a line could be both an in-body header and a scissors line (for
example, "From: -- >8 --"), this is considered a fatal error
(previously, it would be interpreted as an in-body header). (It is not
possible for a line to be both an in-body header and a patchbreak line,
since both require different prefixes.)
The following enumeration shows that processing is the same except (as
described above) the in-body header + scissors line case.
o in-body header (check_header OK)
o passes UTF-8 conversion
o [described above] is scissors line
o [not possible] is patchbreak line
o [not possible] is blank line
o is none of the above - processed as header
o fails UTF-8 conversion - processed as header
o not in-body header
o passes UTF-8 conversion
o is scissors line - processed as scissors
o is patchbreak line - processed as patchbreak
o is blank line - ignored if in header_stage
o is none of the above - log message
o fails UTF-8 conversion - input error
As for the result left in "line" (after the invocation of
handle_commit_msg), it is unused (by its caller, handle_filter, and by
handle_filter's callers, handle_boundary and handle_body) unless this
line is a patchbreak line, in which case handle_patch is subsequently
called (in handle_filter) on "line". In this case, "line" must have
passed UTF-8 conversion both before and after this patch, so the result
is still the same overall.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
---
mailinfo.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 115 insertions(+), 30 deletions(-)
diff --git a/mailinfo.c b/mailinfo.c
index e19abe3..23a56c2 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -340,23 +340,56 @@ static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
return out;
}
-static int convert_to_utf8(struct mailinfo *mi,
- struct strbuf *line, const char *charset)
+/*
+ * Attempts to convert line into UTF-8, storing the result in line.
+ *
+ * This differs from convert_to_utf8 in that conversion non-success is not
+ * considered an error case - mi->input_error is not set, and no error message
+ * is printed.
+ *
+ * If the conversion is unnecessary, returns 0 and stores NULL in old_buf (if
+ * old_buf is not NULL).
+ *
+ * If the conversion is successful, returns 0 and stores the unconverted string
+ * in old_buf and old_len (if they are respectively not NULL).
+ *
+ * If the conversion is unsuccessful, returns -1.
+ */
+static int try_convert_to_utf8(const struct mailinfo *mi, struct strbuf *line,
+ const char *charset, char **old_buf,
+ size_t *old_len)
{
- char *out;
+ char *utf8;
- if (!mi->metainfo_charset || !charset || !*charset)
+ if (!mi->metainfo_charset || !charset || !*charset ||
+ same_encoding(mi->metainfo_charset, charset)) {
+ if (old_buf)
+ *old_buf = NULL;
return 0;
+ }
- if (same_encoding(mi->metainfo_charset, charset))
+ utf8 = reencode_string(line->buf, mi->metainfo_charset, charset);
+ if (utf8) {
+ char *temp = strbuf_detach(line, old_len);
+ if (old_buf)
+ *old_buf = temp;
+ strbuf_attach(line, utf8, strlen(utf8), strlen(utf8));
return 0;
- out = reencode_string(line->buf, mi->metainfo_charset, charset);
- if (!out) {
+ }
+ return -1;
+}
+
+/*
+ * Converts line into UTF-8, setting mi->input_error to -1 upon failure.
+ */
+static int convert_to_utf8(struct mailinfo *mi,
+ struct strbuf *line, const char *charset)
+{
+ if (try_convert_to_utf8(mi, line, charset, NULL, NULL)) {
mi->input_error = -1;
return error("cannot convert from %s to %s",
charset, mi->metainfo_charset);
}
- strbuf_attach(line, out, strlen(out), strlen(out));
return 0;
}
@@ -515,6 +548,13 @@ static int check_header(struct mailinfo *mi,
return ret;
}
+static int check_header_raw(struct mailinfo *mi,
+ char *buf, size_t len,
+ struct strbuf *hdr_data[], int overwrite) {
+ const struct strbuf sb = {0, len, buf};
+ return check_header(mi, &sb, hdr_data, overwrite);
+}
+
static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
{
struct strbuf *ret;
@@ -623,32 +663,48 @@ static int is_scissors_line(const struct strbuf *line)
gap * 2 < perforation);
}
-static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+static int resembles_rfc2822_header(const struct strbuf *line)
{
- assert(!mi->filter_stage);
+ char *c;
- if (mi->header_stage) {
- if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
+ if (!isalpha(line->buf[0]))
+ return 0;
+
+ for (c = line->buf + 1; *c != 0; c++) {
+ if (*c == ':')
+ return 1;
+ else if (*c != '-' && !isalpha(*c))
return 0;
}
+ return 0;
+}
- if (mi->use_inbody_headers && mi->header_stage) {
- mi->header_stage = check_header(mi, line, mi->s_hdr_data, 0);
- if (mi->header_stage)
- return 0;
- } else
- /* Only trim the first (blank) line of the commit message
- * when ignoring in-body headers.
- */
- mi->header_stage = 0;
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+ int ret = 0;
+ int utf8_result;
+ char *old_buf;
+ size_t old_len;
+
+ assert(!mi->filter_stage);
- /* normalize the log message to UTF-8. */
- if (convert_to_utf8(mi, line, mi->charset.buf))
- return 0; /* mi->input_error already set */
+ /*
+ * Obtain UTF8 for scissors line and patchbreak checks, but retain the
+ * undecoded line in case we need to process it as an in-body header.
+ */
+ utf8_result = try_convert_to_utf8(mi, line, mi->charset.buf, &old_buf,
+ &old_len);
- if (mi->use_scissors && is_scissors_line(line)) {
+ if (!utf8_result && mi->use_scissors && is_scissors_line(line)) {
int i;
+ if (resembles_rfc2822_header(line))
+ /*
+ * Explicitly reject scissor lines that resemble a RFC
+ * 2822 header, to avoid being prone to error.
+ */
+ die("scissors line resembles RFC 2822 header");
+
strbuf_setlen(&mi->log_message, 0);
mi->header_stage = 1;
@@ -661,18 +717,47 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
strbuf_release(mi->s_hdr_data[i]);
mi->s_hdr_data[i] = NULL;
}
- return 0;
+ goto handle_commit_msg_out;
}
-
- if (patchbreak(line)) {
+ if (!utf8_result && patchbreak(line)) {
if (mi->message_id)
strbuf_addf(&mi->log_message,
"Message-Id: %s\n", mi->message_id);
- return 1;
+ ret = 1;
+ goto handle_commit_msg_out;
}
+ if (mi->header_stage) {
+ char *buf = old_buf ? old_buf : line->buf;
+ if (buf[0] == 0 || (buf[0] == '\n' && buf[1] == 0))
+ goto handle_commit_msg_out;
+ }
+
+ if (mi->use_inbody_headers && mi->header_stage) {
+ char *buf = old_buf ? old_buf : line->buf;
+ size_t len = old_buf ? old_len : line->len;
+ mi->header_stage = check_header_raw(mi, buf, len,
+ mi->s_hdr_data, 0);
+ if (mi->header_stage)
+ goto handle_commit_msg_out;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ mi->header_stage = 0;
+
+ /* If adding as a log message, conversion to UTF-8 is required. */
+ if (utf8_result) {
+ mi->input_error = -1;
+ error("cannot convert from %s to %s",
+ mi->charset.buf, mi->metainfo_charset);
+ goto handle_commit_msg_out;
+ }
strbuf_addbuf(&mi->log_message, line);
- return 0;
+
+handle_commit_msg_out:
+ free(old_buf);
+ return ret;
}
static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
--
2.10.0.rc2.20.g5b18e70
^ permalink raw reply related
* [RFC/PATCH 0/3] handle multiline in-body headers
From: Jonathan Tan @ 2016-09-16 17:37 UTC (permalink / raw)
To: git; +Cc: Jonathan Tan, peff, gitster
In-Reply-To: <20160907063819.dd7aulnlsytcuyqj@sigill.intra.peff.net>
Thanks, Peff, for the explanation and the method to reproduce the issue.
The issue seems to be in mailinfo.c - this patch set addresses that, and I have
also included a test for "git am" in t/t4150-am.sh to show the effect of this
patch set on that command.
Jonathan Tan (3):
mailinfo: refactor commit message processing
mailinfo: correct malformed test example
mailinfo: handle in-body header continuations
mailinfo.c | 165 ++++++++++++++++++++++++++++-------
mailinfo.h | 1 +
t/t4150-am.sh | 23 +++++
t/t5100-mailinfo.sh | 4 +-
t/t5100/info0008--no-inbody-headers | 5 ++
t/t5100/info0018 | 5 ++
t/t5100/msg0008--no-inbody-headers | 6 ++
t/t5100/msg0015--no-inbody-headers | 1 +
t/t5100/msg0018 | 2 +
t/t5100/patch0008--no-inbody-headers | 0
t/t5100/patch0018 | 6 ++
t/t5100/sample.mbox | 20 +++++
12 files changed, 206 insertions(+), 32 deletions(-)
create mode 100644 t/t5100/info0008--no-inbody-headers
create mode 100644 t/t5100/info0018
create mode 100644 t/t5100/msg0008--no-inbody-headers
create mode 100644 t/t5100/msg0018
create mode 100644 t/t5100/patch0008--no-inbody-headers
create mode 100644 t/t5100/patch0018
--
2.10.0.rc2.20.g5b18e70
^ permalink raw reply
* Re: [PATCH] format-patch: Add --rfc for the common case of [RFC PATCH]
From: Jacob Keller @ 2016-09-16 17:34 UTC (permalink / raw)
To: Josh Triplett; +Cc: Git mailing list, Andrew Donnellan
In-Reply-To: <28c5d2c59851279858df22e844c6ff7c09f33199.1474046573.git-series.josh@joshtriplett.org>
On Fri, Sep 16, 2016 at 10:27 AM, Josh Triplett <josh@joshtriplett.org> wrote:
> This provides a shorter and more convenient alias for
> --subject-prefix='RFC PATCH'.
>
> Add a test covering --rfc.
>
> Signed-off-by: Josh Triplett <josh@joshtriplett.org>
> ---
>
> By far, the most common subject-prefix I've seen other than "PATCH" is
> "RFC PATCH" (or occasionally "PATCH RFC"). Seems worth optimizing for
> the common case, to avoid having to spell it out the long way as
> --subject-prefix='RFC PATCH'.
>
I agree!
Thanks,
Jake
^ permalink raw reply
* [PATCH] format-patch: Add --rfc for the common case of [RFC PATCH]
From: Josh Triplett @ 2016-09-16 17:27 UTC (permalink / raw)
To: git; +Cc: Andrew Donnellan
This provides a shorter and more convenient alias for
--subject-prefix='RFC PATCH'.
Add a test covering --rfc.
Signed-off-by: Josh Triplett <josh@joshtriplett.org>
---
By far, the most common subject-prefix I've seen other than "PATCH" is
"RFC PATCH" (or occasionally "PATCH RFC"). Seems worth optimizing for
the common case, to avoid having to spell it out the long way as
--subject-prefix='RFC PATCH'.
builtin/log.c | 10 ++++++++++
t/t4014-format-patch.sh | 9 +++++++++
2 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 92dc34d..48d6a38 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1112,6 +1112,13 @@ static int subject_prefix_callback(const struct option *opt, const char *arg,
return 0;
}
+static int rfc_callback(const struct option *opt, const char *arg, int unset)
+{
+ subject_prefix = 1;
+ ((struct rev_info *)opt->value)->subject_prefix = xstrdup("RFC PATCH");
+ return 0;
+}
+
static int numbered_cmdline_opt = 0;
static int numbered_callback(const struct option *opt, const char *arg,
@@ -1419,6 +1426,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("start numbering patches at <n> instead of 1")),
OPT_INTEGER('v', "reroll-count", &reroll_count,
N_("mark the series as Nth re-roll")),
+ { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
+ N_("Use [RFC PATCH] instead of [PATCH]"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
N_("Use [<prefix>] instead of [PATCH]"),
PARSE_OPT_NONEG, subject_prefix_callback },
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b0579dd..81b0498 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1073,6 +1073,15 @@ test_expect_success 'empty subject prefix does not have extra space' '
test_cmp expect actual
'
+cat >expect <<'EOF'
+Subject: [RFC PATCH 1/1] header with . in it
+EOF
+test_expect_success '--rfc' '
+ git format-patch -n -1 --stdout --rfc >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
test_expect_success '--from=ident notices bogus ident' '
test_must_fail git format-patch -1 --stdout --from=foo >patch
'
base-commit: 6ebdac1bab966b720d776aa43ca188fe378b1f4b
--
git-series 0.8.10
^ permalink raw reply related
* Re: [PATCH 1/2] serialize collection of changed submodules
From: Junio C Hamano @ 2016-09-16 17:27 UTC (permalink / raw)
To: Heiko Voigt
Cc: Jeff King, Stefan Beller, git@vger.kernel.org, Jens Lehmann,
Fredrik Gustafsson, Leandro Lucarella
In-Reply-To: <20160914173124.GA7613@sandbox>
Heiko Voigt <hvoigt@hvoigt.net> writes:
> +static struct sha1_array *get_sha1s_from_list(struct string_list *submodules,
> + const char *path)
> +{
> + struct string_list_item *item;
> + struct sha1_array *hashes;
> +
> + item = string_list_insert(submodules, path);
> + if (item->util)
> + return (struct sha1_array *) item->util;
> +
> + hashes = (struct sha1_array *) xmalloc(sizeof(struct sha1_array));
> + /* NEEDSWORK: should we add an initializer function for
> + * sha1_array ? */
> + memset(hashes, 0, sizeof(struct sha1_array));
> + item->util = hashes;
/* NEEDSWORK: should we have SHA1_ARRAY_INIT etc.? */
item->util = xcalloc(1, sizeof(struct sha1_array));
> static void collect_submodules_from_diff(struct diff_queue_struct *q,
> struct diff_options *options,
> void *data)
> {
> int i;
> - struct string_list *needs_pushing = data;
> + struct string_list *submodules = data;
>
> for (i = 0; i < q->nr; i++) {
> struct diff_filepair *p = q->queue[i];
> + struct sha1_array *hashes;
> if (!S_ISGITLINK(p->two->mode))
> continue;
> - if (submodule_needs_pushing(p->two->path, p->two->oid.hash))
> - string_list_insert(needs_pushing, p->two->path);
> + hashes = get_sha1s_from_list(submodules, p->two->path);
> + sha1_array_append(hashes, p->two->oid.hash);
> }
> }
So the idea at this step is still let each commit in the top-level
history inspected for any submodule change, but the result is
collected in a mapping (submodule -> [ list of submodule commits ]).
As we do not expect too many "oops, the old commit was better, so
let's revert and rebind the old one from the submodule" in the
history of the top-level, appending and then running for-each-unique
is an efficient way, instead of first checking if we already have
it and then inserting new ones to maintain the uniqueness.
Makes sense.
> @@ -582,14 +601,41 @@ static void find_unpushed_submodule_commits(struct commit *commit,
> diff_tree_combined_merge(commit, 1, &rev);
> }
>
> +struct collect_submodule_from_sha1s_data {
> + char *submodule_path;
> + struct string_list *needs_pushing;
> +};
> +
> +static void collect_submodules_from_sha1s(const unsigned char sha1[20],
> + void *data)
> +{
> + struct collect_submodule_from_sha1s_data *me =
> + (struct collect_submodule_from_sha1s_data *) data;
> +
> + if (submodule_needs_pushing(me->submodule_path, sha1))
> + string_list_insert(me->needs_pushing, me->submodule_path);
> +}
This is called from sha1_array_for_each_unique() that iterates over
the submodule commit object names for one submodule and then ends up
calling submodule_needs_pushing() number of times, which smells less
efficient than it could be. You can ask
rev-list <all the submodule commits to be pushed> --not --remotes
just once in the submodule repository. I imagine that is what you'll
do in the next patch.
An obvious but much less efficient way to optimize this part would
be to see if me->needs_pushing already has me->submodule_path and
skip the check for submodule_needs_pushing(), but if you drop the
call by find_unpushed_submodule to sha1_array_for_each_unique() to
walk new submodule commits one by one, that would become irrelevant.
> +static void free_submodules_sha1s(struct string_list *submodules)
> +{
> + int i;
> + for (i = 0; i < submodules->nr; i++) {
> + struct string_list_item *item = &submodules->items[i];
> + struct sha1_array *hashes = (struct sha1_array *) item->util;
> + sha1_array_clear(hashes);
> + }
> + string_list_clear(submodules, 1);
> +}
> +
> int find_unpushed_submodules(unsigned char new_sha1[20],
> const char *remotes_name, struct string_list *needs_pushing)
> {
> struct rev_info rev;
> struct commit *commit;
> const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
> - int argc = ARRAY_SIZE(argv) - 1;
> + int argc = ARRAY_SIZE(argv) - 1, i;
> char *sha1_copy;
> + struct string_list submodules = STRING_LIST_INIT_DUP;
>
> struct strbuf remotes_arg = STRBUF_INIT;
>
> @@ -603,12 +649,23 @@ int find_unpushed_submodules(unsigned char new_sha1[20],
> die("revision walk setup failed");
>
> while ((commit = get_revision(&rev)) != NULL)
> - find_unpushed_submodule_commits(commit, needs_pushing);
> + find_unpushed_submodule_commits(commit, &submodules);
>
> reset_revision_walk();
> free(sha1_copy);
> strbuf_release(&remotes_arg);
>
> + for (i = 0; i < submodules.nr; i++) {
> + struct string_list_item *item = &submodules.items[i];
> + struct collect_submodule_from_sha1s_data data;
> + data.submodule_path = item->string;
> + data.needs_pushing = needs_pushing;
> + sha1_array_for_each_unique((struct sha1_array *) item->util,
> + collect_submodules_from_sha1s,
> + &data);
> + }
> + free_submodules_sha1s(&submodules);
> +
> return needs_pushing->nr;
> }
^ permalink raw reply
* Re: [PATCH v2 1/1] git-p4: Add --checkpoint-period option to sync/clone
From: Lars Schneider @ 2016-09-16 16:19 UTC (permalink / raw)
To: Ori Rawlings; +Cc: git, Vitor Antunes, Luke Diamand, Pete Wyckoff
In-Reply-To: <728dfb8e2bf4aa9f6297eada7b8e8a107fd382e6.1473973732.git-series.orirawlings@gmail.com>
> On 15 Sep 2016, at 23:17, Ori Rawlings <orirawlings@gmail.com> wrote:
>
> Importing a long history from Perforce into git using the git-p4 tool
> can be especially challenging. The `git p4 clone` operation is based
> on an all-or-nothing transactionality guarantee. Under real-world
> conditions like network unreliability or a busy Perforce server,
> `git p4 clone` and `git p4 sync` operations can easily fail, forcing a
> user to restart the import process from the beginning. The longer the
> history being imported, the more likely a fault occurs during the
> process. Long enough imports thus become statistically unlikely to ever
> succeed.
>
> The underlying git fast-import protocol supports an explicit checkpoint
> command. The idea here is to optionally allow the user to force an
> explicit checkpoint every <x> seconds. If the sync/clone operation fails
> branches are left updated at the appropriate commit available during the
> latest checkpoint. This allows a user to resume importing Perforce
> history while only having to repeat at most approximately <x> seconds
> worth of import activity.
This looks interesting! I ran into the same issue and added a parameter to the p4 commands to retry (patch not yet proposed to the mailing list):
https://github.com/autodesk-forks/git/commit/fcfc96a7814935ee6cefb9d69e44def30a90eabb
Would it make sense to print the "git-p4 resume command" in case an error happens and checkpoints are written?
Cheers,
Lars
^ permalink raw reply
* Re: [wishlist?] make submodule commands robust to having non-submodule Subprojects
From: Jacob Keller @ 2016-09-16 15:40 UTC (permalink / raw)
To: Heiko Voigt
Cc: Junio C Hamano, Stefan Beller, Yaroslav Halchenko,
git@vger.kernel.org
In-Reply-To: <20160916141143.GA47240@book.hvoigt.net>
On Fri, Sep 16, 2016 at 7:11 AM, Heiko Voigt <hvoigt@hvoigt.net> wrote:
> How about just
>
> git submodule add <submodulepath>
>
> ? I remember back in the days when I started with submodules thats the
> way I imagined submodules would work:
>
> 1. clone the submodule into a directory
> 2. git submodule add it
> 3. git commit everything
>
> Because that how you basically work with files. So instead of adding
> another option I would rather like to autodetect that:
>
> * its a relative path inside this repo that is passed to
> 'git submodule add'
> * there is no .gitmodules entry
> * and no .git/config
> ==> create those from a remote in the submodule
>
> Corner cases:
>
> * If there is more than one remote we could tell the user to use an
> option to specify which one to use.
> * Barf in case there is no remote (not adding the submodule except -f
> is used).
> * If the gitlink is already there but no .gitmodules entry, 'git
> submodule add' will just add the entry as if it was initially added.
>
> Instead of giving an error message that the submodule is already added
> we could actually be nicer to the user and try to fix things for him
> instead.
>
This makes sense to me. Possibly we could warn in this case, so that
the user knows that something was "off" but I don't think we should be
failing here...
Regards,
Jake
> Cheers Heiko
^ permalink raw reply
* Re: [wishlist?] make submodule commands robust to having non-submodule Subprojects
From: Heiko Voigt @ 2016-09-16 14:11 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Stefan Beller, Yaroslav Halchenko, git@vger.kernel.org
In-Reply-To: <xmqqsht1nhlh.fsf@gitster.mtv.corp.google.com>
On Thu, Sep 15, 2016 at 11:27:54AM -0700, Junio C Hamano wrote:
> Stefan Beller <sbeller@google.com> writes:
> > So how about this fictional work flow:
> >
> > $ git init top
> > $ cd top
> > $ git commit --allow-empty -m 'initial in top'
> > $ git init sub
> > $ git -C sub commit --allow-empty -m 'initial in sub'
> > $ git add sub
> > You added a gitlink, but no corresponding entry in
> > .gitmodules is found. This is fine for gits core functionality, but
> > the submodule command gets confused by this unless you add 'sub'
> > to your .gitmodules via `git submodule add --already-in-tree \
> > --reuse-submodules-origin-as-URL sub`. Alternatively you can make this
> > message disappear by configuring advice.gitlinkPitfalls.
>
> I am not sure if I agree with that direction.
>
> If the trend in Git community collectively these days is to make
> usage of submodules easier and smoother, I'd imagine that you would
> want to teach "git add" that was given a submodule to "git submodule
> add" instead by default, with an option "git add --no-gitmodules
> sub" to disable it, or something like that.
>
> > $ git submodule add --fixup-modules-file ./sub sub
> > Adding .gitmodule entry only for `sub` to use `git -C remote
> > show origin` as URL.
>
> I agree that a feature like this is needed regardless of what
> happens at "git add" time.
How about just
git submodule add <submodulepath>
? I remember back in the days when I started with submodules thats the
way I imagined submodules would work:
1. clone the submodule into a directory
2. git submodule add it
3. git commit everything
Because that how you basically work with files. So instead of adding
another option I would rather like to autodetect that:
* its a relative path inside this repo that is passed to
'git submodule add'
* there is no .gitmodules entry
* and no .git/config
==> create those from a remote in the submodule
Corner cases:
* If there is more than one remote we could tell the user to use an
option to specify which one to use.
* Barf in case there is no remote (not adding the submodule except -f
is used).
* If the gitlink is already there but no .gitmodules entry, 'git
submodule add' will just add the entry as if it was initially added.
Instead of giving an error message that the submodule is already added
we could actually be nicer to the user and try to fix things for him
instead.
Cheers Heiko
^ permalink raw reply
* Re: [PATCH 3/2] batch check whether submodule needs pushing into one call
From: Heiko Voigt @ 2016-09-16 12:31 UTC (permalink / raw)
To: Junio C Hamano
Cc: Jeff King, Stefan Beller, git@vger.kernel.org, Jens Lehmann,
Fredrik Gustafsson, Leandro Lucarella
In-Reply-To: <20160916094019.GB1488@book.hvoigt.net>
On Fri, Sep 16, 2016 at 11:40:19AM +0200, Heiko Voigt wrote:
> > By the way, with the two new patches, 'pu' seems to start failing
> > some tests, e.g. 5533 5404 5405.
>
> Ah ok I did only test on master, will look into those.
Ok I had a look into these and the reason t5533 fails is because on pu
--recurse-submodules is enabled by default and I missed the case when
overwriting a ref. In that case we get the sha1 from the remote side as
old. So we could catch that and fall back to all revisions there, but...
... tl;dr: The solution to use the old revisions from the remote side
was too simple and does not make matters better but actually worse for
some typical usecases. Its only in the last patch.
... that lead me to further thinking about the previous solution (using
the locally cached remote refs) which might actually be a good default
for the non-fastforward cases like creating new ref or overwriting a
ref.
My current patch would handle the --mirror case nicer, since it gets a
lot of old revs to reduce the revisions to check. For the typical one
branch push it would most likely be worse. Except when the user is
updating (fast-forwarding) the remote ref we would scan all revs of a
ref until the root because we do not get enough valid revs that already
exist on the remote.
The most exact solution would be to use all actual remote refs available
(not sure if we have them at this point in the process?) another
solution would be to still append the --remotes=<remotename> option as a
fallback to reduce the revisions.
What do others think? Will leave this for a little bit further thinking.
Its just the last patch ("use actual start hashes for submodule push
check instead of local refs") that needs to go back to the drawing
board.
Cheers Heiko
^ permalink raw reply
* Re: [PATCH 3/2] batch check whether submodule needs pushing into one call
From: Heiko Voigt @ 2016-09-16 9:40 UTC (permalink / raw)
To: Junio C Hamano
Cc: Jeff King, Stefan Beller, git@vger.kernel.org, Jens Lehmann,
Fredrik Gustafsson, Leandro Lucarella
In-Reply-To: <xmqq1t0kna51.fsf@gitster.mtv.corp.google.com>
On Thu, Sep 15, 2016 at 02:08:58PM -0700, Junio C Hamano wrote:
> Heiko Voigt <hvoigt@hvoigt.net> writes:
>
> > if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
> > struct child_process cp = CHILD_PROCESS_INIT;
> > - const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
> > +
> > + argv_array_push(&cp.args, "rev-list");
> > + sha1_array_for_each_unique(hashes, append_hash_to_argv, &cp.args);
> > + argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL);
> > +
> > struct strbuf buf = STRBUF_INIT;
> > int needs_pushing = 0;
>
> These two become decl-after-stmt; move your new lines a bit lower,
> perhaps?
Thanks, missed those. Will do.
> > - argv[1] = sha1_to_hex(sha1);
> > - cp.argv = argv;
> > prepare_submodule_repo_env(&cp.env_array);
>
> By the way, with the two new patches, 'pu' seems to start failing
> some tests, e.g. 5533 5404 5405.
Ah ok I did only test on master, will look into those.
Cheers Heiko
^ permalink raw reply
* Re: [RFC] extending pathspec support to submodules
From: Heiko Voigt @ 2016-09-16 9:34 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, Brandon Williams, git@vger.kernel.org, Duy Nguyen,
Jens Lehmann
In-Reply-To: <CAGZ79kZJUQhY_bEi1G3zMYR2iGq5gosfVsBP_CFoaMydXP6QUw@mail.gmail.com>
Hi,
On Thu, Sep 15, 2016 at 03:28:21PM -0700, Stefan Beller wrote:
> On Thu, Sep 15, 2016 at 3:08 PM, Junio C Hamano <gitster@pobox.com> wrote:
> > Brandon Williams <bmwill@google.com> writes:
> >
> >> You're right that seems like the best course of action and it already falls
> >> inline with what I did with a first patch to ls-files to support submodules.
> >> In that patch I did exactly as you suggest and pass in the prefix to the
> >> submodule and make the child responsible for prepending the prefix to all of
> >> its output. This way we can simply pass through the whole pathspec (as apposed
> >> to my original idea of stripping the prefix off the pathspec prior to passing
> >> it to the child...which can get complicated with wild characters) to the
> >> childprocess and when checking if a file matches the pathspec we can check if
> >> the prefix + file path matches.
> >
> > That's brilliant. A few observations.
> >
> > * With that change to tell the command that is spawned in a
> > submodule directory where the submodule repository is in the
> > context of the top-level superproject _and_ require it to take a
> > pathspec as relative to the top-level superproject, you no longer
> > worry about having to find where to cut the pathspec given at the
> > top-level to adjust it for the submodule's context. That may
> > simplify things.
>
> I wonder how this plays together with the prefix in the superproject, e.g.
>
> cd super/unrelated-path
> # when invoking a git command the internal prefix is "unrelated-path/"
> git ls-files ../submodule-*
> # a submodule in submodule-A would be run in submodule-A
> # with a superproject prefix of super/ ? but additionally we nned
> to know we're
> # not at the root of the superproject.
Do we need to know that? The internal prefix is internal to each
repository and can be treated as such. I would expect that the prefix is
only prefixed when needed. E.g. when we display output to the user,
match files, ...
How about "../submodule-A" as the submodule prefix in the situation you
describe? The wildcard would be resolved by the superproject since the
directory is still in its domain.
I would think of the submodule prefix as the path relative to the
command that started everything. E.g. if we have a tree like this:
*--subA
/
super *--subB--subsubB
\
*--dirC
where subA, subB and subsubB are submodules and dirC is just a
directory inside super.
We would get the following prefixes when issuing a command in dirC that
has a pathspec for subsubB:
subB: ../subB
subsubB: ../subB/subsubB
An interesting case is when we issue a command in subA:
super: ..
subB: ../subB
subsubB: ../subB/subsubB
A rule for the prefix option could be: Always specified when crossing a
repository boundary with the pathspec (including upwards).
I have not completely thought this through though so just take this as
some food for thought. Since I am not sure what Junio's rationale behind
making the prefix relative to the toplevel superproject was, but I guess
finding it could be a challenge in some situations. I.e. is the
repository in home directory tracking all the dot-files really the
superproject or was it that other one I found before?
> > So we may have to rethink what this option name should be. "You
> > are running in a repository that is used as a submodule in a
> > larger context, which has the submodule at this path" is what the
> > option tells the command; if any existing command already has
> > such an option, we should use it. If we are inventing one,
> > perhaps "--submodule-path" (I didn't check if there are existing
> > options that sound similar to it and mean completely different
> > things, in which case that name is not usable)?
>
> Would it make sense to add the '--submodule-path' to a more generic
> part of the code? It's not just ls-files/grep that have to solve exactly this
> problem. Up to now we just did not go for those commands, though.
Yes I think so, since it should also handle starting from a submodule
with a pathspec to the superproject or other submodule. In case we
go with my above suggestion I would suggest a more generic name since
the option could also be passed to processes handling the superproject.
E.g. something like --module-prefix or --repository-prefix comes to my
mind, not checked though.
Cheers Heiko
^ permalink raw reply
* [PATCH] Documentation/config: default for color.* is color.ui
From: Matthieu Moy @ 2016-09-16 7:32 UTC (permalink / raw)
To: gitster; +Cc: git, Anatoly Borodin, Matthieu Moy
In-Reply-To: <nrfihd@blaine.gmane.org>
Since 4c7f181 (make color.ui default to 'auto', 2013-06-10), the
default for color.* when nothing is set is 'auto' and we still claimed
that the default was 'false'. Be more precise by saying explicitly
that the default is to follow color.ui, and recall that the default is
'auto' to avoid one indirection for the reader.
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
Documentation/config.txt | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 32f065c..66429fb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -953,7 +953,8 @@ color.branch::
A boolean to enable/disable color in the output of
linkgit:git-branch[1]. May be set to `always`,
`false` (or `never`) or `auto` (or `true`), in which case colors are used
- only when the output is to a terminal. Defaults to false.
+ only when the output is to a terminal. If unset, then the
+ value of `color.ui` is used (`auto` by default).
color.branch.<slot>::
Use customized color for branch coloration. `<slot>` is one of
@@ -968,7 +969,8 @@ color.diff::
linkgit:git-log[1], and linkgit:git-show[1] will use color
for all patches. If it is set to `true` or `auto`, those
commands will only use color when output is to the terminal.
- Defaults to false.
+ If unset, then the value of `color.ui` is used (`auto` by
+ default).
+
This does not affect linkgit:git-format-patch[1] or the
'git-diff-{asterisk}' plumbing commands. Can be overridden on the
@@ -991,7 +993,8 @@ color.decorate.<slot>::
color.grep::
When set to `always`, always highlight matches. When `false` (or
`never`), never. When set to `true` or `auto`, use color only
- when the output is written to the terminal. Defaults to `false`.
+ when the output is written to the terminal. If unset, then the
+ value of `color.ui` is used (`auto` by default).
color.grep.<slot>::
Use customized color for grep colorization. `<slot>` specifies which
@@ -1024,7 +1027,8 @@ color.interactive::
and displays (such as those used by "git-add --interactive" and
"git-clean --interactive"). When false (or `never`), never.
When set to `true` or `auto`, use colors only when the output is
- to the terminal. Defaults to false.
+ to the terminal. If unset, then the value of `color.ui` is
+ used (`auto` by default).
color.interactive.<slot>::
Use customized color for 'git add --interactive' and 'git clean
@@ -1040,13 +1044,15 @@ color.showBranch::
A boolean to enable/disable color in the output of
linkgit:git-show-branch[1]. May be set to `always`,
`false` (or `never`) or `auto` (or `true`), in which case colors are used
- only when the output is to a terminal. Defaults to false.
+ only when the output is to a terminal. If unset, then the
+ value of `color.ui` is used (`auto` by default).
color.status::
A boolean to enable/disable color in the output of
linkgit:git-status[1]. May be set to `always`,
`false` (or `never`) or `auto` (or `true`), in which case colors are used
- only when the output is to a terminal. Defaults to false.
+ only when the output is to a terminal. If unset, then the
+ value of `color.ui` is used (`auto` by default).
color.status.<slot>::
Use customized color for status colorization. `<slot>` is
--
2.10.0.rc0.1.g07c9292
^ permalink raw reply related
* Re: Potentially misleading color.* defaults explanation in git-config(1)
From: Matthieu Moy @ 2016-09-16 7:25 UTC (permalink / raw)
To: Anatoly Borodin; +Cc: git
In-Reply-To: <nrfihd$a4o$1@blaine.gmane.org>
Anatoly Borodin <anatoly.borodin@gmail.com> writes:
> Hi All!
>
> git-config(1) says:
>
> color.branch
> A boolean to enable/disable color in the output of git-branch(1).
> May be set to always, false (or never) or auto (or true), in which
> case colors are used only when the output is to a terminal.
So far, so good.
> Defaults to false.
The truth is: Defaults to following color.ui, which used to default to
false but now defaults to auto.
My bad, I forgot to update these parts of the docs when changing the
default for color.ui (a while back already). Patch follows.
> (2) git config color.branch false ; git branch
Unrelated from the question, but you could write
git -c color.branch=false git branch
to set a configuration value just for one command.
--
Matthieu Moy
http://www-verimag.imag.fr/~moy/
^ permalink raw reply
* Re: Gitattributes file is not respected when switching between branches
From: Виталий Ищенко @ 2016-09-16 6:51 UTC (permalink / raw)
To: Torsten Bögershausen; +Cc: git
In-Reply-To: <7c14756e-29f9-b475-f5f5-597acb8cea98@web.de>
Sorry for delay.
".gitattributes" indeed is not present in "master", but this is intentionally
It is placed only in following 2 branches:
feature-branch
unix-feature-branch
This is how flow looks on windows
$ git --version
git version 2.9.3.windows.1
vitalii.ishchenko@DESKTOP-9TC9UPB MINGW64 /c/work/repos/gitattributes (master)
$ git ls-files --eol
i/lf w/crlf attr/ testfile-crlf.txt
vitalii.ishchenko@DESKTOP-9TC9UPB MINGW64 /c/work/repos/gitattributes (master)
$ git checkout feature-branch
Switched to branch 'feature-branch'
Your branch is up-to-date with 'origin/feature-branch'.
vitalii.ishchenko@DESKTOP-9TC9UPB MINGW64 /c/work/repos/gitattributes
(feature-branch)
$ git ls-files --eol
i/lf w/lf attr/text eol=lf .gitattributes
i/lf w/crlf attr/text eol=lf testfile-crlf.txt
On Mon, Sep 12, 2016 at 10:42 PM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 12.09.16 21:35, Torsten Bögershausen wrote:
>> On 12.09.16 14:55, Виталий Ищенко wrote:
>>> Good day
>>>
>>> I faced following issue with gitattributes file (at least eol setting)
>>> when was trying to force `lf` mode on windows.
>>>
>>> We have 2 branches: master & dev. With master set as HEAD in repository
>>>
>>> I've added `.gitattributes` with following content to `dev` branch
>>>
>>> ```
>>> * text eol=lf
>>> ```
>>>
>>> Now when you clone this repo on other machine and checkout dev branch,
>>> eol setting is not respected.
>>> As a workaround you can rm all files except .git folder and do hard reset.
>>>
>>> Issue is reproducible on windows & unix versions. Test repo can be
>>> found on github
>>> https://github.com/betalb/gitattributes-issue
>>>
>>> master branch - one file without gitattributes
>>> feature-branch - .gitattributes added with eol=lf
>>> unix-feature-branch - .gitattributes added with eol=crlf
>>>
>>> Thanks,
>>> Vitalii
>> Some more information may be needed, to help to debug.
>>
>> Which version of Git are you using ?
>> What does
>>
>> git ls-files --eol
>>
>> say ?
> Obs, All information was in the email.
>
> tb@xxx:/tmp/gitattributes-issue> git ls-files --eol
> i/lf w/lf attr/ testfile-crlf.txt
> tb@xxx:/tmp/gitattributes-issue> ls -al
> total 8
> drwxr-xr-x 4 tb wheel 136 Sep 12 21:38 .
> drwxrwxrwt 19 root wheel 646 Sep 12 21:38 ..
> drwxr-xr-x 13 tb wheel 442 Sep 12 21:38 .git
> -rw-r--r-- 1 tb wheel 60 Sep 12 21:38 testfile-crlf.txt
> tb@xxx:/tmp/gitattributes-issue>
>
> Could it be that you didn't commit the file ".gitattributes" ?
> This could help:
> git add .gitattributes && git commit -m "Add .gitattributes"
>
>
>
>
>
>
>
^ permalink raw reply
* Potentially misleading color.* defaults explanation in git-config(1)
From: Anatoly Borodin @ 2016-09-16 1:40 UTC (permalink / raw)
To: git
Hi All!
git-config(1) says:
color.branch
A boolean to enable/disable color in the output of git-branch(1).
May be set to always, false (or never) or auto (or true), in which
case colors are used only when the output is to a terminal.
Defaults to false.
If the value false is the default, and neither color.branch nor
color.ui is set in any config file, one can expect that
(1) git branch
and
(2) git config color.branch false ; git branch
produce the same result. But only (2) produces colorless output, (1)
uses colors (that probably depend on the default value of color.ui).
The same story with color.diff and git-show, color.grep, etc.
Is it me being a non-native English speaker, or does this part really
need to be rewritten?
PS git version 2.9.3
--
Mit freundlichen Grüßen,
Anatoly Borodin
^ permalink raw reply
* [PATCH 11/11] Resumable clone: implement primer logic in git-clone
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Use transport_download_primer and transport_prime_clone in git clone.
This only supports a fully connected packfile.
transport_prime_clone and transport_download_primer are executed
completely independent of transport_(get|fetch)_remote_refs, et al.
transport_download_primer is executed based on the existence of an
alt_resource. The idea is that the "prime clone" execution should be
able to attempt retrieving an alternate resource without dying, as
opposed to depending on the result of upload pack's "capabilities" to
indicate whether or not the client can attempt it.
If a resumable resource is available, execute a codepath with the
following modular components:
- downloading resource to a specific directory
- using the resource (for pack, indexing and generating the bundle
file)
- cleaning up the resource (if the download or use fails)
- cleaning up the resource (if the download or use succeeds)
If resume is interrupted on the client side, the alternate resource
info is written to the RESUMABLE file in the git directory.
On resume, the required info is extracted by parsing the created
config file, and that info is used to determine the work and git
directories. If these cannot be determined, the program exits.
The writing of the refspec and determination of the initial git
directories are skipped, along with transport_prime_clone.
The main purpose of this series of patches is to flesh out a codepath
for automatic resuming, manual resuming, and leaving a resumable
directory on exit--the logic for when to do these still needs more
work.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
Documentation/git-clone.txt | 16 ++
builtin/clone.c | 590 +++++++++++++++++++++++++++++++++++++-------
t/t9904-git-prime-clone.sh | 181 ++++++++++++++
3 files changed, 698 insertions(+), 89 deletions(-)
create mode 100755 t/t9904-git-prime-clone.sh
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index b7c467a..5934bb6 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -16,6 +16,7 @@ SYNOPSIS
[--depth <depth>] [--[no-]single-branch]
[--recursive | --recurse-submodules] [--] <repository>
[<directory>]
+'git clone --resume <resumable_dir>'
DESCRIPTION
-----------
@@ -172,6 +173,12 @@ objects from the source repository into a pack in the cloned repository.
via ssh, this specifies a non-default path for the command
run on the other end.
+--prime-clone <prime-clone>::
+-p <prime-clone>::
+ When given and the repository to clone from is accessed
+ via ssh, this specifies a non-default path for the command
+ run on the other end.
+
--template=<template_directory>::
Specify the directory from which templates will be used;
(See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
@@ -232,6 +239,15 @@ objects from the source repository into a pack in the cloned repository.
for `host.xz:foo/.git`). Cloning into an existing directory
is only allowed if the directory is empty.
+--resume::
+ Resume a partially cloned repo in a "resumable" state. This
+ can only be specified with a single local directory (<resumable
+ dir>). This is incompatible with all other options.
+
+<resumable_dir>::
+ The directory of the partial clone. This could be either the
+ work directory or the git directory.
+
:git-clone: 1
include::urls.txt[]
diff --git a/builtin/clone.c b/builtin/clone.c
index 9ac6c01..d9a13dc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -8,7 +8,9 @@
* Clone a repository into a different directory that does not yet exist.
*/
+#include "cache.h"
#include "builtin.h"
+#include "bundle.h"
#include "lockfile.h"
#include "parse-options.h"
#include "fetch-pack.h"
@@ -40,17 +42,20 @@ static const char * const builtin_clone_usage[] = {
static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
+static int option_resume;
static char *option_template, *option_depth;
-static char *option_origin = NULL;
+static const char *option_origin = NULL;
static char *option_branch = NULL;
static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
+static char *option_prime_clone = "git-prime-clone";
static int option_verbosity;
static int option_progress = -1;
static enum transport_family family;
static struct string_list option_config;
static struct string_list option_reference;
static int option_dissociate;
+static const struct alt_resource *alt_res = NULL;
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
@@ -85,10 +90,14 @@ static struct option builtin_clone_options[] = {
N_("checkout <branch> instead of the remote's HEAD")),
OPT_STRING('u', "upload-pack", &option_upload_pack, N_("path"),
N_("path to git-upload-pack on the remote")),
+ OPT_STRING('p', "prime-clone", &option_prime_clone, N_("path"),
+ N_("path to git-prime-clone on the remote")),
OPT_STRING(0, "depth", &option_depth, N_("depth"),
N_("create a shallow clone of that depth")),
OPT_BOOL(0, "single-branch", &option_single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_BOOL(0, "resume", &option_resume,
+ N_("continue a resumable clone")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
@@ -278,6 +287,21 @@ static void strip_trailing_slashes(char *dir)
*end = '\0';
}
+static char *get_filename(const char *dir)
+{
+ char *dir_copy = xstrdup(dir);
+ strip_trailing_slashes(dir_copy);
+ char *filename, *final = NULL;
+
+ filename = find_last_dir_sep(dir);
+
+ if (filename && *(++filename))
+ final = xstrdup(filename);
+
+ free(dir_copy);
+ return final;
+}
+
static int add_one_reference(struct string_list_item *item, void *cb_data)
{
char *ref_git;
@@ -451,6 +475,7 @@ static const char *junk_work_tree;
static const char *junk_git_dir;
static enum {
JUNK_LEAVE_NONE,
+ JUNK_LEAVE_RESUMABLE,
JUNK_LEAVE_REPO,
JUNK_LEAVE_ALL
} junk_mode = JUNK_LEAVE_NONE;
@@ -460,6 +485,29 @@ N_("Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"and retry the checkout with 'git checkout -f HEAD'\n");
+static const char junk_leave_resumable_msg[] =
+N_("Clone interrupted while copying resumable resource.\n"
+ "Try using 'git clone --resume <new_directory>',\n"
+ "where <new_directory> is either the new working \n"
+ "directory or git directory.\n\n"
+ "If this does not succeed, it could be because the\n"
+ "resource has been moved, corrupted, or changed.\n"
+ "If this is the case, you should remove <new_directory>\n"
+ "and run the original command.\n");
+
+static void write_resumable_resource()
+{
+ const char *filename = git_path_resumable();
+ struct strbuf content = STRBUF_INIT;
+ strbuf_addf(&content, "%s\n%s\n", alt_res->url, alt_res->filetype);
+ int fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, content.buf, content.len) != content.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+}
+
static void remove_junk(void)
{
struct strbuf sb = STRBUF_INIT;
@@ -467,7 +515,11 @@ static void remove_junk(void)
switch (junk_mode) {
case JUNK_LEAVE_REPO:
warning("%s", _(junk_leave_repo_msg));
- /* fall-through */
+ return;
+ case JUNK_LEAVE_RESUMABLE:
+ write_resumable_resource();
+ warning("%s", _(junk_leave_resumable_msg));
+ return;
case JUNK_LEAVE_ALL:
return;
default:
@@ -562,7 +614,7 @@ static void write_remote_refs(const struct ref *local_refs)
die("%s", err.buf);
for (r = local_refs; r; r = r->next) {
- if (!r->peer_ref)
+ if (!r->peer_ref || ref_exists(r->peer_ref->name))
continue;
if (ref_transaction_create(t, r->peer_ref->name, r->old_oid.hash,
0, NULL, &err))
@@ -820,11 +872,296 @@ static void dissociate_from_references(void)
free(alternates);
}
+static int do_index_pack(const char *in_pack_file, const char *out_idx_file)
+{
+ const char *argv[] = { "index-pack", "--clone-bundle", "-v",
+ "--check-self-contained-and-connected", "-o",
+ out_idx_file, in_pack_file, NULL };
+ return run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDOUT);
+}
+
+static const char *replace_extension(const char *filename, const char *existing,
+ const char *replacement)
+{
+ struct strbuf new_filename = STRBUF_INIT;
+ int existing_len = strlen(existing);
+ int replacement_len = strlen(replacement);
+ int filename_len = strlen(filename);
+
+ if (!(filename && existing && replacement)) {
+ return NULL;
+ }
+
+ if (!strncmp(filename + filename_len - existing_len,
+ existing, existing_len)) {
+ int existing_position = filename_len - existing_len;
+ strbuf_addstr(&new_filename, filename);
+ strbuf_splice(&new_filename, existing_position, existing_len,
+ replacement, replacement_len);
+ }
+
+ return strbuf_detach(&new_filename, NULL);
+}
+
+static const char *setup_and_index_pack(const char *filename)
+{
+ const char *primer_idx_path = NULL, *primer_bndl_path = NULL;
+ primer_idx_path = replace_extension(filename, ".pack", ".idx");
+ primer_bndl_path = replace_extension(filename, ".pack", ".bndl");
+
+ if (!(primer_idx_path && primer_bndl_path)) {
+ warning("invalid pack filename '%s', falling back to full "
+ "clone", filename);
+ return NULL;
+ }
+
+ if (!file_exists(primer_bndl_path)) {
+ if (do_index_pack(filename, primer_idx_path)) {
+ warning("could not index primer pack, falling back to "
+ "full clone");
+ return NULL;
+ }
+ }
+
+ return primer_bndl_path;
+}
+
+static int write_bundle_refs(const char *bundle_filename)
+{
+ struct ref_transaction *t;
+ struct bundle_header history_tips;
+ const char *temp_ref_base = "resume";
+ struct strbuf err = STRBUF_INIT;
+ int i;
+
+ init_bundle_header(&history_tips, bundle_filename);
+ read_bundle_header(&history_tips);
+
+ t = ref_transaction_begin(&err);
+ for (i = 0; i < history_tips.references.nr; i++) {
+ struct strbuf ref_name = STRBUF_INIT;
+ strbuf_addf(&ref_name, "refs/temp/%s/%s/temp-%s",
+ option_origin, temp_ref_base,
+ sha1_to_hex(history_tips.references.list[i].sha1));
+ if (!ref_exists(ref_name.buf)) {
+ if (ref_transaction_create(t, ref_name.buf,
+ history_tips.references.list[i].sha1,
+ 0, NULL, &err)) {
+ warning(_("%s"), err.buf);
+ return -1;
+ }
+ }
+ strbuf_release(&ref_name);
+ }
+
+ if (initial_ref_transaction_commit(t, &err)) {
+ warning("%s", err.buf);
+ return -1;
+ }
+ ref_transaction_free(t);
+ release_bundle_header(&history_tips);
+ return 0;
+}
+
+static int use_alt_resource_pack(const char *alt_res_path)
+{
+ int ret = -1;
+ const char *bundle_path = setup_and_index_pack(alt_res_path);
+ if (bundle_path)
+ ret = write_bundle_refs(bundle_path);
+ return ret;
+}
+
+static int use_alt_resource(const char *alt_res_path)
+{
+ int ret = -1;
+ if (!strcmp(alt_res->filetype, "pack"))
+ ret = use_alt_resource_pack(alt_res_path);
+ return ret;
+}
+
+static void clean_alt_resource_pack(const char *resource_path,
+ int prime_successful)
+{
+ struct bundle_header history_tips;
+ const char *temp_ref_base = "resume";
+ const char *bundle_path;
+
+ if (!resource_path)
+ return;
+
+ bundle_path = replace_extension(resource_path, ".pack", ".bndl");
+
+ if (prime_successful) {
+ init_bundle_header(&history_tips, bundle_path);
+ read_bundle_header(&history_tips);
+
+ for (int i = 0; i < history_tips.references.nr; i++) {
+ struct strbuf ref_name = STRBUF_INIT;
+ strbuf_addf(&ref_name, "refs/temp/%s/%s/temp-%s",
+ option_origin, temp_ref_base,
+ sha1_to_hex(history_tips.references.list[i].sha1));
+ if (ref_exists(ref_name.buf)) {
+ delete_ref(ref_name.buf,
+ history_tips.references.list[i].sha1,
+ 0);
+ }
+ strbuf_release(&ref_name);
+ }
+ release_bundle_header(&history_tips);
+ }
+
+ if (!prime_successful) {
+ const char *tmp_path = mkpath("%s.temp", resource_path);
+ const char *idx_path = replace_extension(resource_path, ".pack",
+ ".idx");
+ if (file_exists(resource_path)) {
+ unlink(resource_path);
+ }
+ if (file_exists(tmp_path)) {
+ unlink(tmp_path);
+ }
+ if (file_exists(idx_path)) {
+ unlink(idx_path);
+ }
+ }
+ if (file_exists(bundle_path)) {
+ unlink(bundle_path);
+ }
+}
+
+static const char *fetch_alt_resource_pack(struct transport *transport,
+ const char *base_dir)
+{
+ struct strbuf download_path = STRBUF_INIT;
+ const char *resource_path = NULL;
+ struct remote *primer_remote = remote_get(alt_res->url);
+ struct transport *primer_transport = transport_get(primer_remote,
+ alt_res->url);
+ strbuf_addf(&download_path, "%s/objects/pack", base_dir);
+ fprintf(stderr, "Downloading primer: %s...\n", alt_res->url);
+ resource_path = transport_download_primer(primer_transport, alt_res,
+ download_path.buf);
+ transport_disconnect(primer_transport);
+ return resource_path;
+}
+
+static void clean_alt_resource(const char *resource_path, int prime_successful)
+{
+ if (!strcmp(alt_res->filetype, "pack"))
+ clean_alt_resource_pack(resource_path, prime_successful);
+}
+
+static const char *fetch_alt_resource(struct transport *transport,
+ const char *base_dir)
+{
+ const char *resource_path = NULL;
+ if (!strcmp(alt_res->filetype, "pack"))
+ resource_path = fetch_alt_resource_pack(transport, base_dir);
+ return resource_path;
+}
+
+static const struct alt_resource *get_last_alt_resource(void)
+{
+ struct alt_resource *ret = NULL;
+ FILE *fp;
+ if (fp = fopen(git_path_resumable(), "r")) {
+ ret = xcalloc(1, sizeof(struct alt_resource));
+ struct strbuf line = STRBUF_INIT;
+ strbuf_getline(&line, fp);
+ ret->url = strbuf_detach(&line, NULL);
+ strbuf_getline(&line, fp);
+ ret->filetype = strbuf_detach(&line, NULL);
+ fclose(fp);
+ }
+ return ret;
+}
+
+struct remote_config {
+ const char *name;
+ const char *fetch_pattern;
+ const char *worktree;
+ int bare;
+ int mirror;
+};
+
+static int get_remote_info(const char *key, const char *value, void *priv)
+{
+ struct remote_config *p = priv;
+ char *sub_key;
+ char *name;
+
+ if (skip_prefix(key, "remote.", &key)) {
+ name = xstrdup(key);
+ sub_key = strchr(name, '.');
+ *sub_key++ = 0;
+ if (!p->name)
+ p->name = xstrdup(name);
+ else if (!strcmp(sub_key, "fetch"))
+ git_config_string(&p->fetch_pattern, key, value);
+ else if (!strcmp(sub_key, "mirror"))
+ p->mirror = git_config_bool(key, value);
+ free(name);
+ }
+ else if (!strcmp(key, "core.bare"))
+ p->bare = git_config_bool(key, value);
+ else if (!strcmp(key, "core.worktree"))
+ git_config_string(&p->worktree, key, value);
+
+ return 0;
+}
+
+static void get_existing_state(char *dir, const char **git_dir,
+ const char **work_tree,
+ struct remote_config *past_info)
+{
+ if (is_git_directory(dir)) {
+ *git_dir = xstrdup(dir);
+ *work_tree = NULL;
+ }
+ else if (file_exists(mkpath("%s/.git", dir))){
+ *work_tree = xstrdup(dir);
+ *git_dir = xstrdup(resolve_gitdir(mkpath("%s/.git", dir)));
+ }
+
+ if (!*git_dir)
+ die(_("'%s' does not appear to be a git repo."), dir);
+
+ set_git_dir(*git_dir);
+ git_config(get_remote_info, past_info);
+
+ if (!*work_tree) {
+ if (past_info->worktree) {
+ *work_tree = past_info->worktree;
+ }
+ else if (!past_info->bare) {
+ int containing_dir_success = 1;
+ char *filename = get_filename(*git_dir);
+ if (filename && !strcmp(filename, ".git")) {
+ const char *parent_dir = mkpath("%s/..",
+ *git_dir);
+ *work_tree = xstrdup(real_path(parent_dir));
+ if (access(*work_tree, W_OK) < 0)
+ containing_dir_success = 0;
+ }
+ else {
+ containing_dir_success = 0;
+ }
+ if (!containing_dir_success)
+ die(_("'%s' is configured for a work tree, "
+ "but no candidate exists."), dir);
+ }
+ }
+ if (*work_tree)
+ set_git_work_tree(*work_tree);
+}
+
int cmd_clone(int argc, const char **argv, const char *prefix)
{
- int is_bundle = 0, is_local;
+ int is_bundle = 0, is_local, argc_original, option_count;
struct stat buf;
- const char *repo_name, *repo, *work_tree, *git_dir;
+ const char *repo_name, *repo, *work_tree, *git_dir = NULL;
+ const char *resource_path;
char *path, *dir;
int dest_exists;
const struct ref *refs, *remote_head;
@@ -838,13 +1175,23 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
const char *src_ref_prefix = "refs/heads/";
struct remote *remote;
int err = 0, complete_refs_before_fetch = 1;
-
struct refspec *refspec;
const char *fetch_pattern;
packet_trace_identity("clone");
+ argc_original = argc;
argc = parse_options(argc, argv, prefix, builtin_clone_options,
builtin_clone_usage, 0);
+ option_count = argc_original - argc;
+
+ if (option_resume && option_count > 2) {
+ die(_("--resume is incompatible with all other options."));
+ }
+
+ if (option_resume && argc != 1) {
+ die(_("--resume must be specified with a single resumable "
+ "directory."));
+ }
if (argc > 2)
usage_msg_opt(_("Too many arguments."),
@@ -872,105 +1219,140 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (!option_origin)
option_origin = "origin";
- repo_name = argv[0];
+ if (option_resume) {
+ struct remote_config past_info = {0};
+ dir = xstrdup(real_path(argv[0]));
+ strip_trailing_slashes(dir);
+ if (!file_exists(dir))
+ die(_("directory '%s' does not exist."), dir);
+ get_existing_state(dir, &git_dir, &work_tree, &past_info);
+
+ if (!work_tree)
+ option_no_checkout = 1;
+ if (past_info.fetch_pattern)
+ fetch_pattern = past_info.fetch_pattern;
+ else {
+ struct strbuf fetch_temp = STRBUF_INIT;
+ strbuf_addstr(&branch_top, src_ref_prefix);
+ strbuf_addf(&fetch_temp, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
+ fetch_pattern = strbuf_detach(&fetch_temp, NULL);
+ }
- path = get_repo_path(repo_name, &is_bundle);
- if (path)
- repo = xstrdup(absolute_path(repo_name));
- else if (!strchr(repo_name, ':'))
- die(_("repository '%s' does not exist"), repo_name);
- else
- repo = repo_name;
+ option_origin = past_info.name;
+ option_mirror = past_info.mirror;
+ option_bare = past_info.bare;
+ refspec = parse_fetch_refspec(1, &fetch_pattern);
- /* no need to be strict, transport_set_option() will validate it again */
- if (option_depth && atoi(option_depth) < 1)
- die(_("depth %s is not a positive number"), option_depth);
+ if (!(alt_res = get_last_alt_resource()))
+ die(_("--resume option used, but current "
+ "directory is not resumable"));
- if (argc == 2)
- dir = xstrdup(argv[1]);
- else
- dir = guess_dir_name(repo_name, is_bundle, option_bare);
- strip_trailing_slashes(dir);
+ junk_mode = JUNK_LEAVE_RESUMABLE;
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+ }
+ else {
+ repo_name = argv[0];
+
+ path = get_repo_path(repo_name, &is_bundle);
+ if (path)
+ repo = xstrdup(absolute_path(repo_name));
+ else if (!strchr(repo_name, ':'))
+ die(_("repository '%s' does not exist"), repo_name);
+ else
+ repo = repo_name;
- dest_exists = !stat(dir, &buf);
- if (dest_exists && !is_empty_dir(dir))
- die(_("destination path '%s' already exists and is not "
- "an empty directory."), dir);
+ /* no need to be strict, transport_set_option() will validate it again */
+ if (option_depth && atoi(option_depth) < 1)
+ die(_("depth %s is not a positive number"), option_depth);
- strbuf_addf(&reflog_msg, "clone: from %s", repo);
+ if (argc == 2)
+ dir = xstrdup(argv[1]);
+ else
+ dir = guess_dir_name(repo_name, is_bundle, option_bare);
+ strip_trailing_slashes(dir);
- if (option_bare)
- work_tree = NULL;
- else {
- work_tree = getenv("GIT_WORK_TREE");
- if (work_tree && !stat(work_tree, &buf))
- die(_("working tree '%s' already exists."), work_tree);
- }
+ dest_exists = !stat(dir, &buf);
+ if (dest_exists && !is_empty_dir(dir))
+ die(_("destination path '%s' already exists and is not "
+ "an empty directory."), dir);
- if (option_bare || work_tree)
- git_dir = xstrdup(dir);
- else {
- work_tree = dir;
- git_dir = mkpathdup("%s/.git", dir);
- }
+ strbuf_addf(&reflog_msg, "clone: from %s", repo);
- atexit(remove_junk);
- sigchain_push_common(remove_junk_on_signal);
-
- if (!option_bare) {
- if (safe_create_leading_directories_const(work_tree) < 0)
- die_errno(_("could not create leading directories of '%s'"),
- work_tree);
- if (!dest_exists && mkdir(work_tree, 0777))
- die_errno(_("could not create work tree dir '%s'"),
- work_tree);
- junk_work_tree = work_tree;
- set_git_work_tree(work_tree);
- }
+ if (option_bare)
+ work_tree = NULL;
+ else {
+ work_tree = getenv("GIT_WORK_TREE");
+ if (work_tree && !stat(work_tree, &buf))
+ die(_("working tree '%s' already exists."), work_tree);
+ }
- junk_git_dir = git_dir;
- if (safe_create_leading_directories_const(git_dir) < 0)
- die(_("could not create leading directories of '%s'"), git_dir);
+ if (option_bare || work_tree)
+ git_dir = xstrdup(dir);
+ else {
+ work_tree = dir;
+ git_dir = mkpathdup("%s/.git", dir);
+ }
- set_git_dir_init(git_dir, real_git_dir, 0);
- if (real_git_dir) {
- git_dir = real_git_dir;
- junk_git_dir = real_git_dir;
- }
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
- if (0 <= option_verbosity) {
- if (option_bare)
- fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
- else
- fprintf(stderr, _("Cloning into '%s'...\n"), dir);
- }
- init_db(option_template, INIT_DB_QUIET);
- write_config(&option_config);
+ if (!option_bare) {
+ if (safe_create_leading_directories_const(work_tree) < 0)
+ die_errno(_("could not create leading directories of '%s'"),
+ work_tree);
+ if (!dest_exists && mkdir(work_tree, 0777))
+ die_errno(_("could not create work tree dir '%s'"),
+ work_tree);
+ junk_work_tree = work_tree;
+ set_git_work_tree(work_tree);
+ }
- git_config(git_default_config, NULL);
+ junk_git_dir = git_dir;
+ if (safe_create_leading_directories_const(git_dir) < 0)
+ die(_("could not create leading directories of '%s'"), git_dir);
- if (option_bare) {
- if (option_mirror)
- src_ref_prefix = "refs/";
- strbuf_addstr(&branch_top, src_ref_prefix);
+ set_git_dir_init(git_dir, real_git_dir, 0);
+ if (real_git_dir) {
+ git_dir = real_git_dir;
+ junk_git_dir = real_git_dir;
+ }
- git_config_set("core.bare", "true");
- } else {
- strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
- }
+ if (0 <= option_verbosity) {
+ if (option_bare)
+ fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
+ else
+ fprintf(stderr, _("Cloning into '%s'...\n"), dir);
+ }
+ init_db(option_template, INIT_DB_QUIET);
+ write_config(&option_config);
+
+ git_config(git_default_config, NULL);
- strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
- strbuf_addf(&key, "remote.%s.url", option_origin);
- git_config_set(key.buf, repo);
- strbuf_reset(&key);
+ if (option_bare) {
+ if (option_mirror)
+ src_ref_prefix = "refs/";
+ strbuf_addstr(&branch_top, src_ref_prefix);
- if (option_reference.nr)
- setup_reference();
+ git_config_set("core.bare", "true");
+ } else {
+ strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
+ }
+
+ strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+ strbuf_addf(&key, "remote.%s.url", option_origin);
+ git_config_set(key.buf, repo);
+ strbuf_reset(&key);
- fetch_pattern = value.buf;
- refspec = parse_fetch_refspec(1, &fetch_pattern);
+ if (option_reference.nr)
+ setup_reference();
- strbuf_reset(&value);
+ fetch_pattern = value.buf;
+ refspec = parse_fetch_refspec(1, &fetch_pattern);
+
+ strbuf_reset(&value);
+ }
remote = remote_get(option_origin);
transport = transport_get(remote, remote->url[0]);
@@ -1003,6 +1385,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_single_branch)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (option_prime_clone)
+ transport_set_option(transport, TRANS_OPT_PRIMECLONE,
+ option_prime_clone);
+
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
@@ -1010,6 +1396,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (transport->smart_options && !option_depth)
transport->smart_options->check_self_contained_and_connected = 1;
+ if (!is_local && option_reference.nr == 0 && !alt_res)
+ alt_res = transport_prime_clone(transport);
refs = transport_get_remote_refs(transport);
if (refs) {
@@ -1064,8 +1452,24 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
"refs/heads/master");
}
- write_refspec_config(src_ref_prefix, our_head_points_at,
- remote_head_points_at, &branch_top);
+ if (!option_resume) {
+ write_refspec_config(src_ref_prefix, our_head_points_at,
+ remote_head_points_at, &branch_top);
+ }
+
+ if (alt_res) {
+ junk_mode = JUNK_LEAVE_RESUMABLE;
+ resource_path = fetch_alt_resource(transport, git_dir);
+ if (!resource_path || use_alt_resource(resource_path) < 0) {
+ if (option_resume)
+ die(_("resumable resource is no longer "
+ "available or usable"));
+ junk_mode = JUNK_LEAVE_NONE;
+ clean_alt_resource(resource_path, 0);
+ resource_path = NULL;
+ alt_res = NULL;
+ }
+ }
if (is_local)
clone_local(path, git_dir);
@@ -1085,9 +1489,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
dissociate_from_references();
}
+ if (resource_path) {
+ clean_alt_resource(resource_path, 1);
+ }
+
junk_mode = JUNK_LEAVE_REPO;
err = checkout();
+ if (file_exists(git_path_resumable())) {
+ unlink(git_path_resumable());
+ }
+
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
diff --git a/t/t9904-git-prime-clone.sh b/t/t9904-git-prime-clone.sh
new file mode 100755
index 0000000..257cea9
--- /dev/null
+++ b/t/t9904-git-prime-clone.sh
@@ -0,0 +1,181 @@
+#!/bin/sh
+
+test_description='tests for git prime-clone'
+. ./test-lib.sh
+
+ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'resume fails for no parameters' '
+ test_must_fail git clone --resume
+'
+
+test_expect_success 'resume fails with other options' '
+ test_must_fail git clone --resume --bare
+'
+
+test_expect_success 'resume fails for excess parameters' '
+ test_must_fail git clone --resume a b
+'
+
+test_expect_success 'resume fails for nonexistent directory' '
+ test_must_fail git clone --resume nonexistent
+'
+
+test_expect_success 'setup repo and httpd' '
+ mkdir server &&
+ cd server &&
+ git init &&
+ echo "content\\n" >example.c &&
+ git add example.c &&
+ git commit -m "I am a packed commit" &&
+ git repack . &&
+ git config --local http.primeclone true &&
+ git config --local primeclone.url \
+ $HTTPD_URL/server/.git/objects/pack/$(find .git/objects/pack/*.pack -printf "%f") &&
+ git config --local primeclone.filetype pack &&
+ echo "content\\n" >example2.c &&
+ echo "new content\\n" >example.c &&
+ git add example.c example2.c &&
+ git commit -m "I am an unpacked commit" &&
+ cd - &&
+ mv server "$HTTPD_DOCUMENT_ROOT_PATH"
+'
+
+test_expect_success 'prime-clone works http' '
+ git clone $HTTPD_URL/smart/server client &&
+ rm -r client
+'
+
+test_expect_success 'prime-clone falls back not permitted' '
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local http.primeclone false &&
+ cd - &&
+ git clone $HTTPD_URL/smart/server client &&
+ rm -r client &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local http.primeclone true &&
+ cd -
+'
+
+test_expect_success 'prime-clone falls back not enabled' '
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local primeclone.enabled 0 &&
+ cd - &&
+ git clone $HTTPD_URL/smart/server client &&
+ rm -r client &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local --unset-all primeclone.enabled &&
+ cd -
+'
+
+test_expect_success 'prime-clone falls back incorrect config' '
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local --unset-all primeclone.filetype &&
+ cd - &&
+ git clone $HTTPD_URL/smart/server client &&
+ rm -r client &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local primeclone.filetype pack &&
+ cd -
+'
+
+test_expect_success 'clone resume fails in complete/unmarked directory' '
+ git clone $HTTPD_URL/smart/server client &&
+ test_must_fail git clone --resume client &&
+ rm -r client
+'
+
+test_expect_success 'clone resume works with marked repo (work dir, normal)' '
+ git clone $HTTPD_URL/smart/server client &&
+ cd client &&
+ rm .git/objects/pack/*.idx &&
+ echo -n "$HTTPD_URL/server/" > .git/RESUMABLE &&
+ find .git/objects/pack/*.pack >> .git/RESUMABLE &&
+ echo "pack" >> .git/RESUMABLE &&
+ mv $(find .git/objects/pack/*.pack) $(find .git/objects/pack/*.pack).tmp &&
+ sed -i "2,$ d" $(find .git/objects/pack/*.pack.tmp) &&
+ rm * &&
+ git clone --resume . &&
+ cd - &&
+ rm -r client
+'
+
+test_expect_success 'clone resume works with marked repo (git dir, normal)' '
+ git clone $HTTPD_URL/smart/server client &&
+ cd client &&
+ rm .git/objects/pack/*.idx &&
+ echo -n "$HTTPD_URL/server/" > .git/RESUMABLE &&
+ find .git/objects/pack/*.pack >> .git/RESUMABLE &&
+ echo "pack" >> .git/RESUMABLE &&
+ mv $(find .git/objects/pack/*.pack) $(find .git/objects/pack/*.pack).tmp &&
+ sed -i "2,$ d" $(find .git/objects/pack/*.pack.tmp) &&
+ rm * &&
+ git clone --resume .git &&
+ cd - &&
+ rm -r client
+'
+
+test_expect_success 'clone resume works inside marked repo (git dir, normal)' '
+ git clone $HTTPD_URL/smart/server client &&
+ cd client &&
+ rm .git/objects/pack/*.idx &&
+ echo -n "$HTTPD_URL/server/" > .git/RESUMABLE &&
+ find .git/objects/pack/*.pack >> .git/RESUMABLE &&
+ echo "pack" >> .git/RESUMABLE &&
+ mv $(find .git/objects/pack/*.pack) $(find .git/objects/pack/*.pack).tmp &&
+ sed -i "2,$ d" $(find .git/objects/pack/*.pack.tmp) &&
+ rm * &&
+ cd .git &&
+ git clone --resume . &&
+ cd ../.. &&
+ rm -r client
+'
+
+test_expect_success 'clone resume works with marked repo (work dir, split)' '
+ git clone --separate-git-dir=separate_dir.git \
+ $HTTPD_URL/smart/server client &&
+ cd separate_dir.git &&
+ rm objects/pack/*.idx &&
+ echo -n "$HTTPD_URL/server/" > RESUMABLE &&
+ echo ".git/$(find objects/pack/*.pack)" >> RESUMABLE &&
+ echo "pack" >> RESUMABLE &&
+ mv $(find objects/pack/*.pack) $(find objects/pack/*.pack).tmp &&
+ sed -i "2,$ d" $(find objects/pack/*.pack.tmp) &&
+ cd ../client &&
+ rm * &&
+ cd .. &&
+ git clone --resume client &&
+ rm -r client separate_dir.git
+'
+
+test_expect_success 'clone resume works with marked repo (git dir, split)' '
+ git clone --separate-git-dir=separate_dir.git \
+ $HTTPD_URL/smart/server client &&
+ cd separate_dir.git &&
+ rm objects/pack/*.idx &&
+ echo -n "$HTTPD_URL/server/" > RESUMABLE &&
+ echo ".git/$(find objects/pack/*.pack)" >> RESUMABLE &&
+ echo "pack" >> RESUMABLE &&
+ mv $(find objects/pack/*.pack) $(find objects/pack/*.pack).tmp &&
+ sed -i "2,$ d" $(find objects/pack/*.pack.tmp) &&
+ cd ../client &&
+ rm * &&
+ cd .. &&
+ git clone --resume separate_dir.git &&
+ rm -r client separate_dir.git
+'
+
+test_expect_success 'prime-clone falls back unusable file' '
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ git config --local primeclone.url $HTTPD_URL/server/.git/HEAD &&
+ cd - &&
+ git clone $HTTPD_URL/smart/server client &&
+ rm -r client &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/server" &&
+ cd -
+'
+
+stop_httpd
+test_done
--
2.7.4
^ permalink raw reply related
* [PATCH 09/11] path: add resumable marker
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Create function to get gitdir file RESUMABLE.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
cache.h | 1 +
path.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/cache.h b/cache.h
index d7ff46e..1f4117c 100644
--- a/cache.h
+++ b/cache.h
@@ -811,6 +811,7 @@ const char *git_path_merge_mode(void);
const char *git_path_merge_head(void);
const char *git_path_fetch_head(void);
const char *git_path_shallow(void);
+const char *git_path_resumable(void);
/*
* Return the name of the file in the local object database that would
diff --git a/path.c b/path.c
index 8b7e168..9360ed9 100644
--- a/path.c
+++ b/path.c
@@ -1201,4 +1201,5 @@ GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR")
GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE")
GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD")
GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD")
+GIT_PATH_FUNC(git_path_resumable, "RESUMABLE")
GIT_PATH_FUNC(git_path_shallow, "shallow")
--
2.7.4
^ permalink raw reply related
* [PATCH 10/11] run command: add RUN_COMMAND_NO_STDOUT
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Add option RUN_COMMAND_NO_STDOUT, which sets no_stdout on a child
process.
This will be used by git clone when calling index-pack on a downloaded
packfile.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
run-command.c | 1 +
run-command.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/run-command.c b/run-command.c
index 863dad5..c4f82f9 100644
--- a/run-command.c
+++ b/run-command.c
@@ -574,6 +574,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
cmd.silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0;
cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
+ cmd.no_stdout = opt & RUN_COMMAND_NO_STDOUT ? 1 : 0;
cmd.dir = dir;
cmd.env = env;
return run_command(&cmd);
diff --git a/run-command.h b/run-command.h
index 42917e8..2d2c871 100644
--- a/run-command.h
+++ b/run-command.h
@@ -70,6 +70,7 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);
#define RUN_SILENT_EXEC_FAILURE 8
#define RUN_USING_SHELL 16
#define RUN_CLEAN_ON_EXIT 32
+#define RUN_COMMAND_NO_STDOUT 64
int run_command_v_opt(const char **argv, int opt);
/*
--
2.7.4
^ permalink raw reply related
* [PATCH 08/11] Resumable clone: create transport_download_primer
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Create function transport_download_primer and components
to invoke and pass commands to remote-curl.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
transport-helper.c | 24 ++++++++++++++++++++++++
transport.c | 9 +++++++++
transport.h | 7 +++++++
3 files changed, 40 insertions(+)
diff --git a/transport-helper.c b/transport-helper.c
index eb185d5..2ff96ef 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -29,6 +29,7 @@ struct helper_data {
check_connectivity : 1,
no_disconnect_req : 1,
no_private_update : 1,
+ download_primer : 1,
prime_clone : 1;
char *export_marks;
char *import_marks;
@@ -183,6 +184,8 @@ static struct child_process *get_helper(struct transport *transport)
data->check_connectivity = 1;
else if (!strcmp(capname, "prime-clone"))
data->prime_clone = 1;
+ else if (!strcmp(capname, "download-primer"))
+ data->download_primer = 1;
else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) {
ALLOC_GROW(refspecs,
refspec_nr + 1,
@@ -1058,6 +1061,26 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
return ret;
}
+static const char *download_primer(struct transport *transport,
+ const struct alt_resource *res,
+ const char *base_path)
+{
+ struct helper_data *data = transport->data;
+ struct child_process *helper;
+ struct strbuf buf = STRBUF_INIT, out = STRBUF_INIT;
+ char *ret = NULL;
+
+ helper = get_helper(transport);
+
+ strbuf_addf(&buf, "download-primer %s %s\n", res->url, base_path);
+ sendline(data, &buf);
+ recvline(data, &out);
+ strbuf_release(&buf);
+ if (out.len > 0)
+ ret = strbuf_detach(&out, NULL);
+ return ret;
+}
+
static const struct alt_resource *const get_alt_res_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
@@ -1115,6 +1138,7 @@ int transport_helper_init(struct transport *transport, const char *name)
transport->data = data;
transport->set_option = set_helper_option;
transport->get_refs_list = get_refs_list;
+ transport->download_primer = download_primer;
transport->prime_clone = get_alt_res_helper;
transport->fetch = fetch;
transport->push_refs = push_refs;
diff --git a/transport.c b/transport.c
index dd0d839..3b33029 100644
--- a/transport.c
+++ b/transport.c
@@ -572,6 +572,15 @@ const struct alt_resource *const transport_prime_clone(struct transport *transpo
return transport->alt_res;
}
+const char *transport_download_primer(struct transport *transport,
+ const struct alt_resource *alt_res,
+ const char *base_dir)
+{
+ if (transport->download_primer)
+ return transport->download_primer(transport, alt_res, base_dir);
+ return NULL;
+}
+
static int connect_git(struct transport *transport, const char *name,
const char *executable, int fd[2])
{
diff --git a/transport.h b/transport.h
index 2bb6963..1484d6d 100644
--- a/transport.h
+++ b/transport.h
@@ -83,6 +83,10 @@ struct transport {
**/
const struct alt_resource *const (*prime_clone)(struct transport *transport);
+ const char *(*download_primer)(struct transport *transport,
+ const struct alt_resource *alt_res,
+ const char *base_path);
+
/**
* Fetch the objects for the given refs. Note that this gets
* an array, and should ignore the list structure.
@@ -228,6 +232,9 @@ int transport_push(struct transport *connection,
const struct ref *transport_get_remote_refs(struct transport *transport);
const struct alt_resource *const transport_prime_clone(struct transport *transport);
+const char *transport_download_primer(struct transport *transport,
+ const struct alt_resource *alt_res,
+ const char *base_path);
int transport_fetch_refs(struct transport *transport, struct ref *refs);
void transport_unlock_pack(struct transport *transport);
--
2.7.4
^ permalink raw reply related
* [PATCH 07/11] Resumable clone: add resumable download to http/curl
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Create resumable download procedure and progress display function.
The conversion from B to KB occurs because otherwise the byte counts
for large repos (i.e. Linux) overflow calculating percentage.
The download protocol includes the resource's URL, and the directory
the resource will be downloaded to. The url passed to remote-curl on
invocation does not matter (git clone will use the resource url
again here).
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
http.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
http.h | 7 ++++-
remote-curl.c | 27 +++++++++++++++++++
3 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/http.c b/http.c
index 1d5e3bb..93d6324 100644
--- a/http.c
+++ b/http.c
@@ -10,6 +10,8 @@
#include "pkt-line.h"
#include "gettext.h"
#include "transport.h"
+#include "progress.h"
+#include "dir.h"
#if LIBCURL_VERSION_NUM >= 0x070a08
long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
@@ -1136,7 +1138,10 @@ static int handle_curl_result(struct slot_results *results)
curl_easy_strerror(results->curl_result),
sizeof(curl_errorstr));
#endif
- return HTTP_ERROR;
+ if (results->http_code >= 400)
+ return HTTP_ERROR;
+ else
+ return HTTP_ERROR_RESUMABLE;
}
}
@@ -1365,6 +1370,40 @@ static void http_opt_request_remainder(CURL *curl, off_t pos)
#define HTTP_REQUEST_STRBUF 0
#define HTTP_REQUEST_FILE 1
+static int bytes_to_rounded_kb(double bytes)
+{
+ return (int) (bytes + 512)/1024;
+}
+
+int progress_func(void *data, double total_to_download, double now_downloaded,
+ double total_to_upload, double now_uploadeded)
+{
+ struct progress **progress = data;
+ int kilobytes = total_to_download >= 1024;
+
+ if (total_to_download <= 0.0) {
+ return 0;
+ }
+ if (kilobytes) {
+ now_downloaded = bytes_to_rounded_kb(now_downloaded);
+ total_to_download = bytes_to_rounded_kb(total_to_download);
+ }
+ if (!*progress && now_downloaded < total_to_download) {
+ if (total_to_download > 1024)
+ *progress = start_progress("Downloading (KB)",
+ total_to_download);
+ else
+ *progress = start_progress("Downloading (B)",
+ total_to_download);
+ }
+ display_progress(*progress, now_downloaded);
+ if (now_downloaded == total_to_download) {
+ stop_progress(progress);
+ }
+ return 0;
+}
+
+
static int http_request(const char *url,
void *result, int target,
const struct http_get_options *options)
@@ -1373,6 +1412,7 @@ static int http_request(const char *url,
struct slot_results results;
struct curl_slist *headers = NULL;
struct strbuf buf = STRBUF_INIT;
+ struct progress *progress = NULL;
const char *accept_language;
int ret;
@@ -1389,6 +1429,16 @@ static int http_request(const char *url,
off_t posn = ftello(result);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
fwrite);
+ if (options && options->progress) {
+ curl_easy_setopt(slot->curl,
+ CURLOPT_NOPROGRESS, 0);
+ curl_easy_setopt(slot->curl,
+ CURLOPT_PROGRESSFUNCTION,
+ progress_func);
+ curl_easy_setopt(slot->curl,
+ CURLOPT_PROGRESSDATA,
+ &progress);
+ }
if (posn > 0)
http_opt_request_remainder(slot->curl, posn);
} else
@@ -1559,6 +1609,40 @@ cleanup:
return ret;
}
+int http_download_primer(const char *url, const char *out_file)
+{
+ int ret = 0, try_count = HTTP_TRY_COUNT;
+ struct http_get_options options = {0};
+ options.progress = 1;
+
+ if (file_exists(out_file)) {
+ fprintf(stderr,
+ "File already downloaded: '%s', skipping...\n",
+ out_file);
+ return ret;
+ }
+
+ do {
+ if (try_count != HTTP_TRY_COUNT) {
+ fprintf(stderr, "Connection interrupted for some "
+ "reason, retrying (%d attempts left)\n",
+ try_count);
+ struct timeval time = {10, 0}; // 1s
+ select(0, NULL, NULL, NULL, &time);
+ }
+ ret = http_get_file(url, out_file, &options);
+ try_count--;
+ } while (try_count > 0 && ret == HTTP_ERROR_RESUMABLE);
+
+ if (ret != HTTP_OK) {
+ error("Unable to get resource: %s", url);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+
int http_fetch_ref(const char *base, struct ref *ref)
{
struct http_get_options options = {0};
diff --git a/http.h b/http.h
index 4ef4bbd..6a7ce7b 100644
--- a/http.h
+++ b/http.h
@@ -138,7 +138,8 @@ extern char *get_remote_object_url(const char *url, const char *hex,
/* Options for http_get_*() */
struct http_get_options {
unsigned no_cache:1,
- keep_error:1;
+ keep_error:1,
+ progress:1;
/* If non-NULL, returns the content-type of the response. */
struct strbuf *content_type;
@@ -172,6 +173,7 @@ struct http_get_options {
#define HTTP_START_FAILED 3
#define HTTP_REAUTH 4
#define HTTP_NOAUTH 5
+#define HTTP_ERROR_RESUMABLE 6
/*
* Requests a URL and stores the result in a strbuf.
@@ -180,6 +182,9 @@ struct http_get_options {
*/
int http_get_strbuf(const char *url, struct strbuf *result, struct http_get_options *options);
+#define HTTP_TRY_COUNT 5
+int http_download_primer(const char *url, const char *out_file);
+
extern int http_fetch_ref(const char *base, struct ref *ref);
/* Helpers for fetching packs */
diff --git a/remote-curl.c b/remote-curl.c
index 8ebb587..051ba52 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -394,6 +394,30 @@ static void prime_clone(void)
free(result_full);
}
+static void download_primer(const char *url, const char *base_dir)
+{
+ char *slash_ptr = strchr(url, '/'), *out_file;
+ struct strbuf out_path = STRBUF_INIT;
+ do {
+ out_file = slash_ptr + 1;
+ } while (slash_ptr = strchr(out_file, '/'));
+ strbuf_addf(&out_path, "%s/%s", base_dir, out_file);
+ if (!http_download_primer(url, out_path.buf))
+ printf("%s\n", out_path.buf);
+ printf("\n");
+ fflush(stdout);
+}
+
+static void parse_download_primer(struct strbuf *buf)
+{
+ const char *remote_url;
+ if (skip_prefix(buf->buf, "download-primer ", &remote_url)) {
+ char *base_path;
+ base_path = strchr(remote_url, ' ');
+ *base_path++ = '\0';
+ download_primer(remote_url, base_path);
+ }
+}
static struct discovery *discover_refs(const char *service, int for_push)
{
@@ -1105,6 +1129,8 @@ int main(int argc, const char **argv)
} else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) {
int for_push = !!strstr(buf.buf + 4, "for-push");
output_refs(get_refs(for_push));
+ } else if (starts_with(buf.buf, "download-primer")) {
+ parse_download_primer(&buf);
} else if (!strcmp(buf.buf, "prime-clone")) {
prime_clone();
} else if (starts_with(buf.buf, "push ")) {
@@ -1132,6 +1158,7 @@ int main(int argc, const char **argv)
printf("fetch\n");
printf("option\n");
printf("push\n");
+ printf("download-primer\n");
printf("prime-clone\n");
printf("check-connectivity\n");
printf("\n");
--
2.7.4
^ permalink raw reply related
* [PATCH 06/11] Resumable clone: implement transport_prime_clone
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Create transport_prime_clone API, as well as all internal methods.
Create representations of alt_resource and prime-clone path options.
The intention of get_alt_res_helper is solely to parse the output of
remote-curl because transport-helper does not handle verbose options
or speaking to the user verbosely. Therefore, all error parsing is
done with remote-curl, and any protocol breach between remote-curl and
transport-helper will treated as a bug and result in death.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
transport-helper.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-
transport.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
transport.h | 20 ++++++++++++++++++++
3 files changed, 114 insertions(+), 1 deletion(-)
diff --git a/transport-helper.c b/transport-helper.c
index b934183..eb185d5 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -28,7 +28,8 @@ struct helper_data {
signed_tags : 1,
check_connectivity : 1,
no_disconnect_req : 1,
- no_private_update : 1;
+ no_private_update : 1,
+ prime_clone : 1;
char *export_marks;
char *import_marks;
/* These go from remote name (as in "list") to private name */
@@ -180,6 +181,8 @@ static struct child_process *get_helper(struct transport *transport)
data->export = 1;
else if (!strcmp(capname, "check-connectivity"))
data->check_connectivity = 1;
+ else if (!strcmp(capname, "prime-clone"))
+ data->prime_clone = 1;
else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) {
ALLOC_GROW(refspecs,
refspec_nr + 1,
@@ -248,6 +251,7 @@ static int disconnect_helper(struct transport *transport)
}
static const char *unsupported_options[] = {
+ TRANS_OPT_PRIMECLONE,
TRANS_OPT_UPLOADPACK,
TRANS_OPT_RECEIVEPACK,
TRANS_OPT_THIN,
@@ -1054,6 +1058,50 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
return ret;
}
+static const struct alt_resource *const get_alt_res_helper(struct transport *transport)
+{
+ struct helper_data *data = transport->data;
+ char *url = NULL, *filetype = NULL;
+ struct alt_resource *ret = NULL;
+ struct strbuf out = STRBUF_INIT;
+ struct child_process *helper = get_helper(transport);
+ int err = 0;
+
+ helper = get_helper(transport);
+ write_constant(helper->in, "prime-clone\n");
+
+ while (!recvline(data, &out)) {
+ char *space = strchr(out.buf, ' ');
+
+ if (!*out.buf)
+ break;
+
+ if (starts_with(out.buf, "error")) {
+ err = 1;
+ continue;
+ }
+
+ if (!space || strchr(space + 1, ' '))
+ die("malformed alternate resource response: %s\n",
+ out.buf);
+
+ if ((filetype && url) || err)
+ continue;
+
+ filetype = xstrndup(out.buf, (space - out.buf));
+ url = xstrdup(space + 1);
+ }
+
+ if (filetype && url && !err) {
+ ret = xcalloc(1, sizeof(*ret));
+ ret->filetype = filetype;
+ ret->url = url;
+ }
+
+ strbuf_release(&out);
+ return ret;
+}
+
int transport_helper_init(struct transport *transport, const char *name)
{
struct helper_data *data = xcalloc(1, sizeof(*data));
@@ -1067,6 +1115,7 @@ int transport_helper_init(struct transport *transport, const char *name)
transport->data = data;
transport->set_option = set_helper_option;
transport->get_refs_list = get_refs_list;
+ transport->prime_clone = get_alt_res_helper;
transport->fetch = fetch;
transport->push_refs = push_refs;
transport->disconnect = release_helper;
diff --git a/transport.c b/transport.c
index 7bd3206..dd0d839 100644
--- a/transport.c
+++ b/transport.c
@@ -131,6 +131,9 @@ static int set_git_option(struct git_transport_options *opts,
} else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
opts->receivepack = value;
return 0;
+ } else if (!strcmp(name, TRANS_OPT_PRIMECLONE)) {
+ opts->primeclone = value;
+ return 0;
} else if (!strcmp(name, TRANS_OPT_THIN)) {
opts->thin = !!value;
return 0;
@@ -533,6 +536,42 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
return ret;
}
+const struct alt_resource *const get_alt_res_via_connect(struct transport *transport)
+{
+ struct git_transport_data *data = transport->data;
+ const struct alt_resource *res = NULL;
+ int flags = transport->verbose > 0 ? 0 : CONNECT_SUPPRESS_ERRORS;
+
+ data->conn = git_connect(data->fd, transport->url,
+ transport->smart_options->primeclone, flags);
+ res = get_alt_res_connect(data->fd[0], flags);
+
+ close(data->fd[0]);
+ close(data->fd[1]);
+ finish_connect(data->conn);
+ data->conn = NULL;
+
+ return res;
+}
+
+const struct alt_resource *const transport_prime_clone(struct transport *transport)
+{
+ if (transport->prime_clone && !transport->alt_res)
+ transport->alt_res = transport->prime_clone(transport);
+ if (transport->verbose > 0) {
+ if (transport->alt_res)
+ // redundant at this point, but will be
+ // more useful in future iterations with
+ // lists of potential resources
+ fprintf(stderr, "alt resource found: %s (%s)\n",
+ transport->alt_res->url,
+ transport->alt_res->filetype);
+ else
+ fprintf(stderr, "alt res not found\n");
+ }
+ return transport->alt_res;
+}
+
static int connect_git(struct transport *transport, const char *name,
const char *executable, int fd[2])
{
@@ -691,6 +730,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
ret->set_option = NULL;
ret->get_refs_list = get_refs_via_connect;
ret->fetch = fetch_refs_via_pack;
+ ret->prime_clone = get_alt_res_via_connect;
ret->push_refs = git_transport_push;
ret->connect = connect_git;
ret->disconnect = disconnect_git;
@@ -713,6 +753,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
ret->smart_options->receivepack = "git-receive-pack";
if (remote->receivepack)
ret->smart_options->receivepack = remote->receivepack;
+ // No remote.*.primeclone config because prime-clone only
+ // applies to clone. After that, it is never used the repo
+ // again.
+ ret->smart_options->primeclone = "git-prime-clone";
}
return ret;
diff --git a/transport.h b/transport.h
index c681408..2bb6963 100644
--- a/transport.h
+++ b/transport.h
@@ -15,6 +15,7 @@ struct git_transport_options {
int depth;
const char *uploadpack;
const char *receivepack;
+ const char *primeclone;
struct push_cas_option *cas;
};
@@ -24,11 +25,17 @@ enum transport_family {
TRANSPORT_FAMILY_IPV6
};
+struct alt_resource {
+ char *url;
+ char *filetype;
+};
+
struct transport {
struct remote *remote;
const char *url;
void *data;
const struct ref *remote_refs;
+ const struct alt_resource *alt_res;
/**
* Indicates whether we already called get_refs_list(); set by
@@ -68,6 +75,15 @@ struct transport {
struct ref *(*get_refs_list)(struct transport *transport, int for_push);
/**
+ * Returns the location of an alternate resource to fetch before
+ * cloning.
+ *
+ * If the transport cannot determine an alternate resource, then
+ * NULL is returned.
+ **/
+ const struct alt_resource *const (*prime_clone)(struct transport *transport);
+
+ /**
* Fetch the objects for the given refs. Note that this gets
* an array, and should ignore the list structure.
*
@@ -164,6 +180,9 @@ int transport_restrict_protocols(void);
/* The program to use on the remote side to send a pack */
#define TRANS_OPT_UPLOADPACK "uploadpack"
+/* The program to use on the remote side to check for alternate resource */
+#define TRANS_OPT_PRIMECLONE "primeclone"
+
/* The program to use on the remote side to receive a pack */
#define TRANS_OPT_RECEIVEPACK "receivepack"
@@ -208,6 +227,7 @@ int transport_push(struct transport *connection,
unsigned int * reject_reasons);
const struct ref *transport_get_remote_refs(struct transport *transport);
+const struct alt_resource *const transport_prime_clone(struct transport *transport);
int transport_fetch_refs(struct transport *transport, struct ref *refs);
void transport_unlock_pack(struct transport *transport);
--
2.7.4
^ permalink raw reply related
* [PATCH 05/11] Resumable clone: add output parsing to connect.c
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Add method for transport to call when parsing primeclone output from
stdin. Suppress stderr when using git_connect with ssh, unless output
is verbose.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
connect.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
connect.h | 10 ++++++----
2 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/connect.c b/connect.c
index 0478631..284de53 100644
--- a/connect.c
+++ b/connect.c
@@ -804,6 +804,10 @@ struct child_process *git_connect(int fd[2], const char *url,
}
argv_array_push(&conn->args, cmd.buf);
+ if (flags & CONNECT_SUPPRESS_ERRORS) {
+ conn->no_stderr = 1;
+ }
+
if (start_command(conn))
die("unable to fork");
@@ -831,3 +835,46 @@ int finish_connect(struct child_process *conn)
free(conn);
return code;
}
+
+const struct alt_resource *const get_alt_res_connect(int fd, int flags)
+{
+ struct alt_resource *res = NULL;
+ const char *line;
+ char *url = NULL, *filetype = NULL;
+ char *error_string = NULL;
+
+ while (line = packet_read_line_gentle(fd, NULL)) {
+ const char *space = strchr(line, ' ');
+
+ // We will eventually support multiple resources, so always
+ // parse the whole message
+ if ((filetype && url) || error_string) {
+ continue;
+ }
+ if (skip_prefix(line, "ERR ", &line) || !space ||
+ strchr(space + 1, ' ')) {
+ error_string = xstrdup(line);
+ continue;
+ }
+ filetype = xstrndup(line, (space - line));
+ url = xstrdup(space + 1);
+ }
+
+ if (filetype && url && !error_string){
+ res = xcalloc(1, sizeof(*res));
+ res->filetype = filetype;
+ res->url = url;
+ }
+ else {
+ if (!(flags & CONNECT_SUPPRESS_ERRORS)) {
+ if (error_string)
+ fprintf(stderr, "prime clone protocol error: "
+ "got '%s'\n", error_string);
+ else
+ fprintf(stderr, "did not get required "
+ "components for alternate resource\n");
+ }
+ }
+
+ return res;
+}
diff --git a/connect.h b/connect.h
index 01f14cd..966c0eb 100644
--- a/connect.h
+++ b/connect.h
@@ -1,10 +1,11 @@
#ifndef CONNECT_H
#define CONNECT_H
-#define CONNECT_VERBOSE (1u << 0)
-#define CONNECT_DIAG_URL (1u << 1)
-#define CONNECT_IPV4 (1u << 2)
-#define CONNECT_IPV6 (1u << 3)
+#define CONNECT_VERBOSE (1u << 0)
+#define CONNECT_DIAG_URL (1u << 1)
+#define CONNECT_IPV4 (1u << 2)
+#define CONNECT_IPV6 (1u << 3)
+#define CONNECT_SUPPRESS_ERRORS (1u << 4)
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
extern int git_connection_is_socket(struct child_process *conn);
@@ -12,5 +13,6 @@ extern int server_supports(const char *feature);
extern int parse_feature_request(const char *features, const char *feature);
extern const char *server_feature_value(const char *feature, int *len_ret);
extern int url_is_local_not_ssh(const char *url);
+const struct alt_resource *const get_alt_res_connect(int fd, int flags);
#endif
--
2.7.4
^ permalink raw reply related
* [PATCH 04/11] Resumable clone: add prime-clone to remote-curl
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Add function and interface to handle prime-clone input, extracting
and using duplicate functionality from discover_refs as function
request_service.
Because part of our goal is for prime_clone to recover from errors,
HTTP errors are only optionally printed to screen and never cause
death in this case.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
remote-curl.c | 165 ++++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 121 insertions(+), 44 deletions(-)
diff --git a/remote-curl.c b/remote-curl.c
index 15e48e2..8ebb587 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -13,6 +13,8 @@
#include "sha1-array.h"
#include "send-pack.h"
+#define HTTP_ERROR_GENTLE (1u << 0)
+
static struct remote *remote;
/* always ends with a trailing slash */
static struct strbuf url = STRBUF_INIT;
@@ -244,7 +246,31 @@ static int show_http_message(struct strbuf *type, struct strbuf *charset,
return 0;
}
-static struct discovery *discover_refs(const char *service, int for_push)
+static char *http_handle_result(int http_return)
+{
+ struct strbuf error = STRBUF_INIT;
+
+ switch (http_return) {
+ case HTTP_OK:
+ return NULL;
+ case HTTP_MISSING_TARGET:
+ strbuf_addf(&error, "repository '%s' not found", url.buf);
+ break;
+ case HTTP_NOAUTH:
+ strbuf_addf(&error, "Authentication failed for '%s'",
+ url.buf);
+ break;
+ default:
+ strbuf_addf(&error, "unable to access '%s': %s", url.buf,
+ curl_errorstr);
+ break;
+ }
+
+ return strbuf_detach(&error, NULL);
+}
+
+static int request_service(char const *const service, char **buffer_full,
+ char **buffer_msg, size_t *buffer_len, int flags)
{
struct strbuf exp = STRBUF_INIT;
struct strbuf type = STRBUF_INIT;
@@ -252,13 +278,9 @@ static struct discovery *discover_refs(const char *service, int for_push)
struct strbuf buffer = STRBUF_INIT;
struct strbuf refs_url = STRBUF_INIT;
struct strbuf effective_url = STRBUF_INIT;
- struct discovery *last = last_discovery;
- int http_ret, maybe_smart = 0;
- struct http_get_options options;
-
- if (last && !strcmp(service, last->service))
- return last;
- free_discovery(last);
+ int http_ret, maybe_smart = 0, ran_smart = 0;
+ struct http_get_options get_options;
+ const char *error_string;
strbuf_addf(&refs_url, "%sinfo/refs", url.buf);
if ((starts_with(url.buf, "http://") || starts_with(url.buf, "https://")) &&
@@ -271,45 +293,41 @@ static struct discovery *discover_refs(const char *service, int for_push)
strbuf_addf(&refs_url, "service=%s", service);
}
- memset(&options, 0, sizeof(options));
- options.content_type = &type;
- options.charset = &charset;
- options.effective_url = &effective_url;
- options.base_url = &url;
- options.no_cache = 1;
- options.keep_error = 1;
-
- http_ret = http_get_strbuf(refs_url.buf, &buffer, &options);
- switch (http_ret) {
- case HTTP_OK:
- break;
- case HTTP_MISSING_TARGET:
- show_http_message(&type, &charset, &buffer);
- die("repository '%s' not found", url.buf);
- case HTTP_NOAUTH:
- show_http_message(&type, &charset, &buffer);
- die("Authentication failed for '%s'", url.buf);
- default:
- show_http_message(&type, &charset, &buffer);
- die("unable to access '%s': %s", url.buf, curl_errorstr);
+ memset(&get_options, 0, sizeof(get_options));
+ get_options.content_type = &type;
+ get_options.charset = &charset;
+ get_options.effective_url = &effective_url;
+ get_options.base_url = &url;
+ get_options.no_cache = 1;
+ get_options.keep_error = 1;
+
+ http_ret = http_get_strbuf(refs_url.buf, &buffer, &get_options);
+ error_string = http_handle_result(http_ret);
+ if (error_string) {
+ if (!(flags & HTTP_ERROR_GENTLE)) {
+ show_http_message(&type, &charset, &buffer);
+ die("%s", error_string);
+ }
+ else if (options.verbosity > 1) {
+ show_http_message(&type, &charset, &buffer);
+ fprintf(stderr, "%s\n", error_string);
+ }
}
- last= xcalloc(1, sizeof(*last_discovery));
- last->service = service;
- last->buf_alloc = strbuf_detach(&buffer, &last->len);
- last->buf = last->buf_alloc;
+ *buffer_full = strbuf_detach(&buffer, buffer_len);
+ *buffer_msg = *buffer_full;
strbuf_addf(&exp, "application/x-%s-advertisement", service);
if (maybe_smart &&
- (5 <= last->len && last->buf[4] == '#') &&
- !strbuf_cmp(&exp, &type)) {
+ (5 <= *buffer_len && (*buffer_msg)[4] == '#') &&
+ !strbuf_cmp(&exp, &type) && http_ret == HTTP_OK) {
char *line;
/*
* smart HTTP response; validate that the service
* pkt-line matches our request.
*/
- line = packet_read_line_buf(&last->buf, &last->len, NULL);
+ line = packet_read_line_buf(buffer_msg, buffer_len, NULL);
strbuf_reset(&exp);
strbuf_addf(&exp, "# service=%s", service);
@@ -321,23 +339,80 @@ static struct discovery *discover_refs(const char *service, int for_push)
* until a packet flush marker. Ignore these now, but
* in the future we might start to scan them.
*/
- while (packet_read_line_buf(&last->buf, &last->len, NULL))
+ while (packet_read_line_buf(buffer_msg, buffer_len, NULL))
;
- last->proto_git = 1;
+ ran_smart = 1;
}
- if (last->proto_git)
- last->refs = parse_git_refs(last, for_push);
- else
- last->refs = parse_info_refs(last);
-
strbuf_release(&refs_url);
strbuf_release(&exp);
strbuf_release(&type);
strbuf_release(&charset);
strbuf_release(&effective_url);
strbuf_release(&buffer);
+
+ return ran_smart;
+}
+
+static void prime_clone(void)
+{
+ char *result, *result_full, *line;
+ size_t result_len;
+ int err = 0, one_successful = 0;
+
+ if (request_service("git-prime-clone", &result_full, &result,
+ &result_len, HTTP_ERROR_GENTLE)) {
+ while (line = packet_read_line_buf_gentle(&result, &result_len,
+ NULL)) {
+ char *space = strchr(line ,' ');
+
+ // We will eventually support multiple resources, so
+ // always parse the whole message
+ if (err)
+ continue;
+ if (!space || strchr(space + 1, ' ')) {
+ if (options.verbosity > 1)
+ fprintf(stderr, "prime clone "
+ "protocol error: got '%s'\n",
+ line);
+ printf("error\n");
+ err = 1;
+ continue;
+ }
+
+ one_successful = 1;
+ printf("%s\n", line);
+ }
+ if (!one_successful && options.verbosity > 1)
+ fprintf(stderr, "did not get required components for "
+ "alternate resource\n");
+ }
+
+ printf("\n");
+ fflush(stdout);
+ free(result_full);
+}
+
+
+static struct discovery *discover_refs(const char *service, int for_push)
+{
+ struct discovery *last = last_discovery;
+
+ if (last && !strcmp(service, last->service))
+ return last;
+ free_discovery(last);
+
+ last= xcalloc(1, sizeof(*last_discovery));
+ last->service = service;
+ last->proto_git = request_service(service, &last->buf_alloc,
+ &last->buf, &last->len, 0);
+
+ if (last->proto_git)
+ last->refs = parse_git_refs(last, for_push);
+ else
+ last->refs = parse_info_refs(last);
+
last_discovery = last;
return last;
}
@@ -1030,7 +1105,8 @@ int main(int argc, const char **argv)
} else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) {
int for_push = !!strstr(buf.buf + 4, "for-push");
output_refs(get_refs(for_push));
-
+ } else if (!strcmp(buf.buf, "prime-clone")) {
+ prime_clone();
} else if (starts_with(buf.buf, "push ")) {
parse_push(&buf);
@@ -1056,6 +1132,7 @@ int main(int argc, const char **argv)
printf("fetch\n");
printf("option\n");
printf("push\n");
+ printf("prime-clone\n");
printf("check-connectivity\n");
printf("\n");
fflush(stdout);
--
2.7.4
^ permalink raw reply related
* [PATCH 03/11] pkt-line: create gentle packet_read_line functions
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Create a functions that can read malformed messages without dying.
Includes creation of flag PACKET_READ_GENTLE_ALL. For use handling
prime-clone (or other server error) responses.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
pkt-line.c | 47 ++++++++++++++++++++++++++++++++++++++---------
pkt-line.h | 16 ++++++++++++++++
2 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/pkt-line.c b/pkt-line.c
index 62fdb37..96060e5 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -155,13 +155,17 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
*src_size -= ret;
} else {
ret = read_in_full(fd, dst, size);
- if (ret < 0)
+ if (ret < 0) {
+ if (options & PACKET_READ_GENTLE_ALL)
+ return -1;
+
die_errno("read error");
+ }
}
/* And complain if we didn't get enough bytes to satisfy the read. */
if (ret < size) {
- if (options & PACKET_READ_GENTLE_ON_EOF)
+ if (options & (PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ALL))
return -1;
die("The remote end hung up unexpectedly");
@@ -205,15 +209,23 @@ int packet_read(int fd, char **src_buf, size_t *src_len,
if (ret < 0)
return ret;
len = packet_length(linelen);
- if (len < 0)
+ if (len < 0) {
+ if (options & PACKET_READ_GENTLE_ALL)
+ return -1;
+
die("protocol error: bad line length character: %.4s", linelen);
+ }
if (!len) {
packet_trace("0000", 4, 0);
return 0;
}
len -= 4;
- if (len >= size)
+ if (len >= size) {
+ if (options & PACKET_READ_GENTLE_ALL)
+ return -1;
+
die("protocol error: bad line length %d", len);
+ }
ret = get_packet_data(fd, src_buf, src_len, buffer, len, options);
if (ret < 0)
return ret;
@@ -229,22 +241,39 @@ int packet_read(int fd, char **src_buf, size_t *src_len,
static char *packet_read_line_generic(int fd,
char **src, size_t *src_len,
- int *dst_len)
+ int *dst_len, int flags)
{
int len = packet_read(fd, src, src_len,
packet_buffer, sizeof(packet_buffer),
- PACKET_READ_CHOMP_NEWLINE);
+ flags);
if (dst_len)
*dst_len = len;
- return len ? packet_buffer : NULL;
+ return len > 0 ? packet_buffer : NULL;
}
char *packet_read_line(int fd, int *len_p)
{
- return packet_read_line_generic(fd, NULL, NULL, len_p);
+ return packet_read_line_generic(fd, NULL, NULL, len_p,
+ PACKET_READ_CHOMP_NEWLINE);
}
char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)
{
- return packet_read_line_generic(-1, src, src_len, dst_len);
+ return packet_read_line_generic(-1, src, src_len, dst_len,
+ PACKET_READ_CHOMP_NEWLINE);
+}
+
+char *packet_read_line_gentle(int fd, int *len_p)
+{
+ return packet_read_line_generic(fd, NULL, NULL, len_p,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ALL);
+}
+
+
+char *packet_read_line_buf_gentle(char **src, size_t *src_len, int *dst_len)
+{
+ return packet_read_line_generic(-1, src, src_len, dst_len,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ALL);
}
diff --git a/pkt-line.h b/pkt-line.h
index 3cb9d91..553e42e 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -52,11 +52,15 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
* condition 4 (truncated input), but instead return -1. However, we will still
* die for the other 3 conditions.
*
+ * If options contains PACKET_READ_GENTLE_ALL, we will not die on any of the
+ * conditions, but return -1 instead.
+ *
* If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if
* present) is removed from the buffer before returning.
*/
#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+#define PACKET_READ_GENTLE_ALL (1u<<2)
int packet_read(int fd, char **src_buffer, size_t *src_len, char
*buffer, unsigned size, int options);
@@ -75,6 +79,18 @@ char *packet_read_line(int fd, int *size);
*/
char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
+/*
+ * Same as packet_read_line, but does not die on any errors (uses
+ * PACKET_READ_GENTLE_ALL).
+ */
+char *packet_read_line_gentle(int fd, int *len_p);
+
+/*
+ * Same as packet_read_line_buf, but does not die on any errors (uses
+ * PACKET_READ_GENTLE_ALL).
+ */
+char *packet_read_line_buf_gentle(char **src_buf, size_t *src_len, int *size);
+
#define DEFAULT_PACKET_MAX 1000
#define LARGE_PACKET_MAX 65520
extern char packet_buffer[LARGE_PACKET_MAX];
--
2.7.4
^ permalink raw reply related
* [PATCH 02/11] Resumable clone: add prime-clone endpoints
From: Kevin Wern @ 2016-09-16 0:12 UTC (permalink / raw)
To: git
In-Reply-To: <1473984742-12516-1-git-send-email-kevin.m.wern@gmail.com>
Add logic to serve git-prime-clone to git and http clients.
Do not pass --stateless-rpc and --advertise-refs options to
prime-clone. It is inherently stateless and an 'advertisement'.
Signed-off-by: Kevin Wern <kevin.m.wern@gmail.com>
---
Documentation/git-daemon.txt | 7 +++++++
Documentation/git-http-backend.txt | 7 +++++++
daemon.c | 7 +++++++
http-backend.c | 22 +++++++++++++++++-----
4 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index a69b361..853faab 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -231,6 +231,13 @@ receive-pack::
enabled by setting `daemon.receivepack` configuration item to
`true`.
+primeclone::
+ This serves 'git prime-clone' service to clients, allowing
+ 'git clone' clients to get the location of a static resource
+ to download and integrate before performing an incremental
+ fetch. It is 'false' by default, but can be enabled by setting
+ it to `true`.
+
EXAMPLES
--------
We assume the following in /etc/services::
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 9268fb6..40be74e 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -54,6 +54,13 @@ http.receivepack::
disabled by setting this item to `false`, or enabled for all
users, including anonymous users, by setting it to `true`.
+http.primeclone::
+ This serves 'git prime-clone' service to clients, allowing
+ 'git clone' clients to get the location of a static resource
+ to download and integrate before performing an incremental
+ fetch. It is 'false' by default, but can be enabled by setting
+ it to `true`.
+
URL TRANSLATION
---------------
To determine the location of the repository on disk, 'git http-backend'
diff --git a/daemon.c b/daemon.c
index 8d45c33..c2f539c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -475,10 +475,17 @@ static int receive_pack(void)
return run_service_command(argv);
}
+static int prime_clone(void)
+{
+ static const char *argv[] = { "prime-clone", "--strict", ".", NULL };
+ return run_service_command(argv);
+}
+
static struct daemon_service daemon_service[] = {
{ "upload-archive", "uploadarch", upload_archive, 0, 1 },
{ "upload-pack", "uploadpack", upload_pack, 1, 1 },
{ "receive-pack", "receivepack", receive_pack, 0, 1 },
+ { "prime-clone", "primeclone", prime_clone, 0, 1 },
};
static void enable_service(const char *name, int ena)
diff --git a/http-backend.c b/http-backend.c
index 8870a26..9c89a10 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -27,6 +27,7 @@ struct rpc_service {
static struct rpc_service rpc_service[] = {
{ "upload-pack", "uploadpack", 1, 1 },
{ "receive-pack", "receivepack", 0, -1 },
+ { "prime-clone", "primeclone", 0, -1 },
};
static struct string_list *get_parameters(void)
@@ -450,11 +451,22 @@ static void get_info_refs(char *arg)
hdr_nocache();
if (service_name) {
- const char *argv[] = {NULL /* service name */,
- "--stateless-rpc", "--advertise-refs",
- ".", NULL};
+ struct argv_array argv;
struct rpc_service *svc = select_service(service_name);
+ argv_array_init(&argv);
+ argv_array_push(&argv, svc->name);
+
+ // prime-clone does not need --stateless-rpc and
+ // --advertise-refs options. Maybe it will in the future, but
+ // until then it seems best to do this instead of adding
+ // "dummy" options.
+ if (strcmp(svc->name, "prime-clone") != 0) {
+ argv_array_pushl(&argv, "--stateless-rpc",
+ "--advertise-refs", NULL);
+ }
+
+ argv_array_pushl(&argv, ".", NULL);
strbuf_addf(&buf, "application/x-git-%s-advertisement",
svc->name);
hdr_str(content_type, buf.buf);
@@ -463,8 +475,8 @@ static void get_info_refs(char *arg)
packet_write(1, "# service=git-%s\n", svc->name);
packet_flush(1);
- argv[0] = svc->name;
- run_service(argv, 0);
+ run_service(argv.argv, 0);
+ argv_array_clear(&argv);
} else {
select_getanyfile();
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox