submodule--helper: convert the bulk of cmd_add() to C

Introduce the 'add' subcommand to `submodule--helper.c` that does all the work 'submodule add' past the parsing of flags. We also remove the constness of the sm_path field of the `add_data` struct. This is needed so that it can be modified by normalize_path_copy(). As with the previous conversions, this is meant to be a faithful conversion with no modification to the behaviour of `submodule add`. Signed-off-by: Atharva Raykar <raykar.ath@gmail.com> Mentored-by: Christian Couder <christian.couder@gmail.com> Helped-by: Kaartic Sivaraam <kaartic.sivaraam@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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Atharva Raykar committed Aug 10, 2021 at 17:16 UTC a6226fd772b1dfff87fa8dc040d97efa75a3721c
2 files changed +166 -95
builtin/submodule--helper.c
+164 -1
@@ -2748,7 +2748,7 @@ struct add_data {
2748 const char *prefix;
2749 const char *branch;
2750 const char *reference_path;
2751 - const char *sm_path;
2751 + char *sm_path;
2752 const char *sm_name;
2753 const char *repo;
2754 const char *realrepo;
@@ -3047,6 +3047,168 @@ static int add_config(int argc, const char **argv, const char *prefix)
3047 return 0;
3048 }
3049
3050 +static void die_on_index_match(const char *path, int force)
3051 +{
3052 + struct pathspec ps;
3053 + const char *args[] = { path, NULL };
3054 + parse_pathspec(&ps, 0, PATHSPEC_PREFER_CWD, NULL, args);
3055 +
3056 + if (read_cache_preload(NULL) < 0)
3057 + die(_("index file corrupt"));
3058 +
3059 + if (ps.nr) {
3060 + int i;
3061 + char *ps_matched = xcalloc(ps.nr, 1);
3062 +
3063 + /* TODO: audit for interaction with sparse-index. */
3064 + ensure_full_index(&the_index);
3065 +
3066 + /*
3067 + * Since there is only one pathspec, we just need
3068 + * need to check ps_matched[0] to know if a cache
3069 + * entry matched.
3070 + */
3071 + for (i = 0; i < active_nr; i++) {
3072 + ce_path_match(&the_index, active_cache[i], &ps,
3073 + ps_matched);
3074 +
3075 + if (ps_matched[0]) {
3076 + if (!force)
3077 + die(_("'%s' already exists in the index"),
3078 + path);
3079 + if (!S_ISGITLINK(active_cache[i]->ce_mode))
3080 + die(_("'%s' already exists in the index "
3081 + "and is not a submodule"), path);
3082 + break;
3083 + }
3084 + }
3085 + free(ps_matched);
3086 + }
3087 +}
3088 +
3089 +static void die_on_repo_without_commits(const char *path)
3090 +{
3091 + struct strbuf sb = STRBUF_INIT;
3092 + strbuf_addstr(&sb, path);
3093 + if (is_nonbare_repository_dir(&sb)) {
3094 + struct object_id oid;
3095 + if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
3096 + die(_("'%s' does not have a commit checked out"), path);
3097 + }
3098 +}
3099 +
3100 +static int module_add(int argc, const char **argv, const char *prefix)
3101 +{
3102 + int force = 0, quiet = 0, progress = 0, dissociate = 0;
3103 + struct add_data add_data = ADD_DATA_INIT;
3104 +
3105 + struct option options[] = {
3106 + OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
3107 + N_("branch of repository to add as submodule")),
3108 + OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
3109 + PARSE_OPT_NOCOMPLETE),
3110 + OPT__QUIET(&quiet, N_("print only error messages")),
3111 + OPT_BOOL(0, "progress", &progress, N_("force cloning progress")),
3112 + OPT_STRING(0, "reference", &add_data.reference_path, N_("repository"),
3113 + N_("reference repository")),
3114 + OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
3115 + OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
3116 + N_("sets the submodule’s name to the given string "
3117 + "instead of defaulting to its path")),
3118 + OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")),
3119 + OPT_END()
3120 + };
3121 +
3122 + const char *const usage[] = {
3123 + N_("git submodule--helper add [<options>] [--] <repository> [<path>]"),
3124 + NULL
3125 + };
3126 +
3127 + argc = parse_options(argc, argv, prefix, options, usage, 0);
3128 +
3129 + if (!is_writing_gitmodules_ok())
3130 + die(_("please make sure that the .gitmodules file is in the working tree"));
3131 +
3132 + if (prefix && *prefix &&
3133 + add_data.reference_path && !is_absolute_path(add_data.reference_path))
3134 + add_data.reference_path = xstrfmt("%s%s", prefix, add_data.reference_path);
3135 +
3136 + if (argc == 0 || argc > 2)
3137 + usage_with_options(usage, options);
3138 +
3139 + add_data.repo = argv[0];
3140 + if (argc == 1)
3141 + add_data.sm_path = git_url_basename(add_data.repo, 0, 0);
3142 + else
3143 + add_data.sm_path = xstrdup(argv[1]);
3144 +
3145 + if (prefix && *prefix && !is_absolute_path(add_data.sm_path))
3146 + add_data.sm_path = xstrfmt("%s%s", prefix, add_data.sm_path);
3147 +
3148 + if (starts_with_dot_dot_slash(add_data.repo) ||
3149 + starts_with_dot_slash(add_data.repo)) {
3150 + if (prefix)
3151 + die(_("Relative path can only be used from the toplevel "
3152 + "of the working tree"));
3153 +
3154 + /* dereference source url relative to parent's url */
3155 + add_data.realrepo = compute_submodule_clone_url(add_data.repo, NULL, 1);
3156 + } else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) {
3157 + add_data.realrepo = add_data.repo;
3158 + } else {
3159 + die(_("repo URL: '%s' must be absolute or begin with ./|../"),
3160 + add_data.repo);
3161 + }
3162 +
3163 + /*
3164 + * normalize path:
3165 + * multiple //; leading ./; /./; /../;
3166 + */
3167 + normalize_path_copy(add_data.sm_path, add_data.sm_path);
3168 + strip_dir_trailing_slashes(add_data.sm_path);
3169 +
3170 + die_on_index_match(add_data.sm_path, force);
3171 + die_on_repo_without_commits(add_data.sm_path);
3172 +
3173 + if (!force) {
3174 + int exit_code = -1;
3175 + struct strbuf sb = STRBUF_INIT;
3176 + struct child_process cp = CHILD_PROCESS_INIT;
3177 + cp.git_cmd = 1;
3178 + cp.no_stdout = 1;
3179 + strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing",
3180 + "--no-warn-embedded-repo", add_data.sm_path, NULL);
3181 + if ((exit_code = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) {
3182 + strbuf_complete_line(&sb);
3183 + fputs(sb.buf, stderr);
3184 + free(add_data.sm_path);
3185 + return exit_code;
3186 + }
3187 + strbuf_release(&sb);
3188 + }
3189 +
3190 + if(!add_data.sm_name)
3191 + add_data.sm_name = add_data.sm_path;
3192 +
3193 + if (check_submodule_name(add_data.sm_name))
3194 + die(_("'%s' is not a valid submodule name"), add_data.sm_name);
3195 +
3196 + add_data.prefix = prefix;
3197 + add_data.force = !!force;
3198 + add_data.quiet = !!quiet;
3199 + add_data.progress = !!progress;
3200 + add_data.dissociate = !!dissociate;
3201 +
3202 + if (add_submodule(&add_data)) {
3203 + free(add_data.sm_path);
3204 + return 1;
3205 + }
3206 + configure_added_submodule(&add_data);
3207 + free(add_data.sm_path);
3208 +
3209 + return 0;
3210 +}
3211 +
3212 #define SUPPORT_SUPER_PREFIX (1<<0)
3213
3214 struct cmd_struct {
@@ -3061,6 +3223,7 @@ static struct cmd_struct commands[] = {
3223 {"clone", module_clone, 0},
3224 {"add-clone", add_clone, 0},
3225 {"add-config", add_config, 0},
3226 + {"add", module_add, SUPPORT_SUPER_PREFIX},
3227 {"update-module-mode", module_update_module_mode, 0},
3228 {"update-clone", update_clone, 0},
3229 {"ensure-core-worktree", ensure_core_worktree, 0},
git-submodule.sh
+2 -94
@@ -145,104 +145,12 @@ cmd_add()
145 shift
146 done
147
148 - if ! git submodule--helper config --check-writeable >/dev/null 2>&1
148 + if test -z "$1"
149 then
150 - die "fatal: $(eval_gettext "please make sure that the .gitmodules file is in the working tree")"
151 - fi
152 -
153 - if test -n "$reference_path"
154 - then
155 - is_absolute_path "$reference_path" ||
156 - reference_path="$wt_prefix$reference_path"
157 -
158 - reference="--reference=$reference_path"
159 - fi
160 -
161 - repo=$1
162 - sm_path=$2
163 -
164 - if test -z "$sm_path"; then
165 - sm_path=$(printf '%s\n' "$repo" |
166 - sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
167 - fi
168 -
169 - if test -z "$repo" || test -z "$sm_path"; then
150 usage
151 fi
152
173 - is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
174 -
175 - # assure repo is absolute or relative to parent
176 - case "$repo" in
177 - ./*|../*)
178 - test -z "$wt_prefix" ||
179 - die "fatal: $(gettext "Relative path can only be used from the toplevel of the working tree")"
180 -
181 - # dereference source url relative to parent's url
182 - realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
183 - ;;
184 - *:*|/*)
185 - # absolute url
186 - realrepo=$repo
187 - ;;
188 - *)
189 - die "fatal: $(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
190 - ;;
191 - esac
192 -
193 - # normalize path:
194 - # multiple //; leading ./; /./; /../; trailing /
195 - sm_path=$(printf '%s/\n' "$sm_path" |
196 - sed -e '
197 - s|//*|/|g
198 - s|^\(\./\)*||
199 - s|/\(\./\)*|/|g
200 - :start
201 - s|\([^/]*\)/\.\./||
202 - tstart
203 - s|/*$||
204 - ')
205 - if test -z "$force"
206 - then
207 - git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
208 - die "fatal: $(eval_gettext "'\$sm_path' already exists in the index")"
209 - else
210 - git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 &&
211 - die "fatal: $(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
212 - fi
213 -
214 - if test -d "$sm_path" &&
215 - test -z $(git -C "$sm_path" rev-parse --show-cdup 2>/dev/null)
216 - then
217 - git -C "$sm_path" rev-parse --verify -q HEAD >/dev/null ||
218 - die "fatal: $(eval_gettext "'\$sm_path' does not have a commit checked out")"
219 - fi
220 -
221 - if test -z "$force"
222 - then
223 - dryerr=$(git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" 2>&1 >/dev/null)
224 - res=$?
225 - if test $res -ne 0
226 - then
227 - echo >&2 "$dryerr"
228 - exit $res
229 - fi
230 - fi
231 -
232 - if test -n "$custom_name"
233 - then
234 - sm_name="$custom_name"
235 - else
236 - sm_name="$sm_path"
237 - fi
238 -
239 - if ! git submodule--helper check-name "$sm_name"
240 - then
241 - die "fatal: $(eval_gettext "'$sm_name' is not a valid submodule name")"
242 - fi
243 -
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 submodule--helper add-config ${force:+--force} ${branch:+--branch "$branch"} --url "$repo" --resolved-url "$realrepo" --path "$sm_path" --name "$sm_name"
153 + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${GIT_QUIET:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@"
154 }
155
156 #