git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2] specifying ranges: we did not mean to make ".." an empty set

From
Junio C Hamano <gitster@pobox.com>
Date
May 2, 2011, 20:56 UTC
Message-ID
<7v62pslt2k.fsf_-_@alter.siamese.dyndns.org>
In-Reply-To
<7vhb9clu0n.fsf@alter.siamese.dyndns.org>

Either end of revision range operator can be omitted to default to HEAD, as in "origin.." (what did I do since I forked) or "..origin" (what did they do since I forked). But the current parser interprets ".." as an empty range "HEAD..HEAD", and worse yet, because ".." does exist on the filesystem, we get this annoying output:

  $ cd Documentation/howto
  $ git log .. ;# give me recent commits that touch Documentation/ area.
  fatal: ambiguous argument '..': both revision and filename
  Use '--' to separate filenames from revisions

Surely we could say "git log ../" or even "git log -- .." to disambiguate, but we shouldn't have to.

Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * With docs and tests.  In an early version, I forgot to restore the
   revision string that is temporarily NUL terminated with *dotdot = '.', 
   which resulted in "git log .." from Doc/howto to show not just the log
   of Doc but show everything.  The new test in t4202 tries to catch that
   rather common mistake as well.
 Documentation/revisions.txt    |    7 +++++++
 builtin/rev-parse.c            |   16 ++++++++++++++--
 revision.c                     |   16 ++++++++++++++--
 t/t1506-rev-parse-diagnosis.sh |   14 ++++++++++++++
 t/t4202-log.sh                 |    7 +++++++
 5 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 3d4b79c..fd043f7 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -184,6 +184,13 @@ of `r1` and `r2` and is defined as
 It is the set of commits that are reachable from either one of
 `r1` or `r2` but not from both.
 
+In these two shorthands, you can omit one end and let it default to HEAD.
+For example, 'origin..' is a shorthand for 'origin..HEAD' and asks "What
+did I do since I forked from the origin branch?"  Similarly, '..origin'
+is a shorthand for 'HEAD..origin' and asks "What did the origin do since
+I forked from them?"  Note that you cannot omit both ends.  '..' is not
+an empty range that is both reachable and unreachable from HEAD.
+
 Two other shorthands for naming a set that is formed by a commit
 and its parent commits exist.  The `r1{caret}@` notation means all
 parents of `r1`.  `r1{caret}!` includes commit `r1` but excludes
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index a5a1c86..54ce44b 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -219,6 +219,7 @@ static int try_difference(const char *arg)
 	const char *next;
 	const char *this;
 	int symmetric;
+	static const char head_by_default[] = "HEAD";
 
 	if (!(dotdot = strstr(arg, "..")))
 		return 0;
@@ -230,9 +231,20 @@ static int try_difference(const char *arg)
 	next += symmetric;
 
 	if (!*next)
-		next = "HEAD";
+		next = head_by_default;
 	if (dotdot == arg)
-		this = "HEAD";
+		this = head_by_default;
+
+	if (this == head_by_default && next == head_by_default &&
+	    !symmetric) {
+		/*
+		 * Just ".."?  That is not a range but the
+		 * pathspec for the parent directory.
+		 */
+		*dotdot = '.';
+		return 0;
+	}
+
 	if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
 		show_rev(NORMAL, end, next);
 		show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
diff --git a/revision.c b/revision.c
index ded8812..4572aaf 100644
--- a/revision.c
+++ b/revision.c
@@ -1004,14 +1004,26 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
 		const char *this = arg;
 		int symmetric = *next == '.';
 		unsigned int flags_exclude = flags ^ UNINTERESTING;
+		static const char head_by_default[] = "HEAD";
 
 		*dotdot = 0;
 		next += symmetric;
 
 		if (!*next)
-			next = "HEAD";
+			next = head_by_default;
 		if (dotdot == arg)
-			this = "HEAD";
+			this = head_by_default;
+		if (this == head_by_default && next == head_by_default &&
+		    !symmetric) {
+			/*
+			 * Just ".."?  That is not a range but the
+			 * pathspec for the parent directory.
+			 */
+			if (!cant_be_filename) {
+				*dotdot = '.';
+				return -1;
+			}
+		}
 		if (!get_sha1(this, from_sha1) &&
 		    !get_sha1(next, sha1)) {
 			struct commit *a, *b;
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 0eeeb0e..4485d0a 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -75,4 +75,18 @@ test_expect_success 'invalid @{n} reference' '
 	grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
 '
 
+test_expect_success 'dotdot is not an empty set' '
+	( H=$(git rev-parse HEAD) && echo $H ; echo ^$H ) >expect &&
+
+	git rev-parse HEAD.. >actual &&
+	test_cmp expect actual &&
+
+	git rev-parse ..HEAD >actual &&
+	test_cmp expect actual &&
+
+	echo .. >expect &&
+	git rev-parse .. >actual &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 2043bb8..b89f9af 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -463,4 +463,11 @@ test_expect_success 'show added path under "--follow -M"' '
 	)
 '
 
+test_expect_success 'dotdot is a parent directory' '
+	mkdir -p a/b &&
+	( echo sixth && echo fifth ) >expect &&
+	( cd a/b && git log --format=%s .. ) >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
1.7.5.253.g2243e
Previous: Jeff KingNext: Jeff King
Message 6 of 19 in “[Annoyance] "git log .." thinks ".." is ambiguous”
  1. Junio C HamanoMay 2, 2011
  2. Jeff KingMay 2, 2011
  3. Jeff KingMay 2, 2011
  4. Junio C HamanoMay 2, 2011
  5. Jeff KingMay 2, 2011
  6. specifying ranges: we did not mean to make ".." an empty setJunio C Hamano, May 2, 2011
  7. Jeff KingMay 2, 2011
  8. Junio C HamanoMay 2, 2011
  9. Michael J GruberMay 3, 2011
  10. Junio C HamanoMay 3, 2011
  11. Michael J GruberMay 4, 2011
  12. Junio C HamanoMay 4, 2011
  13. Junio C HamanoMay 4, 2011
  14. Joshua JuranMay 3, 2011
  15. Michael J GruberMay 3, 2011
  16. Joshua JuranMay 3, 2011
  17. Michael J GruberMay 3, 2011
  18. Junio C HamanoMay 3, 2011
  19. John SzakmeisterMay 3, 2011

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.