#include "commit.h"
#include "diff.h"
#include "revision.h"
-#include "quote.h"
#include "remote.h"
#include "string-list.h"
#include "thread-utils.h"
#include "sigchain.h"
#include "argv-array.h"
+#include "refs.h"
static int debug;
option : 1,
push : 1,
connect : 1,
- no_disconnect_req : 1;
+ signed_tags : 1,
+ check_connectivity : 1,
+ no_disconnect_req : 1,
+ no_private_update : 1;
char *export_marks;
char *import_marks;
/* These go from remote name (as in "list") to private name */
die_errno("Full write to remote helper failed");
}
-static int recvline_fh(FILE *helper, struct strbuf *buffer)
+static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
{
strbuf_reset(buffer);
if (debug)
if (strbuf_getline(buffer, helper, '\n') == EOF) {
if (debug)
fprintf(stderr, "Debug: Remote helper quit.\n");
- exit(128);
+ return 1;
}
if (debug)
static int recvline(struct helper_data *helper, struct strbuf *buffer)
{
- return recvline_fh(helper->out, buffer);
-}
-
-static void xchgline(struct helper_data *helper, struct strbuf *buffer)
-{
- sendline(helper, buffer);
- recvline(helper, buffer);
+ return recvline_fh(helper->out, buffer, helper->name);
}
static void write_constant(int fd, const char *str)
free(data);
}
+static void standard_options(struct transport *t);
+
static struct child_process *get_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
- struct argv_array argv = ARGV_ARRAY_INIT;
struct strbuf buf = STRBUF_INIT;
struct child_process *helper;
const char **refspecs = NULL;
int refspec_alloc = 0;
int duped;
int code;
- char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1];
- const char *helper_env[] = {
- git_dir_buf,
- NULL
- };
-
if (data->helper)
return data->helper;
- helper = xcalloc(1, sizeof(*helper));
+ helper = xmalloc(sizeof(*helper));
+ child_process_init(helper);
helper->in = -1;
helper->out = -1;
helper->err = 0;
- argv_array_pushf(&argv, "git-remote-%s", data->name);
- argv_array_push(&argv, transport->remote->name);
- argv_array_push(&argv, remove_ext_force(transport->url));
- helper->argv = argv_array_detach(&argv, NULL);
+ 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));
helper->git_cmd = 0;
helper->silent_exec_failure = 1;
- snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir());
- helper->env = helper_env;
+ argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT,
+ get_git_dir());
code = start_command(helper);
if (code < 0 && errno == ENOENT)
write_constant(helper->in, "capabilities\n");
while (1) {
- const char *capname;
+ const char *capname, *arg;
int mandatory = 0;
- recvline(data, &buf);
+ if (recvline(data, &buf))
+ exit(128);
if (!*buf.buf)
break;
data->bidi_import = 1;
else if (!strcmp(capname, "export"))
data->export = 1;
- else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
+ else if (!strcmp(capname, "check-connectivity"))
+ data->check_connectivity = 1;
+ else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) {
ALLOC_GROW(refspecs,
refspec_nr + 1,
refspec_alloc);
- refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
+ refspecs[refspec_nr++] = xstrdup(arg);
} else if (!strcmp(capname, "connect")) {
data->connect = 1;
- } else if (!prefixcmp(capname, "export-marks ")) {
- struct strbuf arg = STRBUF_INIT;
- strbuf_addstr(&arg, "--export-marks=");
- strbuf_addstr(&arg, capname + strlen("export-marks "));
- data->export_marks = strbuf_detach(&arg, NULL);
- } else if (!prefixcmp(capname, "import-marks")) {
- struct strbuf arg = STRBUF_INIT;
- strbuf_addstr(&arg, "--import-marks=");
- strbuf_addstr(&arg, capname + strlen("import-marks "));
- data->import_marks = strbuf_detach(&arg, NULL);
+ } else if (!strcmp(capname, "signed-tags")) {
+ data->signed_tags = 1;
+ } 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);
+ } else if (starts_with(capname, "no-private-update")) {
+ data->no_private_update = 1;
} else if (mandatory) {
die("Unknown mandatory capability %s. This remote "
"helper probably needs newer version of Git.",
for (i = 0; i < refspec_nr; i++)
free((char *)refspecs[i]);
free(refspecs);
+ } else if (data->import || data->bidi_import || data->export) {
+ warning("This remote helper should implement refspec capability.");
}
strbuf_release(&buf);
if (debug)
fprintf(stderr, "Debug: Capabilities complete.\n");
+ standard_options(transport);
return data->helper;
}
close(data->helper->out);
fclose(data->out);
res = finish_command(data->helper);
- argv_array_free_detached(data->helper->argv);
free(data->helper);
data->helper = NULL;
}
TRANS_OPT_THIN,
TRANS_OPT_KEEP
};
+
static const char *boolean_options[] = {
TRANS_OPT_THIN,
TRANS_OPT_KEEP,
- TRANS_OPT_FOLLOWTAGS
+ TRANS_OPT_FOLLOWTAGS,
+ TRANS_OPT_PUSH_CERT
};
static int set_helper_option(struct transport *transport,
quote_c_style(value, &buf, NULL, 0);
strbuf_addch(&buf, '\n');
- xchgline(data, &buf);
+ sendline(data, &buf);
+ if (recvline(data, &buf))
+ exit(128);
if (!strcmp(buf.buf, "ok"))
ret = 0;
- else if (!prefixcmp(buf.buf, "error")) {
+ else if (starts_with(buf.buf, "error")) {
ret = -1;
} else if (!strcmp(buf.buf, "unsupported"))
ret = 1;
int i;
struct strbuf buf = STRBUF_INIT;
- standard_options(transport);
-
for (i = 0; i < nr_heads; i++) {
const struct ref *posn = to_fetch[i];
if (posn->status & REF_STATUS_UPTODATE)
continue;
strbuf_addf(&buf, "fetch %s %s\n",
- sha1_to_hex(posn->old_sha1), posn->name);
+ sha1_to_hex(posn->old_sha1),
+ posn->symref ? posn->symref : posn->name);
}
strbuf_addch(&buf, '\n');
sendline(data, &buf);
while (1) {
- recvline(data, &buf);
+ if (recvline(data, &buf))
+ exit(128);
- if (!prefixcmp(buf.buf, "lock ")) {
+ if (starts_with(buf.buf, "lock ")) {
const char *name = buf.buf + 5;
if (transport->pack_lockfile)
warning("%s also locked %s", data->name, name);
else
transport->pack_lockfile = xstrdup(name);
}
+ 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;
else if (!buf.len)
break;
else
{
struct child_process *helper = get_helper(transport);
struct helper_data *data = transport->data;
- struct argv_array argv = ARGV_ARRAY_INIT;
int cat_blob_fd, code;
- memset(fastimport, 0, sizeof(*fastimport));
+ child_process_init(fastimport);
fastimport->in = helper->out;
- argv_array_push(&argv, "fast-import");
- argv_array_push(&argv, debug ? "--stats" : "--quiet");
+ argv_array_push(&fastimport->args, "fast-import");
+ argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
if (data->bidi_import) {
cat_blob_fd = xdup(helper->in);
- argv_array_pushf(&argv, "--cat-blob-fd=%d", cat_blob_fd);
+ argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd);
}
- fastimport->argv = argv.argv;
fastimport->git_cmd = 1;
code = start_command(fastimport);
{
struct helper_data *data = transport->data;
struct child_process *helper = get_helper(transport);
- int argc = 0, i;
- memset(fastexport, 0, sizeof(*fastexport));
+ int i;
+
+ child_process_init(fastexport);
/* we need to duplicate helper->in because we want to use it after
* fastexport is done with it. */
fastexport->out = dup(helper->in);
- fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv));
- fastexport->argv[argc++] = "fast-export";
- fastexport->argv[argc++] = "--use-done-feature";
+ 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)
- fastexport->argv[argc++] = data->export_marks;
+ argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks);
if (data->import_marks)
- fastexport->argv[argc++] = data->import_marks;
+ argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks);
for (i = 0; i < revlist_args->nr; i++)
- fastexport->argv[argc++] = revlist_args->items[i].string;
+ argv_array_push(&fastexport->args, revlist_args->items[i].string);
fastexport->git_cmd = 1;
return start_command(fastexport);
if (posn->status & REF_STATUS_UPTODATE)
continue;
- strbuf_addf(&buf, "import %s\n", posn->name);
+ strbuf_addf(&buf, "import %s\n",
+ posn->symref ? posn->symref : posn->name);
sendline(data, &buf);
strbuf_reset(&buf);
}
if (finish_command(&fastimport))
die("Error while running fast-import");
- argv_array_free_detached(fastimport.argv);
/*
* The fast-import stream of a remote helper that advertises
* were fetching.
*
* (If no "refspec" capability was specified, for historical
- * reasons we default to *:*.)
+ * reasons we default to the equivalent of *:*.)
*
* Store the result in to_fetch[i].old_sha1. Callers such
* as "git fetch" can use the value to write feedback to the
* fast-forward or this is a forced update.
*/
for (i = 0; i < nr_heads; i++) {
- char *private;
+ char *private, *name;
posn = to_fetch[i];
if (posn->status & REF_STATUS_UPTODATE)
continue;
+ name = posn->symref ? posn->symref : posn->name;
if (data->refspecs)
- private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
+ private = apply_refspecs(data->refspecs, data->refspec_nr, name);
else
- private = xstrdup(posn->name);
+ private = xstrdup(name);
if (private) {
read_ref(private, posn->old_sha1);
free(private);
goto exit;
sendline(data, &cmdbuf);
- recvline_fh(input, &cmdbuf);
+ if (recvline_fh(input, &cmdbuf, name))
+ exit(128);
+
if (!strcmp(cmdbuf.buf, "")) {
data->no_disconnect_req = 1;
if (debug)
if (!count)
return 0;
+ 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");
+
if (data->fetch)
return fetch_with_fetch(transport, nr_heads, to_fetch);
return -1;
}
-static void push_update_ref_status(struct strbuf *buf,
+static int push_update_ref_status(struct strbuf *buf,
struct ref **ref,
struct ref *remote_refs)
{
char *refname, *msg;
- int status;
+ int status, forced = 0;
- if (!prefixcmp(buf->buf, "ok ")) {
+ if (starts_with(buf->buf, "ok ")) {
status = REF_STATUS_OK;
refname = buf->buf + 3;
- } else if (!prefixcmp(buf->buf, "error ")) {
+ } else if (starts_with(buf->buf, "error ")) {
status = REF_STATUS_REMOTE_REJECT;
refname = buf->buf + 6;
} else
free(msg);
msg = NULL;
}
+ else if (!strcmp(msg, "stale info")) {
+ status = REF_STATUS_REJECT_STALE;
+ free(msg);
+ msg = NULL;
+ }
+ else if (!strcmp(msg, "forced update")) {
+ forced = 1;
+ free(msg);
+ msg = NULL;
+ }
}
if (*ref)
*ref = find_ref_by_name(remote_refs, refname);
if (!*ref) {
warning("helper reported unexpected status of %s", refname);
- return;
+ return 1;
}
if ((*ref)->status != REF_STATUS_NONE) {
* status reported by the remote helper if the latter is 'no match'.
*/
if (status == REF_STATUS_NONE)
- return;
+ return 1;
}
(*ref)->status = status;
+ (*ref)->forced_update |= forced;
(*ref)->remote_status = msg;
+ return !(status == REF_STATUS_OK);
}
-static void push_update_refs_status(struct helper_data *data,
- struct ref *remote_refs)
+static int push_update_refs_status(struct helper_data *data,
+ struct ref *remote_refs,
+ int flags)
{
struct strbuf buf = STRBUF_INIT;
struct ref *ref = remote_refs;
+ int ret = 0;
+
for (;;) {
- recvline(data, &buf);
+ char *private;
+
+ if (recvline(data, &buf)) {
+ ret = 1;
+ break;
+ }
+
if (!buf.len)
break;
- push_update_ref_status(&buf, &ref, remote_refs);
+ if (push_update_ref_status(&buf, &ref, remote_refs))
+ continue;
+
+ if (flags & TRANSPORT_PUSH_DRY_RUN || !data->refspecs || data->no_private_update)
+ continue;
+
+ /* propagate back the update to the remote namespace */
+ private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+ if (!private)
+ continue;
+ update_ref("update by helper", private, ref->new_sha1, NULL, 0, 0);
+ free(private);
}
strbuf_release(&buf);
+ return ret;
}
static int push_refs_with_push(struct transport *transport,
- struct ref *remote_refs, int flags)
+ struct ref *remote_refs, int flags)
{
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;
+ struct string_list cas_options = STRING_LIST_INIT_DUP;
+ struct string_list_item *cas_option;
get_helper(transport);
if (!data->push)
/* Check for statuses set by set_ref_status_for_push() */
switch (ref->status) {
case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_REJECT_STALE:
case REF_STATUS_REJECT_ALREADY_EXISTS:
case REF_STATUS_UPTODATE:
continue;
strbuf_addch(&buf, ':');
strbuf_addstr(&buf, ref->name);
strbuf_addch(&buf, '\n');
+
+ /*
+ * The "--force-with-lease" options without explicit
+ * values to expect have already been expanded into
+ * the ref->old_sha1_expect[] field; we can ignore
+ * 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",
+ ref->name, sha1_to_hex(ref->old_sha1_expect));
+ string_list_append(&cas_options, strbuf_detach(&cas, NULL));
+ }
}
- if (buf.len == 0)
+ if (buf.len == 0) {
+ string_list_clear(&cas_options, 0);
return 0;
+ }
- standard_options(transport);
+ for_each_string_list_item(cas_option, &cas_options)
+ set_helper_option(transport, "cas", cas_option->string);
if (flags & TRANSPORT_PUSH_DRY_RUN) {
if (set_helper_option(transport, "dry-run", "true") != 0)
die("helper %s does not support dry-run", data->name);
+ } else if (flags & TRANSPORT_PUSH_CERT) {
+ if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+ die("helper %s does not support --signed", data->name);
}
strbuf_addch(&buf, '\n');
sendline(data, &buf);
strbuf_release(&buf);
- push_update_refs_status(data, remote_refs);
- return 0;
+ return push_update_refs_status(data, remote_refs, flags);
}
static int push_refs_with_export(struct transport *transport,
struct ref *ref;
struct child_process *helper, exporter;
struct helper_data *data = transport->data;
- struct string_list revlist_args = STRING_LIST_INIT_NODUP;
+ struct string_list revlist_args = STRING_LIST_INIT_DUP;
struct strbuf buf = STRBUF_INIT;
+ if (!data->refspecs)
+ die("remote-helper doesn't support push; refspec needed");
+
+ if (flags & TRANSPORT_PUSH_DRY_RUN) {
+ if (set_helper_option(transport, "dry-run", "true") != 0)
+ die("helper %s does not support dry-run", data->name);
+ } else if (flags & TRANSPORT_PUSH_CERT) {
+ if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+ die("helper %s does not support --signed", data->name);
+ }
+
+ if (flags & TRANSPORT_PUSH_FORCE) {
+ if (set_helper_option(transport, "force", "true") != 0)
+ warning("helper %s does not support 'force'", data->name);
+ }
+
helper = get_helper(transport);
write_constant(helper->in, "export\n");
- strbuf_reset(&buf);
-
for (ref = remote_refs; ref; ref = ref->next) {
char *private;
unsigned char sha1[20];
- if (!data->refspecs)
- continue;
private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
if (private && !get_sha1(private, sha1)) {
strbuf_addf(&buf, "^%s", private);
string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
+ hashcpy(ref->old_sha1, sha1);
}
free(private);
- if (ref->deletion) {
- die("remote-helpers do not support ref deletion");
+ if (ref->peer_ref) {
+ if (strcmp(ref->name, ref->peer_ref->name)) {
+ if (!ref->deletion) {
+ const char *name;
+ int flag;
+
+ /* Follow symbolic refs (mainly for HEAD). */
+ name = resolve_ref_unsafe(
+ ref->peer_ref->name,
+ RESOLVE_REF_READING,
+ sha1, &flag);
+ if (!name || !(flag & REF_ISSYMREF))
+ name = ref->peer_ref->name;
+
+ strbuf_addf(&buf, "%s:%s", name, ref->name);
+ } else
+ strbuf_addf(&buf, ":%s", ref->name);
+
+ string_list_append(&revlist_args, "--refspec");
+ string_list_append(&revlist_args, buf.buf);
+ strbuf_release(&buf);
+ }
+ if (!ref->deletion)
+ string_list_append(&revlist_args, ref->peer_ref->name);
}
-
- if (ref->peer_ref)
- string_list_append(&revlist_args, ref->peer_ref->name);
-
}
if (get_exporter(transport, &exporter, &revlist_args))
die("Couldn't run fast-export");
+ string_list_clear(&revlist_args, 1);
+
if (finish_command(&exporter))
die("Error while running fast-export");
- push_update_refs_status(data, remote_refs);
+ 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;
}
while (1) {
char *eov, *eon;
- recvline(data, &buf);
+ if (recvline(data, &buf))
+ exit(128);
if (!*buf.buf)
break;
int transport_helper_init(struct transport *transport, const char *name)
{
- struct helper_data *data = xcalloc(sizeof(*data), 1);
+ struct helper_data *data = xcalloc(1, sizeof(*data));
data->name = name;
if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
#define PBUFFERSIZE 8192
/* Print bidirectional transfer loop debug message. */
+__attribute__((format (printf, 1, 2)))
static void transfer_debug(const char *fmt, ...)
{
va_list args;
return -1;
} else if (bytes == 0) {
transfer_debug("%s EOF (with %i bytes in buffer)",
- t->src_name, t->bufuse);
+ t->src_name, (int)t->bufuse);
t->state = SSTATE_FLUSHING;
} else if (bytes > 0) {
t->bufuse += bytes;
return 0; /* Nothing to write. */
transfer_debug("%s is writable", t->dest_name);
- bytes = write(t->dest, t->buf, t->bufuse);
- if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
- errno != EINTR) {
+ bytes = xwrite(t->dest, t->buf, t->bufuse);
+ if (bytes < 0 && errno != EWOULDBLOCK) {
error("write(%s) failed: %s", t->dest_name, strerror(errno));
return -1;
} else if (bytes > 0) {
#ifndef NO_PTHREADS
/*
- * Join thread, with apporiate errors on failure. Name is name for the
+ * Join thread, with appropriate errors on failure. Name is name for the
* thread (for error messages). Returns 0 on success, 1 on failure.
*/
static int tloop_join(pthread_t thread, const char *name)
}
/*
- * Join process, with apporiate errors on failure. Name is name for the
+ * Join process, with appropriate errors on failure. Name is name for the
* process (for error messages). Returns 0 on success, 1 on failure.
*/
static int tloop_join(pid_t pid, const char *name)