This patch series introduces the `add clone` and `add config`
subcommands in `git submodule--helper` with the goal of converting part
of the shell code in `git-submodule.sh` related to `git submodule add`
into C code.
The first patch [1/2] has already been sent to the mailing list before:
https://lore.kernel.org/git/20210604110524.84326-1-raykar.ath@gmail.com/
No changes have been made to it since then.
Because patch [2/2] depends on changes introduced in [1/2] I am sending
them together as a series.
The eventual goal is to replace all of the shell code with equivalent C
code. Subsequent patches will replace all of shell the code past the
flag parsing of `cmd_add()` into a single call to subcommand
`submodule--helper add` which will make use of the functions introduced
in these two patches.
Link to hosted Git repository for containing these patches:
https://github.com/tfidfwastaken/git/commits/submodule-add-in-c
Atharva Raykar (2):
submodule--helper: introduce add-clone subcommand
submodule--helper: introduce add-config subcommand
builtin/submodule--helper.c | 312 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 66 +-------
2 files changed, 314 insertions(+), 64 deletions(-)
--
2.31.1
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n".
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 113 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +--------
2 files changed, 114 insertions(+), 27 deletions(-)
@@ -2943,6 +2943,118 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++key=xstrfmt("submodule.%s.path",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->sm_path);+free(key);+key=xstrfmt("submodule.%s.url",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->repo);+free(key);+if(add_data->branch){+key=xstrfmt("submodule.%s.branch",add_data->sm_path);+config_set_in_gitmodules_file_gently(key,add_data->branch);+free(key);+}++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec)+warning(_("The submodule.active configuration exists, but "+"no pathspec was specified. Setting the value of "+"submodule.%s.active to 'true'."),add_data->sm_name);++/*+*Ifsubmodule.activedoesnotexist,wewillactivatethis+*moduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_BOOL('f',"force",&force,+N_("allow adding an otherwise ignored submodule path")),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule
name has been supplied with a name that clashes with a local submodule,
the message shown to the user ("A git directory for 'foo' is found
locally...") is prepended with "error" for clarity.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 199 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +------
2 files changed, 200 insertions(+), 37 deletions(-)
@@ -2745,6 +2745,204 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticchar*parse_token(char**begin,constchar*end,int*tok_len)+{+char*tok_start,*pos=*begin;+while(pos!=end&&(*pos!=' '&&*pos!='\t'&&*pos!='\n'))+pos++;+tok_start=*begin;+*tok_len=pos-*begin;+*begin=pos+1;+returntok_start;+}++staticchar*get_next_line(char*constbegin,constchar*constend)+{+char*pos=begin;+while(pos!=end&&*pos++!='\n');+returnpos;+}++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*line;+char*begin=sb_remote_out.buf;+char*end=sb_remote_out.buf+sb_remote_out.len;+while(begin!=end&&(line=get_next_line(begin,end))){+intnamelen=0,urllen=0,taillen=0;+char*name=parse_token(&begin,line,&namelen);+char*url=parse_token(&begin,line,&urllen);+char*tail=parse_token(&begin,line,&taillen);+if(!memcmp(tail,"(fetch)",7))+fprintf(output," %.*s\t%.*s\n",+namelen,name,urllen,url);+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;+/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_directory(submod_gitdir_path)||file_exists(submod_gitdir_path))+printf(_("Adding existing path at '%s' to index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+free(submod_gitdir_path);+}else{+structstrvecclone_args=STRVEC_INIT;+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+error(_("A git directory for '%s' is found "+"locally with remote(s):"),add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+fprintf(stderr,+_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+free(submod_gitdir_path);+return1;+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++strvec_pushl(&clone_args,"clone","--path",add_data->sm_path,"--name",+add_data->sm_name,"--url",add_data->realrepo,NULL);+if(add_data->quiet)+strvec_push(&clone_args,"--quiet");+if(add_data->progress)+strvec_push(&clone_args,"--progress");+if(add_data->prefix)+strvec_pushl(&clone_args,"--prefix",add_data->prefix,NULL);+if(add_data->reference_path)+strvec_pushl(&clone_args,"--reference",+add_data->reference_path,NULL);+if(add_data->dissociate)+strvec_push(&clone_args,"--dissociate");+if(add_data->depth>=0)+strvec_pushf(&clone_args,"--depth=%d",add_data->depth);++if(module_clone(clone_args.nr,clone_args.v,add_data->prefix)){+strvec_clear(&clone_args);+return-1;+}+strvec_clear(&clone_args);++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&add_data.prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT_BOOL('f',"force",&force,+N_("allow adding an otherwise ignored submodule path")),+OPT__QUIET(&quiet,"Suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [--prefix=<path>] [--quiet] [--force] "+"[--reference <repository>] [--depth <depth>] [-b|--branch <branch>]"+"[--progress] [--dissociate] --url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
Is `git` executable that found in $PATH used? I have both
distro-packaged Git and custom-compiled Git installed, and I would like
the latter to be able to use git-submodule from its own install prefix
(/path/to/git-prefix or whatsever).
--
An old man doll... just what I always wanted! - Clara
Is `git` executable that found in $PATH used? I have both
distro-packaged Git and custom-compiled Git installed, and I would like
the latter to be able to use git-submodule from its own install prefix
(/path/to/git-prefix or whatsever).
This is a different issue than what this patch is doing.
From: Christian Couder <hidden> Date: 2021-06-07 09:25:06
On Sat, Jun 5, 2021 at 1:42 PM Atharva Raykar [off-list ref] wrote:
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n".
Maybe explaining a bit how this warning is useful could help reviewers
here. Especially what could happen if no value is specified in
'submodule.active'?
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
[...]
+ /*
+ * NEEDSWORK: In a multi-working-tree world this needs to be
+ * set in the per-worktree config.
+ */
+ pathspec_key_exists = !git_config_get_string("submodule.active",
+ &submod_pathspec);
+ if (pathspec_key_exists && !submod_pathspec)
+ warning(_("The submodule.active configuration exists, but "
+ "no pathspec was specified. Setting the value of "
+ "submodule.%s.active to 'true'."), add_data->sm_name);
It's not very clear that we will actually set
'submodule.<name>.active' to true below as it depends on the result of
calling is_submodule_active(), and anyway is_submodule_active() will
check again if "submodule.active" is set, which is wasteful.
Maybe we could set a variable, called for example "activate" here,
with something like:
if (pathspec_key_exists && !submod_pathspec) {
warning(...);
activate = 1;
}
and below use a check like:
if (!pathspec_key_exists || activate ||
!is_submodule_active(the_repository, add_data->sm_path)) {
...
+ /*
+ * If submodule.active does not exist, we will activate this
+ * module unconditionally.
+ *
+ * Otherwise, we ask is_submodule_active(), which iterates
+ * through all the values of 'submodule.active' to determine
+ * if this module is already active.
+ */
+ if (!pathspec_key_exists ||
+ !is_submodule_active(the_repository, add_data->sm_path)) {
+ key = xstrfmt("submodule.%s.active", add_data->sm_name);
+ git_config_set_gently(key, "true");
+ free(key);
+ }
+}
On 07-Jun-2021, at 14:54, Christian Couder [off-list ref] wrote:
On Sat, Jun 5, 2021 at 1:42 PM Atharva Raykar [off-list ref] wrote:
quoted
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n".
Maybe explaining a bit how this warning is useful could help reviewers
here. Especially what could happen if no value is specified in
'submodule.active'?
Will do.
For now I'll leave an explanation here as well, so that those
who might see this thread can know the motivation behind it.
(I'll make it more concise in my cover letter of v2)
Junio in his review of Shourya's patch[1] said:
When a user has "[submodule] active" in his or her
configuration file, it is a configuration error. When Git reads
"submodule.active" configuration variable to make a decision (like
the above code) and finds that the user has such an error, the user
would appreciate if the error is pointed out, so that it can be
corrected, rather than silently ignored.
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
[...]
quoted
+ /*
+ * NEEDSWORK: In a multi-working-tree world this needs to be
+ * set in the per-worktree config.
+ */
+ pathspec_key_exists = !git_config_get_string("submodule.active",
+ &submod_pathspec);
+ if (pathspec_key_exists && !submod_pathspec)
+ warning(_("The submodule.active configuration exists, but "
+ "no pathspec was specified. Setting the value of "
+ "submodule.%s.active to 'true'."), add_data->sm_name);
It's not very clear that we will actually set
'submodule.<name>.active' to true below as it depends on the result of
calling is_submodule_active()
Hmm, I see the issue.
Would it be more accurate to say this:
"The submodule.active configuration exists, but no pathspec
was specified. If the module is not already active, the value
of 'submodule.<name>.active' will be set to 'true'."
, and anyway is_submodule_active() will
check again if "submodule.active" is set, which is wasteful.
Maybe we could set a variable, called for example "activate" here,
with something like:
if (pathspec_key_exists && !submod_pathspec) {
warning(...);
activate = 1;
}
and below use a check like:
if (!pathspec_key_exists || activate ||
!is_submodule_active(the_repository, add_data->sm_path)) {
Got it. Thanks for suggesting this improvement!
...
quoted
+ /*
+ * If submodule.active does not exist, we will activate this
+ * module unconditionally.
+ *
+ * Otherwise, we ask is_submodule_active(), which iterates
+ * through all the values of 'submodule.active' to determine
+ * if this module is already active.
+ */
+ if (!pathspec_key_exists ||
+ !is_submodule_active(the_repository, add_data->sm_path)) {
+ key = xstrfmt("submodule.%s.active", add_data->sm_name);
+ git_config_set_gently(key, "true");
+ free(key);
+ }
+}
I have elaborated the commit message more, to explain why a warning is emitted
for an empty value in 'submodule.active'. The warning has been worded more
accurately than before.
An unnecessary extra check for 'submodule.active' has been avoided.
I have included a range diff, in case that is useful.
My fork containing these changes can be found at:
https://github.com/tfidfwastaken/git/commits/submodule-add-in-c
Emily and Jonathan: To be on the safe side, I have CC'd you so you know where I
keep my changes, and we can avoid potential conflicts, as I believe you are
working on this area as well. Just so you know, every week, I update the link to
all my ongoing work at:
https://atharvaraykar.me/gitnotes/#my-public-gitgit-branches
Atharva Raykar (2):
submodule--helper: introduce add-clone subcommand
submodule--helper: introduce add-config subcommand
builtin/submodule--helper.c | 315 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 66 +-------
2 files changed, 317 insertions(+), 64 deletions(-)
Range-diff against v1:
1: 398bfa713d = 1: 4374ebb6b1 submodule--helper: introduce add-clone subcommand
2: f9954cfcf7 ! 2: 406022d0f7 submodule--helper: introduce add-config subcommand
@@ Commit message
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
- 'submodule.active', ie, the config looks like: "[submodule] active\n".
+ 'submodule.active', ie, the config looks like: "[submodule] active\n",
+ because it is an invalid configuration. It would be helpful to let the
+ user know that the pathspec is unset, and the value of
+ 'submodule.<name>.active' might be set to 'true' so that they can
+ rectify their configuration and prevent future surprises (especially
+ given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
@@ builtin/submodule--helper.c: static int add_clone(int argc, const char **argv, c
+ char *key, *submod_pathspec = NULL;
+ struct child_process add_submod = CHILD_PROCESS_INIT;
+ struct child_process add_gitmodules = CHILD_PROCESS_INIT;
-+ int pathspec_key_exists;
++ int pathspec_key_exists, activate = 0;
+
+ key = xstrfmt("submodule.%s.url", add_data->sm_name);
+ git_config_set_gently(key, add_data->realrepo);
@@ builtin/submodule--helper.c: static int add_clone(int argc, const char **argv, c
+ */
+ pathspec_key_exists = !git_config_get_string("submodule.active",
+ &submod_pathspec);
-+ if (pathspec_key_exists && !submod_pathspec)
-+ warning(_("The submodule.active configuration exists, but "
-+ "no pathspec was specified. Setting the value of "
-+ "submodule.%s.active to 'true'."), add_data->sm_name);
++ if (pathspec_key_exists && !submod_pathspec) {
++ warning(_("The submodule.active configuration exists, but the "
++ "pathspec was unset. If the submodule is not already "
++ "active, the value of submodule.%s.active will be "
++ "be set to 'true'."), add_data->sm_name);
++ activate = 1;
++ }
+
+ /*
-+ * If submodule.active does not exist, we will activate this
-+ * module unconditionally.
++ * If submodule.active does not exist, or if the pathspec was unset,
++ * we will activate this module unconditionally.
+ *
+ * Otherwise, we ask is_submodule_active(), which iterates
+ * through all the values of 'submodule.active' to determine
+ * if this module is already active.
+ */
-+ if (!pathspec_key_exists ||
++ if (!pathspec_key_exists || activate ||
+ !is_submodule_active(the_repository, add_data->sm_path)) {
+ key = xstrfmt("submodule.%s.active", add_data->sm_name);
+ git_config_set_gently(key, "true");
--
2.31.1
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 199 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +------
2 files changed, 200 insertions(+), 37 deletions(-)
@@ -2745,6 +2745,204 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticchar*parse_token(char**begin,constchar*end,int*tok_len)+{+char*tok_start,*pos=*begin;+while(pos!=end&&(*pos!=' '&&*pos!='\t'&&*pos!='\n'))+pos++;+tok_start=*begin;+*tok_len=pos-*begin;+*begin=pos+1;+returntok_start;+}++staticchar*get_next_line(char*constbegin,constchar*constend)+{+char*pos=begin;+while(pos!=end&&*pos++!='\n');+returnpos;+}++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*line;+char*begin=sb_remote_out.buf;+char*end=sb_remote_out.buf+sb_remote_out.len;+while(begin!=end&&(line=get_next_line(begin,end))){+intnamelen=0,urllen=0,taillen=0;+char*name=parse_token(&begin,line,&namelen);+char*url=parse_token(&begin,line,&urllen);+char*tail=parse_token(&begin,line,&taillen);+if(!memcmp(tail,"(fetch)",7))+fprintf(output," %.*s\t%.*s\n",+namelen,name,urllen,url);+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;+/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_directory(submod_gitdir_path)||file_exists(submod_gitdir_path))+printf(_("Adding existing path at '%s' to index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+free(submod_gitdir_path);+}else{+structstrvecclone_args=STRVEC_INIT;+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+error(_("A git directory for '%s' is found "+"locally with remote(s):"),add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+fprintf(stderr,+_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+free(submod_gitdir_path);+return1;+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++strvec_pushl(&clone_args,"clone","--path",add_data->sm_path,"--name",+add_data->sm_name,"--url",add_data->realrepo,NULL);+if(add_data->quiet)+strvec_push(&clone_args,"--quiet");+if(add_data->progress)+strvec_push(&clone_args,"--progress");+if(add_data->prefix)+strvec_pushl(&clone_args,"--prefix",add_data->prefix,NULL);+if(add_data->reference_path)+strvec_pushl(&clone_args,"--reference",+add_data->reference_path,NULL);+if(add_data->dissociate)+strvec_push(&clone_args,"--dissociate");+if(add_data->depth>=0)+strvec_pushf(&clone_args,"--depth=%d",add_data->depth);++if(module_clone(clone_args.nr,clone_args.v,add_data->prefix)){+strvec_clear(&clone_args);+return-1;+}+strvec_clear(&clone_args);++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&add_data.prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT_BOOL('f',"force",&force,+N_("allow adding an otherwise ignored submodule path")),+OPT__QUIET(&quiet,"Suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [--prefix=<path>] [--quiet] [--force] "+"[--reference <repository>] [--depth <depth>] [-b|--branch <branch>]"+"[--progress] [--dissociate] --url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n",
because it is an invalid configuration. It would be helpful to let the
user know that the pathspec is unset, and the value of
'submodule.<name>.active' might be set to 'true' so that they can
rectify their configuration and prevent future surprises (especially
given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 116 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +--------
2 files changed, 117 insertions(+), 27 deletions(-)
@@ -2943,6 +2943,121 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++key=xstrfmt("submodule.%s.path",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->sm_path);+free(key);+key=xstrfmt("submodule.%s.url",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->repo);+free(key);+if(add_data->branch){+key=xstrfmt("submodule.%s.branch",add_data->sm_path);+config_set_in_gitmodules_file_gently(key,add_data->branch);+free(key);+}++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec){+warning(_("The submodule.active configuration exists, but the "+"pathspec was unset. If the submodule is not already "+"active, the value of submodule.%s.active will be "+"be set to 'true'."),add_data->sm_name);+activate=1;+}++/*+*Ifsubmodule.activedoesnotexist,orifthepathspecwasunset,+*wewillactivatethismoduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||activate||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_BOOL('f',"force",&force,+N_("allow adding an otherwise ignored submodule path")),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#
From: Đoàn Trần Công Danh <hidden> Date: 2021-06-08 12:33:16
On 2021-06-08 15:26:54+0530, Atharva Raykar [off-list ref] wrote:
quoted hunk
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 199 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +------
2 files changed, 200 insertions(+), 37 deletions(-)
On my first glance, this function looks like a reinvention of strchr(3).
Except that, this function also has a second parameter for "end".
Maybe it has a specical use-case?
And this is the only use-case. Because you also want to check if you
reached the last token or not. I guess you really meant to write:
while ((line = strchr(begin, '\n')) != NULL) {
Anyway, I would name the "line" variable as "nextline"
I think this whole block is better replaced with strip_suffix_mem and
fprintf.
Overral I would replace the block inside capture_command with:
-----8<-----
char *nextline;
char *line = sb_remote_out.buf;
while ((nextline = strchr(line, '\n')) != NULL) {
size_t len = nextline - line;
if (strip_suffix_mem(line, &len, "(fetch)"))
fprintf(output, " %.*s\n", (int)len, line);
line = nextline + 1;
}
---->8-----
And get rid of parse_token and get_next_line functions.
+ }
+ }
+
+ strbuf_release(&sb_remote_out);
+}
+
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
+ if (is_directory(submod_gitdir_path) || file_exists(submod_gitdir_path))
+ printf(_("Adding existing path at '%s' to index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
+ free(submod_gitdir_path);
+ } else {
+ struct strvec clone_args = STRVEC_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
+ error(_("A git directory for '%s' is found "
+ "locally with remote(s):"), add_data->sm_name);
We don't capitalise first character of error message.
IOW, downcase "A git".
Well, it's bug-for-bug with shell implementation, so it doesn't matter much, anyway.
+ show_fetch_remotes(stderr, add_data->sm_name,
+ submod_gitdir_path);
+ fprintf(stderr,
+ _("If you want to reuse this local git "
+ "directory instead of cloning again from\n"
+ " %s\n"
+ "use the '--force' option. If the local git "
+ "directory is not the correct repo\n"
+ "or if you are unsure what this means, choose "
+ "another name with the '--name' option.\n"),
+ add_data->realrepo);
Is there any reason we can't use "error" here?
+ free(submod_gitdir_path);
+ return 1;
+ } else {
+ printf(_("Reactivating local git directory for "
+ "submodule '%s'\n"), add_data->sm_name);
+ }
+ }
+ free(submod_gitdir_path);
+
+ strvec_pushl(&clone_args, "clone", "--path", add_data->sm_path, "--name",
+ add_data->sm_name, "--url", add_data->realrepo, NULL);
+ if (add_data->quiet)
+ strvec_push(&clone_args, "--quiet");
+ if (add_data->progress)
+ strvec_push(&clone_args, "--progress");
+ if (add_data->prefix)
+ strvec_pushl(&clone_args, "--prefix", add_data->prefix, NULL);
+ if (add_data->reference_path)
+ strvec_pushl(&clone_args, "--reference",
+ add_data->reference_path, NULL);
+ if (add_data->dissociate)
+ strvec_push(&clone_args, "--dissociate");
+ if (add_data->depth >= 0)
+ strvec_pushf(&clone_args, "--depth=%d", add_data->depth);
+
+ if (module_clone(clone_args.nr, clone_args.v, add_data->prefix)) {
+ strvec_clear(&clone_args);
+ return -1;
+ }
+ strvec_clear(&clone_args);
+
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.git_cmd = 1;
+ cp.dir = add_data->sm_path;
+ strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
+
+ if (add_data->branch) {
+ strvec_pushl(&cp.args, "-B", add_data->branch, NULL);
+ strvec_pushf(&cp.args, "origin/%s", add_data->branch);
+ }
+
+ if (run_command(&cp))
+ die(_("unable to checkout submodule '%s'"), add_data->sm_path);
+ }
+ return 0;
+}
+
+static int add_clone(int argc, const char **argv, const char *prefix)
+{
+ int force = 0, quiet = 0, dissociate = 0, progress = 0;
+ struct add_data add_data = ADD_DATA_INIT;
+
+ struct option options[] = {
+ OPT_STRING('b', "branch", &add_data.branch,
+ N_("branch"),
+ N_("branch of repository to checkout on cloning")),
+ OPT_STRING(0, "prefix", &add_data.prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &add_data.sm_path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &add_data.sm_name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &add_data.realrepo,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING(0, "reference", &add_data.reference_path,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate,
+ N_("use --reference only while cloning")),
+ OPT_INTEGER(0, "depth", &add_data.depth,
+ N_("depth for shallow clones")),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT_BOOL('f', "force", &force,
+ N_("allow adding an otherwise ignored submodule path")),
We have OPT__FORCE, too.
+ OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
On my first glance, this function looks like a reinvention of strchr(3).
Except that, this function also has a second parameter for "end".
Maybe it has a specical use-case?
And this is the only use-case. Because you also want to check if you
reached the last token or not. I guess you really meant to write:
while ((line = strchr(begin, '\n')) != NULL) {
Anyway, I would name the "line" variable as "nextline"
I think this whole block is better replaced with strip_suffix_mem and
fprintf.
Overral I would replace the block inside capture_command with:
-----8<-----
char *nextline;
char *line = sb_remote_out.buf;
while ((nextline = strchr(line, '\n')) != NULL) {
size_t len = nextline - line;
if (strip_suffix_mem(line, &len, "(fetch)"))
fprintf(output, " %.*s\n", (int)len, line);
line = nextline + 1;
}
---->8-----
And get rid of parse_token and get_next_line functions.
That looks much simpler. Thanks!
I realised that all the token parsing I do is not really necessary.
What I really want to do is "If this line ends with '(fetch)',
print it, but without the '(fetch)'", and I think your version
captures that succinctly.
quoted
+ }
+ }
+
+ strbuf_release(&sb_remote_out);
+}
+
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
+ if (is_directory(submod_gitdir_path) || file_exists(submod_gitdir_path))
+ printf(_("Adding existing path at '%s' to index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
+ free(submod_gitdir_path);
+ } else {
+ struct strvec clone_args = STRVEC_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
+ error(_("A git directory for '%s' is found "
+ "locally with remote(s):"), add_data->sm_name);
We don't capitalise first character of error message.
IOW, downcase "A git".
Got it.
Well, it's bug-for-bug with shell implementation, so it doesn't matter much, anyway.
While it is meant to be a faithful implementation, I think this
is a good opportunity to make minor style fixes.
quoted
+ show_fetch_remotes(stderr, add_data->sm_name,
+ submod_gitdir_path);
+ fprintf(stderr,
+ _("If you want to reuse this local git "
+ "directory instead of cloning again from\n"
+ " %s\n"
+ "use the '--force' option. If the local git "
+ "directory is not the correct repo\n"
+ "or if you are unsure what this means, choose "
+ "another name with the '--name' option.\n"),
+ add_data->realrepo);
Is there any reason we can't use "error" here?
The message in its entirety looks like this:
error: A git directory for 'test' is found locally with remote(s):
origin git@github.com:tfidfwastaken/abc.git
If you want to reuse this local git directory instead of cloning again from
git@github.com:tfidfwastaken/test.git
use the '--force' option. If the local git directory is not the correct repo
or if you are unsure what this means, choose another name with the '--name' option.
Since the 'error:' is already there in the first line, having it
prepended before 'If you want to reuse...' felt redundant to me.
Besides, it's more of an informational message about what a user
can do next, rather than a message that signifies an error.
If there is a preferred convention or label for such messages,
I can use that. The shell version did not have any such thing though.
quoted
[...]
+ struct option options[] = {
+ OPT_STRING('b', "branch", &add_data.branch,
+ N_("branch"),
+ N_("branch of repository to checkout on cloning")),
+ OPT_STRING(0, "prefix", &add_data.prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &add_data.sm_path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &add_data.sm_name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &add_data.realrepo,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING(0, "reference", &add_data.reference_path,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate,
+ N_("use --reference only while cloning")),
+ OPT_INTEGER(0, "depth", &add_data.depth,
+ N_("depth for shallow clones")),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT_BOOL('f', "force", &force,
+ N_("allow adding an otherwise ignored submodule path")),
We have OPT__FORCE, too.
Will switch over to that.
quoted
+ OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
From above usage, I think url, path, name is required, should we have a check for them, here?
We could. The reason why I was not too rigorous about this is
because I plan to eliminate the shell interface for this helper
eventually and call add-clone from within C, in the next few
patches.
But this is a small ask, and I can just add a quick check just
to be extra safe, so I'll do it.
die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
fi
- # perhaps the path exists and is already a git repo, else clone it
- if test -e "$sm_path"
- then
- if test -d "$sm_path"/.git || test -f "$sm_path"/.git
- then
- eval_gettextln "Adding existing repo at '\$sm_path' to the index"
- else
- die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
- fi
-
- else
- if test -d ".git/modules/$sm_name"
- then
- if test -z "$force"
- then
- eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
- GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2
- die "$(eval_gettextln "\
-If you want to reuse this local git directory instead of cloning again from
- \$realrepo
-use the '--force' option. If the local git directory is not the correct repo
-or you are unsure what this means choose another name with the '--name' option.")"
- else
- eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
- fi
- fi
- git submodule--helper clone ${GIT_QUIET:+--quiet} ${progress:+"--progress"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
- (
- sanitize_submodule_env
- cd "$sm_path" &&
- # ash fails to wordsplit ${branch:+-b "$branch"...}
- case "$branch" in
- '') git checkout -f -q ;;
- ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
- esac
- ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
- fi
+ git submodule--helper add-clone ${GIT_QUIET:+--quiet} ${force:+"--force"} ${progress:+"--progress"} ${branch:+--branch "$branch"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
git config submodule."$sm_name".url "$realrepo"
git add --no-warn-embedded-repo $force "$sm_path" ||
--
2.31.1
On my first glance, this function looks like a reinvention of strchr(3).
Except that, this function also has a second parameter for "end".
Maybe it has a specical use-case?
And this is the only use-case. Because you also want to check if you
reached the last token or not. I guess you really meant to write:
while ((line = strchr(begin, '\n')) != NULL) {
Anyway, I would name the "line" variable as "nextline"
I think this whole block is better replaced with strip_suffix_mem and
fprintf.
Overral I would replace the block inside capture_command with:
-----8<-----
char *nextline;
char *line = sb_remote_out.buf;
while ((nextline = strchr(line, '\n')) != NULL) {
size_t len = nextline - line;
if (strip_suffix_mem(line, &len, "(fetch)"))
fprintf(output, " %.*s\n", (int)len, line);
Fix-up for my suggestion:
To be bug-for-bug with shell implementation, it should be:
if (strip_suffix_mem(line, &len, " (fetch)"))
quoted
line = nextline + 1;
}
---->8-----
And get rid of parse_token and get_next_line functions.
That looks much simpler. Thanks!
I realised that all the token parsing I do is not really necessary.
What I really want to do is "If this line ends with '(fetch)',
print it, but without the '(fetch)'", and I think your version
captures that succinctly.
quoted
quoted
+ }
+ }
+
+ strbuf_release(&sb_remote_out);
+}
+
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
+ if (is_directory(submod_gitdir_path) || file_exists(submod_gitdir_path))
+ printf(_("Adding existing path at '%s' to index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
+ free(submod_gitdir_path);
+ } else {
+ struct strvec clone_args = STRVEC_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
+ error(_("A git directory for '%s' is found "
+ "locally with remote(s):"), add_data->sm_name);
We don't capitalise first character of error message.
IOW, downcase "A git".
Got it.
quoted
Well, it's bug-for-bug with shell implementation, so it doesn't matter much, anyway.
While it is meant to be a faithful implementation, I think this
is a good opportunity to make minor style fixes.
quoted
quoted
+ show_fetch_remotes(stderr, add_data->sm_name,
+ submod_gitdir_path);
+ fprintf(stderr,
+ _("If you want to reuse this local git "
+ "directory instead of cloning again from\n"
+ " %s\n"
+ "use the '--force' option. If the local git "
+ "directory is not the correct repo\n"
+ "or if you are unsure what this means, choose "
+ "another name with the '--name' option.\n"),
+ add_data->realrepo);
Is there any reason we can't use "error" here?
The message in its entirety looks like this:
error: A git directory for 'test' is found locally with remote(s):
origin git@github.com:tfidfwastaken/abc.git
If you want to reuse this local git directory instead of cloning again from
git@github.com:tfidfwastaken/test.git
use the '--force' option. If the local git directory is not the correct repo
or if you are unsure what this means, choose another name with the '--name' option.
Since the 'error:' is already there in the first line, having it
prepended before 'If you want to reuse...' felt redundant to me.
Besides, it's more of an informational message about what a user
can do next, rather than a message that signifies an error.
If there is a preferred convention or label for such messages,
I can use that. The shell version did not have any such thing though.
quoted
quoted
[...]
+ struct option options[] = {
+ OPT_STRING('b', "branch", &add_data.branch,
+ N_("branch"),
+ N_("branch of repository to checkout on cloning")),
+ OPT_STRING(0, "prefix", &add_data.prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &add_data.sm_path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &add_data.sm_name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &add_data.realrepo,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING(0, "reference", &add_data.reference_path,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate,
+ N_("use --reference only while cloning")),
+ OPT_INTEGER(0, "depth", &add_data.depth,
+ N_("depth for shallow clones")),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT_BOOL('f', "force", &force,
+ N_("allow adding an otherwise ignored submodule path")),
We have OPT__FORCE, too.
Will switch over to that.
quoted
quoted
+ OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
From above usage, I think url, path, name is required, should we have a check for them, here?
We could. The reason why I was not too rigorous about this is
because I plan to eliminate the shell interface for this helper
eventually and call add-clone from within C, in the next few
patches.
But this is a small ask, and I can just add a quick check just
to be extra safe, so I'll do it.
die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
fi
- # perhaps the path exists and is already a git repo, else clone it
- if test -e "$sm_path"
- then
- if test -d "$sm_path"/.git || test -f "$sm_path"/.git
- then
- eval_gettextln "Adding existing repo at '\$sm_path' to the index"
- else
- die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
- fi
-
- else
- if test -d ".git/modules/$sm_name"
- then
- if test -z "$force"
- then
- eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
- GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2
- die "$(eval_gettextln "\
-If you want to reuse this local git directory instead of cloning again from
- \$realrepo
-use the '--force' option. If the local git directory is not the correct repo
-or you are unsure what this means choose another name with the '--name' option.")"
- else
- eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
- fi
- fi
- git submodule--helper clone ${GIT_QUIET:+--quiet} ${progress:+"--progress"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
- (
- sanitize_submodule_env
- cd "$sm_path" &&
- # ash fails to wordsplit ${branch:+-b "$branch"...}
- case "$branch" in
- '') git checkout -f -q ;;
- ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
- esac
- ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
- fi
+ git submodule--helper add-clone ${GIT_QUIET:+--quiet} ${force:+"--force"} ${progress:+"--progress"} ${branch:+--branch "$branch"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
git config submodule."$sm_name".url "$realrepo"
git add --no-warn-embedded-repo $force "$sm_path" ||
--
2.31.1
Notable changes since v2:
- In show_fetch_remotes(), remove the get_next_line() and parse_token()
in favour of a simpler solution that uses strchr() and
strip_suffix_mem()
- Use OPT__FORCE() instead of OPT_BOOL for '--force' flags
- Add checks for number of arguments in the helper subcommands
- Simplify usage string for add-clone and make error messages start in
lowercase.
Atharva Raykar (2):
submodule--helper: introduce add-clone subcommand
submodule--helper: introduce add-config subcommand
builtin/submodule--helper.c | 299 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 66 +-------
2 files changed, 301 insertions(+), 64 deletions(-)
--
2.31.1
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
Helped-by: Đoàn Trần Công Danh [off-list ref]
---
builtin/submodule--helper.c | 180 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +-------
2 files changed, 181 insertions(+), 37 deletions(-)
@@ -2745,6 +2745,185 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*next_line;+char*line=sb_remote_out.buf;+while((next_line=strchr(line,'\n'))!=NULL){+size_tlen=next_line-line;+if(strip_suffix_mem(line,&len," (fetch)"))+fprintf(output," %.*s\n",(int)len,line);+line=next_line+1;+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;++/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_directory(submod_gitdir_path)||file_exists(submod_gitdir_path))+printf(_("Adding existing path at '%s' to index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+free(submod_gitdir_path);+}else{+structstrvecclone_args=STRVEC_INIT;+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+error(_("a git directory for '%s' is found "+"locally with remote(s):"),add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+fprintf(stderr,+_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+free(submod_gitdir_path);+return1;+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++strvec_pushl(&clone_args,"clone","--path",add_data->sm_path,"--name",+add_data->sm_name,"--url",add_data->realrepo,NULL);+if(add_data->quiet)+strvec_push(&clone_args,"--quiet");+if(add_data->progress)+strvec_push(&clone_args,"--progress");+if(add_data->prefix)+strvec_pushl(&clone_args,"--prefix",add_data->prefix,NULL);+if(add_data->reference_path)+strvec_pushl(&clone_args,"--reference",+add_data->reference_path,NULL);+if(add_data->dissociate)+strvec_push(&clone_args,"--dissociate");+if(add_data->depth>=0)+strvec_pushf(&clone_args,"--depth=%d",add_data->depth);++if(module_clone(clone_args.nr,clone_args.v,add_data->prefix)){+strvec_clear(&clone_args);+return-1;+}+strvec_clear(&clone_args);++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&add_data.prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT__QUIET(&quiet,"suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [<options>...] "+"--url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n",
because it is an invalid configuration. It would be helpful to let the
user know that the pathspec is unset, and the value of
'submodule.<name>.active' might be set to 'true' so that they can
rectify their configuration and prevent future surprises (especially
given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 119 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +--------
2 files changed, 120 insertions(+), 27 deletions(-)
@@ -2924,6 +2924,124 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++key=xstrfmt("submodule.%s.path",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->sm_path);+free(key);+key=xstrfmt("submodule.%s.url",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->repo);+free(key);+if(add_data->branch){+key=xstrfmt("submodule.%s.branch",add_data->sm_path);+config_set_in_gitmodules_file_gently(key,add_data->branch);+free(key);+}++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec){+warning(_("The submodule.active configuration exists, but the "+"pathspec was unset. If the submodule is not already "+"active, the value of submodule.%s.active will be "+"be set to 'true'."),add_data->sm_name);+activate=1;+}++/*+*Ifsubmodule.activedoesnotexist,orifthepathspecwasunset,+*wewillactivatethismoduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||activate||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#
This version modifies 'module_clone()' and separates the flag parsing from the
actual cloning logic. This allows us to use the functionality of
'submodule--helper clone' without needing to push arguments to a strvec from
'add_submodule'. We use a new struct called `module_clone_data` instead.
Because this change involves moving the contents of 'module_clone()' to
'clone_submodule()', the whole function had to be relocated further down so that
all the helpers it calls are available to it.
Other changes include making error output match more closely to the shell
version, and better usage of the C API
('is_directory()' -> 'is_nonbare_repo_dir()')
Atharva Raykar (3):
submodule--helper: refactor module_clone()
submodule--helper: introduce add-clone subcommand
submodule--helper: introduce add-config subcommand
builtin/submodule--helper.c | 536 ++++++++++++++++++++++++++++--------
git-submodule.sh | 66 +----
2 files changed, 425 insertions(+), 177 deletions(-)
Range-diff against v3:
-: ---------- > 1: 11d035ce75 submodule--helper: refactor module_clone()
1: 3b5f7bec7c ! 2: c85701b79a submodule--helper: introduce add-clone subcommand
@@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
++ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
+
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
++ struct strbuf sm_path = STRBUF_INIT;
++ strbuf_addstr(&sm_path, add_data->sm_path);
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
-+ if (is_directory(submod_gitdir_path) || file_exists(submod_gitdir_path))
-+ printf(_("Adding existing path at '%s' to index\n"),
++ if (is_nonbare_repository_dir(&sm_path))
++ printf(_("Adding existing repo at '%s' to the index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
++ strbuf_release(&sm_path);
+ free(submod_gitdir_path);
+ } else {
-+ struct strvec clone_args = STRVEC_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
-+ error(_("a git directory for '%s' is found "
-+ "locally with remote(s):"), add_data->sm_name);
++ fprintf(stderr, _("A git directory for '%s' is found "
++ "locally with remote(s):"),
++ add_data->sm_name);
+ show_fetch_remotes(stderr, add_data->sm_name,
+ submod_gitdir_path);
-+ fprintf(stderr,
-+ _("If you want to reuse this local git "
-+ "directory instead of cloning again from\n"
-+ " %s\n"
-+ "use the '--force' option. If the local git "
-+ "directory is not the correct repo\n"
-+ "or if you are unsure what this means, choose "
-+ "another name with the '--name' option.\n"),
-+ add_data->realrepo);
+ free(submod_gitdir_path);
-+ return 1;
++ die(_("If you want to reuse this local git "
++ "directory instead of cloning again from\n"
++ " %s\n"
++ "use the '--force' option. If the local git "
++ "directory is not the correct repo\n"
++ "or if you are unsure what this means, choose "
++ "another name with the '--name' option.\n"),
++ add_data->realrepo);
+ } else {
+ printf(_("Reactivating local git directory for "
+ "submodule '%s'\n"), add_data->sm_name);
@@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
+ }
+ free(submod_gitdir_path);
+
-+ strvec_pushl(&clone_args, "clone", "--path", add_data->sm_path, "--name",
-+ add_data->sm_name, "--url", add_data->realrepo, NULL);
-+ if (add_data->quiet)
-+ strvec_push(&clone_args, "--quiet");
-+ if (add_data->progress)
-+ strvec_push(&clone_args, "--progress");
-+ if (add_data->prefix)
-+ strvec_pushl(&clone_args, "--prefix", add_data->prefix, NULL);
++ clone_data.prefix = add_data->prefix;
++ clone_data.path = add_data->sm_path;
++ clone_data.name = add_data->sm_name;
++ clone_data.url = add_data->realrepo;
++ clone_data.quiet = add_data->quiet;
++ clone_data.progress = add_data->progress;
+ if (add_data->reference_path)
-+ strvec_pushl(&clone_args, "--reference",
-+ add_data->reference_path, NULL);
-+ if (add_data->dissociate)
-+ strvec_push(&clone_args, "--dissociate");
++ string_list_append(&clone_data.reference,
++ xstrdup(add_data->reference_path));
++ clone_data.dissociate = add_data->dissociate;
+ if (add_data->depth >= 0)
-+ strvec_pushf(&clone_args, "--depth=%d", add_data->depth);
++ clone_data.depth = xstrfmt("%d", add_data->depth);
+
-+ if (module_clone(clone_args.nr, clone_args.v, add_data->prefix)) {
-+ strvec_clear(&clone_args);
++ if (clone_submodule(&clone_data))
+ return -1;
-+ }
-+ strvec_clear(&clone_args);
+
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.git_cmd = 1;
2: a2a6b4d74c = 3: 6532b4ae11 submodule--helper: introduce add-config subcommand
--
2.31.1
Separate out the core logic of module_clone() from the flag
parsing---this way we can call the equivalent of the `submodule--helper
clone` subcommand directly within C, without needing to push arguments
in a strvec.
---
builtin/submodule--helper.c | 241 +++++++++++++++++++-----------------
1 file changed, 128 insertions(+), 113 deletions(-)
@@ -1802,37 +1777,128 @@ static void prepare_possible_alternates(const char *sm_name,free(error_strategy);}+staticintclone_submodule(structmodule_clone_data*clone_data)+{+char*p,*sm_gitdir;+char*sm_alternate=NULL,*error_strategy=NULL;+structstrbufsb=STRBUF_INIT;+structchild_processcp=CHILD_PROCESS_INIT;++strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),clone_data->name);+sm_gitdir=absolute_pathdup(sb.buf);+strbuf_reset(&sb);++if(!is_absolute_path(clone_data->path)){+strbuf_addf(&sb,"%s/%s",get_git_work_tree(),clone_data->path);+clone_data->path=strbuf_detach(&sb,NULL);+}else{+clone_data->path=xstrdup(clone_data->path);+}++if(validate_submodule_git_dir(sm_gitdir,clone_data->name)<0)+die(_("refusing to create/use '%s' in another submodule's "+"git dir"),sm_gitdir);++if(!file_exists(sm_gitdir)){+if(safe_create_leading_directories_const(sm_gitdir)<0)+die(_("could not create directory '%s'"),sm_gitdir);++prepare_possible_alternates(clone_data->name,&clone_data->reference);++strvec_push(&cp.args,"clone");+strvec_push(&cp.args,"--no-checkout");+if(clone_data->quiet)+strvec_push(&cp.args,"--quiet");+if(clone_data->progress)+strvec_push(&cp.args,"--progress");+if(clone_data->depth&&*(clone_data->depth))+strvec_pushl(&cp.args,"--depth",clone_data->depth,NULL);+if(clone_data->reference.nr){+structstring_list_item*item;+for_each_string_list_item(item,&clone_data->reference)+strvec_pushl(&cp.args,"--reference",+item->string,NULL);+}+if(clone_data->dissociate)+strvec_push(&cp.args,"--dissociate");+if(sm_gitdir&&*sm_gitdir)+strvec_pushl(&cp.args,"--separate-git-dir",sm_gitdir,NULL);+if(clone_data->single_branch>=0)+strvec_push(&cp.args,clone_data->single_branch?+"--single-branch":+"--no-single-branch");++strvec_push(&cp.args,"--");+strvec_push(&cp.args,clone_data->url);+strvec_push(&cp.args,clone_data->path);++cp.git_cmd=1;+prepare_submodule_repo_env(&cp.env_array);+cp.no_stdin=1;++if(run_command(&cp))+die(_("clone of '%s' into submodule path '%s' failed"),+clone_data->url,clone_data->path);+}else{+if(clone_data->require_init&&!access(clone_data->path,X_OK)&&+!is_empty_dir(clone_data->path))+die(_("directory not empty: '%s'"),clone_data->path);+if(safe_create_leading_directories_const(clone_data->path)<0)+die(_("could not create directory '%s'"),clone_data->path);+strbuf_addf(&sb,"%s/index",sm_gitdir);+unlink_or_warn(sb.buf);+strbuf_reset(&sb);+}++connect_work_tree_and_git_dir(clone_data->path,sm_gitdir,0);++p=git_pathdup_submodule(clone_data->path,"config");+if(!p)+die(_("could not get submodule directory for '%s'"),clone_data->path);++/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */+git_config_get_string("submodule.alternateLocation",&sm_alternate);+if(sm_alternate)+git_config_set_in_file(p,"submodule.alternateLocation",+sm_alternate);+git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);+if(error_strategy)+git_config_set_in_file(p,"submodule.alternateErrorStrategy",+error_strategy);++free(sm_alternate);+free(error_strategy);++strbuf_release(&sb);+free(sm_gitdir);+free(p);+return0;+}+staticintmodule_clone(intargc,constchar**argv,constchar*prefix){-constchar*name=NULL,*url=NULL,*depth=NULL;-intquiet=0;-intprogress=0;-char*p,*path=NULL,*sm_gitdir;-structstrbufsb=STRBUF_INIT;-structstring_listreference=STRING_LIST_INIT_NODUP;-intdissociate=0,require_init=0;-char*sm_alternate=NULL,*error_strategy=NULL;-intsingle_branch=-1;+intdissociate=0,quiet=0,progress=0,require_init=0;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;structoptionmodule_clone_options[]={-OPT_STRING(0,"prefix",&prefix,+OPT_STRING(0,"prefix",&clone_data.prefix,N_("path"),N_("alternative anchor for relative paths")),-OPT_STRING(0,"path",&path,+OPT_STRING(0,"path",&clone_data.path,N_("path"),N_("where the new submodule will be cloned to")),-OPT_STRING(0,"name",&name,+OPT_STRING(0,"name",&clone_data.name,N_("string"),N_("name of the new submodule")),-OPT_STRING(0,"url",&url,+OPT_STRING(0,"url",&clone_data.url,N_("string"),N_("url where to clone the submodule from")),-OPT_STRING_LIST(0,"reference",&reference,+OPT_STRING_LIST(0,"reference",&clone_data.reference,N_("repo"),N_("reference repository")),OPT_BOOL(0,"dissociate",&dissociate,N_("use --reference only while cloning")),-OPT_STRING(0,"depth",&depth,+OPT_STRING(0,"depth",&clone_data.depth,N_("string"),N_("depth for shallow clones")),OPT__QUIET(&quiet,"Suppress output for cloning a submodule"),
@@ -1840,7 +1906,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)N_("force cloning progress")),OPT_BOOL(0,"require-init",&require_init,N_("disallow cloning into non-empty directory")),-OPT_BOOL(0,"single-branch",&single_branch,+OPT_BOOL(0,"single-branch",&clone_data.single_branch,N_("clone only one branch, HEAD or --branch")),OPT_END()};
@@ -1856,67 +1922,16 @@ static int module_clone(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,module_clone_options,git_submodule_helper_usage,0);-if(argc||!url||!path||!*path)+clone_data.dissociate=!!dissociate;+clone_data.quiet=!!quiet;+clone_data.progress=!!progress;+clone_data.require_init=!!require_init;++if(argc||!clone_data.url||!clone_data.path||!*(clone_data.path))usage_with_options(git_submodule_helper_usage,module_clone_options);-strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),name);-sm_gitdir=absolute_pathdup(sb.buf);-strbuf_reset(&sb);--if(!is_absolute_path(path)){-strbuf_addf(&sb,"%s/%s",get_git_work_tree(),path);-path=strbuf_detach(&sb,NULL);-}else-path=xstrdup(path);--if(validate_submodule_git_dir(sm_gitdir,name)<0)-die(_("refusing to create/use '%s' in another submodule's "-"git dir"),sm_gitdir);--if(!file_exists(sm_gitdir)){-if(safe_create_leading_directories_const(sm_gitdir)<0)-die(_("could not create directory '%s'"),sm_gitdir);--prepare_possible_alternates(name,&reference);--if(clone_submodule(path,sm_gitdir,url,depth,&reference,dissociate,-quiet,progress,single_branch))-die(_("clone of '%s' into submodule path '%s' failed"),-url,path);-}else{-if(require_init&&!access(path,X_OK)&&!is_empty_dir(path))-die(_("directory not empty: '%s'"),path);-if(safe_create_leading_directories_const(path)<0)-die(_("could not create directory '%s'"),path);-strbuf_addf(&sb,"%s/index",sm_gitdir);-unlink_or_warn(sb.buf);-strbuf_reset(&sb);-}--connect_work_tree_and_git_dir(path,sm_gitdir,0);--p=git_pathdup_submodule(path,"config");-if(!p)-die(_("could not get submodule directory for '%s'"),path);--/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */-git_config_get_string("submodule.alternateLocation",&sm_alternate);-if(sm_alternate)-git_config_set_in_file(p,"submodule.alternateLocation",-sm_alternate);-git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);-if(error_strategy)-git_config_set_in_file(p,"submodule.alternateErrorStrategy",-error_strategy);--free(sm_alternate);-free(error_strategy);--strbuf_release(&sb);-free(sm_gitdir);-free(path);-free(p);+clone_submodule(&clone_data);return0;}
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n",
because it is an invalid configuration. It would be helpful to let the
user know that the pathspec is unset, and the value of
'submodule.<name>.active' might be set to 'true' so that they can
rectify their configuration and prevent future surprises (especially
given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 119 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +--------
2 files changed, 120 insertions(+), 27 deletions(-)
@@ -2935,6 +2935,124 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++key=xstrfmt("submodule.%s.path",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->sm_path);+free(key);+key=xstrfmt("submodule.%s.url",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->repo);+free(key);+if(add_data->branch){+key=xstrfmt("submodule.%s.branch",add_data->sm_path);+config_set_in_gitmodules_file_gently(key,add_data->branch);+free(key);+}++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec){+warning(_("The submodule.active configuration exists, but the "+"pathspec was unset. If the submodule is not already "+"active, the value of submodule.%s.active will be "+"be set to 'true'."),add_data->sm_name);+activate=1;+}++/*+*Ifsubmodule.activedoesnotexist,orifthepathspecwasunset,+*wewillactivatethismoduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||activate||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
Helped-by: Đoàn Trần Công Danh [off-list ref]
---
builtin/submodule--helper.c | 176 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +-------
2 files changed, 177 insertions(+), 37 deletions(-)
@@ -2760,6 +2760,181 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*next_line;+char*line=sb_remote_out.buf;+while((next_line=strchr(line,'\n'))!=NULL){+size_tlen=next_line-line;+if(strip_suffix_mem(line,&len," (fetch)"))+fprintf(output," %.*s\n",(int)len,line);+line=next_line+1;+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;++/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+structstrbufsm_path=STRBUF_INIT;+strbuf_addstr(&sm_path,add_data->sm_path);+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_nonbare_repository_dir(&sm_path))+printf(_("Adding existing repo at '%s' to the index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+strbuf_release(&sm_path);+free(submod_gitdir_path);+}else{+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+fprintf(stderr,_("A git directory for '%s' is found "+"locally with remote(s):"),+add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+free(submod_gitdir_path);+die(_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++clone_data.prefix=add_data->prefix;+clone_data.path=add_data->sm_path;+clone_data.name=add_data->sm_name;+clone_data.url=add_data->realrepo;+clone_data.quiet=add_data->quiet;+clone_data.progress=add_data->progress;+if(add_data->reference_path)+string_list_append(&clone_data.reference,+xstrdup(add_data->reference_path));+clone_data.dissociate=add_data->dissociate;+if(add_data->depth>=0)+clone_data.depth=xstrfmt("%d",add_data->depth);++if(clone_submodule(&clone_data))+return-1;++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&add_data.prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT__QUIET(&quiet,"suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [<options>...] "+"--url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
I do not have enough expertise to judge the entire content of this
patch. I would like, however, to propose a slight code change for the
sake of readability.
@@ -2935,6 +2935,124 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++key=xstrfmt("submodule.%s.path",add_data->sm_name);+config_set_in_gitmodules_file_gently(key,add_data->sm_path);+free(key);
This above three lines of code is very similar to the two operations that
follows (including the one inside the `if (add_data->branch)`
condition. So [ ... ]
[ ... ] it might be worth to write a small wrapper that will perform: (1)
`xstrfmt()` on the specified config section, (2) set the configuration
in the file and (3) free()'ing the variable inside the wrapper. Thus,
most of these code will become one liners that is easier to read (given
the function is properly named :) ).
After abstracting the code on the wrapper, this code will become
something like:
function_properly_named("submodule.%s.path", add_data->sm_name, add_data->sm_path);
function_properly_named("submodule.%s.url", add_data->sm_name, add_data->repo);
if (add_data->branch)
function_properly_named("submodule.%s.branch", add_data->sm_path, add_data->branch);
Just as an example, here's a diff to demonstrate the argument:
-- >8 --
A proper name than "add_config_in_submodules_file" should be considered - I'm
not very good in naming things.
These change does (should) not change the behavior of code, even though
I believe it make the code simpler to read, I do not have strong
opinions about it. So, take this proposal as you wish.
--
Thanks
Rafael
I do not have enough expertise to judge the entire content of this
patch. I would like, however, to propose a slight code change for the
sake of readability.
This above three lines of code is very similar to the two operations that
follows (including the one inside the `if (add_data->branch)`
condition. So [ ... ]
[ ... ] it might be worth to write a small wrapper that will perform: (1)
`xstrfmt()` on the specified config section, (2) set the configuration
in the file and (3) free()'ing the variable inside the wrapper. Thus,
most of these code will become one liners that is easier to read (given
the function is properly named :) ).
After abstracting the code on the wrapper, this code will become
something like:
function_properly_named("submodule.%s.path", add_data->sm_name, add_data->sm_path);
function_properly_named("submodule.%s.url", add_data->sm_name, add_data->repo);
if (add_data->branch)
function_properly_named("submodule.%s.branch", add_data->sm_path, add_data->branch);
Just as an example, here's a diff to demonstrate the argument:
-- >8 --
if (run_command(&add_submod))
die(_("Failed to add submodule '%s'"), add_data->sm_path);
- key = xstrfmt("submodule.%s.path", add_data->sm_name);
- config_set_in_gitmodules_file_gently(key, add_data->sm_path);
- free(key);
- key = xstrfmt("submodule.%s.url", add_data->sm_name);
- config_set_in_gitmodules_file_gently(key, add_data->repo);
- free(key);
- if (add_data->branch) {
- key = xstrfmt("submodule.%s.branch", add_data->sm_path);
- config_set_in_gitmodules_file_gently(key, add_data->branch);
- free(key);
- }
+ add_config_in_submodules_file("submodule.%s.path", add_data->sm_name, add_data->sm_path);
+ add_config_in_submodules_file("submodule.%s.url", add_data->sm_name, add_data->repo);
+ if (add_data->branch)
+ add_config_in_submodules_file("submodule.%s.branch", add_data->sm_path, add_data->branch);
add_gitmodules.git_cmd = 1;
strvec_pushl(&add_gitmodules.args,
-- >8 --
A proper name than "add_config_in_submodules_file" should be considered - I'm
not very good in naming things.
These change does (should) not change the behavior of code, even though
I believe it make the code simpler to read, I do not have strong
opinions about it. So, take this proposal as you wish.
I agree with you, this will make the code simpler to read. It also
made me realise one thing that I did not replicate exactly from the
shell code.
The original shell code calls 'module_config()', which does an extra
check to see if writing to '.gitmodules' is okay. I did not perform
this check, and including that in the wrapper you propose will be a
good idea.
Only two changes since v4:
- Add missing trailers and s.o.b in [1/3]
- In [3/3] introduce a wrapper function called 'config_submodule_in_gitmodules'
that sets the 'submodule.<name>.<var>' configuration, and before doing so,
checks if it is okay to write to '.gitmodules', which was the original
behaviour of the shell version.
Atharva Raykar (3):
submodule--helper: refactor module_clone()
submodule--helper: introduce add-clone subcommand
submodule--helper: introduce add-config subcommand
builtin/submodule--helper.c | 542 ++++++++++++++++++++++++++++--------
git-submodule.sh | 66 +----
2 files changed, 431 insertions(+), 177 deletions(-)
--
2.31.1
Separate out the core logic of module_clone() from the flag
parsing---this way we can call the equivalent of the `submodule--helper
clone` subcommand directly within C, without needing to push arguments
in a strvec.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Suggested-by: Junio C Hamano <redacted>
---
builtin/submodule--helper.c | 241 +++++++++++++++++++-----------------
1 file changed, 128 insertions(+), 113 deletions(-)
@@ -1802,37 +1777,128 @@ static void prepare_possible_alternates(const char *sm_name,free(error_strategy);}-staticintmodule_clone(intargc,constchar**argv,constchar*prefix)+staticintclone_submodule(structmodule_clone_data*clone_data){-constchar*name=NULL,*url=NULL,*depth=NULL;-intquiet=0;-intprogress=0;-char*p,*path=NULL,*sm_gitdir;-structstrbufsb=STRBUF_INIT;-structstring_listreference=STRING_LIST_INIT_NODUP;-intdissociate=0,require_init=0;+char*p,*sm_gitdir;char*sm_alternate=NULL,*error_strategy=NULL;-intsingle_branch=-1;+structstrbufsb=STRBUF_INIT;+structchild_processcp=CHILD_PROCESS_INIT;++strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),clone_data->name);+sm_gitdir=absolute_pathdup(sb.buf);+strbuf_reset(&sb);++if(!is_absolute_path(clone_data->path)){+strbuf_addf(&sb,"%s/%s",get_git_work_tree(),clone_data->path);+clone_data->path=strbuf_detach(&sb,NULL);+}else{+clone_data->path=xstrdup(clone_data->path);+}++if(validate_submodule_git_dir(sm_gitdir,clone_data->name)<0)+die(_("refusing to create/use '%s' in another submodule's "+"git dir"),sm_gitdir);++if(!file_exists(sm_gitdir)){+if(safe_create_leading_directories_const(sm_gitdir)<0)+die(_("could not create directory '%s'"),sm_gitdir);++prepare_possible_alternates(clone_data->name,&clone_data->reference);++strvec_push(&cp.args,"clone");+strvec_push(&cp.args,"--no-checkout");+if(clone_data->quiet)+strvec_push(&cp.args,"--quiet");+if(clone_data->progress)+strvec_push(&cp.args,"--progress");+if(clone_data->depth&&*(clone_data->depth))+strvec_pushl(&cp.args,"--depth",clone_data->depth,NULL);+if(clone_data->reference.nr){+structstring_list_item*item;+for_each_string_list_item(item,&clone_data->reference)+strvec_pushl(&cp.args,"--reference",+item->string,NULL);+}+if(clone_data->dissociate)+strvec_push(&cp.args,"--dissociate");+if(sm_gitdir&&*sm_gitdir)+strvec_pushl(&cp.args,"--separate-git-dir",sm_gitdir,NULL);+if(clone_data->single_branch>=0)+strvec_push(&cp.args,clone_data->single_branch?+"--single-branch":+"--no-single-branch");++strvec_push(&cp.args,"--");+strvec_push(&cp.args,clone_data->url);+strvec_push(&cp.args,clone_data->path);++cp.git_cmd=1;+prepare_submodule_repo_env(&cp.env_array);+cp.no_stdin=1;++if(run_command(&cp))+die(_("clone of '%s' into submodule path '%s' failed"),+clone_data->url,clone_data->path);+}else{+if(clone_data->require_init&&!access(clone_data->path,X_OK)&&+!is_empty_dir(clone_data->path))+die(_("directory not empty: '%s'"),clone_data->path);+if(safe_create_leading_directories_const(clone_data->path)<0)+die(_("could not create directory '%s'"),clone_data->path);+strbuf_addf(&sb,"%s/index",sm_gitdir);+unlink_or_warn(sb.buf);+strbuf_reset(&sb);+}++connect_work_tree_and_git_dir(clone_data->path,sm_gitdir,0);++p=git_pathdup_submodule(clone_data->path,"config");+if(!p)+die(_("could not get submodule directory for '%s'"),clone_data->path);++/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */+git_config_get_string("submodule.alternateLocation",&sm_alternate);+if(sm_alternate)+git_config_set_in_file(p,"submodule.alternateLocation",+sm_alternate);+git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);+if(error_strategy)+git_config_set_in_file(p,"submodule.alternateErrorStrategy",+error_strategy);++free(sm_alternate);+free(error_strategy);++strbuf_release(&sb);+free(sm_gitdir);+free(p);+return0;+}++staticintmodule_clone(intargc,constchar**argv,constchar*prefix)+{+intdissociate=0,quiet=0,progress=0,require_init=0;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;structoptionmodule_clone_options[]={-OPT_STRING(0,"prefix",&prefix,+OPT_STRING(0,"prefix",&clone_data.prefix,N_("path"),N_("alternative anchor for relative paths")),-OPT_STRING(0,"path",&path,+OPT_STRING(0,"path",&clone_data.path,N_("path"),N_("where the new submodule will be cloned to")),-OPT_STRING(0,"name",&name,+OPT_STRING(0,"name",&clone_data.name,N_("string"),N_("name of the new submodule")),-OPT_STRING(0,"url",&url,+OPT_STRING(0,"url",&clone_data.url,N_("string"),N_("url where to clone the submodule from")),-OPT_STRING_LIST(0,"reference",&reference,+OPT_STRING_LIST(0,"reference",&clone_data.reference,N_("repo"),N_("reference repository")),OPT_BOOL(0,"dissociate",&dissociate,N_("use --reference only while cloning")),-OPT_STRING(0,"depth",&depth,+OPT_STRING(0,"depth",&clone_data.depth,N_("string"),N_("depth for shallow clones")),OPT__QUIET(&quiet,"Suppress output for cloning a submodule"),
@@ -1840,7 +1906,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)N_("force cloning progress")),OPT_BOOL(0,"require-init",&require_init,N_("disallow cloning into non-empty directory")),-OPT_BOOL(0,"single-branch",&single_branch,+OPT_BOOL(0,"single-branch",&clone_data.single_branch,N_("clone only one branch, HEAD or --branch")),OPT_END()};
@@ -1856,67 +1922,16 @@ static int module_clone(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,module_clone_options,git_submodule_helper_usage,0);-if(argc||!url||!path||!*path)+clone_data.dissociate=!!dissociate;+clone_data.quiet=!!quiet;+clone_data.progress=!!progress;+clone_data.require_init=!!require_init;++if(argc||!clone_data.url||!clone_data.path||!*(clone_data.path))usage_with_options(git_submodule_helper_usage,module_clone_options);-strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),name);-sm_gitdir=absolute_pathdup(sb.buf);-strbuf_reset(&sb);--if(!is_absolute_path(path)){-strbuf_addf(&sb,"%s/%s",get_git_work_tree(),path);-path=strbuf_detach(&sb,NULL);-}else-path=xstrdup(path);--if(validate_submodule_git_dir(sm_gitdir,name)<0)-die(_("refusing to create/use '%s' in another submodule's "-"git dir"),sm_gitdir);--if(!file_exists(sm_gitdir)){-if(safe_create_leading_directories_const(sm_gitdir)<0)-die(_("could not create directory '%s'"),sm_gitdir);--prepare_possible_alternates(name,&reference);--if(clone_submodule(path,sm_gitdir,url,depth,&reference,dissociate,-quiet,progress,single_branch))-die(_("clone of '%s' into submodule path '%s' failed"),-url,path);-}else{-if(require_init&&!access(path,X_OK)&&!is_empty_dir(path))-die(_("directory not empty: '%s'"),path);-if(safe_create_leading_directories_const(path)<0)-die(_("could not create directory '%s'"),path);-strbuf_addf(&sb,"%s/index",sm_gitdir);-unlink_or_warn(sb.buf);-strbuf_reset(&sb);-}--connect_work_tree_and_git_dir(path,sm_gitdir,0);--p=git_pathdup_submodule(path,"config");-if(!p)-die(_("could not get submodule directory for '%s'"),path);--/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */-git_config_get_string("submodule.alternateLocation",&sm_alternate);-if(sm_alternate)-git_config_set_in_file(p,"submodule.alternateLocation",-sm_alternate);-git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);-if(error_strategy)-git_config_set_in_file(p,"submodule.alternateErrorStrategy",-error_strategy);--free(sm_alternate);-free(error_strategy);--strbuf_release(&sb);-free(sm_gitdir);-free(path);-free(p);+clone_submodule(&clone_data);return0;}
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
Helped-by: Đoàn Trần Công Danh [off-list ref]
---
builtin/submodule--helper.c | 176 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +-------
2 files changed, 177 insertions(+), 37 deletions(-)
@@ -2760,6 +2760,181 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*next_line;+char*line=sb_remote_out.buf;+while((next_line=strchr(line,'\n'))!=NULL){+size_tlen=next_line-line;+if(strip_suffix_mem(line,&len," (fetch)"))+fprintf(output," %.*s\n",(int)len,line);+line=next_line+1;+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;++/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+structstrbufsm_path=STRBUF_INIT;+strbuf_addstr(&sm_path,add_data->sm_path);+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_nonbare_repository_dir(&sm_path))+printf(_("Adding existing repo at '%s' to the index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+strbuf_release(&sm_path);+free(submod_gitdir_path);+}else{+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+fprintf(stderr,_("A git directory for '%s' is found "+"locally with remote(s):"),+add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+free(submod_gitdir_path);+die(_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++clone_data.prefix=add_data->prefix;+clone_data.path=add_data->sm_path;+clone_data.name=add_data->sm_name;+clone_data.url=add_data->realrepo;+clone_data.quiet=add_data->quiet;+clone_data.progress=add_data->progress;+if(add_data->reference_path)+string_list_append(&clone_data.reference,+xstrdup(add_data->reference_path));+clone_data.dissociate=add_data->dissociate;+if(add_data->depth>=0)+clone_data.depth=xstrfmt("%d",add_data->depth);++if(clone_submodule(&clone_data))+return-1;++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&add_data.prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT__QUIET(&quiet,"suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [<options>...] "+"--url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n",
because it is an invalid configuration. It would be helpful to let the
user know that the pathspec is unset, and the value of
'submodule.<name>.active' might be set to 'true' so that they can
rectify their configuration and prevent future surprises (especially
given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 125 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +-------
2 files changed, 126 insertions(+), 27 deletions(-)
@@ -2935,6 +2935,130 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfig_submodule_in_gitmodules(constchar*name,constchar*var,constchar*value)+{+char*key;++if(!is_writing_gitmodules_ok())+die(_("please make sure that the .gitmodules file is in the working tree"));++key=xstrfmt("submodule.%s.%s",name,var);+config_set_in_gitmodules_file_gently(key,value);+free(key);+}++staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++config_submodule_in_gitmodules(add_data->sm_name,"path",add_data->sm_path);+config_submodule_in_gitmodules(add_data->sm_name,"url",add_data->repo);+if(add_data->branch)+config_submodule_in_gitmodules(add_data->sm_name,+"branch",add_data->branch);++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec){+warning(_("The submodule.active configuration exists, but the "+"pathspec was unset. If the submodule is not already "+"active, the value of submodule.%s.active will be "+"be set to 'true'."),add_data->sm_name);+activate=1;+}++/*+*Ifsubmodule.activedoesnotexist,orifthepathspecwasunset,+*wewillactivatethismoduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||activate||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#
Separate out the core logic of module_clone() from the flag
parsing---this way we can call the equivalent of the `submodule--helper
clone` subcommand directly within C, without needing to push arguments
in a strvec.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Suggested-by: Junio C Hamano <redacted>
---
builtin/submodule--helper.c | 241 +++++++++++++++++++-----------------
1 file changed, 128 insertions(+), 113 deletions(-)
@@ -1802,37 +1777,128 @@ static void prepare_possible_alternates(const char *sm_name,free(error_strategy);}+staticintclone_submodule(structmodule_clone_data*clone_data)+{+char*p,*sm_gitdir;+char*sm_alternate=NULL,*error_strategy=NULL;+structstrbufsb=STRBUF_INIT;+structchild_processcp=CHILD_PROCESS_INIT;++strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),clone_data->name);+sm_gitdir=absolute_pathdup(sb.buf);+strbuf_reset(&sb);++if(!is_absolute_path(clone_data->path)){+strbuf_addf(&sb,"%s/%s",get_git_work_tree(),clone_data->path);+clone_data->path=strbuf_detach(&sb,NULL);+}else{+clone_data->path=xstrdup(clone_data->path);+}++if(validate_submodule_git_dir(sm_gitdir,clone_data->name)<0)+die(_("refusing to create/use '%s' in another submodule's "+"git dir"),sm_gitdir);++if(!file_exists(sm_gitdir)){+if(safe_create_leading_directories_const(sm_gitdir)<0)+die(_("could not create directory '%s'"),sm_gitdir);++prepare_possible_alternates(clone_data->name,&clone_data->reference);++strvec_push(&cp.args,"clone");+strvec_push(&cp.args,"--no-checkout");+if(clone_data->quiet)+strvec_push(&cp.args,"--quiet");+if(clone_data->progress)+strvec_push(&cp.args,"--progress");+if(clone_data->depth&&*(clone_data->depth))+strvec_pushl(&cp.args,"--depth",clone_data->depth,NULL);+if(clone_data->reference.nr){+structstring_list_item*item;+for_each_string_list_item(item,&clone_data->reference)+strvec_pushl(&cp.args,"--reference",+item->string,NULL);+}+if(clone_data->dissociate)+strvec_push(&cp.args,"--dissociate");+if(sm_gitdir&&*sm_gitdir)+strvec_pushl(&cp.args,"--separate-git-dir",sm_gitdir,NULL);+if(clone_data->single_branch>=0)+strvec_push(&cp.args,clone_data->single_branch?+"--single-branch":+"--no-single-branch");++strvec_push(&cp.args,"--");+strvec_push(&cp.args,clone_data->url);+strvec_push(&cp.args,clone_data->path);++cp.git_cmd=1;+prepare_submodule_repo_env(&cp.env_array);+cp.no_stdin=1;++if(run_command(&cp))+die(_("clone of '%s' into submodule path '%s' failed"),+clone_data->url,clone_data->path);+}else{+if(clone_data->require_init&&!access(clone_data->path,X_OK)&&+!is_empty_dir(clone_data->path))+die(_("directory not empty: '%s'"),clone_data->path);+if(safe_create_leading_directories_const(clone_data->path)<0)+die(_("could not create directory '%s'"),clone_data->path);+strbuf_addf(&sb,"%s/index",sm_gitdir);+unlink_or_warn(sb.buf);+strbuf_reset(&sb);+}++connect_work_tree_and_git_dir(clone_data->path,sm_gitdir,0);++p=git_pathdup_submodule(clone_data->path,"config");+if(!p)+die(_("could not get submodule directory for '%s'"),clone_data->path);++/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */+git_config_get_string("submodule.alternateLocation",&sm_alternate);+if(sm_alternate)+git_config_set_in_file(p,"submodule.alternateLocation",+sm_alternate);+git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);+if(error_strategy)+git_config_set_in_file(p,"submodule.alternateErrorStrategy",+error_strategy);++free(sm_alternate);+free(error_strategy);++strbuf_release(&sb);+free(sm_gitdir);+free(p);+return0;+}+staticintmodule_clone(intargc,constchar**argv,constchar*prefix){-constchar*name=NULL,*url=NULL,*depth=NULL;-intquiet=0;-intprogress=0;-char*p,*path=NULL,*sm_gitdir;-structstrbufsb=STRBUF_INIT;-structstring_listreference=STRING_LIST_INIT_NODUP;-intdissociate=0,require_init=0;-char*sm_alternate=NULL,*error_strategy=NULL;-intsingle_branch=-1;+intdissociate=0,quiet=0,progress=0,require_init=0;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;structoptionmodule_clone_options[]={-OPT_STRING(0,"prefix",&prefix,+OPT_STRING(0,"prefix",&clone_data.prefix,N_("path"),N_("alternative anchor for relative paths")),-OPT_STRING(0,"path",&path,+OPT_STRING(0,"path",&clone_data.path,N_("path"),N_("where the new submodule will be cloned to")),-OPT_STRING(0,"name",&name,+OPT_STRING(0,"name",&clone_data.name,N_("string"),N_("name of the new submodule")),-OPT_STRING(0,"url",&url,+OPT_STRING(0,"url",&clone_data.url,N_("string"),N_("url where to clone the submodule from")),-OPT_STRING_LIST(0,"reference",&reference,+OPT_STRING_LIST(0,"reference",&clone_data.reference,N_("repo"),N_("reference repository")),OPT_BOOL(0,"dissociate",&dissociate,N_("use --reference only while cloning")),-OPT_STRING(0,"depth",&depth,+OPT_STRING(0,"depth",&clone_data.depth,N_("string"),N_("depth for shallow clones")),OPT__QUIET(&quiet,"Suppress output for cloning a submodule"),
@@ -1840,7 +1906,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)N_("force cloning progress")),OPT_BOOL(0,"require-init",&require_init,N_("disallow cloning into non-empty directory")),-OPT_BOOL(0,"single-branch",&single_branch,+OPT_BOOL(0,"single-branch",&clone_data.single_branch,N_("clone only one branch, HEAD or --branch")),OPT_END()};
@@ -1856,67 +1922,16 @@ static int module_clone(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,module_clone_options,git_submodule_helper_usage,0);-if(argc||!url||!path||!*path)+clone_data.dissociate=!!dissociate;+clone_data.quiet=!!quiet;+clone_data.progress=!!progress;+clone_data.require_init=!!require_init;++if(argc||!clone_data.url||!clone_data.path||!*(clone_data.path))usage_with_options(git_submodule_helper_usage,module_clone_options);-strbuf_addf(&sb,"%s/modules/%s",get_git_dir(),name);-sm_gitdir=absolute_pathdup(sb.buf);-strbuf_reset(&sb);--if(!is_absolute_path(path)){-strbuf_addf(&sb,"%s/%s",get_git_work_tree(),path);-path=strbuf_detach(&sb,NULL);-}else-path=xstrdup(path);--if(validate_submodule_git_dir(sm_gitdir,name)<0)-die(_("refusing to create/use '%s' in another submodule's "-"git dir"),sm_gitdir);--if(!file_exists(sm_gitdir)){-if(safe_create_leading_directories_const(sm_gitdir)<0)-die(_("could not create directory '%s'"),sm_gitdir);--prepare_possible_alternates(name,&reference);--if(clone_submodule(path,sm_gitdir,url,depth,&reference,dissociate,-quiet,progress,single_branch))-die(_("clone of '%s' into submodule path '%s' failed"),-url,path);-}else{-if(require_init&&!access(path,X_OK)&&!is_empty_dir(path))-die(_("directory not empty: '%s'"),path);-if(safe_create_leading_directories_const(path)<0)-die(_("could not create directory '%s'"),path);-strbuf_addf(&sb,"%s/index",sm_gitdir);-unlink_or_warn(sb.buf);-strbuf_reset(&sb);-}--connect_work_tree_and_git_dir(path,sm_gitdir,0);--p=git_pathdup_submodule(path,"config");-if(!p)-die(_("could not get submodule directory for '%s'"),path);--/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */-git_config_get_string("submodule.alternateLocation",&sm_alternate);-if(sm_alternate)-git_config_set_in_file(p,"submodule.alternateLocation",-sm_alternate);-git_config_get_string("submodule.alternateErrorStrategy",&error_strategy);-if(error_strategy)-git_config_set_in_file(p,"submodule.alternateErrorStrategy",-error_strategy);--free(sm_alternate);-free(error_strategy);--strbuf_release(&sb);-free(sm_gitdir);-free(path);-free(p);+clone_submodule(&clone_data);return0;}
Let's add a new "add-clone" subcommand to `git submodule--helper` with
the goal of converting part of the shell code in git-submodule.sh
related to `git submodule add` into C code. This new subcommand clones
the repository that is to be added, and checks out to the appropriate
branch.
This is meant to be a faithful conversion that leaves the behaviour of
'submodule add' unchanged. The only minor change is that if a submodule name has
been supplied with a name that clashes with a local submodule, the message shown
to the user ("A git directory for 'foo' is found locally...") is prepended with
"error" for clarity.
This is part of a series of changes that will result in all of 'submodule add'
being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
Helped-by: Đoàn Trần Công Danh [off-list ref]
---
builtin/submodule--helper.c | 177 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 38 +-------
2 files changed, 178 insertions(+), 37 deletions(-)
@@ -2760,6 +2760,182 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)return!!ret;}+structadd_data{+constchar*prefix;+constchar*branch;+constchar*reference_path;+constchar*sm_path;+constchar*sm_name;+constchar*repo;+constchar*realrepo;+intdepth;+unsignedintforce:1;+unsignedintquiet:1;+unsignedintprogress:1;+unsignedintdissociate:1;+};+#define ADD_DATA_INIT { .depth = -1 }++staticvoidshow_fetch_remotes(FILE*output,constchar*sm_name,constchar*git_dir_path)+{+structchild_processcp_remote=CHILD_PROCESS_INIT;+structstrbufsb_remote_out=STRBUF_INIT;++cp_remote.git_cmd=1;+strvec_pushf(&cp_remote.env_array,+"GIT_DIR=%s",git_dir_path);+strvec_push(&cp_remote.env_array,"GIT_WORK_TREE=.");+strvec_pushl(&cp_remote.args,"remote","-v",NULL);+if(!capture_command(&cp_remote,&sb_remote_out,0)){+char*next_line;+char*line=sb_remote_out.buf;+while((next_line=strchr(line,'\n'))!=NULL){+size_tlen=next_line-line;+if(strip_suffix_mem(line,&len," (fetch)"))+fprintf(output," %.*s\n",(int)len,line);+line=next_line+1;+}+}++strbuf_release(&sb_remote_out);+}++staticintadd_submodule(conststructadd_data*add_data)+{+char*submod_gitdir_path;+structmodule_clone_dataclone_data=MODULE_CLONE_DATA_INIT;++/* perhaps the path already exists and is already a git repo, else clone it */+if(is_directory(add_data->sm_path)){+structstrbufsm_path=STRBUF_INIT;+strbuf_addstr(&sm_path,add_data->sm_path);+submod_gitdir_path=xstrfmt("%s/.git",add_data->sm_path);+if(is_nonbare_repository_dir(&sm_path))+printf(_("Adding existing repo at '%s' to the index\n"),+add_data->sm_path);+else+die(_("'%s' already exists and is not a valid git repo"),+add_data->sm_path);+strbuf_release(&sm_path);+free(submod_gitdir_path);+}else{+structchild_processcp=CHILD_PROCESS_INIT;+submod_gitdir_path=xstrfmt(".git/modules/%s",add_data->sm_name);++if(is_directory(submod_gitdir_path)){+if(!add_data->force){+fprintf(stderr,_("A git directory for '%s' is found "+"locally with remote(s):"),+add_data->sm_name);+show_fetch_remotes(stderr,add_data->sm_name,+submod_gitdir_path);+free(submod_gitdir_path);+die(_("If you want to reuse this local git "+"directory instead of cloning again from\n"+" %s\n"+"use the '--force' option. If the local git "+"directory is not the correct repo\n"+"or if you are unsure what this means, choose "+"another name with the '--name' option.\n"),+add_data->realrepo);+}else{+printf(_("Reactivating local git directory for "+"submodule '%s'\n"),add_data->sm_name);+}+}+free(submod_gitdir_path);++clone_data.prefix=add_data->prefix;+clone_data.path=add_data->sm_path;+clone_data.name=add_data->sm_name;+clone_data.url=add_data->realrepo;+clone_data.quiet=add_data->quiet;+clone_data.progress=add_data->progress;+if(add_data->reference_path)+string_list_append(&clone_data.reference,+xstrdup(add_data->reference_path));+clone_data.dissociate=add_data->dissociate;+if(add_data->depth>=0)+clone_data.depth=xstrfmt("%d",add_data->depth);++if(clone_submodule(&clone_data))+return-1;++prepare_submodule_repo_env(&cp.env_array);+cp.git_cmd=1;+cp.dir=add_data->sm_path;+strvec_pushl(&cp.args,"checkout","-f","-q",NULL);++if(add_data->branch){+strvec_pushl(&cp.args,"-B",add_data->branch,NULL);+strvec_pushf(&cp.args,"origin/%s",add_data->branch);+}++if(run_command(&cp))+die(_("unable to checkout submodule '%s'"),add_data->sm_path);+}+return0;+}++staticintadd_clone(intargc,constchar**argv,constchar*prefix)+{+intforce=0,quiet=0,dissociate=0,progress=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to checkout on cloning")),+OPT_STRING(0,"prefix",&prefix,+N_("path"),+N_("alternative anchor for relative paths")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT_STRING(0,"url",&add_data.realrepo,+N_("string"),+N_("url where to clone the submodule from")),+OPT_STRING(0,"reference",&add_data.reference_path,+N_("repo"),+N_("reference repository")),+OPT_BOOL(0,"dissociate",&dissociate,+N_("use --reference only while cloning")),+OPT_INTEGER(0,"depth",&add_data.depth,+N_("depth for shallow clones")),+OPT_BOOL(0,"progress",&progress,+N_("force cloning progress")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT__QUIET(&quiet,"suppress output for cloning a submodule"),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-clone [<options>...] "+"--url <url> --path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.prefix=prefix;+add_data.progress=!!progress;+add_data.dissociate=!!dissociate;+add_data.force=!!force;+add_data.quiet=!!quiet;++if(add_submodule(&add_data))+return1;++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -241,43 +241,7 @@ cmd_add()die"$(eval_gettext"'$sm_name' is not a valid submodule name")"fi-# perhaps the path exists and is already a git repo, else clone it-iftest-e"$sm_path"-then-iftest-d"$sm_path"/.git||test-f"$sm_path"/.git-then-eval_gettextln"Adding existing repo at '\$sm_path' to the index"-else-die"$(eval_gettext"'\$sm_path' already exists and is not a valid git repo")"-fi--else-iftest-d".git/modules/$sm_name"-then-iftest-z"$force"-then-eval_gettextln>&2"A git directory for '\$sm_name' is found locally with remote(s):"-GIT_DIR=".git/modules/$sm_name"GIT_WORK_TREE=.gitremote-v|grep'(fetch)'|sed-es,^," ",-es,' (fetch)',,>&2-die"$(eval_gettextln"\-Ifyouwanttoreusethislocalgitdirectoryinsteadofcloningagainfrom-\$realrepo-usethe'--force'option.Ifthelocalgitdirectoryisnotthecorrectrepo-oryouareunsurewhatthismeanschooseanothernamewiththe'--name'option.")"-else-eval_gettextln"Reactivating local git directory for submodule '\$sm_name'."-fi-fi-gitsubmodule--helperclone${GIT_QUIET:+--quiet}${progress:+"--progress"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-(-sanitize_submodule_env-cd"$sm_path"&&-# ash fails to wordsplit ${branch:+-b "$branch"...}-case"$branch"in-'')gitcheckout-f-q;;-?*)gitcheckout-f-q-B"$branch""origin/$branch";;-esac-)||die"$(eval_gettext"Unable to checkout submodule '\$sm_path'")"-fi+gitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exitgitconfigsubmodule."$sm_name".url"$realrepo"gitadd--no-warn-embedded-repo$force"$sm_path"||
Add a new "add-config" subcommand to `git submodule--helper` with the
goal of converting part of the shell code in git-submodule.sh related to
`git submodule add` into C code. This new subcommand sets the
configuration variables of a newly added submodule, by registering the
url in local git config, as well as the submodule name and path in the
.gitmodules file. It also sets 'submodule.<name>.active' to "true" if
the submodule path has not already been covered by any pathspec
specified in 'submodule.active'.
This is meant to be a faithful conversion from shell to C, with only one
minor change: A warning is emitted if no value is specified in
'submodule.active', ie, the config looks like: "[submodule] active\n",
because it is an invalid configuration. It would be helpful to let the
user know that the pathspec is unset, and the value of
'submodule.<name>.active' might be set to 'true' so that they can
rectify their configuration and prevent future surprises (especially
given that the latter variable has a higher priority than the former).
The structure of the conditional to check if we need to set the 'active'
toggle looks different from the shell version -- but behaves the same.
The change was made to decrease code duplication. A comment has been
added to explain that only one value of 'submodule.active' is obtained
to check if we need to call is_submodule_active() at all.
This is part of a series of changes that will result in all of
'submodule add' being converted to C.
Signed-off-by: Atharva Raykar <redacted>
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Shourya Shukla [off-list ref]
Based-on-patch-by: Prathamesh Chavan [off-list ref]
---
builtin/submodule--helper.c | 125 ++++++++++++++++++++++++++++++++++++
git-submodule.sh | 28 +-------
2 files changed, 126 insertions(+), 27 deletions(-)
@@ -2936,6 +2936,130 @@ static int add_clone(int argc, const char **argv, const char *prefix)return0;}+staticvoidconfig_submodule_in_gitmodules(constchar*name,constchar*var,constchar*value)+{+char*key;++if(!is_writing_gitmodules_ok())+die(_("please make sure that the .gitmodules file is in the working tree"));++key=xstrfmt("submodule.%s.%s",name,var);+config_set_in_gitmodules_file_gently(key,value);+free(key);+}++staticvoidconfigure_added_submodule(structadd_data*add_data)+{+char*key,*submod_pathspec=NULL;+structchild_processadd_submod=CHILD_PROCESS_INIT;+structchild_processadd_gitmodules=CHILD_PROCESS_INIT;+intpathspec_key_exists,activate=0;++key=xstrfmt("submodule.%s.url",add_data->sm_name);+git_config_set_gently(key,add_data->realrepo);+free(key);++add_submod.git_cmd=1;+strvec_pushl(&add_submod.args,"add",+"--no-warn-embedded-repo",NULL);+if(add_data->force)+strvec_push(&add_submod.args,"--force");+strvec_pushl(&add_submod.args,"--",add_data->sm_path,NULL);++if(run_command(&add_submod))+die(_("Failed to add submodule '%s'"),add_data->sm_path);++config_submodule_in_gitmodules(add_data->sm_name,"path",add_data->sm_path);+config_submodule_in_gitmodules(add_data->sm_name,"url",add_data->repo);+if(add_data->branch)+config_submodule_in_gitmodules(add_data->sm_name,+"branch",add_data->branch);++add_gitmodules.git_cmd=1;+strvec_pushl(&add_gitmodules.args,+"add","--force","--",".gitmodules",NULL);++if(run_command(&add_gitmodules))+die(_("Failed to register submodule '%s'"),add_data->sm_path);++/*+*NEEDSWORK:Inamulti-working-treeworldthisneedstobe+*setintheper-worktreeconfig.+*/+pathspec_key_exists=!git_config_get_string("submodule.active",+&submod_pathspec);+if(pathspec_key_exists&&!submod_pathspec){+warning(_("The submodule.active configuration exists, but the "+"pathspec was unset. If the submodule is not already "+"active, the value of submodule.%s.active will be "+"be set to 'true'."),add_data->sm_name);+activate=1;+}++/*+*Ifsubmodule.activedoesnotexist,orifthepathspecwasunset,+*wewillactivatethismoduleunconditionally.+*+*Otherwise,weaskis_submodule_active(),whichiterates+*throughallthevaluesof'submodule.active'todetermine+*ifthismoduleisalreadyactive.+*/+if(!pathspec_key_exists||activate||+!is_submodule_active(the_repository,add_data->sm_path)){+key=xstrfmt("submodule.%s.active",add_data->sm_name);+git_config_set_gently(key,"true");+free(key);+}+}++staticintadd_config(intargc,constchar**argv,constchar*prefix)+{+intforce=0;+structadd_dataadd_data=ADD_DATA_INIT;++structoptionoptions[]={+OPT_STRING('b',"branch",&add_data.branch,+N_("branch"),+N_("branch of repository to store in "+"the submodule configuration")),+OPT_STRING(0,"url",&add_data.repo,+N_("string"),+N_("url to clone submodule from")),+OPT_STRING(0,"resolved-url",&add_data.realrepo,+N_("string"),+N_("url to clone the submodule from, after it has "+"been dereferenced relative to parent's url, "+"in the case where <url> is a relative url")),+OPT_STRING(0,"path",&add_data.sm_path,+N_("path"),+N_("where the new submodule will be cloned to")),+OPT_STRING(0,"name",&add_data.sm_name,+N_("string"),+N_("name of the new submodule")),+OPT__FORCE(&force,N_("allow adding an otherwise ignored submodule path"),+PARSE_OPT_NOCOMPLETE),+OPT_END()+};++constchar*constusage[]={+N_("git submodule--helper add-config "+"[--force|-f] [--branch|-b <branch>] "+"--url <url> --resolved-url <resolved-url> "+"--path <path> --name <name>"),+NULL+};++argc=parse_options(argc,argv,prefix,options,usage,0);++if(argc!=0)+usage_with_options(usage,options);++add_data.force=!!force;+configure_added_submodule(&add_data);++return0;+}+#define SUPPORT_SUPER_PREFIX (1<<0)structcmd_struct{
@@ -242,33 +242,7 @@ cmd_add()figitsubmodule--helperadd-clone${GIT_QUIET:+--quiet}${force:+"--force"}${progress:+"--progress"}${branch:+--branch "$branch"}--prefix"$wt_prefix"--path"$sm_path"--name"$sm_name"--url"$realrepo"${reference:+"$reference"}${dissociate:+"--dissociate"}${depth:+"$depth"}||exit-gitconfigsubmodule."$sm_name".url"$realrepo"--gitadd--no-warn-embedded-repo$force"$sm_path"||-die"$(eval_gettext"Failed to add submodule '\$sm_path'")"--gitsubmodule--helperconfigsubmodule."$sm_name".path"$sm_path"&&-gitsubmodule--helperconfigsubmodule."$sm_name".url"$repo"&&-iftest-n"$branch"-then-gitsubmodule--helperconfigsubmodule."$sm_name".branch"$branch"-fi&&-gitadd--force.gitmodules||-die"$(eval_gettext"Failed to register submodule '\$sm_path'")"--# NEEDSWORK: In a multi-working-tree world, this needs to be-# set in the per-worktree config.-ifgitconfig--getsubmodule.active>/dev/null-then-# If the submodule being adding isn't already covered by the-# current configured pathspec, set the submodule's active flag-if!gitsubmodule--helperis-active"$sm_path"-then-gitconfigsubmodule."$sm_name".active"true"-fi-else-gitconfigsubmodule."$sm_name".active"true"-fi+gitsubmodule--helperadd-config${force:+--force}${branch:+--branch "$branch"}--url"$repo"--resolved-url"$realrepo"--path"$sm_path"--name"$sm_name"}#