* [PATCH 14/14] Build in merge
From: Miklos Vajna @ 2008-07-01 2:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <cover.1214879690.git.vmiklos@frugalware.org>
Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
Makefile | 2 +-
builtin-merge.c | 1148 +++++++++++++++++++++++++
builtin.h | 1 +
git-merge.sh => contrib/examples/git-merge.sh | 0
git.c | 1 +
5 files changed, 1151 insertions(+), 1 deletions(-)
create mode 100644 builtin-merge.c
rename git-merge.sh => contrib/examples/git-merge.sh (100%)
diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-merge-stupid.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..bb8d985
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+ DEFAULT_TWOHEAD = 1,
+ DEFAULT_OCTOPUS = 2,
+ NO_FAST_FORWARD = 4,
+ NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git-merge [options] <remote>...",
+ "git-merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+ { "recur", (void *)NO_TRIVIAL },
+ { "recursive", (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+ { "octopus", (void *)DEFAULT_OCTOPUS },
+ { "resolve", (void *)0 },
+ { "stupid", (void *)0 },
+ { "ours", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+ { "subtree", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+ ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addf(buf, "%s\n\n", arg);
+ have_message = 1;
+ }
+ return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+ struct path_list *list)
+{
+ int i;
+
+ if (!path)
+ return NULL;
+
+ for (i = 0; i < list->nr; i++)
+ if (!strcmp(path, list->items[i].path))
+ return &list->items[i];
+ return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+ path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i;
+ struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+ if (unset)
+ return 0;
+
+ if (item)
+ path_list_append_strategy(item);
+ else {
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ for (i = 0; i < strategies.nr; i++)
+ strbuf_addf(&err, " %s", strategies.items[i].path);
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+ fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+ exit(1);
+ }
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast forward (default)"),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out;
+ struct commit_list *j;
+ int fd;
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could not write to %s", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ strbuf_init(&out, 0);
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+ NULL, NULL, rev.date_mode, 0);
+ }
+ write(fd, out.buf, out.len);
+ close(fd);
+ strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+ env[0] = index;
+ env[1] = NULL;
+
+ if (squash)
+ argv[1] = "1";
+ else
+ argv[1] = "0";
+ argv[2] = NULL;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message;
+
+ strbuf_init(&reflog_message, 0);
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (!merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook("post-merge");
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf;
+ const char *ptr;
+ int len = 0;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, remote);
+ resolve_ref(buf.buf, branch_head, 0, 0);
+
+ if (!hashcmp(remote_head->sha1, branch_head)) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ return;
+ }
+ /* See if remote matches <name>~<number>, or <name>^ */
+ ptr = strrchr(remote, '^');
+ if (ptr && ptr[1] == '\0') {
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ }
+ else {
+ ptr = strrchr(remote, '~');
+ if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+ len = ptr-remote;
+ ptr++;
+ for (ptr++; *ptr; ptr++)
+ if (!isdigit(*ptr)) {
+ len = 0;
+ break;
+ }
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, len+11);
+ if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s' (early part) of .\n",
+ sha1_to_hex(remote_head->sha1), truname.buf);
+ return;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line;
+ char *ptr;
+
+ strbuf_init(&line, 0);
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die("could not open %s for reading: %s",
+ git_path("FETCH_HEAD"), strerror(errno));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ return;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ opts.head_idx = 2;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf;
+
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ const struct index_state *state = &the_index;
+ int i, ret = 0;
+
+ for (i = 0; i < state->cache_nr; i++)
+ if (ce_stage(state->cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ dir.show_ignored = 1;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ list->strdup_paths = 1;
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ path_list_append(q, list);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ path_list_append(q, list);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+ struct path_list list;
+ int i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list);
+ if (list.nr) {
+ for (i = 0; i < list.nr; i++) {
+ struct path_list_item *item;
+
+ item = unsorted_path_list_lookup(list.items[i].path,
+ &strategies);
+ if (item)
+ path_list_append_strategy(item);
+ }
+ return;
+ }
+ for (i = 0; i < strategies.nr; i++)
+ if ((enum strategy)strategies.items[i].util & strategy)
+ path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list parent;
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent.item = remoteheads->item;
+ parent.next = NULL;
+ commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ struct path_list_item *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');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere();
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+ return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 1) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ if (read_cache() < 0)
+ die("failed to read the cache");
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ setup_work_tree();
+ if (unmerged_cache())
+ die("You are in the middle of a conflicted merge.");
+
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && flag & REF_ISSYMREF) {
+ const char *ptr = skip_prefix(branch, "refs/heads/");
+ if (ptr)
+ branch = ptr;
+ } else
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * committish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ strbuf_init(&buf, 0);
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * If the merged head is a valid one there is no reason
+ * to forbid "git merge" into a branch yet to be born.
+ * We do the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ strbuf_init(&msg, 0);
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ remotes = &commit_list_insert(lookup_commit(o->sha1),
+ remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies.nr) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ refresh_cache(REFRESH_QUIET);
+ strbuf_init(&msg, 0);
+ strbuf_addstr(&msg, "Fast forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies.nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies.nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies.items[i].path);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = &use_strategies.items[i];
+
+ ret = try_merge_strategy(use_strategies.items[i].path,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy =
+ &use_strategies.items[i];
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies.nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies.items[0].path);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy->path);
+ try_merge_strategy(best_strategy->path, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die("Could not write to %s", git_path("MERGE_MSG"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
+ { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
--
1.5.6.1
^ permalink raw reply related
* [PATCH 00/14] Build in merge
From: Miklos Vajna @ 2008-07-01 2:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <0ed87f20b5fa79f4a16f23a522cfc7c52f77ce0e.1214878711.git.vmiklos@frugalware.org>
Sorry for the resend, but I again squashed an unrelated hunk to the last
patch, so I'm sending the changed patches:
08: Add new test to ensure git-merge handles more than 25 refs.
13: git-commit-tree: make it usable from other builtins
14: Build in merge
I really hope I won't overlook this next time.
The interdiff from the previous version is empty, I just moved that
unrelated hunk from patch 13 to patch 08.
Junio C Hamano (2):
Introduce get_merge_bases_many()
Introduce reduce_heads()
Miklos Vajna (12):
Move split_cmdline() to alias.c
Move commit_list_count() to commit.c
Move parse-options's skip_prefix() to git-compat-util.h
Add new test to ensure git-merge handles pull.twohead and
pull.octopus
Move read_cache_unmerged() to read-cache.c
git-fmt-merge-msg: make it usable from other builtins
Introduce get_octopus_merge_bases() in commit.c
Add new test to ensure git-merge handles more than 25 refs.
Add new test case to ensure git-merge reduces octopus parents when
possible
Add new test case to ensure git-merge prepends the custom merge
message
git-commit-tree: make it usable from other builtins
Build in merge
Makefile | 2 +-
alias.c | 54 ++
builtin-commit-tree.c | 71 +-
builtin-fmt-merge-msg.c | 155 ++--
builtin-merge-recursive.c | 8 -
builtin-merge.c | 1148 +++++++++++++++++++++++++
builtin-read-tree.c | 24 -
builtin-remote.c | 39 +-
builtin.h | 8 +
cache.h | 3 +
commit.c | 136 +++-
commit.h | 4 +
git-merge.sh => contrib/examples/git-merge.sh | 0
git-compat-util.h | 6 +
git.c | 54 +--
parse-options.c | 6 -
read-cache.c | 31 +
t/t7601-merge-pull-config.sh | 129 +++
t/t7602-merge-octopus-many.sh | 52 ++
t/t7603-merge-reduce-heads.sh | 63 ++
t/t7604-merge-custom-message.sh | 37 +
21 files changed, 1811 insertions(+), 219 deletions(-)
create mode 100644 builtin-merge.c
rename git-merge.sh => contrib/examples/git-merge.sh (100%)
create mode 100755 t/t7601-merge-pull-config.sh
create mode 100755 t/t7602-merge-octopus-many.sh
create mode 100755 t/t7603-merge-reduce-heads.sh
create mode 100755 t/t7604-merge-custom-message.sh
^ permalink raw reply
* [PATCH 13/14] git-commit-tree: make it usable from other builtins
From: Miklos Vajna @ 2008-07-01 2:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <cover.1214879690.git.vmiklos@frugalware.org>
Move all functionality (except option parsing) from cmd_commit_tree() to
commit_tree(), so that other builtins can use it without a child
process.
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
builtin-commit-tree.c | 71 ++++++++++++++++++++++++++++--------------------
builtin.h | 4 +++
2 files changed, 45 insertions(+), 30 deletions(-)
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 3881f6c..7a9a309 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
"You may want to amend it after fixing the message, or set the config\n"
"variable i18n.commitencoding to the encoding your project uses.\n";
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret)
{
- int i;
- struct commit_list *parents = NULL;
- unsigned char tree_sha1[20];
- unsigned char commit_sha1[20];
- struct strbuf buffer;
int encoding_is_utf8;
+ struct strbuf buffer;
- git_config(git_default_config, NULL);
-
- if (argc < 2)
- usage(commit_tree_usage);
- if (get_sha1(argv[1], tree_sha1))
- die("Not a valid object name %s", argv[1]);
-
- check_valid(tree_sha1, OBJ_TREE);
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- check_valid(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
- }
+ check_valid(tree, OBJ_TREE);
/* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
- strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+ strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
/*
* NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addch(&buffer, '\n');
/* And add the comment */
- if (strbuf_read(&buffer, 0, 0) < 0)
- die("git-commit-tree: read returned %s", strerror(errno));
+ strbuf_addstr(&buffer, msg);
/* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
- if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+ return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct commit_list *parents = NULL;
+ unsigned char tree_sha1[20];
+ unsigned char commit_sha1[20];
+ struct strbuf buffer = STRBUF_INIT;
+
+ git_config(git_default_config, NULL);
+
+ if (argc < 2)
+ usage(commit_tree_usage);
+ if (get_sha1(argv[1], tree_sha1))
+ die("Not a valid object name %s", argv[1]);
+
+ for (i = 2; i < argc; i += 2) {
+ unsigned char sha1[20];
+ const char *a, *b;
+ a = argv[i]; b = argv[i+1];
+ if (!b || strcmp(a, "-p"))
+ usage(commit_tree_usage);
+
+ if (get_sha1(b, sha1))
+ die("Not a valid object name %s", b);
+ check_valid(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ }
+
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die("git-commit-tree: read returned %s", strerror(errno));
+
+ if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
printf("%s\n", sha1_to_hex(commit_sha1));
return 0;
}
diff --git a/builtin.h b/builtin.h
index 2b01fea..05ee56f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -3,6 +3,8 @@
#include "git-compat-util.h"
#include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
extern const char git_version_string[];
extern const char git_usage_string[];
@@ -14,6 +16,8 @@ extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
--
1.5.6.1
^ permalink raw reply related
* [PATCH 08/14] Add new test to ensure git-merge handles more than 25 refs.
From: Miklos Vajna @ 2008-07-01 2:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <cover.1214879690.git.vmiklos@frugalware.org>
The old shell version handled only 25 refs but we no longer have this
limitation. Add a test to make sure this limitation will not be
introduced again in the future.
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
t/t7602-merge-octopus-many.sh | 52 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 52 insertions(+), 0 deletions(-)
create mode 100755 t/t7602-merge-octopus-many.sh
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755
index 0000000..fcb8285
--- /dev/null
+++ b/t/t7602-merge-octopus-many.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo c0 > c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ i=1 &&
+ while test $i -le 30
+ do
+ git reset --hard c0 &&
+ echo c$i > c$i.c &&
+ git add c$i.c &&
+ git commit -m c$i &&
+ git tag c$i &&
+ i=`expr $i + 1` || return 1
+ done
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
+ git reset --hard c1 &&
+ i=2 &&
+ refs="" &&
+ while test $i -le 30
+ do
+ refs="$refs c$i"
+ i=`expr $i + 1`
+ done
+ git merge $refs &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ i=1 &&
+ while test $i -le 30
+ do
+ test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+ i=`expr $i + 1` || return 1
+ done &&
+ git diff --exit-code &&
+ i=1 &&
+ while test $i -le 30
+ do
+ test -f c$i.c &&
+ i=`expr $i + 1` || return 1
+ done
+'
+
+test_done
--
1.5.6.1
^ permalink raw reply related
* [PATCH 14/14] Build in merge
From: Miklos Vajna @ 2008-07-01 2:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <20080701021317.GS4729@genesis.frugalware.org>
Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
Interdiff since the previous version:
git diff fab9109..b86e8ad
Individual commits:
git log b86e8ad
Makefile | 2 +-
builtin-merge.c | 1148 +++++++++++++++++++++++++
builtin.h | 1 +
git-merge.sh => contrib/examples/git-merge.sh | 0
git.c | 1 +
t/t7602-merge-octopus-many.sh | 2 +-
6 files changed, 1152 insertions(+), 2 deletions(-)
create mode 100644 builtin-merge.c
rename git-merge.sh => contrib/examples/git-merge.sh (100%)
diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-merge-stupid.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..bb8d985
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+ DEFAULT_TWOHEAD = 1,
+ DEFAULT_OCTOPUS = 2,
+ NO_FAST_FORWARD = 4,
+ NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git-merge [options] <remote>...",
+ "git-merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+ { "recur", (void *)NO_TRIVIAL },
+ { "recursive", (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+ { "octopus", (void *)DEFAULT_OCTOPUS },
+ { "resolve", (void *)0 },
+ { "stupid", (void *)0 },
+ { "ours", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+ { "subtree", (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+ ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addf(buf, "%s\n\n", arg);
+ have_message = 1;
+ }
+ return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+ struct path_list *list)
+{
+ int i;
+
+ if (!path)
+ return NULL;
+
+ for (i = 0; i < list->nr; i++)
+ if (!strcmp(path, list->items[i].path))
+ return &list->items[i];
+ return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+ path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i;
+ struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+ if (unset)
+ return 0;
+
+ if (item)
+ path_list_append_strategy(item);
+ else {
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ for (i = 0; i < strategies.nr; i++)
+ strbuf_addf(&err, " %s", strategies.items[i].path);
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+ fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+ exit(1);
+ }
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast forward (default)"),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out;
+ struct commit_list *j;
+ int fd;
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could not write to %s", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ strbuf_init(&out, 0);
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+ NULL, NULL, rev.date_mode, 0);
+ }
+ write(fd, out.buf, out.len);
+ close(fd);
+ strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+ env[0] = index;
+ env[1] = NULL;
+
+ if (squash)
+ argv[1] = "1";
+ else
+ argv[1] = "0";
+ argv[2] = NULL;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message;
+
+ strbuf_init(&reflog_message, 0);
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (!merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook("post-merge");
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf;
+ const char *ptr;
+ int len = 0;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, remote);
+ resolve_ref(buf.buf, branch_head, 0, 0);
+
+ if (!hashcmp(remote_head->sha1, branch_head)) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ return;
+ }
+ /* See if remote matches <name>~<number>, or <name>^ */
+ ptr = strrchr(remote, '^');
+ if (ptr && ptr[1] == '\0') {
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ }
+ else {
+ ptr = strrchr(remote, '~');
+ if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+ len = ptr-remote;
+ ptr++;
+ for (ptr++; *ptr; ptr++)
+ if (!isdigit(*ptr)) {
+ len = 0;
+ break;
+ }
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, len+11);
+ if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s' (early part) of .\n",
+ sha1_to_hex(remote_head->sha1), truname.buf);
+ return;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line;
+ char *ptr;
+
+ strbuf_init(&line, 0);
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die("could not open %s for reading: %s",
+ git_path("FETCH_HEAD"), strerror(errno));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ return;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ opts.head_idx = 2;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf;
+
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ const struct index_state *state = &the_index;
+ int i, ret = 0;
+
+ for (i = 0; i < state->cache_nr; i++)
+ if (ce_stage(state->cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ dir.show_ignored = 1;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ list->strdup_paths = 1;
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ path_list_append(q, list);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ path_list_append(q, list);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+ struct path_list list;
+ int i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list);
+ if (list.nr) {
+ for (i = 0; i < list.nr; i++) {
+ struct path_list_item *item;
+
+ item = unsorted_path_list_lookup(list.items[i].path,
+ &strategies);
+ if (item)
+ path_list_append_strategy(item);
+ }
+ return;
+ }
+ for (i = 0; i < strategies.nr; i++)
+ if ((enum strategy)strategies.items[i].util & strategy)
+ path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list parent;
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent.item = remoteheads->item;
+ parent.next = NULL;
+ commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ struct path_list_item *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');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere();
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+ return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 1) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ if (read_cache() < 0)
+ die("failed to read the cache");
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ setup_work_tree();
+ if (unmerged_cache())
+ die("You are in the middle of a conflicted merge.");
+
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && flag & REF_ISSYMREF) {
+ const char *ptr = skip_prefix(branch, "refs/heads/");
+ if (ptr)
+ branch = ptr;
+ } else
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * committish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ strbuf_init(&buf, 0);
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * If the merged head is a valid one there is no reason
+ * to forbid "git merge" into a branch yet to be born.
+ * We do the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ strbuf_init(&msg, 0);
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ remotes = &commit_list_insert(lookup_commit(o->sha1),
+ remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies.nr) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ refresh_cache(REFRESH_QUIET);
+ strbuf_init(&msg, 0);
+ strbuf_addstr(&msg, "Fast forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies.nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies.nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies.items[i].path);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = &use_strategies.items[i];
+
+ ret = try_merge_strategy(use_strategies.items[i].path,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy =
+ &use_strategies.items[i];
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies.nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies.items[0].path);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy->path);
+ try_merge_strategy(best_strategy->path, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die("Could not write to %s", git_path("MERGE_MSG"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
+ { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
done
'
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
git reset --hard c1 &&
i=2 &&
refs="" &&
--
1.5.6.1
^ permalink raw reply related
* [PATCH 13/14] git-commit-tree: make it usable from other builtins
From: Miklos Vajna @ 2008-07-01 2:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <20080701021317.GS4729@genesis.frugalware.org>
Move all functionality (except option parsing) from cmd_commit_tree() to
commit_tree(), so that other builtins can use it without a child
process.
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
This patch is against master, you need to pull from me or first rebase
mv/merge-in-c against current master.
I did it this way to avoid an unnecessary conflict with Dscho's recent
patch to commit-tree (ef98c5c).
builtin-commit-tree.c | 71 ++++++++++++++++++++++++++++--------------------
builtin.h | 4 +++
2 files changed, 45 insertions(+), 30 deletions(-)
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 3881f6c..7a9a309 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
"You may want to amend it after fixing the message, or set the config\n"
"variable i18n.commitencoding to the encoding your project uses.\n";
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret)
{
- int i;
- struct commit_list *parents = NULL;
- unsigned char tree_sha1[20];
- unsigned char commit_sha1[20];
- struct strbuf buffer;
int encoding_is_utf8;
+ struct strbuf buffer;
- git_config(git_default_config, NULL);
-
- if (argc < 2)
- usage(commit_tree_usage);
- if (get_sha1(argv[1], tree_sha1))
- die("Not a valid object name %s", argv[1]);
-
- check_valid(tree_sha1, OBJ_TREE);
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- check_valid(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
- }
+ check_valid(tree, OBJ_TREE);
/* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
- strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+ strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
/*
* NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addch(&buffer, '\n');
/* And add the comment */
- if (strbuf_read(&buffer, 0, 0) < 0)
- die("git-commit-tree: read returned %s", strerror(errno));
+ strbuf_addstr(&buffer, msg);
/* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
- if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+ return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct commit_list *parents = NULL;
+ unsigned char tree_sha1[20];
+ unsigned char commit_sha1[20];
+ struct strbuf buffer = STRBUF_INIT;
+
+ git_config(git_default_config, NULL);
+
+ if (argc < 2)
+ usage(commit_tree_usage);
+ if (get_sha1(argv[1], tree_sha1))
+ die("Not a valid object name %s", argv[1]);
+
+ for (i = 2; i < argc; i += 2) {
+ unsigned char sha1[20];
+ const char *a, *b;
+ a = argv[i]; b = argv[i+1];
+ if (!b || strcmp(a, "-p"))
+ usage(commit_tree_usage);
+
+ if (get_sha1(b, sha1))
+ die("Not a valid object name %s", b);
+ check_valid(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ }
+
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die("git-commit-tree: read returned %s", strerror(errno));
+
+ if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
printf("%s\n", sha1_to_hex(commit_sha1));
return 0;
}
diff --git a/builtin.h b/builtin.h
index 2b01fea..05ee56f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -3,6 +3,8 @@
#include "git-compat-util.h"
#include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
extern const char git_version_string[];
extern const char git_usage_string[];
@@ -14,6 +16,8 @@ extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
--
1.5.6.1
^ permalink raw reply related
* Re: [PATCH 13/13] Build in merge
From: Miklos Vajna @ 2008-07-01 2:13 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <7vd4lz4gtw.fsf@gitster.siamese.dyndns.org>
[-- Attachment #1: Type: text/plain, Size: 11364 bytes --]
On Sun, Jun 29, 2008 at 10:44:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > +static void save_state(void)
> > +{
> > + int fd;
> > + struct child_process stash;
> > + const char *argv[] = {"stash", "create", NULL};
> > +
> > + fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
> > + if (fd < 0)
> > + die("Could not write to %s", git_path("MERGE_STASH"));
> > + memset(&stash, 0, sizeof(stash));
> > + stash.argv = argv;
> > + stash.out = fd;
> > + stash.git_cmd = 1;
> > + run_command(&stash);
> > +}
>
> I first thought "heh, that's clever" until I noticed that we use "stash
> create" with "stash apply" these days instead of cpio for this. I suspect
> that we can do away without leaving the stash in this temporary file, but
> that comment applies to the scripted version as well.
We can. I just did it. ;-)
> By the way, it would be consistent to name counterpart to dropsave in the
> scripted version as "drop_save" if you use "save_state" and "restore_state".
OK, renamed.
>
> > +static void reset_hard(unsigned const char *sha1, int verbose)
> > +{
> > + struct tree *tree;
> > + struct unpack_trees_options opts;
> > + struct tree_desc t;
> > +
> > + memset(&opts, 0, sizeof(opts));
> > + opts.head_idx = -1;
> > + opts.src_index = &the_index;
> > + opts.dst_index = &the_index;
> > + opts.update = 1;
> > + opts.reset = 1;
> > + if (verbose)
> > + opts.verbose_update = 1;
> > +
> > + tree = parse_tree_indirect(sha1);
> > + if (!tree)
> > + die("failed to unpack %s tree object", sha1_to_hex(sha1));
> > + parse_tree(tree);
> > + init_tree_desc(&t, tree->buffer, tree->size);
> > + if (unpack_trees(1, &t, &opts))
> > + exit(128); /* We've already reported the error, finish dying */
> > +}
>
> Isn't this trashing all the cached stat info from the index? If this is
> emulating "reset --hard", it also should set opts.merge and do
> oneway_merge, after reading the current index in, I think. Resetting the
> index and the working tree is not particularly performance critical part,
> but trashing the cached stat info would hurt the performance of everything
> that reads the index after this function returns quite badly. I suspect
> that you might be better off forking the real thing (reset --hard) if you
> cannot get it right here.
I just realized that builtin-reset forks read-tree as well, so I did
almost the same.
> > +/* Get the name for the merge commit's message. */
> > +static void merge_name(const char *remote, struct strbuf *msg)
> > ...
> > + strbuf_init(&buf, 0);
> > + strbuf_addstr(&buf, "refs/heads/");
> > + strbuf_addstr(&buf, remote);
> > + dwim_ref(buf.buf, buf.len, branch_head, &ref);
> > + if (!hashcmp(remote_head->sha1, branch_head)) {
> > + strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> > + sha1_to_hex(branch_head), remote);
> > + return;
> > + }
>
> Hmm, why not resolve_ref() so that it does not dwim at all? The point of
> the code is so that you can be confident that 'blah' *is* a local branch
> when you say "branch 'blah'".
Yes. I'm now using resolve_ref().
> > + /* See if remote matches <name>~<number>, or <name>^ */
> > + ptr = strrchr(remote, '^');
> > + if (ptr && ptr[1] == '\0') {
> > + len = strlen(remote);
> > + while ((ptr = (char *)memrchr(remote, '^', len)))
> > + if (ptr && ptr[1] == '\0')
> > + len = ptr - remote - 1;
> > + else
> > + break;
>
> That's a funny way to say:
>
> for (len = 0, ptr = remote + strlen(remote);
> remote < ptr && ptr[-1] == '^';
> ptr--)
> len++;
Ah, and this way I don't need memrchr(), which was pointed out to be
problemtic on Cygwin.
>
> > + if (len) {
> > + struct strbuf truname = STRBUF_INIT;
> > + strbuf_addstr(&truname, remote);
> > + strbuf_setlen(&truname, len);
> > + if (dwim_ref(truname.buf, truname.len, buf_sha, &ref)) {
> > + strbuf_addf(msg,
> > + "%s\t\tbranch '%s' (early part) of .\n",
> > + sha1_to_hex(remote_head->sha1), truname.buf);
> > + return;
>
> Isn't this wrong? Giving "v1.5.6~20" to this code will strip ~20 and make
> remote = "v1.5.6", to which dwim_ref() will happily say Ok, and you end up
> saying "branch 'v1.5.6' (early part)", don't you?
Right. Now I do
strbuf_addstr(&truname, "refs/heads/");
Before appending the remote name to truname, so that should exclude
tags.
> > +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> > + unsigned char *one)
> > +{
> > + int i, nr_trees = 0;
> > + struct tree *trees[MAX_UNPACK_TREES];
> > + struct tree_desc t[MAX_UNPACK_TREES];
> > + struct unpack_trees_options opts;
> > +
> > + memset(&opts, 0, sizeof(opts));
> > + opts.head_idx = -1;
>
> Is this the correct head_idx value for this three-way merge? I think it
> should be 2 but please double check.
Yes, you are right. I just checked builtin-read-tree and it's 2, not -1.
> > +static int commit_tree_trivial(const char *msg, unsigned const char *tree,
> > + struct commit_list *parents, unsigned char *ret)
> > +{
> > ...
> > +}
>
> We may want to have another patch before this one to abstract most of
> cmd_commit_tree() out, perhaps?
Done. And now builtin-merge uses commit_tree() as well.
> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > ...
> > + /*
> > + * This could be traditional "merge <msg> HEAD <commit>..." and
> > + * the way we can tell it is to see if the second token is HEAD,
> > + * but some people might have misused the interface and used a
> > + * committish that is the same as HEAD there instead.
> > + * Traditional format never would have "-m" so it is an
> > + * additional safety measure to check for it.
> > + */
> > + strbuf_init(&buf, 0);
> > + if (argc > 1) {
> > + unsigned char second_sha1[20];
> > +
> > + if (get_sha1(argv[1], second_sha1))
> > + die("Not a valid ref: %s", argv[1]);
> > + second_token = lookup_commit_reference_gently(second_sha1, 0);
> > + if (!second_token)
> > + die("'%s' is not a commit", argv[1]);
>
> Interesting.
>
> This _superficially_ is quite wrong, because the purpose of this part of
> the code is to tell if we got old-style invocation, and we should not
> barfing merely because what we got is _not_ old-style. If it is not
> old-style, then it would be new-style, and the logic to tell if it is
> old-style should ideally not have much knowledge about the new-style
> invocation to say "hey, that's an incocrrect new-style invocation". By
> the way, this part should probably be in a separate function:
>
> static int is_old_style_invocation(int ac, const char **gv);
OK, I broke out is_old_style_invocation() from cmd_merge().
> Old-style invocation of "git merge" (primarily by "git pull") was
> to call it as:
>
> git merge "message here" HEAD $commit1 $commit2...
>
> and it checks the second token ("HEAD" in the above, but people can misuse
> the interface to name the current branch name). If the second token is
> not a ref that resolves to a commit, all you know is that this is _not_ an
> old-style invocation, and calling the program with new-style is not a
> crime.
>
> The only reason this is wrong only superficially is because new style
> invocation would always be:
>
> git merge [options] $commit1 $commit2...
>
> after stripping the options, and these seemingly wrong die() will complain
> when you try to create an Octopus with the new-style syntax and the
> parameter given as the second remote parent is not a commit. So the logic
> is wrong, the fact that the user gets the same error message for incorrect
> old-style invocation (perhaps "git merge <msg> HAED $commit") and
> incorrect new-style invocation "git merge $commit1 $nonsense" is just an
> accident, and the end result does not hurt, but asks for a "Huh? why does
> it check and complain only the second parent here but not the first one?".
>
> It is interesting, but feels quite dirty.
Now if the second token is a valid SHA1 then I die() if it's not a
commit, but otherwise I just assume it's a new-style invocation.
> > + if (!have_message && second_token &&
> > + !hashcmp(second_token->object.sha1, head)) {
>
> You need to know that resolve_ref() cleared head[] when head_invalid is
> true when reading this code to notice that, unlike the previous round of
> this patch, it is Ok not to check head_invalid is fine here. I somehow
> feel it is an unnecessary optimization/obfuscation.
>
> But once you have "is_old_style_invocation" suggested earlier, this part
> would look much cleaner and the above comment would become unnecessary.
Yes, now it's just:
if (!have_message && is_old_style_invocation(argc, argv)) {
>
> > + for (i = 0; i < argc; i++) {
> > + struct object *o;
> > +
> > + o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
> > + if (!o)
> > + die("%s - not something we can merge", argv[i]);
> > + remotes = &commit_list_insert(lookup_commit(o->sha1),
> > + remotes)->next;
> > +
> > + strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
> > + setenv(buf.buf, argv[i], 1);
> > + strbuf_reset(&buf);
> > + }
> > +
> > + o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> > + 0, NULL, OBJ_COMMIT);
> > + if (!o)
> > + return 0;
> > +
> > + if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> > + return 0;
>
> When o does not peel well, or checkout_fast_forward() returns failure,
> that would be a failure case, wouldn't it? Why return 0?
>
> Maybe you misread "exit" in shell scripts? It does not mean exit(0); it
> means "exit with the same exit status as the last command". So
>
> new_head=$(git rev-parse ...) &&
> git read-tree -m -u ... &&
> finish || exit
>
> will exit non-zero if any of the commands chained by && fails.
Thanks, that was the case. I thought "false || exit" exits with status
code 0.
> > + /*
> > + * At this point, we need a real merge. No matter what strategy
> > + * we use, it would operate on the index, possibly affecting the
> > + * working tree, and when resolved cleanly, have the desired
> > + * tree in the index -- this means that the index must be in
> > + * sync with the head commit. The strategies are responsible
> > + * to ensure this.
> > + */
> > + if (use_strategies.nr != 1) {
> > + /*
> > + * Stash away the local changes so that we can try more
> > + * than one.
> > + */
> > + save_state();
> > + single_strategy = 0;
> > + } else {
> > + unlink(git_path("MERGE_STASH"));
> > + single_strategy = 1;
>
> I think s/single_strategy/(use_strategies.nr == 1)/ in the remainder of the
> code would be taking advantage of working in C ;-)
I dropped single_strategy.
>
> > + if (ret) {
> > + /*
> > + * The backend exits with 1 when conflicts are
> > + * left to be resolved, with 2 when it does not
> > + * handle the given merge at all.
> > + */
> > + if (ret == 1) {
>
> Probably from here til ...
>
> > + int cnt = 0;
> > ...
> > + cnt += count_unmerged_entries();
>
> ... here should be a separate "evaluate_result()" function.
Done.
[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Junio C Hamano @ 2008-07-01 1:13 UTC (permalink / raw)
To: Christian Couder
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <200807010220.44657.chriscool@tuxfamily.org>
Christian Couder <chriscool@tuxfamily.org> writes:
> Another option is to introduce a switch to "git bisect start",
> perhaps --strict, to please people who always want to use good revs that
> are ancestor of the bad revs, so they get a nice error when this is not the
> case.
I do not see much niceness value to such an option, though. I think such
a switch is going the opposite way from improving usability.
You cannot bisect the history with such a draconian switch when you learn
from somebody that 'maint' is Ok but 'master' is broken, and you already
know it is a regression somewhere introduced in 'master', because you know
you did _not_ fix it (or at least you do not remember fixing it) only for
'maint'.
^ permalink raw reply
* Re: What's cooking in git.git (topics)
From: Stephen Rothwell @ 2008-07-01 0:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristian Høgsberg, git, akpm, pasky
In-Reply-To: <7vabh2vaav.fsf@gitster.siamese.dyndns.org>
[-- Attachment #1: Type: text/plain, Size: 483 bytes --]
On Mon, 30 Jun 2008 15:15:52 -0700 Junio C Hamano <gitster@pobox.com> wrote:
>
> branches, and Stephen who is also a heavy integrator even though I do not
> know if he is in branches camp or uses more modern style), but they now
I use "git remote add" for each new repository and haven't had anything
in the branches directory of any of my trees for a long time ...
--
Cheers,
Stephen Rothwell sfr@canb.auug.org.au
http://www.canb.auug.org.au/~sfr/
[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Christian Couder @ 2008-07-01 0:20 UTC (permalink / raw)
To: Junio C Hamano
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <7vej6etra7.fsf@gitster.siamese.dyndns.org>
Le mardi 1 juillet 2008, Junio C Hamano a écrit :
> Christian Couder <chriscool@tuxfamily.org> writes:
> > Yes, but the fact is that the user may wrongly think that F is an
> > ancestor of D or he may not remember/know about the rule that saying "F
> > is good" means "everything from A to F is good". That's why this patch
> > adds a safety net by detecting end erroring out in this case.
>
> Yeah, sorry about the confusion earlier.
>
> But I do not think forbidding forked topology very early in bisection
> process is a very good idea. The user would be at loss when told:
>
> echo >&2 "Maybe you mistake good and bad revs?"
Yeah, perhaps we should then check that it can be a mistake by testing if
bad is an ancestor of good. If bad is indeed an ancestor of good, it's
probably a mistake and we may even ask if the user wants good and bad to be
swaped (assuming he gave only one good rev of course).
> Aside from the "test a trial merge" idea I floated in the other message,
> when we detect such a fork, perhaps we can suggest testing the merge base
> version (B in your picture) first? We would immediately know as the user
> would say "B is bad" if the topology is problematic.
Yes this can be a good idea, if the user gave only one good rev. It maybe
more tricky if he gave many good revs, but in this case we may perhaps drop
siblings good revs as long as one good rev is an ancestor of the bad rev.
> Then, we can suggest the user that breakage at D may not be a regression
> but a longstanding bug that was recently fixed somewhere between B and F.
>
> The user then can decide to bisect to find the fix (so that it can be
> cherry picked on top of D) or merge F into D to propagate the fix forward
> if it is not important to find out which exact commit fixed the issue.
>
> Hmm?
Yeah that might be a plan.
Another option is to introduce a switch to "git bisect start",
perhaps --strict, to please people who always want to use good revs that
are ancestor of the bad revs, so they get a nice error when this is not the
case.
Thanks,
Christian.
^ permalink raw reply
* Re: How to reduce remaining differences to 4msysgit? (was What's cooking in git.git (topics))
From: Clifford Caoile @ 2008-07-01 0:03 UTC (permalink / raw)
To: johannes.sixt; +Cc: prohaska, msysGit, Junio C Hamano, Git Mailing List
In-Reply-To: <200806302047.56935.johannes.sixt@telecom.at>
Hi:
On Tue, Jul 1, 2008 at 3:47 AM, Johannes Sixt <johannes.sixt@telecom.at> wrote:
>
> On Montag, 30. Juni 2008, Steffen Prohaska wrote:
>> On Jun 30, 2008, at 11:08 AM, Junio C Hamano wrote:
>> > * MinGW will be in.
>>
>> How should we proceed to get rid of the differences?
> ...
> Then there are the extra patches in 4msysgit. From my POV, they are not
> _required_ because I can appearently work with git on Windows without them. I
> think some of them are not necessary. Can we go through them again?
As one of the extra patches in 4msysgit, there is the cca/git.el
branch [1] which I contributed [2] previously for Emacs git.el users
on Windows. However I have not gotten any feedback whatsoever, so
perhaps parking it in 4msysgit is not appropriate. I plan to
separately to host these patch(es). Please ignore it or remove it at
your convenience.
References:
[1] http://repo.or.cz/w/git/mingw/4msysgit.git?a=shortlog;h=refs/heads/cca/git.el
[2] http://thread.gmane.org/gmane.comp.version-control.msysgit/2140
Best regards,
Clifford Caoile
^ permalink raw reply
* [PATCH/RFC] Emulating symlinks on Windows with file copies
From: Patrick Higgins @ 2008-06-30 23:54 UTC (permalink / raw)
To: git; +Cc: Patrick Higgins
Signed-off-by: Patrick Higgins <patrick.higgins@cexp.com>
---
This is a very basic script to test out the idea of using copies to emulate
symlinks on Windows. I don't track any changes in the index yet which seems
necessary as a next step.
Symlinks get deleted and replaced with copies. Existing unchanged copies
are ignored. Copies that have been modified get merged into the target of
the link.
This might have some messy cases that I haven't considered yet, but it seems
like a fairly straightforward way to make a repository containing file
symlinks useful on Windows.
I have not yet started on directories. This technique would probably get
messy with directories. Perhaps junction (reparse) points would be better
for directories?
git-sync-symlinks.perl | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 44 insertions(+), 0 deletions(-)
create mode 100755 git-sync-symlinks.perl
diff --git a/git-sync-symlinks.perl b/git-sync-symlinks.perl
new file mode 100755
index 0000000..223ae83
--- /dev/null
+++ b/git-sync-symlinks.perl
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Copy;
+use Fcntl ':mode';
+
+my $filepipe = open(FILELIST, '-|', 'git', 'ls-tree', '-z', '-r', 'HEAD')
+ or die("Cannot call git ls-tree : $!");
+
+local $/ = "\0";
+
+while ( <FILELIST> ) {
+ chomp;
+ if (/^120000 blob ([0-9a-f]{40})\t(.*)$/) {
+ my ($id, $path) = ($1, $2);
+ my $target = `git cat-file blob $id`;
+ chomp($target);
+ my @path_stat = lstat($path);
+ my @target_stat = stat($target);
+
+ if (S_ISLNK($path_stat[2])) {
+ printf("Copying '%s' to '%s'\n", $target, $path);
+ unlink($path);
+ copy($target, $path);
+ }
+ elsif ($path_stat[7] != $target_stat[7] ||
+ `git hash-object $path` ne `git hash-object $target`)
+ {
+ printf("Merging '%s' to '%s'\n", $path, $target);
+ my @target_parts = split(/\s+/, `git ls-tree HEAD $target`);
+ my $target_id = $target_parts[2];
+ my $original = $target . ".BASE";
+ open(ORIGINAL, '-|', 'git', 'cat-file', 'blob', $target_id);
+ open(ORIGINAL_OUT, '>', $original);
+ while ( <ORIGINAL> ) {
+ print ORIGINAL_OUT $_;
+ }
+ close ORIGINAL_OUT;
+ close ORIGINAL;
+ system('git', 'merge-file', $target, $original, $path);
+ }
+ }
+}
+close FILELIST;
--
1.5.6.dirty
^ permalink raw reply related
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Junio C Hamano @ 2008-06-30 23:52 UTC (permalink / raw)
To: Christian Couder
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <200807010146.09206.chriscool@tuxfamily.org>
Christian Couder <chriscool@tuxfamily.org> writes:
> Yes, but the fact is that the user may wrongly think that F is an ancestor
> of D or he may not remember/know about the rule that saying "F is good"
> means "everything from A to F is good". That's why this patch adds a safety
> net by detecting end erroring out in this case.
Yeah, sorry about the confusion earlier.
But I do not think forbidding forked topology very early in bisection
process is a very good idea. The user would be at loss when told:
echo >&2 "Maybe you mistake good and bad revs?"
Aside from the "test a trial merge" idea I floated in the other message,
when we detect such a fork, perhaps we can suggest testing the merge base
version (B in your picture) first? We would immediately know as the user
would say "B is bad" if the topology is problematic.
Then, we can suggest the user that breakage at D may not be a regression
but a longstanding bug that was recently fixed somewhere between B and F.
The user then can decide to bisect to find the fix (so that it can be
cherry picked on top of D) or merge F into D to propagate the fix forward
if it is not important to find out which exact commit fixed the issue.
Hmm?
^ permalink raw reply
* [PATCH] Teach "git apply" to prepend a prefix with "--root=<root>"
From: Johannes Schindelin @ 2008-06-30 23:44 UTC (permalink / raw)
To: H. Peter Anvin, git
With "git apply --root=<root>", all file names in the patch are prepended
with <root>. If a "-p" value was given, the paths are stripped _before_
prepending <root>.
Wished for by HPA.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
Only lightly tested.
Documentation/git-apply.txt | 6 +++++-
builtin-apply.c | 24 ++++++++++++++++++++++++
t/t4128-apply-root.sh | 32 ++++++++++++++++++++++++++++++++
3 files changed, 61 insertions(+), 1 deletions(-)
create mode 100755 t/t4128-apply-root.sh
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index c5ee636..7c51830 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -14,7 +14,7 @@ SYNOPSIS
[--allow-binary-replacement | --binary] [--reject] [-z]
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
[--whitespace=<nowarn|warn|fix|error|error-all>]
- [--exclude=PATH] [--verbose] [<patch>...]
+ [--exclude=PATH] [--root=<root>] [--verbose] [<patch>...]
DESCRIPTION
-----------
@@ -182,6 +182,10 @@ behavior:
by inspecting the patch (e.g. after editing the patch without
adjusting the hunk headers appropriately).
+--root=<root>::
+ Prepend <root> to all filenames. If a "-p" argument was passed, too,
+ it is applied before prepending the new root.
+
Configuration
-------------
diff --git a/builtin-apply.c b/builtin-apply.c
index 9fcfe39..d56d913 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -58,6 +58,8 @@ static int whitespace_error;
static int squelch_whitespace_errors = 5;
static int applied_after_fixing_ws;
static const char *patch_input_file;
+static const char *root;
+static int root_len;
static void parse_whitespace_option(const char *option)
{
@@ -340,6 +342,8 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
*/
strbuf_remove(&name, 0, cp - name.buf);
free(def);
+ if (root)
+ strbuf_insert(&name, 0, root, root_len);
return strbuf_detach(&name, NULL);
}
}
@@ -378,6 +382,14 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
free(def);
}
+ if (root) {
+ char *ret = xmalloc(root_len + len + 1);
+ strcpy(ret, root);
+ memcpy(ret + root_len, start, len);
+ ret[root_len + len] = '\0';
+ return ret;
+ }
+
return xmemdupz(start, len);
}
@@ -3240,6 +3252,18 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
options |= RECOUNT;
continue;
}
+ if (!strncmp(arg, "--root=", strlen("--root="))) {
+ arg += strlen("--root=");
+ root_len = strlen(arg);
+ if (root_len && arg[root_len + 1] != '/') {
+ char *new_root;
+ root = new_root = xmalloc(root_len + 2);
+ strcpy(new_root, arg);
+ strcpy(new_root + root_len++, "/");
+ } else
+ root = arg;
+ continue;
+ }
if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
diff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh
new file mode 100755
index 0000000..80b5af2
--- /dev/null
+++ b/t/t4128-apply-root.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='apply same filename'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ mkdir -p some/sub/dir &&
+ echo Hello > some/sub/dir/file &&
+ git add some/sub/dir/file
+
+'
+
+cat > patch << EOF
+diff a/bla/blub/dir/file b/bla/blub/dir/file
+--- a/bla/blub/dir/file
++++ b/bla/blub/dir/file
+@@ -1,1 +1,1 @@
+-Hello
++Bello
+EOF
+
+test_expect_success 'apply --root -p --index' '
+
+ git apply --root=some/sub -p3 --index patch &&
+ test Bello = $(git show :some/sub/dir/file) &&
+ test Bello = $(cat some/sub/dir/file)
+
+'
+
+test_done
--
1.5.6.1.297.g148d9
^ permalink raw reply related
* Re: [PATCH] Make default expiration period of reflog used for stash infinite
From: Olivier Marin @ 2008-06-30 23:45 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v3amweiaz.fsf@gitster.siamese.dyndns.org>
Junio C Hamano a écrit :
> This makes the default expiration period for the reflog that implements
> stash infinite.
I did not read the whole thread so maybe I missed something but I though you
wanted to apply Nanako's patch before?
The patch: http://article.gmane.org/gmane.comp.version-control.git/85055
Olivier.
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Junio C Hamano @ 2008-06-30 23:42 UTC (permalink / raw)
To: Christian Couder
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <7vprpyts7j.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
> Christian Couder <chriscool@tuxfamily.org> writes:
>
>> Le mardi 1 juillet 2008, Junio C Hamano a écrit :
>>> Christian Couder <chriscool@tuxfamily.org> writes:
>>> > Before this patch "git bisect" doesn't really work when it is given
>>> > some good revs that are siblings of the bad rev.
>>> >
>>> > For example if there is the following history:
>>> >
>>> > A-B-C-D
>>> > \E-F
>>> >
>>> > and we launch "git bisect start D F" then only C and D will be
>>> > considered as possible first bad commit.
>>
>> I am assuming the first bad commit in the graph is A and it is fixed by F.
>
> Ah, I see your confusion here. bisect is about finding regressions.
> "Older ones were good, and now there is a breakage. Who broke it?"
>
> If F fixed it, that is already outside the bisection's scope. The user
> needs to know that by saying F is good, he is saying he knows everything
> that leads to F is good.
Ok, I wasn't thinking straight.
The tricky part is that the user does not know.
Until we somehow can check B is bad, we cannot tell that the original
bisection was done incorrectly (in the sense that it was not finding
regression, but what the user could find is which commit fixed it).
If we perform a test merge between D and F and test that tree, and if that
turns out to be Ok, that would be a good indication of existence of a fix
(i.e. the topology is not "old good code was broken somewhere", but "old
broken code was fixed somewhere"). I am not sure how applicable that
approach would be in practice, though.
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Christian Couder @ 2008-06-30 23:46 UTC (permalink / raw)
To: Junio C Hamano
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <7vprpyts7j.fsf@gitster.siamese.dyndns.org>
Le mardi 1 juillet 2008, Junio C Hamano a écrit :
> Christian Couder <chriscool@tuxfamily.org> writes:
> > Le mardi 1 juillet 2008, Junio C Hamano a écrit :
> >> Christian Couder <chriscool@tuxfamily.org> writes:
> >> > Before this patch "git bisect" doesn't really work when it is given
> >> > some good revs that are siblings of the bad rev.
> >> >
> >> > For example if there is the following history:
> >> >
> >> > A-B-C-D
> >> > \E-F
> >> >
> >> > and we launch "git bisect start D F" then only C and D will be
> >> > considered as possible first bad commit.
> >
> > I am assuming the first bad commit in the graph is A and it is fixed by
> > F.
>
> Ah, I see your confusion here. bisect is about finding regressions.
> "Older ones were good, and now there is a breakage. Who broke it?"
>
> If F fixed it, that is already outside the bisection's scope. The user
> needs to know that by saying F is good, he is saying he knows everything
> that leads to F is good.
Yes, but the fact is that the user may wrongly think that F is an ancestor
of D or he may not remember/know about the rule that saying "F is good"
means "everything from A to F is good". That's why this patch adds a safety
net by detecting end erroring out in this case.
Regards,
Christian.
^ permalink raw reply
* Re: [PATCH 3/7] Documentation: complicate example of "man git-command"
From: Christian Couder @ 2008-06-30 23:39 UTC (permalink / raw)
To: Jonathan Nieder
Cc: git, Junio C Hamano, Nguyen Thai Ngoc Duy, Jon Loeliger,
J. Bruce Fields
In-Reply-To: <Pine.GSO.4.62.0806301706090.7190@harper.uchicago.edu>
Le mardi 1 juillet 2008, Jonathan Nieder a écrit :
> The manual page for the command invoked as "git clone" is named
> git-clone(1), and similarly for the rest of the git commands.
> Make sure our first example of this in tutorials makes it clear
> that it is the first two words of a command line that make up the
> command's name (that is: for example, the effect of "git svn
> dcommit" is described in git-svn(1)).
>
> Signed-off-by: Jonathan Nieder <jrnieder@uchicago.edu>
> ---
> If I didn't make this change, consistency would mean
> saying "you can get documentation for a command such
> as `git-diff` with `man git-diff`", which might not be
> very enlightening.
By the way, while at it, maybe we could talk about "git help <cmd/topic>" at
the same time.
Thanks,
Christian.
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Junio C Hamano @ 2008-06-30 23:32 UTC (permalink / raw)
To: Christian Couder
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <200807010116.30214.chriscool@tuxfamily.org>
Christian Couder <chriscool@tuxfamily.org> writes:
> Le mardi 1 juillet 2008, Junio C Hamano a écrit :
>> Christian Couder <chriscool@tuxfamily.org> writes:
>> > Before this patch "git bisect" doesn't really work when it is given
>> > some good revs that are siblings of the bad rev.
>> >
>> > For example if there is the following history:
>> >
>> > A-B-C-D
>> > \E-F
>> >
>> > and we launch "git bisect start D F" then only C and D will be
>> > considered as possible first bad commit.
>
> I am assuming the first bad commit in the graph is A and it is fixed by F.
Ah, I see your confusion here. bisect is about finding regressions.
"Older ones were good, and now there is a breakage. Who broke it?"
If F fixed it, that is already outside the bisection's scope. The user
needs to know that by saying F is good, he is saying he knows everything
that leads to F is good.
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Junio C Hamano @ 2008-06-30 23:27 UTC (permalink / raw)
To: Christian Couder
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <200807010116.30214.chriscool@tuxfamily.org>
Christian Couder <chriscool@tuxfamily.org> writes:
> Yes, I assume C is also bad.
>
>> ... which means you are dealing with *two* breakages. That's outside
>> what bisect deals with.
>
> Sorry, I don't understand why I am assuming 2 breakages.
Your topology is
>> > A-B-C-D
>> > \E-F
and you start with "D F" so D is bad and F is good, right?
bisect operates on a history with a single breakage that separates older
good ones and newer bad ones.
Now D is bad, so there must be somewhere in
A--B--C--D
that broke D. It could be D itself, or it may have been broken at A
already.
But if A or B were bad, then, because F is good, this part of the picture:
A--B--E--F
does not deal with "old good followed by new bad sparated by a single
breakage point". Maybe E or F have fixed it, but then before that is bad
and after that is good. That's not what bisect deals with.
Going back to the original picture, then:
>> > A-B-C-D
>> > \E-F
For F to be good, all of its ancestors are by definition good (that is the
basic assumption of bisection). A and B are good. If D is broken, C or D
must be broken.
So we do not decend into B nor its ancestor.
The logic is the same if the history is linear.
A--B--C--D--E--F
If B is good and F is bad, and if you check D and find out that D is still
good, then we can ignore everything behind D, and that is what lets us
limit the search space and suspect only E or F. This is exactly the same
"Everything that leads to a good one is good" principle upon which
bisection works.
^ permalink raw reply
* Re: [PATCH 1/7] Documentation: fix links to tutorials and other new manual pages
From: Christian Couder @ 2008-06-30 23:30 UTC (permalink / raw)
To: Jonathan Nieder; +Cc: git, Junio C Hamano, Jon Loeliger
In-Reply-To: <Pine.GSO.4.62.0806301657320.7190@harper.uchicago.edu>
Le mardi 1 juillet 2008, Jonathan Nieder a écrit :
> With the conversion of HTML documentation to man pages
>
> tutorial.html -> gittutorial (7)
> tutorial-2.html -> gittutorial-2 (7)
> cvs-migration.html -> gitcvs-migration (7)
> diffcore.html -> gitdiffcore (7)
> repository-layout.html -> gitrepository-layout (5)
> hooks.html -> githooks (5)
> glossary.html -> gitglossary (7)
> core-tutorial.html -> gitcore-tutorial (7)
>
> and the automatic update of references to these pages,
> a little debris was left behind. We clear it away.
I left them behind because I thought they might be usefull and in fact in
some places you also leave them like this:
> -to start with linkgit:gittutorial[7][a tutorial introduction to git]
> +to start with "A Tutorial Introduction to GIT" (linkgit:gittutorial[7])
I understand that some people might prefer your version though.
Thanks,
Christian.
^ permalink raw reply
* Re: [PATCH] bisect: error out when given any good rev that is not an ancestor of the bad rev
From: Christian Couder @ 2008-06-30 23:16 UTC (permalink / raw)
To: Junio C Hamano
Cc: Johannes Schindelin, Michael Haggerty, Jeff King, git,
Linus Torvalds
In-Reply-To: <7v3amuv8yg.fsf@gitster.siamese.dyndns.org>
Le mardi 1 juillet 2008, Junio C Hamano a écrit :
> Christian Couder <chriscool@tuxfamily.org> writes:
> > Before this patch "git bisect" doesn't really work when it is given
> > some good revs that are siblings of the bad rev.
> >
> > For example if there is the following history:
> >
> > A-B-C-D
> > \E-F
> >
> > and we launch "git bisect start D F" then only C and D will be
> > considered as possible first bad commit. This is wrong because A, B and
> > E may be bad too if the bug exists everywhere except in F that fixes
> > it.
>
> Please don't.
>
> bisect is about finding a single regression by partitioning the graph
> into older good section and newer bad section with a *single* "first bad
> commit".
>
> For your "this could also be possible" scenario is already outside the
> realm. You are assuming A, B and F is good, and D is bad.
I am assuming the first bad commit in the graph is A and it is fixed by F.
> But if E is
> bad, then that breakage cannot possibly affect the transition between B
> and D from good to bad (E cannot break D), so C must *also* be bad.
Yes, I assume C is also bad.
> ... which means you are dealing with *two* breakages. That's outside
> what bisect deals with.
Sorry, I don't understand why I am assuming 2 breakages.
> And this does not need to have forked development. If the graph were
> like this:
>
> A-B-C-D-E-F
>
> and if F is bad and B is good, with your logic, after checking that D is
> already bad, we cannot discount E --- after somehow fixing D, we _might_
> also be introducing another breakage with E. You cannot even check for
> that anyway, but the logic is the same.
I don't think the logic is the same because in your case git bisect will
report one first bad commit anyway even if this bad commit has been fixed
latter.
In my case above, git bisect currently considers C and D as the only
possible bad commits because when it is told that F is good, it assumes
that all the ancestors or F are good too. And this means that B is
considered good so C will be found as the first bad commit. I think this
can be really misleading because there is no good->bad transition between B
and C.
Regards,
Christian.
^ permalink raw reply
* Re: What's cooking in git.git (topics)
From: Johannes Schindelin @ 2008-06-30 23:09 UTC (permalink / raw)
To: Andrew Morton; +Cc: Junio C Hamano, krh, git, sfr, pasky
In-Reply-To: <20080630155147.009a24b7.akpm@linux-foundation.org>
Hi,
On Mon, 30 Jun 2008, Andrew Morton wrote:
> - It's bad to have the same info in two places, and to have to
> support two different ways of doing the same thing for ever.
It is actually worse: we have three places.
> For me the biggest part of migrating would be working out what on
> earth the format of the new files is. Maybe it's documented
> somewhere undiscoverable, dunno.
The easiest way is to
git config remote.$NICKNAME.url $URL
where you said
echo $URL > .git/branches/$NICKNAME
Actually, you might even like this command better:
git remote add $NICKNAME $URL
Many ways to go to Rome,
Dscho
^ permalink raw reply
* Re: [PATCH 13/13] Build in merge
From: Miklos Vajna @ 2008-06-30 22:58 UTC (permalink / raw)
To: Olivier Marin; +Cc: Junio C Hamano, git, Johannes Schindelin
In-Reply-To: <486961C7.2090603@free.fr>
[-- Attachment #1: Type: text/plain, Size: 1474 bytes --]
On Tue, Jul 01, 2008 at 12:44:23AM +0200, Olivier Marin <dkr+ml.git@free.fr> wrote:
> > + if (new_head && show_diffstat) {
> > + struct diff_options opts;
> > + diff_setup(&opts);
> > + opts.output_format |=
> > + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
> > + opts.detect_rename = DIFF_DETECT_RENAME;
> > + if (diff_use_color_default > 0)
> > + DIFF_OPT_SET(&opts, COLOR_DIFF);
>
> Ah, I missed that. You should call diff_setup_done(), to finish diff_setup()
> and have a recursive diffstat.
>
> For example:
>
> $ git checkout -b test c0f5c69
> $ git merge 2dce956
> Updating c0f5c69..2dce956
> Fast forward
> help.c | 22 ++++++++++++++--------
> 1 files changed, 14 insertions(+), 8 deletions(-)
>
> is wrong. Correct diffstat is:
>
> $ git diff --stat c0f5c69..2dce956
> Documentation/gitcli.txt | 37 +++++++++++++++++++++++++++++++++----
> Documentation/gittutorial-2.txt | 10 +++++-----
> help.c | 22 ++++++++++++++--------
> t/test-lib.sh | 2 +-
> 4 files changed, 53 insertions(+), 18 deletions(-)
Fixed in my working branch.
> > + git_config(git_merge_config, NULL);
> > +
> > + /* for color.diff and diff.color */
> > + git_config(git_diff_ui_config, NULL);
>
> Also, what about doing "return git_diff_ui_config(k, v, cb)" at the end of
> git_merge_config() instead, to avoid parsing config files twice.
Sure, changed.
Thanks! :)
[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* Re: [PATCH] Add another fast-import example, this time for .zip files
From: Johannes Schindelin @ 2008-06-30 22:53 UTC (permalink / raw)
To: spearce, git
In-Reply-To: <alpine.DEB.1.00.0806301948130.9925@racer>
Hi,
On Mon, 30 Jun 2008, Johannes Schindelin wrote:
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>
> I needed that today.
... and I needed that on top, which I forgot to commit, and therefore
never sent in my first mail on this subject:
-- snipsnap --
diff --git a/contrib/fast-import/import-zip.py b/contrib/fast-import/import-zips.py
similarity index 85%
rename from contrib/fast-import/import-zip.py
rename to contrib/fast-import/import-zips.py
index 4200a66..fcf5823 100755
--- a/contrib/fast-import/import-zip.py
+++ b/contrib/fast-import/import-zips.py
@@ -40,6 +40,11 @@ for zipfile in argv[1:]:
if commit_time < info.date_time:
commit_time = info.date_time
+ if common_prefix == None:
+ common_prefix = name[:name.rfind('/') + 1]
+ else:
+ while not name.startswith(common_prefix):
+ common_prefix = name[:name.rfind('/') + 1]
mark[name] = ':' + str(next_mark)
next_mark += 1
@@ -56,7 +61,8 @@ for zipfile in argv[1:]:
'', 'deleteall'))
for name in mark.keys():
- fast_import.write('M 100644 ' + mark[name] + ' ' + name + "\n")
+ fast_import.write('M 100644 ' + mark[name] + ' ' +
+ name[len(common_prefix):] + "\n")
printlines(('', 'tag ' + path.basename(zipfile), \
'from ' + branch_ref, 'tagger ' + committer, \
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox