{"thread":{"id":"61642","subject":"[PATCH] submodule: allow gitsubmodules to be pointed at tags","startedAt":"2024-06-17T12:01:51Z","lastAt":"2024-06-24T16:40:23Z","messageCount":4,"participants":["dark0dave via GitGitGadget","Sean Allred","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"497243","messageId":"pull.1703.git.git.1718625706679.gitgitgadget@gmail.com","threadId":"61642","inReplyTo":null,"subject":"[PATCH] submodule: allow gitsubmodules to be pointed at tags","fromName":"dark0dave via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-06-17T12:01:46Z","receivedAt":"2024-06-17T12:01:51Z","isPatch":true,"sender":{"key":"name:dark0dave","avatar":null},"body":"From: dark0dave <dark0dave@mykolab.com>\n\nIntent: Enable git submodules to point at tags,\neffectively freezing them\n\nContext: There is a large repo with 50+ submodules, the goal is\nto have some submodules update to HEAD but others to remain fixed.\nThe update command does this by recursing through\ndirectories. This is very useful, however if an update is run,\nall the submodules are updated to HEAD.\n\nThe goals:\n- Be able to create a submodule from a fixed tag (complete)\n- Be able to run git submodule update, with a flag to either update\nor keep the submodule pointed at the supplied tag (complete)\n\nExample:\n\ngit submodule add changes\n\ngit submodule add --tag v34.3\nhttps://github.com/Gibberlings3/SwordCoastStratagems.git\ncat .gitmodules\n[submodule \"SwordCoastStratagems\"]\n  path = SwordCoastStratagems\n  url = https://github.com/Gibberlings3/SwordCoastStratagems.git\n  tag = v34.3\n\ngit submodule update changes\n\ngit submodule update  --remote -f --checkout\n\nNo changes as we are fixed to the tag\n\ngit submodule update --remote -f --checkout --no-single-tag\nSubmodule path 'SwordCoastStratagems': checked out\n'77426a2e14dbe584af811f922a6da3a48d74c074'\n\nChecks out to HEAD of remote\n\nCC: Revi Ewer <revi.ewer@example.com>,\nIll Takalook <ill.takalook@example.net>\n\nSigned-off-by: dark0dave <dark0dave@mykolab.com>\n---\n    submodule: allow gitsubmodules to be pointed at tags @dark0dave\n    \n    I want to enable users freezing their submodules to a tag.\n    \n    Example:\n    \n    \n    Add\n    ===\n    \n    git submodule add changes\n    \n    git submodule add --tag v34.3 https://github.com/Gibberlings3/SwordCoastStratagems.git\n    cat .gitmodules\n    [submodule \"SwordCoastStratagems\"]\n      path = SwordCoastStratagems\n      url = https://github.com/Gibberlings3/SwordCoastStratagems.git\n      tag = v34.3\n    \n    \n    \n    Update\n    ======\n    \n    git submodule update changes\n    \n    \n    Single tag\n    ==========\n    \n    git submodule update  --remote -f --checkout\n    $\n    \n    \n    No changes as we are fixed to the tag\n    \n    \n    No Single tag\n    =============\n    \n    git submodule update --remote -f --checkout --no-single-tag\n    Submodule path 'SwordCoastStratagems': checked out '77426a2e14dbe584af811f922a6da3a48d74c074'\n    \n    \n    Checks out to HEAD of remote\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1703%2Fdark0dave%2Fmaster-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1703/dark0dave/master-v1\nPull-Request: https://github.com/git/git/pull/1703\n\n builtin/submodule--helper.c            | 119 +++++++++++++++++++++++--\n contrib/completion/git-completion.bash |   8 +-\n git-submodule.sh                       |  59 +++++++++++-\n submodule-config.c                     |  14 +++\n submodule-config.h                     |   1 +\n 5 files changed, 189 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 880ab4456ee..e7f150230e9 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1541,9 +1541,11 @@ struct module_clone_data {\n \tunsigned int dissociate: 1;\n \tunsigned int require_init: 1;\n \tint single_branch;\n+\tint single_tag;\n };\n #define MODULE_CLONE_DATA_INIT { \\\n \t.single_branch = -1, \\\n+\t.single_tag = -1, \\\n }\n \n struct submodule_alternate_setup {\n@@ -1754,6 +1756,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n \t\t\t\t    \"--single-branch\" :\n \t\t\t\t    \"--no-single-branch\");\n+\t\tif (clone_data->single_tag >= 0)\n+\t\t\tstrvec_push(&cp.args, clone_data->single_tag ?\n+\t\t\t\t    \"--single-branch\" :\n+\t\t\t\t    \"--no-single-branch\");\n \n \t\tstrvec_push(&cp.args, \"--\");\n \t\tstrvec_push(&cp.args, clone_data->url);\n@@ -1981,6 +1987,7 @@ struct update_data {\n \tint depth;\n \tint max_jobs;\n \tint single_branch;\n+\tint single_tag;\n \tint recommend_shallow;\n \tunsigned int require_init;\n \tunsigned int force;\n@@ -2004,6 +2011,7 @@ struct update_data {\n \t.recommend_shallow = -1, \\\n \t.references = STRING_LIST_INIT_DUP, \\\n \t.single_branch = -1, \\\n+\t.single_tag = -1, \\\n \t.max_jobs = 1, \\\n }\n \n@@ -2151,6 +2159,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n \t\tstrvec_push(&child->args, suc->update_data->single_branch ?\n \t\t\t\t\t      \"--single-branch\" :\n \t\t\t\t\t      \"--no-single-branch\");\n+\tif (suc->update_data->single_tag >= 0)\n+\t\tstrvec_push(&child->args, suc->update_data->single_tag ?\n+\t\t\t\t\t      \"--single-tag\" :\n+\t\t\t\t\t      \"--no-single-tag\");\n \n cleanup:\n \tfree(displaypath);\n@@ -2297,6 +2309,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n \tcp.dir = module_path;\n \n \tstrvec_push(&cp.args, \"fetch\");\n+\n \tif (quiet)\n \t\tstrvec_push(&cp.args, \"--quiet\");\n \tif (depth)\n@@ -2434,6 +2447,30 @@ static int run_update_procedure(const struct update_data *ud)\n \treturn run_update_command(ud, subforce);\n }\n \n+static int remote_submodule_tag(const char *path, const char **tag)\n+{\n+\tconst struct submodule *sub;\n+\tchar *key;\n+\t*tag = NULL;\n+\n+\tsub = submodule_from_path(the_repository, null_oid(), path);\n+\tif (!sub)\n+\t\treturn die_message(_(\"could not initialize submodule at path '%s'\"),\n+\t\t\t\t   path);\n+\n+\tkey = xstrfmt(\"submodule.%s.tag\", sub->name);\n+\tif (repo_config_get_string_tmp(the_repository, key, tag))\n+\t\t*tag = sub->tag;\n+\tfree(key);\n+\n+\tif (!*tag) {\n+\t\t/* No tag found */\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int remote_submodule_branch(const char *path, const char **branch)\n {\n \tconst struct submodule *sub;\n@@ -2579,6 +2616,10 @@ static void update_data_to_args(const struct update_data *update_data,\n \t\tstrvec_push(args, update_data->single_branch ?\n \t\t\t\t    \"--single-branch\" :\n \t\t\t\t    \"--no-single-branch\");\n+\tif (update_data->single_tag >= 0)\n+\t\tstrvec_push(args, update_data->single_tag ?\n+\t\t\t\t    \"--single-branch\" :\n+\t\t\t\t    \"--no-single-branch\");\n }\n \n static int update_submodule(struct update_data *update_data)\n@@ -2606,16 +2647,22 @@ static int update_submodule(struct update_data *update_data)\n \tif (update_data->remote) {\n \t\tchar *remote_name;\n \t\tconst char *branch;\n+\t\tconst char *tag;\n \t\tchar *remote_ref;\n \t\tint code;\n \n \t\tcode = get_default_remote_submodule(update_data->sm_path, &remote_name);\n \t\tif (code)\n \t\t\treturn code;\n-\t\tcode = remote_submodule_branch(update_data->sm_path, &branch);\n-\t\tif (code)\n-\t\t\treturn code;\n-\t\tremote_ref = xstrfmt(\"refs/remotes/%s/%s\", remote_name, branch);\n+\t\tcode = remote_submodule_tag(update_data->sm_path, &tag);\n+\t\tif (!code && update_data->single_tag) {\n+\t\t\t\tremote_ref = xstrfmt(\"refs/tags/%s\", tag);\n+\t\t} else {\n+\t\t\tcode = remote_submodule_branch(update_data->sm_path, &branch);\n+\t\t\tif (code)\n+\t\t\t\treturn code;\n+\t\t\tremote_ref = xstrfmt(\"refs/remotes/%s/%s\", remote_name, branch);\n+\t\t}\n \n \t\tfree(remote_name);\n \n@@ -2781,6 +2828,8 @@ static int module_update(int argc, const char **argv, const char *prefix)\n \t\t\t   N_(\"disallow cloning into non-empty directory, implies --init\")),\n \t\tOPT_BOOL(0, \"single-branch\", &opt.single_branch,\n \t\t\t N_(\"clone only one branch, HEAD or --branch\")),\n+\t\tOPT_BOOL(0, \"single-tag\", &opt.single_tag,\n+\t\t\t N_(\"clone only one tag, --tag\")),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n \t\tOPT_END()\n \t};\n@@ -2790,7 +2839,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n \t\t\" [-N|--no-fetch] [-f|--force]\"\n \t\t\" [--checkout|--merge|--rebase]\"\n \t\t\" [--[no-]recommend-shallow] [--reference <repository>]\"\n-\t\t\" [--recursive] [--[no-]single-branch] [--] [<path>...]\"),\n+\t\t\" [--recursive] [--[no-]single-branch] [--[no-]single-tag] [--] [<path>...]\"),\n \t\tNULL\n \t};\n \n@@ -3058,6 +3107,52 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+\n+static int module_set_tag(int argc, const char **argv, const char *prefix)\n+{\n+\tint ret;\n+\tconst char *opt_tag = NULL;\n+\tconst char *path;\n+\tchar *config_name;\n+\tstruct option options[] = {\n+\t\t/*\n+\t\t * We accept the `quiet` option for uniformity across subcommands,\n+\t\t * though there is nothing to make less verbose in this subcommand.\n+\t\t */\n+\t\tOPT_NOOP_NOARG('q', \"quiet\"),\n+\n+\t\tOPT_STRING('t', \"tag\", &opt_tag, N_(\"tag\"),\n+\t\t\tN_(\"set the tracking tag\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule set-tag [-q|--quiet] (-t|--tag) <tag> <path>\"),\n+\t\tNULL\n+\t};\n+\tconst struct submodule *sub;\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (!opt_tag)\n+\t\tdie(_(\"--tag required\"));\n+\n+\tif (argc != 1 || !(path = argv[0]))\n+\t\tusage_with_options(usage, options);\n+\n+\tsub = submodule_from_path(the_repository, null_oid(), path);\n+\n+\tif (!sub)\n+\t\tdie(_(\"no submodule mapping found in .gitmodules for path '%s'\"),\n+\t\t    path);\n+\n+\tconfig_name = xstrfmt(\"submodule.%s.tag\", sub->tag);\n+\tret = config_set_in_gitmodules_file_gently(config_name, opt_tag);\n+\n+\tfree(config_name);\n+\treturn !!ret;\n+}\n+\n static int module_create_branch(int argc, const char **argv, const char *prefix)\n {\n \tenum branch_track track;\n@@ -3098,6 +3193,7 @@ static int module_create_branch(int argc, const char **argv, const char *prefix)\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n+\tconst char *tag;\n \tconst char *reference_path;\n \tchar *sm_path;\n \tconst char *sm_name;\n@@ -3219,7 +3315,9 @@ static int add_submodule(const struct add_data *add_data)\n \t\t */\n \t\tstrvec_pushl(&cp.args, \"checkout\", \"-f\", \"-q\", NULL);\n \n-\t\tif (add_data->branch) {\n+\t\tif (add_data->tag) {\n+\t\t\tstrvec_pushf(&cp.args, \"%s\", add_data->tag);\n+\t\t} else if (add_data->branch) {\n \t\t\tstrvec_pushl(&cp.args, \"-B\", add_data->branch, NULL);\n \t\t\tstrvec_pushf(&cp.args, \"origin/%s\", add_data->branch);\n \t\t}\n@@ -3272,7 +3370,11 @@ static void configure_added_submodule(struct add_data *add_data)\n \t    config_submodule_in_gitmodules(add_data->sm_name, \"url\", add_data->repo))\n \t\tdie(_(\"Failed to register submodule '%s'\"), add_data->sm_path);\n \n-\tif (add_data->branch) {\n+\tif (add_data->tag) {\n+\t\tif (config_submodule_in_gitmodules(add_data->sm_name,\n+\t\t\t\t\t\t\"tag\", add_data->tag))\n+\t\t\tdie(_(\"Failed to register submodule '%s'\"), add_data->sm_path);\n+\t} else if (add_data->branch) {\n \t\tif (config_submodule_in_gitmodules(add_data->sm_name,\n \t\t\t\t\t\t   \"branch\", add_data->branch))\n \t\t\tdie(_(\"Failed to register submodule '%s'\"), add_data->sm_path);\n@@ -3371,6 +3473,8 @@ static int module_add(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_STRING('b', \"branch\", &add_data.branch, N_(\"branch\"),\n \t\t\t   N_(\"branch of repository to add as submodule\")),\n+\t\tOPT_STRING('t', \"tag\", &add_data.tag, N_(\"tag\"),\n+\t\t\t   N_(\"tag of repository to add as submodule\")),\n \t\tOPT__FORCE(&force, N_(\"allow adding an otherwise ignored submodule path\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n@@ -3506,6 +3610,7 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\n \t\tOPT_SUBCOMMAND(\"set-url\", &fn, module_set_url),\n \t\tOPT_SUBCOMMAND(\"set-branch\", &fn, module_set_branch),\n \t\tOPT_SUBCOMMAND(\"create-branch\", &fn, module_create_branch),\n+\t\tOPT_SUBCOMMAND(\"set-tag\", &fn, module_set_tag),\n \t\tOPT_END()\n \t};\n \targc = parse_options(argc, argv, prefix, options, usage, 0);\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 60a22d619a8..5e087bc443a 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3057,6 +3057,9 @@ _git_remote ()\n \tset-head,--*)\n \t\t__gitcomp_builtin remote_set-head\n \t\t;;\n+\tset-tag,--*)\n+\t\t__gitcomp_builtin remote_set-tag\n+\t\t;;\n \tset-branches,--*)\n \t\t__gitcomp_builtin remote_set-branches\n \t\t;;\n@@ -3471,7 +3474,7 @@ _git_submodule ()\n {\n \t__git_has_doubledash && return\n \n-\tlocal subcommands=\"add status init deinit update set-branch set-url summary foreach sync absorbgitdirs\"\n+\tlocal subcommands=\"add status init deinit update set-branch set-tag set-url summary foreach sync absorbgitdirs\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\tcase \"$cur\" in\n@@ -3502,6 +3505,9 @@ _git_submodule ()\n \t\t\t--force --rebase --merge --reference --depth --recursive --jobs\n \t\t\"\n \t\t;;\n+\tset-tag,--*)\n+\t\t__gitcomp \"--tag\"\n+\t\t;;\n \tset-branch,--*)\n \t\t__gitcomp \"--default --branch\"\n \t\t;;\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 7f9582d9234..bbb17244d73 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -6,12 +6,13 @@\n \n dashless=$(basename \"$0\" | sed -e 's/-/ /')\n USAGE=\"[--quiet] [--cached]\n-   or: $dashless [--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n+   or: $dashless [--quiet] add [-b <branch>] [-t <tag>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] init [--] [<path>...]\n    or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...)\n-   or: $dashless [--quiet] update [--init [--filter=<filter-spec>]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...]\n+   or: $dashless [--quiet] update [--init [--filter=<filter-spec>]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--[no-]single-tag] [--] [<path>...]\n    or: $dashless [--quiet] set-branch (--default|--branch <branch>) [--] <path>\n+   or: $dashless [--quiet] set-tag (--tag <tag>) [--] <path>\n    or: $dashless [--quiet] set-url [--] <path> <newurl>\n    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n    or: $dashless [--quiet] foreach [--recursive] <command>\n@@ -49,6 +50,7 @@ depth=\n progress=\n dissociate=\n single_branch=\n+single_tag=\n jobs=\n recommend_shallow=\n filter=\n@@ -77,6 +79,11 @@ cmd_add()\n \t\t\tbranch=$2\n \t\t\tshift\n \t\t\t;;\n+\t\t-t | --tag)\n+\t\t\tcase \"$2\" in '') usage ;; esac\n+\t\t\ttag=$2\n+\t\t\tshift\n+\t\t\t;;\n \t\t-f | --force)\n \t\t\tforce=$1\n \t\t\t;;\n@@ -129,7 +136,7 @@ cmd_add()\n \t\tusage\n \tfi\n \n-\tgit ${wt_prefix:+-C \"$wt_prefix\"} submodule--helper add ${quiet:+--quiet} ${force:+--force} ${progress:+\"--progress\"} ${branch:+--branch \"$branch\"} ${reference_path:+--reference \"$reference_path\"} ${dissociate:+--dissociate} ${custom_name:+--name \"$custom_name\"} ${depth:+\"$depth\"} -- \"$@\"\n+\tgit ${wt_prefix:+-C \"$wt_prefix\"} submodule--helper add ${quiet:+--quiet} ${force:+--force} ${progress:+\"--progress\"} ${branch:+--branch \"$branch\"}  ${tag:+--tag \"$tag\"} ${reference_path:+--reference \"$reference_path\"} ${dissociate:+--dissociate} ${custom_name:+--name \"$custom_name\"} ${depth:+\"$depth\"} -- \"$@\"\n }\n \n #\n@@ -316,6 +323,12 @@ cmd_update()\n \t\t--no-single-branch)\n \t\t\tsingle_branch=\"--no-single-branch\"\n \t\t\t;;\n+\t\t--single-tag)\n+\t\t\tsingle_tag=\"--single-tag\"\n+\t\t\t;;\n+\t\t--no-single-tag)\n+\t\t\tsingle_tag=\"--no-single-tag\"\n+\t\t\t;;\n \t\t--filter)\n \t\t\tcase \"$2\" in '') usage ;; esac\n \t\t\tfilter=\"--filter=$2\"\n@@ -355,6 +368,7 @@ cmd_update()\n \t\t${require_init:+--require-init} \\\n \t\t${dissociate:+\"--dissociate\"} \\\n \t\t$single_branch \\\n+\t\t$single_tag \\\n \t\t$recommend_shallow \\\n \t\t$jobs \\\n \t\t$filter \\\n@@ -402,6 +416,43 @@ cmd_set_branch() {\n \tgit ${wt_prefix:+-C \"$wt_prefix\"} submodule--helper set-branch ${quiet:+--quiet} ${branch:+--branch \"$branch\"} ${default:+--default} -- \"$@\"\n }\n \n+#\n+# Configures a submodule's default tag\n+#\n+# $@ = requested path\n+#\n+cmd_set_tag() {\n+\tdefault=\n+\ttag=\n+\n+\twhile test $# -ne 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-q|--quiet)\n+\t\t\t# we don't do anything with this but we need to accept it\n+\t\t\t;;\n+\t\t-t|--tag)\n+\t\t\tcase \"$2\" in '') usage ;; esac\n+\t\t\ttag=$2\n+\t\t\tshift\n+\t\t\t;;\n+\t\t--)\n+\t\t\tshift\n+\t\t\tbreak\n+\t\t\t;;\n+\t\t-*)\n+\t\t\tusage\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone\n+\n+\tgit ${wt_prefix:+-C \"$wt_prefix\"} submodule--helper set-tag ${quiet:+--quiet} ${tag:+--tag \"$tag\"} -- \"$@\"\n+}\n+\n #\n # Configures a submodule's remote url\n #\n@@ -571,7 +622,7 @@ cmd_absorbgitdirs()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | deinit | update | set-branch | set-url | status | summary | sync | absorbgitdirs)\n+\tadd | foreach | init | deinit | update | set-branch | set-tag | set-url | status | summary | sync | absorbgitdirs)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex ec45ea67b95..a258406cced 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -93,6 +93,7 @@ static void free_one_config(struct submodule_entry *entry)\n \tfree((void *) entry->config->branch);\n \tfree((void *) entry->config->url);\n \tfree((void *) entry->config->ignore);\n+\tfree((void *) entry->config->tag);\n \tfree((void *) entry->config->update_strategy.command);\n \tfree(entry->config);\n }\n@@ -415,6 +416,7 @@ static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache,\n \tsubmodule->fetch_recurse = RECURSE_SUBMODULES_NONE;\n \tsubmodule->ignore = NULL;\n \tsubmodule->branch = NULL;\n+\tsubmodule->tag = NULL;\n \tsubmodule->recommend_shallow = -1;\n \n \toidcpy(&submodule->gitmodules_oid, gitmodules_oid);\n@@ -660,6 +662,8 @@ static int parse_config(const char *var, const char *value,\n \t} else if (!strcmp(item.buf, \"branch\")) {\n \t\tif (!value)\n \t\t\tret = config_error_nonbool(var);\n+\t\telse if (submodule->branch && submodule->tag)\n+\t\t\tdie(_(\"can not specify both tag and branch '%s'\"), var);\n \t\telse if (!me->overwrite && submodule->branch)\n \t\t\twarn_multiple_config(me->treeish_name, submodule->name,\n \t\t\t\t\t     \"branch\");\n@@ -667,6 +671,16 @@ static int parse_config(const char *var, const char *value,\n \t\t\tfree((void *)submodule->branch);\n \t\t\tsubmodule->branch = xstrdup(value);\n \t\t}\n+\t} else if (!strcmp(item.buf, \"tag\")) {\n+\t\tif (!value)\n+\t\t\tret = config_error_nonbool(var);\n+\t\telse if (!me->overwrite && submodule->tag)\n+\t\t\twarn_multiple_config(me->treeish_name, submodule->name,\n+\t\t\t\t\t     \"tag\");\n+\t\telse {\n+\t\t\tfree((void *)submodule->tag);\n+\t\t\tsubmodule->tag = xstrdup(value);\n+\t\t}\n \t}\n \n \tstrbuf_release(&name);\ndiff --git a/submodule-config.h b/submodule-config.h\nindex b6133af71b0..522683cc130 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -38,6 +38,7 @@ struct submodule {\n \tenum submodule_recurse_mode fetch_recurse;\n \tconst char *ignore;\n \tconst char *branch;\n+\tconst char *tag;\n \tstruct submodule_update_strategy update_strategy;\n \t/* the object id of the responsible .gitmodules file */\n \tstruct object_id gitmodules_oid;\n\nbase-commit: d63586cb314731c851f28e14fc8012988467e2da\n-- \ngitgitgadget\n"},{"id":"497563","messageId":"m0wmmepj3b.fsf@epic96565.epic.com","threadId":"61642","inReplyTo":"pull.1703.git.git.1718625706679.gitgitgadget@gmail.com","subject":"Re: [PATCH] submodule: allow gitsubmodules to be pointed at tags","fromName":"Sean Allred","fromEmail":"allred.sean@gmail.com","sentAt":"2024-06-24T13:26:48Z","receivedAt":"2024-06-24T13:26:50Z","isPatch":true,"sender":{"key":"allred.sean@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2082195?v=4"},"body":"@dark0dave: sorry for the duplicate message; mu4e changed its default\nkeybindings recently and I neglected to reply to the list as well...\n\n\"dark0dave via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Intent: Enable git submodules to point at tags,\n> effectively freezing them\n>\n> Context: There is a large repo with 50+ submodules, the goal is\n> to have some submodules update to HEAD but others to remain fixed.\n> The update command does this by recursing through\n> directories. This is very useful, however if an update is run,\n> all the submodules are updated to HEAD.\n>\n> The goals:\n> - Be able to create a submodule from a fixed tag (complete)\n> - Be able to run git submodule update, with a flag to either update\n> or keep the submodule pointed at the supplied tag (complete)\n\nThis is an interesting idea, so far as I understand it. I certainly\nappreciate the desire to freeze your dependencies. I don't use\nsubmodules often, but I do want to point out an inherent assumption in\nyour design: that tags don't move.\n\nTags can move. It's usually impolite, but there can be reasonable\nuse-cases for doing so.\n\nWith this in mind, what can go wrong if you have a submodule with a\nconfiguration that doesn't necessarily match the tree?\n\n-- \nSean Allred\n"},{"id":"497564","messageId":"m0sex2pi7q.fsf@epic96565.epic.com","threadId":"61642","inReplyTo":"7D5BFC58-9BF1-4818-828D-453C416949EB@mykolab.com","subject":"Re: [PATCH] submodule: allow gitsubmodules to be pointed at tags","fromName":"Sean Allred","fromEmail":"allred.sean@gmail.com","sentAt":"2024-06-24T13:45:45Z","receivedAt":"2024-06-24T13:45:49Z","isPatch":true,"sender":{"key":"allred.sean@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2082195?v=4"},"body":"> On 24 June 2024 13:26:48 UTC, Sean Allred <allred.sean@gmail.com> wrote:\n>>This is an interesting idea, so far as I understand it. I certainly\n>>appreciate the desire to freeze your dependencies. I don't use\n>>submodules often, but I do want to point out an inherent assumption in\n>>your design: that tags don't move.\n>>\n>>Tags can move. It's usually impolite, but there can be reasonable\n>>use-cases for doing so.\n>>\n>>With this in mind, what can go wrong if you have a submodule with a\n>>configuration that doesn't necessarily match the tree?\n\nDavid <dark0dave@mykolab.com> writes:\n> That's fine, my assumption is that if the tag moved the owner of the\n> repo intends this to happen. Like a hot fix or security change.\n>\n> What will happen is on update the submodule will be moved to the new\n> commit. Just like if a branch was updated. Its very similar code\n> between the two, as this is a super light touch change.\n\nI'm assuming that the superproject tree itself still gets the subproject\ncommit OID, so the commit is still stable when tags in the subproject\nare moved (setting aside the possibility that the commit in the\nsubproject may no longer be reachable... *shudder*). But doesn't it seem\nodd -- particularly for the concept of tags -- that the behavior of\nupdating that tree could change depending on when you do it? For\ndevelopers that add external dependencies as subprojects and choose to\nuse this tagging feature, they could be in for a real surprise if they\ndon't realize this quirk of the implementation -- and they could\nconsider that behavior broken.\n\nIf you're ok with that behavior, then does it make sense to generalize\nit to the concept of a ref instead of just a tag name? This may do a\nbetter job of communicating the idea that no ref is inherently\nimmovable. (Not saying this is a good idea, but answering that question\nmight further hone in on why tags are the 'right' choice here as opposed\nto any named commit.)\n\n-- \nSean Allred\n"},{"id":"497578","messageId":"xmqq4j9icn0t.fsf@gitster.g","threadId":"61642","inReplyTo":"m0sex2pi7q.fsf@epic96565.epic.com","subject":"Re: [PATCH] submodule: allow gitsubmodules to be pointed at tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-24T16:40:18Z","receivedAt":"2024-06-24T16:40:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sean Allred <allred.sean@gmail.com> writes:\n\n> If you're ok with that behavior, then does it make sense to\n> generalize it to the concept of a ref instead of just a tag name?\n> This may do a better job of communicating the idea that no ref is\n> inherently immovable.\n\nYup, I only took a brief glance at the patch, and I didn't quite see\nthe point of duplicationg an almost parallel mechanism to an\nexisting (--single-branch, set-branch) pair of the feature, only to\nreplace \"branch\" with \"tag\" (the patch noise / code churn did not\nseem worth it).\n\nIn other words, is there a situation where a branch cannot be used\n(or using a branch is more cumbersome or awkward) when the user\nwants to use a tag with this patch?  As users can just as easily\nmove tags as they can move branches, the verb \"freeze\" used in the\nproposed log message probably does not reflect reality anyway, as\nyou already pointed out.\n\nThanks.\n"}]}