#include "revision.h"
#include "run-command.h"
#include "diffcore.h"
+#include "refs.h"
+#include "string-list.h"
+
+static struct string_list config_name_for_path;
+static struct string_list config_fetch_recurse_submodules_for_name;
+static struct string_list config_ignore_for_name;
+static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
+static struct string_list changed_submodule_paths;
static int add_submodule_odb(const char *path)
{
return ret;
}
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+ const char *path)
+{
+ struct string_list_item *path_option, *ignore_option;
+ path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+ if (path_option) {
+ ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
+ if (ignore_option)
+ handle_ignore_submodules_arg(diffopt, ignore_option->util);
+ }
+}
+
+int submodule_config(const char *var, const char *value, void *cb)
+{
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+ else if (!strcmp(var, "fetch.recursesubmodules")) {
+ config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value);
+ return 0;
+ }
+ return 0;
+}
+
+void gitmodules_config(void)
+{
+ const char *work_tree = get_git_work_tree();
+ if (work_tree) {
+ struct strbuf gitmodules_path = STRBUF_INIT;
+ strbuf_addstr(&gitmodules_path, work_tree);
+ strbuf_addstr(&gitmodules_path, "/.gitmodules");
+ git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+ strbuf_release(&gitmodules_path);
+ }
+}
+
+int parse_submodule_config_option(const char *var, const char *value)
+{
+ int len;
+ struct string_list_item *config;
+ struct strbuf submodname = STRBUF_INIT;
+
+ var += 10; /* Skip "submodule." */
+
+ len = strlen(var);
+ if ((len > 5) && !strcmp(var + len - 5, ".path")) {
+ strbuf_add(&submodname, var, len - 5);
+ config = unsorted_string_list_lookup(&config_name_for_path, value);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_name_for_path, xstrdup(value));
+ config->util = strbuf_detach(&submodname, NULL);
+ strbuf_release(&submodname);
+ } else if ((len > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
+ strbuf_add(&submodname, var, len - 23);
+ config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+ if (!config)
+ config = string_list_append(&config_fetch_recurse_submodules_for_name,
+ strbuf_detach(&submodname, NULL));
+ config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value);
+ strbuf_release(&submodname);
+ } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
+ if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
+ strcmp(value, "all") && strcmp(value, "none")) {
+ warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
+ return 0;
+ }
+
+ strbuf_add(&submodname, var, len - 7);
+ config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_ignore_for_name,
+ strbuf_detach(&submodname, NULL));
+ strbuf_release(&submodname);
+ config->util = xstrdup(value);
+ return 0;
+ }
+ return 0;
+}
+
+void handle_ignore_submodules_arg(struct diff_options *diffopt,
+ const char *arg)
+{
+ DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
+
+ if (!strcmp(arg, "all"))
+ DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
+ else if (!strcmp(arg, "untracked"))
+ DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+ else if (!strcmp(arg, "dirty"))
+ DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
+ else if (strcmp(arg, "none"))
+ die("bad --ignore-submodules argument: %s", arg);
+}
+
+static int prepare_submodule_summary(struct rev_info *rev, const char *path,
+ struct commit *left, struct commit *right,
+ int *fast_forward, int *fast_backward)
+{
+ struct commit_list *merge_bases, *list;
+
+ init_revisions(rev, NULL);
+ setup_revisions(0, NULL, rev, NULL);
+ rev->left_right = 1;
+ rev->first_parent_only = 1;
+ left->object.flags |= SYMMETRIC_LEFT;
+ add_pending_object(rev, &left->object, path);
+ add_pending_object(rev, &right->object, path);
+ merge_bases = get_merge_bases(left, right, 1);
+ if (merge_bases) {
+ if (merge_bases->item == left)
+ *fast_forward = 1;
+ else if (merge_bases->item == right)
+ *fast_backward = 1;
+ }
+ for (list = merge_bases; list; list = list->next) {
+ list->item->object.flags |= UNINTERESTING;
+ add_pending_object(rev, &list->item->object,
+ sha1_to_hex(list->item->object.sha1));
+ }
+ return prepare_revision_walk(rev);
+}
+
+static void print_submodule_summary(struct rev_info *rev, FILE *f,
+ const char *del, const char *add, const char *reset)
+{
+ static const char format[] = " %m %s";
+ struct strbuf sb = STRBUF_INIT;
+ struct commit *commit;
+
+ while ((commit = get_revision(rev))) {
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = rev->date_mode;
+ strbuf_setlen(&sb, 0);
+ if (commit->object.flags & SYMMETRIC_LEFT) {
+ if (del)
+ strbuf_addstr(&sb, del);
+ }
+ else if (add)
+ strbuf_addstr(&sb, add);
+ format_commit_message(commit, format, &sb, &ctx);
+ if (reset)
+ strbuf_addstr(&sb, reset);
+ strbuf_addch(&sb, '\n');
+ fprintf(f, "%s", sb.buf);
+ }
+ strbuf_release(&sb);
+}
+
+int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)
+{
+ switch (git_config_maybe_bool(opt, arg)) {
+ case 1:
+ return RECURSE_SUBMODULES_ON;
+ case 0:
+ return RECURSE_SUBMODULES_OFF;
+ default:
+ if (!strcmp(arg, "on-demand"))
+ return RECURSE_SUBMODULES_ON_DEMAND;
+ die("bad %s argument: %s", opt, arg);
+ }
+}
+
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
const char *del, const char *add, const char *reset)
{
struct rev_info rev;
- struct commit *commit, *left = left, *right = right;
- struct commit_list *merge_bases, *list;
+ struct commit *left = left, *right = right;
const char *message = NULL;
struct strbuf sb = STRBUF_INIT;
- static const char *format = " %m %s";
int fast_forward = 0, fast_backward = 0;
if (is_null_sha1(two))
!(right = lookup_commit_reference(two)))
message = "(commits not present)";
- if (!message) {
- init_revisions(&rev, NULL);
- setup_revisions(0, NULL, &rev, NULL);
- rev.left_right = 1;
- rev.first_parent_only = 1;
- left->object.flags |= SYMMETRIC_LEFT;
- add_pending_object(&rev, &left->object, path);
- add_pending_object(&rev, &right->object, path);
- merge_bases = get_merge_bases(left, right, 1);
- if (merge_bases) {
- if (merge_bases->item == left)
- fast_forward = 1;
- else if (merge_bases->item == right)
- fast_backward = 1;
- }
- for (list = merge_bases; list; list = list->next) {
- list->item->object.flags |= UNINTERESTING;
- add_pending_object(&rev, &list->item->object,
- sha1_to_hex(list->item->object.sha1));
- }
- if (prepare_revision_walk(&rev))
- message = "(revision walker failed)";
- }
+ if (!message &&
+ prepare_submodule_summary(&rev, path, left, right,
+ &fast_forward, &fast_backward))
+ message = "(revision walker failed)";
if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
fprintf(f, "Submodule %s contains untracked content\n", path);
fwrite(sb.buf, sb.len, 1, f);
if (!message) {
- while ((commit = get_revision(&rev))) {
- struct pretty_print_context ctx = {0};
- ctx.date_mode = rev.date_mode;
- strbuf_setlen(&sb, 0);
- if (commit->object.flags & SYMMETRIC_LEFT) {
- if (del)
- strbuf_addstr(&sb, del);
- }
- else if (add)
- strbuf_addstr(&sb, add);
- format_commit_message(commit, format, &sb, &ctx);
- if (reset)
- strbuf_addstr(&sb, reset);
- strbuf_addch(&sb, '\n');
- fprintf(f, "%s", sb.buf);
- }
+ print_submodule_summary(&rev, f, del, add, reset);
clear_commit_marks(left, ~0);
clear_commit_marks(right, ~0);
}
+
strbuf_release(&sb);
}
+void set_config_fetch_recurse_submodules(int value)
+{
+ config_fetch_recurse_submodules = value;
+}
+
+static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
+{
+ int is_present = 0;
+ if (!add_submodule_odb(path) && lookup_commit_reference(sha1)) {
+ /* Even if the submodule is checked out and the commit is
+ * present, make sure it is reachable from a ref. */
+ struct child_process cp;
+ const char *argv[] = {"rev-list", "-n", "1", NULL, "--not", "--all", NULL};
+ struct strbuf buf = STRBUF_INIT;
+
+ argv[3] = sha1_to_hex(sha1);
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.out = -1;
+ cp.dir = path;
+ if (!run_command(&cp) && !strbuf_read(&buf, cp.out, 1024))
+ is_present = 1;
+
+ close(cp.out);
+ strbuf_release(&buf);
+ }
+ return is_present;
+}
+
+static void submodule_collect_changed_cb(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!S_ISGITLINK(p->two->mode))
+ continue;
+
+ if (S_ISGITLINK(p->one->mode)) {
+ /* NEEDSWORK: We should honor the name configured in
+ * the .gitmodules file of the commit we are examining
+ * here to be able to correctly follow submodules
+ * being moved around. */
+ struct string_list_item *path;
+ path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path);
+ if (!path && !is_submodule_commit_present(p->two->path, p->two->sha1))
+ string_list_append(&changed_submodule_paths, xstrdup(p->two->path));
+ } else {
+ /* Submodule is new or was moved here */
+ /* NEEDSWORK: When the .git directories of submodules
+ * live inside the superprojects .git directory some
+ * day we should fetch new submodules directly into
+ * that location too when config or options request
+ * that so they can be checked out from there. */
+ continue;
+ }
+ }
+}
+
+void check_for_new_submodule_commits(unsigned char new_sha1[20])
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, NULL, "--not", "--all", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+
+ init_revisions(&rev, NULL);
+ argv[1] = xstrdup(sha1_to_hex(new_sha1));
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ /*
+ * Collect all submodules (whether checked out or not) for which new
+ * commits have been recorded upstream in "changed_submodule_paths".
+ */
+ while ((commit = get_revision(&rev))) {
+ struct commit_list *parent = commit->parents;
+ while (parent) {
+ struct diff_options diff_opts;
+ diff_setup(&diff_opts);
+ diff_opts.output_format |= DIFF_FORMAT_CALLBACK;
+ diff_opts.format_callback = submodule_collect_changed_cb;
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(parent->item->object.sha1, commit->object.sha1, "", &diff_opts);
+ diffcore_std(&diff_opts);
+ diff_flush(&diff_opts);
+ parent = parent->next;
+ }
+ }
+ free((char *)argv[1]);
+}
+
+int fetch_populated_submodules(int num_options, const char **options,
+ const char *prefix, int command_line_option,
+ int quiet)
+{
+ int i, result = 0, argc = 0, default_argc;
+ struct child_process cp;
+ const char **argv;
+ struct string_list_item *name_for_path;
+ const char *work_tree = get_git_work_tree();
+ if (!work_tree)
+ goto out;
+
+ if (!the_index.initialized)
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ /* 6: "fetch" (options) --recurse-submodules-default default "--submodule-prefix" prefix NULL */
+ argv = xcalloc(num_options + 6, sizeof(const char *));
+ argv[argc++] = "fetch";
+ for (i = 0; i < num_options; i++)
+ argv[argc++] = options[i];
+ argv[argc++] = "--recurse-submodules-default";
+ default_argc = argc++;
+ argv[argc++] = "--submodule-prefix";
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+
+ for (i = 0; i < active_nr; i++) {
+ struct strbuf submodule_path = STRBUF_INIT;
+ struct strbuf submodule_git_dir = STRBUF_INIT;
+ struct strbuf submodule_prefix = STRBUF_INIT;
+ struct cache_entry *ce = active_cache[i];
+ const char *git_dir, *name, *default_argv;
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ name = ce->name;
+ name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
+ if (name_for_path)
+ name = name_for_path->util;
+
+ default_argv = "yes";
+ if (command_line_option == RECURSE_SUBMODULES_DEFAULT) {
+ struct string_list_item *fetch_recurse_submodules_option;
+ fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
+ if (fetch_recurse_submodules_option) {
+ if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF)
+ continue;
+ if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+ } else {
+ if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF)
+ continue;
+ if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+ }
+ } else if (command_line_option == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+
+ strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
+ strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
+ strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
+ git_dir = read_gitfile_gently(submodule_git_dir.buf);
+ if (!git_dir)
+ git_dir = submodule_git_dir.buf;
+ if (is_directory(git_dir)) {
+ if (!quiet)
+ printf("Fetching submodule %s%s\n", prefix, ce->name);
+ cp.dir = submodule_path.buf;
+ argv[default_argc] = default_argv;
+ argv[argc] = submodule_prefix.buf;
+ if (run_command(&cp))
+ result = 1;
+ }
+ strbuf_release(&submodule_path);
+ strbuf_release(&submodule_git_dir);
+ strbuf_release(&submodule_prefix);
+ }
+ free(argv);
+out:
+ string_list_clear(&changed_submodule_paths, 1);
+ return result;
+}
+
unsigned is_submodule_modified(const char *path, int ignore_untracked)
{
ssize_t len;
strbuf_release(&buf);
return dirty_submodule;
}
+
+static int find_first_merges(struct object_array *result, const char *path,
+ struct commit *a, struct commit *b)
+{
+ int i, j;
+ struct object_array merges;
+ struct commit *commit;
+ int contains_another;
+
+ char merged_revision[42];
+ const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+ "--all", merged_revision, NULL };
+ struct rev_info revs;
+ struct setup_revision_opt rev_opts;
+
+ memset(&merges, 0, sizeof(merges));
+ memset(result, 0, sizeof(struct object_array));
+ memset(&rev_opts, 0, sizeof(rev_opts));
+
+ /* get all revisions that merge commit a */
+ snprintf(merged_revision, sizeof(merged_revision), "^%s",
+ sha1_to_hex(a->object.sha1));
+ init_revisions(&revs, NULL);
+ rev_opts.submodule = path;
+ setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);
+
+ /* save all revisions from the above list that contain b */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct object *o = &(commit->object);
+ if (in_merge_bases(b, &commit, 1))
+ add_object_array(o, NULL, &merges);
+ }
+
+ /* Now we've got all merges that contain a and b. Prune all
+ * merges that contain another found merge and save them in
+ * result.
+ */
+ for (i = 0; i < merges.nr; i++) {
+ struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+ contains_another = 0;
+ for (j = 0; j < merges.nr; j++) {
+ struct commit *m2 = (struct commit *) merges.objects[j].item;
+ if (i != j && in_merge_bases(m2, &m1, 1)) {
+ contains_another = 1;
+ break;
+ }
+ }
+
+ if (!contains_another)
+ add_object_array(merges.objects[i].item,
+ merges.objects[i].name, result);
+ }
+
+ free(merges.objects);
+ return result->nr;
+}
+
+static void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+ fprintf(stderr, "%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+#define MERGE_WARNING(path, msg) \
+ warning("Failed to merge submodule %s (%s)", path, msg);
+
+int merge_submodule(unsigned char result[20], const char *path,
+ const unsigned char base[20], const unsigned char a[20],
+ const unsigned char b[20])
+{
+ struct commit *commit_base, *commit_a, *commit_b;
+ int parent_count;
+ struct object_array merges;
+
+ int i;
+
+ /* store a in result in case we fail */
+ hashcpy(result, a);
+
+ /* we can not handle deletion conflicts */
+ if (is_null_sha1(base))
+ return 0;
+ if (is_null_sha1(a))
+ return 0;
+ if (is_null_sha1(b))
+ return 0;
+
+ if (add_submodule_odb(path)) {
+ MERGE_WARNING(path, "not checked out");
+ return 0;
+ }
+
+ if (!(commit_base = lookup_commit_reference(base)) ||
+ !(commit_a = lookup_commit_reference(a)) ||
+ !(commit_b = lookup_commit_reference(b))) {
+ MERGE_WARNING(path, "commits not present");
+ return 0;
+ }
+
+ /* check whether both changes are forward */
+ if (!in_merge_bases(commit_base, &commit_a, 1) ||
+ !in_merge_bases(commit_base, &commit_b, 1)) {
+ MERGE_WARNING(path, "commits don't follow merge-base");
+ return 0;
+ }
+
+ /* Case #1: a is contained in b or vice versa */
+ if (in_merge_bases(commit_a, &commit_b, 1)) {
+ hashcpy(result, b);
+ return 1;
+ }
+ if (in_merge_bases(commit_b, &commit_a, 1)) {
+ hashcpy(result, a);
+ return 1;
+ }
+
+ /*
+ * Case #2: There are one or more merges that contain a and b in
+ * the submodule. If there is only one, then present it as a
+ * suggestion to the user, but leave it marked unmerged so the
+ * user needs to confirm the resolution.
+ */
+
+ /* find commit which merges them */
+ parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+ switch (parent_count) {
+ case 0:
+ MERGE_WARNING(path, "merge following commits not found");
+ break;
+
+ case 1:
+ MERGE_WARNING(path, "not fast-forward");
+ fprintf(stderr, "Found a possible merge resolution "
+ "for the submodule:\n");
+ print_commit((struct commit *) merges.objects[0].item);
+ fprintf(stderr,
+ "If this is correct simply add it to the index "
+ "for example\n"
+ "by using:\n\n"
+ " git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+ "which will accept this suggestion.\n",
+ sha1_to_hex(merges.objects[0].item->sha1), path);
+ break;
+
+ default:
+ MERGE_WARNING(path, "multiple merges found");
+ for (i = 0; i < merges.nr; i++)
+ print_commit((struct commit *) merges.objects[i].item);
+ }
+
+ free(merges.objects);
+ return 0;
+}