diff options
-rw-r--r-- | Documentation/git-push.txt | 8 | ||||
-rw-r--r-- | builtin-push.c | 14 | ||||
-rw-r--r-- | builtin-send-pack.c | 127 | ||||
-rw-r--r-- | http-push.c | 4 | ||||
-rw-r--r-- | receive-pack.c | 4 | ||||
-rw-r--r-- | remote.c | 15 | ||||
-rw-r--r-- | remote.h | 7 | ||||
-rw-r--r-- | send-pack.h | 1 | ||||
-rwxr-xr-x | t/t5405-send-pack-rewind.sh | 42 | ||||
-rwxr-xr-x | t/t5517-push-mirror.sh | 228 | ||||
-rw-r--r-- | transport.c | 7 | ||||
-rw-r--r-- | transport.h | 3 |
12 files changed, 418 insertions, 42 deletions
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 4a68aaba34..b8003c63c7 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -63,6 +63,14 @@ the remote repository. Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/heads/` be pushed. +\--mirror:: + Instead of naming each ref to push, specifies that all + refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/` + be mirrored to the remote repository. Newly created local + refs will be pushed to the remote end, locally updated refs + will be force updated on the remote end, and deleted refs + will be removed from the remote end. + \--dry-run:: Do everything except actually send the updates. diff --git a/builtin-push.c b/builtin-push.c index 6d1da07c46..41df717f84 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", + "git-push [--all | --mirror] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]", NULL, }; @@ -91,6 +91,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; int all = 0; + int mirror = 0; int dry_run = 0; int force = 0; int tags = 0; @@ -100,6 +101,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose), OPT_STRING( 0 , "repo", &repo, "repository", "repository"), OPT_BOOLEAN( 0 , "all", &all, "push all refs"), + OPT_BOOLEAN( 0 , "mirror", &mirror, "mirror all refs"), OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), OPT_BOOLEAN( 0 , "dry-run", &dry_run, "dry run"), OPT_BOOLEAN('f', "force", &force, "force updates"), @@ -121,13 +123,21 @@ int cmd_push(int argc, const char **argv, const char *prefix) add_refspec("refs/tags/*"); if (all) flags |= TRANSPORT_PUSH_ALL; + if (mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1); } - if ((flags & TRANSPORT_PUSH_ALL) && refspec) + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) usage_with_options(push_usage, options); + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == + (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { + error("--all and --mirror are incompatible"); + usage_with_options(push_usage, options); + } + return do_push(repo, flags); } diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 22cb50bc2b..418925eb01 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -8,7 +8,7 @@ #include "send-pack.h" static const char send_pack_usage[] = -"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" +"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" " --all and explicit <ref> specification are mutually exclusive."; static struct send_pack_args args = { @@ -195,7 +195,8 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref) return; if (!remote_find_tracking(remote, &rs)) { - fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (args.verbose) + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); if (is_null_sha1(ref->peer_ref->new_sha1)) { if (delete_ref(rs.dst, NULL)) error("Failed to delete"); @@ -206,7 +207,18 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref) } } -static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, const char **refspec) +static const char *prettify_ref(const char *name) +{ + return name + ( + !prefixcmp(name, "refs/heads/") ? 11 : + !prefixcmp(name, "refs/tags/") ? 10 : + !prefixcmp(name, "refs/remotes/") ? 13 : + 0); +} + +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) + +static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec) { struct ref *ref; int new_refs; @@ -214,6 +226,13 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, int ask_for_status_report = 0; int allow_deleting_refs = 0; int expect_status_report = 0; + int shown_dest = 0; + int flags = MATCH_REFS_NONE; + + if (args.send_all) + flags |= MATCH_REFS_ALL; + if (args.send_mirror) + flags |= MATCH_REFS_MIRROR; /* No funny business with the matcher */ remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); @@ -229,7 +248,7 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, if (!remote_tail) remote_tail = &remote_refs; if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, args.send_all)) + nr_refspec, refspec, flags)) return -1; if (!remote_refs) { @@ -245,21 +264,41 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; int will_delete_ref; + const char *pretty_ref; + const char *pretty_peer = NULL; /* only used when not deleting */ + const unsigned char *new_sha1; - if (!ref->peer_ref) - continue; + if (!ref->peer_ref) { + if (!args.send_mirror) + continue; + new_sha1 = null_sha1; + } + else + new_sha1 = ref->peer_ref->new_sha1; + + if (!shown_dest) { + fprintf(stderr, "To %s\n", dest); + shown_dest = 1; + } + will_delete_ref = is_null_sha1(new_sha1); + + pretty_ref = prettify_ref(ref->name); + if (!will_delete_ref) + pretty_peer = prettify_ref(ref->peer_ref->name); - will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); if (will_delete_ref && !allow_deleting_refs) { - error("remote does not support deleting refs"); + fprintf(stderr, " ! %-*s %s (remote does not support deleting refs)\n", + SUMMARY_WIDTH, "[rejected]", pretty_ref); ret = -2; continue; } if (!will_delete_ref && - !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { + !hashcmp(ref->old_sha1, new_sha1)) { if (args.verbose) - fprintf(stderr, "'%s': up-to-date\n", ref->name); + fprintf(stderr, " = %-*s %s -> %s\n", + SUMMARY_WIDTH, "[up to date]", + pretty_peer, pretty_ref); continue; } @@ -287,8 +326,7 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, !is_null_sha1(ref->old_sha1) && !ref->force) { if (!has_sha1_file(ref->old_sha1) || - !ref_newer(ref->peer_ref->new_sha1, - ref->old_sha1)) { + !ref_newer(new_sha1, ref->old_sha1)) { /* We do not have the remote ref, or * we know that the remote ref is not * an ancestor of what we are trying to @@ -296,17 +334,14 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, * commits at the remote end and likely * we were not up to date to begin with. */ - error("remote '%s' is not an ancestor of\n" - " local '%s'.\n" - " Maybe you are not up-to-date and " - "need to pull first?", - ref->name, - ref->peer_ref->name); + fprintf(stderr, " ! %-*s %s -> %s (non-fast forward)\n", + SUMMARY_WIDTH, "[rejected]", + pretty_peer, pretty_ref); ret = -2; continue; } } - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + hashcpy(ref->new_sha1, new_sha1); if (!will_delete_ref) new_refs++; strcpy(old_hex, sha1_to_hex(ref->old_sha1)); @@ -325,14 +360,41 @@ static int do_send_pack(int in, int out, struct remote *remote, int nr_refspec, old_hex, new_hex, ref->name); } if (will_delete_ref) - fprintf(stderr, "deleting '%s'\n", ref->name); + fprintf(stderr, " - %-*s %s\n", + SUMMARY_WIDTH, "[deleting]", + pretty_ref); + else if (is_null_sha1(ref->old_sha1)) { + const char *msg; + + if (!prefixcmp(ref->name, "refs/tags/")) + msg = "[new tag]"; + else + msg = "[new branch]"; + fprintf(stderr, " * %-*s %s -> %s\n", + SUMMARY_WIDTH, msg, + pretty_peer, pretty_ref); + } else { - fprintf(stderr, "updating '%s'", ref->name); - if (strcmp(ref->name, ref->peer_ref->name)) - fprintf(stderr, " using '%s'", - ref->peer_ref->name); - fprintf(stderr, "\n from %s\n to %s\n", - old_hex, new_hex); + char quickref[83]; + char type = ' '; + const char *msg = ""; + const char *old_abb; + old_abb = find_unique_abbrev(ref->old_sha1, DEFAULT_ABBREV); + strcpy(quickref, old_abb ? old_abb : old_hex); + if (ref_newer(ref->peer_ref->new_sha1, ref->old_sha1)) + strcat(quickref, ".."); + else { + strcat(quickref, "..."); + type = '+'; + msg = " (forced update)"; + } + strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + + fprintf(stderr, " %c %-*s %s -> %s%s\n", + type, + SUMMARY_WIDTH, quickref, + pretty_peer, pretty_ref, + msg); } } @@ -411,6 +473,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.dry_run = 1; continue; } + if (!strcmp(arg, "--mirror")) { + args.send_mirror = 1; + continue; + } if (!strcmp(arg, "--force")) { args.force_update = 1; continue; @@ -435,7 +501,12 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } if (!dest) usage(send_pack_usage); - if (heads && args.send_all) + /* + * --all and --mirror are incompatible; neither makes sense + * with any refspecs. + */ + if ((heads && (args.send_all || args.send_mirror)) || + (args.send_all && args.send_mirror)) usage(send_pack_usage); if (remote_name) { @@ -461,7 +532,7 @@ int send_pack(struct send_pack_args *my_args, verify_remote_names(nr_heads, heads); conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0); - ret = do_send_pack(fd[0], fd[1], remote, nr_heads, heads); + ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads); close(fd[0]); close(fd[1]); ret |= finish_connect(conn); diff --git a/http-push.c b/http-push.c index 99328f5909..66b81f1726 100644 --- a/http-push.c +++ b/http-push.c @@ -78,7 +78,7 @@ static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; static int push_verbosely; -static int push_all; +static int push_all = MATCH_REFS_NONE; static int force_all; static int dry_run; @@ -2300,7 +2300,7 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--all")) { - push_all = 1; + push_all = MATCH_REFS_ALL; continue; } if (!strcmp(arg, "--force")) { diff --git a/receive-pack.c b/receive-pack.c index 38e35c06b9..ed44b897f6 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -204,8 +204,6 @@ static const char *update(struct command *cmd) error("failed to delete %s", name); return "failed to delete"; } - fprintf(stderr, "%s: %s -> deleted\n", name, - sha1_to_hex(old_sha1)); return NULL; /* good */ } else { @@ -217,8 +215,6 @@ static const char *update(struct command *cmd) if (write_ref_sha1(lock, new_sha1, "push")) { return "failed to write"; /* error() already called */ } - fprintf(stderr, "%s: %s -> %s\n", name, - sha1_to_hex(old_sha1), sha1_to_hex(new_sha1)); return NULL; /* good */ } } @@ -722,10 +722,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, * without thinking. */ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, const char **refspec, int all) + int nr_refspec, const char **refspec, int flags) { struct refspec *rs = parse_ref_spec(nr_refspec, (const char **) refspec); + int send_all = flags & MATCH_REFS_ALL; + int send_mirror = flags & MATCH_REFS_MIRROR; if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) return -1; @@ -742,7 +744,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (!pat) continue; } - else if (prefixcmp(src->name, "refs/heads/")) + else if (!send_mirror && prefixcmp(src->name, "refs/heads/")) /* * "matching refs"; traditionally we pushed everything * including refs outside refs/heads/ hierarchy, but @@ -763,10 +765,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, if (dst_peer && dst_peer->peer_ref) /* We're already sending something to this ref. */ goto free_name; - if (!dst_peer && !nr_refspec && !all) - /* Remote doesn't have it, and we have no + + if (!dst_peer && !nr_refspec && !(send_all || send_mirror)) + /* + * Remote doesn't have it, and we have no * explicit pattern, and we don't have - * --all. */ + * --all nor --mirror. + */ goto free_name; if (!dst_peer) { /* Create a new one and link it */ @@ -102,4 +102,11 @@ struct branch *branch_get(const char *name); int branch_has_merge_config(struct branch *branch); int branch_merge_matches(struct branch *, int n, const char *); +/* Flags to match_refs. */ +enum match_refs_flags { + MATCH_REFS_NONE = 0, + MATCH_REFS_ALL = (1 << 0), + MATCH_REFS_MIRROR = (1 << 1), +}; + #endif diff --git a/send-pack.h b/send-pack.h index 7a24f71c77..8ff1dc3539 100644 --- a/send-pack.h +++ b/send-pack.h @@ -5,6 +5,7 @@ struct send_pack_args { const char *receivepack; unsigned verbose:1, send_all:1, + send_mirror:1, force_update:1, use_thin_pack:1, dry_run:1; diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh new file mode 100755 index 0000000000..86abc62271 --- /dev/null +++ b/t/t5405-send-pack-rewind.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='forced push to replace commit we do not have' + +. ./test-lib.sh + +test_expect_success setup ' + + >file1 && git add file1 && test_tick && + git commit -m Initial && + + mkdir another && ( + cd another && + git init && + git fetch .. master:master + ) && + + >file2 && git add file2 && test_tick && + git commit -m Second + +' + +test_expect_success 'non forced push should die not segfault' ' + + ( + cd another && + git push .. master:master + test $? = 1 + ) + +' + +test_expect_success 'forced push should succeed' ' + + ( + cd another && + git push .. +master:master + ) + +' + +test_done diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh new file mode 100755 index 0000000000..ed3fec192a --- /dev/null +++ b/t/t5517-push-mirror.sh @@ -0,0 +1,228 @@ +#!/bin/sh + +test_description='pushing to a mirror repository' + +. ./test-lib.sh + +D=`pwd` + +invert () { + if "$@"; then + return 1 + else + return 0 + fi +} + +mk_repo_pair () { + rm -rf master mirror && + mkdir mirror && + ( + cd mirror && + git init + ) && + mkdir master && + ( + cd master && + git init && + git config remote.up.url ../mirror + ) +} + + +# BRANCH tests +test_expect_success 'push mirror creates new branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git push --mirror up && + git reset --hard HEAD^ + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes branches' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + +test_expect_success 'push mirror adds, updates and removes branches together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git branch remove master && + git push --mirror up && + git branch -D remove && + git branch add master && + echo two >foo && git add foo && git commit -m two && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/heads/master) && + master_add=$(cd master && git show-ref -s --verify refs/heads/add) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/heads/master) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/heads/add) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/heads/remove + ) + +' + + +# TAG tests +test_expect_success 'push mirror creates new tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror force updates existing tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git push --mirror up && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up && + git reset --hard HEAD^ + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + test "$master_master" = "$mirror_master" + +' + +test_expect_success 'push mirror removes tags' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove + git push --mirror up + ) && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_expect_success 'push mirror adds, updates and removes tags together' ' + + mk_repo_pair && + ( + cd master && + echo one >foo && git add foo && git commit -m one && + git tag -f tmaster master && + git tag -f tremove master && + git push --mirror up && + git tag -d tremove && + git tag tadd master && + echo two >foo && git add foo && git commit -m two && + git tag -f tmaster master && + git push --mirror up + ) && + master_master=$(cd master && git show-ref -s --verify refs/tags/tmaster) && + master_add=$(cd master && git show-ref -s --verify refs/tags/tadd) && + mirror_master=$(cd mirror && git show-ref -s --verify refs/tags/tmaster) && + mirror_add=$(cd mirror && git show-ref -s --verify refs/tags/tadd) && + test "$master_master" = "$mirror_master" && + test "$master_add" = "$mirror_add" && + ( + cd mirror && + invert git show-ref -s --verify refs/tags/tremove + ) + +' + +test_done diff --git a/transport.c b/transport.c index 5cb809bff6..8d9bdbe8de 100644 --- a/transport.c +++ b/transport.c @@ -284,6 +284,9 @@ static int rsync_transport_push(struct transport *transport, struct child_process rsync; const char *args[10]; + if (flags & TRANSPORT_PUSH_MIRROR) + return error("rsync transport does not support mirror mode"); + /* first push the objects */ strbuf_addstr(&buf, transport->url); @@ -387,6 +390,9 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons int argc; int err; + if (flags & TRANSPORT_PUSH_MIRROR) + return error("http transport does not support mirror mode"); + argv = xmalloc((refspec_nr + 12) * sizeof(char *)); argv[0] = "http-push"; argc = 1; @@ -657,6 +663,7 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const args.receivepack = data->receivepack; args.send_all = !!(flags & TRANSPORT_PUSH_ALL); + args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); diff --git a/transport.h b/transport.h index a2a36d029e..6fb4526cda 100644 --- a/transport.h +++ b/transport.h @@ -30,7 +30,8 @@ struct transport { #define TRANSPORT_PUSH_ALL 1 #define TRANSPORT_PUSH_FORCE 2 #define TRANSPORT_PUSH_DRY_RUN 4 -#define TRANSPORT_PUSH_VERBOSE 8 +#define TRANSPORT_PUSH_MIRROR 8 +#define TRANSPORT_PUSH_VERBOSE 16 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); |