diff options
author | 2020-10-27 15:09:49 -0700 | |
---|---|---|
committer | 2020-10-27 15:09:50 -0700 | |
commit | 40696c67274305d6258539de5a36649cf833f712 (patch) | |
tree | c7f286e661d93790c42b5443cd4b0ab9dd9c6e4e | |
parent | Merge branch 'sk/force-if-includes' (diff) | |
parent | clone: allow configurable default for `-o`/`--origin` (diff) | |
download | tgif-40696c67274305d6258539de5a36649cf833f712.tar.xz |
Merge branch 'sb/clone-origin'
"git clone" learned clone.defaultremotename configuration variable
to customize what nickname to use to call the remote the repository
was cloned from.
* sb/clone-origin:
clone: allow configurable default for `-o`/`--origin`
clone: read new remote name from remote_name instead of option_origin
clone: validate --origin option before use
refs: consolidate remote name validation
remote: add tests for add and rename with invalid names
clone: use more conventional config/option layering
clone: add tests for --template and some disallowed option pairs
-rw-r--r-- | Documentation/config.txt | 2 | ||||
-rw-r--r-- | Documentation/config/clone.txt | 4 | ||||
-rw-r--r-- | Documentation/git-clone.txt | 5 | ||||
-rw-r--r-- | builtin/clone.c | 71 | ||||
-rw-r--r-- | builtin/remote.c | 7 | ||||
-rw-r--r-- | refspec.c | 10 | ||||
-rw-r--r-- | refspec.h | 1 | ||||
-rwxr-xr-x | t/t5505-remote.sh | 13 | ||||
-rwxr-xr-x | t/t5606-clone-options.sh | 68 |
9 files changed, 158 insertions, 23 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index bf706b950e..025ca4df11 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -334,6 +334,8 @@ include::config/checkout.txt[] include::config/clean.txt[] +include::config/clone.txt[] + include::config/color.txt[] include::config/column.txt[] diff --git a/Documentation/config/clone.txt b/Documentation/config/clone.txt new file mode 100644 index 0000000000..47de36a5fe --- /dev/null +++ b/Documentation/config/clone.txt @@ -0,0 +1,4 @@ +clone.defaultRemoteName:: + The name of the remote to create when cloning a repository. Defaults to + `origin`, and can be overridden by passing the `--origin` command-line + option to linkgit:git-clone[1]. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 097e6a86c5..876aedcd47 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -183,8 +183,9 @@ objects from the source repository into a pack in the cloned repository. -o <name>:: --origin <name>:: - Instead of using the remote name `origin` to keep track - of the upstream repository, use `<name>`. + Instead of using the remote name `origin` to keep track of the upstream + repository, use `<name>`. Overrides `clone.defaultRemoteName` from the + config. -b <name>:: --branch <name>:: diff --git a/builtin/clone.c b/builtin/clone.c index 391aa41075..a0841923cf 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -53,6 +53,7 @@ static int option_shallow_submodules; static int deepen; static char *option_template, *option_depth, *option_since; static char *option_origin = NULL; +static char *remote_name = NULL; static char *option_branch = NULL; static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; @@ -721,7 +722,7 @@ static void update_head(const struct ref *our, const struct ref *remote, if (!option_bare) { update_ref(msg, "HEAD", &our->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - install_branch_config(0, head, option_origin, our->name); + install_branch_config(0, head, remote_name, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(the_repository, @@ -851,8 +852,26 @@ static int checkout(int submodule_progress) return err; } +static int git_clone_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "clone.defaultremotename")) { + free(remote_name); + remote_name = xstrdup(v); + } + return git_default_config(k, v, cb); +} + static int write_one_config(const char *key, const char *value, void *data) { + /* + * give git_clone_config a chance to write config values back to the + * environment, since git_config_set_multivar_gently only deals with + * config-file writes + */ + int apply_failed = git_clone_config(key, value, data); + if (apply_failed) + return apply_failed; + return git_config_set_multivar_gently(key, value ? value : "true", CONFIG_REGEX_NONE, 0); @@ -905,12 +924,12 @@ static void write_refspec_config(const char *src_ref_prefix, } /* Configure the remote */ if (value.len) { - strbuf_addf(&key, "remote.%s.fetch", option_origin); + strbuf_addf(&key, "remote.%s.fetch", remote_name); git_config_set_multivar(key.buf, value.buf, "^$", 0); strbuf_reset(&key); if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); + strbuf_addf(&key, "remote.%s.mirror", remote_name); git_config_set(key.buf, "true"); strbuf_reset(&key); } @@ -963,6 +982,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct strvec ref_prefixes = STRVEC_INIT; packet_trace_identity("clone"); + + git_config(git_clone_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -991,9 +1013,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_no_checkout = 1; } - if (!option_origin) - option_origin = "origin"; - repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); @@ -1124,9 +1143,30 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (real_git_dir) git_dir = real_git_dir; + /* + * additional config can be injected with -c, make sure it's included + * after init_db, which clears the entire config environment. + */ write_config(&option_config); - git_config(git_default_config, NULL); + /* + * re-read config after init_db and write_config to pick up any config + * injected by --template and --config, respectively. + */ + git_config(git_clone_config, NULL); + + /* + * apply the remote name provided by --origin only after this second + * call to git_config, to ensure it overrides all config-based values. + */ + if (option_origin != NULL) + remote_name = xstrdup(option_origin); + + if (remote_name == NULL) + remote_name = xstrdup("origin"); + + if (!valid_remote_name(remote_name)) + die(_("'%s' is not a valid remote name"), remote_name); if (option_bare) { if (option_mirror) @@ -1135,15 +1175,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set("core.bare", "true"); } else { - strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); + strbuf_addf(&branch_top, "refs/remotes/%s/", remote_name); } - strbuf_addf(&key, "remote.%s.url", option_origin); + strbuf_addf(&key, "remote.%s.url", remote_name); git_config_set(key.buf, repo); strbuf_reset(&key); if (option_no_tags) { - strbuf_addf(&key, "remote.%s.tagOpt", option_origin); + strbuf_addf(&key, "remote.%s.tagOpt", remote_name); git_config_set(key.buf, "--no-tags"); strbuf_reset(&key); } @@ -1154,7 +1194,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_sparse_checkout && git_sparse_checkout_init(dir)) return 1; - remote = remote_get(option_origin); + remote = remote_get(remote_name); refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, branch_top.buf); @@ -1266,7 +1306,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (!our_head_points_at) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); } else our_head_points_at = remote_head_points_at; @@ -1274,7 +1314,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else { if (option_branch) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); mapped_refs = NULL; @@ -1286,7 +1326,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *branch = git_default_branch_name(); char *ref = xstrfmt("refs/heads/%s", branch); - install_branch_config(0, branch, option_origin, ref); + install_branch_config(0, branch, remote_name, ref); free(ref); } } @@ -1295,7 +1335,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at, &branch_top); if (filter_options.choice) - partial_clone_register(option_origin, &filter_options); + partial_clone_register(remote_name, &filter_options); if (is_local) clone_local(path, git_dir); @@ -1327,6 +1367,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_mode = JUNK_LEAVE_REPO; err = checkout(submodule_progress); + free(remote_name); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); diff --git a/builtin/remote.c b/builtin/remote.c index 64b4b551eb..63f2b46c3d 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -194,8 +194,7 @@ static int add(int argc, const char **argv) if (remote_is_configured(remote, 1)) die(_("remote %s already exists."), name); - strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); - if (!valid_fetch_refspec(buf2.buf)) + if (!valid_remote_name(name)) die(_("'%s' is not a valid remote name"), name); strbuf_addf(&buf, "remote.%s.url", name); @@ -696,11 +695,9 @@ static int mv(int argc, const char **argv) if (remote_is_configured(newremote, 1)) die(_("remote %s already exists."), rename.new_name); - strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new_name); - if (!valid_fetch_refspec(buf.buf)) + if (!valid_remote_name(rename.new_name)) die(_("'%s' is not a valid remote name"), rename.new_name); - strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s", rename.old_name); strbuf_addf(&buf2, "remote.%s", rename.new_name); if (git_config_rename_section(buf.buf, buf2.buf) < 1) @@ -245,6 +245,16 @@ int valid_fetch_refspec(const char *fetch_refspec_str) return ret; } +int valid_remote_name(const char *name) +{ + int result; + struct strbuf refspec = STRBUF_INIT; + strbuf_addf(&refspec, "refs/heads/test:refs/remotes/%s/test", name); + result = valid_fetch_refspec(refspec.buf); + strbuf_release(&refspec); + return result; +} + void refspec_ref_prefixes(const struct refspec *rs, struct strvec *ref_prefixes) { @@ -64,6 +64,7 @@ void refspec_appendn(struct refspec *rs, const char **refspecs, int nr); void refspec_clear(struct refspec *rs); int valid_fetch_refspec(const char *refspec); +int valid_remote_name(const char *name); struct strvec; /* diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 8d62edd98b..1156f52069 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -179,6 +179,13 @@ test_expect_success 'rename errors out early when deleting non-existent branch' ) ' +test_expect_success 'rename errors out early when when new name is invalid' ' + test_config remote.foo.vcs bar && + echo "fatal: '\''invalid...name'\'' is not a valid remote name" >expect && + test_must_fail git remote rename foo invalid...name 2>actual && + test_i18ncmp expect actual +' + test_expect_success 'add existing foreign_vcs remote' ' test_config remote.foo.vcs bar && echo "fatal: remote foo already exists." >expect && @@ -194,6 +201,12 @@ test_expect_success 'add existing foreign_vcs remote' ' test_i18ncmp expect actual ' +test_expect_success 'add invalid foreign_vcs remote' ' + echo "fatal: '\''invalid...name'\'' is not a valid remote name" >expect && + test_must_fail git remote add invalid...name bar 2>actual && + test_i18ncmp expect actual +' + cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index e69427f881..5e201e7d85 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -15,7 +15,73 @@ test_expect_success 'setup' ' test_expect_success 'clone -o' ' git clone -o foo parent clone-o && - (cd clone-o && git rev-parse --verify refs/remotes/foo/master) + git -C clone-o rev-parse --verify refs/remotes/foo/master + +' + +test_expect_success 'rejects invalid -o/--origin' ' + + test_must_fail git clone -o "bad...name" parent clone-bad-name 2>err && + test_i18ngrep "'\''bad...name'\'' is not a valid remote name" err + +' + +test_expect_success 'disallows --bare with --origin' ' + + test_must_fail git clone -o foo --bare parent clone-bare-o 2>err && + test_debug "cat err" && + test_i18ngrep -e "--bare and --origin foo options are incompatible" err + +' + +test_expect_success 'disallows --bare with --separate-git-dir' ' + + test_must_fail git clone --bare --separate-git-dir dot-git-destiation parent clone-bare-sgd 2>err && + test_debug "cat err" && + test_i18ngrep -e "--bare and --separate-git-dir are incompatible" err + +' + +test_expect_success 'uses "origin" for default remote name' ' + + git clone parent clone-default-origin && + git -C clone-default-origin rev-parse --verify refs/remotes/origin/master + +' + +test_expect_success 'prefers --template config over normal config' ' + + template="$TRASH_DIRECTORY/template-with-config" && + mkdir "$template" && + git config --file "$template/config" foo.bar from_template && + test_config_global foo.bar from_global && + git clone "--template=$template" parent clone-template-config && + test "$(git -C clone-template-config config --local foo.bar)" = "from_template" + +' + +test_expect_success 'prefers -c config over --template config' ' + + template="$TRASH_DIRECTORY/template-with-ignored-config" && + mkdir "$template" && + git config --file "$template/config" foo.bar from_template && + git clone "--template=$template" -c foo.bar=inline parent clone-template-inline-config && + test "$(git -C clone-template-inline-config config --local foo.bar)" = "inline" + +' + +test_expect_success 'prefers config "clone.defaultRemoteName" over default' ' + + test_config_global clone.defaultRemoteName from_config && + git clone parent clone-config-origin && + git -C clone-config-origin rev-parse --verify refs/remotes/from_config/master + +' + +test_expect_success 'prefers --origin over -c config' ' + + git clone -c clone.defaultRemoteName=inline --origin from_option parent clone-o-and-inline-config && + git -C clone-o-and-inline-config rev-parse --verify refs/remotes/from_option/master ' |