Skip to content

Commit ead8dbe

Browse files
committed
Merge branch 'pc/submodule-helper'
Rewrite two more "git submodule" subcommands in C. * pc/submodule-helper: submodule: port submodule subcommand 'deinit' from shell to C submodule: port submodule subcommand 'sync' from shell to C
2 parents 922ffec + 2e61273 commit ead8dbe

File tree

2 files changed

+342
-110
lines changed

2 files changed

+342
-110
lines changed

builtin/submodule--helper.c

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#define OPT_QUIET (1 << 0)
2121
#define OPT_CACHED (1 << 1)
2222
#define OPT_RECURSIVE (1 << 2)
23+
#define OPT_FORCE (1 << 3)
2324

2425
typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
2526
void *cb_data);
@@ -50,6 +51,20 @@ static char *get_default_remote(void)
5051
return ret;
5152
}
5253

54+
static int print_default_remote(int argc, const char **argv, const char *prefix)
55+
{
56+
const char *remote;
57+
58+
if (argc != 1)
59+
die(_("submodule--helper print-default-remote takes no arguments"));
60+
61+
remote = get_default_remote();
62+
if (remote)
63+
printf("%s\n", remote);
64+
65+
return 0;
66+
}
67+
5368
static int starts_with_dot_slash(const char *str)
5469
{
5570
return str[0] == '.' && is_dir_sep(str[1]);
@@ -358,6 +373,25 @@ static void module_list_active(struct module_list *list)
358373
*list = active_modules;
359374
}
360375

376+
static char *get_up_path(const char *path)
377+
{
378+
int i;
379+
struct strbuf sb = STRBUF_INIT;
380+
381+
for (i = count_slashes(path); i; i--)
382+
strbuf_addstr(&sb, "../");
383+
384+
/*
385+
* Check if 'path' ends with slash or not
386+
* for having the same output for dir/sub_dir
387+
* and dir/sub_dir/
388+
*/
389+
if (!is_dir_sep(path[strlen(path) - 1]))
390+
strbuf_addstr(&sb, "../");
391+
392+
return strbuf_detach(&sb, NULL);
393+
}
394+
361395
static int module_list(int argc, const char **argv, const char *prefix)
362396
{
363397
int i;
@@ -718,6 +752,309 @@ static int module_name(int argc, const char **argv, const char *prefix)
718752
return 0;
719753
}
720754

755+
struct sync_cb {
756+
const char *prefix;
757+
unsigned int flags;
758+
};
759+
760+
#define SYNC_CB_INIT { NULL, 0 }
761+
762+
static void sync_submodule(const char *path, const char *prefix,
763+
unsigned int flags)
764+
{
765+
const struct submodule *sub;
766+
char *remote_key = NULL;
767+
char *sub_origin_url, *super_config_url, *displaypath;
768+
struct strbuf sb = STRBUF_INIT;
769+
struct child_process cp = CHILD_PROCESS_INIT;
770+
char *sub_config_path = NULL;
771+
772+
if (!is_submodule_active(the_repository, path))
773+
return;
774+
775+
sub = submodule_from_path(&null_oid, path);
776+
777+
if (sub && sub->url) {
778+
if (starts_with_dot_dot_slash(sub->url) ||
779+
starts_with_dot_slash(sub->url)) {
780+
char *remote_url, *up_path;
781+
char *remote = get_default_remote();
782+
strbuf_addf(&sb, "remote.%s.url", remote);
783+
784+
if (git_config_get_string(sb.buf, &remote_url))
785+
remote_url = xgetcwd();
786+
787+
up_path = get_up_path(path);
788+
sub_origin_url = relative_url(remote_url, sub->url, up_path);
789+
super_config_url = relative_url(remote_url, sub->url, NULL);
790+
791+
free(remote);
792+
free(up_path);
793+
free(remote_url);
794+
} else {
795+
sub_origin_url = xstrdup(sub->url);
796+
super_config_url = xstrdup(sub->url);
797+
}
798+
} else {
799+
sub_origin_url = xstrdup("");
800+
super_config_url = xstrdup("");
801+
}
802+
803+
displaypath = get_submodule_displaypath(path, prefix);
804+
805+
if (!(flags & OPT_QUIET))
806+
printf(_("Synchronizing submodule url for '%s'\n"),
807+
displaypath);
808+
809+
strbuf_reset(&sb);
810+
strbuf_addf(&sb, "submodule.%s.url", sub->name);
811+
if (git_config_set_gently(sb.buf, super_config_url))
812+
die(_("failed to register url for submodule path '%s'"),
813+
displaypath);
814+
815+
if (!is_submodule_populated_gently(path, NULL))
816+
goto cleanup;
817+
818+
prepare_submodule_repo_env(&cp.env_array);
819+
cp.git_cmd = 1;
820+
cp.dir = path;
821+
argv_array_pushl(&cp.args, "submodule--helper",
822+
"print-default-remote", NULL);
823+
824+
strbuf_reset(&sb);
825+
if (capture_command(&cp, &sb, 0))
826+
die(_("failed to get the default remote for submodule '%s'"),
827+
path);
828+
829+
strbuf_strip_suffix(&sb, "\n");
830+
remote_key = xstrfmt("remote.%s.url", sb.buf);
831+
832+
strbuf_reset(&sb);
833+
submodule_to_gitdir(&sb, path);
834+
strbuf_addstr(&sb, "/config");
835+
836+
if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url))
837+
die(_("failed to update remote for submodule '%s'"),
838+
path);
839+
840+
if (flags & OPT_RECURSIVE) {
841+
struct child_process cpr = CHILD_PROCESS_INIT;
842+
843+
cpr.git_cmd = 1;
844+
cpr.dir = path;
845+
prepare_submodule_repo_env(&cpr.env_array);
846+
847+
argv_array_push(&cpr.args, "--super-prefix");
848+
argv_array_pushf(&cpr.args, "%s/", displaypath);
849+
argv_array_pushl(&cpr.args, "submodule--helper", "sync",
850+
"--recursive", NULL);
851+
852+
if (flags & OPT_QUIET)
853+
argv_array_push(&cpr.args, "--quiet");
854+
855+
if (run_command(&cpr))
856+
die(_("failed to recurse into submodule '%s'"),
857+
path);
858+
}
859+
860+
cleanup:
861+
free(super_config_url);
862+
free(sub_origin_url);
863+
strbuf_release(&sb);
864+
free(remote_key);
865+
free(displaypath);
866+
free(sub_config_path);
867+
}
868+
869+
static void sync_submodule_cb(const struct cache_entry *list_item, void *cb_data)
870+
{
871+
struct sync_cb *info = cb_data;
872+
sync_submodule(list_item->name, info->prefix, info->flags);
873+
874+
}
875+
876+
static int module_sync(int argc, const char **argv, const char *prefix)
877+
{
878+
struct sync_cb info = SYNC_CB_INIT;
879+
struct pathspec pathspec;
880+
struct module_list list = MODULE_LIST_INIT;
881+
int quiet = 0;
882+
int recursive = 0;
883+
884+
struct option module_sync_options[] = {
885+
OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")),
886+
OPT_BOOL(0, "recursive", &recursive,
887+
N_("Recurse into nested submodules")),
888+
OPT_END()
889+
};
890+
891+
const char *const git_submodule_helper_usage[] = {
892+
N_("git submodule--helper sync [--quiet] [--recursive] [<path>]"),
893+
NULL
894+
};
895+
896+
argc = parse_options(argc, argv, prefix, module_sync_options,
897+
git_submodule_helper_usage, 0);
898+
899+
if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
900+
return 1;
901+
902+
info.prefix = prefix;
903+
if (quiet)
904+
info.flags |= OPT_QUIET;
905+
if (recursive)
906+
info.flags |= OPT_RECURSIVE;
907+
908+
for_each_listed_submodule(&list, sync_submodule_cb, &info);
909+
910+
return 0;
911+
}
912+
913+
struct deinit_cb {
914+
const char *prefix;
915+
unsigned int flags;
916+
};
917+
#define DEINIT_CB_INIT { NULL, 0 }
918+
919+
static void deinit_submodule(const char *path, const char *prefix,
920+
unsigned int flags)
921+
{
922+
const struct submodule *sub;
923+
char *displaypath = NULL;
924+
struct child_process cp_config = CHILD_PROCESS_INIT;
925+
struct strbuf sb_config = STRBUF_INIT;
926+
char *sub_git_dir = xstrfmt("%s/.git", path);
927+
928+
sub = submodule_from_path(&null_oid, path);
929+
930+
if (!sub || !sub->name)
931+
goto cleanup;
932+
933+
displaypath = get_submodule_displaypath(path, prefix);
934+
935+
/* remove the submodule work tree (unless the user already did it) */
936+
if (is_directory(path)) {
937+
struct strbuf sb_rm = STRBUF_INIT;
938+
const char *format;
939+
940+
/*
941+
* protect submodules containing a .git directory
942+
* NEEDSWORK: instead of dying, automatically call
943+
* absorbgitdirs and (possibly) warn.
944+
*/
945+
if (is_directory(sub_git_dir))
946+
die(_("Submodule work tree '%s' contains a .git "
947+
"directory (use 'rm -rf' if you really want "
948+
"to remove it including all of its history)"),
949+
displaypath);
950+
951+
if (!(flags & OPT_FORCE)) {
952+
struct child_process cp_rm = CHILD_PROCESS_INIT;
953+
cp_rm.git_cmd = 1;
954+
argv_array_pushl(&cp_rm.args, "rm", "-qn",
955+
path, NULL);
956+
957+
if (run_command(&cp_rm))
958+
die(_("Submodule work tree '%s' contains local "
959+
"modifications; use '-f' to discard them"),
960+
displaypath);
961+
}
962+
963+
strbuf_addstr(&sb_rm, path);
964+
965+
if (!remove_dir_recursively(&sb_rm, 0))
966+
format = _("Cleared directory '%s'\n");
967+
else
968+
format = _("Could not remove submodule work tree '%s'\n");
969+
970+
if (!(flags & OPT_QUIET))
971+
printf(format, displaypath);
972+
973+
strbuf_release(&sb_rm);
974+
}
975+
976+
if (mkdir(path, 0777))
977+
printf(_("could not create empty submodule directory %s"),
978+
displaypath);
979+
980+
cp_config.git_cmd = 1;
981+
argv_array_pushl(&cp_config.args, "config", "--get-regexp", NULL);
982+
argv_array_pushf(&cp_config.args, "submodule.%s\\.", sub->name);
983+
984+
/* remove the .git/config entries (unless the user already did it) */
985+
if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) {
986+
char *sub_key = xstrfmt("submodule.%s", sub->name);
987+
/*
988+
* remove the whole section so we have a clean state when
989+
* the user later decides to init this submodule again
990+
*/
991+
git_config_rename_section_in_file(NULL, sub_key, NULL);
992+
if (!(flags & OPT_QUIET))
993+
printf(_("Submodule '%s' (%s) unregistered for path '%s'\n"),
994+
sub->name, sub->url, displaypath);
995+
free(sub_key);
996+
}
997+
998+
cleanup:
999+
free(displaypath);
1000+
free(sub_git_dir);
1001+
strbuf_release(&sb_config);
1002+
}
1003+
1004+
static void deinit_submodule_cb(const struct cache_entry *list_item,
1005+
void *cb_data)
1006+
{
1007+
struct deinit_cb *info = cb_data;
1008+
deinit_submodule(list_item->name, info->prefix, info->flags);
1009+
}
1010+
1011+
static int module_deinit(int argc, const char **argv, const char *prefix)
1012+
{
1013+
struct deinit_cb info = DEINIT_CB_INIT;
1014+
struct pathspec pathspec;
1015+
struct module_list list = MODULE_LIST_INIT;
1016+
int quiet = 0;
1017+
int force = 0;
1018+
int all = 0;
1019+
1020+
struct option module_deinit_options[] = {
1021+
OPT__QUIET(&quiet, N_("Suppress submodule status output")),
1022+
OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes")),
1023+
OPT_BOOL(0, "all", &all, N_("Unregister all submodules")),
1024+
OPT_END()
1025+
};
1026+
1027+
const char *const git_submodule_helper_usage[] = {
1028+
N_("git submodule deinit [--quiet] [-f | --force] [--all | [--] [<path>...]]"),
1029+
NULL
1030+
};
1031+
1032+
argc = parse_options(argc, argv, prefix, module_deinit_options,
1033+
git_submodule_helper_usage, 0);
1034+
1035+
if (all && argc) {
1036+
error("pathspec and --all are incompatible");
1037+
usage_with_options(git_submodule_helper_usage,
1038+
module_deinit_options);
1039+
}
1040+
1041+
if (!argc && !all)
1042+
die(_("Use '--all' if you really want to deinitialize all submodules"));
1043+
1044+
if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
1045+
BUG("module_list_compute should not choke on empty pathspec");
1046+
1047+
info.prefix = prefix;
1048+
if (quiet)
1049+
info.flags |= OPT_QUIET;
1050+
if (force)
1051+
info.flags |= OPT_FORCE;
1052+
1053+
for_each_listed_submodule(&list, deinit_submodule_cb, &info);
1054+
1055+
return 0;
1056+
}
1057+
7211058
static int clone_submodule(const char *path, const char *gitdir, const char *url,
7221059
const char *depth, struct string_list *reference,
7231060
int quiet, int progress)
@@ -1498,6 +1835,9 @@ static struct cmd_struct commands[] = {
14981835
{"resolve-relative-url-test", resolve_relative_url_test, 0},
14991836
{"init", module_init, SUPPORT_SUPER_PREFIX},
15001837
{"status", module_status, SUPPORT_SUPER_PREFIX},
1838+
{"print-default-remote", print_default_remote, 0},
1839+
{"sync", module_sync, SUPPORT_SUPER_PREFIX},
1840+
{"deinit", module_deinit, 0},
15011841
{"remote-branch", resolve_remote_submodule_branch, 0},
15021842
{"push-check", push_check, 0},
15031843
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},

0 commit comments

Comments
 (0)