submodule: allow gitsubmodules to be pointed at tags
PATCH 1/1 submodule: allow gitsubmodules to be pointed at tags
From: dark0dave <[email protected]>
Intent: Enable git submodules to point at tags,
effectively freezing them
Context: There is a large repo with 50+ submodules, the goal is to have some submodules update to HEAD but others to remain fixed. The update command does this by recursing through directories. This is very useful, however if an update is run, all the submodules are updated to HEAD.
The goals:
- Be able to create a submodule from a fixed tag (complete)
- Be able to run git submodule update, with a flag to either update or keep the submodule pointed at the supplied tag (complete)
Example:
git submodule add changes
git submodule add --tag v34.3
https://github.com/Gibberlings3/SwordCoastStratagems.git
cat .gitmodules
[submodule "SwordCoastStratagems"]
path = SwordCoastStratagems
url = https://github.com/Gibberlings3/SwordCoastStratagems.git
tag = v34.3
git submodule update changes
git submodule update --remote -f --checkout
No changes as we are fixed to the tag
git submodule update --remote -f --checkout --no-single-tag Submodule path 'SwordCoastStratagems': checked out '77426a2e14dbe584af811f922a6da3a48d74c074'
Checks out to HEAD of remote
CC: Revi Ewer <[email protected]>,
Ill Takalook <[email protected]>
---
submodule: allow gitsubmodules to be pointed at tags @dark0dave
I want to enable users freezing their submodules to a tag.
Example:
Add
===
git submodule add changes
git submodule add --tag v34.3 https://github.com/Gibberlings3/SwordCoastStratagems.git
cat .gitmodules
[submodule "SwordCoastStratagems"]
path = SwordCoastStratagems
url = https://github.com/Gibberlings3/SwordCoastStratagems.git
tag = v34.3
Update
======
git submodule update changes
Single tag
==========
git submodule update --remote -f --checkout
$
No changes as we are fixed to the tag
No Single tag
=============
git submodule update --remote -f --checkout --no-single-tag
Submodule path 'SwordCoastStratagems': checked out '77426a2e14dbe584af811f922a6da3a48d74c074'
Checks out to HEAD of remotePublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1703%2Fdark0dave%2Fmaster-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1703/dark0dave/master-v1
Pull-Request: https://github.com/git/git/pull/1703
builtin/submodule--helper.c | 119 +++++++++++++++++++++++-- contrib/completion/git-completion.bash | 8 +- git-submodule.sh | 59 +++++++++++- submodule-config.c | 14 +++ submodule-config.h | 1 + 5 files changed, 189 insertions(+), 12 deletions(-)
builtin/submodule--helper.c +112 -7
@@ -1541,9 +1541,11 @@ struct module_clone_data { unsigned int dissociate: 1; unsigned int require_init: 1;int single_branch; +int single_tag; };#define MODULE_CLONE_DATA_INIT { \ .single_branch = -1, \ + .single_tag = -1, \ }struct submodule_alternate_setup {
@@ -1754,6 +1756,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,strvec_push (&cp.args, clone_data->single_branch ?"--single-branch" :"--no-single-branch" ); +if (clone_data->single_tag >=0 ) +strvec_push (&cp.args, clone_data->single_tag ? +"--single-branch" : +"--no-single-branch" );strvec_push (&cp.args,"--" );strvec_push (&cp.args, clone_data->url);
@@ -1981,6 +1987,7 @@ struct update_data {int depth;int max_jobs;int single_branch; +int single_tag;int recommend_shallow;unsigned int require_init;unsigned int force;
@@ -2004,6 +2011,7 @@ struct update_data { .recommend_shallow = -1, \ .references = STRING_LIST_INIT_DUP, \ .single_branch = -1, \ + .single_tag = -1, \ .max_jobs = 1, \ }
@@ -2151,6 +2159,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,strvec_push (&child->args, suc->update_data->single_branch ?"--single-branch" :"--no-single-branch" ); +if (suc->update_data->single_tag >=0 ) +strvec_push (&child->args, suc->update_data->single_tag ? +"--single-tag" : +"--no-single-tag" ); cleanup:free (displaypath);
@@ -2297,6 +2309,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, cp.dir = module_path;strvec_push (&cp.args,"fetch" ); +if (quiet)strvec_push (&cp.args,"--quiet" );if (depth)
@@ -2434,6 +2447,30 @@ static int run_update_procedure(const struct update_data *ud)return run_update_command (ud, subforce); } +static int remote_submodule_tag (const char *path,const char **tag) +{ +const struct submodule *sub; +char *key; + *tag =NULL ; + + sub =submodule_from_path (the_repository,null_oid (), path); +if (!sub) +return die_message (_ ("could not initialize submodule at path '%s'" ), + path); + + key =xstrfmt ("submodule.%s.tag" , sub->name); +if (repo_config_get_string_tmp (the_repository, key, tag)) + *tag = sub->tag; +free (key); + +if (!*tag) { +/* No tag found */ +return 1 ; + } + +return 0 ; +} +static int remote_submodule_branch (const char *path,const char **branch) {const struct submodule *sub;
@@ -2579,6 +2616,10 @@ static void update_data_to_args(const struct update_data *update_data,strvec_push (args, update_data->single_branch ?"--single-branch" :"--no-single-branch" ); +if (update_data->single_tag >=0 ) +strvec_push (args, update_data->single_tag ? +"--single-branch" : +"--no-single-branch" ); }static int update_submodule (struct update_data *update_data)
@@ -2606,16 +2647,22 @@ static int update_submodule(struct update_data *update_data)if (update_data->remote) {char *remote_name;const char *branch; +const char *tag;char *remote_ref;int code; code =get_default_remote_submodule (update_data->sm_path, &remote_name);if (code)return code; - code =remote_submodule_branch (update_data->sm_path, &branch); -if (code) -return code; - remote_ref =xstrfmt ("refs/remotes/%s/%s" , remote_name, branch); + code =remote_submodule_tag (update_data->sm_path, &tag); +if (!code && update_data->single_tag) { + remote_ref =xstrfmt ("refs/tags/%s" , tag); + }else { + code =remote_submodule_branch (update_data->sm_path, &branch); +if (code) +return code; + remote_ref =xstrfmt ("refs/remotes/%s/%s" , remote_name, branch); + }free (remote_name);
@@ -2781,6 +2828,8 @@ static int module_update(int argc, const char **argv, const char *prefix) N_("disallow cloning into non-empty directory, implies --init")),OPT_BOOL (0 ,"single-branch" , &opt.single_branch,N_ ("clone only one branch, HEAD or --branch" )), +OPT_BOOL (0 ,"single-tag" , &opt.single_tag, +N_ ("clone only one tag, --tag" )),OPT_PARSE_LIST_OBJECTS_FILTER (&filter_options), OPT_END() };
@@ -2790,7 +2839,7 @@ static int module_update(int argc, const char **argv, const char *prefix)" [-N|--no-fetch] [-f|--force]" " [--checkout|--merge|--rebase]" " [--[no-]recommend-shallow] [--reference <repository>]" - " [--recursive] [--[no-]single-branch] [--] [<path>...]"), + " [--recursive] [--[no-]single-branch] [--[no-]single-tag] [--] [<path>...]"), NULL };
@@ -3058,6 +3107,52 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return !!ret; } + +static int module_set_tag (int argc,const char **argv,const char *prefix) +{ +int ret; +const char *opt_tag =NULL ; +const char *path; +char *config_name; +struct option options[] = { +/* +* We accept the `quiet` option for uniformity across subcommands, +* though there is nothing to make less verbose in this subcommand. +*/ +OPT_NOOP_NOARG ('q' ,"quiet" ), + +OPT_STRING ('t' ,"tag" , &opt_tag,N_ ("tag" ), +N_ ("set the tracking tag" )), +OPT_END () + }; + +const char *const usage[] = { +N_ ("git submodule set-tag [-q|--quiet] (-t|--tag) <tag> <path>" ), +NULL + }; +const struct submodule *sub; + + argc =parse_options (argc, argv, prefix, options, usage,0 ); + +if (!opt_tag) +die (_ ("--tag required" )); + +if (argc !=1 || !(path = argv[0 ])) +usage_with_options (usage, options); + + sub =submodule_from_path (the_repository,null_oid (), path); + +if (!sub) +die (_ ("no submodule mapping found in .gitmodules for path '%s'" ), + path); + + config_name =xstrfmt ("submodule.%s.tag" , sub->tag); + ret =config_set_in_gitmodules_file_gently (config_name, opt_tag); + +free (config_name); +return !!ret; +} +static int module_create_branch (int argc,const char **argv,const char *prefix) {enum branch_track track;
@@ -3098,6 +3193,7 @@ static int module_create_branch(int argc, const char **argv, const char *prefix)struct add_data {const char *prefix;const char *branch; +const char *tag;const char *reference_path;char *sm_path;const char *sm_name;
@@ -3219,7 +3315,9 @@ static int add_submodule(const struct add_data *add_data)*/ strvec_pushl (&cp.args,"checkout" ,"-f" ,"-q" ,NULL ); -if (add_data->branch) { +if (add_data->tag) { +strvec_pushf (&cp.args,"%s" , add_data->tag); + }else if (add_data->branch) {strvec_pushl (&cp.args,"-B" , add_data->branch,NULL );strvec_pushf (&cp.args,"origin/%s" , add_data->branch); }
@@ -3272,7 +3370,11 @@ static void configure_added_submodule(struct add_data *add_data) config_submodule_in_gitmodules(add_data->sm_name, "url", add_data->repo))die (_ ("Failed to register submodule '%s'" ), add_data->sm_path); -if (add_data->branch) { +if (add_data->tag) { +if (config_submodule_in_gitmodules (add_data->sm_name, +"tag" , add_data->tag)) +die (_ ("Failed to register submodule '%s'" ), add_data->sm_path); + }else if (add_data->branch) {if (config_submodule_in_gitmodules (add_data->sm_name,"branch" , add_data->branch))die (_ ("Failed to register submodule '%s'" ), add_data->sm_path);
@@ -3371,6 +3473,8 @@ static int module_add(int argc, const char **argv, const char *prefix)struct option options[] = {OPT_STRING ('b' ,"branch" , &add_data.branch,N_ ("branch" ),N_ ("branch of repository to add as submodule" )), +OPT_STRING ('t' ,"tag" , &add_data.tag,N_ ("tag" ), +N_ ("tag of repository to add as submodule" )),OPT__FORCE (&force,N_ ("allow adding an otherwise ignored submodule path" ),PARSE_OPT_NOCOMPLETE ),OPT__QUIET (&quiet,N_ ("print only error messages" )),
@@ -3506,6 +3610,7 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix)OPT_SUBCOMMAND ("set-url" , &fn, module_set_url),OPT_SUBCOMMAND ("set-branch" , &fn, module_set_branch),OPT_SUBCOMMAND ("create-branch" , &fn, module_create_branch), +OPT_SUBCOMMAND ("set-tag" , &fn, module_set_tag), OPT_END() }; argc =parse_options (argc, argv, prefix, options, usage,0 );
contrib/completion/git-completion.bash +7 -1
@@ -3057,6 +3057,9 @@ _git_remote () set-head,--*) __gitcomp_builtin remote_set-head ;; + set-tag,--*) + __gitcomp_builtin remote_set-tag + ;; set-branches,--*) __gitcomp_builtin remote_set-branches ;;
@@ -3471,7 +3474,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init deinit update set-branch set-url summary foreach sync absorbgitdirs" + local subcommands="add status init deinit update set-branch set-tag set-url summary foreach sync absorbgitdirs" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in
@@ -3502,6 +3505,9 @@ _git_submodule () --force --rebase --merge --reference --depth --recursive --jobs " ;; + set-tag,--*) + __gitcomp "--tag" + ;; set-branch,--*) __gitcomp "--default --branch" ;;
git-submodule.sh +55 -4
111 lines not on this page · raw message
submodule-config.c +14 -0
@@ -93,6 +93,7 @@ static void free_one_config(struct submodule_entry *entry)free ((void *) entry->config->branch);free ((void *) entry->config->url);free ((void *) entry->config->ignore); +free ((void *) entry->config->tag);free ((void *) entry->config->update_strategy.command);free (entry->config); }
@@ -415,6 +416,7 @@ static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, submodule->fetch_recurse =RECURSE_SUBMODULES_NONE ; submodule->ignore =NULL ; submodule->branch =NULL ; + submodule->tag =NULL ; submodule->recommend_shallow =-1 ;oidcpy (&submodule->gitmodules_oid, gitmodules_oid);
@@ -660,6 +662,8 @@ static int parse_config(const char *var, const char *value, }else if (!strcmp (item.buf,"branch" )) {if (!value) ret =config_error_nonbool (var); +else if (submodule->branch && submodule->tag) +die (_ ("can not specify both tag and branch '%s'" ), var);else if (!me->overwrite && submodule->branch)warn_multiple_config (me->treeish_name, submodule->name,"branch" );
@@ -667,6 +671,16 @@ static int parse_config(const char *var, const char *value,free ((void *)submodule->branch); submodule->branch =xstrdup (value); } + }else if (!strcmp (item.buf,"tag" )) { +if (!value) + ret =config_error_nonbool (var); +else if (!me->overwrite && submodule->tag) +warn_multiple_config (me->treeish_name, submodule->name, +"tag" ); +else { +free ((void *)submodule->tag); + submodule->tag =xstrdup (value); + } }strbuf_release (&name);
submodule-config.h +1 -0
@@ -38,6 +38,7 @@ struct submodule {enum submodule_recurse_mode fetch_recurse;const char *ignore;const char *branch; +const char *tag;struct submodule_update_strategy update_strategy;/* the object id of the responsible .gitmodules file */ struct object_id gitmodules_oid;
base-commit: d63586cb314731c851f28e14fc8012988467e2da
Signature · 1 line
gitgitgadget
@dark0dave: sorry for the duplicate message; mu4e changed its default keybindings recently and I neglected to reply to the list as well...
Quoted 9 lines · "dark0dave via GitGitGadget" <[email protected]> writes:
Intent: Enable git submodules to point at tags,
effectively freezing themContext: There is a large repo with 50+ submodules, the goal is to have some submodules update to HEAD but others to remain fixed. The update command does this by recursing through directories. This is very useful, however if an update is run, all the submodules are updated to HEAD.
Quoted 4 lines
The goals:
- Be able to create a submodule from a fixed tag (complete)
- Be able to run git submodule update, with a flag to either update or keep the submodule pointed at the supplied tag (complete)
This is an interesting idea, so far as I understand it. I certainly appreciate the desire to freeze your dependencies. I don't use submodules often, but I do want to point out an inherent assumption in your design: that tags don't move.
Tags can move. It's usually impolite, but there can be reasonable use-cases for doing so.
With this in mind, what can go wrong if you have a submodule with a configuration that doesn't necessarily match the tree?
Signature · 1 line
Sean Allred
Quoted 11 lines · Sean Allred
Quoted 10 lines
This is an interesting idea, so far as I understand it. I certainly appreciate the desire to freeze your dependencies. I don't use submodules often, but I do want to point out an inherent assumption in your design: that tags don't move.
Tags can move. It's usually impolite, but there can be reasonable use-cases for doing so.
With this in mind, what can go wrong if you have a submodule with a configuration that doesn't necessarily match the tree?
Quoted 6 lines · David
That's fine, my assumption is that if the tag moved the owner of the repo intends this to happen. Like a hot fix or security change.
What will happen is on update the submodule will be moved to the new commit. Just like if a branch was updated. Its very similar code between the two, as this is a super light touch change.
I'm assuming that the superproject tree itself still gets the subproject commit OID, so the commit is still stable when tags in the subproject are moved (setting aside the possibility that the commit in the subproject may no longer be reachable... *shudder*). But doesn't it seem odd -- particularly for the concept of tags -- that the behavior of updating that tree could change depending on when you do it? For developers that add external dependencies as subprojects and choose to use this tagging feature, they could be in for a real surprise if they don't realize this quirk of the implementation -- and they could consider that behavior broken.
If you're ok with that behavior, then does it make sense to generalize it to the concept of a ref instead of just a tag name? This may do a better job of communicating the idea that no ref is inherently immovable. (Not saying this is a good idea, but answering that question might further hone in on why tags are the 'right' choice here as opposed to any named commit.)
Signature · 1 line
Sean Allred
Quoted 4 lines · Sean Allred
If you're ok with that behavior, then does it make sense to generalize it to the concept of a ref instead of just a tag name? This may do a better job of communicating the idea that no ref is inherently immovable.
Yup, I only took a brief glance at the patch, and I didn't quite see the point of duplicationg an almost parallel mechanism to an existing (--single-branch, set-branch) pair of the feature, only to replace "branch" with "tag" (the patch noise / code churn did not seem worth it).
In other words, is there a situation where a branch cannot be used (or using a branch is more cumbersome or awkward) when the user wants to use a tag with this patch? As users can just as easily move tags as they can move branches, the verb "freeze" used in the proposed log message probably does not reflect reality anyway, as you already pointed out.
Thanks.