* diff/Checking out by date specification
From: Toby Corkindale @ 2008-06-30 2:24 UTC (permalink / raw)
To: git
Hi,
`man git-rev-parse` seems to indicate that one can look at the state of
the repository by date (absolute or relative), and use this for diff or
checkout, etc.
It includes examples such as "{yesterday}" and "{1979-02-26 18:30:00}".
I'm struggling to get this to work in real life with cloned
repositories, as I receive an error that the log only goes back to
whenever I cloned the repository.
Is there a way to make this operate upon the remote log?
Eg:
$ git clone git://git.kernel.org/pub/scm/git/git.git
$ cd git
$ git diff "@{yesterday}"
warning: Log for '' only goes back to Mon, 30 Jun 2008 12:22:52 +1000.
fatal: bad object @{yesterday}
Cheers,
Toby
--
Toby Corkindale
Software developer
w: www.rea-group.com
REA Group refers to realestate.com.au Ltd (ASX:REA)
Warning - This e-mail transmission may contain confidential information.
If you have received this transmission in error, please notify us
immediately on (61 3) 9897 1121 or by reply email to the sender. You
must destroy the e-mail immediately and not use, copy, distribute or
disclose the contents.
^ permalink raw reply
* Re: [RFC/PATCH] Documentation: Don't assume git-sh-setup and git-parse-remote are in the PATH
From: Jonathan Nieder @ 2008-06-30 2:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Alex Riesen, Jeff King
In-Reply-To: <7vbq1kawje.fsf@gitster.siamese.dyndns.org>
Junio C Hamano wrote:
> jrnieder@uchicago.edu writes:
>
>> [patch to use . "$(git --exec-path)/git-{parse-remote,sh-setup}"
>> instead of assuming they are in PATH]
>>
>> Junio: I stole the commit message from you. I hope you don't mind.
>
> I don't, but reading the script again, I suspect it is not clear enough
> that the user is also responsible for setting up GIT_DIR appropriately
> before using it, perhaps by sourcing git-sh-setup. We probably would want
> to add it in a separate patch.
Doesn't git-parse-remote.sh begin
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || :;
? And I don't imagine we want people to source git-sh-setup before
sourcing git-sh-setup...
Confused,
Jonathan
^ permalink raw reply
* Re: [PATCH 1/2] clone: respect the settings in $HOME/.gitconfig and /etc/gitconfig
From: Daniel Barkalow @ 2008-06-30 2:21 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Johannes Schindelin, Pieter de Bie, Git Mailinglist
In-Reply-To: <7vabh390sc.fsf@gitster.siamese.dyndns.org>
On Sun, 29 Jun 2008, Junio C Hamano wrote:
> Daniel Barkalow <barkalow@iabervon.org> writes:
>
> > ... In any case, I don't think "git clone" is at
> > all special with respect to GIT_CONFIG.
>
> I think "git init" and "git clone" are very special with respect to
> GIT_CONFIG.
>
> * When "init" is run to create a new repository and initialize it, the
> user would want the initial set of configuration populated in the
> configuration file _for that repository_. There however may be some
> customization that affects the way how "init" operates, which might be
> taken from $HOME/.gitconfig. The meaning of GIT_CONFIG can get fuzzy
> here. Possibilities:
>
> (1) Instead of $HOME/.gitconfig (or /etc/gitconfig), the user might
> want such customizations to be read from the file specified by
> GIT_CONFIG. But the user wants to make the resulting new
> repository usable without any custom GIT_CONFIG set (i.e. its
> $GIT_DIR/config will be the place the configuration is written).
>
> (2) The user may want to create a new repository that cannot be used
> with GIT_CONFIG (for some strange reason), i.e. no $GIT_DIR/config
> for the repository, and GIT_CONFIG is used to specify where that
> separate configuration file for the new repository is. The way
> "init" operates does not come from that configuration file that is
> to be created but from elsewhere.
>
> * When "clone" is run, the same confusion as initializing "init" applies.
> In addition, custom GIT_CONFIG to read customizations for behaviour of
> "init" and "fetch" that is done internally by "clone" would play larger
> role.
>
> * When "init" is run to reinitialize an existing repository, it is not
> special in any way with respect to GIT_CONFIG, compared to other
> commands. The GIT_CONFIG names the configuration for that existing
> repository, so we read from it and write to it.
>
> I personally think the case (2) is a very narrow special case that I do
> not think of any sane reason to even wanting to do so. IOW, "you _could_
> interpret the presense of GIT_CONFIG that the user may want to do so, but
> why?" (1) is also probably not very sensible but it makes more sense.
Actually, (1) has never worked for either clone or init. Init always
obeyed GIT_CONFIG as for where it put the output (in fact git-clone.sh
used GIT_CONFIG to cause git-init to write to the created repo, IIRC).
Clone always replaced GIT_CONFIG with the destination location before it
read any configuration, so it wouldn't see configuration that was in the
user's GIT_CONFIG (or in any pre-existing config file at all).
Historically, these commands don't support (1) and aren't consistant with
each other.
I think it would make sense if there were some way to provide custom
configuration to a particular invocation of "git clone", but GIT_CONFIG as
currently handled by config.c can't do it.
> I think Dscho's original patch would support the semantics (1) and it is
> probably much saner than (2) which is your version does (if I am reading
> the two patches correctly).
My patch makes "git clone" do what "git init; git remote; git fetch" do
with respect to GIT_CONFIG. I don't think this behavior is really useful
for any of those programs or for the combination, but at least having them
match would be consistant. I think the sensible thing would be for
config.c to always write to git_path("config") (or a variable used by
"git config"), and read: git_path("config"), $GIT_CONFIG, ~/.gitconfig,
/etc/gitconfig (in order of decreasing precedence); and further have
builtin-config.c set the "file to write to" variable based on $GIT_CONFIG
or "--file", "--global", or "--system".
But I don't really understand what the purpose of the feature was in the
first place, aside from the narrow case of being able to read and write
files that use the config file format with "git config".
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Re: [PATCH 1/2] clone: respect the settings in $HOME/.gitconfig and /etc/gitconfig
From: Daniel Barkalow @ 2008-06-30 1:54 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, Pieter de Bie, Git Mailinglist
In-Reply-To: <alpine.DEB.1.00.0806300134200.9925@racer>
On Mon, 30 Jun 2008, Johannes Schindelin wrote:
> Hi,
>
> On Sun, 29 Jun 2008, Daniel Barkalow wrote:
>
> > On Sun, 29 Jun 2008, Johannes Schindelin wrote:
> >
> > > On Sun, 29 Jun 2008, Daniel Barkalow wrote:
> > >
> > > > Did we even make a commitment on whether:
> > > >
> > > > GIT_CONFIG=foo git clone bar
> > > >
> > > > must ignore the environment variable, or simply doesn't necessarily
> > > > obey it?
> > >
> > > I'd rather strongly argue that no matter what is the answer to this
> > > question, we _HAVE TO_ unsetenv() GIT_CONFIG at some stage, otherwise
> > > no .git/config will be written.
> >
> > Why should .git/config get written?
>
> Because the user asked for a clone, where she reasonably expects a git
> repository with all the [core] and the initial [remote "origin"] settings
> to be written as it should be, _even if_ setting the config to somewhere
> else? Hmm?
But those should be written to the location of the config file, where
subsequent commands will find them, which is $GIT_CONFIG if it's set and
git commands in general use it. I mean:
$ export GIT_CONFIG=/home/barkalow/something
$ git clone git://git.kernel.org/pub/scm/git/git.git
$ cd git
$ git fetch
fatal: 'origin': unable to chdir or not a git archive
fatal: The remote end hung up unexpectedly
(because "git clone" currently ignores GIT_CONFIG, but "git fetch"
doesn't, so it can't find the initial [remote "origin"] settings).
> IMITCNVHO it would be a serious mistake to write the config somewhere else
> with "clone".
>
> If that still does not convince you, "git init" also writes to
> ".git/config" regardless of the user's (possibly bogus) GIT_CONFIG.
No, "git init" has always written to GIT_CONFIG. In fact, git-clone.sh
used to depend on it writing to GIT_CONFIG, which is how it caused the
config file to be written into the new clone.
> It is just such a basic thing that you must _not_ use GIT_CONFIG for
> writing with git clone or git init.
Surely, then, you must not use GIT_CONFIG when reading the options that
git clone writes? But I think this reduces to "you must not use GIT_CONFIG
when using a repository", which pretty much leaves "git config". And I
think it's only ever *useful* for "git config" anyway.
It doesn't make much sense to ensure that "git clone" works if you have
GIT_CONFIG set when nothing else works in that situation. I still don't
know what setting it is good for (and the commit that introduced it
explained what it did, but not why), but I think we should be consistant
about whether or not it affects where git expects configuration to be.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* [PATCH 13/13] Build in merge
From: Miklos Vajna @ 2008-06-30 1:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <20080630013612.GY2058@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 9824cf3..8611c7b
Individual commits:
git log 8611c7b
Makefile | 2 +-
builtin-merge.c | 1173 +++++++++++++++++++++++++
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, 1177 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 b003e3e..3d8c3d2 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
@@ -511,6 +510,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..187038c
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1173 @@
+/*
+ * 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];
+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 dropsave(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_STASH"));
+}
+
+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);
+}
+
+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 */
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (access(git_path("MERGE_STASH"), R_OK) < 0)
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ if (strbuf_read_file(&sb, git_path("MERGE_STASH"), 0) < 0)
+ die("could not read MERGE_STASH: %s", strerror(errno));
+ args[2] = sb.buf;
+
+ /*
+ * 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);
+ dropsave();
+}
+
+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);
+ 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;
+ char *ptr;
+ int len = 0;
+ char *ref;
+
+ 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);
+ 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;
+ }
+ /* 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;
+ }
+ 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, 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;
+ }
+ }
+
+ 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 0;
+}
+
+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;
+ 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 int commit_tree_trivial(const char *msg, unsigned const char *tree,
+ struct commit_list *parents, unsigned char *ret)
+{
+ struct commit_list *i;
+ struct strbuf buf;
+ int encoding_is_utf8;
+
+ /* Not having i18n.commitencoding is the same as having utf-8 */
+ encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+ strbuf_init(&buf, 8192); /* should avoid reallocs for the headers */
+ strbuf_addf(&buf, "tree %s\n", sha1_to_hex(tree));
+
+ for (i = parents; i; i = i->next)
+ strbuf_addf(&buf, "parent %s\n",
+ sha1_to_hex(i->item->object.sha1));
+
+ /* Person/date information */
+ strbuf_addf(&buf, "author %s\n",
+ git_author_info(IDENT_ERROR_ON_NO_NAME));
+ strbuf_addf(&buf, "committer %s\n",
+ git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ if (!encoding_is_utf8)
+ strbuf_addf(&buf, "encoding %s\n", git_commit_encoding);
+ strbuf_addch(&buf, '\n');
+
+ /* And add the comment */
+ strbuf_addstr(&buf, msg);
+
+ write_sha1_file(buf.buf, buf.len, commit_type, ret);
+ strbuf_release(&buf);
+ return *ret;
+}
+
+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_trivial(merge_msg.buf, result_tree, &parent,
+ result_commit);
+ finish(result_commit, "In-index merge");
+ dropsave();
+ 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_trivial(merge_msg.buf, result_tree, parents,
+ result_commit);
+ free_commit_list(parents);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ dropsave();
+ 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;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct commit *second_token = NULL;
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i, single_strategy;
+ 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.diff and diff.color */
+ git_config(git_diff_ui_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 (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]);
+ }
+
+ if (!have_message && second_token &&
+ !hashcmp(second_token->object.sha1, head)) {
+ 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 ((unsigned int)use_strategies.items[i].util &
+ NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if ((unsigned int)use_strategies.items[i].util & 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 0;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 0;
+
+ finish(o->sha1, msg.buf);
+ dropsave();
+ 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();
+ single_strategy = 0;
+ } else {
+ unlink(git_path("MERGE_STASH"));
+ single_strategy = 1;
+ }
+
+ for (i = 0; i < use_strategies.nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (!single_strategy)
+ 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 = 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();
+
+ 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 2b01fea..8bf5280 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,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
* Re: [PATCH 13/13] Build in merge
From: Miklos Vajna @ 2008-06-30 1:36 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin
In-Reply-To: <7vprq0fzum.fsf@gitster.siamese.dyndns.org>
[-- Attachment #1: Type: text/plain, Size: 11348 bytes --]
On Sun, Jun 29, 2008 at 12:46:09AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > +/* 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;
> > + 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)
> > + return;
>
> Hmm. This is a faithful translation of scripted version, but I wonder
> what should happen when we got a non-commit here...
Hm, I think we do not consider that case normal, at least git fsck
(since commit 6232f62) checks for it.
I replaced the silent return with a die().
> > +
> > + strbuf_init(&buf, 0);
> > + strbuf_addstr(&buf, "refs/heads/");
> > + strbuf_addstr(&buf, remote);
> > + get_sha1(buf.buf, branch_head);
>
> This does not correspond to the computation of $bh in the scripted version
> that makes sure "remote" is actually a bare name of branch, e.g. "master",
> without any adornment like "master~5^3~8. Your code would succeed and
> leave the same object name in branch_head[] as remote_head->sha1, wouldn't
> it?
I replaced it with a dwim_ref() call, to achieve the same behaviour.
> > + 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>^ */
>
> The scripted version did not handle <name>^, so this is an extension.
> Don't you want also handle <name>^^^ if we are extending it?
I did so, now it accepts <name>^, <name>^^, <name>^^^, etc.
> > + ptr = strrchr(remote, '^');
> > + if (ptr && ptr[1] == '\0')
> > + len = ptr-remote;
> > + 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, remote);
> > + strbuf_setlen(&truname, len);
> > + if (!get_sha1(truname.buf, buf_sha)) {
>
> Again, isn't this wrong? You are not making sure truname is the name of
> existing local branch. HEAD@{7}~23 will pass get_sha1() but you are not
> merging an early part of HEAD@{7} branch.
Now I'm using dwim_ref() here as well.
> > + strbuf_addf(msg,
> > + "%s\t\tbranch '%s' (early part) of .\n",
> > + sha1_to_hex(remote_head->sha1), truname.buf);
> > + return;
> > + }
> > + }
>
> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > +{
>
> This is an ultra-huge function. I wonder if it can further split up to
> make it easier to maintain.
Yes, just like the scripted version. Hm no, that was even longer. (I
mean I already introduced a lot of static functions to make the C
equivalent of the "main" part of git-merge.sh shorter.) OK, it was 474
lines long today, but now I introduced 3 new static functions and that
made it "only" 410 lines long.
> > + /*
> > + * 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);
> > + strbuf_init(&head_arg, 0);
> > + if (argc > 1)
> > + second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);
>
> If the second token was a string that could resolve to an object name that
> does not peel to commit (say "merge -m 'HEAD^{tree}' other"), you will get
> a complaint fro mpeel-to-type "I expected a commit but you gave something
> else". You (or more likely Dscho) might have said "that won't matter in
> practice", but I think you should really do get_sha1() followed by
> lookup_commit_reference_gently() here to avoid the errors.
Fixed.
>
> > + head_invalid = get_sha1("HEAD", head);
>
> You've already done this earlier with resolve_ref() haven't you?
Ah yes. I had the global 'head' and the local 'sha1' variable for the
same purpose, now I got rid of the local 'sha1' variable in cmd_merge()
so resolve_ref() writes now to the 'head' variable and then this line is
not necessary, as I can write head_invalid right after resolve_ref().
>
> > + if (!have_message && second_token &&
> > + !hashcmp(second_token->sha1, head)) {
>
> Isn't this wrong if head_invalid is true?
if head_invalid is true, then 'head' will be filled with 0s, hashcmp()
will never return 0 so the condition will be never true. That's what the
shell version:
head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null)
does as well.
> > + strbuf_addstr(&merge_msg, argv[0]);
> > + strbuf_addstr(&head_arg, argv[1]);
> > + argv += 2;
> > + argc -= 2;
>
> I do not think there is any point using strbuf for head_arg. Shouldn't it
> simply be a "const char *"?
Now it is.
> > + 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;
>
> Makes one wonder reset_hard() (aka "read-tree --reset -u HEAD") ever fail
> and return here (iow, without calling die()). The answer is luckily no
> in this case, but it is somewhat unnerving to reviewers.
Actually reset_hard does not return if an error occures:
if (unpack_trees(1, &t, &opts))
exit(128); /* We've already reported the error, finish dying */
That's exactly how we already have it in builtin-commit.
>
> > + } else {
> > + /* We are invoked directly as the first-class UI. */
> > + strbuf_addstr(&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.
> > + */
> > + struct strbuf msg;
>
> Decl-after-statement.
You already fixed this. :-)
> > + for (i = 0; i < use_strategies.nr; i++) {
> > + if ((unsigned int)use_strategies.items[i].util &
> > + NO_FAST_FORWARD)
> > + allow_fast_forward = 0;
> > + if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
> > + allow_trivial = 0;
>
> Can we abstract out these ugly casts? Any code that use path_list to
> store anything but list of paths (i.e. some value keyed with string) tends
> to have this readability issue.
If you don't cast, you can't use the & operator. If I change the
path_list_item's util to be an unsigned number then I break fast-export.
I think if we _really_ want to get rid of those casts, we could have
something like:
diff --git a/path-list.h b/path-list.h
index ca2cbba..1f57e81 100644
--- a/path-list.h
+++ b/path-list.h
@@ -4,6 +4,7 @@
struct path_list_item {
char *path;
void *util;
+ unsigned int flags;
};
struct path_list
{
But I'm not sure if that's a good idea. Also, fast-export will still
have casts after such a change.
> > + if (!common)
> > + ; /* No common ancestors found. We need a real merge. */
> > + else if (!remoteheads->next &&
> > + !hashcmp(common->item->object.sha1,
> > + remoteheads->item->object.sha1)) {
>
> Wouldn't the latter be "common->item == remoteheads->item" simply?
Right, changed.
> You do not have the check to make sure there is only one common ancestor
> (scripted version compares $common and $1 textually to achieve this), and
> checking only the first one of them. Is this correct?
Yes. I changed it to:
else if (!remoteheads->next && !common->next &&
common->item == remoteheads->item) {
And now I have the check.
>
> > + /*
> > + * If head can reach all the remote heads then we are up
> > + * to date.
> > + */
>
> The comment is wrong --- you are doing "... but first the most common case
> of merging one remote" here.
I changed it to match the shell version:
/*
* 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 &&
> > + !hashcmp(common->item->object.sha1, head)) {
> > + /* Again the most common case of merging one remote. */
>
> Here again you are not checking there is only one common, and checking
> only the first one of them.
Changed to:
} else if (allow_fast_forward && !remoteheads->next &&
!common->next &&
!hashcmp(common->item->object.sha1, head)) {
Which should add the proper check.
> > + if (merge_one_remote(head, remoteheads->item->object.sha1))
> > + return 0;
>
> Isn't "merge_one_remote()" just a "git checkout" after fast-forward? The
> function feels misnamed.
Thanks. I'm terribly bad at naming. Renamed to checkout_fast_forward().
> > + finish(o->sha1, msg.buf);
> > + dropsave();
> > + return 0;
> > + } else if (!remoteheads->next && common->next)
> > + ;
>
> Here you are checking common->next but earlier if/elseif chain didn't so
> it is too late.
Now, that I do, I think the condition is OK.
> > + else if (!remoteheads->next && option_commit) {
> > + /*
> > + * We are not doing octopus, not fast forward, and have
> > + * only one common.
>
> Here again you did not check "have only one common" did you?
Actually the shell version did not check here, either, but yes, I would
have to. Now I do.
> > + printf("Trying really trivial in-index merge...\n");
> > + if (!read_tree_trivial(common->item->object.sha1,
> > + head, remoteheads->item->object.sha1)) {
> > + 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_trivial(merge_msg.buf,
> > + result_tree, &parent,
> > + result_commit);
> > + finish(result_commit, "In-index merge");
> > + dropsave();
> > + return 0;
> > + }
> > + printf("Nope.\n");
> > + }
>
> There weren't any good way to squelch error messages selectively from the
> trivial one in the scripted version and that is the only reason we
> surround read-tree with "Trying..." and "Wonderful/Nope.". Literal
> translation to make sure you get identical output in the first round of
> this series is good, but after the code stabilizes, we may want to squelch
> these messages. Something to keep in mind but not now.
OK.
[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply related
* Re: [PATCH] Fix t4017-diff-retval for white-space from wc
From: Brian Gernhardt @ 2008-06-30 1:29 UTC (permalink / raw)
To: Brian Gernhardt; +Cc: Git List
In-Reply-To: <1214789205-43490-1-git-send-email-benji@silverinsanity.com>
Sorry about duplicate. Forgot to remove sendemail.to when testing git-
send-email. (Finally tracked down my problems to a bug in
Term::ReadLine::Perl when the prompt was longer than the terminal
width.)
~~ Brian
On Jun 29, 2008, at 9:26 PM, Brian Gernhardt wrote:
> Signed-off-by: Brian Gernhardt <benji@silverinsanity.com>
> ---
> t/t4017-diff-retval.sh | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
> index d748d45..60dd201 100755
> --- a/t/t4017-diff-retval.sh
> +++ b/t/t4017-diff-retval.sh
> @@ -123,7 +123,7 @@ test_expect_success 'check detects leftover
> conflict markers' '
> git --no-pager diff --cached --check >test.out
> test $? = 2
> ) &&
> - test "$(grep "conflict marker" test.out | wc -l)" = 3 &&
> + test 3 = $(grep "conflict marker" test.out | wc -l) &&
> git reset --hard
> '
>
> --
> 1.5.6.105.g6f4b
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] Fix t4017-diff-retval for white-space from wc
From: Brian Gernhardt @ 2008-06-30 1:26 UTC (permalink / raw)
To: Git List; +Cc: Brian Gernhardt
Signed-off-by: Brian Gernhardt <benji@silverinsanity.com>
---
t/t4017-diff-retval.sh | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index d748d45..60dd201 100755
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
@@ -123,7 +123,7 @@ test_expect_success 'check detects leftover conflict markers' '
git --no-pager diff --cached --check >test.out
test $? = 2
) &&
- test "$(grep "conflict marker" test.out | wc -l)" = 3 &&
+ test 3 = $(grep "conflict marker" test.out | wc -l) &&
git reset --hard
'
--
1.5.6.105.g6f4b
^ permalink raw reply related
* [JGIT PATCH] Paper bag fix IndexPack thin pack completion support
From: Shawn O. Pearce @ 2008-06-30 1:25 UTC (permalink / raw)
To: Robin Rosenberg; +Cc: git
We must write the whole object at the end of the file, not in the
middle of the file on top of some delta (or other) whole object.
Writing in the middle of the file causes subtle corruption as we
cannot unpack a delta.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
This is a serious data corruption in IndexPack. It was introduced
by me in "Compute packed object entry CRC32 data during IndexPack",
which is presently in `pu` as 4b8e1c0c. It would be best if we
squash this into the commit.
.../src/org/spearce/jgit/transport/IndexPack.java | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
index b35cec3..24a0577 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java
@@ -407,10 +407,10 @@ public class IndexPack {
final PackedObjectInfo oe;
crc.reset();
+ packOut.seek(end);
writeWhole(def, typeCode, data);
oe = new PackedObjectInfo(end, (int) crc.getValue(), baseId);
entries[entryCount++] = oe;
- packOut.seek(end);
end = packOut.getFilePointer();
resolveChildDeltas(oe.getOffset(), typeCode, data, oe);
--
1.5.6.74.g8a5e
--
Shawn.
^ permalink raw reply related
* Re: [PATCH 1/2] clone: respect the settings in $HOME/.gitconfig and /etc/gitconfig
From: Junio C Hamano @ 2008-06-30 1:20 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: Johannes Schindelin, Pieter de Bie, Git Mailinglist
In-Reply-To: <alpine.LNX.1.00.0806291821520.19665@iabervon.org>
Daniel Barkalow <barkalow@iabervon.org> writes:
> ... In any case, I don't think "git clone" is at
> all special with respect to GIT_CONFIG.
I think "git init" and "git clone" are very special with respect to
GIT_CONFIG.
* When "init" is run to create a new repository and initialize it, the
user would want the initial set of configuration populated in the
configuration file _for that repository_. There however may be some
customization that affects the way how "init" operates, which might be
taken from $HOME/.gitconfig. The meaning of GIT_CONFIG can get fuzzy
here. Possibilities:
(1) Instead of $HOME/.gitconfig (or /etc/gitconfig), the user might
want such customizations to be read from the file specified by
GIT_CONFIG. But the user wants to make the resulting new
repository usable without any custom GIT_CONFIG set (i.e. its
$GIT_DIR/config will be the place the configuration is written).
(2) The user may want to create a new repository that cannot be used
with GIT_CONFIG (for some strange reason), i.e. no $GIT_DIR/config
for the repository, and GIT_CONFIG is used to specify where that
separate configuration file for the new repository is. The way
"init" operates does not come from that configuration file that is
to be created but from elsewhere.
* When "clone" is run, the same confusion as initializing "init" applies.
In addition, custom GIT_CONFIG to read customizations for behaviour of
"init" and "fetch" that is done internally by "clone" would play larger
role.
* When "init" is run to reinitialize an existing repository, it is not
special in any way with respect to GIT_CONFIG, compared to other
commands. The GIT_CONFIG names the configuration for that existing
repository, so we read from it and write to it.
I personally think the case (2) is a very narrow special case that I do
not think of any sane reason to even wanting to do so. IOW, "you _could_
interpret the presense of GIT_CONFIG that the user may want to do so, but
why?" (1) is also probably not very sensible but it makes more sense.
I think Dscho's original patch would support the semantics (1) and it is
probably much saner than (2) which is your version does (if I am reading
the two patches correctly).
^ permalink raw reply
* Re: perl t9700 failures?
From: Linus Torvalds @ 2008-06-30 1:17 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Git Mailing List, Lea Wiemann
In-Reply-To: <7v4p7c9erj.fsf@gitster.siamese.dyndns.org>
On Sun, 29 Jun 2008, Junio C Hamano wrote:
>
> Perlhaps something liek this should be sufficient.
Works-for-me(tm)
Linus
^ permalink raw reply
* [StGit PATCH] Write to a stack log when stack is modified
From: Karl Hasselström @ 2008-06-30 1:02 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git
Create a log branch (called <branchname>.stgit) for each StGit branch,
and write to it whenever the stack is modified.
Commands using the new infrastructure write to the log when they
commit a transaction. Commands using the old infrastructure get a log
entry write written for them when they exit, unless they explicitly
ask for this not to happen.
The only thing you can do with this log at the moment is look at it.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
Now rewritten to use a log format that should be more efficient (see
the docs at the top of lib/log.py). There is still a simplified log,
but it's not expensive since we recompute only the parts that have
changed since the last log entry.
A few of the other patches in the series had to be fixed up as well,
but mostly trivially since the interface between the log stuff and the
rest did not change; I won't spam the list with them. I'll be pushing
out the updated series to
git://repo.or.cz/stgit/kha.git experimental
in a few minutes.
stgit/commands/branch.py | 19 ++-
stgit/commands/common.py | 8 +
stgit/commands/diff.py | 2
stgit/commands/files.py | 2
stgit/commands/id.py | 2
stgit/commands/log.py | 2
stgit/commands/mail.py | 2
stgit/commands/patches.py | 2
stgit/commands/show.py | 2
stgit/commands/status.py | 3
stgit/lib/git.py | 3
stgit/lib/log.py | 340 +++++++++++++++++++++++++++++++++++++++++++++
stgit/lib/stack.py | 9 +
stgit/lib/transaction.py | 3
stgit/main.py | 2
15 files changed, 384 insertions(+), 17 deletions(-)
create mode 100644 stgit/lib/log.py
diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index 50684bb..edbb01c 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -25,7 +25,7 @@ from stgit.commands.common import *
from stgit.utils import *
from stgit.out import *
from stgit import stack, git, basedir
-
+from stgit.lib import log
help = 'manage patch stacks'
usage = """%prog [options] branch-name [commit-id]
@@ -40,7 +40,7 @@ When displaying the branches, the names can be prefixed with
If not given any options, switch to the named branch."""
-directory = DirectoryGotoToplevel()
+directory = DirectoryGotoToplevel(log = False)
options = [make_option('-c', '--create',
help = 'create a new development branch',
action = 'store_true'),
@@ -161,6 +161,7 @@ def func(parser, options, args):
parent_branch = parentbranch)
out.info('Branch "%s" created' % args[0])
+ log.compat_log_entry('branch --create')
return
elif options.clone:
@@ -181,6 +182,8 @@ def func(parser, options, args):
crt_series.clone(clone)
out.done()
+ log.copy_log(log.default_repo(), crt_series.get_name(), clone,
+ 'branch --clone')
return
elif options.delete:
@@ -188,6 +191,7 @@ def func(parser, options, args):
if len(args) != 1:
parser.error('incorrect number of arguments')
__delete_branch(args[0], options.force)
+ log.delete_log(log.default_repo(), args[0])
return
elif options.list:
@@ -195,13 +199,16 @@ def func(parser, options, args):
if len(args) != 0:
parser.error('incorrect number of arguments')
- branches = git.get_heads()
- branches.sort()
+ branches = set(git.get_heads())
+ for br in set(branches):
+ m = re.match(r'^(.*)\.stgit$', br)
+ if m and m.group(1) in branches:
+ branches.remove(br)
if branches:
out.info('Available branches:')
max_len = max([len(i) for i in branches])
- for i in branches:
+ for i in sorted(branches):
__print_branch(i, max_len)
else:
out.info('No branches')
@@ -238,7 +245,7 @@ def func(parser, options, args):
stack.Series(args[0]).rename(args[1])
out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
-
+ log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename')
return
elif options.unprotect:
diff --git a/stgit/commands/common.py b/stgit/commands/common.py
index 029ec65..fd02398 100644
--- a/stgit/commands/common.py
+++ b/stgit/commands/common.py
@@ -28,6 +28,7 @@ from stgit.run import *
from stgit import stack, git, basedir
from stgit.config import config, file_extensions
from stgit.lib import stack as libstack
+from stgit.lib import log
# Command exception class
class CmdException(StgException):
@@ -478,8 +479,9 @@ class DirectoryException(StgException):
pass
class _Directory(object):
- def __init__(self, needs_current_series = True):
+ def __init__(self, needs_current_series = True, log = True):
self.needs_current_series = needs_current_series
+ self.log = log
@readonly_constant_property
def git_dir(self):
try:
@@ -512,6 +514,9 @@ class _Directory(object):
).output_one_line()]
def cd_to_topdir(self):
os.chdir(self.__topdir_path)
+ def write_log(self, msg):
+ if self.log:
+ log.compat_log_entry(msg)
class DirectoryAnywhere(_Directory):
def setup(self):
@@ -536,6 +541,7 @@ class DirectoryHasRepositoryLib(_Directory):
"""For commands that use the new infrastructure in stgit.lib.*."""
def __init__(self):
self.needs_current_series = False
+ self.log = False # stgit.lib.transaction handles logging
def setup(self):
# This will throw an exception if we don't have a repository.
self.repository = libstack.Repository.default()
diff --git a/stgit/commands/diff.py b/stgit/commands/diff.py
index fd6be34..8966642 100644
--- a/stgit/commands/diff.py
+++ b/stgit/commands/diff.py
@@ -42,7 +42,7 @@ rev = '([patch][//[bottom | top]]) | <tree-ish> | base'
If neither bottom nor top are given but a '//' is present, the command
shows the specified patch (defaulting to the current one)."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-r', '--range',
metavar = 'rev1[..[rev2]]', dest = 'revs',
help = 'show the diff between revisions'),
diff --git a/stgit/commands/files.py b/stgit/commands/files.py
index b43b12f..7844f8d 100644
--- a/stgit/commands/files.py
+++ b/stgit/commands/files.py
@@ -34,7 +34,7 @@ given patch. Note that this command doesn't show the files modified in
the working tree and not yet included in the patch by a 'refresh'
command. Use the 'diff' or 'status' commands for these files."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-s', '--stat',
help = 'show the diff stat',
action = 'store_true'),
diff --git a/stgit/commands/id.py b/stgit/commands/id.py
index 94b0229..5bb1ad2 100644
--- a/stgit/commands/id.py
+++ b/stgit/commands/id.py
@@ -33,7 +33,7 @@ the standard GIT id's like heads and tags, this command also accepts
'top' or 'bottom' are passed and <patch> is a valid patch name, 'top'
will be used by default."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-b', '--branch',
help = 'use BRANCH instead of the default one')]
diff --git a/stgit/commands/log.py b/stgit/commands/log.py
index 52d55a5..13e0baa 100644
--- a/stgit/commands/log.py
+++ b/stgit/commands/log.py
@@ -44,7 +44,7 @@ represent the changes to the entire base of the current
patch. Conflicts reset the patch content and a subsequent 'refresh'
will show the entire patch."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-b', '--branch',
help = 'use BRANCH instead of the default one'),
make_option('-p', '--patch',
diff --git a/stgit/commands/mail.py b/stgit/commands/mail.py
index b4d4e18..4027170 100644
--- a/stgit/commands/mail.py
+++ b/stgit/commands/mail.py
@@ -90,7 +90,7 @@ the following:
%(prefix)s - 'prefix ' string passed on the command line
%(shortdescr)s - the first line of the patch description"""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-a', '--all',
help = 'e-mail all the applied patches',
action = 'store_true'),
diff --git a/stgit/commands/patches.py b/stgit/commands/patches.py
index 140699d..c95c40f 100644
--- a/stgit/commands/patches.py
+++ b/stgit/commands/patches.py
@@ -33,7 +33,7 @@ it shows the patches affected by the local tree modifications. The
'--diff' option also lists the patch log and the diff for the given
files."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-d', '--diff',
help = 'show the diff for the given files',
action = 'store_true'),
diff --git a/stgit/commands/show.py b/stgit/commands/show.py
index b77a9c8..dd2a3a3 100644
--- a/stgit/commands/show.py
+++ b/stgit/commands/show.py
@@ -30,7 +30,7 @@ Show the commit log and the diff corresponding to the given
patches. The output is similar to that generated by the 'git show'
command."""
-directory = DirectoryHasRepository()
+directory = DirectoryHasRepository(log = False)
options = [make_option('-b', '--branch',
help = 'use BRANCH instead of the default one'),
make_option('-a', '--applied',
diff --git a/stgit/commands/status.py b/stgit/commands/status.py
index a5b2f88..4d13112 100644
--- a/stgit/commands/status.py
+++ b/stgit/commands/status.py
@@ -40,7 +40,7 @@ under revision control. The files are prefixed as follows:
A 'refresh' command clears the status of the modified, new and deleted
files."""
-directory = DirectoryHasRepository(needs_current_series = False)
+directory = DirectoryHasRepository(needs_current_series = False, log = False)
options = [make_option('-m', '--modified',
help = 'show modified files only',
action = 'store_true'),
@@ -106,6 +106,7 @@ def func(parser, options, args):
directory.cd_to_topdir()
if options.reset:
+ directory.log = True
if args:
conflicts = git.get_conflicts()
git.resolved([fn for fn in args if fn in conflicts])
diff --git a/stgit/lib/git.py b/stgit/lib/git.py
index a8881f4..35e03d2 100644
--- a/stgit/lib/git.py
+++ b/stgit/lib/git.py
@@ -139,6 +139,7 @@ class Person(Immutable, Repr):
assert isinstance(self.__date, Date) or self.__date in [None, NoValue]
name = property(lambda self: self.__name)
email = property(lambda self: self.__email)
+ name_email = property(lambda self: '%s <%s>' % (self.name, self.email))
date = property(lambda self: self.__date)
def set_name(self, name):
return type(self)(name = name, defaults = self)
@@ -147,7 +148,7 @@ class Person(Immutable, Repr):
def set_date(self, date):
return type(self)(date = date, defaults = self)
def __str__(self):
- return '%s <%s> %s' % (self.name, self.email, self.date)
+ return '%s %s' % (self.name_email, self.date)
@classmethod
def parse(cls, s):
m = re.match(r'^([^<]*)<([^>]*)>\s+(\d+\s+[+-]\d{4})$', s)
diff --git a/stgit/lib/log.py b/stgit/lib/log.py
new file mode 100644
index 0000000..1188b24
--- /dev/null
+++ b/stgit/lib/log.py
@@ -0,0 +1,340 @@
+r"""This module contains functions and classes for manipulating
+I{patch stack logs} (or just I{stack logs}).
+
+A stack log is a git branch. Each commit contains the complete state
+of the stack at the moment it was written; the most recent commit has
+the most recent state.
+
+For a branch C{I{foo}}, the stack log is stored in C{I{foo}.stgit}.
+
+Stack log format (full)
+=======================
+
+Commit message
+--------------
+
+First comes a message for human consumption; in most cases it is just
+a subject line: the stg subcommand name and possibly some important
+command-line flag.
+
+An exception to this is log commits for undo and redo. Their subject
+line is "C{undo I{n}}" and "C{redo I{n}}"; the positive integer I{n}
+says how many steps were undone or redone.
+
+Following the message is a newline, three dashes, and another newline.
+Then come, each on its own line,
+
+ - C{Version:} I{n}
+
+ where I{n} must be 0. (Future versions of StGit might change the
+ log format; when this is done, this version number will be
+ incremented.)
+
+ - C{Previous:} I{sha1 or C{None}}
+
+ The commit of the previous log entry, or C{None} if this is the
+ first entry.
+
+ - C{Head:} I{sha1}
+
+ The current branch head.
+
+ - C{Applied:}
+
+ Marks the start of the list of applied patches. They are listed in
+ order, each on its own line: first one or more spaces, then the
+ patch name, then a colon, then the patch's sha1.
+
+ - C{Unapplied:}
+
+ Same as C{Applied:}, but for the unapplied patches.
+
+Tree
+----
+
+The tree is not significant.
+
+Parents
+-------
+
+ - The first parent is the I{simplified log}, described below.
+
+ - The rest of the parents are just there to make sure that all the
+ commits referred to in the log entry -- patches, branch head,
+ previous log entry -- are ancestors of the log commit.
+
+Stack log format (simplified)
+=============================
+
+The simplified log contains no information not in the full log; its
+purpose is ease of visualization.
+
+Commit message
+--------------
+
+Same as for the full log, but just the message part; the three dashes
+and the log data are omitted.
+
+Tree
+----
+
+ - One blob, C{meta}, that contains the log data (but not the three
+ dashes) that was omitted from the commit message.
+
+ - One subtree, C{patches}, that contains one blob per patch::
+
+ Bottom: <sha1 of patch's bottom tree>
+ Top: <sha1 of patch's top tree>
+ Author: <author name and e-mail>
+ Date: <patch timestamp>
+
+ <commit message>
+
+ ---
+
+ <patch diff>
+
+Parents
+-------
+
+Just one parent: the simplified version of the previous log entry. (If
+there is no previous log entry, there are no parents.)"""
+
+from stgit.lib import git, stack
+from stgit import exception, utils
+from stgit.out import out
+import StringIO
+
+class LogException(exception.StgException):
+ pass
+
+class LogParseException(LogException):
+ pass
+
+def patch_file(repo, cd):
+ return repo.commit(git.BlobData(''.join(s + '\n' for s in [
+ 'Bottom: %s' % cd.parent.data.tree.sha1,
+ 'Top: %s' % cd.tree.sha1,
+ 'Author: %s' % cd.author.name_email,
+ 'Date: %s' % cd.author.date,
+ '',
+ cd.message,
+ '',
+ '---',
+ '',
+ repo.diff_tree(cd.parent.data.tree, cd.tree, ['-M']
+ ).strip()])))
+
+def log_ref(branch):
+ return 'refs/heads/%s.stgit' % branch
+
+class LogEntry(object):
+ __separator = '\n---\n'
+ __max_parents = 16
+ def __init__(self, repo, prev, head, applied, unapplied, patches, message):
+ self.__repo = repo
+ self.__prev = prev
+ self.head = head
+ self.applied = applied
+ self.unapplied = unapplied
+ self.patches = patches
+ self.message = message
+ @property
+ def prev(self):
+ if self.__prev != None and not isinstance(self.__prev, LogEntry):
+ self.__prev = self.__from_commit_full(self.__repo, self.__prev)
+ return self.__prev
+ @classmethod
+ def from_stack(cls, prev, stack, message):
+ return cls(
+ repo = stack.repository,
+ prev = prev,
+ head = stack.head,
+ applied = list(stack.patchorder.applied),
+ unapplied = list(stack.patchorder.unapplied),
+ patches = dict((pn, stack.patches.get(pn).commit)
+ for pn in stack.patchorder.all),
+ message = message)
+ @staticmethod
+ def __parse_metadata(repo, metadata):
+ """Parse a stack log metadata string."""
+ if not metadata.startswith('Version:'):
+ raise LogParseException('Malformed log metadata')
+ metadata = metadata.splitlines()
+ version_str = utils.strip_leading('Version:', metadata.pop(0)).strip()
+ try:
+ version = int(version_str)
+ except ValueError:
+ raise LogParseException(
+ 'Malformed version number: %r' % version_str)
+ if version != 0:
+ raise LogException('Log is version %d, which is too new' % version)
+ parsed = {}
+ for line in metadata:
+ if line.startswith(' '):
+ parsed[key].append(line.strip())
+ else:
+ key, val = [x.strip() for x in line.split(':', 1)]
+ if val:
+ parsed[key] = val
+ else:
+ parsed[key] = []
+ prev = parsed['Previous']
+ if prev == 'None':
+ prev = None
+ else:
+ prev = repo.get_commit(prev)
+ head = repo.get_commit(parsed['Head'])
+ lists = { 'Applied': [], 'Unapplied': [] }
+ patches = {}
+ for lst in lists.keys():
+ for entry in parsed[lst]:
+ pn, sha1 = [x.strip() for x in entry.split(':')]
+ lists[lst].append(pn)
+ patches[pn] = repo.get_commit(sha1)
+ return (prev, head, lists['Applied'], lists['Unapplied'], patches)
+ @classmethod
+ def __from_commit_full(cls, repo, commit):
+ """Parse a full stack log commit."""
+ if not cls.__separator in commit.data.message:
+ raise LogParseException('Not a full log')
+ message, metadata = commit.data.message.rsplit(cls.__separator, 1)
+ (prev, head, applied, unapplied, patches
+ ) = cls.__parse_metadata(repo, metadata)
+ lg = cls(repo, prev, head, applied, unapplied, patches, message)
+ lg.simplified = commit.data.parents[0]
+ lg.commit = commit
+ return lg
+ @classmethod
+ def __from_commit_simplified(cls, repo, commit):
+ """Parse a simplified stack log commit."""
+ message = commit.data.message
+ try:
+ perm, meta = commit.data.tree.data.entries['meta']
+ except KeyError:
+ raise LogParseException('Not a simplified log')
+ (prev, head, applied, unapplied, patches
+ ) = cls.__parse_metadata(repo, meta.data.str)
+ lg = cls(repo, prev, head, applied, unapplied, patches, message)
+ lg.simplified = commit
+ return lg
+ @classmethod
+ def from_commit(cls, repo, commit):
+ """Parse a stack log commit, either full or simplified."""
+ try:
+ return cls.__from_commit_full(repo, commit)
+ except LogException, e:
+ full_exc = e
+ try:
+ return cls.__from_commit_simplified(repo, commit)
+ except LogParseException, e:
+ # Couldn't parse it as a simplified log. Raise the
+ # exception we got while trying to parse it as a full log,
+ # since that exception might be more informative than a
+ # simple parse exception.
+ raise full_exc
+ def __metadata_string(self):
+ e = StringIO.StringIO()
+ e.write('Version: 0\n')
+ if self.prev == None:
+ e.write('Previous: None\n')
+ else:
+ e.write('Previous: %s\n' % self.prev.commit.sha1)
+ e.write('Head: %s\n' % self.head.sha1)
+ for lst, title in [(self.applied, 'Applied'),
+ (self.unapplied, 'Unapplied')]:
+ e.write('%s:\n' % title)
+ for pn in lst:
+ e.write(' %s: %s\n' % (pn, self.patches[pn].sha1))
+ return e.getvalue()
+ def __parents(self):
+ """Return the set of parents this log entry needs in order to be a
+ descendant of all the commits it refers to."""
+ xp = set([self.head]) | set(self.patches[pn] for pn in self.unapplied)
+ if self.applied:
+ xp.add(self.patches[self.applied[-1]])
+ if self.prev != None:
+ xp.add(self.prev.commit)
+ xp -= set(self.prev.patches.values())
+ return xp
+ def __simplified_tree(self, metadata):
+ if self.prev == None:
+ def pf(c):
+ return patch_file(self.__repo, c.data)
+ else:
+ prev_top_tree = self.prev.simplified.data.tree
+ perm, prev_patch_tree = prev_top_tree.data.entries['patches']
+ # Map from Commit object to patch_file() results taken
+ # from the previous log entry.
+ c2b = dict((self.prev.patches[pn], pf) for pn, pf
+ in prev_patch_tree.data.entries.iteritems())
+ def pf(c):
+ r = c2b.get(c, None)
+ if not r:
+ r = patch_file(self.__repo, c.data)
+ return r
+ patches = dict((pn, pf(c)) for pn, c in self.patches.iteritems())
+ return self.__repo.commit(git.TreeData({
+ 'meta': self.__repo.commit(git.BlobData(metadata)),
+ 'patches': self.__repo.commit(git.TreeData(patches)) }))
+ def write_commit(self):
+ metadata = self.__metadata_string()
+ self.simplified = self.__repo.commit(git.CommitData(
+ tree = self.__simplified_tree(metadata),
+ parents = [prev.simplified for prev in [self.prev]
+ if prev != None],
+ message = self.message))
+ parents = list(self.__parents())
+ while len(parents) > self.__max_parents - 1:
+ g = self.__repo.commit(git.CommitData(
+ tree = self.head.data.tree,
+ parents = parents[-self.__max_parents:],
+ message = 'Stack log parent grouping'))
+ parents[-self.__max_parents:] = [g]
+ self.commit = self.__repo.commit(git.CommitData(
+ tree = self.head.data.tree,
+ parents = [self.simplified] + parents,
+ message = self.message + self.__separator + metadata))
+
+def log_entry(stack, msg):
+ """Write a new log entry for the stack."""
+ ref = log_ref(stack.name)
+ try:
+ last_log = stack.repository.refs.get(ref)
+ except KeyError:
+ last_log = None
+ try:
+ new_log = LogEntry.from_stack(last_log, stack, msg)
+ except LogException, e:
+ out.warn(str(e), 'No log entry written.')
+ return
+ new_log.write_commit()
+ stack.repository.refs.set(ref, new_log.commit, msg)
+
+def compat_log_entry(msg):
+ """Write a new log entry. (Convenience function intended for use by
+ code not yet converted to the new infrastructure.)"""
+ repo = default_repo()
+ stack = repo.get_stack(repo.current_branch_name)
+ log_entry(stack, msg)
+
+def delete_log(repo, branch):
+ ref = log_ref(branch)
+ if repo.refs.exists(ref):
+ repo.refs.delete(ref)
+
+def rename_log(repo, old_branch, new_branch, msg):
+ old_ref = log_ref(old_branch)
+ new_ref = log_ref(new_branch)
+ if repo.refs.exists(old_ref):
+ repo.refs.set(new_ref, repo.refs.get(old_ref), msg)
+ repo.refs.delete(old_ref)
+
+def copy_log(repo, src_branch, dst_branch, msg):
+ src_ref = log_ref(src_branch)
+ dst_ref = log_ref(dst_branch)
+ if repo.refs.exists(src_ref):
+ repo.refs.set(dst_ref, repo.refs.get(src_ref), msg)
+
+def default_repo():
+ return stack.Repository.default()
diff --git a/stgit/lib/stack.py b/stgit/lib/stack.py
index 9cb3967..62a1ec2 100644
--- a/stgit/lib/stack.py
+++ b/stgit/lib/stack.py
@@ -165,6 +165,15 @@ class Stack(git.Branch):
).commit.data.parent
else:
return self.head
+ @property
+ def top(self):
+ """Commit of the topmost patch, or the stack base if no patches are
+ applied."""
+ if self.patchorder.applied:
+ return self.patches.get(self.patchorder.applied[-1]).commit
+ else:
+ # When no patches are applied, base == head.
+ return self.head
def head_top_equal(self):
if not self.patchorder.applied:
return True
diff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py
index e47997e..4c4da1a 100644
--- a/stgit/lib/transaction.py
+++ b/stgit/lib/transaction.py
@@ -4,7 +4,7 @@ updates to an StGit stack in a safe and convenient way."""
from stgit import exception, utils
from stgit.utils import any, all
from stgit.out import *
-from stgit.lib import git
+from stgit.lib import git, log
class TransactionException(exception.StgException):
"""Exception raised when something goes wrong with a
@@ -170,6 +170,7 @@ class StackTransaction(object):
_print_current_patch(self.__stack.patchorder.applied, self.__applied)
self.__stack.patchorder.applied = self.__applied
self.__stack.patchorder.unapplied = self.__unapplied
+ log.log_entry(self.__stack, self.__msg)
if self.__error:
return utils.STGIT_CONFLICT
diff --git a/stgit/main.py b/stgit/main.py
index aa1f8ef..ec0e840 100644
--- a/stgit/main.py
+++ b/stgit/main.py
@@ -277,6 +277,7 @@ def main():
ret = command.func(parser, options, args)
except (StgException, IOError, ParsingError, NoSectionError), err:
+ directory.write_log(cmd)
out.error(str(err), title = '%s %s' % (prog, cmd))
if debug_level > 0:
traceback.print_exc()
@@ -292,4 +293,5 @@ def main():
traceback.print_exc()
sys.exit(utils.STGIT_BUG_ERROR)
+ directory.write_log(cmd)
sys.exit(ret or utils.STGIT_SUCCESS)
^ permalink raw reply related
* Re: [PATCH 1/2] clone: respect the settings in $HOME/.gitconfig and /etc/gitconfig
From: Johannes Schindelin @ 2008-06-30 0:41 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: Junio C Hamano, Pieter de Bie, Git Mailinglist
In-Reply-To: <alpine.LNX.1.00.0806291821520.19665@iabervon.org>
Hi,
On Sun, 29 Jun 2008, Daniel Barkalow wrote:
> On Sun, 29 Jun 2008, Johannes Schindelin wrote:
>
> > On Sun, 29 Jun 2008, Daniel Barkalow wrote:
> >
> > > Did we even make a commitment on whether:
> > >
> > > GIT_CONFIG=foo git clone bar
> > >
> > > must ignore the environment variable, or simply doesn't necessarily
> > > obey it?
> >
> > I'd rather strongly argue that no matter what is the answer to this
> > question, we _HAVE TO_ unsetenv() GIT_CONFIG at some stage, otherwise
> > no .git/config will be written.
>
> Why should .git/config get written?
Because the user asked for a clone, where she reasonably expects a git
repository with all the [core] and the initial [remote "origin"] settings
to be written as it should be, _even if_ setting the config to somewhere
else? Hmm?
IMITCNVHO it would be a serious mistake to write the config somewhere else
with "clone".
If that still does not convince you, "git init" also writes to
".git/config" regardless of the user's (possibly bogus) GIT_CONFIG.
It is just such a basic thing that you must _not_ use GIT_CONFIG for
writing with git clone or git init.
Ciao,
Dscho
^ permalink raw reply
* Re: pread() over NFS (again) [1.5.5.4]
From: Shawn O. Pearce @ 2008-06-30 0:32 UTC (permalink / raw)
To: Trond Myklebust
Cc: J. Bruce Fields, Junio C Hamano, logank, Christian Holtje, git,
Trond Myklebust
In-Reply-To: <1214578229.7437.14.camel@localhost>
Trond Myklebust <Trond.Myklebust@netapp.com> wrote:
> On Thu, 2008-06-26 at 22:57 -0400, J. Bruce Fields wrote:
> > On Thu, Jun 26, 2008 at 04:38:40PM -0700, Junio C Hamano wrote:
> > > logank@sent.com writes:
> > >
> > > > On Jun 26, 2008, at 1:56 PM, Junio C Hamano wrote:
> > > >
> > > >>> "The file shouldn't be short unless someone truncated it, or there
> > > >>> is a bug in index-pack. Neither is very likely, but I don't think
> > > >>> we would want to retry pread'ing the same block forever.
> > > >>
> > > >> I don't think we would want to retry even once. Return value of 0
> > > >> from
> > > >> pread is defined to be an EOF, isn't it?
> > > >
> > > > No, it seems to be a simple error-out in this case. We have 2.4.20
> > > > systems with nfs-utils 0.3.3 and used to frequently get the same error
> > > > while pushing. I made a similar change back in February and haven't
> > > > had a problem since:
> > > >
> > > > diff --git a/index-pack.c b/index-pack.c
> > > > index 5ac91ba..85c8bdb 100644
> > > > --- a/index-pack.c
> > > > +++ b/index-pack.c
> > > > @@ -313,7 +313,14 @@ static void *get_data_from_pack(struct
> > > > object_entry *obj)
> > > > src = xmalloc(len);
> > > > data = src;
> > > > do {
> > > > + // It appears that if multiple threads read across NFS, the
> > > > + // second read will fail. I know this is awful, but we wait for
> > > > + // a little bit and try again.
> > > > ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
> > > > + if (n <= 0) {
> > > > + sleep(1);
> > > > + n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
> > > > + }
> > > > if (n <= 0)
> > > > die("cannot pread pack file: %s", strerror(errno));
> > > > rdy += n;
> > > >
> > > > I use a sleep request since it seems less likely that the other thread
> > > > will have an outstanding request after a second of waiting.
> > >
> > > Gaah. Don't we have NFS experts in house? Bruce, perhaps?
> >
> > Trond, you don't have any idea why a 2.6.9-42.0.8.ELsmp client (2.4.28
> > server) might be returning spurious 0's from pread()?
> >
> > Seems like everything is happening from that one client--the file isn't
> > being simultaneously accessed from the server or from another client.
>
> Is the file only being read, or could there be a simultaneous write to
> the same file? I'm surmising this could be an effect resulting from
> simultaneous cache invalidations: prior to Linux 2.6.20 or so, we
> weren't rigorously following the VFS/VM rules for page locking, and so
> page cache invalidation in particular could have some curious
> side-effects.
The file was created and opened O_CREAT|O_EXCL|O_RDWR, by this
process, written linearly using write(2), without any lseeks.
We kept the file descriptor open and starting issuing pread(2)
calls for earlier offsets we had alread written. One of those
kicks back EOF far too early (and results in this bug report).
Note the only accesses we are using is write(2) and pread(2), and
once we start reading we don't ever go back to writing. The pread(2)
calls are typically issued in ascending offsets, and we read each
position only once. This is to try and take advantage of any
read-ahead the kernel may be able to do. The pread(2) calls are
rarely (if ever) on a block/page boundary.
Nobody else should know about this file. Its written to a temporary
name and no other well behaved processes would attempt to read
the file until it gets closed and renamed to its final destination.
We haven't reached that far in the processing when we get this error,
so there should be only one file descriptor open on the inode, and
its the same one that wrote the data.
--
Shawn.
^ permalink raw reply
* Re: [PATCH v8] gitweb: add test suite with Test::WWW::Mechanize::CGI
From: Lea Wiemann @ 2008-06-30 0:30 UTC (permalink / raw)
To: Jakub Narebski; +Cc: git
In-Reply-To: <200806300156.28908.jnareb@gmail.com>
Jakub Narebski wrote:
> some time ago I have send a patch which converted support for
> links with hash and without action to use redirect instead of
> silently filling correct action based on type of object [...]
> IMHO it is better as it should prevent bookmarking "expensive" URL.
Haha, I'd actually suggest the opposite. ;-) Figuring out the right
action is almost free since you have to fetch the object anyways, so I
doubt it'll make any difference performance-wise (though it'd be
interesting to benchmark this). However, gitweb's URLs are
prohibitively long -- so that nobody uses them in email --, and
(automatically?) dropping the action parameter where possible would be a
good first step to shortening them. Another idea would be to shorten
the hashes.
>> there are links to line numbers that don't exist
>
> I wonder if those are intentional (or at least known) breaking, to form
> approximate blame file history browsing;
*nods* I wasn't following this in detail; if it turns out to be
unfixable, we could also remove the fragment checks for line numbers
(rather than running them as "TODO:" tests).
> [Git::Commit:] you should not error out on unknown header in commit object,
Unless this can actually happen in practice, I'd rather die aggressively
-- it prevents errors (like cache hiccups) slipping through unnoticed.
^ permalink raw reply
* Re: [PATCH v8] gitweb: add test suite with Test::WWW::Mechanize::CGI
From: Jakub Narebski @ 2008-06-29 23:56 UTC (permalink / raw)
To: Lea Wiemann; +Cc: git
In-Reply-To: <48681D21.1040302@gmail.com>
On Mon, 30 June 2008, Lea Wiemann wrote:
> Jakub Narebski wrote:
>> On Thu, 26 June 2008, Lea Wiemann wrote:
>>>
>>> - Follow redirects rather than failing.
>>
>> Nice. Where it is?
>
> test_page doesn't use $mech->get_ok anymore, but rather calls $mech->get
> and checks that the status is [23][0-9][0-9]. If it's 3xx, it also
> follows the redirect.
Thanks for the info.
By the way, some time ago I have send a patch (dropped, perhaps because
of it being feature freeze, or just lost) which converted support for
links with hash and without action (introduced in 7f9778b by Gerrit
Pape) to use redirect (like for 'object' action) instead of silently
filling correct action based on type of object (given by hash). IMHO
it is better as it should prevent bookmarking "expensive" URL.
So this is useful, and could/would be even more useful.
>>> - Do not test correctness of line number fragments (#l[0-9]+); they're
>>> broken too often right now.
>>
>> What do you mean by broken?
>
> This is only visible with --long-tests -- there are links to line
> numbers that don't exist (IOW the fragment doesn't exist in a name or id
> attribute on the target page). I've even seen links to #l0. Bug fixes
> welcome. ;-)
I wonder if those are intentional (or at least known) breaking, to form
approximate blame file history browsing; there was discussion about it
on git mailing list some time ago (it resulted in adding "parent" line
to blame porcelain/incremental output... or was it only in 'pu'?).
>> Good work!
>
> Thanks! :-) I'll send v9 in (hopefully) 2-3 days, together with the
> first working version of the caching code.
One issue of note, after brief peek at Git::Repo code: you should not
error out on unknown header in commit object, but either save its value
under its name, or just skip it.
Unless this has changed...
--
Jakub Narebski
Poland
^ permalink raw reply
* Re: [PATCH v8] gitweb: add test suite with Test::WWW::Mechanize::CGI
From: Lea Wiemann @ 2008-06-29 23:39 UTC (permalink / raw)
To: Jakub Narebski; +Cc: git
In-Reply-To: <200806300047.12224.jnareb@gmail.com>
Jakub Narebski wrote:
> On Thu, 26 June 2008, Lea Wiemann wrote:
>> It also uses HTML::Lint, XML::Parser, and Archive::Tar (if present, each)
>
> Wouldn't it be better to use "(each if present)"?
> I am not a native English speaker, though.
Me neither, but I prefer "if present, each" off the top of my head...
>> - Add simple page caching (reduces execution time without --long-tests
>> by 25%). That's *really* helpful when you have to run those tests
>> on a regular basis. ;)
>
> What do you need caching for? You check if page was already accessed
> when spidering...
This is actually about the short tests (I don't think it gains much for
spidering, percentage-wise). The test suite uses some repetitive set-up
code (like get_summary && follow_link 'tree' && ...), so the second and
third times these pages are loaded we can save some time.
> replace $mech->page_links_ok [with] finding all the links [...],
> then filtering out links which you have checked already, then checking
> selected links using $mech->links_ok
That's basically what it does right now. :)
>> +package OurMechanize;
>> +use base qw( Test::WWW::Mechanize::CGI );
>
> Why this package is not named WWW::Mechanize::CGI::Cached, I wonder?
> Is it because of corrected cgi_application method?
Yeah, basically. :) Plus, it's not to be confused with a proper
implementation of a TWM::CGI::Cached package. (For instance, it uses
the URL rather than the complete request as cache key. Hence it ignores
headers like Referer; but that's great for the Gitweb tests since TWM
apparently sets the Referer, but Gitweb doesn't check it, so there's no
need to refetch a page because the Referer has changed.)
> By the way, if you want to add a comment to mentioned WM::CGI ticket [...]
Thanks; done.
>> - Follow redirects rather than failing.
>
> Nice. Where it is?
test_page doesn't use $mech->get_ok anymore, but rather calls $mech->get
and checks that the status is [23][0-9][0-9]. If it's 3xx, it also
follows the redirect.
> I begin to wonder if
> splitting this test into smaller part wouldn't be a good idea...
It's not yet painful (or long-running) enough for me to care. :) I'm
fine with a 500-lines test module.
>> +# Diff formattting problem.
>
> (One 't' too many:
Fixed.
>> + follow_link( { url_abs_regex => qr/a=blob_plain/ },
>> + 'linked file name'); # bang
>
> I'll try to investigate; I guess this uses wrong name or wrong hash
> for preimage.
Thanks!
>> - Do not test correctness of line number fragments (#l[0-9]+); they're
>> broken too often right now.
>
> What do you mean by broken?
This is only visible with --long-tests -- there are links to line
numbers that don't exist (IOW the fragment doesn't exist in a name or id
attribute on the target page). I've even seen links to #l0. Bug fixes
welcome. ;-)
(There are also some other [mostly minor] issues marked with "TODO:" in
the test code; I didn't want to swamp the list with half a dozen bug
reports though, apart from time constraints on my end.)
> Good work!
Thanks! :-) I'll send v9 in (hopefully) 2-3 days, together with the
first working version of the caching code.
-- Lea
^ permalink raw reply
* Re: perl t9700 failures?
From: Lea Wiemann @ 2008-06-29 22:56 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Linus Torvalds, Git Mailing List
In-Reply-To: <7vzlp47zy8.fsf@gitster.siamese.dyndns.org>
Junio C Hamano wrote:
> +perl -MTest::More -e 0 2>/dev/null || {
> + say skip "Perl Test::More unavailable, skipping test"
That looks fine -- the git scripts that use Git.pm are still tested
separately, so even if this test is skipped, Git.pm can be assumed to
not be broken.
Jakub Narebski wrote:
> +perl -MTest::More -e '' >/dev/null 2>&1 || {
> [...]
> +perl -e 'use 5.006002;' >/dev/null 2>&1 || {
I don't think checking for 5.6.2 (which is the version in which
Test::More was added) is actually necessary. If someone installs
Test::More on an older Perl version, we might as well run the tests --
apparently Git.pm works even with older versions, since
t3701-add-interactive.sh seems to work fine for Linus.
Johannes Schindelin wrote:
> And given that I _actively_ warned [...] I
> am actually a little pleased
Johannes, I'd actually be a little pleased if you either
- stop "actively warning" and start actively sending patches, or
- spare the list (and in particular, my mailbox) your whining.
TIA.
^ permalink raw reply
* Re: perl t9700 failures?
From: Jakub Narebski @ 2008-06-29 22:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Linus Torvalds, Git Mailing List, Lea Wiemann
In-Reply-To: <7vzlp47zy8.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
> +perl -MTest::More -e 0 2>/dev/null || {
> + say skip "Perl Test::More unavailable, skipping test"
> + test_done
> +}
> +
I think it would be nice to have "test_skip" function in test-lib.sh;
a few tests beside this one (t9500 gitweb test, git-svn tests) have
not always filled requirements.
--
Jakub Narebski
Poland
ShadeHawk on #git
^ permalink raw reply
* Re: [PATCH 1/2] clone: respect the settings in $HOME/.gitconfig and /etc/gitconfig
From: Daniel Barkalow @ 2008-06-29 22:47 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, Pieter de Bie, Git Mailinglist
In-Reply-To: <alpine.DEB.1.00.0806292248160.9925@racer>
On Sun, 29 Jun 2008, Johannes Schindelin wrote:
> Hi,
>
> On Sun, 29 Jun 2008, Daniel Barkalow wrote:
>
> > Did we even make a commitment on whether:
> >
> > GIT_CONFIG=foo git clone bar
> >
> > must ignore the environment variable, or simply doesn't necessarily obey
> > it?
>
> I'd rather strongly argue that no matter what is the answer to this
> question, we _HAVE TO_ unsetenv() GIT_CONFIG at some stage, otherwise no
> .git/config will be written.
Why should .git/config get written? The user is explicitly using a
different file instead, so .git/config really shouldn't get written,
unless the user isn't allowed to use the environment variable or the
environment variable shouldn't apply.
Actually, perhaps the right thing is to remove the code to look at the
environment variable from config.c and have it in builtin-config.c, since
only "git config" is actually documented to be affected by GIT_CONFIG at
all. But I don't really know what the variable is supposed to do, beyond
what's in the documentation. In any case, I don't think "git clone" is at
all special with respect to GIT_CONFIG.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Re: [PATCH v8] gitweb: add test suite with Test::WWW::Mechanize::CGI
From: Jakub Narebski @ 2008-06-29 22:47 UTC (permalink / raw)
To: Lea Wiemann; +Cc: git
In-Reply-To: <1214488126-6783-1-git-send-email-LeWiemann@gmail.com>
On Thu, 26 June 2008, Lea Wiemann wrote:
> This test uses Test::WWW::Mechanize::CGI to check gitweb's output. It
> also uses HTML::Lint, XML::Parser, and Archive::Tar (if present, each)
Wouldn't it be better to use "(each if present)"?
I am not a native English speaker, though.
[...]
> Changes since v7:
>
> - In Makefile, dump $PERL_PATH to GIT-BUILD-OPTIONS (which gets
> source'd by test-lib.sh), and use it in t9710-perl-git-repo.sh.
Good idea.
> - .git/description in the test repository no longer depends on $0
> (this would e.g. cause 'cd t; make' to fail).
> +cat >.git/description <<EOF
> +gitweb test repository
> +EOF
I'd rather use more descriptive name, so when you look it up in gitweb
to manually (visually?) checks its output you would know of which test
is it. Something like "gitweb Mechanize test repository", or "test
repository for gitweb's Mechanize test".
> - Add test_link subroutine and use it everywhere in place of
> ok(find_link...) so that links whose presence get tested get checked
> and spidered in --long-tests mode.
Nice.
> - Add simple page caching (reduces execution time without --long-tests
> by 25%). That's *really* helpful when you have to run those tests
> on a regular basis. ;)
What do you need caching for? You check if page was already accessed
when spidering...
If it is about checking links, alternate solution would be to replace
simple $mech->page_links_ok( [ $desc ] ) by finding all the links
either using $mech->followable_links() or $mech->find_all_links( ... ),
or just $mech->links, then filtering out links which you have checked
already, then checking selected links using $mech->links_ok( $links [, $desc ] )
> (WWW::Mechanize::Cached won't work with
> TWM::CGI, so we have to implement it ourselves; but it's easier
> anyway.)
> +package OurMechanize;
> +
> +use base qw( Test::WWW::Mechanize::CGI );
Why this package is not named WWW::Mechanize::CGI::Cached, I wonder?
Is it because of corrected cgi_application method?
> +
> +my %page_cache;
> +# Cache requests.
> +sub _make_request {
> + my ($self, $request) = (shift, shift);
> +
> + my $response;
> + unless ($response = Storable::thaw($page_cache{$request->uri})) {
> + $response = $self->SUPER::_make_request($request, @_);
> + $page_cache{$request->uri} = Storable::freeze($response);
> + }
> + return $response;
> +}
> +
> +# Fix whitespace problem.
> +sub cgi_application {
> + my ($self, $application) = @_;
> +
> + # This subroutine was copied (and modified) from
> + # WWW::Mechanize::CGI 0.3, which is licensed 'under the same
> + # terms as perl itself' and thus GPL compatible.
> + my $cgi = sub {
> + # Use exec, not the shell, to support embedded
> + # whitespace in the path to $application.
> + # http://rt.cpan.org/Ticket/Display.html?id=36654
> + my $status = system $application $application;
> + my $value = $status >> 8;
> +
> + croak( qq/Failed to execute application '$application'. Reason: '$!'/ )
> + if ( $status == -1 );
> + croak( qq/Application '$application' exited with value: $value/ )
> + if ( $value > 0 );
> + };
> +
> + $self->cgi($cgi);
> +}
> +
By the way, if you want to add a comment to mentioned WM::CGI ticket
http://rt.cpan.org/Ticket/Display.html?id=36654 you have to either
register, or send comment via mail with the following info
>From "Bugs in WWW-Mechanize-CGI via RT" <bug-WWW-Mechanize-CGI@rt.cpan.org>:
|
| Please include the string:
|
| [rt.cpan.org #36654]
|
| in the subject line of all future correspondence about this issue. To do so,
| you may reply to this message.
> - Follow redirects rather than failing.
Nice. Where it is?
> - Test subdirectories in tree view.
> - Test error handling for non-existent hashes or hashes of wrong type.
> - Test commitdiff_plain view.
> - Expand test for history view.
> - Test tag objects (not just symbolic tags) in tag list.
More tests are always nice to have, although I begin to wonder if
splitting this test into smaller part wouldn't be a good idea...
> - Test a specific bug (under "diff formatting", marked TODO).
> +# Diff formattting problem.
(One 't' too many: should be "Diff formatting problem")
> +if (get_summary &&
> + follow_link( { text_regex => qr/renamed/ }, 'commit with rename') &&
> + follow_link( { text => 'commitdiff' }, 'commitdiff')) {
> + TODO: {
> + local $TODO = "bad a/* link in diff";
> + if (follow_link( { text_regex => qr!^a/! },
> + 'a/* link (probably wrong)')) {
> + # The page we land on here is broken already.
> + follow_link( { url_abs_regex => qr/a=blob_plain/ },
> + 'linked file name'); # bang
> + }
> + }
> +}
I'll try to investigate; I guess this uses wrong name or wrong hash
for preimage.
> - Do not test correctness of line number fragments (#l[0-9]+); they're
> broken too often right now.
What do you mean by broken?
> - Probably some more minor improvements I've forgotten about. :)
For example
> +die "this must be run by calling the t/t*.sh shell script(s)\n"
> + if Cwd->cwd !~ /trash directory$/;
Good work!
--
Jakub Narebski
Poland
^ permalink raw reply
* [StGit PATCH 2/2] Fix "stg sink" with no applied patches (bug 11887)
From: Karl Hasselström @ 2008-06-29 22:45 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, Erik Sandberg
In-Reply-To: <20080629224440.9267.3591.stgit@yoghurt>
There were two separate things to fix: bail out if we need a current
patch and there isn't one (because there are no applied patches), and
make sure we don't try to pop patches that don't exist.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/sink.py | 8 ++++++--
t/t1501-sink.sh | 2 +-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/stgit/commands/sink.py b/stgit/commands/sink.py
index 2167d87..d8f79b4 100644
--- a/stgit/commands/sink.py
+++ b/stgit/commands/sink.py
@@ -58,9 +58,13 @@ def func(parser, options, args):
if len(args) > 0:
patches = parse_patches(args, all)
else:
- patches = [ crt_series.get_current() ]
+ current = crt_series.get_current()
+ if not current:
+ raise CmdException('No patch applied')
+ patches = [current]
- crt_series.pop_patch(options.to or oldapplied[0])
+ if oldapplied:
+ crt_series.pop_patch(options.to or oldapplied[0])
push_patches(crt_series, patches)
if not options.nopush:
diff --git a/t/t1501-sink.sh b/t/t1501-sink.sh
index 3872c4b..6af45fe 100755
--- a/t/t1501-sink.sh
+++ b/t/t1501-sink.sh
@@ -20,7 +20,7 @@ test_expect_success 'sink without applied patches' '
! stg sink
'
-test_expect_failure 'sink a specific patch without applied patches' '
+test_expect_success 'sink a specific patch without applied patches' '
stg sink y &&
test $(echo $(stg applied)) = "y"
'
^ permalink raw reply related
* [StGit PATCH 1/2] Try "stg sink" without applied patches
From: Karl Hasselström @ 2008-06-29 22:45 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, Erik Sandberg
In-Reply-To: <20080629224440.9267.3591.stgit@yoghurt>
It doesn't work, neither with an implicit nor an explicit patch to
sink. This is bug 11887 in the bug tracker.
(The implicit sink testcase actually passes, but that's just because
the test suite can't distinguish between a program bug and an orderly
abort.)
The test was adapted from the script attached to the bug report,
written by Erik Sandberg.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
t/t1501-sink.sh | 28 ++++++++++++++++++++++++++++
1 files changed, 28 insertions(+), 0 deletions(-)
create mode 100755 t/t1501-sink.sh
diff --git a/t/t1501-sink.sh b/t/t1501-sink.sh
new file mode 100755
index 0000000..3872c4b
--- /dev/null
+++ b/t/t1501-sink.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='Test "stg sink"'
+
+. ./test-lib.sh
+
+test_expect_success 'Initialize StGit stack' '
+ echo 000 >> x &&
+ git add x &&
+ git commit -m initial &&
+ echo 000 >> y &&
+ git add y &&
+ git commit -m y &&
+ stg init &&
+ stg uncommit &&
+ stg pop
+'
+
+test_expect_success 'sink without applied patches' '
+ ! stg sink
+'
+
+test_expect_failure 'sink a specific patch without applied patches' '
+ stg sink y &&
+ test $(echo $(stg applied)) = "y"
+'
+
+test_done
^ permalink raw reply related
* [StGit PATCH 0/2] Test+fix for bug 11887 (stg sink)
From: Karl Hasselström @ 2008-06-29 22:45 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, Erik Sandberg
This should go on the stable branch, of course.
---
Karl Hasselström (2):
Fix "stg sink" with no applied patches (bug 11887)
Try "stg sink" without applied patches
stgit/commands/sink.py | 8 ++++++--
t/t1501-sink.sh | 28 ++++++++++++++++++++++++++++
2 files changed, 34 insertions(+), 2 deletions(-)
create mode 100755 t/t1501-sink.sh
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
^ permalink raw reply
* Re: What's cooking in git.git (topics)
From: Steffen Prohaska @ 2008-06-29 22:00 UTC (permalink / raw)
To: Pieter de Bie; +Cc: Junio C Hamano, Linus Torvalds, Git Mailinglist
In-Reply-To: <A87D312D-8B65-4D57-84AC-8FC07A27B937@ai.rug.nl>
On Jun 29, 2008, at 10:15 PM, Pieter de Bie wrote:
>
> On 29 jun 2008, at 22:11, Junio C Hamano wrote:
>
>> use of them from your scripts after adding
>> output from "git --exec-path" to the $PATH will still be supported
>> in
>> 1.6.0, but users are again strongly encouraged to adjust their
>> scripts to use "git xyzzy" form, as we will stop installing
>> "git-xyzzy" hardlinks for built-in commands in later releases.
>
> I think msysgit doesn't (didn't?) install the hardlinks to conserve
> space,
> as Windows doesn't support hard links. Perhaps we should mention that
> as well?
Windows does support hardlinks and msysgit uses them.
Steffen
^ permalink raw reply
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