static void find_merge_parents(struct merge_parents *result, struct strbuf *in, unsigned char *head) { struct commit_list *parents, *next; struct commit *head_commit; int pos = 0, i, j; parents = NULL; while (pos < in->len) { int len; char *p = in->buf + pos; char *newline = strchr(p, '\n'); unsigned char sha1[20]; struct commit *parent; struct object *obj; len = newline ? newline - p : strlen(p); pos += len + !!newline; if (len < 43 || get_sha1_hex(p, sha1) || p[40] != '\t' || p[41] != '\t') continue; /* skip not-for-merge */ /* * Do not use get_merge_parent() here; we do not have * "name" here and we do not want to contaminate its * util field yet. */ obj = parse_object(sha1); parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT); if (!parent) continue; commit_list_insert(parent, &parents); add_merge_parent(result, obj->sha1, parent->object.sha1); } head_commit = lookup_commit(head); if (head_commit) commit_list_insert(head_commit, &parents); parents = reduce_heads(parents); while (parents) { for (i = 0; i < result->nr; i++) if (!hashcmp(result->item[i].commit, parents->item->object.sha1)) result->item[i].used = 1; next = parents->next; free(parents); parents = next; } for (i = j = 0; i < result->nr; i++) { if (result->item[i].used) { if (i != j) result->item[j] = result->item[i]; j++; } } result->nr = j; }
static int finish_automerge(struct commit_list *common, unsigned char *result_tree, const char *wt_strategy) { struct commit_list *parents = NULL, *j; struct strbuf buf = STRBUF_INIT; unsigned char result_commit[20]; free_commit_list(common); if (allow_fast_forward) { parents = remoteheads; commit_list_insert(lookup_commit(head), &parents); parents = reduce_heads(parents); } else { struct commit_list **pptr = &parents; pptr = &commit_list_insert(lookup_commit(head), pptr)->next; for (j = remoteheads; j; j = j->next) pptr = &commit_list_insert(j->item, pptr)->next; } free_commit_list(remoteheads); strbuf_addch(&merge_msg, '\n'); run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); strbuf_addf(&buf, "Merge made by %s.", wt_strategy); finish(result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; }
static struct commit_list *collect_parents(struct commit *head_commit, int *head_subsumed, int argc, const char **argv) { int i; struct commit_list *remoteheads = NULL, *parents, *next; struct commit_list **remotes = &remoteheads; if (head_commit) remotes = &commit_list_insert(head_commit, remotes)->next; for (i = 0; i < argc; i++) { struct commit *commit = get_merge_parent(argv[i]); if (!commit) die(_("%s - not something we can merge"), argv[i]); remotes = &commit_list_insert(commit, remotes)->next; } *remotes = NULL; parents = reduce_heads(remoteheads); *head_subsumed = 1; /* we will flip this to 0 when we find it */ for (remoteheads = NULL, remotes = &remoteheads; parents; parents = next) { struct commit *commit = parents->item; next = parents->next; if (commit == head_commit) *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; } return remoteheads; }
static struct commit_list *reduce_parents(struct commit *head_commit, int *head_subsumed, struct commit_list *remoteheads) { struct commit_list *parents, **remotes; /* * Is the current HEAD reachable from another commit being * merged? If so we do not want to record it as a parent of * the resulting merge, unless --no-ff is given. We will flip * this variable to 0 when we find HEAD among the independent * tips being merged. */ *head_subsumed = 1; /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); remoteheads = NULL; remotes = &remoteheads; while (parents) { struct commit *commit = pop_commit(&parents); if (commit == head_commit) *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; } return remoteheads; }
static int finish_automerge(struct commit *head, struct commit_list *common, unsigned char *result_tree, const char *wt_strategy) { struct commit_list *parents = NULL, *j; struct strbuf buf = STRBUF_INIT; unsigned char result_commit[20]; free_commit_list(common); if (allow_fast_forward) { parents = remoteheads; commit_list_insert(head, &parents); parents = reduce_heads(parents); } else { struct commit_list **pptr = &parents; pptr = &commit_list_insert(head, pptr)->next; for (j = remoteheads; j; j = j->next) pptr = &commit_list_insert(j->item, pptr)->next; } strbuf_addch(&merge_msg, '\n'); prepare_to_commit(); free_commit_list(remoteheads); if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL, sign_commit)) die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; }
static int handle_octopus(int count, const char **args, int reduce, int show_all) { struct commit_list *revs = NULL; struct commit_list *result; int i; if (reduce) show_all = 1; for (i = count - 1; i >= 0; i--) commit_list_insert(get_commit_reference(args[i]), &revs); result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs); if (!result) return 1; while (result) { printf("%s\n", sha1_to_hex(result->item->object.sha1)); if (!show_all) return 0; result = result->next; } return 0; }
static int handle_independent(int count, const char **args) { struct commit_list *revs = NULL; struct commit_list *result; int i; for (i = count - 1; i >= 0; i--) commit_list_insert(get_commit_reference(args[i]), &revs); result = reduce_heads(revs); if (!result) return 1; while (result) { printf("%s\n", oid_to_hex(&result->item->object.oid)); result = result->next; } return 0; }
/** * Sets merge_base to the octopus merge base of curr_head, merge_head and * fork_point. Returns 0 if a merge base is found, 1 otherwise. */ static int get_octopus_merge_base(unsigned char *merge_base, const unsigned char *curr_head, const unsigned char *merge_head, const unsigned char *fork_point) { struct commit_list *revs = NULL, *result; commit_list_insert(lookup_commit_reference(curr_head), &revs); commit_list_insert(lookup_commit_reference(merge_head), &revs); if (!is_null_sha1(fork_point)) commit_list_insert(lookup_commit_reference(fork_point), &revs); result = reduce_heads(get_octopus_merge_bases(revs)); free_commit_list(revs); if (!result) return 1; hashcpy(merge_base, result->item->object.oid.hash); return 0; }
/** * Sets merge_base to the octopus merge base of curr_head, merge_head and * fork_point. Returns 0 if a merge base is found, 1 otherwise. */ static int get_octopus_merge_base(struct object_id *merge_base, const struct object_id *curr_head, const struct object_id *merge_head, const struct object_id *fork_point) { struct commit_list *revs = NULL, *result; commit_list_insert(lookup_commit_reference(curr_head), &revs); commit_list_insert(lookup_commit_reference(merge_head), &revs); if (!is_null_oid(fork_point)) commit_list_insert(lookup_commit_reference(fork_point), &revs); result = reduce_heads(get_octopus_merge_bases(revs)); free_commit_list(revs); if (!result) return 1; oidcpy(merge_base, &result->item->object.oid); return 0; }
int cmd_commit(int argc, const char **argv, const char *prefix) { static struct wt_status s; static struct option builtin_commit_options[] = { OPT__QUIET(&quiet, N_("suppress summary after successful commit")), OPT__VERBOSE(&verbose, N_("show diff in commit message template")), OPT_GROUP(N_("Commit message options")), OPT_FILENAME('F', "file", &logfile, N_("read message from file")), OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, /* end commit message options */ OPT_GROUP(N_("Commit contents options")), OPT_BOOL('a', "all", &all, N_("commit all changed files")), OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")), OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), OPT_BOOL('o', "only", &only, N_("commit only specified files")), OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), OPT_BOOL('z', "null", &s.null_termination, N_("terminate entries with NUL")), OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, N_("ok to record an empty change")), OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message, N_("ok to record a change with an empty message")), OPT_END() }; struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl; unsigned char sha1[20]; struct ref_lock *ref_lock; struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); status_init_config(&s, git_commit_config); status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ s.colopts = 0; if (get_sha1("HEAD", sha1)) current_head = NULL; else { current_head = lookup_commit_or_die(sha1, "HEAD"); if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); if (dry_run) return dry_run_commit(argc, argv, prefix, current_head, &s); index_file = prepare_index(argc, argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ if (!prepare_to_commit(index_file, prefix, current_head, &s, &author_ident)) { rollback_index_files(); return 1; } /* Determine parents */ reflog_msg = getenv("GIT_REFLOG_ACTION"); if (!current_head) { if (!reflog_msg) reflog_msg = "commit (initial)"; } else if (amend) { struct commit_list *c; if (!reflog_msg) reflog_msg = "commit (amend)"; for (c = current_head->parents; c; c = c->next) pptr = &commit_list_insert(c->item, pptr)->next; } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; int allow_fast_forward = 1; if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = &commit_list_insert(current_head, pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { struct commit *parent; parent = get_merge_parent(m.buf); if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); pptr = &commit_list_insert(parent, pptr)->next; } fclose(fp); strbuf_release(&m); if (!stat(git_path("MERGE_MODE"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; } if (allow_fast_forward) parents = reduce_heads(parents); } else { if (!reflog_msg) reflog_msg = (whence == FROM_CHERRY_PICK) ? "commit (cherry-pick)" : "commit"; pptr = &commit_list_insert(current_head, pptr)->next; } /* Finally, get the commit message */ strbuf_reset(&sb); if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { int saved_errno = errno; rollback_index_files(); die(_("could not read commit message: %s"), strerror(saved_errno)); } /* Truncate the message just before the diff, if any. */ if (verbose) wt_status_truncate_message_at_cut_line(&sb); if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (template_untouched(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); exit(1); } if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, _("Aborting commit due to empty commit message.\n")); exit(1); } if (amend) { const char *exclude_gpgsig[2] = { "gpgsig", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else { struct commit_extra_header **tail = &extra; append_merge_tag_headers(parents, &tail); } if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, author_ident.buf, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", !current_head ? NULL : current_head->object.sha1, 0, NULL); nl = strchr(sb.buf, '\n'); if (nl) strbuf_setlen(&sb, nl + 1 - sb.buf); else strbuf_addch(&sb, '\n'); strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); if (!ref_lock) { rollback_index_files(); die(_("cannot lock HEAD ref")); } if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { rollback_index_files(); die(_("cannot update HEAD ref")); } unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("REVERT_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); unlink(git_path("SQUASH_MSG")); if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { struct notes_rewrite_cfg *cfg; cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { /* we are amending, so current_head is not NULL */ copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(current_head->object.sha1, sha1); } if (!quiet) print_summary(prefix, sha1, !current_head); return 0; }
int cmd_commit(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl, *p; unsigned char commit_sha1[20]; struct ref_lock *ref_lock; struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; int allow_fast_forward = 1; struct wt_status s; wt_status_prepare(&s); git_config(git_commit_config, &s); in_merge = file_exists(git_path("MERGE_HEAD")); s.in_merge = in_merge; if (s.use_color == -1) s.use_color = git_use_color_default; argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix, &s); if (dry_run) { if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; return dry_run_commit(argc, argv, prefix, &s); } index_file = prepare_index(argc, argv, prefix, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ if (!prepare_to_commit(index_file, prefix, &s)) { rollback_index_files(); return 1; } /* Determine parents */ reflog_msg = getenv("GIT_REFLOG_ACTION"); if (initial_commit) { if (!reflog_msg) reflog_msg = "commit (initial)"; } else if (amend) { struct commit_list *c; struct commit *commit; if (!reflog_msg) reflog_msg = "commit (amend)"; commit = lookup_commit(head_sha1); if (!commit || parse_commit(commit)) die("could not parse HEAD commit"); for (c = commit->parents; c; c = c->next) pptr = &commit_list_insert(c->item, pptr)->next; } else if (in_merge) { struct strbuf m = STRBUF_INIT; FILE *fp; if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) die_errno("could not open '%s' for reading", git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { unsigned char sha1[20]; if (get_sha1_hex(m.buf, sha1) < 0) die("Corrupt MERGE_HEAD file (%s)", m.buf); pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; } fclose(fp); strbuf_release(&m); if (!stat(git_path("MERGE_MODE"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) die_errno("could not read MERGE_MODE"); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; } if (allow_fast_forward) parents = reduce_heads(parents); } else { if (!reflog_msg) reflog_msg = "commit"; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; } /* Finally, get the commit message */ strbuf_reset(&sb); if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { int saved_errno = errno; rollback_index_files(); die("could not read commit message: %s", strerror(saved_errno)); } /* Truncate the message just before the diff, if any. */ if (verbose) { p = strstr(sb.buf, "\ndiff --git "); if (p != NULL) strbuf_setlen(&sb, p - sb.buf + 1); } if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, "Aborting commit due to empty commit message.\n"); exit(1); } if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, fmt_ident(author_name, author_email, author_date, IDENT_ERROR_ON_NO_NAME))) { rollback_index_files(); die("failed to write commit object"); } ref_lock = lock_any_ref_for_update("HEAD", initial_commit ? NULL : head_sha1, 0); nl = strchr(sb.buf, '\n'); if (nl) strbuf_setlen(&sb, nl + 1 - sb.buf); else strbuf_addch(&sb, '\n'); strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); if (!ref_lock) { rollback_index_files(); die("cannot lock HEAD ref"); } if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { rollback_index_files(); die("cannot update HEAD ref"); } unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); unlink(git_path("SQUASH_MSG")); if (commit_index_files()) die ("Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover."); rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { struct notes_rewrite_cfg *cfg; cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { copy_note_for_rewrite(cfg, head_sha1, commit_sha1); finish_copy_notes_for_rewrite(cfg); } run_rewrite_hook(head_sha1, commit_sha1); } if (!quiet) print_summary(prefix, commit_sha1); return 0; }