|
20 | 20 | #define OPT_QUIET (1 << 0) |
21 | 21 | #define OPT_CACHED (1 << 1) |
22 | 22 | #define OPT_RECURSIVE (1 << 2) |
| 23 | +#define OPT_FORCE (1 << 3) |
23 | 24 |
|
24 | 25 | typedef void (*each_submodule_fn)(const struct cache_entry *list_item, |
25 | 26 | void *cb_data); |
@@ -50,6 +51,20 @@ static char *get_default_remote(void) |
50 | 51 | return ret; |
51 | 52 | } |
52 | 53 |
|
| 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 | + |
53 | 68 | static int starts_with_dot_slash(const char *str) |
54 | 69 | { |
55 | 70 | return str[0] == '.' && is_dir_sep(str[1]); |
@@ -358,6 +373,25 @@ static void module_list_active(struct module_list *list) |
358 | 373 | *list = active_modules; |
359 | 374 | } |
360 | 375 |
|
| 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 | + |
361 | 395 | static int module_list(int argc, const char **argv, const char *prefix) |
362 | 396 | { |
363 | 397 | int i; |
@@ -718,6 +752,309 @@ static int module_name(int argc, const char **argv, const char *prefix) |
718 | 752 | return 0; |
719 | 753 | } |
720 | 754 |
|
| 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 | + |
721 | 1058 | static int clone_submodule(const char *path, const char *gitdir, const char *url, |
722 | 1059 | const char *depth, struct string_list *reference, |
723 | 1060 | int quiet, int progress) |
@@ -1498,6 +1835,9 @@ static struct cmd_struct commands[] = { |
1498 | 1835 | {"resolve-relative-url-test", resolve_relative_url_test, 0}, |
1499 | 1836 | {"init", module_init, SUPPORT_SUPER_PREFIX}, |
1500 | 1837 | {"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}, |
1501 | 1841 | {"remote-branch", resolve_remote_submodule_branch, 0}, |
1502 | 1842 | {"push-check", push_check, 0}, |
1503 | 1843 | {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, |
|
0 commit comments