2007-09-11 11:03:04 +08:00
|
|
|
#include "cache.h"
|
2017-06-15 02:07:36 +08:00
|
|
|
#include "config.h"
|
2007-09-11 11:03:04 +08:00
|
|
|
#include "transport.h"
|
2021-09-27 03:03:26 +08:00
|
|
|
#include "hook.h"
|
2007-09-11 11:03:11 +08:00
|
|
|
#include "pkt-line.h"
|
|
|
|
#include "fetch-pack.h"
|
2013-07-09 04:56:53 +08:00
|
|
|
#include "remote.h"
|
|
|
|
#include "connect.h"
|
2007-10-30 10:03:42 +08:00
|
|
|
#include "send-pack.h"
|
2007-09-11 11:03:11 +08:00
|
|
|
#include "walker.h"
|
2007-09-11 11:03:21 +08:00
|
|
|
#include "bundle.h"
|
2007-10-01 07:59:39 +08:00
|
|
|
#include "dir.h"
|
|
|
|
#include "refs.h"
|
2018-05-17 06:57:48 +08:00
|
|
|
#include "refspec.h"
|
2010-01-17 05:45:31 +08:00
|
|
|
#include "branch.h"
|
2010-05-23 17:17:55 +08:00
|
|
|
#include "url.h"
|
2011-08-20 06:08:47 +08:00
|
|
|
#include "submodule.h"
|
2012-03-29 15:21:23 +08:00
|
|
|
#include "string-list.h"
|
2020-03-30 22:03:46 +08:00
|
|
|
#include "oid-array.h"
|
2015-11-16 16:05:58 +08:00
|
|
|
#include "sigchain.h"
|
2017-12-15 05:44:45 +08:00
|
|
|
#include "transport-internal.h"
|
2018-03-15 02:31:45 +08:00
|
|
|
#include "protocol.h"
|
2018-03-24 01:20:56 +08:00
|
|
|
#include "object-store.h"
|
2018-04-21 18:10:00 +08:00
|
|
|
#include "color.h"
|
|
|
|
|
|
|
|
static int transport_use_color = -1;
|
|
|
|
static char transport_colors[][COLOR_MAXLEN] = {
|
|
|
|
GIT_COLOR_RESET,
|
|
|
|
GIT_COLOR_RED /* REJECTED */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum color_transport {
|
|
|
|
TRANSPORT_COLOR_RESET = 0,
|
|
|
|
TRANSPORT_COLOR_REJECTED = 1
|
|
|
|
};
|
|
|
|
|
|
|
|
static int transport_color_config(void)
|
|
|
|
{
|
|
|
|
const char *keys[] = {
|
|
|
|
"color.transport.reset",
|
|
|
|
"color.transport.rejected"
|
|
|
|
}, *key = "color.transport";
|
|
|
|
char *value;
|
|
|
|
int i;
|
|
|
|
static int initialized;
|
|
|
|
|
|
|
|
if (initialized)
|
|
|
|
return 0;
|
|
|
|
initialized = 1;
|
|
|
|
|
|
|
|
if (!git_config_get_string(key, &value))
|
|
|
|
transport_use_color = git_config_colorbool(key, value);
|
|
|
|
|
|
|
|
if (!want_color_stderr(transport_use_color))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(keys); i++)
|
|
|
|
if (!git_config_get_string(keys[i], &value)) {
|
|
|
|
if (!value)
|
|
|
|
return config_error_nonbool(keys[i]);
|
|
|
|
if (color_parse(value, transport_colors[i]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *transport_get_color(enum color_transport ix)
|
|
|
|
{
|
|
|
|
if (want_color_stderr(transport_use_color))
|
|
|
|
return transport_colors[ix];
|
|
|
|
return "";
|
|
|
|
}
|
2007-10-01 07:59:39 +08:00
|
|
|
|
2010-01-17 05:45:31 +08:00
|
|
|
static void set_upstreams(struct transport *transport, struct ref *refs,
|
|
|
|
int pretend)
|
|
|
|
{
|
|
|
|
struct ref *ref;
|
|
|
|
for (ref = refs; ref; ref = ref->next) {
|
|
|
|
const char *localname;
|
|
|
|
const char *tmp;
|
|
|
|
const char *remotename;
|
|
|
|
int flag = 0;
|
|
|
|
/*
|
|
|
|
* Check suitability for tracking. Must be successful /
|
|
|
|
* already up-to-date ref create/modify (not delete).
|
|
|
|
*/
|
|
|
|
if (ref->status != REF_STATUS_OK &&
|
|
|
|
ref->status != REF_STATUS_UPTODATE)
|
|
|
|
continue;
|
|
|
|
if (!ref->peer_ref)
|
|
|
|
continue;
|
2015-11-10 10:22:20 +08:00
|
|
|
if (is_null_oid(&ref->new_oid))
|
2010-01-17 05:45:31 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Follow symbolic refs (mainly for HEAD). */
|
|
|
|
localname = ref->peer_ref->name;
|
|
|
|
remotename = ref->name;
|
2014-07-16 03:59:36 +08:00
|
|
|
tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,
|
2017-09-23 17:45:04 +08:00
|
|
|
NULL, &flag);
|
2010-01-17 05:45:31 +08:00
|
|
|
if (tmp && flag & REF_ISSYMREF &&
|
2013-12-01 04:55:40 +08:00
|
|
|
starts_with(tmp, "refs/heads/"))
|
2010-01-17 05:45:31 +08:00
|
|
|
localname = tmp;
|
|
|
|
|
|
|
|
/* Both source and destination must be local branches. */
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!localname || !starts_with(localname, "refs/heads/"))
|
2010-01-17 05:45:31 +08:00
|
|
|
continue;
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!remotename || !starts_with(remotename, "refs/heads/"))
|
2010-01-17 05:45:31 +08:00
|
|
|
continue;
|
|
|
|
|
2021-04-15 20:33:53 +08:00
|
|
|
if (!pretend) {
|
|
|
|
int flag = transport->verbose < 0 ? 0 : BRANCH_CONFIG_VERBOSE;
|
|
|
|
install_branch_config(flag, localname + 11,
|
|
|
|
transport->remote->name, remotename);
|
|
|
|
} else if (transport->verbose >= 0)
|
2016-06-18 04:20:53 +08:00
|
|
|
printf(_("Would set upstream of '%s' to '%s' of '%s'\n"),
|
2010-01-17 05:45:31 +08:00
|
|
|
localname + 11, remotename + 11,
|
|
|
|
transport->remote->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-09-11 11:03:21 +08:00
|
|
|
struct bundle_transport_data {
|
|
|
|
int fd;
|
|
|
|
struct bundle_header header;
|
2019-08-22 06:20:10 +08:00
|
|
|
unsigned get_refs_from_bundle_called : 1;
|
2007-09-11 11:03:21 +08:00
|
|
|
};
|
|
|
|
|
2022-03-05 02:32:10 +08:00
|
|
|
static void get_refs_from_bundle_inner(struct transport *transport)
|
2007-09-11 11:03:21 +08:00
|
|
|
{
|
|
|
|
struct bundle_transport_data *data = transport->data;
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2019-08-22 06:20:10 +08:00
|
|
|
data->get_refs_from_bundle_called = 1;
|
|
|
|
|
2007-09-11 11:03:21 +08:00
|
|
|
if (data->fd > 0)
|
|
|
|
close(data->fd);
|
|
|
|
data->fd = read_bundle_header(transport->url, &data->header);
|
|
|
|
if (data->fd < 0)
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("could not read bundle '%s'"), transport->url);
|
2020-06-20 01:56:00 +08:00
|
|
|
|
|
|
|
transport->hash_algo = data->header.hash_algo;
|
2022-03-05 02:32:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct ref *get_refs_from_bundle(struct transport *transport,
|
|
|
|
int for_push,
|
|
|
|
struct transport_ls_refs_options *transport_options)
|
|
|
|
{
|
|
|
|
struct bundle_transport_data *data = transport->data;
|
|
|
|
struct ref *result = NULL;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (for_push)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
get_refs_from_bundle_inner(transport);
|
2020-06-20 01:56:00 +08:00
|
|
|
|
2007-09-11 11:03:21 +08:00
|
|
|
for (i = 0; i < data->header.references.nr; i++) {
|
bundle: remove "ref_list" in favor of string-list.c API
Move away from the "struct ref_list" in bundle.c in favor of the
almost identical string-list.c API.
That API fits this use-case perfectly, but did not exist in its
current form when this code was added in 2e0afafebd (Add git-bundle:
move objects and references by archive, 2007-02-22), with hindsight we
could have used the path-list API, which later got renamed to
string-list. See 8fd2cb4069 (Extract helper bits from
c-merge-recursive work, 2006-07-25)
We need to change "name" to "string" and "oid" to "util" to make this
conversion, but other than that the APIs are pretty much identical for
what bundle.c made use of.
Let's also replace the memset(..,0,...) pattern with a more idiomatic
"INIT" macro, and finally add a *_release() function so to free the
allocated memory.
Before this the add_to_ref_list() would leak memory, now e.g. "bundle
list-heads" reports no memory leaks at all under valgrind.
In the bundle_header_init() function we're using a clever trick to
memcpy() what we'd get from the corresponding
BUNDLE_HEADER_INIT. There is a concurrent series to make use of that
pattern more generally, see [1].
1. https://lore.kernel.org/git/cover-0.5-00000000000-20210701T104855Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-02 17:57:32 +08:00
|
|
|
struct string_list_item *e = data->header.references.items + i;
|
|
|
|
const char *name = e->string;
|
2021-07-02 17:57:31 +08:00
|
|
|
struct ref *ref = alloc_ref(name);
|
bundle: remove "ref_list" in favor of string-list.c API
Move away from the "struct ref_list" in bundle.c in favor of the
almost identical string-list.c API.
That API fits this use-case perfectly, but did not exist in its
current form when this code was added in 2e0afafebd (Add git-bundle:
move objects and references by archive, 2007-02-22), with hindsight we
could have used the path-list API, which later got renamed to
string-list. See 8fd2cb4069 (Extract helper bits from
c-merge-recursive work, 2006-07-25)
We need to change "name" to "string" and "oid" to "util" to make this
conversion, but other than that the APIs are pretty much identical for
what bundle.c made use of.
Let's also replace the memset(..,0,...) pattern with a more idiomatic
"INIT" macro, and finally add a *_release() function so to free the
allocated memory.
Before this the add_to_ref_list() would leak memory, now e.g. "bundle
list-heads" reports no memory leaks at all under valgrind.
In the bundle_header_init() function we're using a clever trick to
memcpy() what we'd get from the corresponding
BUNDLE_HEADER_INIT. There is a concurrent series to make use of that
pattern more generally, see [1].
1. https://lore.kernel.org/git/cover-0.5-00000000000-20210701T104855Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-02 17:57:32 +08:00
|
|
|
struct object_id *oid = e->util;
|
2021-07-02 17:57:31 +08:00
|
|
|
oidcpy(&ref->old_oid, oid);
|
2007-09-11 11:03:21 +08:00
|
|
|
ref->next = result;
|
|
|
|
result = ref;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2007-09-14 15:31:23 +08:00
|
|
|
static int fetch_refs_from_bundle(struct transport *transport,
|
fetch-pack: unify ref in and out param
When a user fetches:
- at least one up-to-date ref and at least one non-up-to-date ref,
- using HTTP with protocol v0 (or something else that uses the fetch
command of a remote helper)
some refs might not be updated after the fetch.
This bug was introduced in commit 989b8c4452 ("fetch-pack: put shallow
info in output parameter", 2018-06-28) which allowed transports to
report the refs that they have fetched in a new out-parameter
"fetched_refs". If they do so, transport_fetch_refs() makes this
information available to its caller.
Users of "fetched_refs" rely on the following 3 properties:
(1) it is the complete list of refs that was passed to
transport_fetch_refs(),
(2) it has shallow information (REF_STATUS_REJECT_SHALLOW set if
relevant), and
(3) it has updated OIDs if ref-in-want was used (introduced after
989b8c4452).
In an effort to satisfy (1), whenever transport_fetch_refs()
filters the refs sent to the transport, it re-adds the filtered refs to
whatever the transport supplies before returning it to the user.
However, the implementation in 989b8c4452 unconditionally re-adds the
filtered refs without checking if the transport refrained from reporting
anything in "fetched_refs" (which it is allowed to do), resulting in an
incomplete list, no longer satisfying (1).
An earlier effort to resolve this [1] solved the issue by readding the
filtered refs only if the transport did not refrain from reporting in
"fetched_refs", but after further discussion, it seems that the better
solution is to revert the API change that introduced "fetched_refs".
This API change was first suggested as part of a ref-in-want
implementation that allowed for ref patterns and, thus, there could be
drastic differences between the input refs and the refs actually fetched
[2]; we eventually decided to only allow exact ref names, but this API
change remained even though its necessity was decreased.
Therefore, revert this API change by reverting commit 989b8c4452, and
make receive_wanted_refs() update the OIDs in the sought array (like how
update_shallow() updates shallow information in the sought array)
instead. A test is also included to show that the user-visible bug
discussed at the beginning of this commit message no longer exists.
[1] https://public-inbox.org/git/20180801171806.GA122458@google.com/
[2] https://public-inbox.org/git/86a128c5fb710a41791e7183207c4d64889f9307.1485381677.git.jonathantanmy@google.com/
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-02 04:13:20 +08:00
|
|
|
int nr_heads, struct ref **to_fetch)
|
2007-09-11 11:03:21 +08:00
|
|
|
{
|
|
|
|
struct bundle_transport_data *data = transport->data;
|
2021-09-05 15:34:43 +08:00
|
|
|
struct strvec extra_index_pack_args = STRVEC_INIT;
|
2020-06-20 01:56:00 +08:00
|
|
|
int ret;
|
2019-08-22 06:20:10 +08:00
|
|
|
|
2021-09-05 15:34:43 +08:00
|
|
|
if (transport->progress)
|
|
|
|
strvec_push(&extra_index_pack_args, "-v");
|
|
|
|
|
2019-08-22 06:20:10 +08:00
|
|
|
if (!data->get_refs_from_bundle_called)
|
2022-03-05 02:32:10 +08:00
|
|
|
get_refs_from_bundle_inner(transport);
|
2020-06-20 01:56:00 +08:00
|
|
|
ret = unbundle(the_repository, &data->header, data->fd,
|
2021-09-05 15:34:43 +08:00
|
|
|
&extra_index_pack_args);
|
2020-06-20 01:56:00 +08:00
|
|
|
transport->hash_algo = data->header.hash_algo;
|
|
|
|
return ret;
|
2007-09-11 11:03:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int close_bundle(struct transport *transport)
|
|
|
|
{
|
|
|
|
struct bundle_transport_data *data = transport->data;
|
|
|
|
if (data->fd > 0)
|
|
|
|
close(data->fd);
|
bundle: remove "ref_list" in favor of string-list.c API
Move away from the "struct ref_list" in bundle.c in favor of the
almost identical string-list.c API.
That API fits this use-case perfectly, but did not exist in its
current form when this code was added in 2e0afafebd (Add git-bundle:
move objects and references by archive, 2007-02-22), with hindsight we
could have used the path-list API, which later got renamed to
string-list. See 8fd2cb4069 (Extract helper bits from
c-merge-recursive work, 2006-07-25)
We need to change "name" to "string" and "oid" to "util" to make this
conversion, but other than that the APIs are pretty much identical for
what bundle.c made use of.
Let's also replace the memset(..,0,...) pattern with a more idiomatic
"INIT" macro, and finally add a *_release() function so to free the
allocated memory.
Before this the add_to_ref_list() would leak memory, now e.g. "bundle
list-heads" reports no memory leaks at all under valgrind.
In the bundle_header_init() function we're using a clever trick to
memcpy() what we'd get from the corresponding
BUNDLE_HEADER_INIT. There is a concurrent series to make use of that
pattern more generally, see [1].
1. https://lore.kernel.org/git/cover-0.5-00000000000-20210701T104855Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-02 17:57:32 +08:00
|
|
|
bundle_header_release(&data->header);
|
2007-09-19 12:49:42 +08:00
|
|
|
free(data);
|
2007-09-11 11:03:21 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-09-11 11:03:04 +08:00
|
|
|
struct git_transport_data {
|
2009-12-09 23:26:30 +08:00
|
|
|
struct git_transport_options options;
|
2008-02-05 02:26:23 +08:00
|
|
|
struct child_process *conn;
|
|
|
|
int fd[2];
|
2009-12-09 23:26:31 +08:00
|
|
|
unsigned got_remote_heads : 1;
|
2018-03-15 02:31:46 +08:00
|
|
|
enum protocol_version version;
|
2017-03-31 09:40:00 +08:00
|
|
|
struct oid_array extra_have;
|
|
|
|
struct oid_array shallow;
|
2007-09-11 11:03:04 +08:00
|
|
|
};
|
|
|
|
|
2009-12-09 23:26:30 +08:00
|
|
|
static int set_git_option(struct git_transport_options *opts,
|
2007-09-11 11:03:04 +08:00
|
|
|
const char *name, const char *value)
|
|
|
|
{
|
2007-09-11 11:03:11 +08:00
|
|
|
if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->uploadpack = value;
|
2007-09-11 11:03:11 +08:00
|
|
|
return 0;
|
|
|
|
} else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->receivepack = value;
|
2007-09-11 11:03:04 +08:00
|
|
|
return 0;
|
|
|
|
} else if (!strcmp(name, TRANS_OPT_THIN)) {
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->thin = !!value;
|
2007-09-11 11:03:04 +08:00
|
|
|
return 0;
|
2008-03-04 11:27:40 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->followtags = !!value;
|
2008-03-04 11:27:40 +08:00
|
|
|
return 0;
|
2007-09-11 11:03:11 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_KEEP)) {
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->keep = !!value;
|
2007-09-11 11:03:11 +08:00
|
|
|
return 0;
|
2013-12-05 21:02:42 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_UPDATE_SHALLOW)) {
|
|
|
|
opts->update_shallow = !!value;
|
|
|
|
return 0;
|
2007-09-11 11:03:11 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_DEPTH)) {
|
|
|
|
if (!value)
|
2009-12-09 23:26:30 +08:00
|
|
|
opts->depth = 0;
|
2012-01-04 18:01:55 +08:00
|
|
|
else {
|
|
|
|
char *end;
|
|
|
|
opts->depth = strtol(value, &end, 0);
|
|
|
|
if (*end)
|
2016-06-18 04:20:53 +08:00
|
|
|
die(_("transport: invalid depth option '%s'"), value);
|
2012-01-04 18:01:55 +08:00
|
|
|
}
|
2007-09-11 11:03:11 +08:00
|
|
|
return 0;
|
2016-06-12 18:53:59 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_DEEPEN_SINCE)) {
|
|
|
|
opts->deepen_since = value;
|
|
|
|
return 0;
|
2016-06-12 18:54:04 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_DEEPEN_NOT)) {
|
|
|
|
opts->deepen_not = (const struct string_list *)value;
|
|
|
|
return 0;
|
fetch, upload-pack: --deepen=N extends shallow boundary by N commits
In git-fetch, --depth argument is always relative with the latest
remote refs. This makes it a bit difficult to cover this use case,
where the user wants to make the shallow history, say 3 levels
deeper. It would work if remote refs have not moved yet, but nobody
can guarantee that, especially when that use case is performed a
couple months after the last clone or "git fetch --depth". Also,
modifying shallow boundary using --depth does not work well with
clones created by --since or --not.
This patch fixes that. A new argument --deepen=<N> will add <N> more (*)
parent commits to the current history regardless of where remote refs
are.
Have/Want negotiation is still respected. So if remote refs move, the
server will send two chunks: one between "have" and "want" and another
to extend shallow history. In theory, the client could send no "want"s
in order to get the second chunk only. But the protocol does not allow
that. Either you send no want lines, which means ls-remote; or you
have to send at least one want line that carries deep-relative to the
server..
The main work was done by Dongcan Jiang. I fixed it up here and there.
And of course all the bugs belong to me.
(*) We could even support --deepen=<N> where <N> is negative. In that
case we can cut some history from the shallow clone. This operation
(and --depth=<shorter depth>) does not require interaction with remote
side (and more complicated to implement as a result).
Helped-by: Duy Nguyen <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Dongcan Jiang <dongcan.jiang@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-06-12 18:54:09 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_DEEPEN_RELATIVE)) {
|
|
|
|
opts->deepen_relative = !!value;
|
|
|
|
return 0;
|
introduce fetch-object: fetch one promisor object
Introduce fetch-object, providing the ability to fetch one object from a
promisor remote.
This uses fetch-pack. To do this, the transport mechanism has been
updated with 2 flags, "from-promisor" to indicate that the resulting
pack comes from a promisor remote (and thus should be annotated as such
by index-pack), and "no-dependents" to indicate that only the objects
themselves need to be fetched (but fetching additional objects is
nevertheless safe).
Whenever "no-dependents" is used, fetch-pack will refrain from using any
object flags, because it is most likely invoked as part of a dynamic
object fetch by another Git command (which may itself use object flags).
An alternative to this is to leave fetch-pack alone, and instead update
the allocation of flags so that fetch-pack's flags never overlap with
any others, but this will end up shrinking the number of flags available
to nearly every other Git command (that is, every Git command that
accesses objects), so the approach in this commit was used instead.
This will be tested in a subsequent commit.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-12-06 00:58:49 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_FROM_PROMISOR)) {
|
|
|
|
opts->from_promisor = !!value;
|
|
|
|
return 0;
|
2017-12-08 23:58:40 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) {
|
2019-06-28 06:54:12 +08:00
|
|
|
list_objects_filter_die_if_populated(&opts->filter_options);
|
2017-12-08 23:58:40 +08:00
|
|
|
parse_list_objects_filter(&opts->filter_options, value);
|
|
|
|
return 0;
|
2022-03-28 22:02:08 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_REFETCH)) {
|
|
|
|
opts->refetch = !!value;
|
|
|
|
return 0;
|
2021-04-01 18:46:59 +08:00
|
|
|
} else if (!strcmp(name, TRANS_OPT_REJECT_SHALLOW)) {
|
|
|
|
opts->reject_shallow = !!value;
|
|
|
|
return 0;
|
2007-09-11 11:03:04 +08:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-01-29 06:51:23 +08:00
|
|
|
static int connect_setup(struct transport *transport, int for_push)
|
2008-02-05 02:26:23 +08:00
|
|
|
{
|
|
|
|
struct git_transport_data *data = transport->data;
|
2016-01-29 06:51:23 +08:00
|
|
|
int flags = transport->verbose > 0 ? CONNECT_VERBOSE : 0;
|
2009-12-09 23:26:31 +08:00
|
|
|
|
|
|
|
if (data->conn)
|
|
|
|
return 0;
|
|
|
|
|
2016-02-03 12:09:14 +08:00
|
|
|
switch (transport->family) {
|
|
|
|
case TRANSPORT_FAMILY_ALL: break;
|
|
|
|
case TRANSPORT_FAMILY_IPV4: flags |= CONNECT_IPV4; break;
|
|
|
|
case TRANSPORT_FAMILY_IPV6: flags |= CONNECT_IPV6; break;
|
|
|
|
}
|
|
|
|
|
2011-09-07 02:06:32 +08:00
|
|
|
data->conn = git_connect(data->fd, transport->url,
|
|
|
|
for_push ? data->options.receivepack :
|
|
|
|
data->options.uploadpack,
|
2016-01-29 06:51:23 +08:00
|
|
|
flags);
|
2009-12-09 23:26:31 +08:00
|
|
|
|
2008-02-05 02:26:23 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-13 03:51:21 +08:00
|
|
|
static void die_if_server_options(struct transport *transport)
|
|
|
|
{
|
|
|
|
if (!transport->server_options || !transport->server_options->nr)
|
|
|
|
return;
|
|
|
|
advise(_("see protocol.version in 'git help config' for more details"));
|
|
|
|
die(_("server options require protocol version 2 or later"));
|
|
|
|
}
|
|
|
|
|
2018-09-28 03:24:04 +08:00
|
|
|
/*
|
|
|
|
* Obtains the protocol version from the transport and writes it to
|
|
|
|
* transport->data->version, first connecting if not already connected.
|
|
|
|
*
|
|
|
|
* If the protocol version is one that allows skipping the listing of remote
|
|
|
|
* refs, and must_list_refs is 0, the listing of remote refs is skipped and
|
|
|
|
* this function returns NULL. Otherwise, this function returns the list of
|
|
|
|
* remote refs.
|
|
|
|
*/
|
|
|
|
static struct ref *handshake(struct transport *transport, int for_push,
|
2021-02-06 04:48:48 +08:00
|
|
|
struct transport_ls_refs_options *options,
|
2018-09-28 03:24:04 +08:00
|
|
|
int must_list_refs)
|
2007-09-11 11:03:11 +08:00
|
|
|
{
|
|
|
|
struct git_transport_data *data = transport->data;
|
2018-03-15 02:31:45 +08:00
|
|
|
struct ref *refs = NULL;
|
|
|
|
struct packet_reader reader;
|
2020-11-12 07:29:30 +08:00
|
|
|
int sid_len;
|
|
|
|
const char *server_sid;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
2016-01-29 06:51:23 +08:00
|
|
|
connect_setup(transport, for_push);
|
2018-03-15 02:31:45 +08:00
|
|
|
|
|
|
|
packet_reader_init(&reader, data->fd[0], NULL, 0,
|
|
|
|
PACKET_READ_CHOMP_NEWLINE |
|
pack-protocol.txt: accept error packets in any context
In the Git pack protocol definition, an error packet may appear only in
a certain context. However, servers can face a runtime error (e.g. I/O
error) at an arbitrary timing. This patch changes the protocol to allow
an error packet to be sent instead of any packet.
Without this protocol spec change, when a server cannot process a
request, there's no way to tell that to a client. Since the server
cannot produce a valid response, it would be forced to cut a connection
without telling why. With this protocol spec change, the server can be
more gentle in this situation. An old client may see these error packets
as an unexpected packet, but this is not worse than having an unexpected
EOF.
Following this protocol spec change, the error packet handling code is
moved to pkt-line.c. Implementation wise, this implementation uses
pkt-line to communicate with a subprocess. Since this is not a part of
Git protocol, it's possible that a packet that is not supposed to be an
error packet is mistakenly parsed as an error packet. This error packet
handling is enabled only for the Git pack protocol parsing code
considering this.
Signed-off-by: Masaya Suzuki <masayasuzuki@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-12-30 05:19:15 +08:00
|
|
|
PACKET_READ_GENTLE_ON_EOF |
|
|
|
|
PACKET_READ_DIE_ON_ERR_PACKET);
|
2018-03-15 02:31:45 +08:00
|
|
|
|
2018-03-15 02:31:46 +08:00
|
|
|
data->version = discover_version(&reader);
|
|
|
|
switch (data->version) {
|
2018-03-15 02:31:47 +08:00
|
|
|
case protocol_v2:
|
2020-11-12 07:29:30 +08:00
|
|
|
if (server_feature_v2("session-id", &server_sid))
|
|
|
|
trace2_data_string("transfer", NULL, "server-sid", server_sid);
|
2018-09-28 03:24:04 +08:00
|
|
|
if (must_list_refs)
|
|
|
|
get_remote_refs(data->fd[1], &reader, &refs, for_push,
|
2021-02-06 04:48:48 +08:00
|
|
|
options,
|
2020-05-19 18:54:00 +08:00
|
|
|
transport->server_options,
|
|
|
|
transport->stateless_rpc);
|
2018-03-15 02:31:47 +08:00
|
|
|
break;
|
2018-03-15 02:31:45 +08:00
|
|
|
case protocol_v1:
|
|
|
|
case protocol_v0:
|
2019-04-13 03:51:21 +08:00
|
|
|
die_if_server_options(transport);
|
2018-03-15 02:31:45 +08:00
|
|
|
get_remote_heads(&reader, &refs,
|
|
|
|
for_push ? REF_NORMAL : 0,
|
|
|
|
&data->extra_have,
|
|
|
|
&data->shallow);
|
2020-11-12 07:29:30 +08:00
|
|
|
server_sid = server_feature_value("session-id", &sid_len);
|
|
|
|
if (server_sid) {
|
|
|
|
char *sid = xstrndup(server_sid, sid_len);
|
|
|
|
trace2_data_string("transfer", NULL, "server-sid", sid);
|
|
|
|
free(sid);
|
|
|
|
}
|
2018-03-15 02:31:45 +08:00
|
|
|
break;
|
|
|
|
case protocol_unknown_version:
|
|
|
|
BUG("unknown protocol version");
|
|
|
|
}
|
2009-12-09 23:26:31 +08:00
|
|
|
data->got_remote_heads = 1;
|
2020-05-26 03:58:55 +08:00
|
|
|
transport->hash_algo = reader.hash_algo;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
2018-09-28 03:24:04 +08:00
|
|
|
if (reader.line_peeked)
|
|
|
|
BUG("buffer must be empty at the end of handshake()");
|
|
|
|
|
2007-09-11 11:03:11 +08:00
|
|
|
return refs;
|
|
|
|
}
|
|
|
|
|
2018-09-28 03:24:04 +08:00
|
|
|
static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
|
2021-02-06 04:48:48 +08:00
|
|
|
struct transport_ls_refs_options *options)
|
2018-09-28 03:24:04 +08:00
|
|
|
{
|
2021-02-06 04:48:48 +08:00
|
|
|
return handshake(transport, for_push, options, 1);
|
2018-09-28 03:24:04 +08:00
|
|
|
}
|
|
|
|
|
2007-09-14 15:31:23 +08:00
|
|
|
static int fetch_refs_via_pack(struct transport *transport,
|
fetch-pack: unify ref in and out param
When a user fetches:
- at least one up-to-date ref and at least one non-up-to-date ref,
- using HTTP with protocol v0 (or something else that uses the fetch
command of a remote helper)
some refs might not be updated after the fetch.
This bug was introduced in commit 989b8c4452 ("fetch-pack: put shallow
info in output parameter", 2018-06-28) which allowed transports to
report the refs that they have fetched in a new out-parameter
"fetched_refs". If they do so, transport_fetch_refs() makes this
information available to its caller.
Users of "fetched_refs" rely on the following 3 properties:
(1) it is the complete list of refs that was passed to
transport_fetch_refs(),
(2) it has shallow information (REF_STATUS_REJECT_SHALLOW set if
relevant), and
(3) it has updated OIDs if ref-in-want was used (introduced after
989b8c4452).
In an effort to satisfy (1), whenever transport_fetch_refs()
filters the refs sent to the transport, it re-adds the filtered refs to
whatever the transport supplies before returning it to the user.
However, the implementation in 989b8c4452 unconditionally re-adds the
filtered refs without checking if the transport refrained from reporting
anything in "fetched_refs" (which it is allowed to do), resulting in an
incomplete list, no longer satisfying (1).
An earlier effort to resolve this [1] solved the issue by readding the
filtered refs only if the transport did not refrain from reporting in
"fetched_refs", but after further discussion, it seems that the better
solution is to revert the API change that introduced "fetched_refs".
This API change was first suggested as part of a ref-in-want
implementation that allowed for ref patterns and, thus, there could be
drastic differences between the input refs and the refs actually fetched
[2]; we eventually decided to only allow exact ref names, but this API
change remained even though its necessity was decreased.
Therefore, revert this API change by reverting commit 989b8c4452, and
make receive_wanted_refs() update the OIDs in the sought array (like how
update_shallow() updates shallow information in the sought array)
instead. A test is also included to show that the user-visible bug
discussed at the beginning of this commit message no longer exists.
[1] https://public-inbox.org/git/20180801171806.GA122458@google.com/
[2] https://public-inbox.org/git/86a128c5fb710a41791e7183207c4d64889f9307.1485381677.git.jonathantanmy@google.com/
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-02 04:13:20 +08:00
|
|
|
int nr_heads, struct ref **to_fetch)
|
2007-09-11 11:03:11 +08:00
|
|
|
{
|
2017-02-23 00:02:15 +08:00
|
|
|
int ret = 0;
|
2007-09-11 11:03:11 +08:00
|
|
|
struct git_transport_data *data = transport->data;
|
2018-03-15 02:31:46 +08:00
|
|
|
struct ref *refs = NULL;
|
2007-09-11 11:03:11 +08:00
|
|
|
struct fetch_pack_args args;
|
2008-02-29 00:10:51 +08:00
|
|
|
struct ref *refs_tmp = NULL;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
2007-09-19 12:49:35 +08:00
|
|
|
memset(&args, 0, sizeof(args));
|
2009-12-09 23:26:30 +08:00
|
|
|
args.uploadpack = data->options.uploadpack;
|
|
|
|
args.keep_pack = data->options.keep;
|
2007-09-19 12:49:35 +08:00
|
|
|
args.lock_pack = 1;
|
2009-12-09 23:26:30 +08:00
|
|
|
args.use_thin_pack = data->options.thin;
|
|
|
|
args.include_tag = data->options.followtags;
|
make "git push -v" actually verbose
Providing a single "-v" to "git push" currently does
nothing. Giving two flags ("git push -v -v") turns on the
first level of verbosity.
This is caused by a regression introduced in 8afd8dc (push:
support multiple levels of verbosity, 2010-02-24). Before
the series containing 8afd8dc, the verbosity handling for
fetching and pushing was completely separate. Commit bde873c
refactored the verbosity handling out of the fetch side, and
then 8afd8dc converted push to use the refactored code.
However, the fetch and push sides numbered and passed along
their verbosity levels differently. For both, a verbosity
level of "-1" meant "quiet", and "0" meant "default output".
But from there they differed.
For fetch, a verbosity level of "1" indicated to the "fetch"
program that it should make the status table slightly more
verbose, showing up-to-date entries. A verbosity level of
"2" meant that we should pass a verbose flag to the
transport; in the case of fetch-pack, this displays protocol
debugging information.
As a result, the refactored code in bde873c checks for
"verbosity >= 2", and only then passes it on to the
transport. From the transport code's perspective, a
verbosity of 0 or 1 both meant "0".
Push, on the other hand, does not show its own status table;
that is always handled by the transport layer or below
(originally send-pack itself, but these days it is done by
the transport code). So a verbosity level of 1 meant that we
should pass the verbose flag to send-pack, so that it knows
we want a verbose status table. However, once 8afd8dc
switched it to the refactored fetch code, a verbosity level
of 1 was now being ignored. Thus, you needed to
artificially bump the verbosity to 2 (via "-v -v") to have
any effect.
We can fix this by letting the transport code know about the
true verbosity level (i.e., let it distinguish level 0 or
1).
We then have to also make an adjustment to any transport
methods that assumed "verbose > 0" meant they could spew
lots of debugging information. Before, they could only get
"0" or "2", but now they will also receive "1". They need to
adjust their condition for turning on such spew from
"verbose > 0" to "verbose > 1".
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-17 17:37:15 +08:00
|
|
|
args.verbose = (transport->verbose > 1);
|
2008-10-05 21:53:00 +08:00
|
|
|
args.quiet = (transport->verbose < 0);
|
2010-02-24 20:50:26 +08:00
|
|
|
args.no_progress = !transport->progress;
|
2009-12-09 23:26:30 +08:00
|
|
|
args.depth = data->options.depth;
|
2016-06-12 18:53:59 +08:00
|
|
|
args.deepen_since = data->options.deepen_since;
|
2016-06-12 18:54:04 +08:00
|
|
|
args.deepen_not = data->options.deepen_not;
|
fetch, upload-pack: --deepen=N extends shallow boundary by N commits
In git-fetch, --depth argument is always relative with the latest
remote refs. This makes it a bit difficult to cover this use case,
where the user wants to make the shallow history, say 3 levels
deeper. It would work if remote refs have not moved yet, but nobody
can guarantee that, especially when that use case is performed a
couple months after the last clone or "git fetch --depth". Also,
modifying shallow boundary using --depth does not work well with
clones created by --since or --not.
This patch fixes that. A new argument --deepen=<N> will add <N> more (*)
parent commits to the current history regardless of where remote refs
are.
Have/Want negotiation is still respected. So if remote refs move, the
server will send two chunks: one between "have" and "want" and another
to extend shallow history. In theory, the client could send no "want"s
in order to get the second chunk only. But the protocol does not allow
that. Either you send no want lines, which means ls-remote; or you
have to send at least one want line that carries deep-relative to the
server..
The main work was done by Dongcan Jiang. I fixed it up here and there.
And of course all the bugs belong to me.
(*) We could even support --deepen=<N> where <N> is negative. In that
case we can cut some history from the shallow clone. This operation
(and --depth=<shorter depth>) does not require interaction with remote
side (and more complicated to implement as a result).
Helped-by: Duy Nguyen <pclouds@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Dongcan Jiang <dongcan.jiang@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-06-12 18:54:09 +08:00
|
|
|
args.deepen_relative = data->options.deepen_relative;
|
2013-05-26 09:16:17 +08:00
|
|
|
args.check_self_contained_and_connected =
|
|
|
|
data->options.check_self_contained_and_connected;
|
2013-12-05 21:02:39 +08:00
|
|
|
args.cloning = transport->cloning;
|
2013-12-05 21:02:42 +08:00
|
|
|
args.update_shallow = data->options.update_shallow;
|
introduce fetch-object: fetch one promisor object
Introduce fetch-object, providing the ability to fetch one object from a
promisor remote.
This uses fetch-pack. To do this, the transport mechanism has been
updated with 2 flags, "from-promisor" to indicate that the resulting
pack comes from a promisor remote (and thus should be annotated as such
by index-pack), and "no-dependents" to indicate that only the objects
themselves need to be fetched (but fetching additional objects is
nevertheless safe).
Whenever "no-dependents" is used, fetch-pack will refrain from using any
object flags, because it is most likely invoked as part of a dynamic
object fetch by another Git command (which may itself use object flags).
An alternative to this is to leave fetch-pack alone, and instead update
the allocation of flags so that fetch-pack's flags never overlap with
any others, but this will end up shrinking the number of flags available
to nearly every other Git command (that is, every Git command that
accesses objects), so the approach in this commit was used instead.
This will be tested in a subsequent commit.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-12-06 00:58:49 +08:00
|
|
|
args.from_promisor = data->options.from_promisor;
|
2017-12-08 23:58:40 +08:00
|
|
|
args.filter_options = data->options.filter_options;
|
2022-03-28 22:02:08 +08:00
|
|
|
args.refetch = data->options.refetch;
|
2018-03-16 01:31:34 +08:00
|
|
|
args.stateless_rpc = transport->stateless_rpc;
|
2018-04-24 06:46:24 +08:00
|
|
|
args.server_options = transport->server_options;
|
2018-07-03 06:39:44 +08:00
|
|
|
args.negotiation_tips = data->options.negotiation_tips;
|
2021-04-01 18:46:59 +08:00
|
|
|
args.reject_shallow_remote = transport->smart_options->reject_shallow;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
2018-09-28 03:24:05 +08:00
|
|
|
if (!data->got_remote_heads) {
|
|
|
|
int i;
|
|
|
|
int must_list_refs = 0;
|
|
|
|
for (i = 0; i < nr_heads; i++) {
|
|
|
|
if (!to_fetch[i]->exact_oid) {
|
|
|
|
must_list_refs = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
refs_tmp = handshake(transport, 0, NULL, must_list_refs);
|
|
|
|
}
|
2008-02-05 02:26:23 +08:00
|
|
|
|
2020-05-19 18:53:56 +08:00
|
|
|
if (data->version == protocol_unknown_version)
|
2018-03-15 02:31:46 +08:00
|
|
|
BUG("unknown protocol version");
|
2020-05-19 18:53:56 +08:00
|
|
|
else if (data->version <= protocol_v1)
|
|
|
|
die_if_server_options(transport);
|
|
|
|
|
fetch: teach independent negotiation (no packfile)
Currently, the packfile negotiation step within a Git fetch cannot be
done independent of sending the packfile, even though there is at least
one application wherein this is useful. Therefore, make it possible for
this negotiation step to be done independently. A subsequent commit will
use this for one such application - push negotiation.
This feature is for protocol v2 only. (An implementation for protocol v0
would require a separate implementation in the fetch, transport, and
transport helper code.)
In the protocol, the main hindrance towards independent negotiation is
that the server can unilaterally decide to send the packfile. This is
solved by a "wait-for-done" argument: the server will then wait for the
client to say "done". In practice, the client will never say it; instead
it will cease requests once it is satisfied.
In the client, the main change lies in the transport and transport
helper code. fetch_refs_via_pack() performs everything needed - protocol
version and capability checks, and the negotiation itself.
There are 2 code paths that do not go through fetch_refs_via_pack() that
needed to be individually excluded: the bundle transport (excluded
through requiring smart_options, which the bundle transport doesn't
support) and transport helpers that do not support takeover. If or when
we support independent negotiation for protocol v0, we will need to
modify these 2 code paths to support it. But for now, report failure if
independent negotiation is requested in these cases.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-05 05:16:01 +08:00
|
|
|
if (data->options.acked_commits) {
|
|
|
|
if (data->version < protocol_v2) {
|
|
|
|
warning(_("--negotiate-only requires protocol v2"));
|
|
|
|
ret = -1;
|
|
|
|
} else if (!server_supports_feature("fetch", "wait-for-done", 0)) {
|
|
|
|
warning(_("server does not support wait-for-done"));
|
|
|
|
ret = -1;
|
|
|
|
} else {
|
|
|
|
negotiate_using_fetch(data->options.negotiation_tips,
|
|
|
|
transport->server_options,
|
|
|
|
transport->stateless_rpc,
|
|
|
|
data->fd,
|
|
|
|
data->options.acked_commits);
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-05-19 18:53:56 +08:00
|
|
|
refs = fetch_pack(&args, data->fd,
|
|
|
|
refs_tmp ? refs_tmp : transport->remote_refs,
|
|
|
|
to_fetch, nr_heads, &data->shallow,
|
2020-06-26 03:27:47 +08:00
|
|
|
&transport->pack_lockfiles, data->version);
|
2008-02-05 02:26:23 +08:00
|
|
|
|
2009-12-09 23:26:31 +08:00
|
|
|
data->got_remote_heads = 0;
|
2013-05-26 09:16:17 +08:00
|
|
|
data->options.self_contained_and_connected =
|
|
|
|
args.self_contained_and_connected;
|
fetch-pack: write shallow, then check connectivity
When fetching, connectivity is checked after the shallow file is
updated. There are 2 issues with this: (1) the connectivity check is
only performed up to ancestors of existing refs (which is not thorough
enough if we were deepening an existing ref in the first place), and (2)
there is no rollback of the shallow file if the connectivity check
fails.
To solve (1), update the connectivity check to check the ancestry chain
completely in the case of a deepening fetch by refraining from passing
"--not --all" when invoking rev-list in connected.c.
To solve (2), have fetch_pack() perform its own connectivity check
before updating the shallow file. To support existing use cases in which
"git fetch-pack" is used to download objects without much regard as to
the connectivity of the resulting objects with respect to the existing
repository, the connectivity check is only done if necessary (that is,
the fetch is not a clone, and the fetch involves shallow/deepen
functionality). "git fetch" still performs its own connectivity check,
preserving correctness but sometimes performing redundant work. This
redundancy is mitigated by the fact that fetch_pack() reports if it has
performed a connectivity check itself, and if the transport supports
connect or stateless-connect, it will bubble up that report so that "git
fetch" knows not to perform the connectivity check in such a case.
This was noticed when a user tried to deepen an existing repository by
fetching with --no-shallow from a server that did not send all necessary
objects - the connectivity check as run by "git fetch" succeeded, but a
subsequent "git fsck" failed.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-07-03 06:08:43 +08:00
|
|
|
data->options.connectivity_checked = args.connectivity_checked;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
2017-02-23 00:02:15 +08:00
|
|
|
if (refs == NULL)
|
|
|
|
ret = -1;
|
|
|
|
if (report_unmatched_refs(to_fetch, nr_heads))
|
|
|
|
ret = -1;
|
|
|
|
|
fetch: teach independent negotiation (no packfile)
Currently, the packfile negotiation step within a Git fetch cannot be
done independent of sending the packfile, even though there is at least
one application wherein this is useful. Therefore, make it possible for
this negotiation step to be done independently. A subsequent commit will
use this for one such application - push negotiation.
This feature is for protocol v2 only. (An implementation for protocol v0
would require a separate implementation in the fetch, transport, and
transport helper code.)
In the protocol, the main hindrance towards independent negotiation is
that the server can unilaterally decide to send the packfile. This is
solved by a "wait-for-done" argument: the server will then wait for the
client to say "done". In practice, the client will never say it; instead
it will cease requests once it is satisfied.
In the client, the main change lies in the transport and transport
helper code. fetch_refs_via_pack() performs everything needed - protocol
version and capability checks, and the negotiation itself.
There are 2 code paths that do not go through fetch_refs_via_pack() that
needed to be individually excluded: the bundle transport (excluded
through requiring smart_options, which the bundle transport doesn't
support) and transport helpers that do not support takeover. If or when
we support independent negotiation for protocol v0, we will need to
modify these 2 code paths to support it. But for now, report failure if
independent negotiation is requested in these cases.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-05 05:16:01 +08:00
|
|
|
cleanup:
|
|
|
|
close(data->fd[0]);
|
fetch-pack: signal v2 server that we are done making requests
When fetching with the v0 protocol over ssh (or a local upload-pack with
pipes), the server closes the connection as soon as it is finished
sending the pack. So even though the client may still be operating on
the data via index-pack (e.g., resolving deltas, checking connectivity,
etc), the server has released all resources.
With the v2 protocol, however, the server considers the ssh session only
as a transport, with individual requests coming over it. After sending
the pack, it goes back to its main loop, waiting for another request to
come from the client. As a result, the ssh session hangs around until
the client process ends, which may be much later (because resolving
deltas, etc, may consume a lot of CPU).
This is bad for two reasons:
- it's consuming resources on the server to leave open a connection
that won't see any more use
- if something bad happens to the ssh connection in the meantime (say,
it gets killed by the network because it's idle, as happened in a
real-world report), then ssh will exit non-zero, and we'll propagate
the error up the stack.
The server is correct here not to hang up after serving the pack. The v2
protocol's design is meant to allow multiple requests like this, and
hanging up would be the wrong thing for a hypothetical client which was
planning to make more requests (though in practice, the git.git client
never would, and I doubt any other implementations would either).
The right thing is instead for the client to signal to the server that
it's not interested in making more requests. We can do that by closing
the pipe descriptor we use to write to ssh. This will propagate to the
server upload-pack as an EOF when it tries to read the next request (and
then it will close its half, and the whole connection will go away).
It's important to do this "half duplex" shutdown, because we have to do
it _before_ we actually receive the pack. This is an artifact of the way
fetch-pack and index-pack (or unpack-objects) interact. We hand the
connection off to index-pack (really, a sideband demuxer which feeds
it), and then wait until it returns. And it doesn't do that until it has
resolved all of the deltas in the pack, even though it was done reading
from the server long before.
So just closing the connection fully after index-pack returns would be
too late; we'd have held it open much longer than was necessary. And
teaching index-pack to close the connection is awkward. It's not even
seeing the whole conversation (the sideband demuxer is, but it doesn't
actually know what's in the packets, or when the end comes).
Note that this close() is happening deep within the transport code. It's
possible that a caller would want to perform other operations over the
same ssh transport after receiving the pack. But as of the current code,
none of the callers do, and there haven't been discussions of any plans
to change this. If we need to support that later, we can probably do so
by passing down a flag for "you're the last request on the transport;
it's OK to close" instead of the code just assuming that's true.
The description above all discusses v2 ssh, so it's worth thinking about
how this interacts with other protocols:
- in v0 protocols, we could do the same half-duplex shutdown (it just
goes into the v0 do_fetch_pack() instead). This does work, but since
it doesn't have the same persistence problem in the first place,
there's little reason to change it at this point.
- local fetches against git-upload-pack on the same machine will
behave the same as ssh (they are talking over two pipes, and see EOF
on their input pipe)
- fetches against git-daemon will run this same code, and close one of
the descriptors. In practice, this won't do anything, since there
our two descriptors are dups of each other, and not part of a
half-duplex pair. The right thing would probably be to call
shutdown(SHUT_WR) on it. I didn't bother with that here. It doesn't
face the same error-code problem (since it's just a TCP connection),
so it's really only an optimization problem. And git:// is not that
widely used these days, and has less impact on server resources than
an ssh termination.
- v2 http doesn't suffer from this problem in the first place, as our
pipes terminate at a local git-remote-https, which is passing data
along as individual requests via curl. Probably curl is keeping the
TCP/TLS connection open for more requests, and we might be able to
tell it manually "hey, we are done making requests now". But I think
that's much less important. It again doesn't suffer from the
error-code problem, and HTTP keepalive is pretty well understood
(importantly, the timeouts can be set low, because clients like curl
know how to reconnect for subsequent requests if necessary). So it's
probably not worth figuring out how to tell curl that we're done
(though if we do, this patch is probably the first step anyway;
fetch-pack closes the pipe back to remote-https, which would be the
signal that it should tell curl we're done).
The code is pretty straightforward. We close the pipe at the right
moment, and set it to -1 to mark it as invalid. I modified the later
cleanup code to avoid calling close(-1). That's not strictly necessary,
since close(-1) is a noop, but hopefully makes things a bit more obvious
to a reader.
I suspect that trying to call more transport functions after the close()
(e.g., calling transport_fetch_refs() again) would fail, as it's not
smart enough to realize we need to re-open the ssh connection. But
that's already true when v0 is in use. And no current callers want to do
that (and again, the solution is probably a flag in the transport code
to keep things open, which can be added later).
There's no test here, as the situation it covers is inherently racy (the
question is when upload-pack exits, compared to when index-pack finishes
resolving deltas and exits). The rather gross shell snippet below does
recreate the problematic situation; when run on a sufficiently-large
repository (git.git works fine), it kills an "idle" upload-pack while
the client is resolving deltas, leading to a failed clone.
(
git clone --no-local --progress . foo.git 2>&1
echo >&2 "clone exit code=$?"
) |
tr '\r' '\n' |
while read line
do
case "$done,$line" in
,Resolving*)
echo "hit resolving deltas; killing upload-pack"
killall -9 git-upload-pack
done=t
;;
esac
done
Reported-by: Greg Pflaum <greg.pflaum@pnp-hcl.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-20 00:11:05 +08:00
|
|
|
if (data->fd[1] >= 0)
|
|
|
|
close(data->fd[1]);
|
fetch: teach independent negotiation (no packfile)
Currently, the packfile negotiation step within a Git fetch cannot be
done independent of sending the packfile, even though there is at least
one application wherein this is useful. Therefore, make it possible for
this negotiation step to be done independently. A subsequent commit will
use this for one such application - push negotiation.
This feature is for protocol v2 only. (An implementation for protocol v0
would require a separate implementation in the fetch, transport, and
transport helper code.)
In the protocol, the main hindrance towards independent negotiation is
that the server can unilaterally decide to send the packfile. This is
solved by a "wait-for-done" argument: the server will then wait for the
client to say "done". In practice, the client will never say it; instead
it will cease requests once it is satisfied.
In the client, the main change lies in the transport and transport
helper code. fetch_refs_via_pack() performs everything needed - protocol
version and capability checks, and the negotiation itself.
There are 2 code paths that do not go through fetch_refs_via_pack() that
needed to be individually excluded: the bundle transport (excluded
through requiring smart_options, which the bundle transport doesn't
support) and transport helpers that do not support takeover. If or when
we support independent negotiation for protocol v0, we will need to
modify these 2 code paths to support it. But for now, report failure if
independent negotiation is requested in these cases.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-05 05:16:01 +08:00
|
|
|
if (finish_connect(data->conn))
|
|
|
|
ret = -1;
|
|
|
|
data->conn = NULL;
|
|
|
|
|
2008-02-29 00:10:51 +08:00
|
|
|
free_refs(refs_tmp);
|
fetch-pack: unify ref in and out param
When a user fetches:
- at least one up-to-date ref and at least one non-up-to-date ref,
- using HTTP with protocol v0 (or something else that uses the fetch
command of a remote helper)
some refs might not be updated after the fetch.
This bug was introduced in commit 989b8c4452 ("fetch-pack: put shallow
info in output parameter", 2018-06-28) which allowed transports to
report the refs that they have fetched in a new out-parameter
"fetched_refs". If they do so, transport_fetch_refs() makes this
information available to its caller.
Users of "fetched_refs" rely on the following 3 properties:
(1) it is the complete list of refs that was passed to
transport_fetch_refs(),
(2) it has shallow information (REF_STATUS_REJECT_SHALLOW set if
relevant), and
(3) it has updated OIDs if ref-in-want was used (introduced after
989b8c4452).
In an effort to satisfy (1), whenever transport_fetch_refs()
filters the refs sent to the transport, it re-adds the filtered refs to
whatever the transport supplies before returning it to the user.
However, the implementation in 989b8c4452 unconditionally re-adds the
filtered refs without checking if the transport refrained from reporting
anything in "fetched_refs" (which it is allowed to do), resulting in an
incomplete list, no longer satisfying (1).
An earlier effort to resolve this [1] solved the issue by readding the
filtered refs only if the transport did not refrain from reporting in
"fetched_refs", but after further discussion, it seems that the better
solution is to revert the API change that introduced "fetched_refs".
This API change was first suggested as part of a ref-in-want
implementation that allowed for ref patterns and, thus, there could be
drastic differences between the input refs and the refs actually fetched
[2]; we eventually decided to only allow exact ref names, but this API
change remained even though its necessity was decreased.
Therefore, revert this API change by reverting commit 989b8c4452, and
make receive_wanted_refs() update the OIDs in the sought array (like how
update_shallow() updates shallow information in the sought array)
instead. A test is also included to show that the user-visible bug
discussed at the beginning of this commit message no longer exists.
[1] https://public-inbox.org/git/20180801171806.GA122458@google.com/
[2] https://public-inbox.org/git/86a128c5fb710a41791e7183207c4d64889f9307.1485381677.git.jonathantanmy@google.com/
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-02 04:13:20 +08:00
|
|
|
free_refs(refs);
|
2017-02-23 00:02:15 +08:00
|
|
|
return ret;
|
2007-09-11 11:03:11 +08:00
|
|
|
}
|
|
|
|
|
2009-08-06 04:23:26 +08:00
|
|
|
static int push_had_errors(struct ref *ref)
|
|
|
|
{
|
|
|
|
for (; ref; ref = ref->next) {
|
|
|
|
switch (ref->status) {
|
|
|
|
case REF_STATUS_NONE:
|
|
|
|
case REF_STATUS_UPTODATE:
|
|
|
|
case REF_STATUS_OK:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-17 07:42:52 +08:00
|
|
|
int transport_refs_pushed(struct ref *ref)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
|
|
|
for (; ref; ref = ref->next) {
|
|
|
|
switch(ref->status) {
|
|
|
|
case REF_STATUS_NONE:
|
|
|
|
case REF_STATUS_UPTODATE:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:45:50 +08:00
|
|
|
static void update_one_tracking_ref(struct remote *remote, char *refname,
|
|
|
|
struct object_id *new_oid, int deletion,
|
|
|
|
int verbose)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
2018-05-17 06:57:49 +08:00
|
|
|
struct refspec_item rs;
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2020-08-15 08:25:08 +08:00
|
|
|
memset(&rs, 0, sizeof(rs));
|
2020-08-27 23:45:50 +08:00
|
|
|
rs.src = refname;
|
2009-03-09 09:06:07 +08:00
|
|
|
rs.dst = NULL;
|
|
|
|
|
|
|
|
if (!remote_find_tracking(remote, &rs)) {
|
|
|
|
if (verbose)
|
|
|
|
fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
|
2020-08-27 23:45:50 +08:00
|
|
|
if (deletion)
|
2017-02-21 09:10:32 +08:00
|
|
|
delete_ref(NULL, rs.dst, NULL, 0);
|
2020-08-27 23:45:50 +08:00
|
|
|
else
|
|
|
|
update_ref("update by push", rs.dst, new_oid,
|
2017-10-16 06:06:51 +08:00
|
|
|
NULL, 0, 0);
|
2009-03-09 09:06:07 +08:00
|
|
|
free(rs.dst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:45:50 +08:00
|
|
|
void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
|
|
|
|
{
|
|
|
|
char *refname;
|
|
|
|
struct object_id *new_oid;
|
|
|
|
struct ref_push_report *report;
|
|
|
|
|
|
|
|
if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
report = ref->report;
|
|
|
|
if (!report)
|
|
|
|
update_one_tracking_ref(remote, ref->name, &ref->new_oid,
|
|
|
|
ref->deletion, verbose);
|
|
|
|
else
|
|
|
|
for (; report; report = report->next) {
|
|
|
|
refname = report->ref_name ? (char *)report->ref_name : ref->name;
|
|
|
|
new_oid = report->new_oid ? report->new_oid : &ref->new_oid;
|
|
|
|
update_one_tracking_ref(remote, refname, new_oid,
|
|
|
|
is_null_oid(new_oid), verbose);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-22 05:39:41 +08:00
|
|
|
static void print_ref_status(char flag, const char *summary,
|
|
|
|
struct ref *to, struct ref *from, const char *msg,
|
2020-08-27 23:45:46 +08:00
|
|
|
struct ref_push_report *report,
|
2016-10-22 05:39:41 +08:00
|
|
|
int porcelain, int summary_width)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
2020-08-27 23:45:46 +08:00
|
|
|
const char *to_name;
|
|
|
|
|
|
|
|
if (report && report->ref_name)
|
|
|
|
to_name = report->ref_name;
|
|
|
|
else
|
|
|
|
to_name = to->name;
|
|
|
|
|
2009-06-23 09:10:01 +08:00
|
|
|
if (porcelain) {
|
|
|
|
if (from)
|
2020-08-27 23:45:46 +08:00
|
|
|
fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to_name);
|
2009-06-23 09:10:01 +08:00
|
|
|
else
|
2020-08-27 23:45:46 +08:00
|
|
|
fprintf(stdout, "%c\t:%s\t", flag, to_name);
|
2009-06-23 09:10:01 +08:00
|
|
|
if (msg)
|
|
|
|
fprintf(stdout, "%s (%s)\n", summary, msg);
|
|
|
|
else
|
|
|
|
fprintf(stdout, "%s\n", summary);
|
|
|
|
} else {
|
2018-04-21 18:10:00 +08:00
|
|
|
const char *red = "", *reset = "";
|
|
|
|
if (push_had_errors(to)) {
|
|
|
|
red = transport_get_color(TRANSPORT_COLOR_REJECTED);
|
|
|
|
reset = transport_get_color(TRANSPORT_COLOR_RESET);
|
|
|
|
}
|
|
|
|
fprintf(stderr, " %s%c %-*s%s ", red, flag, summary_width,
|
|
|
|
summary, reset);
|
2009-06-23 09:10:01 +08:00
|
|
|
if (from)
|
2020-08-27 23:45:46 +08:00
|
|
|
fprintf(stderr, "%s -> %s",
|
|
|
|
prettify_refname(from->name),
|
|
|
|
prettify_refname(to_name));
|
2009-06-23 09:10:01 +08:00
|
|
|
else
|
2020-08-27 23:45:46 +08:00
|
|
|
fputs(prettify_refname(to_name), stderr);
|
2009-06-23 09:10:01 +08:00
|
|
|
if (msg) {
|
|
|
|
fputs(" (", stderr);
|
|
|
|
fputs(msg, stderr);
|
|
|
|
fputc(')', stderr);
|
|
|
|
}
|
|
|
|
fputc('\n', stderr);
|
2009-03-09 09:06:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:45:46 +08:00
|
|
|
static void print_ok_ref_status(struct ref *ref,
|
|
|
|
struct ref_push_report *report,
|
|
|
|
int porcelain, int summary_width)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
2020-08-27 23:45:46 +08:00
|
|
|
struct object_id *old_oid;
|
|
|
|
struct object_id *new_oid;
|
|
|
|
const char *ref_name;
|
|
|
|
int forced_update;
|
|
|
|
|
|
|
|
if (report && report->old_oid)
|
|
|
|
old_oid = report->old_oid;
|
|
|
|
else
|
|
|
|
old_oid = &ref->old_oid;
|
|
|
|
if (report && report->new_oid)
|
|
|
|
new_oid = report->new_oid;
|
|
|
|
else
|
|
|
|
new_oid = &ref->new_oid;
|
|
|
|
if (report && report->forced_update)
|
|
|
|
forced_update = report->forced_update;
|
|
|
|
else
|
|
|
|
forced_update = ref->forced_update;
|
|
|
|
if (report && report->ref_name)
|
|
|
|
ref_name = report->ref_name;
|
|
|
|
else
|
|
|
|
ref_name = ref->name;
|
|
|
|
|
2009-03-09 09:06:07 +08:00
|
|
|
if (ref->deletion)
|
2016-10-22 05:39:41 +08:00
|
|
|
print_ref_status('-', "[deleted]", ref, NULL, NULL,
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
|
|
|
else if (is_null_oid(old_oid))
|
2009-03-09 09:06:07 +08:00
|
|
|
print_ref_status('*',
|
2020-08-27 23:45:46 +08:00
|
|
|
(starts_with(ref_name, "refs/tags/")
|
2020-08-27 23:45:42 +08:00
|
|
|
? "[new tag]"
|
2020-08-27 23:45:46 +08:00
|
|
|
: (starts_with(ref_name, "refs/heads/")
|
2020-08-27 23:45:42 +08:00
|
|
|
? "[new branch]"
|
|
|
|
: "[new reference]")),
|
2020-08-27 23:45:46 +08:00
|
|
|
ref, ref->peer_ref, NULL,
|
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
else {
|
2015-09-25 05:07:40 +08:00
|
|
|
struct strbuf quickref = STRBUF_INIT;
|
2009-03-09 09:06:07 +08:00
|
|
|
char type;
|
|
|
|
const char *msg;
|
|
|
|
|
2020-08-27 23:45:46 +08:00
|
|
|
strbuf_add_unique_abbrev(&quickref, old_oid,
|
2016-08-06 23:41:01 +08:00
|
|
|
DEFAULT_ABBREV);
|
2020-08-27 23:45:46 +08:00
|
|
|
if (forced_update) {
|
2015-09-25 05:07:40 +08:00
|
|
|
strbuf_addstr(&quickref, "...");
|
2009-03-09 09:06:07 +08:00
|
|
|
type = '+';
|
|
|
|
msg = "forced update";
|
|
|
|
} else {
|
2015-09-25 05:07:40 +08:00
|
|
|
strbuf_addstr(&quickref, "..");
|
2009-03-09 09:06:07 +08:00
|
|
|
type = ' ';
|
|
|
|
msg = NULL;
|
|
|
|
}
|
2020-08-27 23:45:46 +08:00
|
|
|
strbuf_add_unique_abbrev(&quickref, new_oid,
|
2016-08-06 23:41:01 +08:00
|
|
|
DEFAULT_ABBREV);
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2016-10-22 05:39:41 +08:00
|
|
|
print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
2015-09-25 05:07:40 +08:00
|
|
|
strbuf_release(&quickref);
|
2009-03-09 09:06:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:45:46 +08:00
|
|
|
static int print_one_push_report(struct ref *ref, const char *dest, int count,
|
|
|
|
struct ref_push_report *report,
|
2016-10-22 05:39:41 +08:00
|
|
|
int porcelain, int summary_width)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
2016-07-14 07:36:53 +08:00
|
|
|
if (!count) {
|
|
|
|
char *url = transport_anonymize_url(dest);
|
|
|
|
fprintf(porcelain ? stdout : stderr, "To %s\n", url);
|
|
|
|
free(url);
|
|
|
|
}
|
2009-03-09 09:06:07 +08:00
|
|
|
|
|
|
|
switch(ref->status) {
|
|
|
|
case REF_STATUS_NONE:
|
2016-10-22 05:39:41 +08:00
|
|
|
print_ref_status('X', "[no match]", ref, NULL, NULL,
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
|
|
|
case REF_STATUS_REJECT_NODELETE:
|
|
|
|
print_ref_status('!', "[rejected]", ref, NULL,
|
2016-10-22 05:39:41 +08:00
|
|
|
"remote does not support deleting refs",
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
|
|
|
case REF_STATUS_UPTODATE:
|
|
|
|
print_ref_status('=', "[up to date]", ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
ref->peer_ref, NULL,
|
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
|
|
|
case REF_STATUS_REJECT_NONFASTFORWARD:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"non-fast-forward",
|
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
2012-11-30 09:41:37 +08:00
|
|
|
case REF_STATUS_REJECT_ALREADY_EXISTS:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"already exists",
|
|
|
|
report, porcelain, summary_width);
|
2012-11-30 09:41:37 +08:00
|
|
|
break;
|
push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
When we push to update an existing ref, if:
* the object at the tip of the remote is not a commit; or
* the object we are pushing is not a commit,
it won't be correct to suggest to fetch, integrate and push again,
as the old and new objects will not "merge". We should explain that
the push must be forced when there is a non-committish object is
involved in such a case.
If we do not have the current object at the tip of the remote, we do
not even know that object, when fetched, is something that can be
merged. In such a case, suggesting to pull first just like
non-fast-forward case may not be technically correct, but in
practice, most such failures are seen when you try to push your work
to a branch without knowing that somebody else already pushed to
update the same branch since you forked, so "pull first" would work
as a suggestion most of the time. And if the object at the tip is
not a commit, "pull first" will fail, without making any permanent
damage. As a side effect, it also makes the error message the user
will get during the next "push" attempt easier to understand, now
the user is aware that a non-commit object is involved.
In these cases, the current code already rejects such a push on the
client end, but we used the same error and advice messages as the
ones used when rejecting a non-fast-forward push, i.e. pull from
there and integrate before pushing again.
Introduce new rejection reasons and reword the messages
appropriately.
[jc: with help by Peff on message details]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-24 05:55:30 +08:00
|
|
|
case REF_STATUS_REJECT_FETCH_FIRST:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"fetch first",
|
|
|
|
report, porcelain, summary_width);
|
push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
When we push to update an existing ref, if:
* the object at the tip of the remote is not a commit; or
* the object we are pushing is not a commit,
it won't be correct to suggest to fetch, integrate and push again,
as the old and new objects will not "merge". We should explain that
the push must be forced when there is a non-committish object is
involved in such a case.
If we do not have the current object at the tip of the remote, we do
not even know that object, when fetched, is something that can be
merged. In such a case, suggesting to pull first just like
non-fast-forward case may not be technically correct, but in
practice, most such failures are seen when you try to push your work
to a branch without knowing that somebody else already pushed to
update the same branch since you forked, so "pull first" would work
as a suggestion most of the time. And if the object at the tip is
not a commit, "pull first" will fail, without making any permanent
damage. As a side effect, it also makes the error message the user
will get during the next "push" attempt easier to understand, now
the user is aware that a non-commit object is involved.
In these cases, the current code already rejects such a push on the
client end, but we used the same error and advice messages as the
ones used when rejecting a non-fast-forward push, i.e. pull from
there and integrate before pushing again.
Introduce new rejection reasons and reword the messages
appropriately.
[jc: with help by Peff on message details]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-24 05:55:30 +08:00
|
|
|
break;
|
|
|
|
case REF_STATUS_REJECT_NEEDS_FORCE:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"needs force",
|
|
|
|
report, porcelain, summary_width);
|
push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
When we push to update an existing ref, if:
* the object at the tip of the remote is not a commit; or
* the object we are pushing is not a commit,
it won't be correct to suggest to fetch, integrate and push again,
as the old and new objects will not "merge". We should explain that
the push must be forced when there is a non-committish object is
involved in such a case.
If we do not have the current object at the tip of the remote, we do
not even know that object, when fetched, is something that can be
merged. In such a case, suggesting to pull first just like
non-fast-forward case may not be technically correct, but in
practice, most such failures are seen when you try to push your work
to a branch without knowing that somebody else already pushed to
update the same branch since you forked, so "pull first" would work
as a suggestion most of the time. And if the object at the tip is
not a commit, "pull first" will fail, without making any permanent
damage. As a side effect, it also makes the error message the user
will get during the next "push" attempt easier to understand, now
the user is aware that a non-commit object is involved.
In these cases, the current code already rejects such a push on the
client end, but we used the same error and advice messages as the
ones used when rejecting a non-fast-forward push, i.e. pull from
there and integrate before pushing again.
Introduce new rejection reasons and reword the messages
appropriately.
[jc: with help by Peff on message details]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-24 05:55:30 +08:00
|
|
|
break;
|
2013-07-09 05:42:40 +08:00
|
|
|
case REF_STATUS_REJECT_STALE:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"stale info",
|
|
|
|
report, porcelain, summary_width);
|
2013-07-09 05:42:40 +08:00
|
|
|
break;
|
2020-10-03 20:10:44 +08:00
|
|
|
case REF_STATUS_REJECT_REMOTE_UPDATED:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
|
|
|
"remote ref updated since checkout",
|
|
|
|
report, porcelain, summary_width);
|
|
|
|
break;
|
2013-12-05 21:02:40 +08:00
|
|
|
case REF_STATUS_REJECT_SHALLOW:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2016-10-22 05:39:41 +08:00
|
|
|
"new shallow roots not allowed",
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
2013-12-05 21:02:40 +08:00
|
|
|
break;
|
2009-03-09 09:06:07 +08:00
|
|
|
case REF_STATUS_REMOTE_REJECT:
|
|
|
|
print_ref_status('!', "[remote rejected]", ref,
|
2016-10-22 05:39:41 +08:00
|
|
|
ref->deletion ? NULL : ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
ref->remote_status,
|
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
|
|
|
case REF_STATUS_EXPECTING_REPORT:
|
|
|
|
print_ref_status('!', "[remote failure]", ref,
|
2016-10-22 05:39:41 +08:00
|
|
|
ref->deletion ? NULL : ref->peer_ref,
|
|
|
|
"remote failed to report status",
|
2020-08-27 23:45:46 +08:00
|
|
|
report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
2015-01-08 11:23:22 +08:00
|
|
|
case REF_STATUS_ATOMIC_PUSH_FAILED:
|
|
|
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
2020-08-27 23:45:46 +08:00
|
|
|
"atomic push failed",
|
|
|
|
report, porcelain, summary_width);
|
2015-01-08 11:23:22 +08:00
|
|
|
break;
|
2009-03-09 09:06:07 +08:00
|
|
|
case REF_STATUS_OK:
|
2020-08-27 23:45:46 +08:00
|
|
|
print_ok_ref_status(ref, report, porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:45:46 +08:00
|
|
|
static int print_one_push_status(struct ref *ref, const char *dest, int count,
|
|
|
|
int porcelain, int summary_width)
|
|
|
|
{
|
|
|
|
struct ref_push_report *report;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
if (!ref->report)
|
|
|
|
return print_one_push_report(ref, dest, count,
|
|
|
|
NULL, porcelain, summary_width);
|
|
|
|
|
|
|
|
for (report = ref->report; report; report = report->next)
|
|
|
|
print_one_push_report(ref, dest, count + n++,
|
|
|
|
report, porcelain, summary_width);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2016-10-22 12:33:06 +08:00
|
|
|
static int measure_abbrev(const struct object_id *oid, int sofar)
|
|
|
|
{
|
2017-03-27 00:01:24 +08:00
|
|
|
char hex[GIT_MAX_HEXSZ + 1];
|
2018-03-12 10:27:30 +08:00
|
|
|
int w = find_unique_abbrev_r(hex, oid, DEFAULT_ABBREV);
|
2016-10-22 12:33:06 +08:00
|
|
|
|
|
|
|
return (w < sofar) ? sofar : w;
|
|
|
|
}
|
|
|
|
|
2016-10-22 06:28:07 +08:00
|
|
|
int transport_summary_width(const struct ref *refs)
|
|
|
|
{
|
2016-10-22 12:33:06 +08:00
|
|
|
int maxw = -1;
|
|
|
|
|
|
|
|
for (; refs; refs = refs->next) {
|
|
|
|
maxw = measure_abbrev(&refs->old_oid, maxw);
|
|
|
|
maxw = measure_abbrev(&refs->new_oid, maxw);
|
|
|
|
}
|
|
|
|
if (maxw < 0)
|
|
|
|
maxw = FALLBACK_DEFAULT_ABBREV;
|
|
|
|
return (2 * maxw + 3);
|
2016-10-22 06:28:07 +08:00
|
|
|
}
|
|
|
|
|
2010-02-17 07:42:52 +08:00
|
|
|
void transport_print_push_status(const char *dest, struct ref *refs,
|
2012-11-30 09:41:33 +08:00
|
|
|
int verbose, int porcelain, unsigned int *reject_reasons)
|
2009-03-09 09:06:07 +08:00
|
|
|
{
|
|
|
|
struct ref *ref;
|
|
|
|
int n = 0;
|
push: Provide situational hints for non-fast-forward errors
Pushing a non-fast-forward update to a remote repository will result in
an error, but the hint text doesn't provide the correct resolution in
every case. Give better resolution advice in three push scenarios:
1) If you push your current branch and it triggers a non-fast-forward
error, you should merge remote changes with 'git pull' before pushing
again.
2) If you push to a shared repository others push to, and your local
tracking branches are not kept up to date, the 'matching refs' default
will generate non-fast-forward errors on outdated branches. If this is
your workflow, the 'matching refs' default is not for you. Consider
setting the 'push.default' configuration variable to 'current' or
'upstream' to ensure only your current branch is pushed.
3) If you explicitly specify a ref that is not your current branch or
push matching branches with ':', you will generate a non-fast-forward
error if any pushed branch tip is out of date. You should checkout the
offending branch and merge remote changes before pushing again.
Teach transport.c to recognize these scenarios and configure push.c
to hint for them. If 'git push's default behavior changes or we
discover more scenarios, extension is easy. Standardize on the
advice API and add three new advice variables, 'pushNonFFCurrent',
'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these
to 'false' will disable their affiliated advice. Setting
'pushNonFastForward' to false will disable all three, thus preserving the
config option for users who already set it, but guaranteeing new
users won't disable push advice accidentally.
Based-on-patch-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Christopher Tiwald <christiwald@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-03-20 12:31:33 +08:00
|
|
|
char *head;
|
2016-10-22 06:28:07 +08:00
|
|
|
int summary_width = transport_summary_width(refs);
|
push: Provide situational hints for non-fast-forward errors
Pushing a non-fast-forward update to a remote repository will result in
an error, but the hint text doesn't provide the correct resolution in
every case. Give better resolution advice in three push scenarios:
1) If you push your current branch and it triggers a non-fast-forward
error, you should merge remote changes with 'git pull' before pushing
again.
2) If you push to a shared repository others push to, and your local
tracking branches are not kept up to date, the 'matching refs' default
will generate non-fast-forward errors on outdated branches. If this is
your workflow, the 'matching refs' default is not for you. Consider
setting the 'push.default' configuration variable to 'current' or
'upstream' to ensure only your current branch is pushed.
3) If you explicitly specify a ref that is not your current branch or
push matching branches with ':', you will generate a non-fast-forward
error if any pushed branch tip is out of date. You should checkout the
offending branch and merge remote changes before pushing again.
Teach transport.c to recognize these scenarios and configure push.c
to hint for them. If 'git push's default behavior changes or we
discover more scenarios, extension is easy. Standardize on the
advice API and add three new advice variables, 'pushNonFFCurrent',
'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these
to 'false' will disable their affiliated advice. Setting
'pushNonFastForward' to false will disable all three, thus preserving the
config option for users who already set it, but guaranteeing new
users won't disable push advice accidentally.
Based-on-patch-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Christopher Tiwald <christiwald@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-03-20 12:31:33 +08:00
|
|
|
|
2018-04-21 18:10:00 +08:00
|
|
|
if (transport_color_config() < 0)
|
|
|
|
warning(_("could not parse transport.color.* config"));
|
|
|
|
|
2017-10-01 15:29:03 +08:00
|
|
|
head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
|
2009-03-09 09:06:07 +08:00
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
for (ref = refs; ref; ref = ref->next)
|
|
|
|
if (ref->status == REF_STATUS_UPTODATE)
|
2016-10-22 05:39:41 +08:00
|
|
|
n += print_one_push_status(ref, dest, n,
|
|
|
|
porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (ref = refs; ref; ref = ref->next)
|
|
|
|
if (ref->status == REF_STATUS_OK)
|
2016-10-22 05:39:41 +08:00
|
|
|
n += print_one_push_status(ref, dest, n,
|
|
|
|
porcelain, summary_width);
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2012-11-30 09:41:33 +08:00
|
|
|
*reject_reasons = 0;
|
2009-03-09 09:06:07 +08:00
|
|
|
for (ref = refs; ref; ref = ref->next) {
|
|
|
|
if (ref->status != REF_STATUS_NONE &&
|
|
|
|
ref->status != REF_STATUS_UPTODATE &&
|
|
|
|
ref->status != REF_STATUS_OK)
|
2016-10-22 05:39:41 +08:00
|
|
|
n += print_one_push_status(ref, dest, n,
|
|
|
|
porcelain, summary_width);
|
2012-11-30 09:41:33 +08:00
|
|
|
if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {
|
2013-01-31 20:22:51 +08:00
|
|
|
if (head != NULL && !strcmp(head, ref->name))
|
2012-11-30 09:41:33 +08:00
|
|
|
*reject_reasons |= REJECT_NON_FF_HEAD;
|
push: Provide situational hints for non-fast-forward errors
Pushing a non-fast-forward update to a remote repository will result in
an error, but the hint text doesn't provide the correct resolution in
every case. Give better resolution advice in three push scenarios:
1) If you push your current branch and it triggers a non-fast-forward
error, you should merge remote changes with 'git pull' before pushing
again.
2) If you push to a shared repository others push to, and your local
tracking branches are not kept up to date, the 'matching refs' default
will generate non-fast-forward errors on outdated branches. If this is
your workflow, the 'matching refs' default is not for you. Consider
setting the 'push.default' configuration variable to 'current' or
'upstream' to ensure only your current branch is pushed.
3) If you explicitly specify a ref that is not your current branch or
push matching branches with ':', you will generate a non-fast-forward
error if any pushed branch tip is out of date. You should checkout the
offending branch and merge remote changes before pushing again.
Teach transport.c to recognize these scenarios and configure push.c
to hint for them. If 'git push's default behavior changes or we
discover more scenarios, extension is easy. Standardize on the
advice API and add three new advice variables, 'pushNonFFCurrent',
'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these
to 'false' will disable their affiliated advice. Setting
'pushNonFastForward' to false will disable all three, thus preserving the
config option for users who already set it, but guaranteeing new
users won't disable push advice accidentally.
Based-on-patch-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Christopher Tiwald <christiwald@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-03-20 12:31:33 +08:00
|
|
|
else
|
2012-11-30 09:41:33 +08:00
|
|
|
*reject_reasons |= REJECT_NON_FF_OTHER;
|
2012-11-30 09:41:37 +08:00
|
|
|
} else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {
|
|
|
|
*reject_reasons |= REJECT_ALREADY_EXISTS;
|
push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
When we push to update an existing ref, if:
* the object at the tip of the remote is not a commit; or
* the object we are pushing is not a commit,
it won't be correct to suggest to fetch, integrate and push again,
as the old and new objects will not "merge". We should explain that
the push must be forced when there is a non-committish object is
involved in such a case.
If we do not have the current object at the tip of the remote, we do
not even know that object, when fetched, is something that can be
merged. In such a case, suggesting to pull first just like
non-fast-forward case may not be technically correct, but in
practice, most such failures are seen when you try to push your work
to a branch without knowing that somebody else already pushed to
update the same branch since you forked, so "pull first" would work
as a suggestion most of the time. And if the object at the tip is
not a commit, "pull first" will fail, without making any permanent
damage. As a side effect, it also makes the error message the user
will get during the next "push" attempt easier to understand, now
the user is aware that a non-commit object is involved.
In these cases, the current code already rejects such a push on the
client end, but we used the same error and advice messages as the
ones used when rejecting a non-fast-forward push, i.e. pull from
there and integrate before pushing again.
Introduce new rejection reasons and reword the messages
appropriately.
[jc: with help by Peff on message details]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-24 05:55:30 +08:00
|
|
|
} else if (ref->status == REF_STATUS_REJECT_FETCH_FIRST) {
|
|
|
|
*reject_reasons |= REJECT_FETCH_FIRST;
|
|
|
|
} else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) {
|
|
|
|
*reject_reasons |= REJECT_NEEDS_FORCE;
|
2020-10-03 20:10:45 +08:00
|
|
|
} else if (ref->status == REF_STATUS_REJECT_REMOTE_UPDATED) {
|
|
|
|
*reject_reasons |= REJECT_REF_NEEDS_UPDATE;
|
push: Provide situational hints for non-fast-forward errors
Pushing a non-fast-forward update to a remote repository will result in
an error, but the hint text doesn't provide the correct resolution in
every case. Give better resolution advice in three push scenarios:
1) If you push your current branch and it triggers a non-fast-forward
error, you should merge remote changes with 'git pull' before pushing
again.
2) If you push to a shared repository others push to, and your local
tracking branches are not kept up to date, the 'matching refs' default
will generate non-fast-forward errors on outdated branches. If this is
your workflow, the 'matching refs' default is not for you. Consider
setting the 'push.default' configuration variable to 'current' or
'upstream' to ensure only your current branch is pushed.
3) If you explicitly specify a ref that is not your current branch or
push matching branches with ':', you will generate a non-fast-forward
error if any pushed branch tip is out of date. You should checkout the
offending branch and merge remote changes before pushing again.
Teach transport.c to recognize these scenarios and configure push.c
to hint for them. If 'git push's default behavior changes or we
discover more scenarios, extension is easy. Standardize on the
advice API and add three new advice variables, 'pushNonFFCurrent',
'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these
to 'false' will disable their affiliated advice. Setting
'pushNonFastForward' to false will disable all three, thus preserving the
config option for users who already set it, but guaranteeing new
users won't disable push advice accidentally.
Based-on-patch-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Christopher Tiwald <christiwald@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-03-20 12:31:33 +08:00
|
|
|
}
|
2009-03-09 09:06:07 +08:00
|
|
|
}
|
2014-10-21 09:50:44 +08:00
|
|
|
free(head);
|
2009-03-09 09:06:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int git_transport_push(struct transport *transport, struct ref *remote_refs, int flags)
|
2007-11-09 07:35:32 +08:00
|
|
|
{
|
2007-09-11 11:03:04 +08:00
|
|
|
struct git_transport_data *data = transport->data;
|
2007-10-30 10:03:42 +08:00
|
|
|
struct send_pack_args args;
|
2018-03-15 02:31:46 +08:00
|
|
|
int ret = 0;
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2018-04-21 18:10:00 +08:00
|
|
|
if (transport_color_config() < 0)
|
|
|
|
return -1;
|
|
|
|
|
2018-03-15 02:31:43 +08:00
|
|
|
if (!data->got_remote_heads)
|
2018-03-16 01:31:22 +08:00
|
|
|
get_refs_via_connect(transport, 1, NULL);
|
2007-09-11 11:03:04 +08:00
|
|
|
|
2009-10-31 08:47:41 +08:00
|
|
|
memset(&args, 0, sizeof(args));
|
2007-11-10 07:32:25 +08:00
|
|
|
args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
|
2007-10-30 10:03:42 +08:00
|
|
|
args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
|
2009-12-09 23:26:30 +08:00
|
|
|
args.use_thin_pack = data->options.thin;
|
2010-02-24 20:50:24 +08:00
|
|
|
args.verbose = (transport->verbose > 0);
|
|
|
|
args.quiet = (transport->verbose < 0);
|
2010-10-17 02:37:03 +08:00
|
|
|
args.progress = transport->progress;
|
2007-10-30 10:03:42 +08:00
|
|
|
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
|
2010-02-27 12:52:15 +08:00
|
|
|
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
|
2015-01-08 11:23:23 +08:00
|
|
|
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
|
2016-07-15 05:49:47 +08:00
|
|
|
args.push_options = transport->push_options;
|
2014-08-23 09:15:24 +08:00
|
|
|
args.url = transport->url;
|
2007-10-30 10:03:42 +08:00
|
|
|
|
2015-08-19 23:26:46 +08:00
|
|
|
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
|
|
|
|
args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
|
|
|
|
else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED)
|
|
|
|
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
|
|
|
|
else
|
|
|
|
args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
|
|
|
|
|
2018-03-15 02:31:46 +08:00
|
|
|
switch (data->version) {
|
2018-03-15 02:31:47 +08:00
|
|
|
case protocol_v2:
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("support for protocol v2 not implemented yet"));
|
2018-03-15 02:31:47 +08:00
|
|
|
break;
|
2018-03-15 02:31:46 +08:00
|
|
|
case protocol_v1:
|
|
|
|
case protocol_v0:
|
|
|
|
ret = send_pack(&args, data->fd, data->conn, remote_refs,
|
|
|
|
&data->extra_have);
|
|
|
|
break;
|
|
|
|
case protocol_unknown_version:
|
|
|
|
BUG("unknown protocol version");
|
|
|
|
}
|
2009-03-09 09:06:07 +08:00
|
|
|
|
|
|
|
close(data->fd[1]);
|
|
|
|
close(data->fd[0]);
|
2020-04-17 17:45:32 +08:00
|
|
|
/*
|
|
|
|
* Atomic push may abort the connection early and close the pipe,
|
|
|
|
* which may cause an error for `finish_connect()`. Ignore this error
|
|
|
|
* for atomic git-push.
|
|
|
|
*/
|
|
|
|
if (ret || args.atomic)
|
|
|
|
finish_connect(data->conn);
|
|
|
|
else
|
|
|
|
ret = finish_connect(data->conn);
|
2009-03-09 09:06:07 +08:00
|
|
|
data->conn = NULL;
|
2009-12-09 23:26:31 +08:00
|
|
|
data->got_remote_heads = 0;
|
2009-03-09 09:06:07 +08:00
|
|
|
|
|
|
|
return ret;
|
2007-09-11 11:03:04 +08:00
|
|
|
}
|
|
|
|
|
2009-12-09 23:26:33 +08:00
|
|
|
static int connect_git(struct transport *transport, const char *name,
|
|
|
|
const char *executable, int fd[2])
|
|
|
|
{
|
|
|
|
struct git_transport_data *data = transport->data;
|
|
|
|
data->conn = git_connect(data->fd, transport->url,
|
|
|
|
executable, 0);
|
|
|
|
fd[0] = data->fd[0];
|
|
|
|
fd[1] = data->fd[1];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-09-19 12:49:42 +08:00
|
|
|
static int disconnect_git(struct transport *transport)
|
|
|
|
{
|
2008-02-05 02:26:23 +08:00
|
|
|
struct git_transport_data *data = transport->data;
|
|
|
|
if (data->conn) {
|
transport: don't flush when disconnecting stateless-rpc helper
Since ba227857d2 (Reduce the number of connects when fetching,
2008-02-04), when we disconnect a git transport, we send a final flush
packet. This cleanly tells the other side that we're done, and avoids
the other side complaining "the remote end hung up unexpectedly" (though
we'd only see that for transports that pass along the server stderr,
like ssh or local-host).
But when we've initiated a v2 stateless-connect session over a transport
helper, there's no point in sending this flush packet. Each operation
we've performed is self-contained, and the other side is fine with us
hanging up between operations.
But much worse, by sending the flush packet we may cause the helper to
issue an entirely new request _just_ to send the flush packet. So we can
incur an extra network request just to say "by the way, we have nothing
more to send".
Let's drop this extra flush packet. As the test shows, this reduces the
number of POSTs required for a v2 ls-remote over http from 2 to 1.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-08 15:10:09 +08:00
|
|
|
if (data->got_remote_heads && !transport->stateless_rpc)
|
2009-12-09 23:26:31 +08:00
|
|
|
packet_flush(data->fd[1]);
|
2008-02-05 02:26:23 +08:00
|
|
|
close(data->fd[0]);
|
fetch-pack: signal v2 server that we are done making requests
When fetching with the v0 protocol over ssh (or a local upload-pack with
pipes), the server closes the connection as soon as it is finished
sending the pack. So even though the client may still be operating on
the data via index-pack (e.g., resolving deltas, checking connectivity,
etc), the server has released all resources.
With the v2 protocol, however, the server considers the ssh session only
as a transport, with individual requests coming over it. After sending
the pack, it goes back to its main loop, waiting for another request to
come from the client. As a result, the ssh session hangs around until
the client process ends, which may be much later (because resolving
deltas, etc, may consume a lot of CPU).
This is bad for two reasons:
- it's consuming resources on the server to leave open a connection
that won't see any more use
- if something bad happens to the ssh connection in the meantime (say,
it gets killed by the network because it's idle, as happened in a
real-world report), then ssh will exit non-zero, and we'll propagate
the error up the stack.
The server is correct here not to hang up after serving the pack. The v2
protocol's design is meant to allow multiple requests like this, and
hanging up would be the wrong thing for a hypothetical client which was
planning to make more requests (though in practice, the git.git client
never would, and I doubt any other implementations would either).
The right thing is instead for the client to signal to the server that
it's not interested in making more requests. We can do that by closing
the pipe descriptor we use to write to ssh. This will propagate to the
server upload-pack as an EOF when it tries to read the next request (and
then it will close its half, and the whole connection will go away).
It's important to do this "half duplex" shutdown, because we have to do
it _before_ we actually receive the pack. This is an artifact of the way
fetch-pack and index-pack (or unpack-objects) interact. We hand the
connection off to index-pack (really, a sideband demuxer which feeds
it), and then wait until it returns. And it doesn't do that until it has
resolved all of the deltas in the pack, even though it was done reading
from the server long before.
So just closing the connection fully after index-pack returns would be
too late; we'd have held it open much longer than was necessary. And
teaching index-pack to close the connection is awkward. It's not even
seeing the whole conversation (the sideband demuxer is, but it doesn't
actually know what's in the packets, or when the end comes).
Note that this close() is happening deep within the transport code. It's
possible that a caller would want to perform other operations over the
same ssh transport after receiving the pack. But as of the current code,
none of the callers do, and there haven't been discussions of any plans
to change this. If we need to support that later, we can probably do so
by passing down a flag for "you're the last request on the transport;
it's OK to close" instead of the code just assuming that's true.
The description above all discusses v2 ssh, so it's worth thinking about
how this interacts with other protocols:
- in v0 protocols, we could do the same half-duplex shutdown (it just
goes into the v0 do_fetch_pack() instead). This does work, but since
it doesn't have the same persistence problem in the first place,
there's little reason to change it at this point.
- local fetches against git-upload-pack on the same machine will
behave the same as ssh (they are talking over two pipes, and see EOF
on their input pipe)
- fetches against git-daemon will run this same code, and close one of
the descriptors. In practice, this won't do anything, since there
our two descriptors are dups of each other, and not part of a
half-duplex pair. The right thing would probably be to call
shutdown(SHUT_WR) on it. I didn't bother with that here. It doesn't
face the same error-code problem (since it's just a TCP connection),
so it's really only an optimization problem. And git:// is not that
widely used these days, and has less impact on server resources than
an ssh termination.
- v2 http doesn't suffer from this problem in the first place, as our
pipes terminate at a local git-remote-https, which is passing data
along as individual requests via curl. Probably curl is keeping the
TCP/TLS connection open for more requests, and we might be able to
tell it manually "hey, we are done making requests now". But I think
that's much less important. It again doesn't suffer from the
error-code problem, and HTTP keepalive is pretty well understood
(importantly, the timeouts can be set low, because clients like curl
know how to reconnect for subsequent requests if necessary). So it's
probably not worth figuring out how to tell curl that we're done
(though if we do, this patch is probably the first step anyway;
fetch-pack closes the pipe back to remote-https, which would be the
signal that it should tell curl we're done).
The code is pretty straightforward. We close the pipe at the right
moment, and set it to -1 to mark it as invalid. I modified the later
cleanup code to avoid calling close(-1). That's not strictly necessary,
since close(-1) is a noop, but hopefully makes things a bit more obvious
to a reader.
I suspect that trying to call more transport functions after the close()
(e.g., calling transport_fetch_refs() again) would fail, as it's not
smart enough to realize we need to re-open the ssh connection. But
that's already true when v0 is in use. And no current callers want to do
that (and again, the solution is probably a flag in the transport code
to keep things open, which can be added later).
There's no test here, as the situation it covers is inherently racy (the
question is when upload-pack exits, compared to when index-pack finishes
resolving deltas and exits). The rather gross shell snippet below does
recreate the problematic situation; when run on a sufficiently-large
repository (git.git works fine), it kills an "idle" upload-pack while
the client is resolving deltas, leading to a failed clone.
(
git clone --no-local --progress . foo.git 2>&1
echo >&2 "clone exit code=$?"
) |
tr '\r' '\n' |
while read line
do
case "$done,$line" in
,Resolving*)
echo "hit resolving deltas; killing upload-pack"
killall -9 git-upload-pack
done=t
;;
esac
done
Reported-by: Greg Pflaum <greg.pflaum@pnp-hcl.com>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-20 00:11:05 +08:00
|
|
|
if (data->fd[1] >= 0)
|
|
|
|
close(data->fd[1]);
|
2008-02-05 02:26:23 +08:00
|
|
|
finish_connect(data->conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(data);
|
2007-09-19 12:49:42 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-15 05:44:45 +08:00
|
|
|
static struct transport_vtable taken_over_vtable = {
|
2021-08-05 09:25:36 +08:00
|
|
|
.get_refs_list = get_refs_via_connect,
|
|
|
|
.fetch_refs = fetch_refs_via_pack,
|
|
|
|
.push_refs = git_transport_push,
|
|
|
|
.disconnect = disconnect_git
|
2017-12-15 05:44:45 +08:00
|
|
|
};
|
|
|
|
|
2009-12-09 23:26:31 +08:00
|
|
|
void transport_take_over(struct transport *transport,
|
|
|
|
struct child_process *child)
|
|
|
|
{
|
|
|
|
struct git_transport_data *data;
|
|
|
|
|
|
|
|
if (!transport->smart_options)
|
2018-05-02 17:38:39 +08:00
|
|
|
BUG("taking over transport requires non-NULL "
|
2009-12-09 23:26:31 +08:00
|
|
|
"smart_options field.");
|
|
|
|
|
2021-03-14 00:17:22 +08:00
|
|
|
CALLOC_ARRAY(data, 1);
|
2009-12-09 23:26:31 +08:00
|
|
|
data->options = *transport->smart_options;
|
|
|
|
data->conn = child;
|
|
|
|
data->fd[0] = data->conn->out;
|
|
|
|
data->fd[1] = data->conn->in;
|
|
|
|
data->got_remote_heads = 0;
|
|
|
|
transport->data = data;
|
|
|
|
|
2017-12-15 05:44:45 +08:00
|
|
|
transport->vtable = &taken_over_vtable;
|
2009-12-09 23:26:31 +08:00
|
|
|
transport->smart_options = &(data->options);
|
fetch: work around "transport-take-over" hack
A Git-aware "connect" transport allows the "transport_take_over" to
redirect generic transport requests like fetch(), push_refs() and
get_refs_list() to the native Git transport handling methods. The
take-over process replaces transport->data with a fake data that
these method implementations understand.
While this hack works OK for a single request, it breaks when the
transport needs to make more than one requests. transport->data
that used to hold necessary information for the specific helper to
work correctly is destroyed during the take-over process.
One codepath that this matters is "git fetch" in auto-follow mode;
when it does not get all the tags that ought to point at the history
it got (which can be determined by looking at the peeled tags in the
initial advertisement) from the primary transfer, it internally
makes a second request to complete the fetch. Because "take-over"
hack has already destroyed the data necessary to talk to the
transport helper by the time this happens, the second request cannot
make a request to the helper to make another connection to fetch
these additional tags.
Mark such a transport as "cannot_reuse", and use a separate
transport to perform the backfill fetch in order to work around
this breakage.
Note that this problem does not manifest itself when running t5802,
because our upload-pack gives you all the necessary auto-followed
tags during the primary transfer. You would need to step through
"git fetch" in a debugger, stop immediately after the primary
transfer finishes and writes these auto-followed tags, remove the
tag references and repack/prune the repository to convince the
"find-non-local-tags" procedure that the primary transfer failed to
give us all the necessary tags, and then let it continue, in order
to trigger the bug in the secondary transfer this patch fixes.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-08-08 06:47:18 +08:00
|
|
|
|
|
|
|
transport->cannot_reuse = 1;
|
2009-12-09 23:26:31 +08:00
|
|
|
}
|
|
|
|
|
2007-09-11 11:03:04 +08:00
|
|
|
static int is_file(const char *url)
|
|
|
|
{
|
|
|
|
struct stat buf;
|
|
|
|
if (stat(url, &buf))
|
|
|
|
return 0;
|
|
|
|
return S_ISREG(buf.st_mode);
|
|
|
|
}
|
|
|
|
|
2009-12-09 23:26:29 +08:00
|
|
|
static int external_specification_len(const char *url)
|
|
|
|
{
|
|
|
|
return strchr(url, ':') - url;
|
|
|
|
}
|
|
|
|
|
2015-09-23 06:03:49 +08:00
|
|
|
static const struct string_list *protocol_whitelist(void)
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
{
|
2015-09-23 06:03:49 +08:00
|
|
|
static int enabled = -1;
|
|
|
|
static struct string_list allowed = STRING_LIST_INIT_DUP;
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
|
2015-09-23 06:03:49 +08:00
|
|
|
if (enabled < 0) {
|
|
|
|
const char *v = getenv("GIT_ALLOW_PROTOCOL");
|
|
|
|
if (v) {
|
|
|
|
string_list_split(&allowed, v, ':', -1);
|
|
|
|
string_list_sort(&allowed);
|
|
|
|
enabled = 1;
|
|
|
|
} else {
|
|
|
|
enabled = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return enabled ? &allowed : NULL;
|
|
|
|
}
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
|
2016-12-15 06:39:52 +08:00
|
|
|
enum protocol_allow_config {
|
|
|
|
PROTOCOL_ALLOW_NEVER = 0,
|
|
|
|
PROTOCOL_ALLOW_USER_ONLY,
|
|
|
|
PROTOCOL_ALLOW_ALWAYS
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum protocol_allow_config parse_protocol_config(const char *key,
|
|
|
|
const char *value)
|
2015-09-23 06:03:49 +08:00
|
|
|
{
|
2016-12-15 06:39:52 +08:00
|
|
|
if (!strcasecmp(value, "always"))
|
|
|
|
return PROTOCOL_ALLOW_ALWAYS;
|
|
|
|
else if (!strcasecmp(value, "never"))
|
|
|
|
return PROTOCOL_ALLOW_NEVER;
|
|
|
|
else if (!strcasecmp(value, "user"))
|
|
|
|
return PROTOCOL_ALLOW_USER_ONLY;
|
|
|
|
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("unknown value for config '%s': %s"), key, value);
|
2015-09-23 06:03:49 +08:00
|
|
|
}
|
|
|
|
|
2016-12-15 06:39:52 +08:00
|
|
|
static enum protocol_allow_config get_protocol_config(const char *type)
|
2015-09-23 06:03:49 +08:00
|
|
|
{
|
2016-12-15 06:39:52 +08:00
|
|
|
char *key = xstrfmt("protocol.%s.allow", type);
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
/* first check the per-protocol config */
|
|
|
|
if (!git_config_get_string(key, &value)) {
|
|
|
|
enum protocol_allow_config ret =
|
|
|
|
parse_protocol_config(key, value);
|
|
|
|
free(key);
|
|
|
|
free(value);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
free(key);
|
|
|
|
|
|
|
|
/* if defined, fallback to user-defined default for unknown protocols */
|
|
|
|
if (!git_config_get_string("protocol.allow", &value)) {
|
|
|
|
enum protocol_allow_config ret =
|
|
|
|
parse_protocol_config("protocol.allow", value);
|
|
|
|
free(value);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fallback to built-in defaults */
|
|
|
|
/* known safe */
|
|
|
|
if (!strcmp(type, "http") ||
|
|
|
|
!strcmp(type, "https") ||
|
|
|
|
!strcmp(type, "git") ||
|
|
|
|
!strcmp(type, "ssh") ||
|
|
|
|
!strcmp(type, "file"))
|
|
|
|
return PROTOCOL_ALLOW_ALWAYS;
|
|
|
|
|
|
|
|
/* known scary; err on the side of caution */
|
|
|
|
if (!strcmp(type, "ext"))
|
|
|
|
return PROTOCOL_ALLOW_NEVER;
|
|
|
|
|
|
|
|
/* unknown; by default let them be used only directly by the user */
|
|
|
|
return PROTOCOL_ALLOW_USER_ONLY;
|
2015-09-23 06:03:49 +08:00
|
|
|
}
|
|
|
|
|
2016-12-15 06:39:54 +08:00
|
|
|
int is_transport_allowed(const char *type, int from_user)
|
2015-09-23 06:03:49 +08:00
|
|
|
{
|
2016-12-15 06:39:52 +08:00
|
|
|
const struct string_list *whitelist = protocol_whitelist();
|
|
|
|
if (whitelist)
|
|
|
|
return string_list_has_string(whitelist, type);
|
|
|
|
|
|
|
|
switch (get_protocol_config(type)) {
|
|
|
|
case PROTOCOL_ALLOW_ALWAYS:
|
|
|
|
return 1;
|
|
|
|
case PROTOCOL_ALLOW_NEVER:
|
|
|
|
return 0;
|
|
|
|
case PROTOCOL_ALLOW_USER_ONLY:
|
2016-12-15 06:39:54 +08:00
|
|
|
if (from_user < 0)
|
|
|
|
from_user = git_env_bool("GIT_PROTOCOL_FROM_USER", 1);
|
|
|
|
return from_user;
|
2016-12-15 06:39:52 +08:00
|
|
|
}
|
|
|
|
|
2018-05-02 17:38:39 +08:00
|
|
|
BUG("invalid protocol_allow_config type");
|
2015-09-23 06:03:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void transport_check_allowed(const char *type)
|
|
|
|
{
|
2016-12-15 06:39:54 +08:00
|
|
|
if (!is_transport_allowed(type, -1))
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("transport '%s' not allowed"), type);
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
}
|
|
|
|
|
2017-12-15 05:44:45 +08:00
|
|
|
static struct transport_vtable bundle_vtable = {
|
2021-08-05 09:25:36 +08:00
|
|
|
.get_refs_list = get_refs_from_bundle,
|
|
|
|
.fetch_refs = fetch_refs_from_bundle,
|
|
|
|
.disconnect = close_bundle
|
2017-12-15 05:44:45 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct transport_vtable builtin_smart_vtable = {
|
2021-08-05 09:25:36 +08:00
|
|
|
.get_refs_list = get_refs_via_connect,
|
|
|
|
.fetch_refs = fetch_refs_via_pack,
|
|
|
|
.push_refs = git_transport_push,
|
|
|
|
.connect = connect_git,
|
|
|
|
.disconnect = disconnect_git
|
2017-12-15 05:44:45 +08:00
|
|
|
};
|
|
|
|
|
2007-09-15 15:23:14 +08:00
|
|
|
struct transport *transport_get(struct remote *remote, const char *url)
|
2007-09-11 11:03:04 +08:00
|
|
|
{
|
2010-01-28 01:53:17 +08:00
|
|
|
const char *helper;
|
2007-09-15 15:23:07 +08:00
|
|
|
struct transport *ret = xcalloc(1, sizeof(*ret));
|
|
|
|
|
2010-02-24 20:50:26 +08:00
|
|
|
ret->progress = isatty(2);
|
2021-07-01 18:51:29 +08:00
|
|
|
string_list_init_dup(&ret->pack_lockfiles);
|
2010-02-24 20:50:26 +08:00
|
|
|
|
2009-11-04 10:38:51 +08:00
|
|
|
if (!remote)
|
2018-07-21 15:49:19 +08:00
|
|
|
BUG("No remote provided to transport_get()");
|
2009-11-04 10:38:51 +08:00
|
|
|
|
2010-02-16 15:18:21 +08:00
|
|
|
ret->got_remote_refs = 0;
|
2007-09-15 15:23:07 +08:00
|
|
|
ret->remote = remote;
|
2010-01-28 01:53:17 +08:00
|
|
|
helper = remote->foreign_vcs;
|
2009-11-18 09:42:22 +08:00
|
|
|
|
2010-01-28 04:22:37 +08:00
|
|
|
if (!url && remote->url)
|
2009-11-18 09:42:22 +08:00
|
|
|
url = remote->url[0];
|
2007-09-15 15:23:07 +08:00
|
|
|
ret->url = url;
|
|
|
|
|
2009-11-18 09:42:26 +08:00
|
|
|
/* maybe it is a foreign URL? */
|
|
|
|
if (url) {
|
|
|
|
const char *p = url;
|
|
|
|
|
2010-05-23 17:17:55 +08:00
|
|
|
while (is_urlschemechar(p == url, *p))
|
2009-11-18 09:42:26 +08:00
|
|
|
p++;
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(p, "::"))
|
2010-01-28 01:53:17 +08:00
|
|
|
helper = xstrndup(url, p - url);
|
2009-11-18 09:42:26 +08:00
|
|
|
}
|
|
|
|
|
2010-01-28 01:53:17 +08:00
|
|
|
if (helper) {
|
|
|
|
transport_helper_init(ret, helper);
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(url, "rsync:")) {
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("git-over-rsync is no longer supported"));
|
2013-11-29 03:50:03 +08:00
|
|
|
} else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
|
2007-09-11 11:03:21 +08:00
|
|
|
struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
|
bundle: remove "ref_list" in favor of string-list.c API
Move away from the "struct ref_list" in bundle.c in favor of the
almost identical string-list.c API.
That API fits this use-case perfectly, but did not exist in its
current form when this code was added in 2e0afafebd (Add git-bundle:
move objects and references by archive, 2007-02-22), with hindsight we
could have used the path-list API, which later got renamed to
string-list. See 8fd2cb4069 (Extract helper bits from
c-merge-recursive work, 2006-07-25)
We need to change "name" to "string" and "oid" to "util" to make this
conversion, but other than that the APIs are pretty much identical for
what bundle.c made use of.
Let's also replace the memset(..,0,...) pattern with a more idiomatic
"INIT" macro, and finally add a *_release() function so to free the
allocated memory.
Before this the add_to_ref_list() would leak memory, now e.g. "bundle
list-heads" reports no memory leaks at all under valgrind.
In the bundle_header_init() function we're using a clever trick to
memcpy() what we'd get from the corresponding
BUNDLE_HEADER_INIT. There is a concurrent series to make use of that
pattern more generally, see [1].
1. https://lore.kernel.org/git/cover-0.5-00000000000-20210701T104855Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-02 17:57:32 +08:00
|
|
|
bundle_header_init(&data->header);
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
transport_check_allowed("file");
|
2007-09-11 11:03:21 +08:00
|
|
|
ret->data = data;
|
2017-12-15 05:44:45 +08:00
|
|
|
ret->vtable = &bundle_vtable;
|
2009-12-09 23:26:30 +08:00
|
|
|
ret->smart_options = NULL;
|
2009-12-09 23:26:29 +08:00
|
|
|
} else if (!is_url(url)
|
2013-12-01 04:55:40 +08:00
|
|
|
|| starts_with(url, "file://")
|
|
|
|
|| starts_with(url, "git://")
|
|
|
|
|| starts_with(url, "ssh://")
|
2016-02-15 22:29:06 +08:00
|
|
|
|| starts_with(url, "git+ssh://") /* deprecated - do not use */
|
|
|
|
|| starts_with(url, "ssh+git://") /* deprecated - do not use */
|
|
|
|
) {
|
transport: add a protocol-whitelist environment variable
If we are cloning an untrusted remote repository into a
sandbox, we may also want to fetch remote submodules in
order to get the complete view as intended by the other
side. However, that opens us up to attacks where a malicious
user gets us to clone something they would not otherwise
have access to (this is not necessarily a problem by itself,
but we may then act on the cloned contents in a way that
exposes them to the attacker).
Ideally such a setup would sandbox git entirely away from
high-value items, but this is not always practical or easy
to set up (e.g., OS network controls may block multiple
protocols, and we would want to enable some but not others).
We can help this case by providing a way to restrict
particular protocols. We use a whitelist in the environment.
This is more annoying to set up than a blacklist, but
defaults to safety if the set of protocols git supports
grows). If no whitelist is specified, we continue to default
to allowing all protocols (this is an "unsafe" default, but
since the minority of users will want this sandboxing
effect, it is the only sensible one).
A note on the tests: ideally these would all be in a single
test file, but the git-daemon and httpd test infrastructure
is an all-or-nothing proposition rather than a test-by-test
prerequisite. By putting them all together, we would be
unable to test the file-local code on machines without
apache.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-17 01:12:52 +08:00
|
|
|
/*
|
|
|
|
* These are builtin smart transports; "allowed" transports
|
|
|
|
* will be checked individually in git_connect.
|
|
|
|
*/
|
2007-09-11 11:03:04 +08:00
|
|
|
struct git_transport_data *data = xcalloc(1, sizeof(*data));
|
|
|
|
ret->data = data;
|
2017-12-15 05:44:45 +08:00
|
|
|
ret->vtable = &builtin_smart_vtable;
|
2009-12-09 23:26:30 +08:00
|
|
|
ret->smart_options = &(data->options);
|
2007-09-19 12:49:31 +08:00
|
|
|
|
2008-02-05 02:26:23 +08:00
|
|
|
data->conn = NULL;
|
2009-12-09 23:26:31 +08:00
|
|
|
data->got_remote_heads = 0;
|
2009-12-09 23:26:29 +08:00
|
|
|
} else {
|
|
|
|
/* Unknown protocol in URL. Pass to external handler. */
|
|
|
|
int len = external_specification_len(url);
|
2014-12-24 08:18:31 +08:00
|
|
|
char *handler = xmemdupz(url, len);
|
2009-12-09 23:26:29 +08:00
|
|
|
transport_helper_init(ret, handler);
|
2007-09-11 11:03:04 +08:00
|
|
|
}
|
2007-09-15 15:23:07 +08:00
|
|
|
|
2009-12-09 23:26:30 +08:00
|
|
|
if (ret->smart_options) {
|
|
|
|
ret->smart_options->thin = 1;
|
|
|
|
ret->smart_options->uploadpack = "git-upload-pack";
|
|
|
|
if (remote->uploadpack)
|
|
|
|
ret->smart_options->uploadpack = remote->uploadpack;
|
|
|
|
ret->smart_options->receivepack = "git-receive-pack";
|
|
|
|
if (remote->receivepack)
|
|
|
|
ret->smart_options->receivepack = remote->receivepack;
|
|
|
|
}
|
|
|
|
|
2020-05-26 03:58:55 +08:00
|
|
|
ret->hash_algo = &hash_algos[GIT_HASH_SHA1];
|
|
|
|
|
2007-09-11 11:03:04 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-05-26 03:58:55 +08:00
|
|
|
const struct git_hash_algo *transport_get_hash_algo(struct transport *transport)
|
|
|
|
{
|
|
|
|
return transport->hash_algo;
|
|
|
|
}
|
|
|
|
|
2007-09-11 11:03:04 +08:00
|
|
|
int transport_set_option(struct transport *transport,
|
|
|
|
const char *name, const char *value)
|
|
|
|
{
|
2009-12-09 23:26:30 +08:00
|
|
|
int git_reports = 1, protocol_reports = 1;
|
|
|
|
|
|
|
|
if (transport->smart_options)
|
|
|
|
git_reports = set_git_option(transport->smart_options,
|
|
|
|
name, value);
|
|
|
|
|
2017-12-15 05:44:45 +08:00
|
|
|
if (transport->vtable->set_option)
|
|
|
|
protocol_reports = transport->vtable->set_option(transport,
|
|
|
|
name, value);
|
2009-12-09 23:26:30 +08:00
|
|
|
|
|
|
|
/* If either report is 0, report 0 (success). */
|
|
|
|
if (!git_reports || !protocol_reports)
|
|
|
|
return 0;
|
|
|
|
/* If either reports -1 (invalid value), report -1. */
|
|
|
|
if ((git_reports == -1) || (protocol_reports == -1))
|
|
|
|
return -1;
|
|
|
|
/* Otherwise if both report unknown, report unknown. */
|
2007-09-18 16:54:57 +08:00
|
|
|
return 1;
|
2007-09-11 11:03:04 +08:00
|
|
|
}
|
|
|
|
|
2010-02-24 20:50:26 +08:00
|
|
|
void transport_set_verbosity(struct transport *transport, int verbosity,
|
|
|
|
int force_progress)
|
2010-02-24 20:50:23 +08:00
|
|
|
{
|
make "git push -v" actually verbose
Providing a single "-v" to "git push" currently does
nothing. Giving two flags ("git push -v -v") turns on the
first level of verbosity.
This is caused by a regression introduced in 8afd8dc (push:
support multiple levels of verbosity, 2010-02-24). Before
the series containing 8afd8dc, the verbosity handling for
fetching and pushing was completely separate. Commit bde873c
refactored the verbosity handling out of the fetch side, and
then 8afd8dc converted push to use the refactored code.
However, the fetch and push sides numbered and passed along
their verbosity levels differently. For both, a verbosity
level of "-1" meant "quiet", and "0" meant "default output".
But from there they differed.
For fetch, a verbosity level of "1" indicated to the "fetch"
program that it should make the status table slightly more
verbose, showing up-to-date entries. A verbosity level of
"2" meant that we should pass a verbose flag to the
transport; in the case of fetch-pack, this displays protocol
debugging information.
As a result, the refactored code in bde873c checks for
"verbosity >= 2", and only then passes it on to the
transport. From the transport code's perspective, a
verbosity of 0 or 1 both meant "0".
Push, on the other hand, does not show its own status table;
that is always handled by the transport layer or below
(originally send-pack itself, but these days it is done by
the transport code). So a verbosity level of 1 meant that we
should pass the verbose flag to send-pack, so that it knows
we want a verbose status table. However, once 8afd8dc
switched it to the refactored fetch code, a verbosity level
of 1 was now being ignored. Thus, you needed to
artificially bump the verbosity to 2 (via "-v -v") to have
any effect.
We can fix this by letting the transport code know about the
true verbosity level (i.e., let it distinguish level 0 or
1).
We then have to also make an adjustment to any transport
methods that assumed "verbose > 0" meant they could spew
lots of debugging information. Before, they could only get
"0" or "2", but now they will also receive "1". They need to
adjust their condition for turning on such spew from
"verbose > 0" to "verbose > 1".
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-12-17 17:37:15 +08:00
|
|
|
if (verbosity >= 1)
|
2010-02-24 20:50:23 +08:00
|
|
|
transport->verbose = verbosity <= 3 ? verbosity : 3;
|
|
|
|
if (verbosity < 0)
|
|
|
|
transport->verbose = -1;
|
2010-02-24 20:50:26 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Rules used to determine whether to report progress (processing aborts
|
|
|
|
* when a rule is satisfied):
|
|
|
|
*
|
2012-02-14 04:17:15 +08:00
|
|
|
* . Report progress, if force_progress is 1 (ie. --progress).
|
|
|
|
* . Don't report progress, if force_progress is 0 (ie. --no-progress).
|
|
|
|
* . Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
|
|
|
|
* . Report progress if isatty(2) is 1.
|
2010-02-24 20:50:26 +08:00
|
|
|
**/
|
2012-02-14 04:17:15 +08:00
|
|
|
if (force_progress >= 0)
|
|
|
|
transport->progress = !!force_progress;
|
|
|
|
else
|
|
|
|
transport->progress = verbosity >= 0 && isatty(2);
|
2010-02-24 20:50:23 +08:00
|
|
|
}
|
|
|
|
|
2012-03-29 15:21:23 +08:00
|
|
|
static void die_with_unpushed_submodules(struct string_list *needs_pushing)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2016-06-18 04:20:53 +08:00
|
|
|
fprintf(stderr, _("The following submodule paths contain changes that can\n"
|
|
|
|
"not be found on any remote:\n"));
|
2012-03-29 15:21:23 +08:00
|
|
|
for (i = 0; i < needs_pushing->nr; i++)
|
2016-08-24 05:40:08 +08:00
|
|
|
fprintf(stderr, " %s\n", needs_pushing->items[i].string);
|
2016-06-18 04:20:53 +08:00
|
|
|
fprintf(stderr, _("\nPlease try\n\n"
|
|
|
|
" git push --recurse-submodules=on-demand\n\n"
|
|
|
|
"or cd to the path and use\n\n"
|
|
|
|
" git push\n\n"
|
|
|
|
"to push them to a remote.\n\n"));
|
2012-03-29 15:21:23 +08:00
|
|
|
|
|
|
|
string_list_clear(needs_pushing, 0);
|
|
|
|
|
2016-06-18 04:20:53 +08:00
|
|
|
die(_("Aborting."));
|
2012-03-29 15:21:23 +08:00
|
|
|
}
|
|
|
|
|
2013-01-13 13:17:03 +08:00
|
|
|
static int run_pre_push_hook(struct transport *transport,
|
|
|
|
struct ref *remote_refs)
|
|
|
|
{
|
|
|
|
int ret = 0, x;
|
|
|
|
struct ref *r;
|
2014-08-20 03:09:35 +08:00
|
|
|
struct child_process proc = CHILD_PROCESS_INIT;
|
2013-01-13 13:17:03 +08:00
|
|
|
struct strbuf buf;
|
2021-11-26 06:52:21 +08:00
|
|
|
const char *hook_path = find_hook("pre-push");
|
2013-01-13 13:17:03 +08:00
|
|
|
|
2021-11-26 06:52:21 +08:00
|
|
|
if (!hook_path)
|
2013-01-13 13:17:03 +08:00
|
|
|
return 0;
|
|
|
|
|
2021-11-26 06:52:21 +08:00
|
|
|
strvec_push(&proc.args, hook_path);
|
|
|
|
strvec_push(&proc.args, transport->remote->name);
|
|
|
|
strvec_push(&proc.args, transport->url);
|
2013-01-13 13:17:03 +08:00
|
|
|
|
|
|
|
proc.in = -1;
|
2019-02-23 06:25:06 +08:00
|
|
|
proc.trace2_hook_name = "pre-push";
|
2013-01-13 13:17:03 +08:00
|
|
|
|
|
|
|
if (start_command(&proc)) {
|
|
|
|
finish_command(&proc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-11-16 16:05:58 +08:00
|
|
|
sigchain_push(SIGPIPE, SIG_IGN);
|
|
|
|
|
2013-01-13 13:17:03 +08:00
|
|
|
strbuf_init(&buf, 256);
|
|
|
|
|
|
|
|
for (r = remote_refs; r; r = r->next) {
|
|
|
|
if (!r->peer_ref) continue;
|
|
|
|
if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
|
2013-07-09 05:42:40 +08:00
|
|
|
if (r->status == REF_STATUS_REJECT_STALE) continue;
|
2020-10-03 20:10:44 +08:00
|
|
|
if (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue;
|
2013-01-13 13:17:03 +08:00
|
|
|
if (r->status == REF_STATUS_UPTODATE) continue;
|
|
|
|
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf( &buf, "%s %s %s %s\n",
|
2015-11-10 10:22:20 +08:00
|
|
|
r->peer_ref->name, oid_to_hex(&r->new_oid),
|
|
|
|
r->name, oid_to_hex(&r->old_oid));
|
2013-01-13 13:17:03 +08:00
|
|
|
|
2015-11-16 16:05:58 +08:00
|
|
|
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
|
|
|
|
/* We do not mind if a hook does not read all refs. */
|
|
|
|
if (errno != EPIPE)
|
|
|
|
ret = -1;
|
2013-01-13 13:17:03 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_release(&buf);
|
|
|
|
|
|
|
|
x = close(proc.in);
|
|
|
|
if (!ret)
|
|
|
|
ret = x;
|
|
|
|
|
2015-11-16 16:05:58 +08:00
|
|
|
sigchain_pop(SIGPIPE);
|
|
|
|
|
2013-01-13 13:17:03 +08:00
|
|
|
x = finish_command(&proc);
|
|
|
|
if (!ret)
|
|
|
|
ret = x;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-11-10 13:48:55 +08:00
|
|
|
int transport_push(struct repository *r,
|
|
|
|
struct transport *transport,
|
2018-05-17 06:58:17 +08:00
|
|
|
struct refspec *rs, int flags,
|
2012-11-30 09:41:33 +08:00
|
|
|
unsigned int *reject_reasons)
|
2007-09-11 11:03:04 +08:00
|
|
|
{
|
2022-05-20 20:49:50 +08:00
|
|
|
struct ref *remote_refs;
|
|
|
|
struct ref *local_refs;
|
|
|
|
int match_flags = MATCH_REFS_NONE;
|
|
|
|
int verbose = (transport->verbose > 0);
|
|
|
|
int quiet = (transport->verbose < 0);
|
|
|
|
int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
|
|
|
|
int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
|
|
|
|
int push_ret, ret, err;
|
|
|
|
struct transport_ls_refs_options transport_options =
|
|
|
|
TRANSPORT_LS_REFS_OPTIONS_INIT;
|
|
|
|
|
2012-11-30 09:41:33 +08:00
|
|
|
*reject_reasons = 0;
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2018-04-21 18:10:00 +08:00
|
|
|
if (transport_color_config() < 0)
|
|
|
|
return -1;
|
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (!transport->vtable->push_refs)
|
|
|
|
return 1;
|
2014-03-06 03:04:54 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
local_refs = get_local_heads();
|
2018-03-16 01:31:26 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (check_push_refs(local_refs, rs) < 0)
|
|
|
|
return -1;
|
2018-03-16 01:31:26 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
refspec_ref_prefixes(rs, &transport_options.ref_prefixes);
|
2014-03-06 03:04:54 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
trace2_region_enter("transport_push", "get_refs_list", r);
|
|
|
|
remote_refs = transport->vtable->get_refs_list(transport, 1,
|
|
|
|
&transport_options);
|
|
|
|
trace2_region_leave("transport_push", "get_refs_list", r);
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
transport_ls_refs_options_release(&transport_options);
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (flags & TRANSPORT_PUSH_ALL)
|
|
|
|
match_flags |= MATCH_REFS_ALL;
|
|
|
|
if (flags & TRANSPORT_PUSH_MIRROR)
|
|
|
|
match_flags |= MATCH_REFS_MIRROR;
|
|
|
|
if (flags & TRANSPORT_PUSH_PRUNE)
|
|
|
|
match_flags |= MATCH_REFS_PRUNE;
|
|
|
|
if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
|
|
|
|
match_flags |= MATCH_REFS_FOLLOW_TAGS;
|
2013-07-10 02:01:06 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (match_push_refs(local_refs, &remote_refs, rs, match_flags))
|
|
|
|
return -1;
|
2010-01-08 10:12:42 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (transport->smart_options &&
|
|
|
|
transport->smart_options->cas &&
|
|
|
|
!is_empty_cas(transport->smart_options->cas))
|
|
|
|
apply_push_cas(transport->smart_options->cas,
|
|
|
|
transport->remote, remote_refs);
|
2013-01-13 13:17:03 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
set_ref_status_for_push(remote_refs,
|
|
|
|
flags & TRANSPORT_PUSH_MIRROR,
|
|
|
|
flags & TRANSPORT_PUSH_FORCE);
|
|
|
|
|
|
|
|
if (!(flags & TRANSPORT_PUSH_NO_HOOK))
|
|
|
|
if (run_pre_push_hook(transport, remote_refs))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
|
|
|
|
TRANSPORT_RECURSE_SUBMODULES_ONLY)) &&
|
|
|
|
!is_bare_repository()) {
|
|
|
|
struct ref *ref = remote_refs;
|
|
|
|
struct oid_array commits = OID_ARRAY_INIT;
|
|
|
|
|
|
|
|
trace2_region_enter("transport_push", "push_submodules", r);
|
|
|
|
for (; ref; ref = ref->next)
|
|
|
|
if (!is_null_oid(&ref->new_oid))
|
|
|
|
oid_array_append(&commits,
|
|
|
|
&ref->new_oid);
|
|
|
|
|
|
|
|
if (!push_unpushed_submodules(r,
|
|
|
|
&commits,
|
|
|
|
transport->remote,
|
|
|
|
rs,
|
|
|
|
transport->push_options,
|
|
|
|
pretend)) {
|
2017-03-31 09:40:00 +08:00
|
|
|
oid_array_clear(&commits);
|
2019-10-08 12:18:24 +08:00
|
|
|
trace2_region_leave("transport_push", "push_submodules", r);
|
2022-05-20 20:49:50 +08:00
|
|
|
die(_("failed to push all needed submodules"));
|
2012-03-29 15:21:24 +08:00
|
|
|
}
|
2022-05-20 20:49:50 +08:00
|
|
|
oid_array_clear(&commits);
|
|
|
|
trace2_region_leave("transport_push", "push_submodules", r);
|
|
|
|
}
|
2012-03-29 15:21:24 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) ||
|
|
|
|
((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
|
|
|
|
TRANSPORT_RECURSE_SUBMODULES_ONLY)) &&
|
|
|
|
!pretend)) && !is_bare_repository()) {
|
|
|
|
struct ref *ref = remote_refs;
|
|
|
|
struct string_list needs_pushing = STRING_LIST_INIT_DUP;
|
|
|
|
struct oid_array commits = OID_ARRAY_INIT;
|
|
|
|
|
|
|
|
trace2_region_enter("transport_push", "check_submodules", r);
|
|
|
|
for (; ref; ref = ref->next)
|
|
|
|
if (!is_null_oid(&ref->new_oid))
|
|
|
|
oid_array_append(&commits,
|
|
|
|
&ref->new_oid);
|
|
|
|
|
|
|
|
if (find_unpushed_submodules(r,
|
|
|
|
&commits,
|
|
|
|
transport->remote->name,
|
|
|
|
&needs_pushing)) {
|
2017-03-31 09:40:00 +08:00
|
|
|
oid_array_clear(&commits);
|
2019-10-08 12:18:24 +08:00
|
|
|
trace2_region_leave("transport_push", "check_submodules", r);
|
2022-05-20 20:49:50 +08:00
|
|
|
die_with_unpushed_submodules(&needs_pushing);
|
2011-08-20 06:08:47 +08:00
|
|
|
}
|
2022-05-20 20:49:50 +08:00
|
|
|
string_list_clear(&needs_pushing, 0);
|
|
|
|
oid_array_clear(&commits);
|
|
|
|
trace2_region_leave("transport_push", "check_submodules", r);
|
|
|
|
}
|
2011-08-20 06:08:47 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY)) {
|
|
|
|
trace2_region_enter("transport_push", "push_refs", r);
|
|
|
|
push_ret = transport->vtable->push_refs(transport, remote_refs, flags);
|
|
|
|
trace2_region_leave("transport_push", "push_refs", r);
|
|
|
|
} else
|
|
|
|
push_ret = 0;
|
|
|
|
err = push_had_errors(remote_refs);
|
|
|
|
ret = push_ret | err;
|
|
|
|
|
|
|
|
if (!quiet || err)
|
|
|
|
transport_print_push_status(transport->url, remote_refs,
|
|
|
|
verbose | porcelain, porcelain,
|
|
|
|
reject_reasons);
|
|
|
|
|
|
|
|
if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
|
|
|
|
set_upstreams(transport, remote_refs, pretend);
|
|
|
|
|
|
|
|
if (!(flags & (TRANSPORT_PUSH_DRY_RUN |
|
|
|
|
TRANSPORT_RECURSE_SUBMODULES_ONLY))) {
|
|
|
|
struct ref *ref;
|
|
|
|
for (ref = remote_refs; ref; ref = ref->next)
|
|
|
|
transport_update_tracking_ref(transport->remote, ref, verbose);
|
|
|
|
}
|
2009-03-09 09:06:07 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
if (porcelain && !push_ret)
|
|
|
|
puts("Done");
|
|
|
|
else if (!quiet && !ret && !transport_refs_pushed(remote_refs))
|
|
|
|
fprintf(stderr, "Everything up-to-date\n");
|
2010-02-27 12:52:15 +08:00
|
|
|
|
2022-05-20 20:49:50 +08:00
|
|
|
return ret;
|
2007-09-11 11:03:04 +08:00
|
|
|
}
|
|
|
|
|
2018-03-16 01:31:23 +08:00
|
|
|
const struct ref *transport_get_remote_refs(struct transport *transport,
|
2021-02-06 04:48:48 +08:00
|
|
|
struct transport_ls_refs_options *transport_options)
|
2007-09-11 11:03:11 +08:00
|
|
|
{
|
2010-02-16 15:18:21 +08:00
|
|
|
if (!transport->got_remote_refs) {
|
2018-03-16 01:31:23 +08:00
|
|
|
transport->remote_refs =
|
|
|
|
transport->vtable->get_refs_list(transport, 0,
|
2021-02-06 04:48:48 +08:00
|
|
|
transport_options);
|
2010-02-16 15:18:21 +08:00
|
|
|
transport->got_remote_refs = 1;
|
|
|
|
}
|
2009-12-09 23:26:31 +08:00
|
|
|
|
2007-09-11 11:03:11 +08:00
|
|
|
return transport->remote_refs;
|
|
|
|
}
|
|
|
|
|
2022-02-05 08:08:14 +08:00
|
|
|
void transport_ls_refs_options_release(struct transport_ls_refs_options *opts)
|
|
|
|
{
|
|
|
|
strvec_clear(&opts->ref_prefixes);
|
|
|
|
free((char *)opts->unborn_head_target);
|
|
|
|
}
|
|
|
|
|
fetch-pack: unify ref in and out param
When a user fetches:
- at least one up-to-date ref and at least one non-up-to-date ref,
- using HTTP with protocol v0 (or something else that uses the fetch
command of a remote helper)
some refs might not be updated after the fetch.
This bug was introduced in commit 989b8c4452 ("fetch-pack: put shallow
info in output parameter", 2018-06-28) which allowed transports to
report the refs that they have fetched in a new out-parameter
"fetched_refs". If they do so, transport_fetch_refs() makes this
information available to its caller.
Users of "fetched_refs" rely on the following 3 properties:
(1) it is the complete list of refs that was passed to
transport_fetch_refs(),
(2) it has shallow information (REF_STATUS_REJECT_SHALLOW set if
relevant), and
(3) it has updated OIDs if ref-in-want was used (introduced after
989b8c4452).
In an effort to satisfy (1), whenever transport_fetch_refs()
filters the refs sent to the transport, it re-adds the filtered refs to
whatever the transport supplies before returning it to the user.
However, the implementation in 989b8c4452 unconditionally re-adds the
filtered refs without checking if the transport refrained from reporting
anything in "fetched_refs" (which it is allowed to do), resulting in an
incomplete list, no longer satisfying (1).
An earlier effort to resolve this [1] solved the issue by readding the
filtered refs only if the transport did not refrain from reporting in
"fetched_refs", but after further discussion, it seems that the better
solution is to revert the API change that introduced "fetched_refs".
This API change was first suggested as part of a ref-in-want
implementation that allowed for ref patterns and, thus, there could be
drastic differences between the input refs and the refs actually fetched
[2]; we eventually decided to only allow exact ref names, but this API
change remained even though its necessity was decreased.
Therefore, revert this API change by reverting commit 989b8c4452, and
make receive_wanted_refs() update the OIDs in the sought array (like how
update_shallow() updates shallow information in the sought array)
instead. A test is also included to show that the user-visible bug
discussed at the beginning of this commit message no longer exists.
[1] https://public-inbox.org/git/20180801171806.GA122458@google.com/
[2] https://public-inbox.org/git/86a128c5fb710a41791e7183207c4d64889f9307.1485381677.git.jonathantanmy@google.com/
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-02 04:13:20 +08:00
|
|
|
int transport_fetch_refs(struct transport *transport, struct ref *refs)
|
2007-09-11 11:03:11 +08:00
|
|
|
{
|
2007-09-14 15:31:21 +08:00
|
|
|
int rc;
|
2009-08-24 12:04:09 +08:00
|
|
|
int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
|
2009-11-18 09:42:24 +08:00
|
|
|
struct ref **heads = NULL;
|
|
|
|
struct ref *rm;
|
2007-09-11 11:03:11 +08:00
|
|
|
|
|
|
|
for (rm = refs; rm; rm = rm->next) {
|
2009-08-24 12:04:09 +08:00
|
|
|
nr_refs++;
|
2007-09-11 11:03:11 +08:00
|
|
|
if (rm->peer_ref &&
|
2015-11-10 10:22:20 +08:00
|
|
|
!is_null_oid(&rm->old_oid) &&
|
convert "oidcmp() == 0" to oideq()
Using the more restrictive oideq() should, in the long run,
give the compiler more opportunities to optimize these
callsites. For now, this conversion should be a complete
noop with respect to the generated code.
The result is also perhaps a little more readable, as it
avoids the "zero is equal" idiom. Since it's so prevalent in
C, I think seasoned programmers tend not to even notice it
anymore, but it can sometimes make for awkward double
negations (e.g., we can drop a few !!oidcmp() instances
here).
This patch was generated almost entirely by the included
coccinelle patch. This mechanical conversion should be
completely safe, because we check explicitly for cases where
oidcmp() is compared to 0, which is what oideq() is doing
under the hood. Note that we don't have to catch "!oidcmp()"
separately; coccinelle's standard isomorphisms make sure the
two are treated equivalently.
I say "almost" because I did hand-edit the coccinelle output
to fix up a few style violations (it mostly keeps the
original formatting, but sometimes unwraps long lines).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-29 05:22:40 +08:00
|
|
|
oideq(&rm->peer_ref->old_oid, &rm->old_oid))
|
2007-09-11 11:03:11 +08:00
|
|
|
continue;
|
2007-09-14 15:31:18 +08:00
|
|
|
ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
|
2007-09-14 15:31:21 +08:00
|
|
|
heads[nr_heads++] = rm;
|
2007-09-11 11:03:11 +08:00
|
|
|
}
|
|
|
|
|
2009-08-24 12:04:09 +08:00
|
|
|
if (!nr_heads) {
|
|
|
|
/*
|
|
|
|
* When deepening of a shallow repository is requested,
|
|
|
|
* then local and remote refs are likely to still be equal.
|
|
|
|
* Just feed them all to the fetch method in that case.
|
|
|
|
* This condition shouldn't be met in a non-deepening fetch
|
2013-06-19 01:44:58 +08:00
|
|
|
* (see builtin/fetch.c:quickfetch()).
|
2009-08-24 12:04:09 +08:00
|
|
|
*/
|
2016-02-23 06:44:25 +08:00
|
|
|
ALLOC_ARRAY(heads, nr_refs);
|
2009-08-24 12:04:09 +08:00
|
|
|
for (rm = refs; rm; rm = rm->next)
|
|
|
|
heads[nr_heads++] = rm;
|
|
|
|
}
|
|
|
|
|
2021-08-05 09:25:35 +08:00
|
|
|
rc = transport->vtable->fetch_refs(transport, nr_heads, heads);
|
2009-12-09 23:26:31 +08:00
|
|
|
|
2007-09-11 11:03:11 +08:00
|
|
|
free(heads);
|
2007-09-14 15:31:21 +08:00
|
|
|
return rc;
|
2007-09-11 11:03:11 +08:00
|
|
|
}
|
|
|
|
|
fetch: fix deadlock when cleaning up lockfiles in async signals
When fetching packfiles, we write a bunch of lockfiles for the packfiles
we're writing into the repository. In order to not leave behind any
cruft in case we exit or receive a signal, we register both an exit
handler as well as signal handlers for common signals like SIGINT. These
handlers will then unlink the locks and free the data structure tracking
them. We have observed a deadlock in this logic though:
(gdb) bt
#0 __lll_lock_wait_private () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95
#1 0x00007f4932bea2cd in _int_free (av=0x7f4932f2eb20 <main_arena>, p=0x3e3e4200, have_lock=0) at malloc.c:3969
#2 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#3 0x0000000000662ab1 in string_list_clear ()
#4 0x000000000044f5bc in unlock_pack_on_signal ()
#5 <signal handler called>
#6 _int_free (av=0x7f4932f2eb20 <main_arena>, p=<optimized out>, have_lock=0) at malloc.c:4024
#7 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#8 0x000000000065afd5 in strbuf_release ()
#9 0x000000000066ddb9 in delete_tempfile ()
#10 0x0000000000610d0b in files_transaction_cleanup.isra ()
#11 0x0000000000611718 in files_transaction_abort ()
#12 0x000000000060d2ef in ref_transaction_abort ()
#13 0x000000000060d441 in ref_transaction_prepare ()
#14 0x000000000060e0b5 in ref_transaction_commit ()
#15 0x00000000004511c2 in fetch_and_consume_refs ()
#16 0x000000000045279a in cmd_fetch ()
#17 0x0000000000407c48 in handle_builtin ()
#18 0x0000000000408df2 in cmd_main ()
#19 0x00000000004078b5 in main ()
The process was killed with a signal, which caused the signal handler to
kick in and try free the data structures after we have unlinked the
locks. It then deadlocks while calling free(3P).
The root cause of this is that it is not allowed to call certain
functions in async-signal handlers, as specified by signal-safety(7).
Next to most I/O functions, this list of disallowed functions also
includes memory-handling functions like malloc(3P) and free(3P) because
they may not be reentrant. As a result, if we execute such functions in
the signal handler, then they may operate on inconistent state and fail
in unexpected ways.
Fix this bug by not calling non-async-signal-safe functions when running
in the signal handler. We're about to re-raise the signal anyway and
will thus exit, so it's not much of a problem to keep the string list of
lockfiles untouched. Note that it's fine though to call unlink(2), so
we'll still clean up the lockfiles correctly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-07 18:55:47 +08:00
|
|
|
void transport_unlock_pack(struct transport *transport, unsigned int flags)
|
2007-09-14 15:31:23 +08:00
|
|
|
{
|
fetch: fix deadlock when cleaning up lockfiles in async signals
When fetching packfiles, we write a bunch of lockfiles for the packfiles
we're writing into the repository. In order to not leave behind any
cruft in case we exit or receive a signal, we register both an exit
handler as well as signal handlers for common signals like SIGINT. These
handlers will then unlink the locks and free the data structure tracking
them. We have observed a deadlock in this logic though:
(gdb) bt
#0 __lll_lock_wait_private () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95
#1 0x00007f4932bea2cd in _int_free (av=0x7f4932f2eb20 <main_arena>, p=0x3e3e4200, have_lock=0) at malloc.c:3969
#2 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#3 0x0000000000662ab1 in string_list_clear ()
#4 0x000000000044f5bc in unlock_pack_on_signal ()
#5 <signal handler called>
#6 _int_free (av=0x7f4932f2eb20 <main_arena>, p=<optimized out>, have_lock=0) at malloc.c:4024
#7 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#8 0x000000000065afd5 in strbuf_release ()
#9 0x000000000066ddb9 in delete_tempfile ()
#10 0x0000000000610d0b in files_transaction_cleanup.isra ()
#11 0x0000000000611718 in files_transaction_abort ()
#12 0x000000000060d2ef in ref_transaction_abort ()
#13 0x000000000060d441 in ref_transaction_prepare ()
#14 0x000000000060e0b5 in ref_transaction_commit ()
#15 0x00000000004511c2 in fetch_and_consume_refs ()
#16 0x000000000045279a in cmd_fetch ()
#17 0x0000000000407c48 in handle_builtin ()
#18 0x0000000000408df2 in cmd_main ()
#19 0x00000000004078b5 in main ()
The process was killed with a signal, which caused the signal handler to
kick in and try free the data structures after we have unlinked the
locks. It then deadlocks while calling free(3P).
The root cause of this is that it is not allowed to call certain
functions in async-signal handlers, as specified by signal-safety(7).
Next to most I/O functions, this list of disallowed functions also
includes memory-handling functions like malloc(3P) and free(3P) because
they may not be reentrant. As a result, if we execute such functions in
the signal handler, then they may operate on inconistent state and fail
in unexpected ways.
Fix this bug by not calling non-async-signal-safe functions when running
in the signal handler. We're about to re-raise the signal anyway and
will thus exit, so it's not much of a problem to keep the string list of
lockfiles untouched. Note that it's fine though to call unlink(2), so
we'll still clean up the lockfiles correctly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-07 18:55:47 +08:00
|
|
|
int in_signal_handler = !!(flags & TRANSPORT_UNLOCK_PACK_IN_SIGNAL_HANDLER);
|
fetch-pack: support more than one pack lockfile
Whenever a fetch results in a packfile being downloaded, a .keep file is
generated, so that the packfile can be preserved (from, say, a running
"git repack") until refs are written referring to the contents of the
packfile.
In a subsequent patch, a successful fetch using protocol v2 may result
in more than one .keep file being generated. Therefore, teach
fetch_pack() and the transport mechanism to support multiple .keep
files.
Implementation notes:
- builtin/fetch-pack.c normally does not generate .keep files, and thus
is unaffected by this or future changes. However, it has an
undocumented "--lock-pack" feature, used by remote-curl.c when
implementing the "fetch" remote helper command. In keeping with the
remote helper protocol, only one "lock" line will ever be written;
the rest will result in warnings to stderr. However, in practice,
warnings will never be written because the remote-curl.c "fetch" is
only used for protocol v0/v1 (which will not generate multiple .keep
files). (Protocol v2 uses the "stateless-connect" command, not the
"fetch" command.)
- connected.c has an optimization in that connectivity checks on a ref
need not be done if the target object is in a pack known to be
self-contained and connected. If there are multiple packfiles, this
optimization can no longer be done.
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-06-11 04:57:22 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < transport->pack_lockfiles.nr; i++)
|
fetch: fix deadlock when cleaning up lockfiles in async signals
When fetching packfiles, we write a bunch of lockfiles for the packfiles
we're writing into the repository. In order to not leave behind any
cruft in case we exit or receive a signal, we register both an exit
handler as well as signal handlers for common signals like SIGINT. These
handlers will then unlink the locks and free the data structure tracking
them. We have observed a deadlock in this logic though:
(gdb) bt
#0 __lll_lock_wait_private () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95
#1 0x00007f4932bea2cd in _int_free (av=0x7f4932f2eb20 <main_arena>, p=0x3e3e4200, have_lock=0) at malloc.c:3969
#2 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#3 0x0000000000662ab1 in string_list_clear ()
#4 0x000000000044f5bc in unlock_pack_on_signal ()
#5 <signal handler called>
#6 _int_free (av=0x7f4932f2eb20 <main_arena>, p=<optimized out>, have_lock=0) at malloc.c:4024
#7 0x00007f4932bee58c in __GI___libc_free (mem=<optimized out>) at malloc.c:2975
#8 0x000000000065afd5 in strbuf_release ()
#9 0x000000000066ddb9 in delete_tempfile ()
#10 0x0000000000610d0b in files_transaction_cleanup.isra ()
#11 0x0000000000611718 in files_transaction_abort ()
#12 0x000000000060d2ef in ref_transaction_abort ()
#13 0x000000000060d441 in ref_transaction_prepare ()
#14 0x000000000060e0b5 in ref_transaction_commit ()
#15 0x00000000004511c2 in fetch_and_consume_refs ()
#16 0x000000000045279a in cmd_fetch ()
#17 0x0000000000407c48 in handle_builtin ()
#18 0x0000000000408df2 in cmd_main ()
#19 0x00000000004078b5 in main ()
The process was killed with a signal, which caused the signal handler to
kick in and try free the data structures after we have unlinked the
locks. It then deadlocks while calling free(3P).
The root cause of this is that it is not allowed to call certain
functions in async-signal handlers, as specified by signal-safety(7).
Next to most I/O functions, this list of disallowed functions also
includes memory-handling functions like malloc(3P) and free(3P) because
they may not be reentrant. As a result, if we execute such functions in
the signal handler, then they may operate on inconistent state and fail
in unexpected ways.
Fix this bug by not calling non-async-signal-safe functions when running
in the signal handler. We're about to re-raise the signal anyway and
will thus exit, so it's not much of a problem to keep the string list of
lockfiles untouched. Note that it's fine though to call unlink(2), so
we'll still clean up the lockfiles correctly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-07 18:55:47 +08:00
|
|
|
if (in_signal_handler)
|
|
|
|
unlink(transport->pack_lockfiles.items[i].string);
|
|
|
|
else
|
|
|
|
unlink_or_warn(transport->pack_lockfiles.items[i].string);
|
|
|
|
if (!in_signal_handler)
|
|
|
|
string_list_clear(&transport->pack_lockfiles, 0);
|
2007-09-14 15:31:23 +08:00
|
|
|
}
|
|
|
|
|
2009-12-09 23:26:33 +08:00
|
|
|
int transport_connect(struct transport *transport, const char *name,
|
|
|
|
const char *exec, int fd[2])
|
|
|
|
{
|
2017-12-15 05:44:45 +08:00
|
|
|
if (transport->vtable->connect)
|
|
|
|
return transport->vtable->connect(transport, name, exec, fd);
|
2009-12-09 23:26:33 +08:00
|
|
|
else
|
2018-07-21 15:49:40 +08:00
|
|
|
die(_("operation not supported by protocol"));
|
2009-12-09 23:26:33 +08:00
|
|
|
}
|
|
|
|
|
2007-09-11 11:03:04 +08:00
|
|
|
int transport_disconnect(struct transport *transport)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
2017-12-15 05:44:45 +08:00
|
|
|
if (transport->vtable->disconnect)
|
|
|
|
ret = transport->vtable->disconnect(transport);
|
2021-03-22 00:58:37 +08:00
|
|
|
if (transport->got_remote_refs)
|
|
|
|
free_refs((void *)transport->remote_refs);
|
2007-09-11 11:03:04 +08:00
|
|
|
free(transport);
|
|
|
|
return ret;
|
|
|
|
}
|
2009-04-17 16:20:11 +08:00
|
|
|
|
|
|
|
/*
|
2012-03-28 16:41:54 +08:00
|
|
|
* Strip username (and password) from a URL and return
|
2009-04-17 16:20:11 +08:00
|
|
|
* it in a newly allocated string.
|
|
|
|
*/
|
|
|
|
char *transport_anonymize_url(const char *url)
|
|
|
|
{
|
2016-02-23 06:45:05 +08:00
|
|
|
char *scheme_prefix, *anon_part;
|
2009-04-17 16:20:11 +08:00
|
|
|
size_t anon_len, prefix_len = 0;
|
|
|
|
|
|
|
|
anon_part = strchr(url, '@');
|
2013-11-29 03:50:03 +08:00
|
|
|
if (url_is_local_not_ssh(url) || !anon_part)
|
2009-04-17 16:20:11 +08:00
|
|
|
goto literal_copy;
|
|
|
|
|
|
|
|
anon_len = strlen(++anon_part);
|
|
|
|
scheme_prefix = strstr(url, "://");
|
|
|
|
if (!scheme_prefix) {
|
|
|
|
if (!strchr(anon_part, ':'))
|
|
|
|
/* cannot be "me@there:/path/name" */
|
|
|
|
goto literal_copy;
|
|
|
|
} else {
|
|
|
|
const char *cp;
|
|
|
|
/* make sure scheme is reasonable */
|
|
|
|
for (cp = url; cp < scheme_prefix; cp++) {
|
|
|
|
switch (*cp) {
|
|
|
|
/* RFC 1738 2.1 */
|
|
|
|
case '+': case '.': case '-':
|
|
|
|
break; /* ok */
|
|
|
|
default:
|
|
|
|
if (isalnum(*cp))
|
|
|
|
break;
|
|
|
|
/* it isn't */
|
|
|
|
goto literal_copy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* @ past the first slash does not count */
|
|
|
|
cp = strchr(scheme_prefix + 3, '/');
|
|
|
|
if (cp && cp < anon_part)
|
|
|
|
goto literal_copy;
|
|
|
|
prefix_len = scheme_prefix - url + 3;
|
|
|
|
}
|
2016-02-23 06:45:05 +08:00
|
|
|
return xstrfmt("%.*s%.*s", (int)prefix_len, url,
|
|
|
|
(int)anon_len, anon_part);
|
2009-04-17 16:20:11 +08:00
|
|
|
literal_copy:
|
|
|
|
return xstrdup(url);
|
|
|
|
}
|