aboutsummaryrefslogtreecommitdiff
path: root/submodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'submodule.c')
-rw-r--r--submodule.c326
1 files changed, 306 insertions, 20 deletions
diff --git a/submodule.c b/submodule.c
index 5d286e409..91a475874 100644
--- a/submodule.c
+++ b/submodule.c
@@ -5,14 +5,27 @@
#include "commit.h"
#include "revision.h"
#include "run-command.h"
+#include "diffcore.h"
+#include "refs.h"
+#include "string-list.h"
+
+struct string_list config_name_for_path;
+struct string_list config_ignore_for_name;
static int add_submodule_odb(const char *path)
{
struct strbuf objects_directory = STRBUF_INIT;
struct alternate_object_database *alt_odb;
int ret = 0;
+ const char *git_dir;
- strbuf_addf(&objects_directory, "%s/.git/objects/", path);
+ strbuf_addf(&objects_directory, "%s/.git", path);
+ git_dir = read_gitfile_gently(objects_directory.buf);
+ if (git_dir) {
+ strbuf_reset(&objects_directory);
+ strbuf_addstr(&objects_directory, git_dir);
+ }
+ strbuf_addstr(&objects_directory, "/objects/");
if (!is_directory(objects_directory.buf)) {
ret = -1;
goto done;
@@ -38,6 +51,93 @@ done:
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);
+ }
+}
+
+static int submodule_config(const char *var, const char *value, void *cb)
+{
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+ 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 > 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);
+}
+
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
@@ -85,13 +185,21 @@ void show_submodule_summary(FILE *f, const char *path,
message = "(revision walker failed)";
}
+ if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
+ fprintf(f, "Submodule %s contains untracked content\n", path);
+ if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
+ fprintf(f, "Submodule %s contains modified content\n", path);
+
+ if (!hashcmp(one, two)) {
+ strbuf_release(&sb);
+ return;
+ }
+
strbuf_addf(&sb, "Submodule %s %s..", path,
find_unique_abbrev(one, DEFAULT_ABBREV));
if (!fast_backward && !fast_forward)
strbuf_addch(&sb, '.');
strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV));
- if (dirty_submodule)
- strbuf_add(&sb, "-dirty", 6);
if (message)
strbuf_addf(&sb, " %s\n", message);
else
@@ -121,23 +229,26 @@ void show_submodule_summary(FILE *f, const char *path,
strbuf_release(&sb);
}
-int is_submodule_modified(const char *path)
+unsigned is_submodule_modified(const char *path, int ignore_untracked)
{
- int len, i;
+ ssize_t len;
struct child_process cp;
const char *argv[] = {
"status",
"--porcelain",
NULL,
+ NULL,
};
- const char *env[LOCAL_REPO_ENV_SIZE + 3];
struct strbuf buf = STRBUF_INIT;
+ unsigned dirty_submodule = 0;
+ const char *line, *next_line;
+ const char *git_dir;
- for (i = 0; i < LOCAL_REPO_ENV_SIZE; i++)
- env[i] = local_repo_env[i];
-
- strbuf_addf(&buf, "%s/.git/", path);
- if (!is_directory(buf.buf)) {
+ strbuf_addf(&buf, "%s/.git", path);
+ git_dir = read_gitfile_gently(buf.buf);
+ if (!git_dir)
+ git_dir = buf.buf;
+ if (!is_directory(git_dir)) {
strbuf_release(&buf);
/* The submodule is not checked out, so it is not modified */
return 0;
@@ -145,29 +256,204 @@ int is_submodule_modified(const char *path)
}
strbuf_reset(&buf);
- strbuf_addf(&buf, "GIT_WORK_TREE=%s", path);
- env[i++] = strbuf_detach(&buf, NULL);
- strbuf_addf(&buf, "GIT_DIR=%s/.git", path);
- env[i++] = strbuf_detach(&buf, NULL);
- env[i] = NULL;
+ if (ignore_untracked)
+ argv[2] = "-uno";
memset(&cp, 0, sizeof(cp));
cp.argv = argv;
- cp.env = env;
+ cp.env = local_repo_env;
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");
len = strbuf_read(&buf, cp.out, 1024);
+ line = buf.buf;
+ while (len > 2) {
+ if ((line[0] == '?') && (line[1] == '?')) {
+ dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED;
+ if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
+ break;
+ } else {
+ dirty_submodule |= DIRTY_SUBMODULE_MODIFIED;
+ if (ignore_untracked ||
+ (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED))
+ break;
+ }
+ next_line = strchr(line, '\n');
+ if (!next_line)
+ break;
+ next_line++;
+ len -= (next_line - line);
+ line = next_line;
+ }
close(cp.out);
if (finish_command(&cp))
die("git status --porcelain failed");
- for (i = LOCAL_REPO_ENV_SIZE; env[i]; i++)
- free((char *)env[i]);
strbuf_release(&buf);
- return len != 0;
+ 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;
}