submodule--helper: introduce add-clone subcommand

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 'cmd_add()' script unchanged. Signed-off-by: Atharva Raykar <raykar.ath@gmail.com> Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Shourya Shukla <periperidip@gmail.com> Based-on-patch-by: Shourya Shukla <periperidip@gmail.com> Based-on-patch-by: Prathamesh Chavan <pc44800@gmail.com> Helped-by: Đoàn Trần Công Danh <congdanhqx@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Atharva Raykar committed Jul 10, 2021 at 13:18 UTC 8c8195e9c3e21922673575828977845b613c3491
2 files changed +178 -37
builtin/submodule--helper.c
+177
@@ -2760,6 +2760,182 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
2760 return !!ret;
2761 }
2762
2763 +struct add_data {
2764 + const char *prefix;
2765 + const char *branch;
2766 + const char *reference_path;
2767 + const char *sm_path;
2768 + const char *sm_name;
2769 + const char *repo;
2770 + const char *realrepo;
2771 + int depth;
2772 + unsigned int force: 1;
2773 + unsigned int quiet: 1;
2774 + unsigned int progress: 1;
2775 + unsigned int dissociate: 1;
2776 +};
2777 +#define ADD_DATA_INIT { .depth = -1 }
2778 +
2779 +static void show_fetch_remotes(FILE *output, const char *sm_name, const char *git_dir_path)
2780 +{
2781 + struct child_process cp_remote = CHILD_PROCESS_INIT;
2782 + struct strbuf sb_remote_out = STRBUF_INIT;
2783 +
2784 + cp_remote.git_cmd = 1;
2785 + strvec_pushf(&cp_remote.env_array,
2786 + "GIT_DIR=%s", git_dir_path);
2787 + strvec_push(&cp_remote.env_array, "GIT_WORK_TREE=.");
2788 + strvec_pushl(&cp_remote.args, "remote", "-v", NULL);
2789 + if (!capture_command(&cp_remote, &sb_remote_out, 0)) {
2790 + char *next_line;
2791 + char *line = sb_remote_out.buf;
2792 + while ((next_line = strchr(line, '\n')) != NULL) {
2793 + size_t len = next_line - line;
2794 + if (strip_suffix_mem(line, &len, " (fetch)"))
2795 + fprintf(output, " %.*s\n", (int)len, line);
2796 + line = next_line + 1;
2797 + }
2798 + }
2799 +
2800 + strbuf_release(&sb_remote_out);
2801 +}
2802 +
2803 +static int add_submodule(const struct add_data *add_data)
2804 +{
2805 + char *submod_gitdir_path;
2806 + struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
2807 +
2808 + /* perhaps the path already exists and is already a git repo, else clone it */
2809 + if (is_directory(add_data->sm_path)) {
2810 + struct strbuf sm_path = STRBUF_INIT;
2811 + strbuf_addstr(&sm_path, add_data->sm_path);
2812 + submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
2813 + if (is_nonbare_repository_dir(&sm_path))
2814 + printf(_("Adding existing repo at '%s' to the index\n"),
2815 + add_data->sm_path);
2816 + else
2817 + die(_("'%s' already exists and is not a valid git repo"),
2818 + add_data->sm_path);
2819 + strbuf_release(&sm_path);
2820 + free(submod_gitdir_path);
2821 + } else {
2822 + struct child_process cp = CHILD_PROCESS_INIT;
2823 + submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
2824 +
2825 + if (is_directory(submod_gitdir_path)) {
2826 + if (!add_data->force) {
2827 + fprintf(stderr, _("A git directory for '%s' is found "
2828 + "locally with remote(s):"),
2829 + add_data->sm_name);
2830 + show_fetch_remotes(stderr, add_data->sm_name,
2831 + submod_gitdir_path);
2832 + free(submod_gitdir_path);
2833 + die(_("If you want to reuse this local git "
2834 + "directory instead of cloning again from\n"
2835 + " %s\n"
2836 + "use the '--force' option. If the local git "
2837 + "directory is not the correct repo\n"
2838 + "or if you are unsure what this means, choose "
2839 + "another name with the '--name' option.\n"),
2840 + add_data->realrepo);
2841 + } else {
2842 + printf(_("Reactivating local git directory for "
2843 + "submodule '%s'\n"), add_data->sm_name);
2844 + }
2845 + }
2846 + free(submod_gitdir_path);
2847 +
2848 + clone_data.prefix = add_data->prefix;
2849 + clone_data.path = add_data->sm_path;
2850 + clone_data.name = add_data->sm_name;
2851 + clone_data.url = add_data->realrepo;
2852 + clone_data.quiet = add_data->quiet;
2853 + clone_data.progress = add_data->progress;
2854 + if (add_data->reference_path)
2855 + string_list_append(&clone_data.reference,
2856 + xstrdup(add_data->reference_path));
2857 + clone_data.dissociate = add_data->dissociate;
2858 + if (add_data->depth >= 0)
2859 + clone_data.depth = xstrfmt("%d", add_data->depth);
2860 +
2861 + if (clone_submodule(&clone_data))
2862 + return -1;
2863 +
2864 + prepare_submodule_repo_env(&cp.env_array);
2865 + cp.git_cmd = 1;
2866 + cp.dir = add_data->sm_path;
2867 + strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
2868 +
2869 + if (add_data->branch) {
2870 + strvec_pushl(&cp.args, "-B", add_data->branch, NULL);
2871 + strvec_pushf(&cp.args, "origin/%s", add_data->branch);
2872 + }
2873 +
2874 + if (run_command(&cp))
2875 + die(_("unable to checkout submodule '%s'"), add_data->sm_path);
2876 + }
2877 + return 0;
2878 +}
2879 +
2880 +static int add_clone(int argc, const char **argv, const char *prefix)
2881 +{
2882 + int force = 0, quiet = 0, dissociate = 0, progress = 0;
2883 + struct add_data add_data = ADD_DATA_INIT;
2884 +
2885 + struct option options[] = {
2886 + OPT_STRING('b', "branch", &add_data.branch,
2887 + N_("branch"),
2888 + N_("branch of repository to checkout on cloning")),
2889 + OPT_STRING(0, "prefix", &prefix,
2890 + N_("path"),
2891 + N_("alternative anchor for relative paths")),
2892 + OPT_STRING(0, "path", &add_data.sm_path,
2893 + N_("path"),
2894 + N_("where the new submodule will be cloned to")),
2895 + OPT_STRING(0, "name", &add_data.sm_name,
2896 + N_("string"),
2897 + N_("name of the new submodule")),
2898 + OPT_STRING(0, "url", &add_data.realrepo,
2899 + N_("string"),
2900 + N_("url where to clone the submodule from")),
2901 + OPT_STRING(0, "reference", &add_data.reference_path,
2902 + N_("repo"),
2903 + N_("reference repository")),
2904 + OPT_BOOL(0, "dissociate", &dissociate,
2905 + N_("use --reference only while cloning")),
2906 + OPT_INTEGER(0, "depth", &add_data.depth,
2907 + N_("depth for shallow clones")),
2908 + OPT_BOOL(0, "progress", &progress,
2909 + N_("force cloning progress")),
2910 + OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
2911 + PARSE_OPT_NOCOMPLETE),
2912 + OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
2913 + OPT_END()
2914 + };
2915 +
2916 + const char *const usage[] = {
2917 + N_("git submodule--helper add-clone [<options>...] "
2918 + "--url <url> --path <path> --name <name>"),
2919 + NULL
2920 + };
2921 +
2922 + argc = parse_options(argc, argv, prefix, options, usage, 0);
2923 +
2924 + if (argc != 0)
2925 + usage_with_options(usage, options);
2926 +
2927 + add_data.prefix = prefix;
2928 + add_data.progress = !!progress;
2929 + add_data.dissociate = !!dissociate;
2930 + add_data.force = !!force;
2931 + add_data.quiet = !!quiet;
2932 +
2933 + if (add_submodule(&add_data))
2934 + return 1;
2935 +
2936 + return 0;
2937 +}
2938 +
2939 #define SUPPORT_SUPER_PREFIX (1<<0)
2940
2941 struct cmd_struct {
@@ -2772,6 +2948,7 @@ static struct cmd_struct commands[] = {
2948 {"list", module_list, 0},
2949 {"name", module_name, 0},
2950 {"clone", module_clone, 0},
2951 + {"add-clone", add_clone, 0},
2952 {"update-module-mode", module_update_module_mode, 0},
2953 {"update-clone", update_clone, 0},
2954 {"ensure-core-worktree", ensure_core_worktree, 0},
git-submodule.sh
+1 -37
@@ -241,43 +241,7 @@ cmd_add()
241 die "fatal: $(eval_gettext "'$sm_name' is not a valid submodule name")"
242 fi
243
244 - # perhaps the path exists and is already a git repo, else clone it
245 - if test -e "$sm_path"
246 - then
247 - if test -d "$sm_path"/.git || test -f "$sm_path"/.git
248 - then
249 - eval_gettextln "Adding existing repo at '\$sm_path' to the index"
250 - else
251 - die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
252 - fi
253 -
254 - else
255 - if test -d ".git/modules/$sm_name"
256 - then
257 - if test -z "$force"
258 - then
259 - eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
260 - GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2
261 - die "$(eval_gettextln "\
262 -If you want to reuse this local git directory instead of cloning again from
263 - \$realrepo
264 -use the '--force' option. If the local git directory is not the correct repo
265 -or you are unsure what this means choose another name with the '--name' option.")"
266 - else
267 - eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
268 - fi
269 - fi
270 - 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
271 - (
272 - sanitize_submodule_env
273 - cd "$sm_path" &&
274 - # ash fails to wordsplit ${branch:+-b "$branch"...}
275 - case "$branch" in
276 - '') git checkout -f -q ;;
277 - ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
278 - esac
279 - ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
280 - fi
244 + 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
245 git config submodule."$sm_name".url "$realrepo"
246
247 git add --no-warn-embedded-repo $force "$sm_path" ||