2009-08-05 05:01:53 +00:00
|
|
|
#include "cache.h"
|
|
|
|
#include "transport.h"
|
2009-10-31 00:47:30 +00:00
|
|
|
#include "quote.h"
|
2009-08-05 05:01:53 +00:00
|
|
|
#include "run-command.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "revision.h"
|
2009-11-18 01:42:28 +00:00
|
|
|
#include "remote.h"
|
2010-03-29 16:48:27 +00:00
|
|
|
#include "string-list.h"
|
2010-11-17 17:15:34 +00:00
|
|
|
#include "thread-utils.h"
|
disconnect from remote helpers more gently
When git spawns a remote helper program (like git-remote-http),
the last thing we do before closing the pipe to the child
process is to send a blank line, telling the helper that we
are done issuing commands. However, the helper may already
have exited, in which case the parent git process will
receive SIGPIPE and die.
In particular, this can happen with the remote-curl helper
when it encounters errors during a push. The helper reports
individual errors for each ref back to git-push, and then
exits with a non-zero exit code. Depending on the exact
timing of the write, the parent process may or may not
receive SIGPIPE.
This causes intermittent test failure in t5541.8, and is a
side effect of 5238cbf (remote-curl: Fix push status report
when all branches fail). Before that commit, remote-curl
would not send the final blank line to indicate that the
list of status lines was complete; it would just exit,
closing the pipe. The parent git-push would notice the
closed pipe while reading the status report and exit
immediately itself, propagating the failing exit code. But
post-5238cbf, remote-curl completes the status list before
exiting, git-push actually runs to completion, and then it
tries to cleanly disconnect the helper, leading to the
SIGPIPE race above.
This patch drops all error-checking when sending the final
"we are about to hang up" blank line to helpers. There is
nothing useful for the parent process to do about errors at
that point anyway, and certainly failing to send our "we are
done with commands" line to a helper that has already exited
is not a problem.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-23 10:04:34 +00:00
|
|
|
#include "sigchain.h"
|
2012-09-19 15:21:19 +00:00
|
|
|
#include "argv-array.h"
|
2013-04-18 04:14:33 +00:00
|
|
|
#include "refs.h"
|
2017-12-14 21:44:45 +00:00
|
|
|
#include "transport-internal.h"
|
2018-03-15 17:31:34 +00:00
|
|
|
#include "protocol.h"
|
2010-11-17 17:15:34 +00:00
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
static int debug;
|
|
|
|
|
2011-03-16 07:08:34 +00:00
|
|
|
struct helper_data {
|
2009-08-05 05:01:53 +00:00
|
|
|
const char *name;
|
|
|
|
struct child_process *helper;
|
2009-10-31 00:47:28 +00:00
|
|
|
FILE *out;
|
2009-10-31 00:47:29 +00:00
|
|
|
unsigned fetch : 1,
|
2009-12-07 06:40:16 +00:00
|
|
|
import : 1,
|
2012-09-19 15:21:19 +00:00
|
|
|
bidi_import : 1,
|
2010-03-29 16:48:27 +00:00
|
|
|
export : 1,
|
2009-10-31 00:47:30 +00:00
|
|
|
option : 1,
|
2009-12-09 15:26:32 +00:00
|
|
|
push : 1,
|
|
|
|
connect : 1,
|
2018-03-15 17:31:34 +00:00
|
|
|
stateless_connect : 1,
|
2013-04-14 10:57:08 +00:00
|
|
|
signed_tags : 1,
|
2013-07-21 08:18:05 +00:00
|
|
|
check_connectivity : 1,
|
2013-09-03 15:45:14 +00:00
|
|
|
no_disconnect_req : 1,
|
|
|
|
no_private_update : 1;
|
2011-07-16 13:03:40 +00:00
|
|
|
char *export_marks;
|
|
|
|
char *import_marks;
|
2009-11-18 01:42:28 +00:00
|
|
|
/* These go from remote name (as in "list") to private name */
|
|
|
|
struct refspec *refspecs;
|
|
|
|
int refspec_nr;
|
2009-12-09 15:26:31 +00:00
|
|
|
/* Transport options for fetch-pack/send-pack (should one of
|
|
|
|
* those be invoked).
|
|
|
|
*/
|
|
|
|
struct git_transport_options transport_options;
|
2009-08-05 05:01:53 +00:00
|
|
|
};
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
static void sendline(struct helper_data *helper, struct strbuf *buffer)
|
|
|
|
{
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
|
avoid "write_in_full(fd, buf, len) != len" pattern
The return value of write_in_full() is either "-1", or the
requested number of bytes[1]. If we make a partial write
before seeing an error, we still return -1, not a partial
value. This goes back to f6aa66cb95 (write_in_full: really
write in full or return error on disk full., 2007-01-11).
So checking anything except "was the return value negative"
is pointless. And there are a couple of reasons not to do
so:
1. It can do a funny signed/unsigned comparison. If your
"len" is signed (e.g., a size_t) then the compiler will
promote the "-1" to its unsigned variant.
This works out for "!= len" (unless you really were
trying to write the maximum size_t bytes), but is a
bug if you check "< len" (an example of which was fixed
recently in config.c).
We should avoid promoting the mental model that you
need to check the length at all, so that new sites are
not tempted to copy us.
2. Checking for a negative value is shorter to type,
especially when the length is an expression.
3. Linus says so. In d34cf19b89 (Clean up write_in_full()
users, 2007-01-11), right after the write_in_full()
semantics were changed, he wrote:
I really wish every "write_in_full()" user would just
check against "<0" now, but this fixes the nasty and
stupid ones.
Appeals to authority aside, this makes it clear that
writing it this way does not have an intentional
benefit. It's a historical curiosity that we never
bothered to clean up (and which was undoubtedly
cargo-culted into new sites).
So let's convert these obviously-correct cases (this
includes write_str_in_full(), which is just a wrapper for
write_in_full()).
[1] A careful reader may notice there is one way that
write_in_full() can return a different value. If we ask
write() to write N bytes and get a return value that is
_larger_ than N, we could return a larger total. But
besides the fact that this would imply a totally broken
version of write(), it would already invoke undefined
behavior. Our internal remaining counter is an unsigned
size_t, which means that subtracting too many byte will
wrap it around to a very large number. So we'll instantly
begin reading off the end of the buffer, trying to write
gigabytes (or petabytes) of data.
Signed-off-by: Jeff King <peff@peff.net>
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-09-13 17:16:03 +00:00
|
|
|
if (write_in_full(helper->helper->in, buffer->buf, buffer->len) < 0)
|
2009-12-09 15:26:27 +00:00
|
|
|
die_errno("Full write to remote helper failed");
|
|
|
|
}
|
|
|
|
|
2018-03-15 17:31:32 +00:00
|
|
|
static int recvline_fh(FILE *helper, struct strbuf *buffer)
|
2009-12-09 15:26:27 +00:00
|
|
|
{
|
|
|
|
strbuf_reset(buffer);
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Remote helper: Waiting...\n");
|
2015-10-28 20:36:00 +00:00
|
|
|
if (strbuf_getline(buffer, helper) == EOF) {
|
2009-12-09 15:26:27 +00:00
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Remote helper quit.\n");
|
2014-04-12 20:33:29 +00:00
|
|
|
return 1;
|
2009-12-09 15:26:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-09 15:26:32 +00:00
|
|
|
static int recvline(struct helper_data *helper, struct strbuf *buffer)
|
|
|
|
{
|
2018-03-15 17:31:32 +00:00
|
|
|
return recvline_fh(helper->out, buffer);
|
2009-12-09 15:26:32 +00:00
|
|
|
}
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
static void write_constant(int fd, const char *str)
|
|
|
|
{
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Remote helper: -> %s", str);
|
avoid "write_in_full(fd, buf, len) != len" pattern
The return value of write_in_full() is either "-1", or the
requested number of bytes[1]. If we make a partial write
before seeing an error, we still return -1, not a partial
value. This goes back to f6aa66cb95 (write_in_full: really
write in full or return error on disk full., 2007-01-11).
So checking anything except "was the return value negative"
is pointless. And there are a couple of reasons not to do
so:
1. It can do a funny signed/unsigned comparison. If your
"len" is signed (e.g., a size_t) then the compiler will
promote the "-1" to its unsigned variant.
This works out for "!= len" (unless you really were
trying to write the maximum size_t bytes), but is a
bug if you check "< len" (an example of which was fixed
recently in config.c).
We should avoid promoting the mental model that you
need to check the length at all, so that new sites are
not tempted to copy us.
2. Checking for a negative value is shorter to type,
especially when the length is an expression.
3. Linus says so. In d34cf19b89 (Clean up write_in_full()
users, 2007-01-11), right after the write_in_full()
semantics were changed, he wrote:
I really wish every "write_in_full()" user would just
check against "<0" now, but this fixes the nasty and
stupid ones.
Appeals to authority aside, this makes it clear that
writing it this way does not have an intentional
benefit. It's a historical curiosity that we never
bothered to clean up (and which was undoubtedly
cargo-culted into new sites).
So let's convert these obviously-correct cases (this
includes write_str_in_full(), which is just a wrapper for
write_in_full()).
[1] A careful reader may notice there is one way that
write_in_full() can return a different value. If we ask
write() to write N bytes and get a return value that is
_larger_ than N, we could return a larger total. But
besides the fact that this would imply a totally broken
version of write(), it would already invoke undefined
behavior. Our internal remaining counter is an unsigned
size_t, which means that subtracting too many byte will
wrap it around to a very large number. So we'll instantly
begin reading off the end of the buffer, trying to write
gigabytes (or petabytes) of data.
Signed-off-by: Jeff King <peff@peff.net>
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-09-13 17:16:03 +00:00
|
|
|
if (write_in_full(fd, str, strlen(str)) < 0)
|
2009-12-09 15:26:27 +00:00
|
|
|
die_errno("Full write to remote helper failed");
|
|
|
|
}
|
|
|
|
|
Fix sparse warnings
Fix warnings from 'make check'.
- These files don't include 'builtin.h' causing sparse to complain that
cmd_* isn't declared:
builtin/clone.c:364, builtin/fetch-pack.c:797,
builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78,
builtin/merge-index.c:69, builtin/merge-recursive.c:22
builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426
builtin/notes.c:822, builtin/pack-redundant.c:596,
builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149,
builtin/remote.c:1512, builtin/remote-ext.c:240,
builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384,
builtin/unpack-file.c:25, builtin/var.c:75
- These files have symbols which should be marked static since they're
only file scope:
submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13,
submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79,
unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123,
url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48
- These files redeclare symbols to be different types:
builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571,
usage.c:49, usage.c:58, usage.c:63, usage.c:72
- These files use a literal integer 0 when they really should use a NULL
pointer:
daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362
While we're in the area, clean up some unused #includes in builtin files
(mostly exec_cmd.h).
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 07:51:05 +00:00
|
|
|
static const char *remove_ext_force(const char *url)
|
2009-12-09 15:26:29 +00:00
|
|
|
{
|
|
|
|
if (url) {
|
|
|
|
const char *colon = strchr(url, ':');
|
|
|
|
if (colon && colon[1] == ':')
|
|
|
|
return colon + 2;
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2009-12-09 15:26:32 +00:00
|
|
|
static void do_take_over(struct transport *transport)
|
|
|
|
{
|
|
|
|
struct helper_data *data;
|
|
|
|
data = (struct helper_data *)transport->data;
|
|
|
|
transport_take_over(transport, data->helper);
|
|
|
|
fclose(data->out);
|
|
|
|
free(data);
|
|
|
|
}
|
|
|
|
|
2015-02-13 05:24:45 +00:00
|
|
|
static void standard_options(struct transport *t);
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
static struct child_process *get_helper(struct transport *transport)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
struct child_process *helper;
|
2009-11-18 01:42:28 +00:00
|
|
|
const char **refspecs = NULL;
|
|
|
|
int refspec_nr = 0;
|
|
|
|
int refspec_alloc = 0;
|
2009-12-09 15:26:31 +00:00
|
|
|
int duped;
|
2010-01-12 19:53:29 +00:00
|
|
|
int code;
|
2009-08-05 05:01:53 +00:00
|
|
|
|
|
|
|
if (data->helper)
|
|
|
|
return data->helper;
|
|
|
|
|
2014-08-19 19:10:48 +00:00
|
|
|
helper = xmalloc(sizeof(*helper));
|
|
|
|
child_process_init(helper);
|
2009-08-05 05:01:53 +00:00
|
|
|
helper->in = -1;
|
|
|
|
helper->out = -1;
|
|
|
|
helper->err = 0;
|
2014-05-15 08:34:18 +00:00
|
|
|
argv_array_pushf(&helper->args, "git-remote-%s", data->name);
|
|
|
|
argv_array_push(&helper->args, transport->remote->name);
|
|
|
|
argv_array_push(&helper->args, remove_ext_force(transport->url));
|
2010-01-12 19:53:29 +00:00
|
|
|
helper->git_cmd = 0;
|
|
|
|
helper->silent_exec_failure = 1;
|
2011-07-16 13:03:28 +00:00
|
|
|
|
2017-02-14 20:36:19 +00:00
|
|
|
if (have_git_dir())
|
|
|
|
argv_array_pushf(&helper->env_array, "%s=%s",
|
|
|
|
GIT_DIR_ENVIRONMENT, get_git_dir());
|
2011-07-16 13:03:28 +00:00
|
|
|
|
2010-01-12 19:53:29 +00:00
|
|
|
code = start_command(helper);
|
|
|
|
if (code < 0 && errno == ENOENT)
|
|
|
|
die("Unable to find remote helper for '%s'", data->name);
|
|
|
|
else if (code != 0)
|
|
|
|
exit(code);
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
data->helper = helper;
|
2009-12-09 15:26:32 +00:00
|
|
|
data->no_disconnect_req = 0;
|
2009-08-05 05:01:53 +00:00
|
|
|
|
2009-12-09 15:26:31 +00:00
|
|
|
/*
|
2016-01-14 02:32:23 +00:00
|
|
|
* Open the output as FILE* so strbuf_getline_*() family of
|
|
|
|
* functions can be used.
|
2009-12-09 15:26:31 +00:00
|
|
|
* Do this with duped fd because fclose() will close the fd,
|
|
|
|
* and stuff like taking over will require the fd to remain.
|
|
|
|
*/
|
|
|
|
duped = dup(helper->out);
|
|
|
|
if (duped < 0)
|
|
|
|
die_errno("Can't dup helper output fd");
|
|
|
|
data->out = xfdopen(duped, "r");
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
write_constant(helper->in, "capabilities\n");
|
2009-09-04 02:13:51 +00:00
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
while (1) {
|
2014-06-18 19:47:17 +00:00
|
|
|
const char *capname, *arg;
|
2009-12-09 15:26:28 +00:00
|
|
|
int mandatory = 0;
|
2014-04-12 20:33:29 +00:00
|
|
|
if (recvline(data, &buf))
|
|
|
|
exit(128);
|
2009-08-05 05:01:53 +00:00
|
|
|
|
|
|
|
if (!*buf.buf)
|
|
|
|
break;
|
2009-12-09 15:26:28 +00:00
|
|
|
|
|
|
|
if (*buf.buf == '*') {
|
|
|
|
capname = buf.buf + 1;
|
|
|
|
mandatory = 1;
|
|
|
|
} else
|
|
|
|
capname = buf.buf;
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
if (debug)
|
2009-12-09 15:26:28 +00:00
|
|
|
fprintf(stderr, "Debug: Got cap %s\n", capname);
|
|
|
|
if (!strcmp(capname, "fetch"))
|
2009-08-05 05:01:53 +00:00
|
|
|
data->fetch = 1;
|
2009-12-09 15:26:28 +00:00
|
|
|
else if (!strcmp(capname, "option"))
|
2009-10-31 00:47:29 +00:00
|
|
|
data->option = 1;
|
2009-12-09 15:26:28 +00:00
|
|
|
else if (!strcmp(capname, "push"))
|
2009-10-31 00:47:30 +00:00
|
|
|
data->push = 1;
|
2009-12-09 15:26:28 +00:00
|
|
|
else if (!strcmp(capname, "import"))
|
2009-11-18 01:42:27 +00:00
|
|
|
data->import = 1;
|
2012-09-19 15:21:19 +00:00
|
|
|
else if (!strcmp(capname, "bidi-import"))
|
|
|
|
data->bidi_import = 1;
|
2010-03-29 16:48:27 +00:00
|
|
|
else if (!strcmp(capname, "export"))
|
|
|
|
data->export = 1;
|
2013-07-21 08:18:05 +00:00
|
|
|
else if (!strcmp(capname, "check-connectivity"))
|
|
|
|
data->check_connectivity = 1;
|
2014-06-18 19:47:17 +00:00
|
|
|
else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) {
|
2009-11-18 01:42:28 +00:00
|
|
|
ALLOC_GROW(refspecs,
|
|
|
|
refspec_nr + 1,
|
|
|
|
refspec_alloc);
|
2014-06-18 19:47:17 +00:00
|
|
|
refspecs[refspec_nr++] = xstrdup(arg);
|
2009-12-09 15:26:32 +00:00
|
|
|
} else if (!strcmp(capname, "connect")) {
|
|
|
|
data->connect = 1;
|
2018-03-15 17:31:34 +00:00
|
|
|
} else if (!strcmp(capname, "stateless-connect")) {
|
|
|
|
data->stateless_connect = 1;
|
2013-04-14 10:57:08 +00:00
|
|
|
} else if (!strcmp(capname, "signed-tags")) {
|
|
|
|
data->signed_tags = 1;
|
2014-06-18 19:47:17 +00:00
|
|
|
} else if (skip_prefix(capname, "export-marks ", &arg)) {
|
|
|
|
data->export_marks = xstrdup(arg);
|
|
|
|
} else if (skip_prefix(capname, "import-marks ", &arg)) {
|
|
|
|
data->import_marks = xstrdup(arg);
|
2013-11-30 20:55:40 +00:00
|
|
|
} else if (starts_with(capname, "no-private-update")) {
|
2013-09-03 15:45:14 +00:00
|
|
|
data->no_private_update = 1;
|
2009-12-09 15:26:28 +00:00
|
|
|
} else if (mandatory) {
|
2010-02-04 05:23:18 +00:00
|
|
|
die("Unknown mandatory capability %s. This remote "
|
2012-04-30 00:28:45 +00:00
|
|
|
"helper probably needs newer version of Git.",
|
2009-12-09 15:26:28 +00:00
|
|
|
capname);
|
2009-11-18 01:42:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (refspecs) {
|
|
|
|
int i;
|
|
|
|
data->refspec_nr = refspec_nr;
|
|
|
|
data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
|
2013-05-09 01:16:56 +00:00
|
|
|
for (i = 0; i < refspec_nr; i++)
|
2009-11-18 01:42:28 +00:00
|
|
|
free((char *)refspecs[i]);
|
|
|
|
free(refspecs);
|
2013-04-18 04:14:31 +00:00
|
|
|
} else if (data->import || data->bidi_import || data->export) {
|
|
|
|
warning("This remote helper should implement refspec capability.");
|
2009-08-05 05:01:53 +00:00
|
|
|
}
|
2009-11-18 01:42:29 +00:00
|
|
|
strbuf_release(&buf);
|
2009-12-09 15:26:27 +00:00
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Capabilities complete.\n");
|
2015-02-13 05:24:45 +00:00
|
|
|
standard_options(transport);
|
2009-08-05 05:01:53 +00:00
|
|
|
return data->helper;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int disconnect_helper(struct transport *transport)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
2011-07-16 13:03:35 +00:00
|
|
|
int res = 0;
|
2009-12-09 15:26:27 +00:00
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
if (data->helper) {
|
2009-12-09 15:26:27 +00:00
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Disconnecting.\n");
|
2009-12-09 15:26:32 +00:00
|
|
|
if (!data->no_disconnect_req) {
|
disconnect from remote helpers more gently
When git spawns a remote helper program (like git-remote-http),
the last thing we do before closing the pipe to the child
process is to send a blank line, telling the helper that we
are done issuing commands. However, the helper may already
have exited, in which case the parent git process will
receive SIGPIPE and die.
In particular, this can happen with the remote-curl helper
when it encounters errors during a push. The helper reports
individual errors for each ref back to git-push, and then
exits with a non-zero exit code. Depending on the exact
timing of the write, the parent process may or may not
receive SIGPIPE.
This causes intermittent test failure in t5541.8, and is a
side effect of 5238cbf (remote-curl: Fix push status report
when all branches fail). Before that commit, remote-curl
would not send the final blank line to indicate that the
list of status lines was complete; it would just exit,
closing the pipe. The parent git-push would notice the
closed pipe while reading the status report and exit
immediately itself, propagating the failing exit code. But
post-5238cbf, remote-curl completes the status list before
exiting, git-push actually runs to completion, and then it
tries to cleanly disconnect the helper, leading to the
SIGPIPE race above.
This patch drops all error-checking when sending the final
"we are about to hang up" blank line to helpers. There is
nothing useful for the parent process to do about errors at
that point anyway, and certainly failing to send our "we are
done with commands" line to a helper that has already exited
is not a problem.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-23 10:04:34 +00:00
|
|
|
/*
|
|
|
|
* Ignore write errors; there's nothing we can do,
|
|
|
|
* since we're about to close the pipe anyway. And the
|
|
|
|
* most likely error is EPIPE due to the helper dying
|
|
|
|
* to report an error itself.
|
|
|
|
*/
|
|
|
|
sigchain_push(SIGPIPE, SIG_IGN);
|
|
|
|
xwrite(data->helper->in, "\n", 1);
|
|
|
|
sigchain_pop(SIGPIPE);
|
2009-12-09 15:26:32 +00:00
|
|
|
}
|
2009-08-05 05:01:53 +00:00
|
|
|
close(data->helper->in);
|
2009-12-09 15:26:31 +00:00
|
|
|
close(data->helper->out);
|
2009-10-31 00:47:28 +00:00
|
|
|
fclose(data->out);
|
2011-07-16 13:03:35 +00:00
|
|
|
res = finish_command(data->helper);
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(data->helper);
|
2009-08-05 05:01:53 +00:00
|
|
|
}
|
2011-07-16 13:03:35 +00:00
|
|
|
return res;
|
2009-08-05 05:01:53 +00:00
|
|
|
}
|
|
|
|
|
2009-10-31 00:47:29 +00:00
|
|
|
static const char *unsupported_options[] = {
|
|
|
|
TRANS_OPT_UPLOADPACK,
|
|
|
|
TRANS_OPT_RECEIVEPACK,
|
|
|
|
TRANS_OPT_THIN,
|
|
|
|
TRANS_OPT_KEEP
|
|
|
|
};
|
2013-10-31 09:25:40 +00:00
|
|
|
|
2009-10-31 00:47:29 +00:00
|
|
|
static const char *boolean_options[] = {
|
|
|
|
TRANS_OPT_THIN,
|
|
|
|
TRANS_OPT_KEEP,
|
2014-09-15 21:59:00 +00:00
|
|
|
TRANS_OPT_FOLLOWTAGS,
|
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 10:54:09 +00:00
|
|
|
TRANS_OPT_DEEPEN_RELATIVE
|
2009-10-31 00:47:29 +00:00
|
|
|
};
|
|
|
|
|
2016-06-12 10:53:44 +00:00
|
|
|
static int strbuf_set_helper_option(struct helper_data *data,
|
|
|
|
struct strbuf *buf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
sendline(data, buf);
|
|
|
|
if (recvline(data, buf))
|
|
|
|
exit(128);
|
|
|
|
|
|
|
|
if (!strcmp(buf->buf, "ok"))
|
|
|
|
ret = 0;
|
|
|
|
else if (starts_with(buf->buf, "error"))
|
|
|
|
ret = -1;
|
|
|
|
else if (!strcmp(buf->buf, "unsupported"))
|
|
|
|
ret = 1;
|
|
|
|
else {
|
|
|
|
warning("%s unexpectedly said: '%s'", data->name, buf->buf);
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-06-12 10:54:04 +00:00
|
|
|
static int string_list_set_helper_option(struct helper_data *data,
|
|
|
|
const char *name,
|
|
|
|
struct string_list *list)
|
|
|
|
{
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
int i, ret = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < list->nr; i++) {
|
|
|
|
strbuf_addf(&buf, "option %s ", name);
|
|
|
|
quote_c_style(list->items[i].string, &buf, NULL, 0);
|
|
|
|
strbuf_addch(&buf, '\n');
|
|
|
|
|
|
|
|
if ((ret = strbuf_set_helper_option(data, &buf)))
|
|
|
|
break;
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
}
|
|
|
|
strbuf_release(&buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-10-31 00:47:29 +00:00
|
|
|
static int set_helper_option(struct transport *transport,
|
|
|
|
const char *name, const char *value)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
int i, ret, is_bool = 0;
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
get_helper(transport);
|
|
|
|
|
2009-10-31 00:47:29 +00:00
|
|
|
if (!data->option)
|
|
|
|
return 1;
|
|
|
|
|
2016-06-12 10:54:04 +00:00
|
|
|
if (!strcmp(name, "deepen-not"))
|
|
|
|
return string_list_set_helper_option(data, name,
|
|
|
|
(struct string_list *)value);
|
|
|
|
|
2009-10-31 00:47:29 +00:00
|
|
|
for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
|
|
|
|
if (!strcmp(name, unsupported_options[i]))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {
|
|
|
|
if (!strcmp(name, boolean_options[i])) {
|
|
|
|
is_bool = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_addf(&buf, "option %s ", name);
|
|
|
|
if (is_bool)
|
|
|
|
strbuf_addstr(&buf, value ? "true" : "false");
|
|
|
|
else
|
|
|
|
quote_c_style(value, &buf, NULL, 0);
|
|
|
|
strbuf_addch(&buf, '\n');
|
|
|
|
|
2016-06-12 10:53:44 +00:00
|
|
|
ret = strbuf_set_helper_option(data, &buf);
|
2009-10-31 00:47:29 +00:00
|
|
|
strbuf_release(&buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void standard_options(struct transport *t)
|
|
|
|
{
|
|
|
|
char buf[16];
|
|
|
|
int v = t->verbose;
|
|
|
|
|
2010-02-24 12:50:26 +00:00
|
|
|
set_helper_option(t, "progress", t->progress ? "true" : "false");
|
2009-10-31 00:47:29 +00:00
|
|
|
|
2017-03-28 19:47:00 +00:00
|
|
|
xsnprintf(buf, sizeof(buf), "%d", v + 1);
|
2009-10-31 00:47:29 +00:00
|
|
|
set_helper_option(t, "verbosity", buf);
|
2016-02-03 04:09:14 +00:00
|
|
|
|
|
|
|
switch (t->family) {
|
|
|
|
case TRANSPORT_FAMILY_ALL:
|
|
|
|
/*
|
|
|
|
* this is already the default,
|
|
|
|
* do not break old remote helpers by setting "all" here
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
case TRANSPORT_FAMILY_IPV4:
|
|
|
|
set_helper_option(t, "family", "ipv4");
|
|
|
|
break;
|
|
|
|
case TRANSPORT_FAMILY_IPV6:
|
|
|
|
set_helper_option(t, "family", "ipv6");
|
|
|
|
break;
|
|
|
|
}
|
2009-10-31 00:47:29 +00:00
|
|
|
}
|
|
|
|
|
2009-11-18 01:42:21 +00:00
|
|
|
static int release_helper(struct transport *transport)
|
|
|
|
{
|
2011-07-16 13:03:35 +00:00
|
|
|
int res = 0;
|
2009-11-18 01:42:28 +00:00
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
free_refspec(data->refspec_nr, data->refspecs);
|
|
|
|
data->refspecs = NULL;
|
2011-07-16 13:03:35 +00:00
|
|
|
res = disconnect_helper(transport);
|
2009-11-18 01:42:21 +00:00
|
|
|
free(transport->data);
|
2011-07-16 13:03:35 +00:00
|
|
|
return res;
|
2009-11-18 01:42:21 +00:00
|
|
|
}
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
static int fetch_with_fetch(struct transport *transport,
|
2009-11-18 01:42:24 +00:00
|
|
|
int nr_heads, struct ref **to_fetch)
|
2009-08-05 05:01:53 +00:00
|
|
|
{
|
2009-10-31 00:47:28 +00:00
|
|
|
struct helper_data *data = transport->data;
|
2009-08-05 05:01:53 +00:00
|
|
|
int i;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
|
|
|
for (i = 0; i < nr_heads; i++) {
|
2009-08-05 05:01:59 +00:00
|
|
|
const struct ref *posn = to_fetch[i];
|
2009-08-05 05:01:53 +00:00
|
|
|
if (posn->status & REF_STATUS_UPTODATE)
|
|
|
|
continue;
|
2009-09-04 02:13:51 +00:00
|
|
|
|
|
|
|
strbuf_addf(&buf, "fetch %s %s\n",
|
2015-11-10 02:22:20 +00:00
|
|
|
oid_to_hex(&posn->old_oid),
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
posn->symref ? posn->symref : posn->name);
|
2009-10-31 00:47:28 +00:00
|
|
|
}
|
2009-09-04 02:13:51 +00:00
|
|
|
|
2009-10-31 00:47:28 +00:00
|
|
|
strbuf_addch(&buf, '\n');
|
2009-12-09 15:26:27 +00:00
|
|
|
sendline(data, &buf);
|
2009-10-31 00:47:28 +00:00
|
|
|
|
|
|
|
while (1) {
|
2014-04-12 20:33:29 +00:00
|
|
|
if (recvline(data, &buf))
|
|
|
|
exit(128);
|
2009-10-31 00:47:28 +00:00
|
|
|
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(buf.buf, "lock ")) {
|
2009-10-31 00:47:28 +00:00
|
|
|
const char *name = buf.buf + 5;
|
|
|
|
if (transport->pack_lockfile)
|
|
|
|
warning("%s also locked %s", data->name, name);
|
|
|
|
else
|
|
|
|
transport->pack_lockfile = xstrdup(name);
|
|
|
|
}
|
2013-07-21 08:18:05 +00:00
|
|
|
else if (data->check_connectivity &&
|
|
|
|
data->transport_options.check_self_contained_and_connected &&
|
|
|
|
!strcmp(buf.buf, "connectivity-ok"))
|
|
|
|
data->transport_options.self_contained_and_connected = 1;
|
2009-10-31 00:47:28 +00:00
|
|
|
else if (!buf.len)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
warning("%s unexpectedly said: '%s'", data->name, buf.buf);
|
2009-08-05 05:01:53 +00:00
|
|
|
}
|
2009-10-31 00:47:28 +00:00
|
|
|
strbuf_release(&buf);
|
2009-08-05 05:01:53 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-11-18 01:42:27 +00:00
|
|
|
static int get_importer(struct transport *transport, struct child_process *fastimport)
|
|
|
|
{
|
|
|
|
struct child_process *helper = get_helper(transport);
|
2012-09-19 15:21:19 +00:00
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
int cat_blob_fd, code;
|
2014-08-19 19:10:48 +00:00
|
|
|
child_process_init(fastimport);
|
2009-11-18 01:42:27 +00:00
|
|
|
fastimport->in = helper->out;
|
2014-05-15 08:35:06 +00:00
|
|
|
argv_array_push(&fastimport->args, "fast-import");
|
|
|
|
argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
|
2009-11-18 01:42:27 +00:00
|
|
|
|
2012-09-19 15:21:19 +00:00
|
|
|
if (data->bidi_import) {
|
|
|
|
cat_blob_fd = xdup(helper->in);
|
2014-05-15 08:35:06 +00:00
|
|
|
argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd);
|
2012-09-19 15:21:19 +00:00
|
|
|
}
|
2009-11-18 01:42:27 +00:00
|
|
|
fastimport->git_cmd = 1;
|
2012-09-19 15:21:19 +00:00
|
|
|
|
|
|
|
code = start_command(fastimport);
|
|
|
|
return code;
|
2009-11-18 01:42:27 +00:00
|
|
|
}
|
|
|
|
|
2010-03-29 16:48:27 +00:00
|
|
|
static int get_exporter(struct transport *transport,
|
|
|
|
struct child_process *fastexport,
|
|
|
|
struct string_list *revlist_args)
|
|
|
|
{
|
2011-07-16 13:03:40 +00:00
|
|
|
struct helper_data *data = transport->data;
|
2010-03-29 16:48:27 +00:00
|
|
|
struct child_process *helper = get_helper(transport);
|
2014-05-15 08:34:44 +00:00
|
|
|
int i;
|
2014-04-12 20:33:31 +00:00
|
|
|
|
2014-10-28 20:52:34 +00:00
|
|
|
child_process_init(fastexport);
|
2010-03-29 16:48:27 +00:00
|
|
|
|
|
|
|
/* we need to duplicate helper->in because we want to use it after
|
|
|
|
* fastexport is done with it. */
|
|
|
|
fastexport->out = dup(helper->in);
|
2014-05-15 08:34:44 +00:00
|
|
|
argv_array_push(&fastexport->args, "fast-export");
|
|
|
|
argv_array_push(&fastexport->args, "--use-done-feature");
|
|
|
|
argv_array_push(&fastexport->args, data->signed_tags ?
|
|
|
|
"--signed-tags=verbatim" : "--signed-tags=warn-strip");
|
|
|
|
if (data->export_marks)
|
|
|
|
argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks);
|
|
|
|
if (data->import_marks)
|
|
|
|
argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks);
|
2010-03-29 16:48:27 +00:00
|
|
|
|
|
|
|
for (i = 0; i < revlist_args->nr; i++)
|
2014-05-15 08:34:44 +00:00
|
|
|
argv_array_push(&fastexport->args, revlist_args->items[i].string);
|
2010-03-29 16:48:27 +00:00
|
|
|
|
|
|
|
fastexport->git_cmd = 1;
|
|
|
|
return start_command(fastexport);
|
|
|
|
}
|
|
|
|
|
2009-11-18 01:42:27 +00:00
|
|
|
static int fetch_with_import(struct transport *transport,
|
|
|
|
int nr_heads, struct ref **to_fetch)
|
|
|
|
{
|
|
|
|
struct child_process fastimport;
|
2009-11-18 01:42:28 +00:00
|
|
|
struct helper_data *data = transport->data;
|
2009-11-18 01:42:27 +00:00
|
|
|
int i;
|
|
|
|
struct ref *posn;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
get_helper(transport);
|
|
|
|
|
2009-11-18 01:42:27 +00:00
|
|
|
if (get_importer(transport, &fastimport))
|
|
|
|
die("Couldn't run fast-import");
|
|
|
|
|
|
|
|
for (i = 0; i < nr_heads; i++) {
|
|
|
|
posn = to_fetch[i];
|
|
|
|
if (posn->status & REF_STATUS_UPTODATE)
|
|
|
|
continue;
|
|
|
|
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
strbuf_addf(&buf, "import %s\n",
|
|
|
|
posn->symref ? posn->symref : posn->name);
|
2009-12-09 15:26:27 +00:00
|
|
|
sendline(data, &buf);
|
2009-11-18 01:42:27 +00:00
|
|
|
strbuf_reset(&buf);
|
|
|
|
}
|
2011-07-16 13:03:38 +00:00
|
|
|
|
|
|
|
write_constant(data->helper->in, "\n");
|
2012-09-19 15:21:19 +00:00
|
|
|
/*
|
|
|
|
* remote-helpers that advertise the bidi-import capability are required to
|
|
|
|
* buffer the complete batch of import commands until this newline before
|
|
|
|
* sending data to fast-import.
|
|
|
|
* These helpers read back data from fast-import on their stdin, which could
|
|
|
|
* be mixed with import commands, otherwise.
|
|
|
|
*/
|
2011-07-16 13:03:38 +00:00
|
|
|
|
2011-07-16 13:03:35 +00:00
|
|
|
if (finish_command(&fastimport))
|
|
|
|
die("Error while running fast-import");
|
2009-11-18 01:42:27 +00:00
|
|
|
|
2012-07-30 14:31:18 +00:00
|
|
|
/*
|
|
|
|
* The fast-import stream of a remote helper that advertises
|
|
|
|
* the "refspec" capability writes to the refs named after the
|
|
|
|
* right hand side of the first refspec matching each ref we
|
|
|
|
* were fetching.
|
|
|
|
*
|
|
|
|
* (If no "refspec" capability was specified, for historical
|
2013-04-18 04:14:28 +00:00
|
|
|
* reasons we default to the equivalent of *:*.)
|
2012-07-30 14:31:18 +00:00
|
|
|
*
|
|
|
|
* Store the result in to_fetch[i].old_sha1. Callers such
|
|
|
|
* as "git fetch" can use the value to write feedback to the
|
|
|
|
* terminal, populate FETCH_HEAD, and determine what new value
|
|
|
|
* should be written to peer_ref if the update is a
|
|
|
|
* fast-forward or this is a forced update.
|
|
|
|
*/
|
2009-11-18 01:42:27 +00:00
|
|
|
for (i = 0; i < nr_heads; i++) {
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
char *private, *name;
|
2009-11-18 01:42:27 +00:00
|
|
|
posn = to_fetch[i];
|
|
|
|
if (posn->status & REF_STATUS_UPTODATE)
|
|
|
|
continue;
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
name = posn->symref ? posn->symref : posn->name;
|
2009-11-18 01:42:28 +00:00
|
|
|
if (data->refspecs)
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
private = apply_refspecs(data->refspecs, data->refspec_nr, name);
|
2009-11-18 01:42:28 +00:00
|
|
|
else
|
transport-helper: do not request symbolic refs to remote helpers
A typical remote helper will return a `list` of refs containing a symbolic
ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all
the refs are being requested through `fetch` or `import`, including the
symbolic ref.
While this works properly, in some cases of a fetch, like `git fetch url`
or `git fetch origin HEAD`, or any fetch command involving a symbolic ref
without also fetching the corresponding ref it points to, the fetch command
fails with:
fatal: bad object 0000000000000000000000000000000000000000
error: <remote> did not send all necessary objects
(in the case the remote helper returned '?' values to the `list` command).
This is because there is only one ref given to fetch(), and it's not
further resolved to something at the end of fetch_with_import().
While this can be somehow handled in the remote helper itself, by adding
a refspec for the symbolic ref, and storing an explicit ref in a private
namespace, and then handling the `import` for that symbolic ref
specifically, very few existing remote helpers are actually doing that.
So, instead of requesting the exact list of wanted refs to remote helpers,
treat symbolic refs differently and request the ref they point to instead.
Then, resolve the symbolic refs values based on the pointed ref.
This assumes there is no more than one level of indirection (a symbolic
ref doesn't point to another symbolic ref).
Signed-off-by: Mike Hommey <mh@glandium.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-01-19 01:35:07 +00:00
|
|
|
private = xstrdup(name);
|
2011-09-15 21:10:38 +00:00
|
|
|
if (private) {
|
2017-10-15 22:06:56 +00:00
|
|
|
if (read_ref(private, &posn->old_oid) < 0)
|
2015-07-31 23:57:57 +00:00
|
|
|
die("Could not read ref %s", private);
|
2011-09-15 21:10:38 +00:00
|
|
|
free(private);
|
|
|
|
}
|
2009-11-18 01:42:27 +00:00
|
|
|
}
|
2009-11-18 01:42:29 +00:00
|
|
|
strbuf_release(&buf);
|
2009-11-18 01:42:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-15 17:31:33 +00:00
|
|
|
static int run_connect(struct transport *transport, struct strbuf *cmdbuf)
|
2009-12-09 15:26:32 +00:00
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
2018-03-15 17:31:33 +00:00
|
|
|
int ret = 0;
|
|
|
|
int duped;
|
2009-12-09 15:26:32 +00:00
|
|
|
FILE *input;
|
2018-03-15 17:31:33 +00:00
|
|
|
struct child_process *helper;
|
2009-12-09 15:26:32 +00:00
|
|
|
|
|
|
|
helper = get_helper(transport);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Yes, dup the pipe another time, as we need unbuffered version
|
|
|
|
* of input pipe as FILE*. fclose() closes the underlying fd and
|
|
|
|
* stream buffering only can be changed before first I/O operation
|
|
|
|
* on it.
|
|
|
|
*/
|
|
|
|
duped = dup(helper->out);
|
|
|
|
if (duped < 0)
|
|
|
|
die_errno("Can't dup helper output fd");
|
|
|
|
input = xfdopen(duped, "r");
|
|
|
|
setvbuf(input, NULL, _IONBF, 0);
|
|
|
|
|
2018-03-15 17:31:33 +00:00
|
|
|
sendline(data, cmdbuf);
|
|
|
|
if (recvline_fh(input, cmdbuf))
|
|
|
|
exit(128);
|
|
|
|
|
|
|
|
if (!strcmp(cmdbuf->buf, "")) {
|
|
|
|
data->no_disconnect_req = 1;
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Smart transport connection "
|
|
|
|
"ready.\n");
|
|
|
|
ret = 1;
|
|
|
|
} else if (!strcmp(cmdbuf->buf, "fallback")) {
|
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Falling back to dumb "
|
|
|
|
"transport.\n");
|
|
|
|
} else {
|
|
|
|
die("Unknown response to connect: %s",
|
|
|
|
cmdbuf->buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(input);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int process_connect_service(struct transport *transport,
|
|
|
|
const char *name, const char *exec)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
struct strbuf cmdbuf = STRBUF_INIT;
|
|
|
|
int ret = 0;
|
|
|
|
|
2009-12-09 15:26:32 +00:00
|
|
|
/*
|
|
|
|
* Handle --upload-pack and friends. This is fire and forget...
|
|
|
|
* just warn if it fails.
|
|
|
|
*/
|
|
|
|
if (strcmp(name, exec)) {
|
2018-03-15 17:31:33 +00:00
|
|
|
int r = set_helper_option(transport, "servpath", exec);
|
2009-12-09 15:26:32 +00:00
|
|
|
if (r > 0)
|
|
|
|
warning("Setting remote service path not supported by protocol.");
|
|
|
|
else if (r < 0)
|
|
|
|
warning("Invalid remote service path.");
|
|
|
|
}
|
|
|
|
|
2018-03-15 17:31:33 +00:00
|
|
|
if (data->connect) {
|
2009-12-09 15:26:32 +00:00
|
|
|
strbuf_addf(&cmdbuf, "connect %s\n", name);
|
2018-03-15 17:31:33 +00:00
|
|
|
ret = run_connect(transport, &cmdbuf);
|
2018-03-15 17:31:34 +00:00
|
|
|
} else if (data->stateless_connect &&
|
|
|
|
(get_protocol_version_config() == protocol_v2) &&
|
|
|
|
!strcmp("git-upload-pack", name)) {
|
|
|
|
strbuf_addf(&cmdbuf, "stateless-connect %s\n", name);
|
|
|
|
ret = run_connect(transport, &cmdbuf);
|
|
|
|
if (ret)
|
|
|
|
transport->stateless_rpc = 1;
|
2018-03-15 17:31:33 +00:00
|
|
|
}
|
2009-12-09 15:26:32 +00:00
|
|
|
|
2017-08-30 18:20:15 +00:00
|
|
|
strbuf_release(&cmdbuf);
|
2009-12-09 15:26:32 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int process_connect(struct transport *transport,
|
|
|
|
int for_push)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
const char *name;
|
|
|
|
const char *exec;
|
|
|
|
|
|
|
|
name = for_push ? "git-receive-pack" : "git-upload-pack";
|
|
|
|
if (for_push)
|
|
|
|
exec = data->transport_options.receivepack;
|
|
|
|
else
|
|
|
|
exec = data->transport_options.uploadpack;
|
|
|
|
|
|
|
|
return process_connect_service(transport, name, exec);
|
|
|
|
}
|
|
|
|
|
2009-12-09 15:26:33 +00:00
|
|
|
static int connect_helper(struct transport *transport, const char *name,
|
|
|
|
const char *exec, int fd[2])
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
|
|
|
|
/* Get_helper so connect is inited. */
|
|
|
|
get_helper(transport);
|
|
|
|
if (!data->connect)
|
|
|
|
die("Operation not supported by protocol.");
|
|
|
|
|
|
|
|
if (!process_connect_service(transport, name, exec))
|
|
|
|
die("Can't connect to subservice %s.", name);
|
|
|
|
|
|
|
|
fd[0] = data->helper->out;
|
|
|
|
fd[1] = data->helper->in;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
static int fetch(struct transport *transport,
|
2009-11-18 01:42:24 +00:00
|
|
|
int nr_heads, struct ref **to_fetch)
|
2009-08-05 05:01:53 +00:00
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
int i, count;
|
|
|
|
|
2009-12-09 15:26:32 +00:00
|
|
|
if (process_connect(transport, 0)) {
|
|
|
|
do_take_over(transport);
|
2017-12-14 21:44:45 +00:00
|
|
|
return transport->vtable->fetch(transport, nr_heads, to_fetch);
|
2009-12-09 15:26:32 +00:00
|
|
|
}
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
count = 0;
|
|
|
|
for (i = 0; i < nr_heads; i++)
|
|
|
|
if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
|
|
|
|
count++;
|
|
|
|
|
|
|
|
if (!count)
|
|
|
|
return 0;
|
|
|
|
|
2015-02-13 05:24:46 +00:00
|
|
|
if (data->check_connectivity &&
|
|
|
|
data->transport_options.check_self_contained_and_connected)
|
|
|
|
set_helper_option(transport, "check-connectivity", "true");
|
|
|
|
|
|
|
|
if (transport->cloning)
|
|
|
|
set_helper_option(transport, "cloning", "true");
|
|
|
|
|
|
|
|
if (data->transport_options.update_shallow)
|
|
|
|
set_helper_option(transport, "update-shallow", "true");
|
|
|
|
|
2017-12-08 15:58:40 +00:00
|
|
|
if (data->transport_options.filter_options.choice)
|
|
|
|
set_helper_option(
|
|
|
|
transport, "filter",
|
|
|
|
data->transport_options.filter_options.filter_spec);
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
if (data->fetch)
|
|
|
|
return fetch_with_fetch(transport, nr_heads, to_fetch);
|
|
|
|
|
2009-11-18 01:42:27 +00:00
|
|
|
if (data->import)
|
|
|
|
return fetch_with_import(transport, nr_heads, to_fetch);
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-04-18 04:14:33 +00:00
|
|
|
static int push_update_ref_status(struct strbuf *buf,
|
2011-07-16 13:03:34 +00:00
|
|
|
struct ref **ref,
|
|
|
|
struct ref *remote_refs)
|
|
|
|
{
|
|
|
|
char *refname, *msg;
|
2013-11-12 20:56:57 +00:00
|
|
|
int status, forced = 0;
|
2011-07-16 13:03:34 +00:00
|
|
|
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(buf->buf, "ok ")) {
|
2011-07-16 13:03:34 +00:00
|
|
|
status = REF_STATUS_OK;
|
|
|
|
refname = buf->buf + 3;
|
2013-11-30 20:55:40 +00:00
|
|
|
} else if (starts_with(buf->buf, "error ")) {
|
2011-07-16 13:03:34 +00:00
|
|
|
status = REF_STATUS_REMOTE_REJECT;
|
|
|
|
refname = buf->buf + 6;
|
|
|
|
} else
|
2012-04-30 00:28:45 +00:00
|
|
|
die("expected ok/error, helper said '%s'", buf->buf);
|
2011-07-16 13:03:34 +00:00
|
|
|
|
|
|
|
msg = strchr(refname, ' ');
|
|
|
|
if (msg) {
|
|
|
|
struct strbuf msg_buf = STRBUF_INIT;
|
|
|
|
const char *end;
|
|
|
|
|
|
|
|
*msg++ = '\0';
|
|
|
|
if (!unquote_c_style(&msg_buf, msg, &end))
|
|
|
|
msg = strbuf_detach(&msg_buf, NULL);
|
|
|
|
else
|
|
|
|
msg = xstrdup(msg);
|
|
|
|
strbuf_release(&msg_buf);
|
|
|
|
|
|
|
|
if (!strcmp(msg, "no match")) {
|
|
|
|
status = REF_STATUS_NONE;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
else if (!strcmp(msg, "up to date")) {
|
|
|
|
status = REF_STATUS_UPTODATE;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
else if (!strcmp(msg, "non-fast forward")) {
|
|
|
|
status = REF_STATUS_REJECT_NONFASTFORWARD;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
2012-11-30 01:41:37 +00:00
|
|
|
else if (!strcmp(msg, "already exists")) {
|
|
|
|
status = REF_STATUS_REJECT_ALREADY_EXISTS;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2012-11-30 01:41:37 +00:00
|
|
|
}
|
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-23 21:55:30 +00:00
|
|
|
else if (!strcmp(msg, "fetch first")) {
|
|
|
|
status = REF_STATUS_REJECT_FETCH_FIRST;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
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-23 21:55:30 +00:00
|
|
|
}
|
|
|
|
else if (!strcmp(msg, "needs force")) {
|
|
|
|
status = REF_STATUS_REJECT_NEEDS_FORCE;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
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-23 21:55:30 +00:00
|
|
|
}
|
2013-07-08 21:42:40 +00:00
|
|
|
else if (!strcmp(msg, "stale info")) {
|
|
|
|
status = REF_STATUS_REJECT_STALE;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2013-07-08 21:42:40 +00:00
|
|
|
}
|
2013-11-12 20:56:57 +00:00
|
|
|
else if (!strcmp(msg, "forced update")) {
|
|
|
|
forced = 1;
|
2017-06-15 23:15:46 +00:00
|
|
|
FREE_AND_NULL(msg);
|
2013-11-12 20:56:57 +00:00
|
|
|
}
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (*ref)
|
|
|
|
*ref = find_ref_by_name(*ref, refname);
|
|
|
|
if (!*ref)
|
|
|
|
*ref = find_ref_by_name(remote_refs, refname);
|
|
|
|
if (!*ref) {
|
|
|
|
warning("helper reported unexpected status of %s", refname);
|
2013-04-18 04:14:33 +00:00
|
|
|
return 1;
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((*ref)->status != REF_STATUS_NONE) {
|
|
|
|
/*
|
|
|
|
* Earlier, the ref was marked not to be pushed, so ignore the ref
|
|
|
|
* status reported by the remote helper if the latter is 'no match'.
|
|
|
|
*/
|
|
|
|
if (status == REF_STATUS_NONE)
|
2013-04-18 04:14:33 +00:00
|
|
|
return 1;
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
(*ref)->status = status;
|
2014-02-21 09:55:59 +00:00
|
|
|
(*ref)->forced_update |= forced;
|
2011-07-16 13:03:34 +00:00
|
|
|
(*ref)->remote_status = msg;
|
2013-05-10 12:08:30 +00:00
|
|
|
return !(status == REF_STATUS_OK);
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
|
2014-04-12 20:33:30 +00:00
|
|
|
static int push_update_refs_status(struct helper_data *data,
|
2013-10-31 09:36:37 +00:00
|
|
|
struct ref *remote_refs,
|
|
|
|
int flags)
|
2011-07-16 13:03:34 +00:00
|
|
|
{
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
struct ref *ref = remote_refs;
|
2014-04-12 20:33:30 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
2011-07-16 13:03:34 +00:00
|
|
|
for (;;) {
|
2013-04-18 04:14:33 +00:00
|
|
|
char *private;
|
|
|
|
|
2014-04-12 20:33:30 +00:00
|
|
|
if (recvline(data, &buf)) {
|
|
|
|
ret = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-07-16 13:03:34 +00:00
|
|
|
if (!buf.len)
|
|
|
|
break;
|
|
|
|
|
2013-04-18 04:14:33 +00:00
|
|
|
if (push_update_ref_status(&buf, &ref, remote_refs))
|
|
|
|
continue;
|
|
|
|
|
2013-10-31 09:36:37 +00:00
|
|
|
if (flags & TRANSPORT_PUSH_DRY_RUN || !data->refspecs || data->no_private_update)
|
2013-04-18 04:14:33 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* propagate back the update to the remote namespace */
|
|
|
|
private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
|
|
|
|
if (!private)
|
|
|
|
continue;
|
2017-10-15 22:06:51 +00:00
|
|
|
update_ref("update by helper", private, &ref->new_oid, NULL,
|
|
|
|
0, 0);
|
2013-04-18 04:14:33 +00:00
|
|
|
free(private);
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
strbuf_release(&buf);
|
2014-04-12 20:33:30 +00:00
|
|
|
return ret;
|
2011-07-16 13:03:34 +00:00
|
|
|
}
|
|
|
|
|
2015-08-19 15:26:46 +00:00
|
|
|
static void set_common_push_options(struct transport *transport,
|
|
|
|
const char *name, int flags)
|
|
|
|
{
|
|
|
|
if (flags & TRANSPORT_PUSH_DRY_RUN) {
|
|
|
|
if (set_helper_option(transport, "dry-run", "true") != 0)
|
|
|
|
die("helper %s does not support dry-run", name);
|
|
|
|
} else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {
|
|
|
|
if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
|
|
|
|
die("helper %s does not support --signed", name);
|
|
|
|
} else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) {
|
|
|
|
if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0)
|
|
|
|
die("helper %s does not support --signed=if-asked", name);
|
|
|
|
}
|
2017-02-08 22:04:00 +00:00
|
|
|
|
|
|
|
if (flags & TRANSPORT_PUSH_OPTIONS) {
|
|
|
|
struct string_list_item *item;
|
|
|
|
for_each_string_list_item(item, transport->push_options)
|
|
|
|
if (set_helper_option(transport, "push-option", item->string) != 0)
|
|
|
|
die("helper %s does not support 'push-option'", name);
|
|
|
|
}
|
2015-08-19 15:26:46 +00:00
|
|
|
}
|
|
|
|
|
2010-03-29 16:48:27 +00:00
|
|
|
static int push_refs_with_push(struct transport *transport,
|
2013-08-02 22:14:50 +00:00
|
|
|
struct ref *remote_refs, int flags)
|
2009-10-31 00:47:30 +00:00
|
|
|
{
|
|
|
|
int force_all = flags & TRANSPORT_PUSH_FORCE;
|
|
|
|
int mirror = flags & TRANSPORT_PUSH_MIRROR;
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
struct ref *ref;
|
2013-08-02 22:14:50 +00:00
|
|
|
struct string_list cas_options = STRING_LIST_INIT_DUP;
|
|
|
|
struct string_list_item *cas_option;
|
2009-10-31 00:47:30 +00:00
|
|
|
|
2011-03-22 12:50:08 +00:00
|
|
|
get_helper(transport);
|
2009-10-31 00:47:30 +00:00
|
|
|
if (!data->push)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
for (ref = remote_refs; ref; ref = ref->next) {
|
2010-01-08 02:12:42 +00:00
|
|
|
if (!ref->peer_ref && !mirror)
|
2009-10-31 00:47:30 +00:00
|
|
|
continue;
|
|
|
|
|
2010-01-08 02:12:42 +00:00
|
|
|
/* Check for statuses set by set_ref_status_for_push() */
|
|
|
|
switch (ref->status) {
|
|
|
|
case REF_STATUS_REJECT_NONFASTFORWARD:
|
2013-07-08 21:42:40 +00:00
|
|
|
case REF_STATUS_REJECT_STALE:
|
2012-11-30 01:41:37 +00:00
|
|
|
case REF_STATUS_REJECT_ALREADY_EXISTS:
|
2010-01-08 02:12:42 +00:00
|
|
|
case REF_STATUS_UPTODATE:
|
2009-10-31 00:47:30 +00:00
|
|
|
continue;
|
2010-01-08 02:12:42 +00:00
|
|
|
default:
|
|
|
|
; /* do nothing */
|
2009-10-31 00:47:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (force_all)
|
|
|
|
ref->force = 1;
|
|
|
|
|
|
|
|
strbuf_addstr(&buf, "push ");
|
|
|
|
if (!ref->deletion) {
|
|
|
|
if (ref->force)
|
|
|
|
strbuf_addch(&buf, '+');
|
|
|
|
if (ref->peer_ref)
|
|
|
|
strbuf_addstr(&buf, ref->peer_ref->name);
|
|
|
|
else
|
2015-11-10 02:22:20 +00:00
|
|
|
strbuf_addstr(&buf, oid_to_hex(&ref->new_oid));
|
2009-10-31 00:47:30 +00:00
|
|
|
}
|
|
|
|
strbuf_addch(&buf, ':');
|
|
|
|
strbuf_addstr(&buf, ref->name);
|
|
|
|
strbuf_addch(&buf, '\n');
|
2013-08-02 22:14:50 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The "--force-with-lease" options without explicit
|
|
|
|
* values to expect have already been expanded into
|
2015-11-10 02:22:20 +00:00
|
|
|
* the ref->old_oid_expect[] field; we can ignore
|
2013-08-02 22:14:50 +00:00
|
|
|
* transport->smart_options->cas altogether and instead
|
|
|
|
* can enumerate them from the refs.
|
|
|
|
*/
|
|
|
|
if (ref->expect_old_sha1) {
|
|
|
|
struct strbuf cas = STRBUF_INIT;
|
|
|
|
strbuf_addf(&cas, "%s:%s",
|
2015-11-10 02:22:20 +00:00
|
|
|
ref->name, oid_to_hex(&ref->old_oid_expect));
|
2017-12-08 17:29:31 +00:00
|
|
|
string_list_append_nodup(&cas_options,
|
|
|
|
strbuf_detach(&cas, NULL));
|
2013-08-02 22:14:50 +00:00
|
|
|
}
|
2009-10-31 00:47:30 +00:00
|
|
|
}
|
2013-08-02 22:14:50 +00:00
|
|
|
if (buf.len == 0) {
|
|
|
|
string_list_clear(&cas_options, 0);
|
2009-10-31 00:47:31 +00:00
|
|
|
return 0;
|
2013-08-02 22:14:50 +00:00
|
|
|
}
|
2009-10-31 00:47:30 +00:00
|
|
|
|
2013-08-02 22:14:50 +00:00
|
|
|
for_each_string_list_item(cas_option, &cas_options)
|
|
|
|
set_helper_option(transport, "cas", cas_option->string);
|
2015-08-19 15:26:46 +00:00
|
|
|
set_common_push_options(transport, data->name, flags);
|
2009-10-31 00:47:30 +00:00
|
|
|
|
|
|
|
strbuf_addch(&buf, '\n');
|
2009-12-09 15:26:27 +00:00
|
|
|
sendline(data, &buf);
|
2009-10-31 00:47:30 +00:00
|
|
|
strbuf_release(&buf);
|
2017-12-08 17:29:31 +00:00
|
|
|
string_list_clear(&cas_options, 0);
|
2011-07-16 13:03:34 +00:00
|
|
|
|
2014-04-12 20:33:30 +00:00
|
|
|
return push_update_refs_status(data, remote_refs, flags);
|
2009-10-31 00:47:30 +00:00
|
|
|
}
|
|
|
|
|
2010-03-29 16:48:27 +00:00
|
|
|
static int push_refs_with_export(struct transport *transport,
|
|
|
|
struct ref *remote_refs, int flags)
|
|
|
|
{
|
|
|
|
struct ref *ref;
|
|
|
|
struct child_process *helper, exporter;
|
|
|
|
struct helper_data *data = transport->data;
|
2014-04-20 18:59:25 +00:00
|
|
|
struct string_list revlist_args = STRING_LIST_INIT_DUP;
|
2010-03-29 16:48:27 +00:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
2013-04-18 04:14:30 +00:00
|
|
|
if (!data->refspecs)
|
|
|
|
die("remote-helper doesn't support push; refspec needed");
|
|
|
|
|
2015-08-19 15:26:46 +00:00
|
|
|
set_common_push_options(transport, data->name, flags);
|
2013-11-12 20:56:56 +00:00
|
|
|
if (flags & TRANSPORT_PUSH_FORCE) {
|
|
|
|
if (set_helper_option(transport, "force", "true") != 0)
|
|
|
|
warning("helper %s does not support 'force'", data->name);
|
|
|
|
}
|
|
|
|
|
2010-03-29 16:48:27 +00:00
|
|
|
helper = get_helper(transport);
|
|
|
|
|
|
|
|
write_constant(helper->in, "export\n");
|
|
|
|
|
|
|
|
for (ref = remote_refs; ref; ref = ref->next) {
|
|
|
|
char *private;
|
2015-11-10 02:22:24 +00:00
|
|
|
struct object_id oid;
|
2010-03-29 16:48:27 +00:00
|
|
|
|
|
|
|
private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
|
sha1_name: convert get_sha1* to get_oid*
Now that all the callers of get_sha1 directly or indirectly use struct
object_id, rename the functions starting with get_sha1 to start with
get_oid. Convert the internals in sha1_name.c to use struct object_id
as well, and eliminate explicit length checks where possible. Convert a
use of 40 in get_oid_basic to GIT_SHA1_HEXSZ.
Outside of sha1_name.c and cache.h, this transition was made with the
following semantic patch:
@@
expression E1, E2;
@@
- get_sha1(E1, E2.hash)
+ get_oid(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1(E1, E2->hash)
+ get_oid(E1, E2)
@@
expression E1, E2;
@@
- get_sha1_committish(E1, E2.hash)
+ get_oid_committish(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1_committish(E1, E2->hash)
+ get_oid_committish(E1, E2)
@@
expression E1, E2;
@@
- get_sha1_treeish(E1, E2.hash)
+ get_oid_treeish(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1_treeish(E1, E2->hash)
+ get_oid_treeish(E1, E2)
@@
expression E1, E2;
@@
- get_sha1_commit(E1, E2.hash)
+ get_oid_commit(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1_commit(E1, E2->hash)
+ get_oid_commit(E1, E2)
@@
expression E1, E2;
@@
- get_sha1_tree(E1, E2.hash)
+ get_oid_tree(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1_tree(E1, E2->hash)
+ get_oid_tree(E1, E2)
@@
expression E1, E2;
@@
- get_sha1_blob(E1, E2.hash)
+ get_oid_blob(E1, &E2)
@@
expression E1, E2;
@@
- get_sha1_blob(E1, E2->hash)
+ get_oid_blob(E1, E2)
@@
expression E1, E2, E3, E4;
@@
- get_sha1_with_context(E1, E2, E3.hash, E4)
+ get_oid_with_context(E1, E2, &E3, E4)
@@
expression E1, E2, E3, E4;
@@
- get_sha1_with_context(E1, E2, E3->hash, E4)
+ get_oid_with_context(E1, E2, E3, E4)
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 23:49:28 +00:00
|
|
|
if (private && !get_oid(private, &oid)) {
|
2010-03-29 16:48:27 +00:00
|
|
|
strbuf_addf(&buf, "^%s", private);
|
2017-12-08 17:29:31 +00:00
|
|
|
string_list_append_nodup(&revlist_args,
|
|
|
|
strbuf_detach(&buf, NULL));
|
2015-11-10 02:22:24 +00:00
|
|
|
oidcpy(&ref->old_oid, &oid);
|
2010-03-29 16:48:27 +00:00
|
|
|
}
|
2011-07-16 13:03:21 +00:00
|
|
|
free(private);
|
2010-03-29 16:48:27 +00:00
|
|
|
|
2013-05-21 01:02:45 +00:00
|
|
|
if (ref->peer_ref) {
|
2014-04-20 18:59:25 +00:00
|
|
|
if (strcmp(ref->name, ref->peer_ref->name)) {
|
2014-04-20 18:59:29 +00:00
|
|
|
if (!ref->deletion) {
|
|
|
|
const char *name;
|
|
|
|
int flag;
|
|
|
|
|
|
|
|
/* Follow symbolic refs (mainly for HEAD). */
|
refs: convert resolve_ref_unsafe to struct object_id
Convert resolve_ref_unsafe to take a pointer to struct object_id by
converting one remaining caller to use struct object_id, removing the
temporary NULL pointer check in expand_ref, converting the declaration
and definition, and applying the following semantic patch:
@@
expression E1, E2, E3, E4;
@@
- resolve_ref_unsafe(E1, E2, E3.hash, E4)
+ resolve_ref_unsafe(E1, E2, &E3, E4)
@@
expression E1, E2, E3, E4;
@@
- resolve_ref_unsafe(E1, E2, E3->hash, E4)
+ resolve_ref_unsafe(E1, E2, E3, E4)
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-10-15 22:07:09 +00:00
|
|
|
name = resolve_ref_unsafe(ref->peer_ref->name,
|
|
|
|
RESOLVE_REF_READING,
|
|
|
|
&oid, &flag);
|
2014-04-20 18:59:29 +00:00
|
|
|
if (!name || !(flag & REF_ISSYMREF))
|
|
|
|
name = ref->peer_ref->name;
|
2014-04-20 18:59:26 +00:00
|
|
|
|
2014-04-20 18:59:29 +00:00
|
|
|
strbuf_addf(&buf, "%s:%s", name, ref->name);
|
|
|
|
} else
|
|
|
|
strbuf_addf(&buf, ":%s", ref->name);
|
2014-04-20 18:59:26 +00:00
|
|
|
|
2014-04-20 18:59:25 +00:00
|
|
|
string_list_append(&revlist_args, "--refspec");
|
|
|
|
string_list_append(&revlist_args, buf.buf);
|
|
|
|
strbuf_release(&buf);
|
|
|
|
}
|
2014-04-20 18:59:29 +00:00
|
|
|
if (!ref->deletion)
|
|
|
|
string_list_append(&revlist_args, ref->peer_ref->name);
|
2013-05-21 01:02:45 +00:00
|
|
|
}
|
2010-03-29 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
2011-07-16 13:03:40 +00:00
|
|
|
if (get_exporter(transport, &exporter, &revlist_args))
|
2010-03-29 16:48:27 +00:00
|
|
|
die("Couldn't run fast-export");
|
|
|
|
|
2014-04-20 18:59:25 +00:00
|
|
|
string_list_clear(&revlist_args, 1);
|
|
|
|
|
2011-07-16 13:03:35 +00:00
|
|
|
if (finish_command(&exporter))
|
|
|
|
die("Error while running fast-export");
|
2014-04-12 20:33:32 +00:00
|
|
|
if (push_update_refs_status(data, remote_refs, flags))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (data->export_marks) {
|
|
|
|
strbuf_addf(&buf, "%s.tmp", data->export_marks);
|
|
|
|
rename(buf.buf, data->export_marks);
|
|
|
|
strbuf_release(&buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2010-03-29 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int push_refs(struct transport *transport,
|
|
|
|
struct ref *remote_refs, int flags)
|
|
|
|
{
|
|
|
|
struct helper_data *data = transport->data;
|
|
|
|
|
|
|
|
if (process_connect(transport, 1)) {
|
|
|
|
do_take_over(transport);
|
2017-12-14 21:44:45 +00:00
|
|
|
return transport->vtable->push_refs(transport, remote_refs, flags);
|
2010-03-29 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!remote_refs) {
|
|
|
|
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
|
|
|
|
"Perhaps you should specify a branch such as 'master'.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->push)
|
|
|
|
return push_refs_with_push(transport, remote_refs, flags);
|
|
|
|
|
|
|
|
if (data->export)
|
|
|
|
return push_refs_with_export(transport, remote_refs, flags);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-11-18 01:42:30 +00:00
|
|
|
static int has_attribute(const char *attrs, const char *attr) {
|
|
|
|
int len;
|
|
|
|
if (!attrs)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
len = strlen(attr);
|
|
|
|
for (;;) {
|
|
|
|
const char *space = strchrnul(attrs, ' ');
|
|
|
|
if (len == space - attrs && !strncmp(attrs, attr, len))
|
|
|
|
return 1;
|
|
|
|
if (!*space)
|
|
|
|
return 0;
|
|
|
|
attrs = space + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 17:31:22 +00:00
|
|
|
static struct ref *get_refs_list(struct transport *transport, int for_push,
|
|
|
|
const struct argv_array *ref_prefixes)
|
2009-08-05 05:01:53 +00:00
|
|
|
{
|
2009-10-31 00:47:28 +00:00
|
|
|
struct helper_data *data = transport->data;
|
2009-08-05 05:01:53 +00:00
|
|
|
struct child_process *helper;
|
|
|
|
struct ref *ret = NULL;
|
|
|
|
struct ref **tail = &ret;
|
|
|
|
struct ref *posn;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
|
|
|
helper = get_helper(transport);
|
2009-09-04 02:13:51 +00:00
|
|
|
|
2009-12-09 15:26:32 +00:00
|
|
|
if (process_connect(transport, for_push)) {
|
|
|
|
do_take_over(transport);
|
2018-03-15 17:31:22 +00:00
|
|
|
return transport->vtable->get_refs_list(transport, for_push, ref_prefixes);
|
2009-12-09 15:26:32 +00:00
|
|
|
}
|
|
|
|
|
2009-10-31 00:47:30 +00:00
|
|
|
if (data->push && for_push)
|
|
|
|
write_str_in_full(helper->in, "list for-push\n");
|
|
|
|
else
|
|
|
|
write_str_in_full(helper->in, "list\n");
|
2009-08-05 05:01:53 +00:00
|
|
|
|
|
|
|
while (1) {
|
|
|
|
char *eov, *eon;
|
2014-04-12 20:33:29 +00:00
|
|
|
if (recvline(data, &buf))
|
|
|
|
exit(128);
|
2009-08-05 05:01:53 +00:00
|
|
|
|
|
|
|
if (!*buf.buf)
|
|
|
|
break;
|
|
|
|
|
|
|
|
eov = strchr(buf.buf, ' ');
|
|
|
|
if (!eov)
|
|
|
|
die("Malformed response in ref list: %s", buf.buf);
|
|
|
|
eon = strchr(eov + 1, ' ');
|
|
|
|
*eov = '\0';
|
|
|
|
if (eon)
|
|
|
|
*eon = '\0';
|
|
|
|
*tail = alloc_ref(eov + 1);
|
|
|
|
if (buf.buf[0] == '@')
|
|
|
|
(*tail)->symref = xstrdup(buf.buf + 1);
|
|
|
|
else if (buf.buf[0] != '?')
|
2015-11-10 02:22:20 +00:00
|
|
|
get_oid_hex(buf.buf, &(*tail)->old_oid);
|
2009-11-18 01:42:30 +00:00
|
|
|
if (eon) {
|
|
|
|
if (has_attribute(eon + 1, "unchanged")) {
|
|
|
|
(*tail)->status |= REF_STATUS_UPTODATE;
|
2017-10-15 22:06:56 +00:00
|
|
|
if (read_ref((*tail)->name, &(*tail)->old_oid) < 0)
|
2016-06-17 20:21:13 +00:00
|
|
|
die(_("Could not read ref %s"),
|
2015-07-31 23:57:57 +00:00
|
|
|
(*tail)->name);
|
2009-11-18 01:42:30 +00:00
|
|
|
}
|
|
|
|
}
|
2009-08-05 05:01:53 +00:00
|
|
|
tail = &((*tail)->next);
|
|
|
|
}
|
2009-12-09 15:26:27 +00:00
|
|
|
if (debug)
|
|
|
|
fprintf(stderr, "Debug: Read ref listing.\n");
|
2009-08-05 05:01:53 +00:00
|
|
|
strbuf_release(&buf);
|
|
|
|
|
|
|
|
for (posn = ret; posn; posn = posn->next)
|
|
|
|
resolve_remote_symref(posn, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-12-14 21:44:45 +00:00
|
|
|
static struct transport_vtable vtable = {
|
|
|
|
set_helper_option,
|
|
|
|
get_refs_list,
|
|
|
|
fetch,
|
|
|
|
push_refs,
|
|
|
|
connect_helper,
|
|
|
|
release_helper
|
|
|
|
};
|
|
|
|
|
2009-09-04 02:13:49 +00:00
|
|
|
int transport_helper_init(struct transport *transport, const char *name)
|
2009-08-05 05:01:53 +00:00
|
|
|
{
|
2014-05-26 15:33:56 +00:00
|
|
|
struct helper_data *data = xcalloc(1, sizeof(*data));
|
2009-09-04 02:13:49 +00:00
|
|
|
data->name = name;
|
2009-08-05 05:01:53 +00:00
|
|
|
|
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-16 17:12:52 +00:00
|
|
|
transport_check_allowed(name);
|
|
|
|
|
2009-12-09 15:26:27 +00:00
|
|
|
if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
|
|
|
|
debug = 1;
|
|
|
|
|
2009-08-05 05:01:53 +00:00
|
|
|
transport->data = data;
|
2017-12-14 21:44:45 +00:00
|
|
|
transport->vtable = &vtable;
|
2009-12-09 15:26:31 +00:00
|
|
|
transport->smart_options = &(data->transport_options);
|
2009-08-05 05:01:53 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2010-10-12 16:39:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Linux pipes can buffer 65536 bytes at once (and most platforms can
|
|
|
|
* buffer less), so attempt reads and writes with up to that size.
|
|
|
|
*/
|
|
|
|
#define BUFFERSIZE 65536
|
|
|
|
/* This should be enough to hold debugging message. */
|
|
|
|
#define PBUFFERSIZE 8192
|
|
|
|
|
|
|
|
/* Print bidirectional transfer loop debug message. */
|
2013-07-10 00:18:40 +00:00
|
|
|
__attribute__((format (printf, 1, 2)))
|
2010-10-12 16:39:41 +00:00
|
|
|
static void transfer_debug(const char *fmt, ...)
|
|
|
|
{
|
2017-08-21 17:43:48 +00:00
|
|
|
/*
|
|
|
|
* NEEDSWORK: This function is sometimes used from multiple threads, and
|
|
|
|
* we end up using debug_enabled racily. That "should not matter" since
|
|
|
|
* we always write the same value, but it's still wrong. This function
|
|
|
|
* is listed in .tsan-suppressions for the time being.
|
|
|
|
*/
|
|
|
|
|
2010-10-12 16:39:41 +00:00
|
|
|
va_list args;
|
|
|
|
char msgbuf[PBUFFERSIZE];
|
|
|
|
static int debug_enabled = -1;
|
|
|
|
|
|
|
|
if (debug_enabled < 0)
|
|
|
|
debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0;
|
|
|
|
if (!debug_enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
vsnprintf(msgbuf, PBUFFERSIZE, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stream state: More data may be coming in this direction. */
|
2016-08-09 08:53:38 +00:00
|
|
|
#define SSTATE_TRANSFERRING 0
|
2010-10-12 16:39:41 +00:00
|
|
|
/*
|
|
|
|
* Stream state: No more data coming in this direction, flushing rest of
|
|
|
|
* data.
|
|
|
|
*/
|
|
|
|
#define SSTATE_FLUSHING 1
|
|
|
|
/* Stream state: Transfer in this direction finished. */
|
|
|
|
#define SSTATE_FINISHED 2
|
|
|
|
|
2016-08-09 08:53:38 +00:00
|
|
|
#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERRING)
|
2010-10-12 16:39:41 +00:00
|
|
|
#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
|
|
|
|
#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
|
|
|
|
|
|
|
|
/* Unidirectional transfer. */
|
|
|
|
struct unidirectional_transfer {
|
|
|
|
/* Source */
|
|
|
|
int src;
|
|
|
|
/* Destination */
|
|
|
|
int dest;
|
|
|
|
/* Is source socket? */
|
|
|
|
int src_is_sock;
|
|
|
|
/* Is destination socket? */
|
|
|
|
int dest_is_sock;
|
2013-04-11 22:36:10 +00:00
|
|
|
/* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */
|
2010-10-12 16:39:41 +00:00
|
|
|
int state;
|
|
|
|
/* Buffer. */
|
|
|
|
char buf[BUFFERSIZE];
|
|
|
|
/* Buffer used. */
|
|
|
|
size_t bufuse;
|
|
|
|
/* Name of source. */
|
|
|
|
const char *src_name;
|
|
|
|
/* Name of destination. */
|
|
|
|
const char *dest_name;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Closes the target (for writing) if transfer has finished. */
|
|
|
|
static void udt_close_if_finished(struct unidirectional_transfer *t)
|
|
|
|
{
|
|
|
|
if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) {
|
|
|
|
t->state = SSTATE_FINISHED;
|
|
|
|
if (t->dest_is_sock)
|
|
|
|
shutdown(t->dest, SHUT_WR);
|
|
|
|
else
|
|
|
|
close(t->dest);
|
|
|
|
transfer_debug("Closed %s.", t->dest_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2016-05-06 12:36:46 +00:00
|
|
|
* Tries to read data from source into buffer. If buffer is full,
|
2010-10-12 16:39:41 +00:00
|
|
|
* no data is read. Returns 0 on success, -1 on error.
|
|
|
|
*/
|
|
|
|
static int udt_do_read(struct unidirectional_transfer *t)
|
|
|
|
{
|
|
|
|
ssize_t bytes;
|
|
|
|
|
|
|
|
if (t->bufuse == BUFFERSIZE)
|
|
|
|
return 0; /* No space for more. */
|
|
|
|
|
|
|
|
transfer_debug("%s is readable", t->src_name);
|
|
|
|
bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
|
|
|
|
if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
|
|
|
|
errno != EINTR) {
|
2016-05-08 09:47:57 +00:00
|
|
|
error_errno("read(%s) failed", t->src_name);
|
2010-10-12 16:39:41 +00:00
|
|
|
return -1;
|
|
|
|
} else if (bytes == 0) {
|
|
|
|
transfer_debug("%s EOF (with %i bytes in buffer)",
|
2013-07-10 00:18:40 +00:00
|
|
|
t->src_name, (int)t->bufuse);
|
2010-10-12 16:39:41 +00:00
|
|
|
t->state = SSTATE_FLUSHING;
|
|
|
|
} else if (bytes > 0) {
|
|
|
|
t->bufuse += bytes;
|
|
|
|
transfer_debug("Read %i bytes from %s (buffer now at %i)",
|
|
|
|
(int)bytes, t->src_name, (int)t->bufuse);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Tries to write data from buffer into destination. If buffer is empty,
|
|
|
|
* no data is written. Returns 0 on success, -1 on error.
|
|
|
|
*/
|
|
|
|
static int udt_do_write(struct unidirectional_transfer *t)
|
|
|
|
{
|
2011-03-04 23:16:26 +00:00
|
|
|
ssize_t bytes;
|
2010-10-12 16:39:41 +00:00
|
|
|
|
|
|
|
if (t->bufuse == 0)
|
|
|
|
return 0; /* Nothing to write. */
|
|
|
|
|
|
|
|
transfer_debug("%s is writable", t->dest_name);
|
2014-01-17 14:17:09 +00:00
|
|
|
bytes = xwrite(t->dest, t->buf, t->bufuse);
|
|
|
|
if (bytes < 0 && errno != EWOULDBLOCK) {
|
2016-05-08 09:47:57 +00:00
|
|
|
error_errno("write(%s) failed", t->dest_name);
|
2010-10-12 16:39:41 +00:00
|
|
|
return -1;
|
|
|
|
} else if (bytes > 0) {
|
|
|
|
t->bufuse -= bytes;
|
|
|
|
if (t->bufuse)
|
|
|
|
memmove(t->buf, t->buf + bytes, t->bufuse);
|
|
|
|
transfer_debug("Wrote %i bytes to %s (buffer now at %i)",
|
|
|
|
(int)bytes, t->dest_name, (int)t->bufuse);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* State of bidirectional transfer loop. */
|
|
|
|
struct bidirectional_transfer_state {
|
|
|
|
/* Direction from program to git. */
|
|
|
|
struct unidirectional_transfer ptg;
|
|
|
|
/* Direction from git to program. */
|
|
|
|
struct unidirectional_transfer gtp;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *udt_copy_task_routine(void *udt)
|
|
|
|
{
|
|
|
|
struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt;
|
|
|
|
while (t->state != SSTATE_FINISHED) {
|
|
|
|
if (STATE_NEEDS_READING(t->state))
|
|
|
|
if (udt_do_read(t))
|
|
|
|
return NULL;
|
|
|
|
if (STATE_NEEDS_WRITING(t->state))
|
|
|
|
if (udt_do_write(t))
|
|
|
|
return NULL;
|
|
|
|
if (STATE_NEEDS_CLOSING(t->state))
|
|
|
|
udt_close_if_finished(t);
|
|
|
|
}
|
|
|
|
return udt; /* Just some non-NULL value. */
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef NO_PTHREADS
|
|
|
|
|
|
|
|
/*
|
2013-07-29 08:18:21 +00:00
|
|
|
* Join thread, with appropriate errors on failure. Name is name for the
|
2010-10-12 16:39:41 +00:00
|
|
|
* thread (for error messages). Returns 0 on success, 1 on failure.
|
|
|
|
*/
|
|
|
|
static int tloop_join(pthread_t thread, const char *name)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
void *tret;
|
|
|
|
err = pthread_join(thread, &tret);
|
|
|
|
if (!tret) {
|
|
|
|
error("%s thread failed", name);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
error("%s thread failed to join: %s", name, strerror(err));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Spawn the transfer tasks and then wait for them. Returns 0 on success,
|
|
|
|
* -1 on failure.
|
|
|
|
*/
|
|
|
|
static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
|
|
|
|
{
|
|
|
|
pthread_t gtp_thread;
|
|
|
|
pthread_t ptg_thread;
|
|
|
|
int err;
|
|
|
|
int ret = 0;
|
|
|
|
err = pthread_create(>p_thread, NULL, udt_copy_task_routine,
|
|
|
|
&s->gtp);
|
|
|
|
if (err)
|
|
|
|
die("Can't start thread for copying data: %s", strerror(err));
|
|
|
|
err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,
|
|
|
|
&s->ptg);
|
|
|
|
if (err)
|
|
|
|
die("Can't start thread for copying data: %s", strerror(err));
|
|
|
|
|
|
|
|
ret |= tloop_join(gtp_thread, "Git to program copy");
|
|
|
|
ret |= tloop_join(ptg_thread, "Program to git copy");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
|
|
/* Close the source and target (for writing) for transfer. */
|
|
|
|
static void udt_kill_transfer(struct unidirectional_transfer *t)
|
|
|
|
{
|
|
|
|
t->state = SSTATE_FINISHED;
|
|
|
|
/*
|
|
|
|
* Socket read end left open isn't a disaster if nobody
|
|
|
|
* attempts to read from it (mingw compat headers do not
|
|
|
|
* have SHUT_RD)...
|
|
|
|
*
|
|
|
|
* We can't fully close the socket since otherwise gtp
|
|
|
|
* task would first close the socket it sends data to
|
|
|
|
* while closing the ptg file descriptors.
|
|
|
|
*/
|
|
|
|
if (!t->src_is_sock)
|
|
|
|
close(t->src);
|
|
|
|
if (t->dest_is_sock)
|
|
|
|
shutdown(t->dest, SHUT_WR);
|
|
|
|
else
|
|
|
|
close(t->dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-07-29 08:18:21 +00:00
|
|
|
* Join process, with appropriate errors on failure. Name is name for the
|
2010-10-12 16:39:41 +00:00
|
|
|
* process (for error messages). Returns 0 on success, 1 on failure.
|
|
|
|
*/
|
|
|
|
static int tloop_join(pid_t pid, const char *name)
|
|
|
|
{
|
|
|
|
int tret;
|
|
|
|
if (waitpid(pid, &tret, 0) < 0) {
|
2016-05-08 09:47:57 +00:00
|
|
|
error_errno("%s process failed to wait", name);
|
2010-10-12 16:39:41 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
|
|
|
|
error("%s process failed", name);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Spawn the transfer tasks and then wait for them. Returns 0 on success,
|
|
|
|
* -1 on failure.
|
|
|
|
*/
|
|
|
|
static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
|
|
|
|
{
|
|
|
|
pid_t pid1, pid2;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* Fork thread #1: git to program. */
|
|
|
|
pid1 = fork();
|
|
|
|
if (pid1 < 0)
|
|
|
|
die_errno("Can't start thread for copying data");
|
|
|
|
else if (pid1 == 0) {
|
|
|
|
udt_kill_transfer(&s->ptg);
|
|
|
|
exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fork thread #2: program to git. */
|
|
|
|
pid2 = fork();
|
|
|
|
if (pid2 < 0)
|
|
|
|
die_errno("Can't start thread for copying data");
|
|
|
|
else if (pid2 == 0) {
|
|
|
|
udt_kill_transfer(&s->gtp);
|
|
|
|
exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close both streams in parent as to not interfere with
|
|
|
|
* end of file detection and wait for both tasks to finish.
|
|
|
|
*/
|
|
|
|
udt_kill_transfer(&s->gtp);
|
|
|
|
udt_kill_transfer(&s->ptg);
|
|
|
|
ret |= tloop_join(pid1, "Git to program copy");
|
|
|
|
ret |= tloop_join(pid2, "Program to git copy");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copies data from stdin to output and from input to stdout simultaneously.
|
|
|
|
* Additionally filtering through given filter. If filter is NULL, uses
|
|
|
|
* identity filter.
|
|
|
|
*/
|
|
|
|
int bidirectional_transfer_loop(int input, int output)
|
|
|
|
{
|
|
|
|
struct bidirectional_transfer_state state;
|
|
|
|
|
|
|
|
/* Fill the state fields. */
|
|
|
|
state.ptg.src = input;
|
|
|
|
state.ptg.dest = 1;
|
|
|
|
state.ptg.src_is_sock = (input == output);
|
|
|
|
state.ptg.dest_is_sock = 0;
|
2016-08-09 08:53:38 +00:00
|
|
|
state.ptg.state = SSTATE_TRANSFERRING;
|
2010-10-12 16:39:41 +00:00
|
|
|
state.ptg.bufuse = 0;
|
|
|
|
state.ptg.src_name = "remote input";
|
|
|
|
state.ptg.dest_name = "stdout";
|
|
|
|
|
|
|
|
state.gtp.src = 0;
|
|
|
|
state.gtp.dest = output;
|
|
|
|
state.gtp.src_is_sock = 0;
|
|
|
|
state.gtp.dest_is_sock = (input == output);
|
2016-08-09 08:53:38 +00:00
|
|
|
state.gtp.state = SSTATE_TRANSFERRING;
|
2010-10-12 16:39:41 +00:00
|
|
|
state.gtp.bufuse = 0;
|
|
|
|
state.gtp.src_name = "stdin";
|
|
|
|
state.gtp.dest_name = "remote output";
|
|
|
|
|
|
|
|
return tloop_spawnwait_tasks(&state);
|
|
|
|
}
|