Re: [PATCH v2 1/2] Allow git-apply to ignore the hunk headers

9 messages, 4 authors, 2016-06-15 · open the first message on its own page

Re: [PATCH v2 1/2] Allow git-apply to ignore the hunk headers

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:44:41

Johannes Schindelin [off-list ref] writes:
Sometimes, the easiest way to fix up a patch is to edit it directly, even
adding or deleting lines.  Now, many people are not as divine as certain
benevolent dictators as to update the hunk headers correctly at the first
try.

So teach the tool to do it for us.
Two comments and a half.

 * Latest POSIX draft talks about unified context and allows an empty line
   to represent an empty common context line.  GNU diff already emits such
   a diff.  fixup_counts() should take this into account.

 * I'd sleep better at night if 'Probably "diff ..."' part were written in
   a bit more robust way.

 * (minor) There is an established term for this operation: recountdiff,
   so --recount might be a better name.  fixup_counts() also is better
   called recount_diff() if we go this route.

If you are too narrowly focused to only support "git add -e", the first
issue does not matter, because we always emit "SP LF" for such a common
context.  The reason why I care about the first two points is because we
may want to teach git-am about this new option as well in 1.6.0.

And the robustness issue I worry about the second point also applies to a
line that is "^-- $", especially if we were to make this available to
git-am.  Perhaps when the line begins with a '-', the logic could be extra
careful to detect the case where the line looks like the e-mail signature
separator and check one line beyond it to see if it does not look anything
like part of a diff (in which case you stop, without considering the line
you are currently looking at, "^-- $", a deletion of "^- $", as part of
the preimage context).

As to code structure, we might want to make the later parameters to
apply_patch() an integer, of OR'ed flag values, or even a pointer to a
structure that holds options.

Other than that, the patch looks reasonably isolated and clean.

Re: [PATCH v2 1/2] Allow git-apply to ignore the hunk headers

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:41

Hi,

On Thu, 5 Jun 2008, Junio C Hamano wrote:
Johannes Schindelin [off-list ref] writes:
quoted
Sometimes, the easiest way to fix up a patch is to edit it directly, 
even adding or deleting lines.  Now, many people are not as divine as 
certain benevolent dictators as to update the hunk headers correctly 
at the first try.

So teach the tool to do it for us.
Two comments and a half.

 * Latest POSIX draft talks about unified context and allows an empty line
   to represent an empty common context line.  GNU diff already emits such
   a diff.  fixup_counts() should take this into account.
As you pointed out, I wanted to support only add -e.  But that should not 
be an issue at all.  I think a "case ' ': case '\n':" should be enough, 
right?
 * I'd sleep better at night if 'Probably "diff ..."' part were written 
   in a bit more robust way.
How about stopping on "@@" and end of file only, and complaining 
otherwise?
 * (minor) There is an established term for this operation: recountdiff, 
   so --recount might be a better name.  fixup_counts() also is better 
   called recount_diff() if we go this route.
Fine!
If you are too narrowly focused to only support "git add -e", the first 
issue does not matter, because we always emit "SP LF" for such a common 
context.  The reason why I care about the first two points is because we 
may want to teach git-am about this new option as well in 1.6.0.
Point taken.
And the robustness issue I worry about the second point also applies to 
a line that is "^-- $", especially if we were to make this available to 
git-am.  Perhaps when the line begins with a '-', the logic could be 
extra careful to detect the case where the line looks like the e-mail 
signature separator and check one line beyond it to see if it does not 
look anything like part of a diff (in which case you stop, without 
considering the line you are currently looking at, "^-- $", a deletion 
of "^- $", as part of the preimage context).
Is this really an issue?  fixup_counts() is only called after a hunk 
header was read, and that should be well after any "^-- $".
As to code structure, we might want to make the later parameters to 
apply_patch() an integer, of OR'ed flag values, or even a pointer to a 
structure that holds options.
Right.

Will fix up and resubmit.

Ciao,
Dscho

[PATCH v3 0/2] git add --edit

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:41

Changes relative to v2:

- it works now not by chance, but by design,

- empty lines are interpreted as if they contained a single space,

- it works when adding lines to the beginning or end of a file, and

- the apply option has been renamed to --recount, as per Junio's request.

Johannes Schindelin (2):
  Allow git-apply to ignore the hunk headers (AKA recountdiff)
  git-add: introduce --edit (to edit the diff vs. the index)

 Documentation/git-add.txt   |   13 ++++-
 Documentation/git-apply.txt |    7 ++-
 builtin-add.c               |   55 ++++++++++++++++++-
 builtin-apply.c             |   64 ++++++++++++++++++++--
 t/t3702-add-edit.sh         |  126 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 257 insertions(+), 8 deletions(-)
 create mode 100755 t/t3702-add-edit.sh

[PATCH v3 2/2] git-add: introduce --edit (to edit the diff vs. the index)

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:41

With "git add -e [<files>]", Git will fire up an editor with the current
diff relative to the index (i.e. what you would get with "git diff
[<files>]").

Now you can edit the patch as much as you like, including adding/removing
lines, editing the text, whatever.  Make sure, though, that the first
character of the hunk lines is still a space, a plus or a minus.

After you closed the editor, Git will adjust the line counts of the
hunks if necessary, thanks to the --fixup-line-counts option of apply,
and commit the patch.  Except if you deleted everything, in which case
nothing happens (for obvious reasons).

Signed-off-by: Johannes Schindelin <redacted>
---
 Documentation/git-add.txt |   13 ++++-
 builtin-add.c             |   55 +++++++++++++++++++-
 t/t3702-add-edit.sh       |  126 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 191 insertions(+), 3 deletions(-)
 create mode 100755 t/t3702-add-edit.sh
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 1afd0c6..8620ae2 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -8,8 +8,8 @@ git-add - Add file contents to the index
 SYNOPSIS
 --------
 [verse]
-'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
-	  [--ignore-errors] [--] <filepattern>...
+'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [--edit | -e]
+	  [-u] [--refresh] [--ignore-errors] [--] <filepattern>...
 
 DESCRIPTION
 -----------
@@ -70,6 +70,15 @@ OPTIONS
 	bypassed and the 'patch' subcommand is invoked using each of
 	the specified filepatterns before exiting.
 
+-e, \--edit::
+	Open the diff vs. the index in an editor and let the user
+	edit it.  After the editor was closed, adjust the hunk headers
+	and apply the patch to the index.
++
+*NOTE*: Obviously, if you change anything else than the first character
+on lines beginning with a space or a minus, the patch will no longer
+apply.
+
 -u::
 	Update only files that git already knows about, staging modified
 	content for commit and marking deleted files for removal. This
diff --git a/builtin-add.c b/builtin-add.c
index 1da22ee..fe31453 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -19,7 +19,7 @@ static const char * const builtin_add_usage[] = {
 	"git-add [options] [--] <filepattern>...",
 	NULL
 };
-static int patch_interactive = 0, add_interactive = 0;
+static int patch_interactive = 0, add_interactive = 0, edit_interactive = 0;
 static int take_worktree_changes;
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
@@ -186,6 +186,56 @@ int interactive_add(int argc, const char **argv, const char *prefix)
 	return status;
 }
 
+int edit_patch(int argc, const char **argv, const char *prefix)
+{
+	char *file = xstrdup(git_path("ADD_EDIT.patch"));
+	const char *apply_argv[] = { "apply", "--recount", "--cached",
+		file, NULL };
+	struct child_process child;
+	int result = 0, ac;
+	struct stat st;
+
+	memset(&child, 0, sizeof(child));
+	child.argv = xcalloc(sizeof(const char *), (argc + 5));
+	ac = 0;
+	child.git_cmd = 1;
+	child.argv[ac++] = "diff-files";
+	child.argv[ac++] = "--no-color";
+	child.argv[ac++] = "-p";
+	child.argv[ac++] = "--";
+	if (argc) {
+		const char **pathspec = validate_pathspec(argc, argv, prefix);
+		if (!pathspec)
+			return -1;
+		memcpy(&(child.argv[ac]), pathspec, sizeof(*argv) * argc);
+		ac += argc;
+	}
+	child.argv[ac] = NULL;
+	child.out = open(file, O_CREAT | O_WRONLY, 0644);
+	result = child.out < 0 && error("Could not write to '%s'", file);
+
+	if (!result)
+		result = run_command(&child);
+	free(child.argv);
+
+	launch_editor(file, NULL, NULL);
+
+	if (!result)
+		result = stat(file, &st) && error("Could not stat '%s'", file);
+	if (!result && !st.st_size)
+		result = error("Empty patch. Aborted.");
+
+	memset(&child, 0, sizeof(child));
+	child.git_cmd = 1;
+	child.argv = apply_argv;
+	if (!result)
+		result = run_command(&child) &&
+			error("Could not apply '%s'", file);
+	if (!result)
+		unlink(file);
+	return result;
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_error[] =
@@ -200,6 +250,7 @@ static struct option builtin_add_options[] = {
 	OPT_GROUP(""),
 	OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
 	OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+	OPT_BOOLEAN('e', "edit", &edit_interactive, "super-interactive patching"),
 	OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
 	OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
 	OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
@@ -226,6 +277,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, builtin_add_options,
 			  builtin_add_usage, 0);
+	if (edit_interactive)
+		return(edit_patch(argc, argv, prefix));
 	if (patch_interactive)
 		add_interactive = 1;
 	if (add_interactive)
diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh
new file mode 100755
index 0000000..decf727
--- /dev/null
+++ b/t/t3702-add-edit.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='add -e basic tests'
+. ./test-lib.sh
+
+
+cat > file << EOF
+LO, praise of the prowess of people-kings
+of spear-armed Danes, in days long sped,
+we have heard, and what honor the athelings won!
+Oft Scyld the Scefing from squadroned foes,
+from many a tribe, the mead-bench tore,
+awing the earls. Since erst he lay
+friendless, a foundling, fate repaid him:
+for he waxed under welkin, in wealth he throve,
+till before him the folk, both far and near,
+who house by the whale-path, heard his mandate,
+gave him gifts:  a good king he!
+EOF
+
+test_expect_success 'setup' '
+
+	git add file &&
+	test_tick &&
+	git commit -m initial file
+
+'
+
+cat > patch << EOF
+diff --git a/file b/file
+index b9834b5..ef6e94c 100644
+--- a/file
++++ b/file
+@@ -3,1 +3,333 @@ of spear-armed Danes, in days long sped,
+ we have heard, and what honor the athelings won!
++
+ Oft Scyld the Scefing from squadroned foes,
+@@ -2,7 +1,5 @@ awing the earls. Since erst he lay
+ friendless, a foundling, fate repaid him:
++
+ for he waxed under welkin, in wealth he throve,
+EOF
+
+cat > expected << EOF
+diff --git a/file b/file
+index b9834b5..ef6e94c 100644
+--- a/file
++++ b/file
+@@ -1,10 +1,12 @@
+ LO, praise of the prowess of people-kings
+ of spear-armed Danes, in days long sped,
+ we have heard, and what honor the athelings won!
++
+ Oft Scyld the Scefing from squadroned foes,
+ from many a tribe, the mead-bench tore,
+ awing the earls. Since erst he lay
+ friendless, a foundling, fate repaid him:
++
+ for he waxed under welkin, in wealth he throve,
+ till before him the folk, both far and near,
+ who house by the whale-path, heard his mandate,
+EOF
+
+echo "#!$SHELL_PATH" >fake-editor.sh
+cat >> fake-editor.sh <<\EOF
+mv -f "$1" orig-patch &&
+mv -f patch "$1"
+EOF
+
+test_set_editor "$(pwd)/fake-editor.sh"
+chmod a+x fake-editor.sh
+
+test_expect_success 'add -e' '
+
+	cp fake-editor.sh file &&
+	git add -e &&
+	test_cmp fake-editor.sh file &&
+	git diff --cached > out &&
+	test_cmp out expected
+
+'
+
+cat > patch << EOF
+diff --git a/file b/file
+--- a/file
++++ b/file
+@@ -1,1 +1,1 @@
+ gave him gifts:  a good king he!
++
+EOF
+
+test_expect_success 'add -e adds to the end of the file' '
+
+	test_tick &&
+	git commit -m update &&
+	git checkout &&
+	git add -e &&
+	git diff --cached > out &&
+	test "" = "$(git show :file | tail -n 1)"
+
+'
+
+cat > patch << EOF
+diff --git a/file b/file
+--- a/file
++++ b/file
+@@ -1,1 +1,1 @@
++
+ LO, praise of the prowess of people-kings
+EOF
+
+test_expect_success 'add -e adds to the beginning of the file' '
+
+	test_tick &&
+	git commit -m update &&
+	git checkout &&
+	git add -e &&
+	git diff --cached > out &&
+	test "" = "$(git show :file | head -n 1)"
+
+'
+
+test_done
-- 
1.5.6.rc1.181.gb439d

[PATCH v3 1/2] Allow git-apply to ignore the hunk headers (AKA recountdiff)

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:41

Sometimes, the easiest way to fix up a patch is to edit it directly, even
adding or deleting lines.  Now, many people are not as divine as certain
benevolent dictators as to update the hunk headers correctly at the first
try.

So teach the tool to do it for us.

Signed-off-by: Johannes Schindelin <redacted>
---
 Documentation/git-apply.txt |    7 ++++-
 builtin-apply.c             |   64 ++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 66 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 2dec2ec..2fa660e 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -12,7 +12,7 @@ SYNOPSIS
 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index]
 	  [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
 	  [--allow-binary-replacement | --binary] [--reject] [-z]
-	  [-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
+	  [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
 	  [--whitespace=<nowarn|warn|fix|error|error-all>]
 	  [--exclude=PATH] [--verbose] [<patch>...]
 
@@ -169,6 +169,11 @@ behavior:
 	correctly. This option adds support for applying such patches by
 	working around this bug.
 
+--recount::
+	Do not trust the line counts in the hunk headers, but infer them
+	by inspecting the patch (e.g. after editing the patch without
+	adjusting the hunk headers appropriately).
+
 -v, --verbose::
 	Report progress to stderr. By default, only a message about the
 	current patch being applied will be printed. This option will cause
diff --git a/builtin-apply.c b/builtin-apply.c
index c497889..34c220f 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -153,6 +153,7 @@ struct patch {
 	unsigned int is_binary:1;
 	unsigned int is_copy:1;
 	unsigned int is_rename:1;
+	unsigned int recount:1;
 	struct fragment *fragments;
 	char *result;
 	size_t resultsize;
@@ -882,6 +883,50 @@ static int parse_range(const char *line, int len, int offset, const char *expect
 	return offset + ex;
 }
 
+static int recount_diff(char *line, int size, struct fragment *fragment)
+{
+	int line_nr = 0;
+
+	if (size < 1)
+		return -1;
+
+	fragment->oldpos = 2;
+	fragment->oldlines = fragment->newlines = 0;
+
+	for (;;) {
+		int len = linelen(line, size);
+		size -= len;
+		line += len;
+
+		if (size < 1)
+			return 0;
+
+		switch (*line) {
+		case ' ': case '\n':
+			fragment->newlines++;
+			/* fall through */
+		case '-':
+			fragment->oldlines++;
+			break;
+		case '+':
+			fragment->newlines++;
+			if (line_nr == 0) {
+				fragment->leading = 1;
+				fragment->oldpos = 1;
+			}
+			fragment->trailing = 1;
+			break;
+		case '@':
+			return size < 3 || prefixcmp(line, "@@ ");
+		case 'd':
+			return size < 5 || prefixcmp(line, "diff ");
+		default:
+			return -1;
+		}
+		line_nr++;
+	}
+}
+
 /*
  * Parse a unified diff fragment header of the
  * form "@@ -a,b +c,d @@"
@@ -1013,6 +1058,9 @@ static int parse_fragment(char *line, unsigned long size,
 	offset = parse_fragment_header(line, len, fragment);
 	if (offset < 0)
 		return -1;
+	if (offset > 0 && patch->recount &&
+			recount_diff(line + offset, size - offset, fragment))
+		return -1;
 	oldlines = fragment->oldlines;
 	newlines = fragment->newlines;
 	leading = 0;
@@ -2912,7 +2960,8 @@ static void prefix_patches(struct patch *p)
 	}
 }
 
-static int apply_patch(int fd, const char *filename, int inaccurate_eof)
+static int apply_patch(int fd, const char *filename, int inaccurate_eof,
+		int recount)
 {
 	size_t offset;
 	struct strbuf buf;
@@ -2929,6 +2978,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
 
 		patch = xcalloc(1, sizeof(*patch));
 		patch->inaccurate_eof = inaccurate_eof;
+		patch->recount = recount;
 		nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
 		if (nr < 0)
 			break;
@@ -2998,6 +3048,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 	int i;
 	int read_stdin = 1;
 	int inaccurate_eof = 0;
+	int recount = 0;
 	int errs = 0;
 	int is_not_gitdir;
 
@@ -3015,7 +3066,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 		int fd;
 
 		if (!strcmp(arg, "-")) {
-			errs |= apply_patch(0, "<stdin>", inaccurate_eof);
+			errs |= apply_patch(0, "<stdin>", inaccurate_eof,
+					recount);
 			read_stdin = 0;
 			continue;
 		}
@@ -3118,6 +3170,10 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 			inaccurate_eof = 1;
 			continue;
 		}
+		if (!strcmp(arg, "--recount")) {
+			recount = 1;
+			continue;
+		}
 		if (0 < prefix_length)
 			arg = prefix_filename(prefix, prefix_length, arg);
 
@@ -3126,12 +3182,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 			die("can't open patch '%s': %s", arg, strerror(errno));
 		read_stdin = 0;
 		set_default_whitespace_mode(whitespace_option);
-		errs |= apply_patch(fd, arg, inaccurate_eof);
+		errs |= apply_patch(fd, arg, inaccurate_eof, recount);
 		close(fd);
 	}
 	set_default_whitespace_mode(whitespace_option);
 	if (read_stdin)
-		errs |= apply_patch(0, "<stdin>", inaccurate_eof);
+		errs |= apply_patch(0, "<stdin>", inaccurate_eof, recount);
 	if (whitespace_error) {
 		if (squelch_whitespace_errors &&
 		    squelch_whitespace_errors < whitespace_error) {
-- 
1.5.6.rc1.181.gb439d

Re: [PATCH v3 1/2] Allow git-apply to ignore the hunk headers (AKA recountdiff)

From: Govind Salinas <hidden>
Date: 2016-06-15 22:44:41

On Thu, Jun 5, 2008 at 6:06 PM, Johannes Schindelin
[off-list ref] wrote:
+
+               switch (*line) {
+               case ' ': case '\n':
+                       fragment->newlines++;
+                       /* fall through */
+               case '-':
+                       fragment->oldlines++;
+                       break;
+               case '+':
+                       fragment->newlines++;
+                       if (line_nr == 0) {
+                               fragment->leading = 1;
+                               fragment->oldpos = 1;
+                       }
+                       fragment->trailing = 1;
+                       break;
+               case '@':
+                       return size < 3 || prefixcmp(line, "@@ ");
+               case 'd':
+                       return size < 5 || prefixcmp(line, "diff ");
+               default:
+                       return -1;
+               }
+               line_nr++;
+       }
+}
Perhaps this is accounted for and I did not see, but I believe that
a backslash is used for the "no newline at end of file" line.  Does that
need to be allowed here?

Thanks,
Govind.

Re: [PATCH v3 2/2] git-add: introduce --edit (to edit the diff vs. the index)

From: Olivier Marin <hidden>
Date: 2016-06-15 22:44:42

Johannes Schindelin a écrit :
 
+int edit_patch(int argc, const char **argv, const char *prefix)
+{
[...]
+	if (!result)
+		result = run_command(&child);
+	free(child.argv);
+
+	launch_editor(file, NULL, NULL);
Here, it does not launch the editor I defined with core.editor because you
call edit_patch() before calling git_config() in cmd_add().

Also, wouldn't be better to have the edit_patch stuff in add--interactive
instead ? It seems to work the same way than the --patch option.

Just my thoughts.

Olivier.

Re: [PATCH v3 1/2] Allow git-apply to ignore the hunk headers (AKA recountdiff)

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:42

Hi,

On Fri, 6 Jun 2008, Govind Salinas wrote:
On Thu, Jun 5, 2008 at 6:06 PM, Johannes Schindelin
[off-list ref] wrote:
quoted
+
+               switch (*line) {
+               case ' ': case '\n':
+                       fragment->newlines++;
+                       /* fall through */
+               case '-':
+                       fragment->oldlines++;
+                       break;
+               case '+':
+                       fragment->newlines++;
+                       if (line_nr == 0) {
+                               fragment->leading = 1;
+                               fragment->oldpos = 1;
+                       }
+                       fragment->trailing = 1;
+                       break;
+               case '@':
+                       return size < 3 || prefixcmp(line, "@@ ");
+               case 'd':
+                       return size < 5 || prefixcmp(line, "diff ");
+               default:
+                       return -1;
+               }
+               line_nr++;
+       }
+}
Perhaps this is accounted for and I did not see, but I believe that
a backslash is used for the "no newline at end of file" line.  Does that
need to be allowed here?
Will change.

Ciao,
Dscho

Re: [PATCH v3 2/2] git-add: introduce --edit (to edit the diff vs. the index)

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:44:42

Hi,

On Fri, 6 Jun 2008, Olivier Marin wrote:
Johannes Schindelin a écrit :
quoted
+int edit_patch(int argc, const char **argv, const char *prefix)
+{
[...]
quoted
+	if (!result)
+		result = run_command(&child);
+	free(child.argv);
+
+	launch_editor(file, NULL, NULL);
Here, it does not launch the editor I defined with core.editor because 
you call edit_patch() before calling git_config() in cmd_add().
Will fix.
Also, wouldn't be better to have the edit_patch stuff in 
add--interactive instead ? It seems to work the same way than the 
--patch option.
Actually, no.  It does something completely different.  For example, it 
avoids calling a perl script.  At least as long as your editor is not a 
Perl script.

Ciao,
Dscho
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help