* [PATCH] Add support for author-oriented git-rev-list switches [rev 6]
From: Jon Seymour @ 2005-06-09 9:01 UTC (permalink / raw)
To: git; +Cc: jon.seymour
This patch is a complete replacement for the previous --wrt
and --wrt-author patches; it adds support for --wrt-author,
--prune-at-author and --author switches to git-rev-list.
These switches perform various sorting and filtering operations that
help any git user reconstruct a linear view of merge history from
the point of any given change author.
When --wrt-author is specified, --merge-order is implied but
the linearisation of non-linear epochs is adjusted so that branches
contributed to by the author sort after branches contributed to by
others. This has the effect of presenting a linear history of changes as seen
by the author's own workspace.
For example, given this commit history:
a4 ---
| \ \
| b4 |
|/ | |
a3 | |
| | |
a2 | |
| | c3
| | |
| | c2
| b3 |
| | /|
| b2 |
| | c1
| | /
| b1 <-- commit authored by author@domain
a1 |
| |
a0 |
| /
root
--merge-order alone would sort it as follows:
= a4
| c3
| c2
| c1
^ b4
| b3
| b2
| b1
^ a3
| a2
| a1
| a0
= root
however, with --wrt-author --author=author@domain, git-rev-list sorts as follows:
= a4
| c3
| c2
| c1
^ b4
| a3
| a2
| a1
| a0
^ b3
| b2
| b1
= root
This has the effect of showing the first 3 of author@domain's changes
occuring before any of the changes in other branches. This is the
order in which author@domain perceived change occurring - changes
a0->a3 weren't visible in author@domain's workspace until the merge at b4.
If --prune-at-author is specified, then git-rev-list behaves as if a
^argument had been added for each commit authored by the author.
The --prune-at-author option allows an author to see a brief summary of
changes since they last made a contribution to the commit graph. For example:
git-rev-list --prune-at-author --pretty HEAD
To configure which author git-rev-list uses when processing the --wrt-author
and --prune-at-author switches, use the --author=author@domain option. If this
option is not specified, git-rev-list uses GIT_AUTHOR_EMAIL or a value
derived from the local user, host and domain names
(per git-commit-tree's algorithm)
To support this change, some additional functions were added to commit.c to
allow the extraction of author from the commit header.
Also, --show-breaks, like --wrt-author now implies --merge-order rather than
requires it.
This change also updates Documentation/git-rev-list.txt to include
documentation for --wrt-author and other git-rev-list options not currently
documented.
A test case has been included in t/t6001-rev-list-merge-order.sh to
verify this change works as expected.
Use of --wrt-author implies a small overhead (linear with respect to nodes
printed) compared with --merge-order.
This change extracts real email id formatting functions from commit-tree.c
into user.c and user.h.
Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
NOTE TO REVIEWERS: please pay special attention to get_real_identity()
which was extracted from commit-tree.c and placed into user.c. It was
essentially a cut and paste, but since this has the potential to affect the
operation of git-commit-tree, it would be worth being very careful about
checking this change.
Another thing to review is my use of /dev/urandom and dd in my
t/t6001-rev-list-merge-order.sh. I am using it so that two
closely timed commits look diffrent to git. If /dev/urandom doesn't
exist I fall back to a slower, but safer technique using only
date.
[rev 1] * original patch
[rev 2-4] * various cleanups
[rev 5] * --merge-order bug fixes already merged with Linus.
[rev 6] * remerged with Linus' head (98a96b00b88ee35866cd0b1e94697db76bd5ddf9)
* changed name of --exclude-author switch to --prune-at-author
* reformatted to be more consistent with preferred coding style, namely:
* aggressive elimination of intra-block blank lines
except immediately after declarations
* declarations at top of block
* one statement blocks without braces
* everything except first line of function declaration
wrapped at <= 80 columns
* modified test cases to use /dev/urandom to get some entropy for
commit text so that closely spaced commits aren't identical
* added struct epoch_methods to gather 3 function pointers now used
by merge order logic
* added a comment to commit.c to indicate that epoch.c is assuming
that parse_commit stacks parents read from the commit header
rather than queues them
Could someone else review this patch please and if it, does, please your ok
to the list? I am more than happy to make any coding style adjustments required.
Diverged from 98a96b00b88ee35866cd0b1e94697db76bd5ddf9 by Linus Torvalds <torvalds@ppc970.osdl.org>
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,65 +9,113 @@ git-rev-list - Lists commit objects in r
SYNOPSIS
--------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--merge-order* [ *--show-breaks* ] ] <commit>
+'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ]
+[ *--min-age*=timestamp ] [ *--pretty* ] [ *--parents* ] [ *--header* ]
+[ *--merge-order* ] [ *--show-breaks* ] [ *--wrt-author* ]
+[ *--prune-at-author* ] [ *--author=author@domain* ]
+<head-to-include> <head-to-include> ...
+^<base-to-prune>
+^<base-to-prune> ...
DESCRIPTION
-----------
-Lists commit objects in reverse chronological order starting at the
-given commit, taking ancestry relationship into account. This is
-useful to produce human-readable log output.
-
-If *--merge-order* is specified, the commit history is decomposed into a
-unique sequence of minimal, non-linear epochs and maximal, linear epochs.
-Non-linear epochs are then linearised by sorting them into merge order, which
+
+Lists commit objects in reverse chronological order starting at the commits
+specified by head-to-include values, taking ancestry relationship into account.
+git-rev-list will not print any commits that are reachable by any of the
+base-to-prune values.
+
+If *--max-count* is specified, the number of commits output is
+limited to the number specified.
+
+If *--max-age* is specified, commits older than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--min-age* is specified, commits younger than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--pretty* is specified, git-rev-list prints a short summary of each
+commit on stdout. This can be usefully parsed by git-shortlog to produce a
+short log of changes.
+
+If *--parents* is specified, git-rev-list prints a space separated list
+of parents on the same line as the commit identifier.
+
+If *--header* is specified, git-rev-list prints to stdout, on lines after
+the identifier of each commit, the contents of the commit's header followed by
+a trailing NUL.
+
+If *--merge-order* is specified, the commit history is decomposed into a
+unique sequence of minimal, non-linear epochs and maximal, linear epochs.
+Non-linear epochs are then linearised by sorting them into merge order, which
is described below.
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
+Maximal, linear epochs correspond to periods of sequential development.
+Minimal, non-linear epochs correspond to periods of divergent development
+followed by a converging merge. The theory of epochs is described in more
+detail at
link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-
- 1. if a commit P is reachable from commit N, commit P sorts after commit N
- in the linearised list.
- 2. if Pi and Pj are any two parents of a merge M (with i < j), then any
- commit N, such that N is reachable from Pj but not reachable from Pi,
- sorts before all commits reachable from Pi.
-
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
+The merge order for a non-linear epoch is defined as a linearisation for
+which the following invariants are true:
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
+ 1. if a commit P is reachable from commit N, commit P sorts after
+ commit N in the linearised list.
+ 2. if Pi and Pj are any two parents of a merge M (with i < j), then
+ any commit N, such that N is reachable from Pj but not reachable from
+ Pi, sorts before all commits reachable from Pi.
+
+Invariant 1 states that later commits appear before earlier commits
+they are derived from.
+
+Invariant 2 states that commits unique to "later" parents in a merge,
+appear before all commits from "earlier" parents of a merge.
+
+If *--show-breaks* is specified, each item of the list is output with a
+2-character prefix consisting of one of: (|), (^), (=) followed by a space.
+
+Commits marked with (=) represent the boundaries of minimal, non-linear
+epochs and correspond either to the start of a period of divergent development
+or to the end of such a period.
-If *--show-breaks* is specified, each item of the list is output with a
-2-character prefix consisting of one of: (|), (^), (=) followed by a space.
-
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-
-Commits marked with (|) are direct parents of commits immediately preceding
+Commits marked with (|) are direct parents of commits immediately preceding
the marked commit in the list.
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
+Commits marked with (^) are not parents of the immediately preceding commit.
+These "breaks" represent necessary discontinuities implied by trying to
represent an arbtirary DAG in a linear form.
-*--show-breaks* is only valid if *--merge-order* is also specified.
+If *--wrt-author* is specified, then git-rev-list sorts commits so that
+contemporaneously developed commits appear to occur after (that is, sort before)
+any commits authored by the author specified by *--author*. This reflects the
+order in which that author sees contemporaneously developed commits appear
+in her own workspace.
+
+If *--prune-at-author* is specified, then any commit authored by the
+author is marked as uninteresting as if it had been specified with ^ on
+the command line. This option restricts git-rev-list's output to just
+the changes following any changes contributed by the author.
+
+The *--author* option is used to specify the author used by the
+*--wrt-author* and *--prune-at-author* switches. If an *--author* option is
+not specified, the author used is derived from GIT_AUTHOR_EMAIL or, ultimately,
+from the local user, host and domain names as per the logic used by
+git-commit-tree.
+
+Use of *--show-breaks*, *--prune-at-author* or *--wrt-author*
+implies *--merge-order*.
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+Original *--merge-order*, *--wrt-author* and *--prune-at-author* logic
+by Jon Seymour <jon.seymour@gmail.com>
Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by David Greaves, Junio C Hamano, Jon Seymour and
+the git-list <git@vger.kernel.org>.
GIT
---
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -42,9 +42,9 @@ install: $(PROG) $(SCRIPTS)
LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \
tag.o delta.o date.o index.o diff-delta.o patch-delta.o entry.o \
- epoch.o refs.o
+ epoch.o refs.o user.o
LIB_FILE=libgit.a
-LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h
+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h user.h
LIB_H += strbuf.h
LIB_OBJS += strbuf.o
@@ -140,6 +140,7 @@ diffcore-pickaxe.o : $(LIB_H) diffcore.h
diffcore-break.o : $(LIB_H) diffcore.h
diffcore-order.o : $(LIB_H) diffcore.h
epoch.o: $(LIB_H)
+user.o: $(LIB_H)
test: all
$(MAKE) -C t/ all
diff --git a/commit-tree.c b/commit-tree.c
--- a/commit-tree.c
+++ b/commit-tree.c
@@ -4,8 +4,8 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "user.h"
-#include <pwd.h>
#include <time.h>
#include <ctype.h>
@@ -100,17 +100,16 @@ static char *commit_tree_usage = "git-co
int main(int argc, char **argv)
{
- int i, len;
+ int i;
int parents = 0;
unsigned char tree_sha1[20];
unsigned char parent_sha1[MAXPARENT][20];
unsigned char commit_sha1[20];
char *gecos, *realgecos, *commitgecos;
- char *email, *commitemail, realemail[1000];
+ char *email, *commitemail, *realemail;
char date[50], realdate[50];
char *audate, *cmdate;
char comment[1000];
- struct passwd *pw;
char *buffer;
unsigned int size;
@@ -128,19 +127,7 @@ int main(int argc, char **argv)
}
if (!parents)
fprintf(stderr, "Committing initial tree %s\n", argv[1]);
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- realgecos = pw->pw_gecos;
- len = strlen(pw->pw_name);
- memcpy(realemail, pw->pw_name, len);
- realemail[len] = '@';
- gethostname(realemail+len+1, sizeof(realemail)-len-1);
- if (!strchr(realemail+len+1, '.')) {
- strcat(realemail, ".");
- getdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);
- }
-
+ get_real_identity(&realemail, &realgecos);
datestamp(realdate, sizeof(realdate));
strcpy(date, realdate);
diff --git a/commit.c b/commit.c
--- a/commit.c
+++ b/commit.c
@@ -76,6 +76,15 @@ int parse_commit_buffer(struct commit *i
!get_sha1_hex(bufptr + 7, parent)) {
struct commit *new_parent = lookup_commit(parent);
if (new_parent) {
+ /**
+ * The current stacking (as opposed to queueing)
+ * behaviour implied by use of commit_list_insert
+ * is assumed in several places elsewhere in git
+ * - particularly epoch.c. If this ever changes
+ * it will be necessary to carefully re-evaluate
+ * order-sensitive commit list processing loops to
+ * ensure they are still doing the right thing.
+ */
commit_list_insert(new_parent, &item->parents);
add_ref(&item->object, &new_parent->object);
}
@@ -302,3 +311,56 @@ int count_parents(struct commit * commit
return count;
}
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len)
+{
+ char * p = commit->buffer;
+ while (*p != '\n') {
+ char * q = header;
+ int matched;
+
+ for (matched = 1; *p != ' ' && *p != '\n'; p++, q++) {
+ matched = matched && (*q==*p);
+ }
+
+ if (matched && index) {
+ /*
+ * if we matched but we haven't seen the
+ * index'th element yet, just decrement
+ * the index then pretend we didn't match
+ */
+ index--;
+ matched = 0;
+ }
+
+ if (!matched) {
+ /* skip to start of next header line */
+ for (;*p!='\n';p++)
+ ;
+ p++;
+ } else {
+ int count = 0; /* number of characters in value */
+ if (*p == ' ') {
+ p++;
+ count = 0;
+ while(*p != '\n') {
+ if (len > 0)
+ *buffer++=*p;
+ p++;
+ count++;
+ len--;
+ }
+ }
+
+ if (len > 0)
+ *buffer = 0;
+
+ return (len > 0) ? count+1 : -(count+1);
+ }
+ }
+ return 0;
+}
+
+int copy_author(struct commit * commit, char * buffer, int len)
+{
+ return copy_commit_header(commit, "author", 0, buffer, len);
+}
diff --git a/commit.h b/commit.h
--- a/commit.h
+++ b/commit.h
@@ -53,4 +53,23 @@ struct commit *pop_most_recent_commit(st
struct commit *pop_commit(struct commit_list **stack);
int count_parents(struct commit * commit);
+
+/*
+ * Copies the value of the (index+1)'th commit header matching the name
+ * specified into the buffer supplied and append a trailing NUL.
+ *
+ * Returns n<0 if the buffer was too short, where -n is the required length.
+ *
+ * Returns 0 if the header doesn't exist.
+ *
+ * Returns n>0 where n is the length of the copied zero-terminated value,
+ * including the terminating zero.
+ */
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len);
+
+/*
+ * Copies the commit's author value into the buffer supplied.
+ * Return values as per copy_commit_header.
+ */
+int copy_author(struct commit * commit, char * buffer, int len);
#endif /* COMMIT_H */
diff --git a/epoch.c b/epoch.c
--- a/epoch.c
+++ b/epoch.c
@@ -8,7 +8,6 @@
*
*/
#include <stdlib.h>
-
/* Provides arbitrary precision integers required to accurately represent
* fractional mass: */
#include <openssl/bn.h>
@@ -23,7 +22,19 @@ struct fraction {
};
#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
+#define ASSERT(x,m,c) if (!(x)) { assertion_failed(__LINE__, __FUNCTION__, m, c); }
+static void assertion_failed(int line, char * function, char * message, struct commit * item)
+{
+ die( "%s:%d:%s: assertion_failed: %s: commit %s, flags %x",
+ __FILE__,
+ line,
+ function,
+ message,
+ item ? sha1_to_hex(item->object.sha1) : "[]",
+ item ? item->object.flags : 0xFFFFFFFF);
+}
+
static BN_CTX *context = NULL;
static struct fraction *one = NULL;
static struct fraction *zero = NULL;
@@ -58,10 +69,10 @@ static struct fraction *divide(struct fr
BN_init(&bn_divisor);
BN_set_word(&bn_divisor, divisor);
-
BN_copy(&result->numerator, &fraction->numerator);
- BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
+ BN_mul( &result->denominator,
+ &fraction->denominator,
+ &bn_divisor, get_BN_CTX());
BN_clear(&bn_divisor);
return result;
}
@@ -99,28 +110,44 @@ static struct fraction *copy(struct frac
return to;
}
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
+static struct fraction *inc(struct fraction *left, struct fraction *right)
{
BIGNUM a, b, gcd;
BN_init(&a);
BN_init(&b);
BN_init(&gcd);
-
- BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
- BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
- BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
- BN_add(&result->numerator, &a, &b);
-
- BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
- BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
- BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
+ BN_mul( &a,
+ &left->numerator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_mul( &b,
+ &left->denominator,
+ &right->numerator,
+ get_BN_CTX());
+ BN_mul( &left->denominator,
+ &left->denominator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_add( &left->numerator,
+ &a,
+ &b);
+ BN_gcd( &gcd,
+ &left->denominator,
+ &left->numerator,
+ get_BN_CTX());
+ BN_div( &left->denominator,
+ NULL, &left->denominator,
+ &gcd,
+ get_BN_CTX());
+ BN_div( &left->numerator,
+ NULL, &left->numerator,
+ &gcd,
+ get_BN_CTX());
BN_clear(&a);
BN_clear(&b);
BN_clear(&gcd);
-
- return result;
+ return left;
}
static int compare(struct fraction *left, struct fraction *right)
@@ -130,15 +157,11 @@ static int compare(struct fraction *left
BN_init(&a);
BN_init(&b);
-
BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
result = BN_cmp(&a, &b);
-
BN_clear(&a);
BN_clear(&b);
-
return result;
}
@@ -150,21 +173,15 @@ struct mass_counter {
static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
{
struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
- memset(mass_counter, 0, sizeof(*mass_counter));
+ ASSERT(!commit->object.util, "not already initialized", commit);
+
+ memset(mass_counter, 0, sizeof(*mass_counter));
init_fraction(&mass_counter->seen);
init_fraction(&mass_counter->pending);
-
copy(&mass_counter->pending, pending);
copy(&mass_counter->seen, get_zero());
-
- if (commit->object.util) {
- die("multiple attempts to initialize mass counter for %s",
- sha1_to_hex(commit->object.sha1));
- }
-
commit->object.util = mass_counter;
-
return mass_counter;
}
@@ -218,73 +235,63 @@ static int find_base_for_list(struct com
struct commit_list *cleaner = NULL;
struct commit_list *pending = NULL;
struct fraction injected;
+
init_fraction(&injected);
*boundary = NULL;
-
- for (; list; list = list->next) {
+ for (; list && !ret; list = list->next) {
struct commit *item = list->item;
- if (item->object.util) {
- die("%s:%d:%s: logic error: this should not have happened - commit %s",
- __FILE__, __LINE__, __FUNCTION__,
- sha1_to_hex(item->object.sha1));
- }
-
+ ASSERT(!item->object.util, "no duplicates in list", item);
new_mass_counter(list->item, get_one());
- add(&injected, &injected, get_one());
-
+ inc(&injected, get_one());
commit_list_insert(list->item, &cleaner);
commit_list_insert(list->item, &pending);
}
-
while (!*boundary && pending && !ret) {
struct commit *latest = pop_commit(&pending);
struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
int num_parents;
- if ((ret = parse_commit(latest)))
+ ret = parse_commit(latest);
+ if (ret)
continue;
- add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
+ inc(&latest_node->seen, &latest_node->pending);
num_parents = count_parents(latest);
if (num_parents) {
- struct fraction distribution;
- struct commit_list *parents;
+ struct fraction div;
+ struct commit_list *parents = latest->parents;
- divide(init_fraction(&distribution), &latest_node->pending, num_parents);
-
- for (parents = latest->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
- struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
- if (!parent_node) {
- parent_node = new_mass_counter(parent, &distribution);
- insert_by_date(&pending, parent);
- commit_list_insert(parent, &cleaner);
- } else {
- if (!compare(&parent_node->pending, get_zero()))
- insert_by_date(&pending, parent);
- add(&parent_node->pending, &parent_node->pending, &distribution);
- }
+ divide( init_fraction(&div),
+ &latest_node->pending,
+ num_parents);
+ while (parents) {
+ struct mass_counter * pmc;
+ struct commit *pc = parents->item;
+
+ pmc = (struct mass_counter *) pc->object.util;
+ if (!pmc) {
+ pmc = new_mass_counter(pc, get_zero());
+ commit_list_insert(pc, &cleaner);
+ }
+ if (!compare(&pmc->pending, get_zero()))
+ insert_by_date(&pending, pc);
+ inc(&pmc->pending, &div);
+ parents = parents->next;
}
-
- clear_fraction(&distribution);
+ clear_fraction(&div);
}
-
- if (!compare(&latest_node->seen, &injected))
+ if (!compare(&latest_node->seen, &injected)) {
*boundary = latest;
+ }
copy(&latest_node->pending, get_zero());
}
-
while (cleaner) {
struct commit *next = pop_commit(&cleaner);
free_mass_counter((struct mass_counter *) next->object.util);
next->object.util = NULL;
}
-
if (pending)
free_commit_list(pending);
-
clear_fraction(&injected);
return ret;
}
@@ -305,7 +312,6 @@ static int find_base(struct commit *head
}
ret = find_base_for_list(pending, boundary);
free_commit_list(pending);
-
return ret;
}
@@ -319,13 +325,12 @@ static int find_base(struct commit *head
*/
static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
{
- int ret;
struct commit *item = head_of_epoch;
+ int ret;
ret = parse_commit(item);
if (ret)
return ret;
-
if (HAS_EXACTLY_ONE_PARENT(item)) {
/*
* We are at the start of a maximimal linear epoch.
@@ -336,7 +341,6 @@ static int find_next_epoch_boundary(stru
ret = parse_commit(item);
}
*boundary = item;
-
} else {
/*
* Otherwise, we are at the start of a minimal, non-linear
@@ -344,7 +348,6 @@ static int find_next_epoch_boundary(stru
*/
ret = find_base(item, boundary);
}
-
return ret;
}
@@ -354,8 +357,10 @@ static int find_next_epoch_boundary(stru
static int is_parent_of(struct commit *parent, struct commit *child)
{
struct commit_list *parents;
+
for (parents = child->parents; parents; parents = parents->next) {
- if (!memcmp(parent->object.sha1, parents->item->object.sha1,
+ if (!memcmp(parent->object.sha1,
+ parents->item->object.sha1,
sizeof(parents->item->object.sha1)))
return 1;
}
@@ -369,11 +374,12 @@ static int is_parent_of(struct commit *p
*/
static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
{
- struct commit_list *top = *stack;
- if (top && (top->item->object.flags & DISCONTINUITY)) {
- if (is_parent_of(top->item, item)) {
- top->item->object.flags &= ~DISCONTINUITY;
- }
+ struct commit * top_item = *stack ? (*stack)->item : NULL;
+
+ if ( top_item
+ && (top_item->object.flags & DISCONTINUITY)
+ && is_parent_of(top_item, item)) {
+ top_item->object.flags &= ~DISCONTINUITY;
}
commit_list_insert(item, stack);
}
@@ -381,7 +387,7 @@ static void push_onto_merge_order_stack(
/*
* Marks all interesting, visited commits reachable from this commit
* as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
+ * an unvisited node or a node that has already been marked uninteresting.
*
* This doesn't actually mark all ancestors between the start node and the
* epoch boundary uninteresting, but does ensure that they will eventually
@@ -397,7 +403,6 @@ static void mark_ancestors_uninteresting
struct commit_list *next;
commit->object.flags |= UNINTERESTING;
-
/*
* We only need to recurse if
* we are not on the boundary and
@@ -416,65 +421,77 @@ static void mark_ancestors_uninteresting
if (uninteresting || boundary || !visited)
return;
-
for (next = commit->parents; next; next = next->next)
mark_ancestors_uninteresting(next->item);
}
/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
+ * Sorts the nodes of the first epoch of the epoch sequence of the epoch
+ * headed at head into merge order.
*/
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
+static void sort_first_epoch(struct commit *head, struct commit_list **stack, struct epoch_methods * methods)
{
- struct commit_list *parents;
- struct commit_list *reversed_parents = NULL;
+ struct commit_list *reversed = NULL;
+ if (methods->marker) {
+ (*(methods->marker)) (head);
+ }
head->object.flags |= VISITED;
-
/*
- * parse_commit() builds the parent list in reverse order with respect
- * to the order of the git-commit-tree arguments. So we need to reverse
- * this list to output the oldest (or most "local") commits last.
+ * reverse them so that "local" parents are pushed first
+ * and hence print last. Note: this is relying
+ * on parse_commit always stacking rather than queueing
+ * parents it reads from the commit header. If this
+ * assumption ever changes, then this code and
+ * also that in sort_list_merge_order.
*/
- for (parents = head->parents; parents; parents = parents->next)
- commit_list_insert(parents->item, &reversed_parents);
+ if (!head->object.util) {
+ struct commit_list *parents;
- /*
- * TODO: By sorting the parents in a different order, we can alter the
- * merge order to show contemporaneous changes in parallel branches
- * occurring after "local" changes. This is useful for a developer
- * when a developer wants to see all changes that were incorporated
- * into the same merge as her own changes occur after her own
- * changes.
- */
+ /*
+ * make a copy of the parents, but don't destroy them
+ */
+ parents = head->parents;
+ while (parents) {
+ commit_list_insert(parents->item, &reversed);
+ parents = parents->next;
+ }
+ } else {
+ struct commit_list **p;
- while (reversed_parents) {
- struct commit *parent = pop_commit(&reversed_parents);
+ /*
+ * reverse the temporary list, destroying it
+ * in the process list.
+ */
+ p = (struct commit_list **)&head->object.util;
+ while (*p)
+ commit_list_insert(pop_commit(p), &reversed);
+ ASSERT(!head->object.util, "temporary list cleared", head);
+ }
+ while (reversed) {
+ struct commit *parent = pop_commit(&reversed);
if (head->object.flags & UNINTERESTING) {
/*
* Propagates the uninteresting bit to all parents.
- * if we have already visited this parent, then
+ * If we have already visited this parent, then
* the uninteresting bit will be propagated to each
* reachable commit that is still not marked
- * uninteresting and won't otherwise be reached.
+ * uninteresting and won't otherwise be reached by
+ * this traversal.
*/
mark_ancestors_uninteresting(parent);
}
-
if (!(parent->object.flags & VISITED)) {
if (parent->object.flags & BOUNDARY) {
- if (*stack) {
- die("something else is on the stack - %s",
- sha1_to_hex((*stack)->item->object.sha1));
- }
+ ASSERT(!(*stack),
+ "empty stack at boundary",
+ (*stack)->item);
push_onto_merge_order_stack(stack, parent);
parent->object.flags |= VISITED;
-
} else {
- sort_first_epoch(parent, stack);
- if (reversed_parents) {
+ sort_first_epoch(parent, stack, methods);
+ if (reversed) {
/*
* This indicates a possible
* discontinuity it may not be be
@@ -485,23 +502,21 @@ static void sort_first_epoch(struct comm
* The next push onto the stack will
* resolve the question.
*/
- (*stack)->item->object.flags |= DISCONTINUITY;
+ (*stack)->item
+ ->object.flags |= DISCONTINUITY;
}
}
}
}
-
push_onto_merge_order_stack(stack, head);
}
/*
* Emit the contents of the stack.
- *
* The stack is freed and replaced by NULL.
- *
* Sets the return value to STOP if no further output should be generated.
*/
-static int emit_stack(struct commit_list **stack, emitter_func emitter)
+static int emit_stack(struct commit_list **stack, struct epoch_methods * methods)
{
unsigned int seen = 0;
int action = CONTINUE;
@@ -510,15 +525,93 @@ static int emit_stack(struct commit_list
struct commit *next = pop_commit(stack);
seen |= next->object.flags;
if (*stack)
- action = (*emitter) (next);
+ action = (*(methods->emitter)) (next);
}
-
if (*stack) {
free_commit_list(*stack);
*stack = NULL;
- }
+ }
+ if (seen & UNINTERESTING)
+ /**
+ * We stop at the base of the stack, rather than
+ * when we encounter the first UNINTERESTING flag.
+ *
+ * The reason is that there may still be interesting
+ * stuff on the stack but once we reach the base
+ * there can be no more interesting stuff by definition
+ * of what the base of an epoch is - everything reachable
+ * from the base is also reachable from the UNINTERESTING
+ * node and hence is uninteresting.
+ */
+ action = STOP;
+ return action;
+}
- return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
+/*
+ * Sort a list of commits in local last order. A commit is "local"
+ * if any of its ancestors (except the base) causes (*local_test)() to
+ * return a non-zero value.
+ *
+ * The sorted list is returned in *sorted. A side effect of this function
+ * is to set each object.util pointer in each ancestor up until the base
+ * to a list of parents sorted in local_last order.
+ *
+ * Does nothing except set *sorted to NULL if either list or local_test are
+ * NULL.
+ *
+ * The return value contains LOCAL if any of the list is local or had a
+ * local ancestor.
+ */
+static unsigned int sort_list_in_local_last_order(struct commit_list *list, struct commit_list **sorted, struct epoch_methods * methods)
+{
+ struct commit_list *next;
+ struct commit_list *non_local = NULL;
+ struct commit_list *local = NULL;
+ struct commit_list **p = &local;
+ struct commit_list **q = &non_local;
+
+ if (!list || !methods->local_test) {
+ *sorted = NULL;
+ return 0;
+ }
+ for (next = list; next; next = next->next) {
+ struct commit *item = next->item;
+
+ if (item->object.flags & BOUNDARY || item->object.util)
+ /*
+ * we have already visited this item or we have
+ * reached the boundary.
+ */
+ continue;
+ if ((*(methods->local_test)) (item))
+ item->object.flags |= LOCAL;
+ item->object.flags |=
+ sort_list_in_local_last_order(
+ item->parents,
+ (struct commit_list **) &item->object.util,
+ methods);
+ }
+ /*
+ * Create a re-ordered list by copying the locally marked
+ * items to the end of the new list, but otherwise preserving the
+ * order.
+ */
+ for (next = list; next; next = next->next) {
+ if (next->item->object.flags & LOCAL)
+ p = &commit_list_insert(next->item, p)->next;
+ else
+ q = &commit_list_insert(next->item, q)->next;
+ }
+ /*
+ * if parse_commit in commit.c ever changes stack behaviour
+ * to queueing behaviour this needs to change to:
+ *
+ * *p = non_local;
+ * *sorted = local;
+ */
+ *q = local;
+ *sorted = non_local;
+ return (local ? LOCAL : 0);
}
/*
@@ -528,14 +621,13 @@ static int emit_stack(struct commit_list
* Note: this algorithm currently leaves traces of its execution in the
* object flags of nodes it discovers. This should probably be fixed.
*/
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+static int sort_in_merge_order(struct commit *head_of_epoch, struct epoch_methods * methods)
{
struct commit *next = head_of_epoch;
int ret = 0;
int action = CONTINUE;
ret = parse_commit(head_of_epoch);
-
while (next && next->parents && !ret && (action != STOP)) {
struct commit *base = NULL;
@@ -545,34 +637,36 @@ static int sort_in_merge_order(struct co
next->object.flags |= BOUNDARY;
if (base)
base->object.flags |= BOUNDARY;
-
if (HAS_EXACTLY_ONE_PARENT(next)) {
while (HAS_EXACTLY_ONE_PARENT(next)
&& (action != STOP)
&& !ret) {
- if (next->object.flags & UNINTERESTING) {
+ if (methods->marker)
+ (*(methods->marker)) (next);
+ if (next->object.flags & UNINTERESTING)
action = STOP;
- } else {
- action = (*emitter) (next);
- }
+ else
+ action = (*(methods->emitter)) (next);
if (action != STOP) {
next = next->parents->item;
ret = parse_commit(next);
}
}
-
} else {
struct commit_list *stack = NULL;
- sort_first_epoch(next, &stack);
- action = emit_stack(&stack, emitter);
+
+ sort_list_in_local_last_order(
+ next->parents,
+ (struct commit_list **) &next->object.util,
+ methods);
+ sort_first_epoch(next, &stack, methods);
+ action = emit_stack(&stack, methods);
next = base;
}
}
-
if (next && (action != STOP) && !ret) {
- (*emitter) (next);
+ (*(methods->emitter))(next);
}
-
return ret;
}
@@ -582,52 +676,65 @@ static int sort_in_merge_order(struct co
* in this subgraph using the sort_first_epoch algorithm. Once we have
* reached the base we can continue sorting using sort_in_merge_order.
*/
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods)
{
struct commit_list *stack = NULL;
+ struct commit_list *filtered = NULL;
+ struct commit_list **p = &filtered;
struct commit *base;
int ret = 0;
int action = CONTINUE;
- struct commit_list *reversed = NULL;
+ if (!methods)
+ die("methods argument must not be null");
+ if (!methods->emitter)
+ die("an emitter methods must be supplied");
for (; list; list = list->next) {
struct commit *next = list->item;
if (!(next->object.flags & UNINTERESTING)) {
- if (next->object.flags & DUPCHECK) {
- fprintf(stderr, "%s: duplicate commit %s ignored\n",
- __FUNCTION__, sha1_to_hex(next->object.sha1));
- } else {
+ if (!(next->object.flags & DUPCHECK)) {
+ /**
+ * Duplicates will cause find_base_for_list
+ * to die, so we silently remove them
+ * here - it isn't actually an error.
+ */
next->object.flags |= DUPCHECK;
- commit_list_insert(list->item, &reversed);
+ p = &commit_list_insert(list->item, p)->next;
}
}
}
-
- if (!reversed->next) {
+ if (!filtered->next) {
/*
* If there is only one element in the list, we can sort it
* using sort_in_merge_order.
*/
- base = reversed->item;
+ base = filtered->item;
} else {
/*
* Otherwise, we search for the base of the list.
- */
- ret = find_base_for_list(reversed, &base);
+ */
+ struct commit_list *sorted;
+ struct commit_list *reversed = NULL;
+
+ ret = find_base_for_list(filtered, &base);
if (ret)
return ret;
if (base)
base->object.flags |= BOUNDARY;
-
+ sort_list_in_local_last_order(filtered, &sorted, methods);
+ if (!sorted)
+ sorted = filtered;
+ while (sorted)
+ commit_list_insert(pop_commit(&sorted), &reversed);
while (reversed) {
- sort_first_epoch(pop_commit(&reversed), &stack);
+ sort_first_epoch(pop_commit(&reversed), &stack, methods);
if (reversed) {
/*
* If we have more commits to push, then the
- * first push for the next parent may (or may
+ * first push for the next commit may (or may
* not) represent a discontinuity with respect
- * to the parent currently on the top of
+ * to the commit currently on the top of
* the stack.
*
* Mark it for checking here, and check it
@@ -637,13 +744,15 @@ int sort_list_in_merge_order(struct comm
stack->item->object.flags |= DISCONTINUITY;
}
}
-
- action = emit_stack(&stack, emitter);
+ action = emit_stack(&stack, methods);
}
-
if (base && (action != STOP)) {
- ret = sort_in_merge_order(base, emitter);
+ ret = sort_in_merge_order(base, methods);
}
-
return ret;
}
+
+void init_epoch_methods(struct epoch_methods * methods)
+{
+ memset(methods, 0, sizeof(*methods));
+}
diff --git a/epoch.h b/epoch.h
--- a/epoch.h
+++ b/epoch.h
@@ -1,20 +1,55 @@
#ifndef EPOCH_H
#define EPOCH_H
-
-// return codes for emitter_func
-#define STOP 0
-#define CONTINUE 1
-#define DO 2
-typedef int (*emitter_func) (struct commit *);
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
+/**
+ * Flags used by merge order logic and also by rev-list.c
+ */
#define UNINTERESTING (1u<<2)
#define BOUNDARY (1u<<3)
#define VISITED (1u<<4)
#define DISCONTINUITY (1u<<5)
#define DUPCHECK (1u<<6)
+#define LOCAL (1u<<7)
+/**
+ * Return codes for emitter method. Also used by rev-list.c
+ */
+#define STOP 0
+#define CONTINUE 1
+#define DO 2
-#endif /* EPOCH_H */
+struct epoch_methods {
+ /*
+ * Returns 0 if traversal should stop, non-zero if it should continue.
+ */
+ int (*emitter)(struct commit *);
+ /*
+ * Returns non-zero if the commit is regarded "local", 0 otherwise.
+ */
+ int (*local_test)(struct commit *);
+ /*
+ * Implementers may use this method to mark commits uninteresting
+ * according to some locally determined criteria. The tree
+ * will be pruned at any commit so marked.
+ */
+ void (*marker)(struct commit *);
+};
+
+/**
+ * Initializes an epoch_methods structure which
+ * may be customized by the caller by overriding any of the method pointers.
+ */
+extern void init_epoch_methods(struct epoch_methods *);
+
+/**
+ * Sorts the list of commits in merge order, using the methods specified
+ * to customize the tactics of the search.
+ *
+ * The prune points should be marked with the UNINTERESTING flags.
+ *
+ * Note: this algorithm is dirty in the sense that it leaves traces
+ * of its execution in the object.flags word of some or all of the commits
+ * visited.
+ */
+extern int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods);
+#endif /* EPOCH_H */
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -1,18 +1,24 @@
#include "cache.h"
#include "commit.h"
#include "epoch.h"
+#include "user.h"
#define SEEN (1u << 0)
#define INTERESTING (1u << 1)
static const char rev_list_usage[] =
"usage: git-rev-list [OPTION] commit-id <commit-id>\n"
- " --max-count=nr\n"
- " --max-age=epoch\n"
- " --min-age=epoch\n"
- " --header\n"
- " --pretty\n"
- " --merge-order [ --show-breaks ]";
+ " --max-count=nr\n"
+ " --max-age=epoch\n"
+ " --min-age=epoch\n"
+ " --header\n"
+ " --parents\n"
+ " --pretty\n"
+ " --merge-order\n"
+ " --wrt-author\n"
+ " --prune-at-author\n"
+ " --author=author@domain\n"
+ " --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
@@ -24,6 +30,9 @@ static int max_count = -1;
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
static int merge_order = 0;
static int show_breaks = 0;
+static char * local_author = NULL;
+static int prune_at_author = 0;
+static int wrt_author = 0;
static void show_commit(struct commit *commit)
{
@@ -147,6 +156,23 @@ static enum cmit_fmt get_commit_format(c
usage(rev_list_usage);
}
+static int is_local_author(struct commit * commit)
+{
+ static char author[16384];
+ if (copy_author(commit, author, sizeof(author)) > 0) {
+ if (strstr(author, local_author)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void mark_authors_own_uninteresting(struct commit * commit)
+{
+ if (is_local_author(commit)) {
+ commit->object.flags |= UNINTERESTING;
+ }
+}
int main(int argc, char **argv)
{
@@ -186,14 +212,26 @@ int main(int argc, char **argv)
show_parents = 1;
continue;
}
- if (!strncmp(arg, "--merge-order", 13)) {
+ if (!strcmp(arg, "--merge-order")) {
merge_order = 1;
continue;
}
- if (!strncmp(arg, "--show-breaks", 13)) {
+ if (!strcmp(arg, "--show-breaks")) {
show_breaks = 1;
continue;
}
+ if (!strncmp(arg, "--author=", 9)) {
+ local_author=strdup(&arg[9]);
+ continue;
+ }
+ if (!strcmp(arg, "--wrt-author")) {
+ wrt_author = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--prune-at-author")) {
+ prune_at_author = 1;
+ continue;
+ }
flags = 0;
if (*arg == '^') {
@@ -201,7 +239,7 @@ int main(int argc, char **argv)
arg++;
limited = 1;
}
- if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+ if (get_sha1(arg, sha1))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
@@ -213,14 +251,38 @@ int main(int argc, char **argv)
if (!list)
usage(rev_list_usage);
+ if (!merge_order)
+ merge_order = wrt_author || prune_at_author || show_breaks;
+
if (!merge_order) {
if (limited)
list = limit_list(list);
show_commit_list(list);
- } else {
- if (sort_list_in_merge_order(list, &process_commit)) {
- die("merge order sort failed\n");
- }
+ } else {
+ struct epoch_methods methods;
+
+ init_epoch_methods(&methods);
+ if ((prune_at_author|wrt_author) && !local_author) {
+ local_author = gitenv("GIT_AUTHOR_EMAIL") ? : NULL;
+ if (!local_author)
+ get_real_identity(&local_author, NULL);
+ else
+ local_author = strdup(local_author);
+ }
+ if (local_author) {
+ /* add delimiters to improve accuracy of match */
+ char * tmp=xmalloc(strlen(local_author)+3);
+ sprintf(tmp, "<%s>", local_author);
+ free(local_author);
+ local_author = tmp;
+ }
+ methods.emitter = &process_commit;
+ if (wrt_author)
+ methods.local_test = &is_local_author;
+ if (prune_at_author)
+ methods.marker = &mark_authors_own_uninteresting;
+ if (sort_list_in_merge_order(list, &methods))
+ die("merge order sort failed\n");
}
return 0;
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
--- a/t/t6001-rev-list-merge-order.sh
+++ b/t/t6001-rev-list-merge-order.sh
@@ -9,7 +9,33 @@ test_description='Test rev-list --merge-
function do_commit
{
- git-commit-tree "$@" </dev/null
+ (
+ # add some entropy to commits so that the so that
+ # two closely timed commits have different sha1 hashes
+ # fall back to a slower technique if urandom, md5sum or
+ # dd don't exist
+ if [ -c /dev/urandom -a -x "$(which md5sum)" -a -x "$(which dd)" ]
+ then
+ dd if=/dev/urandom bs=1024 count=1 2>/dev/null | md5sum
+ else
+ sleep 1;
+ date
+ fi
+ ) | git-commit-tree "$@" 2>/dev/null
+}
+
+function e_commit
+{
+ export GIT_AUTHOR_EMAIL="author-e@example.com"
+ do_commit "$@"
+ export GIT_AUTHOR_EMAIL=
+}
+
+function f_commit
+{
+ export GIT_AUTHOR_EMAIL="author-f@example.com"
+ do_commit "$@"
+ export GIT_AUTHOR_EMAIL=
}
function check_adjacency
@@ -30,9 +56,10 @@ function check_adjacency
function sed_script
{
- for c in root a0 a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 l0 l1 l2 l3 l4 l5
+ for c in root a0 a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 l0 l1 l2 l3 l4 l5 e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10
+
do
- echo -n "s/${!c}/$c/;"
+ echo -n "s/${!c}/$c/g;"
done
}
@@ -40,17 +67,16 @@ date >path0
git-update-cache --add path0
tree=$(git-write-tree)
root=$(do_commit $tree 2>/dev/null)
-export GIT_COMMITTER_NAME=foobar # to guarantee that the commit is different
l0=$(do_commit $tree -p $root)
l1=$(do_commit $tree -p $l0)
l2=$(do_commit $tree -p $l1)
a0=$(do_commit $tree -p $l2)
a1=$(do_commit $tree -p $a0)
-export GIT_COMMITTER_NAME=foobar2 # to guarantee that the commit is different
b1=$(do_commit $tree -p $a0)
c1=$(do_commit $tree -p $b1)
-export GIT_COMMITTER_NAME=foobar3 # to guarantee that the commit is different
+export GIT_AUTHOR_EMAIL=foobar@example.com
b2=$(do_commit $tree -p $b1)
+export GIT_AUTHOR_EMAIL=
b3=$(do_commit $tree -p $b2)
c2=$(do_commit $tree -p $c1 -p $b2)
c3=$(do_commit $tree -p $c2)
@@ -63,6 +89,32 @@ l4=$(do_commit $tree -p $l3)
l5=$(do_commit $tree -p $l4)
echo $l5 > .git/HEAD
+e1=$(e_commit $tree 2>/dev/null)
+e2=$(e_commit $tree -p $e1)
+f1=$(f_commit $tree -p $e1)
+e3=$(e_commit $tree -p $e2)
+f2=$(f_commit $tree -p $f1)
+e4=$(e_commit $tree -p $e3 -p $f2)
+e5=$(e_commit $tree -p $e4)
+f3=$(f_commit $tree -p $f2)
+f4=$(f_commit $tree -p $f3)
+e6=$(e_commit $tree -p $e5 -p $f4)
+f5=$(f_commit $tree -p $f4)
+f6=$(f_commit $tree -p $f5 -p $e6)
+e7=$(e_commit $tree -p $e6)
+e8=$(e_commit $tree -p $e7)
+e9=$(e_commit $tree -p $e8)
+f7=$(f_commit $tree -p $f6)
+f8=$(f_commit $tree -p $f7)
+f9=$(f_commit $tree -p $f8)
+e10=$(e_commit $tree -p $e9 -p $f8)
+
+
+#> e1 -- e2 -- e3 -- e4 -- e5 -- e6 -- e7 -- e8 -- e9 -- e10
+#> \ / / \ /
+#> - f1 -- f2 -- f3 -- f4 -- f5 -- f6 -- f7 -- f8 -- f9
+
+
git-rev-list --merge-order --show-breaks HEAD | sed "$(sed_script)" > actual-merge-order
cat > expected-merge-order <<EOF
= l5
@@ -86,6 +138,15 @@ cat > expected-merge-order <<EOF
= root
EOF
+
+#
+# cd to t/trash and use
+#
+# git-rev-list ... 2>&1 | sed "$(cat sed.script)"
+#
+# if you ever want to manually debug the operation of git-rev-list
+#
+sed_script > sed.script
git-rev-list HEAD | check_adjacency | sed "$(sed_script)" > actual-default-order
normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
@@ -172,4 +233,125 @@ test_expect_success 'Testing duplicated
test_expect_success 'Testing exclusion near merge' 'git-rev-list --merge-order $a4 ^$c3 2>/dev/null'
+cat > expected-merge-order-6 <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+| b2
+| b1
+= a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+git-rev-list --merge-order --show-breaks --wrt-author --author=foobar@example.com HEAD 2>/dev/null | sed "$(sed_script)" > actual-merge-order-6
+test_expect_success 'Testing --wrt-author --author=foobar@example.com' 'diff expected-merge-order-6 actual-merge-order-6'
+
+cat > expected-merge-order-7 <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+EOF
+
+git-rev-list --show-breaks --wrt-author --author=foobar@example.com --prune-at-author HEAD | sed "$(sed_script)" > actual-merge-order-7
+test_expect_success 'Testing --prune-at-author' 'diff expected-merge-order-7 actual-merge-order-7'
+
+cat > expected-wrt-e <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| f2
+| f1
+^ e3
+| e2
+= e1
+EOF
+
+git-rev-list --show-breaks --wrt-author --author=author-e@example.com $e10 $f9 | sed "$(sed_script)" > actual-wrt-e
+test_expect_success 'Testing --wrt-author --author=author-e' 'diff expected-wrt-e actual-wrt-e'
+
+cat > expected-wrt-f <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| e3
+| e2
+^ f2
+| f1
+= e1
+EOF
+
+cat > expected-wrt-nobody <<EOF
+| e10
+| e9
+| e8
+| e7
+^ f9
+| f8
+| f7
+| f6
+| e6
+| e5
+| e4
+| e3
+| e2
+^ f5
+| f4
+| f3
+| f2
+| f1
+= e1
+EOF
+
+git-rev-list --show-breaks --wrt-author --author=author-f@example.com $e10 $f9 | sed "$(sed_script)" > actual-wrt-f
+test_expect_success 'Testing --wrt-author --author=author-f' 'diff expected-wrt-f actual-wrt-f'
+
+git-rev-list --show-breaks $f9 $e10 | sed "$(sed_script)" > actual-wrt-nobody
+test_expect_success 'Testing --wrt-author' 'diff expected-wrt-nobody actual-wrt-nobody'
+
+#
+#
+#
test_done
diff --git a/user.c b/user.c
new file mode 100644
--- /dev/null
+++ b/user.c
@@ -0,0 +1,33 @@
+#include "cache.h"
+#include "user.h"
+#include <string.h>
+#include <pwd.h>
+
+void get_real_identity(char **email, char **gecos)
+{
+ static char buffer[1000];
+ struct passwd *pw;
+ int len;
+
+ pw = getpwuid(getuid());
+ if (!pw)
+ die("You don't exist. Go away!");
+
+ len = strlen(pw->pw_name);
+ memcpy(buffer, pw->pw_name, len);
+ buffer[len] = '@';
+ gethostname(buffer + len + 1, sizeof(buffer) - len - 1);
+ if (!strchr(buffer + len + 1, '.')) {
+ strcat(buffer, ".");
+ getdomainname(buffer + strlen(buffer),
+ sizeof(buffer) - strlen(buffer) - 1);
+ }
+
+ if (gecos) {
+ *gecos = strdup(pw->pw_gecos);
+ }
+
+ if (email) {
+ *email = strdup(buffer);
+ }
+}
diff --git a/user.h b/user.h
new file mode 100644
--- /dev/null
+++ b/user.h
@@ -0,0 +1,8 @@
+#ifndef USER_H
+#define USER_H
+/*
+ * Allocates two new strings to contain the real email and
+ * name of the current user.
+ */
+extern void get_real_identity(char **email, char **gecos);
+#endif
^ permalink raw reply
* Question: mode on git-resolve-script - it's intentionally non-executable, right?
From: Jon Seymour @ 2005-06-07 16:04 UTC (permalink / raw)
To: Git Mailing List
Can someone confirm that the mode on git-resolve-script is
intentionally non-executable, so as to prevent glitches when git is
used to maintain git?
jon.
--
homepage: http://www.zeta.org.au/~jon/
blog: http://orwelliantremors.blogspot.com/
^ permalink raw reply
* Re: [ANNOUNCE] Cogito-0.11.2
From: Petr Baudis @ 2005-06-09 8:35 UTC (permalink / raw)
To: Dan Holmsand; +Cc: git
In-Reply-To: <42A780A7.3060608@gmail.com>
Dear diary, on Thu, Jun 09, 2005 at 01:35:03AM CEST, I got a letter
where Dan Holmsand <holmsand@gmail.com> told me that...
> Petr Baudis wrote:
> > Here's git-rev-list --pretty HEAD ^cogito-0.11.1 | git-shortlog
> >(BTW, Dan, what about another cg-log option for git-shortlog output? ;-):
>
> Well, since I try to use the same output format as git-rev-list --pretty
> does (most of the time, it *is* just that...), I can actually already do
>
> cg-log -r:cogito-0.11.1 | git-shortlog
>
> and get the same thing.
I'll see how I like it, but currently I find myself use all of the
commit header items a lot, so I'm not too keen to losing some of them,
perhaps except the tree line. Ok, and it might be enough to have the
parent lines only for the merge commits. Nothing against things like
s/^author/Author:/.
--
Petr "Pasky" Baudis
Stuff: http://pasky.or.cz/
<Espy> be careful, some twit might quote you out of context..
^ permalink raw reply
* Re: [PATCH] Add support reading default options from conf file
From: Petr Baudis @ 2005-06-09 8:32 UTC (permalink / raw)
To: Jonas Fonseca; +Cc: Dan Holmsand, git
In-Reply-To: <20050609010056.GA9084@diku.dk>
Dear diary, on Thu, Jun 09, 2005 at 03:00:56AM CEST, I got a letter
where Jonas Fonseca <fonseca@diku.dk> told me that...
> Reply-To:
> In-Reply-To: <42A754D5.10705@gmail.com>
>
> Dan Holmsand <holmsand@gmail.com> wrote Wed, Jun 08, 2005:
> > - Automatic color if the COGITO_AUTO_COLOR environment variable is set.
>
> [ This has been discussed before. Default arguments. The 'new' cg
> wrapper makes this very easy. Not as smart as your env variable
> handling tho'. ]
>
> The default options are read only for Cogito calls going through the new
> cg wrapper which makes it trivial to 'overwrite' them by just calling
> cg-COMMAMD.
>
> Signed-off-by: Jonas Fonseca <fonseca@diku.dk>
No, this is the wrong way. The 'cg' wrapper must not really do anything
special on its own. It's not a _replacement_ for direct calling of the
cg-scripts, it's just an aid for people who don't want to get used to
it. So please do this in cg-Xlib.
Also, I'd prefer the config file to be something like ~/.cgrc. I want to
reserve the .conf file for something more sophisticated. ;-)
--
Petr "Pasky" Baudis
Stuff: http://pasky.or.cz/
<Espy> be careful, some twit might quote you out of context..
^ permalink raw reply
* Re: [PATCH] Fix cogito handling of timezones
From: Petr Baudis @ 2005-06-09 8:29 UTC (permalink / raw)
To: Frank Sorenson; +Cc: Git Mailing List
In-Reply-To: <42A7BA17.5050707@tuxrocks.com>
Dear diary, on Thu, Jun 09, 2005 at 05:40:07AM CEST, I got a letter
where Frank Sorenson <frank@tuxrocks.com> told me that...
> Current cogito behavior treats the timezone offset as if it's decimal,
> but most zone offsets begin with a 0. As a result, the computation
> in cg-Xlib uses the zone offset as an octal number. -0700 looks like
> 4 1/2 hours offset, rather than 7, and -0800 is an invalid octal
> number.
>
> This patch fixes the behavior to strip off leading 0s. It's ugly,
> but it should produce the right values until someone with better
> bash scripting skills than I can fix it.
>
> Signed-off-by: Frank Sorenson <frank@tuxrocks.com>
>
> diff --git a/cg-Xlib b/cg-Xlib
> --- a/cg-Xlib
> +++ b/cg-Xlib
> @@ -51,8 +53,12 @@ showdate () {
> [ "$format" ] || format=-R
> sec=${date[0]}; tz=${date[1]}
> if [ "$has_gnudate" ]; then
> - dtz=${tz/+/}
> - lsec=$(($dtz / 100 * 3600 + $dtz % 100 * 60 + $sec))
> + sign=${tz%%[0-9]*}
> + sign=${sign:?+}
> + dtz=${tz/[+-]}
> + dtz=${dtz##*(0)}
> + dtz=${dtz:?0}
> + lsec=$(($sec + $dtz % 100 $sign $dtz / 100 * 3600))
It looks ok, but shouldn't this be
+ lsec=$(($sec + $dtz % 100 * 60 $sign $dtz / 100 * 3600))
--
Petr "Pasky" Baudis
Stuff: http://pasky.or.cz/
<Espy> be careful, some twit might quote you out of context..
^ permalink raw reply
* Re: git bug?
From: Junio C Hamano @ 2005-06-09 8:15 UTC (permalink / raw)
To: Jeff Garzik; +Cc: git
In-Reply-To: <42A7F769.2050800@pobox.com>
>>>>> "JG" == Jeff Garzik <jgarzik@pobox.com> writes:
JG> 'git-diff-cache -p HEAD' should not show files that I have not modified.
JG> This screws up
JG> git-diff-cache -p HEAD | diffstat -p1 | awk '{print $1}'
JG> for example.
Agreed 100%. If there is a file exists (rather, if the git
thinks the file is there), then diff-cache should not say it is
deleted. That would be wrong.
Sorry for not having a crystal ball to poke into your terminal,
so let me get something straight. I am still unsure the problem
you are reporting is coming from diff-cache bug, or somehow
yesterday's (well it is 01:13AM where I sit ;-)) merge updates
trashed your work tree or index file, so these questions would
help me tell which. When "diff-cache" reports the file
"deleted":
- Do you have that arch/arm/mm/minicache.c file in your working
directory?
- Does git-ls-files know about the file? Does the path show in:
git-ls-files --stage | grep arch/arm/mm/minicache.c
- Does the HEAD tree have the path as well? Does it show in:
git-ls-tree -r HEAD | grep arch/arm/mm/minicache.c
^ permalink raw reply
* Re: git bug?
From: Jeff Garzik @ 2005-06-09 8:01 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vzmu0otg8.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano wrote:
> Jeff, I assume you are talking about this part:
>
>
>>>diff --git a/arch/arm/mm/minicache.c b/arch/arm/mm/minicache.c
>>>deleted file mode 100644
>>>diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
>
>
> that does not have anything between "deleted file..." and the
> next diff about libata-core.c.
>
> In short, this is not really a "bug", but I am open to
> suggestions for improvements (but you need to talk Linus into
> accepting the changes, because changing this would also affect
> his "git-apply" program as well).
It's a bug and a new behavior.
'git-diff-cache -p HEAD' should not show files that I have not modified.
This screws up
git-diff-cache -p HEAD | diffstat -p1 | awk '{print $1}'
for example.
Jeff
^ permalink raw reply
* Re: git bug?
From: Junio C Hamano @ 2005-06-09 7:55 UTC (permalink / raw)
To: Jeff Garzik; +Cc: git
In-Reply-To: <7vy89kq96o.fsf@assigned-by-dhcp.cox.net>
Jeff, I assume you are talking about this part:
>> diff --git a/arch/arm/mm/minicache.c b/arch/arm/mm/minicache.c
>> deleted file mode 100644
>> diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
that does not have anything between "deleted file..." and the
next diff about libata-core.c.
In short, this is not really a "bug", but I am open to
suggestions for improvements (but you need to talk Linus into
accepting the changes, because changing this would also affect
his "git-apply" program as well).
The current "diff-patch" format is generated this way:
(0) if a modification is between different kind of objects
(e.g. a regular file changed to a symlink), then the
following steps are done twice; one "deletion" of the
original, and another "creation" of the result.
(1) "diff --git a/oldname b/newname" line is shown first and it
is always shown. Unless it is a rename/copy diff, oldname
and newname always match. We never say /dev/null on this
line to represent creation/deletion.
(2) Next, optional "diff --git extended headers" appear. See
apply.c for the list of things to expect.
(3) Next, the output from "diff -u -L<oldname> -L<newname>"
between the two file contents follows. Here, oldname and
newname are usually of the form "a/oldname" and
"b/oldname", but we _do_ follow /dev/null convention for
file creation and deletion here. That is, you would see
--- /dev/null
+++ frotz.c
@@ ...
for a file creation, and the opposite for a file deletion.
Unfortunately, if "diff" finds nothing to report, you would
not even see these ---/+++ lines. I think that is what
puzzled you.
By the way, this is all documented (?) behaviour, reported first
by Linus in this message.
Message-ID: <Pine.LNX.4.58.0505261214140.2307@ppc970.osdl.org>
From: Linus Torvalds <torvalds@osdl.org>
Subject: git full diff output issues..
Date: Thu, 26 May 2005 12:19:21 -0700 (PDT)
Cc: Git Mailing List <git@vger.kernel.org>
While testing my "git-apply" thing (coming along quite nicely, thanks for
asking), I've hit a case that is nasty to parse.
This is from the 2.6.12-rc4 -> 2.6.12-rc5 patch:
diff --git a/arch/um/kernel/checksum.c b/arch/um/kernel/checksum.c
deleted file mode 100644
diff --git a/arch/um/kernel/initrd.c b/arch/um/kernel/initrd.c
new file mode 100644
--- /dev/null
+++ b/arch/um/kernel/initrd.c
@@ -0,0 +1,78 @@
and the magic here is that deleted file that was empty to begin with, so
it didn't have a patch, just a note on deletion.
Why is that nasty? Because we don't have the file _name_ in any good
format. The filename only exists int he "diff --git" header, and that one
has the space-parsing issue, which makes it less than optimal.
(the rest omitted)
We had a handful of back-and-forth back then and the resolution
was this:
Message-ID: <Pine.LNX.4.58.0505261316250.2307@ppc970.osdl.org>
From: Linus Torvalds <torvalds@osdl.org>
Subject: Re: git full diff output issues..
Date: Thu, 26 May 2005 13:33:26 -0700 (PDT)
Cc: Junio C Hamano <junkio@cox.net>, Git Mailing List <git@vger.kernel.org>
(some omitted)
However, I ended up just validating the name parsing by making sure that
when I parse the "git --diff" line, I only take the name if I can see it
being the same for both the old and the new. ...
(the rest omitted)
By the way, this is not just "deleting empty files". Any change
that does not involve content changes you would see something
similar to what you found.
Message-ID: <Pine.LNX.4.58.0506050806400.1876@ppc970.osdl.org>
From: Linus Torvalds <torvalds@osdl.org>
Subject: Re: git full diff output issues..
Date: Sun, 5 Jun 2005 08:11:02 -0700 (PDT)
Cc: git@vger.kernel.org
(some omitted)
The only case that was special was literally the "same name, no content
changes, new mode" case, which looked like
diff --git a/oldname.c b/oldname.c
new mode 100755
old mode 100644
and thus _only_ had the name in the (normally ambiguous wrt whitepsace)
header line.
^ permalink raw reply
* Re: git bug?
From: Jeff Garzik @ 2005-06-09 7:39 UTC (permalink / raw)
To: Git Mailing List
In-Reply-To: <42A7E7AD.5030108@pobox.com>
Jeff Garzik wrote:
>
> Just upgraded to the latest git, pulled the latest Linux kernel tree,
> and made a local modification. Here is the strange git-diff-cache output:
>
>> [jgarzik@pretzel libata-dev]$ git-diff-cache -p HEAD diff --git
>> a/arch/arm/mm/minicache.c b/arch/arm/mm/minicache.c
>> deleted file mode 100644
>> diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
>> --- a/drivers/scsi/libata-core.c
>> +++ b/drivers/scsi/libata-core.c
>> @@ -3059,8 +3059,6 @@ static void __ata_qc_complete(struct ata
>> struct ata_port *ap = qc->ap;
>> unsigned int tag, do_clear = 0;
>>
>> - WARN_ON(!assert_spin_locked(&ap->host_set->lock));
>> -
>> if (likely(qc->flags & ATA_QCFLAG_ACTIVE)) {
>> assert(ap->queue_depth);
>> ap->queue_depth--;
>
>
> The libata-core part is correct, the arch/arm/mm part is not.
>
> I pulled using unmodified, upstream git scripts. No sugar added. Here's
> what the repo looks like:
>
>> [jgarzik@pretzel libata-dev]$ git-rev-list --pretty HEAD ^master |
>> git-shortlog Jeff Garzik:
>> Merge /spare/repo/linux-2.6/
>> Automatic merge of /spare/repo/linux-2.6/.git branch HEAD
>>
>> Jens Axboe:
>> libata: fix spinlock bug introduced by NCQ code
>> libata: ncq support update
>> libata-scsi: better placement of cmd completion on err
>> SATA NCQ support
>
>
> It's mirroring to kernel.org right now, at
> rsync.kernel.org://...jgarzik/libata-dev.git if somebody wants to poke
> at it. Top of tree is d032ec9048ff82a704b96b93cfd6f2e8e3a06b19 (when
> fully uploaded and mirrored).
Well, after switching branches and switching back, the behavior is gone.
<shrug>
Jeff
^ permalink raw reply
* [PATCH 3/3] read-tree -m 3-way: handle more trivial merges internally
From: Junio C Hamano @ 2005-06-09 7:06 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7voeagrp11.fsf_-_@assigned-by-dhcp.cox.net>
This patch teaches "read-tree -m O A B" that some more trivial
cases can be handled internally. This allows us to loosen
otherwise too strict index requirements in case #5ALT, where
both branches create a new file identically --- the previous
code required index to be up-to-date and aborted the merge when
it is not, but there is no reason to require it to be up-to-date
in this case.
The test vector has been updated to match the new behaviour as
well.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
read-tree.c | 16 ++++++++++++++++
t/t1000-read-tree-m-3way.sh | 27 +++++++++------------------
2 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -69,6 +69,12 @@ static struct cache_entry *merge_entries
if (same(o,b))
return a;
}
+ /* #5ALT */
+ if (!o && a && b && same(a,b)) {
+ /* Match what git-merge-one-file-script does */
+ printf("Adding %s\n", a->name);
+ return a;
+ }
return NULL;
}
@@ -161,6 +167,16 @@ static int threeway_merge(struct cache_e
return merged_entry(merge, i, dst);
if (i)
verify_uptodate(i);
+
+ /* #6ALT, #8ALT, and #10ALT */
+ if ((o && !a && !b) ||
+ (o && !a && b && same(o, b)) ||
+ (o && a && !b && same(o, a))) {
+ /* Match what git-merge-one-file-script does */
+ printf("Removing %s\n", o->name);
+ return 0;
+ }
+
count = 0;
if (o) { *dst++ = o; count++; }
if (a) { *dst++ = a; count++; }
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -75,21 +75,18 @@ In addition:
. ../lib-read-tree-m-3way.sh
################################################################
-# This is the "no trivial merge unless all three exists" table.
+# Trivial "majority when 3 stages exist" merge plus #5ALT, #6ALT,
+# #8ALT, #10ALT trivial merges.
cat >expected <<\EOF
100644 X 2 AA
100644 X 3 AA
100644 X 2 AN
-100644 X 1 DD
100644 X 3 DF
100644 X 2 DF/DF
100644 X 1 DM
100644 X 3 DM
-100644 X 1 DN
-100644 X 3 DN
-100644 X 2 LL
-100644 X 3 LL
+100644 X 0 LL
100644 X 1 MD
100644 X 2 MD
100644 X 1 MM
@@ -97,8 +94,6 @@ cat >expected <<\EOF
100644 X 3 MM
100644 X 0 MN
100644 X 3 NA
-100644 X 1 ND
-100644 X 2 ND
100644 X 0 NM
100644 X 0 NN
100644 X 0 SS
@@ -108,11 +103,8 @@ cat >expected <<\EOF
100644 X 2 Z/AA
100644 X 3 Z/AA
100644 X 2 Z/AN
-100644 X 1 Z/DD
100644 X 1 Z/DM
100644 X 3 Z/DM
-100644 X 1 Z/DN
-100644 X 3 Z/DN
100644 X 1 Z/MD
100644 X 2 Z/MD
100644 X 1 Z/MM
@@ -120,8 +112,6 @@ cat >expected <<\EOF
100644 X 3 Z/MM
100644 X 0 Z/MN
100644 X 3 Z/NA
-100644 X 1 Z/ND
-100644 X 2 Z/ND
100644 X 0 Z/NM
100644 X 0 Z/NN
EOF
@@ -289,23 +279,24 @@ test_expect_failure \
git-read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
- '5 - must match and be up-to-date in !O && A && B && A==B case.' \
+ '5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
git-update-cache --add LL &&
git-read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '5 (fail) - must match and be up-to-date in !O && A && B && A==B case.' \
+test_expect_success \
+ '5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
git-update-cache --add LL &&
echo extra >>LL &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
test_expect_failure \
- '5 (fail) - must match and be up-to-date in !O && A && B && A==B case.' \
+ '5 (fail) - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
echo extra >>LL &&
------------
^ permalink raw reply
* [PATCH 2/3] read-tree -m 3-way: loosen index requirements that is too strict.
From: Junio C Hamano @ 2005-06-09 7:05 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7voeagrp11.fsf_-_@assigned-by-dhcp.cox.net>
This patch teaches "read-tree -m O A B" that, when only "the
other tree" changed a path, and if the work tree already has
that change, we are not in a situation that would clobber the
cache and the working tree, and lets the merge succeed; this is
case #14ALT in t1000 test.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
read-tree.c | 16 ++++++++++++++++
t/t1000-read-tree-m-3way.sh | 9 +++++++++
2 files changed, 25 insertions(+), 0 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -131,6 +131,22 @@ static int threeway_merge(struct cache_e
struct cache_entry *merge;
int count;
+ /* The case #14ALT is special in that it allows "i" to match
+ * the "merged branch", aka "b" and even be dirty, as an
+ * alternative to the usual 'must match "a" and be up-to-date'
+ * rule.
+ */
+ if (o && a && b && same(o, a) && !same(o, b)) {
+ if (i) {
+ if (same(i, b))
+ ; /* case #14ALT exception */
+ else if (same(i, a))
+ verify_uptodate(i);
+ else
+ return -1;
+ }
+ }
+ else /* otherwise the original rule applies */
/*
* If we have an entry in the index cache ("i"), then we want
* to make sure that it matches any entries in stage 2 ("first
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -455,6 +455,15 @@ test_expect_success \
git-read-tree -m $tree_O $tree_A $tree_B &&
check_result"
+test_expect_success \
+ '14ALT - in O && A && B && O==A && O!=B case, matching B is also OK' \
+ "rm -f .git/index NM &&
+ cp .orig-B/NM NM &&
+ git-update-cache --add NM &&
+ echo extra >>NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
test_expect_failure \
'14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
------------
^ permalink raw reply
* [PATCH 1/3] read-tree.c: rename local variables used in 3-way merge code.
From: Junio C Hamano @ 2005-06-09 7:04 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7voeagrp11.fsf_-_@assigned-by-dhcp.cox.net>
I'd hate to do this, but every time I try to touch this code and
validate what it does against the case matrix in t1000 test, I
get confused. The variable names are renamed to match the case
matrix. Now they are named as:
i -- entry from the index file (formerly known as "old")
o -- merge base (formerly known as "a")
a -- our head (formerly known as "b")
b -- merge head (formerly known as "c")
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
read-tree.c | 40 ++++++++++++++++++++--------------------
1 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -40,9 +40,9 @@ static int same(struct cache_entry *a, s
* This removes all trivial merges that don't change the tree
* and collapses them to state 0.
*/
-static struct cache_entry *merge_entries(struct cache_entry *a,
- struct cache_entry *b,
- struct cache_entry *c)
+static struct cache_entry *merge_entries(struct cache_entry *o,
+ struct cache_entry *a,
+ struct cache_entry *b)
{
/*
* Ok, all three entries describe the same
@@ -58,16 +58,16 @@ static struct cache_entry *merge_entries
* The "all entries exactly the same" case falls out as
* a special case of any of the "two same" cases.
*
- * Here "a" is "original", and "b" and "c" are the two
+ * Here "o" is "original", and "a" and "b" are the two
* trees we are merging.
*/
- if (a && b && c) {
- if (same(b,c))
- return c;
+ if (o && a && b) {
if (same(a,b))
- return c;
- if (same(a,c))
return b;
+ if (same(o,a))
+ return b;
+ if (same(o,b))
+ return a;
}
return NULL;
}
@@ -126,29 +126,29 @@ static int merged_entry(struct cache_ent
static int threeway_merge(struct cache_entry *stages[4], struct cache_entry **dst)
{
- struct cache_entry *old = stages[0];
- struct cache_entry *a = stages[1], *b = stages[2], *c = stages[3];
+ struct cache_entry *i = stages[0];
+ struct cache_entry *o = stages[1], *a = stages[2], *b = stages[3];
struct cache_entry *merge;
int count;
/*
- * If we have an entry in the index cache ("old"), then we want
+ * If we have an entry in the index cache ("i"), then we want
* to make sure that it matches any entries in stage 2 ("first
- * branch", aka "b").
+ * branch", aka "a").
*/
- if (old) {
- if (!b || !same(old, b))
+ if (i) {
+ if (!a || !same(i, a))
return -1;
}
- merge = merge_entries(a, b, c);
+ merge = merge_entries(o, a, b);
if (merge)
- return merged_entry(merge, old, dst);
- if (old)
- verify_uptodate(old);
+ return merged_entry(merge, i, dst);
+ if (i)
+ verify_uptodate(i);
count = 0;
+ if (o) { *dst++ = o; count++; }
if (a) { *dst++ = a; count++; }
if (b) { *dst++ = b; count++; }
- if (c) { *dst++ = c; count++; }
return count;
}
------------
^ permalink raw reply
* [PATCH 0/3] Handling merge conflicts a bit more gracefully
From: Junio C Hamano @ 2005-06-09 7:02 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506081629370.2286@ppc970.osdl.org>
This series consists of three patches.
[PATCH 1/3] read-tree.c: rename local variables used in 3-way merge code.
[PATCH 2/3] read-tree -m 3-way: loosen index requirements that is too strict.
[PATCH 3/3] read-tree -m 3-way: handle more trivial merges internally
You may have noticed that I already described some "alternative
semantics" in the 3-way merge test script t1000. This set of
patches implements some of them, namely the following 5 cases:
O A B result index requirements
-------------------------------------------------------------------
5 missing exists A==B take A must match A, if exists.
------------------------------------------------------------------
6 exists missing missing remove must not exist.
------------------------------------------------------------------
8 exists missing O==B remove must not exist.
------------------------------------------------------------------
10 exists O==A missing remove must match A and be
up-to-date, if exists.
------------------------------------------------------------------
14 exists O==A O!=B take B if exists, must either (1)
match A and be up-to-date,
or (2) match B.
-------------------------------------------------------------------
The first patch is to match the local variable names used in the
functions involved to the names used in the case matrix.
Case #14 is resolved identically as the old code does, but the
index requirement old code placed on this case was stricter than
necessary. In this case, satisfying the usual rule of "match A
and be up-to-date if exists" is certainly OK, but additionally,
if the original index matches the tree being merged (without
even being up-to-date) is also permissible, because there would
be no information loss or work-tree clobbering if we allowed it.
The second patch in the series corrects this.
Case #5, #6, #8, and #10 were traditionally kept unmerged in the
index file when read-tree is done, and resolving them was left
to the script. By resolving these internally, we can loosen the
index requirements without compromising correctness for case #5.
Other three cases could still be left for the "script policy"
because this change does not affect the index requirements for
these cases, but it was simple enough to implement them and this
would not be too controversial a change. The third patch in the
series implements these changes.
^ permalink raw reply
* git bug?
From: Jeff Garzik @ 2005-06-09 6:54 UTC (permalink / raw)
To: Git Mailing List
Just upgraded to the latest git, pulled the latest Linux kernel tree,
and made a local modification. Here is the strange git-diff-cache output:
> [jgarzik@pretzel libata-dev]$ git-diff-cache -p HEAD
> diff --git a/arch/arm/mm/minicache.c b/arch/arm/mm/minicache.c
> deleted file mode 100644
> diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
> --- a/drivers/scsi/libata-core.c
> +++ b/drivers/scsi/libata-core.c
> @@ -3059,8 +3059,6 @@ static void __ata_qc_complete(struct ata
> struct ata_port *ap = qc->ap;
> unsigned int tag, do_clear = 0;
>
> - WARN_ON(!assert_spin_locked(&ap->host_set->lock));
> -
> if (likely(qc->flags & ATA_QCFLAG_ACTIVE)) {
> assert(ap->queue_depth);
> ap->queue_depth--;
The libata-core part is correct, the arch/arm/mm part is not.
I pulled using unmodified, upstream git scripts. No sugar added.
Here's what the repo looks like:
> [jgarzik@pretzel libata-dev]$ git-rev-list --pretty HEAD ^master | git-shortlog
> Jeff Garzik:
> Merge /spare/repo/linux-2.6/
> Automatic merge of /spare/repo/linux-2.6/.git branch HEAD
>
> Jens Axboe:
> libata: fix spinlock bug introduced by NCQ code
> libata: ncq support update
> libata-scsi: better placement of cmd completion on err
> SATA NCQ support
It's mirroring to kernel.org right now, at
rsync.kernel.org://...jgarzik/libata-dev.git if somebody wants to poke
at it. Top of tree is d032ec9048ff82a704b96b93cfd6f2e8e3a06b19 (when
fully uploaded and mirrored).
Jeff
^ permalink raw reply
* Re: gitk-1.1 out
From: Paul Mackerras @ 2005-06-09 4:46 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506080743040.2286@ppc970.osdl.org>
Linus Torvalds writes:
> Can you do the same for things in .git/refs/heads? Possibly using another
> color?
Sure - the hardest part is going to be deciding on the shape and color
to use. :) Perhaps I'll try just using a green rectangle with black
on white text inside and see if that stands out enough.
> I realize that that may sound silly, but a tree that has many branches can
> validly be used with gitk with something like this:
>
> gitk $(ls .git/refs/heads)
Um, did I break that when I changed to using git-rev-list instead of
git-rev-tree?
Paul.
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Junio C Hamano @ 2005-06-09 5:15 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506082145160.2286@ppc970.osdl.org>
>>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
LT> On Wed, 8 Jun 2005, Junio C Hamano wrote:
>> >>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
>>
LT> Yeah, ok, so the fact that we allow missing things in the
LT> index (which was debatable to start with) makes for
LT> exceptions.
>>
>> Not just that. Another big difference is that we allow _extra_
>> things in the index in two-tree case (i.e. local additions).
>> But I do not think these exceptions are necessarily bad.
LT> Well, they'd be bad in a three-way merge.
No question about it. I am not proposing to conditionally
accept extra entries in 3-way case.
But when "read-tree -m H I-mixed-with-H M" 3-way merge is
emulating "read-tree -m H M", it does not need to accept any
extra entries in the cache, because in this case "our head" tree
is "I-mixed-with-H", which by definition contains everything in
the current cache (remember, "I-mixed-with-H" is built by
looking at each path and if it has stage0 then copy it to stage2
otherwise if it has stage1 then copy it to stage2, after reading
the index file into stage0, H into stage1, and M into stage 3).
And after such "3-way merge emulating 2-way fast-forward," you
can commit---the commit will have a single parent, M, and the
difference it contains is the changes the user made while he was
working off of H, rebased to M.
Of course this all assumes that we have a perfectly working
three-way merge ;-).
^ permalink raw reply
* Re: 7fb9de4a830dd8969bc17a219c509a76dd3c9aad
From: Jon Seymour @ 2005-06-06 16:05 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506060858140.1876@ppc970.osdl.org>
On 6/7/05, Linus Torvalds <torvalds@osdl.org> wrote:
>
>
> On Mon, 6 Jun 2005 jon@blackcubes.dyndns.org wrote:
> >
> > [PATCH] Modify git-rev-list to linearise the commit history in merge order.
>
> Much nicer. Will apply after testing, however, not this part:
>
> > @@ -110,6 +145,8 @@ static enum cmit_fmt get_commit_format(c
> > if (!strcmp(arg, "=short"))
> > return CMIT_FMT_SHORT;
> > usage(rev_list_usage);
> > +
> > + return CMIT_FMT_DEFAULT;
> > }
> >
> >
>
> If you're bothered by a compiler warning (that I don't see, wonder why?),
> please mark usage() and die() with "__attribute__ ((__noreturn__))". Make
> it depend on GCC, ie
>
> #ifdef __GCC__
> #define NO_RETURN __attribute__((__noreturn__))
> #else
> #define NO_RETURN
> #endif
>
> and then do
>
> extern void die(const char *, ...) NO_RETURN;
>
> or something like that.
>
> Anyway, that's indepdendent of this patch, but just fyi.
>
> Linus
>
Here's my gcc version string:
gcc (GCC) 3.3.3 20040412 (Red Hat Linux 3.3.3-7)
Shall I resend you the patch with that taken out?
jon.
--
homepage: http://www.zeta.org.au/~jon/
blog: http://orwelliantremors.blogspot.com/
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Linus Torvalds @ 2005-06-09 4:54 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vfyvsuoz3.fsf@assigned-by-dhcp.cox.net>
On Wed, 8 Jun 2005, Junio C Hamano wrote:
> >>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
>
> LT> Yeah, ok, so the fact that we allow missing things in the
> LT> index (which was debatable to start with) makes for
> LT> exceptions.
>
> Not just that. Another big difference is that we allow _extra_
> things in the index in two-tree case (i.e. local additions).
> But I do not think these exceptions are necessarily bad.
Well, they'd be bad in a three-way merge.
The reason they aren't bad in a two-way merge is that you don't commit the
result - the commits have been done already.
That's really the big conceptual difference between two-way and three-way:
never mind the merge algorithm itself.
(In fact, in many ways, two-way merges are really just the same as a
one-way merge, except it now knows where it came from, so it can do sanity
checking).
As to working tree changes:
> which means we are exactly in the same situation as "merge I and
> M pivoting on H" three-way merge, with a dirty work tree. Any
> solution and help we would give to the end-user for the
> three-way case would automatically help this two-way case,
> wouldn't it?
Yes.
In fact, there's a fairly simple solution, which is to remove the current
check for "verify_uptodate()" and instead replace it with the "update"
phase not just writing the file, but actually doing a three-way merge on
it.
NOTE! This would not affect the resulting _tree_ in any way at all. It
would literally only affect how we write out the working directory. Right
now we just fail when the working file isn't up-to-date, and that could be
replaced with instead doing a
merge W I M
where "W" is the working file, "I" is the index file, and "M" is the merge
result that we currently just write out directly.
In the special case of I == M, we already do _exactly_ this: we know that
since I=M, the merge will be W, so we don't do the update at all.
So in fact, doing a 3-way merge is really a generalization of what we
already do, and removes a failure case.
NOTE! This 3way merge is fundamentally _different_ from the 3-way merge
that is done by "git-merge-one-file-script" that we already do. _That_
3-way merge is done not on the working files, but on the results in the
trees, while this new 3way merge would be done purely in the working
directory (ie it wouldn't make sense without the "-u" flag).
If we do this, I'd personally suggest it be another flag, possibly "-u3"
instead of just plain "-u".
Linus
^ permalink raw reply
* Re: git-rev-list --merge-order hangs
From: Jon Seymour @ 2005-06-08 14:17 UTC (permalink / raw)
To: Radoslaw Szkodzinski; +Cc: Git Mailing List
In-Reply-To: <2cfc4032050608020215152887@mail.gmail.com>
Ok, I reproduced exponential behaviour when a commit with no parents
is merged near the head of the kernel and a ^ argument is specified.
Fortunately a work-around is a one line change - a patch will be
forthcoming shortly.
The workaround will still require a full-graph scan but that is linear
rather than
exponential so is tolerable.
A better solution which will not require a full-graph scan will be
delivered in a future
patch.
Thanks for the report.
jon.
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Junio C Hamano @ 2005-06-09 4:35 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506081936370.2286@ppc970.osdl.org>
>>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
LT> Yeah, ok, so the fact that we allow missing things in the
LT> index (which was debatable to start with) makes for
LT> exceptions.
Not just that. Another big difference is that we allow _extra_
things in the index in two-tree case (i.e. local additions).
But I do not think these exceptions are necessarily bad.
And you are right that two-tree is _very_ different from
three-way merge.
LT> We could certainly be stricter about the index contents, and
LT> require that they match the branch we're merging from
LT> exactly, rather than be a subset.
I guess great minds do not always think alike. I was going in
quite the opposite direction. I vaguely recall saying this
before on this list ;-)
With the current three-way code, if I rewrite two-way merge
using the three-way "read-tree -m H I-mixed-with-H M" (emulated
two-tree fast forward, where "I" denotes "tree that would have
resulted from the original cache"), it would give quite
different results from the "carry forward" two-way code we have.
So in that sense, three-way and two-way are quite different.
I have, however, not convinced myself that this difference is
coming from some fundamental difference between two-tree fast
forward and three-way merge. If desirable results fall out
naturally for the "emulated two-way" case by handling three-way
case more carefully (e.g. not having stricter index requirements
than necessary), that would be wonderful. I think, for example,
there are places where we have too strict index requirements in
three-way merge (grep for '(ALT)' in t/t1000*.sh test file).
I probably am dreaming, though.
LT> I think the case that is more important (and more likely to
LT> hit people) is when they have something in their working
LT> tree that conflicts with the merge, and then what you want
LT> is really that the current "update" code do the three-way
LT> merge in the working directory, not that it's done on the
LT> index file contents.
LT> ..., but I don't think the index file is the most important
LT> case. The more important case is the one that the three-way
LT> merge doesn't handle either!
I agree with all of the above. Their working tree has changes
from H, and merging M into H conflicts with those changes. That
means, although they did not actually make a formal commit, what
they have is essentially this:
cache contents
is here
v
---I---
/ ^work tree contents is here
--H
\
----------M
which means we are exactly in the same situation as "merge I and
M pivoting on H" three-way merge, with a dirty work tree. Any
solution and help we would give to the end-user for the
three-way case would automatically help this two-way case,
wouldn't it?
I do not think index file is important either; maybe I am not
really understanding your argument. I fully accept the new
world order with today's merge-one-file-script changes, that the
merge result will be left in the work tree for the user to
verify and sort out. What I am trying to do in the above
picture is to help the end-user forward-porting differences in I
since H (along with the work tree changes since I) when doing a
fast-forward from H to M happens, using the files in the work
tree.
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Linus Torvalds @ 2005-06-09 4:11 UTC (permalink / raw)
To: Jeff Garzik; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <42A7B28A.9010508@pobox.com>
On Wed, 8 Jun 2005, Jeff Garzik wrote:
>
> If this merge-gracefully stuff is all checked into git.git, I can
> definitely give it some real-world testing.
Yup, all there. Not a _ton_ of testing exactly, but I did actually test
both the content conflict case and the "new directory" case. At least
once.
Linus
^ permalink raw reply
* [PATCH] Fix cogito handling of timezones
From: Frank Sorenson @ 2005-06-09 3:40 UTC (permalink / raw)
To: Git Mailing List, Petr Baudis
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Current cogito behavior treats the timezone offset as if it's decimal,
but most zone offsets begin with a 0. As a result, the computation
in cg-Xlib uses the zone offset as an octal number. -0700 looks like
4 1/2 hours offset, rather than 7, and -0800 is an invalid octal
number.
This patch fixes the behavior to strip off leading 0s. It's ugly,
but it should produce the right values until someone with better
bash scripting skills than I can fix it.
Signed-off-by: Frank Sorenson <frank@tuxrocks.com>
diff --git a/cg-Xlib b/cg-Xlib
- --- a/cg-Xlib
+++ b/cg-Xlib
@@ -6,6 +6,8 @@
# This file provides a library containing common code shared with all the
# Cogito programs.
+shopt -s extglob
+
_cg_cmd=${0##*/}
_git=${GIT_DIR:-.git}
@@ -51,8 +53,12 @@ showdate () {
[ "$format" ] || format=-R
sec=${date[0]}; tz=${date[1]}
if [ "$has_gnudate" ]; then
- - dtz=${tz/+/}
- - lsec=$(($dtz / 100 * 3600 + $dtz % 100 * 60 + $sec))
+ sign=${tz%%[0-9]*}
+ sign=${sign:?+}
+ dtz=${tz/[+-]}
+ dtz=${dtz##*(0)}
+ dtz=${dtz:?0}
+ lsec=$(($sec + $dtz % 100 $sign $dtz / 100 * 3600))
pdate="$(date -ud "1970-01-01 UTC + $lsec sec" "$format" 2>/dev/null)"
else
# FIXME: $format
Frank
- --
Frank Sorenson - KD7TZK
Systems Manager, Computer Science Department
Brigham Young University
frank@tuxrocks.com
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org
iD8DBQFCp7oXaI0dwg4A47wRAnOFAJ4jsaQodgxOr3gp8jMYhOxuJ98GFgCgnclC
Zd68hflXn8pV39zBF4YOlUc=
=C+hn
-----END PGP SIGNATURE-----
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Jeff Garzik @ 2005-06-09 3:07 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <Pine.LNX.4.58.0506081336080.2286@ppc970.osdl.org>
Linus Torvalds wrote:
> Comments? It would be good to have people test this and maybe even write a
> few automated tests that it all works as expected..
I've got a few libata branches I have been putting off updating to the
latest kernel, because of merge conflicts ('chs-support' and 'passthru'
branches of libata-dev.git).
If this merge-gracefully stuff is all checked into git.git, I can
definitely give it some real-world testing.
Jeff
^ permalink raw reply
* Re: Handling merge conflicts a bit more gracefully..
From: Linus Torvalds @ 2005-06-09 2:48 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vll5kxolo.fsf@assigned-by-dhcp.cox.net>
On Wed, 8 Jun 2005, Junio C Hamano wrote:
>
> Well, except that $I may validly be an empty tree ;-), so not
> quite.
Yeah, ok, so the fact that we allow missing things in the index (which was
debatable to start with) makes for exceptions.
We could certainly be stricter about the index contents, and require that
they match the branch we're merging from exactly, rather than be a subset.
That said, I'm not convinced that it's worth it, even if it means that a
two-way merge ends up acceping merges that a three-way one never would.
> In case it was not clear, where I am headed is this. I would
> like to rip out the two-tree "carry forward" implementation from
> read-tree, and replace it with:
Yeah, I see that, I'm just not entirely convinced it's a good idea.
The thing is, a two-way merge really _is_ very different from a three-way
one, in that it's a fast-forward, and the fact that it allows for things
that the more complex "full" case wouldn't allow I feel is something of an
advantage. And it _can_ allow them exactly because it's not the full case.
I like your concept:
> When/if that happens, then the current fast-forward code would
> need to be changed from:
>
> read-tree -m $H $M && echo $M >.git/HEAD
>
> to
>
> read-tree -m $H $M &&
> if unmerged paths in the resulting cache
> then
> merge-cache -o merge-one-file-script -a
> fi &&
> echo $M >.git/HEAD
>
> and the user's local changes since H when fast forwarding to M
> would be handled with the same workflow as the three-way case.
but that one doesn't really help the case of stuff he hasn't marked
up-dated, so I think that's actually a special case that _isn't_ the
important one. I think the case that is more important (and more likely to
hit people) is when they have something in their working tree that
conflicts with the merge, and then what you want is really that the
current "update" code do the three-way merge in the working directory, not
that it's done on the index file contents.
So I see where you are coming from, but I don't think the index file is
the most important case. The more important case is the one that the
three-way merge doesn't handle either!
Linus
^ permalink raw reply
* Re: Upstream merging and conflicts (was Re: Using cvs2git to track an external CVS project)
From: Linus Torvalds @ 2005-06-09 2:34 UTC (permalink / raw)
To: Martin Langhoff; +Cc: Git Mailing List
In-Reply-To: <46a038f905060815096382636a@mail.gmail.com>
On Thu, 9 Jun 2005, Martin Langhoff wrote:
> On 6/9/05, Linus Torvalds <torvalds@osdl.org> wrote:
> > Yes, and right now that "manual" part is actualyl fairly high. I'll fix
> > that thing up (today, I hope), making nontrivial merges much easier.
>
> Wow -- I'll be tracking your tree closely then ;-)
Well, it's done, and it's now "much easier" in the sense that anything
that doesn't have metadata changes should be picked up pretty trivially by
the three-way merge thing.
But if you move things around, then you'd need to have a merge that is
aware of movement, ie something much more sophisticated than just 3way
merge.
> > It's true that you can't skip patches, since a merge is always
> > all-or-nothing, and the global history really requires that (in a very
> > fundamental way). However, you _can_ merge from the HEAD, and then say
> > "that patch is no longer relevant in this tree", and remove it. Then
> > you'll never need to worry about that patch any more, because you've
> > merged it and in your merged result, it no longer exists).
>
> I had that strategy in my back-pocket already, but it doesn't sound right.
With global history, you really don't end up having much choice.
The alternative is to have per-file history, which sucks pretty bad
(you're screwed if you have a file that is touched by two things, so it
just moves the problem a bit, but more importantly you now have all the
CVS crud), or then you have to play games like arch or darcs which is all
about re-ordering patches (and then your history is totally malleable,
with all the problems that entails).
> > Yes. I think this is fundamental. "git" needs to converge. It's how git
> > works. git also "crystallizes" the history and makes it unbreakable.
> > Both of these things are _wonderful_ for what they mean, but both of these
> > fundamental issues are _horrible_ for other reasons.
>
> Fair enough -- and actually I'm not convinced it's a horrible thing.
It's absolutely not horrible, but it limits how you work. _I_ think it
limits you in good ways, but it's definitely a limitation.
> Having worked with forever-diverging tools like Arch, I can appreciate
> the value of crystallizing and identifying when you've converged --
> and rebasing all steps forward on the fact that you've converged. This
> is huge.
My gut feel is that it should be possible to have a hybrid system that
handles both the solid "crystalline" phase (aka git) and the "gas" phase
(aka free-lowing patches) and have them integrate with each other well.
That's kind of the way the kernel works, with people using quilt as a way
to capture the patches in between.
My read is that this analogy arch and darcs try to avoid the really solid
crystalline phase entirely and end up being amorphous. You can probably
have that too, but on the other hand it's fairly easy to merge between two
"crystallized" repositories and be totally unambigious about what the
result is, but if there's a lot of the amorpous stuff going on, it's not
clear any more.
> > Whether the kernel model is applicable to anything else, I dunno.
>
> I don't know either -- but I'm sure the toolset around git can support
> a range of dev models. I don't think any other project has such a
> large pool and strong convergence dynamics as the kernel. But git and
> its tools and practices can be (I'm hoping) quite flexible to support
> a range of dev models.
Hey, I obviously think you're right. Using git gives good ways of
communicating the core infrastructure between two (or more) groups, while
then internally within the group they may use loser patch-tracking systems
that don't have the same kind of convergence requirements (but which you
don't need for a "small" set of patches anyway, where "small" can
obviously be hundreds of internal patches).
Linus
^ 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