diff options
Diffstat (limited to 'submodule.c')
-rw-r--r-- | submodule.c | 699 |
1 files changed, 567 insertions, 132 deletions
diff --git a/submodule.c b/submodule.c index 9d8d91c207..3200b7bb2b 100644 --- a/submodule.c +++ b/submodule.c @@ -14,6 +14,7 @@ #include "blob.h" #include "thread-utils.h" #include "quote.h" +#include "worktree.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static int parallel_jobs = 1; @@ -123,35 +124,16 @@ void stage_updated_gitmodules(void) static int add_submodule_odb(const char *path) { struct strbuf objects_directory = STRBUF_INIT; - struct alternate_object_database *alt_odb; int ret = 0; - size_t alloc; - strbuf_git_path_submodule(&objects_directory, path, "objects/"); + ret = strbuf_git_path_submodule(&objects_directory, path, "objects/"); + if (ret) + goto done; if (!is_directory(objects_directory.buf)) { ret = -1; goto done; } - /* avoid adding it twice */ - prepare_alt_odb(); - for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next) - if (alt_odb->name - alt_odb->base == objects_directory.len && - !strncmp(alt_odb->base, objects_directory.buf, - objects_directory.len)) - goto done; - - alloc = st_add(objects_directory.len, 42); /* for "12/345..." sha1 */ - alt_odb = xmalloc(st_add(sizeof(*alt_odb), alloc)); - alt_odb->next = alt_odb_list; - xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf); - alt_odb->name = alt_odb->base + objects_directory.len; - alt_odb->name[2] = '/'; - alt_odb->name[40] = '\0'; - alt_odb->name[41] = '\0'; - alt_odb_list = alt_odb; - - /* add possible alternates from the submodule */ - read_info_alternates(objects_directory.buf, 0); + add_to_alternates_memory(objects_directory.buf); done: strbuf_release(&objects_directory); return ret; @@ -217,6 +199,56 @@ void gitmodules_config(void) } } +void gitmodules_config_sha1(const unsigned char *commit_sha1) +{ + struct strbuf rev = STRBUF_INIT; + unsigned char sha1[20]; + + if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) { + git_config_from_blob_sha1(submodule_config, rev.buf, + sha1, NULL); + } + strbuf_release(&rev); +} + +/* + * Determine if a submodule has been initialized at a given 'path' + */ +int is_submodule_initialized(const char *path) +{ + int ret = 0; + const struct submodule *module = NULL; + + module = submodule_from_path(null_sha1, path); + + if (module) { + char *key = xstrfmt("submodule.%s.url", module->name); + char *value = NULL; + + ret = !git_config_get_string(key, &value); + + free(value); + free(key); + } + + return ret; +} + +/* + * Determine if a submodule has been populated at a given 'path' + */ +int is_submodule_populated(const char *path) +{ + int ret = 0; + char *gitdir = xstrfmt("%s/.git", path); + + if (resolve_gitdir(gitdir)) + ret = 1; + + free(gitdir); + return ret; +} + int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst) { @@ -278,9 +310,9 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, 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) { - struct commit_list *merge_bases, *list; + struct commit_list *list; init_revisions(rev, NULL); setup_revisions(0, NULL, rev, NULL); @@ -289,13 +321,6 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, 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); - 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, @@ -333,31 +358,23 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } -void show_submodule_summary(FILE *f, const char *path, +/* Helper function to display the submodule header line prior to the full + * summary output. If it can locate the submodule objects directory it will + * attempt to lookup both the left and right commits and put them into the + * left and right pointers. + */ +static void show_submodule_header(FILE *f, const char *path, const char *line_prefix, - unsigned char one[20], unsigned char two[20], + struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset) + const char *reset, + struct commit **left, struct commit **right, + struct commit_list **merge_bases) { - struct rev_info rev; - struct commit *left = NULL, *right = NULL; const char *message = NULL; struct strbuf sb = STRBUF_INIT; int fast_forward = 0, fast_backward = 0; - if (is_null_sha1(two)) - message = "(submodule deleted)"; - else if (add_submodule_odb(path)) - message = "(not checked out)"; - else if (is_null_sha1(one)) - message = "(new submodule)"; - else if (!(left = lookup_commit_reference(one)) || - !(right = lookup_commit_reference(two))) - message = "(commits not present)"; - else if (prepare_submodule_summary(&rev, path, left, right, - &fast_forward, &fast_backward)) - message = "(revision walker failed)"; - if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) fprintf(f, "%sSubmodule %s contains untracked content\n", line_prefix, path); @@ -365,29 +382,162 @@ void show_submodule_summary(FILE *f, const char *path, fprintf(f, "%sSubmodule %s contains modified content\n", line_prefix, path); - if (!hashcmp(one, two)) { + if (is_null_oid(one)) + message = "(new submodule)"; + else if (is_null_oid(two)) + message = "(submodule deleted)"; + + if (add_submodule_odb(path)) { + if (!message) + message = "(not initialized)"; + goto output_header; + } + + /* + * Attempt to lookup the commit references, and determine if this is + * a fast forward or fast backwards update. + */ + *left = lookup_commit_reference(one->hash); + *right = lookup_commit_reference(two->hash); + + /* + * Warn about missing commits in the submodule project, but only if + * they aren't null. + */ + if ((!is_null_oid(one) && !*left) || + (!is_null_oid(two) && !*right)) + message = "(commits not present)"; + + *merge_bases = get_merge_bases(*left, *right); + if (*merge_bases) { + if ((*merge_bases)->item == *left) + fast_forward = 1; + else if ((*merge_bases)->item == *right) + fast_backward = 1; + } + + if (!oidcmp(one, two)) { strbuf_release(&sb); return; } +output_header: strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path); - strbuf_add_unique_abbrev(&sb, one, DEFAULT_ABBREV); + strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV); strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "..."); - strbuf_add_unique_abbrev(&sb, two, DEFAULT_ABBREV); + strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV); if (message) strbuf_addf(&sb, " %s%s\n", message, reset); else strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); fwrite(sb.buf, sb.len, 1, f); - if (!message) /* only NULL if we succeeded in setting up the walk */ - print_submodule_summary(&rev, f, line_prefix, del, add, reset); + strbuf_release(&sb); +} + +void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, + struct object_id *one, struct object_id *two, + unsigned dirty_submodule, const char *meta, + const char *del, const char *add, const char *reset) +{ + struct rev_info rev; + struct commit *left = NULL, *right = NULL; + struct commit_list *merge_bases = NULL; + + show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, + meta, reset, &left, &right, &merge_bases); + + /* + * If we don't have both a left and a right pointer, there is no + * reason to try and display a summary. The header line should contain + * all the information the user needs. + */ + if (!left || !right) + goto out; + + /* Treat revision walker failure the same as missing commits */ + if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) { + fprintf(f, "%s(revision walker failed)\n", line_prefix); + goto out; + } + + print_submodule_summary(&rev, f, line_prefix, del, add, reset); + +out: + if (merge_bases) + free_commit_list(merge_bases); + clear_commit_marks(left, ~0); + clear_commit_marks(right, ~0); +} + +void show_submodule_inline_diff(FILE *f, const char *path, + const char *line_prefix, + struct object_id *one, struct object_id *two, + unsigned dirty_submodule, const char *meta, + const char *del, const char *add, const char *reset, + const struct diff_options *o) +{ + const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid; + struct commit *left = NULL, *right = NULL; + struct commit_list *merge_bases = NULL; + struct strbuf submodule_dir = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, + meta, reset, &left, &right, &merge_bases); + + /* We need a valid left and right commit to display a difference */ + if (!(left || is_null_oid(one)) || + !(right || is_null_oid(two))) + goto done; + + if (left) + old = one; + if (right) + new = two; + + fflush(f); + cp.git_cmd = 1; + cp.dir = path; + cp.out = dup(fileno(f)); + cp.no_stdin = 1; + + /* TODO: other options may need to be passed here. */ + argv_array_push(&cp.args, "diff"); + argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix); + if (DIFF_OPT_TST(o, REVERSE_DIFF)) { + argv_array_pushf(&cp.args, "--src-prefix=%s%s/", + o->b_prefix, path); + argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", + o->a_prefix, path); + } else { + argv_array_pushf(&cp.args, "--src-prefix=%s%s/", + o->a_prefix, path); + argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", + o->b_prefix, path); + } + argv_array_push(&cp.args, oid_to_hex(old)); + /* + * If the submodule has modified content, we will diff against the + * work tree, under the assumption that the user has asked for the + * diff format and wishes to actually see all differences even if they + * haven't yet been committed to the submodule yet. + */ + if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED)) + argv_array_push(&cp.args, oid_to_hex(new)); + + if (run_command(&cp)) + fprintf(f, "(diff failed)\n"); + +done: + strbuf_release(&submodule_dir); + if (merge_bases) + free_commit_list(merge_bases); if (left) clear_commit_marks(left, ~0); if (right) clear_commit_marks(right, ~0); - - strbuf_release(&sb); } void set_config_fetch_recurse_submodules(int value) @@ -401,27 +551,67 @@ static int has_remote(const char *refname, const struct object_id *oid, return 1; } -static int submodule_needs_pushing(const char *path, const unsigned char sha1[20]) +static int append_sha1_to_argv(const unsigned char sha1[20], void *data) +{ + struct argv_array *argv = data; + argv_array_push(argv, sha1_to_hex(sha1)); + return 0; +} + +static int check_has_commit(const unsigned char sha1[20], void *data) +{ + int *has_commit = data; + + if (!lookup_commit_reference(sha1)) + *has_commit = 0; + + return 0; +} + +static int submodule_has_commits(const char *path, struct sha1_array *commits) { - if (add_submodule_odb(path) || !lookup_commit_reference(sha1)) + int has_commit = 1; + + if (add_submodule_odb(path)) + return 0; + + sha1_array_for_each_unique(commits, check_has_commit, &has_commit); + return has_commit; +} + +static int submodule_needs_pushing(const char *path, struct sha1_array *commits) +{ + if (!submodule_has_commits(path, commits)) + /* + * NOTE: We do consider it safe to return "no" here. The + * correct answer would be "We do not know" instead of + * "No push needed", but it is quite hard to change + * the submodule pointer without having the submodule + * around. If a user did however change the submodules + * without having the submodule around, this indicates + * an expert who knows what they are doing or a + * maintainer integrating work from other people. In + * both cases it should be safe to skip this check. + */ return 0; if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL}; struct strbuf buf = STRBUF_INIT; int needs_pushing = 0; - argv[1] = sha1_to_hex(sha1); - cp.argv = argv; + argv_array_push(&cp.args, "rev-list"); + sha1_array_for_each_unique(commits, append_sha1_to_argv, &cp.args); + argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; if (start_command(&cp)) - die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s", - sha1_to_hex(sha1), path); + die("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s", + path); if (strbuf_read(&buf, cp.out, 41)) needs_pushing = 1; finish_command(&cp); @@ -433,19 +623,34 @@ static int submodule_needs_pushing(const char *path, const unsigned char sha1[20 return 0; } +static struct sha1_array *submodule_commits(struct string_list *submodules, + const char *path) +{ + struct string_list_item *item; + + item = string_list_insert(submodules, path); + if (item->util) + return (struct sha1_array *) item->util; + + /* NEEDSWORK: should we have sha1_array_init()? */ + item->util = xcalloc(1, sizeof(struct sha1_array)); + return (struct sha1_array *) item->util; +} + static void collect_submodules_from_diff(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; - struct string_list *needs_pushing = data; + struct string_list *submodules = data; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; + struct sha1_array *commits; if (!S_ISGITLINK(p->two->mode)) continue; - if (submodule_needs_pushing(p->two->path, p->two->oid.hash)) - string_list_insert(needs_pushing, p->two->path); + commits = submodule_commits(submodules, p->two->path); + sha1_array_append(commits, p->two->oid.hash); } } @@ -461,46 +666,63 @@ static void find_unpushed_submodule_commits(struct commit *commit, diff_tree_combined_merge(commit, 1, &rev); } -int find_unpushed_submodules(unsigned char new_sha1[20], +static void free_submodules_sha1s(struct string_list *submodules) +{ + struct string_list_item *item; + for_each_string_list_item(item, submodules) + sha1_array_clear((struct sha1_array *) item->util); + string_list_clear(submodules, 1); +} + +int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, struct string_list *needs_pushing) { struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, NULL, "--not", "NULL", NULL}; - int argc = ARRAY_SIZE(argv) - 1; - char *sha1_copy; - - struct strbuf remotes_arg = STRBUF_INIT; + struct string_list submodules = STRING_LIST_INIT_DUP; + struct string_list_item *submodule; + struct argv_array argv = ARGV_ARRAY_INIT; - strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name); init_revisions(&rev, NULL); - sha1_copy = xstrdup(sha1_to_hex(new_sha1)); - argv[1] = sha1_copy; - argv[3] = remotes_arg.buf; - setup_revisions(argc, argv, &rev, NULL); + + /* argv.argv[0] will be ignored by setup_revisions */ + argv_array_push(&argv, "find_unpushed_submodules"); + sha1_array_for_each_unique(commits, append_sha1_to_argv, &argv); + argv_array_push(&argv, "--not"); + argv_array_pushf(&argv, "--remotes=%s", remotes_name); + + setup_revisions(argv.argc, argv.argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); while ((commit = get_revision(&rev)) != NULL) - find_unpushed_submodule_commits(commit, needs_pushing); + find_unpushed_submodule_commits(commit, &submodules); reset_revision_walk(); - free(sha1_copy); - strbuf_release(&remotes_arg); + argv_array_clear(&argv); + + for_each_string_list_item(submodule, &submodules) { + struct sha1_array *commits = (struct sha1_array *) submodule->util; + + if (submodule_needs_pushing(submodule->string, commits)) + string_list_insert(needs_pushing, submodule->string); + } + free_submodules_sha1s(&submodules); return needs_pushing->nr; } -static int push_submodule(const char *path) +static int push_submodule(const char *path, int dry_run) { if (add_submodule_odb(path)) return 1; if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = {"push", NULL}; + argv_array_push(&cp.args, "push"); + if (dry_run) + argv_array_push(&cp.args, "--dry-run"); - cp.argv = argv; prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -513,18 +735,20 @@ static int push_submodule(const char *path) return 1; } -int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name) +int push_unpushed_submodules(struct sha1_array *commits, + const char *remotes_name, + int dry_run) { int i, ret = 1; struct string_list needs_pushing = STRING_LIST_INIT_DUP; - if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing)) + if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing)) return 1; for (i = 0; i < needs_pushing.nr; i++) { const char *path = needs_pushing.items[i].string; fprintf(stderr, "Pushing submodule '%s'\n", path); - if (!push_submodule(path)) { + if (!push_submodule(path, dry_run)) { fprintf(stderr, "Unable to push submodule '%s'\n", path); ret = 0; } @@ -607,9 +831,10 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]) sha1_array_append(&ref_tips_after_fetch, new_sha1); } -static void add_sha1_to_argv(const unsigned char sha1[20], void *data) +static int add_sha1_to_argv(const unsigned char sha1[20], void *data) { argv_array_push(data, sha1_to_hex(sha1)); + return 0; } static void calculate_changed_submodule_paths(void) @@ -918,45 +1143,64 @@ int submodule_uses_gitfile(const char *path) return 1; } -int ok_to_remove_submodule(const char *path) +/* + * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data + * when doing so. + * + * Return 1 if we'd lose data, return 0 if the removal is fine, + * and negative values for errors. + */ +int bad_to_remove_submodule(const char *path, unsigned flags) { ssize_t len; struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = { - "status", - "--porcelain", - "-u", - "--ignore-submodules=none", - NULL, - }; struct strbuf buf = STRBUF_INIT; - int ok_to_remove = 1; + int ret = 0; if (!file_exists(path) || is_empty_dir(path)) - return 1; + return 0; if (!submodule_uses_gitfile(path)) - return 0; + return 1; + + argv_array_pushl(&cp.args, "status", "--porcelain", + "--ignore-submodules=none", NULL); + + if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED) + argv_array_push(&cp.args, "-uno"); + else + argv_array_push(&cp.args, "-uall"); + + if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)) + argv_array_push(&cp.args, "--ignored"); - cp.argv = argv; prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; - if (start_command(&cp)) - die("Could not run 'git status --porcelain -uall --ignore-submodules=none' in submodule %s", path); + if (start_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not start 'git status in submodule '%s'"), + path); + ret = -1; + goto out; + } len = strbuf_read(&buf, cp.out, 1024); if (len > 2) - ok_to_remove = 0; + ret = 1; close(cp.out); - if (finish_command(&cp)) - die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path); - + if (finish_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not run 'git status in submodule '%s'"), + path); + ret = -1; + } +out: strbuf_release(&buf); - return ok_to_remove; + return ret; } static int find_first_merges(struct object_array *result, const char *path, @@ -1122,30 +1366,6 @@ int merge_submodule(unsigned char result[20], const char *path, return 0; } -/* Update gitfile and core.worktree setting to connect work tree and git dir */ -void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) -{ - struct strbuf file_name = STRBUF_INIT; - struct strbuf rel_path = STRBUF_INIT; - const char *real_work_tree = xstrdup(real_path(work_tree)); - - /* Update gitfile */ - strbuf_addf(&file_name, "%s/.git", work_tree); - write_file(file_name.buf, "gitdir: %s", - relative_path(git_dir, real_work_tree, &rel_path)); - - /* Update core.worktree setting */ - strbuf_reset(&file_name); - strbuf_addf(&file_name, "%s/config", git_dir); - git_config_set_in_file(file_name.buf, "core.worktree", - relative_path(real_work_tree, git_dir, - &rel_path)); - - strbuf_release(&file_name); - strbuf_release(&rel_path); - free((void *)real_work_tree); -} - int parallel_submodules(void) { return parallel_jobs; @@ -1159,5 +1379,220 @@ void prepare_submodule_repo_env(struct argv_array *out) if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) argv_array_push(out, *var); } - argv_array_push(out, "GIT_DIR=.git"); + argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT, + DEFAULT_GIT_DIR_ENVIRONMENT); +} + +/* + * Embeds a single submodules git directory into the superprojects git dir, + * non recursively. + */ +static void relocate_single_git_dir_into_superproject(const char *prefix, + const char *path) +{ + char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL; + const char *new_git_dir; + const struct submodule *sub; + + if (submodule_uses_worktrees(path)) + die(_("relocate_gitdir for submodule '%s' with " + "more than one worktree not supported"), path); + + old_git_dir = xstrfmt("%s/.git", path); + if (read_gitfile(old_git_dir)) + /* If it is an actual gitfile, it doesn't need migration. */ + return; + + real_old_git_dir = real_pathdup(old_git_dir, 1); + + sub = submodule_from_path(null_sha1, path); + if (!sub) + die(_("could not lookup name for submodule '%s'"), path); + + new_git_dir = git_path("modules/%s", sub->name); + if (safe_create_leading_directories_const(new_git_dir) < 0) + die(_("could not create directory '%s'"), new_git_dir); + real_new_git_dir = real_pathdup(new_git_dir, 1); + + if (!prefix) + prefix = get_super_prefix(); + + fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"), + prefix ? prefix : "", path, + real_old_git_dir, real_new_git_dir); + + relocate_gitdir(path, real_old_git_dir, real_new_git_dir); + + free(old_git_dir); + free(real_old_git_dir); + free(real_new_git_dir); +} + +/* + * Migrate the git directory of the submodule given by path from + * having its git directory within the working tree to the git dir nested + * in its superprojects git dir under modules/. + */ +void absorb_git_dir_into_superproject(const char *prefix, + const char *path, + unsigned flags) +{ + int err_code; + const char *sub_git_dir; + struct strbuf gitdir = STRBUF_INIT; + strbuf_addf(&gitdir, "%s/.git", path); + sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code); + + /* Not populated? */ + if (!sub_git_dir) { + char *real_new_git_dir; + const char *new_git_dir; + const struct submodule *sub; + + if (err_code == READ_GITFILE_ERR_STAT_FAILED) { + /* unpopulated as expected */ + strbuf_release(&gitdir); + return; + } + + if (err_code != READ_GITFILE_ERR_NOT_A_REPO) + /* We don't know what broke here. */ + read_gitfile_error_die(err_code, path, NULL); + + /* + * Maybe populated, but no git directory was found? + * This can happen if the superproject is a submodule + * itself and was just absorbed. The absorption of the + * superproject did not rewrite the git file links yet, + * fix it now. + */ + sub = submodule_from_path(null_sha1, path); + if (!sub) + die(_("could not lookup name for submodule '%s'"), path); + new_git_dir = git_path("modules/%s", sub->name); + if (safe_create_leading_directories_const(new_git_dir) < 0) + die(_("could not create directory '%s'"), new_git_dir); + real_new_git_dir = real_pathdup(new_git_dir, 1); + connect_work_tree_and_git_dir(path, real_new_git_dir); + + free(real_new_git_dir); + } else { + /* Is it already absorbed into the superprojects git dir? */ + char *real_sub_git_dir = real_pathdup(sub_git_dir, 1); + char *real_common_git_dir = real_pathdup(get_git_common_dir(), 1); + + if (!starts_with(real_sub_git_dir, real_common_git_dir)) + relocate_single_git_dir_into_superproject(prefix, path); + + free(real_sub_git_dir); + free(real_common_git_dir); + } + strbuf_release(&gitdir); + + if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + + if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES) + die("BUG: we don't know how to pass the flags down?"); + + if (get_super_prefix()) + strbuf_addstr(&sb, get_super_prefix()); + strbuf_addstr(&sb, path); + strbuf_addch(&sb, '/'); + + cp.dir = path; + cp.git_cmd = 1; + cp.no_stdin = 1; + argv_array_pushl(&cp.args, "--super-prefix", sb.buf, + "submodule--helper", + "absorb-git-dirs", NULL); + prepare_submodule_repo_env(&cp.env_array); + if (run_command(&cp)) + die(_("could not recurse into submodule '%s'"), path); + + strbuf_release(&sb); + } +} + +const char *get_superproject_working_tree(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + const char *one_up = real_path_if_valid("../"); + const char *cwd = xgetcwd(); + const char *ret = NULL; + const char *subpath; + int code; + ssize_t len; + + if (!is_inside_work_tree()) + /* + * FIXME: + * We might have a superproject, but it is harder + * to determine. + */ + return NULL; + + if (!one_up) + return NULL; + + subpath = relative_path(cwd, one_up, &sb); + + prepare_submodule_repo_env(&cp.env_array); + argv_array_pop(&cp.env_array); + + argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..", + "ls-files", "-z", "--stage", "--full-name", "--", + subpath, NULL); + strbuf_reset(&sb); + + cp.no_stdin = 1; + cp.no_stderr = 1; + cp.out = -1; + cp.git_cmd = 1; + + if (start_command(&cp)) + die(_("could not start ls-files in ..")); + + len = strbuf_read(&sb, cp.out, PATH_MAX); + close(cp.out); + + if (starts_with(sb.buf, "160000")) { + int super_sub_len; + int cwd_len = strlen(cwd); + char *super_sub, *super_wt; + + /* + * There is a superproject having this repo as a submodule. + * The format is <mode> SP <hash> SP <stage> TAB <full name> \0, + * We're only interested in the name after the tab. + */ + super_sub = strchr(sb.buf, '\t') + 1; + super_sub_len = sb.buf + sb.len - super_sub - 1; + + if (super_sub_len > cwd_len || + strcmp(&cwd[cwd_len - super_sub_len], super_sub)) + die (_("BUG: returned path string doesn't match cwd?")); + + super_wt = xstrdup(cwd); + super_wt[cwd_len - super_sub_len] = '\0'; + + ret = real_path(super_wt); + free(super_wt); + } + strbuf_release(&sb); + + code = finish_command(&cp); + + if (code == 128) + /* '../' is not a git repository */ + return NULL; + if (code == 0 && len == 0) + /* There is an unrelated git repository at '../' */ + return NULL; + if (code) + die(_("ls-tree returned unexpected return code %d"), code); + + return ret; } |