Re: [RFC PATCH v4] revision: new rev^-n shorthand for rev^n..rev

2016-09-26 Thread Jeff King
On Mon, Sep 26, 2016 at 02:55:45PM -0700, Junio C Hamano wrote:

> Taking these two together, perhaps squashing this in may be
> sufficient.
> [...]
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index 2c3da19..9474c37 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -333,8 +333,22 @@ static int try_parent_shorthands(const char *arg)
>   if (include_rev)
>   show_rev(NORMAL, sha1, arg);
>   commit = lookup_commit_reference(sha1);
> +
> + if (exclude_parent) {
> + /* do we have enough parents? */
> + for (parent_number = 0, parents = commit->parents;
> +  parents;
> +  parents = parents->next)
> + parent_number++;
> + if (parent_number < exclude_parent) {
> + *dotdot = '^';
> + return 0;
> + }
> + }

I think you can use commit_list_count() to make this a bit shorter,
like:

  if (exclude_parent &&
  commit_list_count(commit->parents) < exclude_parent) {
  *dotdot = '^';
  return 0;
  }

Technically you can drop the first half of the &&, but it is probably a
good idea to avoid the traversal when exclude_parent is not in use.

Also technically, you can stop counting when you hit exclude_parent
(which is only possible with a custom traversal), but it is unlikely
enough that it is probably not worth caring about.

-Peff


Re: [RFC PATCH v4] revision: new rev^-n shorthand for rev^n..rev

2016-09-26 Thread Junio C Hamano
Vegard Nossum  writes:

> +test_expect_success 'rev-parse merge^-0' '
> + test_must_fail git rev-parse merge^-0
> +'
> +
> +test_expect_success 'rev-parse merge^-3' '
> + test_must_fail git rev-parse merge^-3
> +'
> +
> +test_expect_success 'rev-parse merge^-^' '
> + test_must_fail git rev-parse merge^-^
> +'
> +
> +test_expect_success 'rev-list merge^-0' '
> + test_must_fail git rev-list merge^-0
> +'
> +
> +test_expect_success 'rev-list merge^-3' '
> + test_must_fail git rev-list merge^-3
> +'
> +
> +test_expect_success 'rev-list merge^-^' '
> + test_must_fail git rev-list merge^-^
> +'

This seems to be testing failure cases fairly thoroughly, which is a
good sign.  One thing not tested is "merge^-1x" to ensure that no
change mistakenly will break the strtoul() check you have to parse
the parent number in the future, but other than that (and possibly
reusing the set-up of an already existing test), I am fairly happy
with the tests in this patch.

Thanks.




Re: [RFC PATCH v4] revision: new rev^-n shorthand for rev^n..rev

2016-09-26 Thread Junio C Hamano
Junio C Hamano  writes:

> Micronit.  When splitting "for (init; fini; cont)" into multiple
> lines, it is often easier to read to make that into three lines:
>
>   for (parent_number = 1, parents = commit->parents;
>parents;
>parents = parents->next, parent_number++) {
>
>> +if (exclude_parent && parent_number != exclude_parent)
>> +continue;
>> +
>> +show_rev(include_parents ? NORMAL : REVERSED,
>> + parents->item->object.oid.hash, arg);
>> +}
>
> It is very clear to see what is going on.  Good job.
>
>>  *dotdot = '^';
>> +if (exclude_parent >= parent_number)
>> +return 0;
>
> This is not quite nice.  You've already called show_rev() number of
> times, and it is too late to signal an error here.  I think you
> would need to count the number of parents much earlier when
> exclude_parent option is in effect and error out before making any
> call to show_rev().
> ...
> Likewise.  It is way too late to say "Nah, this wasn't a valid rev^-
> notation after all" to the caller after calling add_rev_cmdline()
> and add_pending_object() in the above loop.

Taking these two together, perhaps squashing this in may be
sufficient.

Please do not use --no-prefix when sending a patch to this list, by
the way.

 builtin/rev-parse.c | 18 +++---
 revision.c  | 17 -
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 2c3da19..9474c37 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -333,8 +333,22 @@ static int try_parent_shorthands(const char *arg)
if (include_rev)
show_rev(NORMAL, sha1, arg);
commit = lookup_commit_reference(sha1);
+
+   if (exclude_parent) {
+   /* do we have enough parents? */
+   for (parent_number = 0, parents = commit->parents;
+parents;
+parents = parents->next)
+   parent_number++;
+   if (parent_number < exclude_parent) {
+   *dotdot = '^';
+   return 0;
+   }
+   }
+
for (parent_number = 1, parents = commit->parents;
-parents; parents = parents->next, parent_number++) {
+parents;
+parents = parents->next, parent_number++) {
if (exclude_parent && parent_number != exclude_parent)
continue;
 
@@ -343,8 +357,6 @@ static int try_parent_shorthands(const char *arg)
}
 
*dotdot = '^';
-   if (exclude_parent >= parent_number)
-   return 0;
return 1;
 }
 
diff --git a/revision.c b/revision.c
index 511e1ed..09da7f4 100644
--- a/revision.c
+++ b/revision.c
@@ -1318,8 +1318,23 @@ static int add_parents_only(struct rev_info *revs, const 
char *arg_, int flags,
if (it->type != OBJ_COMMIT)
return 0;
commit = (struct commit *)it;
+
+   if (exclude_parent) {
+   struct commit_list *parents;
+   int parent_number;
+
+   /* do we have enough parents? */
+   for (parent_number = 0, parents = commit->parents;
+parents;
+parents = parents->next)
+   parent_number++;
+   if (parent_number < exclude_parent)
+   return 0;
+   }
+
for (parent_number = 1, parents = commit->parents;
-parents; parents = parents->next, parent_number++) {
+parents;
+parents = parents->next, parent_number++) {
if (exclude_parent && parent_number != exclude_parent)
continue;
 


Re: [RFC PATCH v4] revision: new rev^-n shorthand for rev^n..rev

2016-09-26 Thread Junio C Hamano
Vegard Nossum  writes:

> I often use rev^..rev to get all the commits in the branch that was merged
> in by the merge commit 'rev' (including the merge itself). To save typing
> (or copy-pasting, if the rev is long -- like a full SHA-1 or branch name)
> we can make rev^- a shorthand for that.
>
> The existing syntax rev^! seems like it should do the same thing, but it
> doesn't really do the right thing for merge commits (it doesn't include
> the commits from side branches).
>
> As a natural generalisation, we also accept rev^-n where n excludes the
> nth parent of rev. For example, for a two-parent merge, you can use rev^-2
> to get the set of commits which were made to the main branch while the
> topic branch was prepared.

I am tempted to suggest that this four-line paragraph may be
sufficient:

"git log rev^..rev" is commonly used to show all work done on
and merged from a side branch.  Introduce a short-hand "rev^-"
for this, and also allow it to take "rev^-$n" to mean "reachable
from rev, excluding what is reachable from n-th parent of rev".

This alone is not a strong enough reason to ask you to reroll the
patch.

> diff --git builtin/rev-parse.c builtin/rev-parse.c
> index 76cf05e..2c3da19 100644
> --- builtin/rev-parse.c
> +++ builtin/rev-parse.c
> @@ -298,14 +298,30 @@ static int try_parent_shorthands(const char *arg)
>   unsigned char sha1[20];
>   struct commit *commit;
>   struct commit_list *parents;
> - int parents_only;
> -
> - if ((dotdot = strstr(arg, "^!")))
> - parents_only = 0;
> - else if ((dotdot = strstr(arg, "^@")))
> - parents_only = 1;
> -
> - if (!dotdot || dotdot[2])
> + int parent_number;
> + int include_rev = 0;
> + int include_parents = 0;
> + int exclude_parent = 0;
> +
> + if ((dotdot = strstr(arg, "^!"))) {
> + include_rev = 1;
> + if (dotdot[2])
> + return 0;
> + } else if ((dotdot = strstr(arg, "^@"))) {
> + include_parents = 1;
> + if (dotdot[2])
> + return 0;
> + } else if ((dotdot = strstr(arg, "^-"))) {
> + include_rev = 1;
> + exclude_parent = 1;
> +
> + if (dotdot[2]) {
> + char *end;
> + exclude_parent = strtoul(dotdot + 2, &end, 10);
> + if (*end != '\0' || !exclude_parent)
> + return 0;
> + }
> + } else
>   return 0;

Nice; we can tell where this is going without looking at the rest,
which is a very good sign that the new variables are doing their
work of telling the readers what is going on clearly.

> @@ -314,14 +330,21 @@ static int try_parent_shorthands(const char *arg)
>   return 0;
>   }
>  
> - if (!parents_only)
> + if (include_rev)
>   show_rev(NORMAL, sha1, arg);
>   commit = lookup_commit_reference(sha1);
> - for (parents = commit->parents; parents; parents = parents->next)
> - show_rev(parents_only ? NORMAL : REVERSED,
> - parents->item->object.oid.hash, arg);
> + for (parent_number = 1, parents = commit->parents;
> +  parents; parents = parents->next, parent_number++) {

Micronit.  When splitting "for (init; fini; cont)" into multiple
lines, it is often easier to read to make that into three lines:

for (parent_number = 1, parents = commit->parents;
 parents;
 parents = parents->next, parent_number++) {

> + if (exclude_parent && parent_number != exclude_parent)
> + continue;
> +
> + show_rev(include_parents ? NORMAL : REVERSED,
> +  parents->item->object.oid.hash, arg);
> + }

It is very clear to see what is going on.  Good job.

>   *dotdot = '^';
> + if (exclude_parent >= parent_number)
> + return 0;

This is not quite nice.  You've already called show_rev() number of
times, and it is too late to signal an error here.  I think you
would need to count the number of parents much earlier when
exclude_parent option is in effect and error out before making any
call to show_rev().

> diff --git revision.c revision.c
> index 969b3d1..9ae95bf 100644
> --- revision.c
> +++ revision.c
> @@ -1289,12 +1289,14 @@ void add_index_objects_to_pending(struct rev_info 
> *revs, unsigned flags)
>   }
>  }
>  
> -static int add_parents_only(struct rev_info *revs, const char *arg_, int 
> flags)
> +static int add_parents_only(struct rev_info *revs, const char *arg_, int 
> flags,
> + int exclude_parent)
>  {
>   unsigned char sha1[20];
>   struct object *it;
>   struct commit *commit;
>   struct commit_list *parents;
> + int parent_number;
>   const char *arg = arg_;
>  
>   if (*arg == '^') {
> @@ -1316,12 +1318,18 @@ static int add_parents_only(struct rev_info *re

[RFC PATCH v4] revision: new rev^-n shorthand for rev^n..rev

2016-09-26 Thread Vegard Nossum
I often use rev^..rev to get all the commits in the branch that was merged
in by the merge commit 'rev' (including the merge itself). To save typing
(or copy-pasting, if the rev is long -- like a full SHA-1 or branch name)
we can make rev^- a shorthand for that.

The existing syntax rev^! seems like it should do the same thing, but it
doesn't really do the right thing for merge commits (it doesn't include
the commits from side branches).

As a natural generalisation, we also accept rev^-n where n excludes the
nth parent of rev. For example, for a two-parent merge, you can use rev^-2
to get the set of commits which were made to the main branch while the
topic branch was prepared.

Signed-off-by: Vegard Nossum 

---
[v2: Use ^- instead of % as suggested by Junio Hamano and use some
 common helper functions for parsing.]

[v3: Use 'struct object_id' instead of 'char[20]' and add some tests as
 suggested by Matthieu Moy; fix missing '-' in Documentation/revisions.txt
 as suggested by Ramsay Jones; misc changelog + documentation fixes as
 suggested by Philip Oakley.]

[v4: Documentation fixes and parsing rework suggested by Junio Hamano
 and add some more tests.]
---
 Documentation/revisions.txt | 17 +++-
 builtin/rev-parse.c | 47 +++--
 revision.c  | 32 +--
 t/t6070-rev-parent-exclusion.sh | 90 +
 4 files changed, 168 insertions(+), 18 deletions(-)
 create mode 100755 t/t6070-rev-parent-exclusion.sh

diff --git Documentation/revisions.txt Documentation/revisions.txt
index 4bed5b1..ba11b9c 100644
--- Documentation/revisions.txt
+++ Documentation/revisions.txt
@@ -283,7 +283,7 @@ empty range that is both reachable and unreachable from 
HEAD.
 
 Other {caret} Parent Shorthand Notations
 ~
-Two other shorthands exist, particularly useful for merge commits,
+Three other shorthands exist, particularly useful for merge commits,
 for naming a set that is formed by a commit and its parent commits.
 
 The 'r1{caret}@' notation means all parents of 'r1'.
@@ -291,8 +291,15 @@ The 'r1{caret}@' notation means all parents of 'r1'.
 The 'r1{caret}!' notation includes commit 'r1' but excludes all of its parents.
 By itself, this notation denotes the single commit 'r1'.
 
+The '{caret}-{}' notation includes '' but excludes the th
+parent (i.e. a shorthand for '{caret}..'), with '' = 1 if
+not given. This is typically useful for merge commits where you
+can just pass '{caret}-' to get all the commits in the branch
+that was merged in merge commit '' (including ''
+itself).
+
 While '{caret}' was about specifying a single commit parent, these
-two notations consider all its parents. For example you can say
+three notations also consider its parents. For example you can say
 'HEAD{caret}2{caret}@', however you cannot say 'HEAD{caret}@{caret}2'.
 
 Revision Range Summary
@@ -326,6 +333,10 @@ Revision Range Summary
   as giving commit '' and then all its parents prefixed with
   '{caret}' to exclude them (and their ancestors).
 
+'{caret}-{}', e.g. 'HEAD{caret}-, HEAD{caret}-2'::
+   Equivalent to '{caret}..', with '' = 1 if not
+   given.
+
 Here are a handful of examples using the Loeliger illustration above,
 with each step in the notation's expansion and selection carefully
 spelt out:
@@ -339,6 +350,8 @@ spelt out:
CI J F C
B..C   = ^B CC
B...C  = B ^F C  G H D E B C
+   B^-= B^..B
+ = ^B^1 B  E I J F B
C^@= C^1
  = F   I J F
B^@= B^1 B^2 B^3
diff --git builtin/rev-parse.c builtin/rev-parse.c
index 76cf05e..2c3da19 100644
--- builtin/rev-parse.c
+++ builtin/rev-parse.c
@@ -298,14 +298,30 @@ static int try_parent_shorthands(const char *arg)
unsigned char sha1[20];
struct commit *commit;
struct commit_list *parents;
-   int parents_only;
-
-   if ((dotdot = strstr(arg, "^!")))
-   parents_only = 0;
-   else if ((dotdot = strstr(arg, "^@")))
-   parents_only = 1;
-
-   if (!dotdot || dotdot[2])
+   int parent_number;
+   int include_rev = 0;
+   int include_parents = 0;
+   int exclude_parent = 0;
+
+   if ((dotdot = strstr(arg, "^!"))) {
+   include_rev = 1;
+   if (dotdot[2])
+   return 0;
+   } else if ((dotdot = strstr(arg, "^@"))) {
+   include_parents = 1;
+   if (dotdot[2])
+   return 0;
+   } else if ((dotdot = strstr(arg, "^-"))) {
+   include_rev = 1;
+   exclude_parent = 1;
+
+   if (dotdot[2]) {
+   char *end;
+   exclude_parent = strtoul(dotdot + 2, &end, 10);
+   if (*end != '\0' || !exclude_parent)
+   return 0;
+   }
+