All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cyril Hrubis <chrubis@suse.cz>
To: Yang Xu <xuyang2018.jy@fujitsu.com>
Cc: ltp@lists.linux.it
Subject: Re: [LTP] [PATCH] linkat01: Split into 2 cases and convert to new API
Date: Wed, 17 Jul 2024 13:43:12 +0200	[thread overview]
Message-ID: <ZpeuUNVQd9DXJELe@rei> (raw)
In-Reply-To: <20240325060110.2278-1-xuyang2018.jy@fujitsu.com>

Hi!
>  #define TEST_DIR1 "olddir"
>  #define TEST_DIR2 "newdir"
>  #define TEST_DIR3 "deldir"
>  #define TEST_FILE1 "oldfile"
> -#define TEST_FILE2 "newfile"
> -#define TEST_FIFO "fifo"
>  
> -#define DPATHNAME_FMT	"%s/" TEST_DIR2 "/" TEST_FILE1
> -#define SPATHNAME_FMT	"%s/" TEST_DIR1 "/" TEST_FILE1
> +#define DPATHNAME_FMT "%s/" TEST_DIR2 "/" TEST_FILE1
> +#define SPATHNAME_FMT "%s/" TEST_DIR1 "/" TEST_FILE1
>  
>  static char dpathname[PATH_MAX];
>  static char spathname[PATH_MAX];
> -static int olddirfd, newdirfd = -1, cwd_fd = AT_FDCWD, stdinfd = 0, badfd =
> -    -1, deldirfd;
> -
> -struct test_struct {
> +static int olddirfd;
> +static int newdirfd = -1;
> +static int cwd_fd = AT_FDCWD;
> +static int stdinfd;
> +static int badfd = -1;
> +static int deldirfd;
> +
> +static struct tcase {
>  	int *oldfd;
>  	const char *oldfn;
>  	int *newfd;
> @@ -95,222 +46,122 @@ struct test_struct {
>  	int flags;
>  	const char *referencefn1;
>  	const char *referencefn2;

These two should better be named:

	const char *filename;
	const char *linkname;

So that it's clear where they point to.


Moreover the actual paths seems to be the same for all testcases, so I
suppose that we can just define two macros instead as:

#define FILENAME TEST_DIR1 "/" TEST_FILE1
#define LINKNAME TEST_DIR2 "/" TEST_FILE1

And remove these two members from the structure.

> -	int expected_errno;
> -} test_desc[] = {
> +} tcases[] = {
>  	/* 1. relative paths */
> -	{
> -	&olddirfd, TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 2. abs path at source */
> -	{
> -	&olddirfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0, 0},
> -	    /* 3. abs path at dst */
> -	{
> -	&olddirfd, TEST_FILE1, &newdirfd, dpathname, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 4. relative paths to cwd */
> -	{
> -	&cwd_fd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 5. relative paths to cwd */
> -	{
> -	&olddirfd, TEST_FILE1, &cwd_fd, TEST_DIR2 "/" TEST_FILE1, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 6. abs path at source */
> -	{
> -	&cwd_fd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0, 0},
> -	    /* 7. abs path at dst */
> -	{
> -	&olddirfd, TEST_FILE1, &cwd_fd, dpathname, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 8. relative paths to invalid */
> -	{
> -	&stdinfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> -		    0, 0, ENOTDIR},
> -	    /* 9. relative paths to invalid */
> -	{
> -	&olddirfd, TEST_FILE1, &stdinfd, TEST_DIR2 "/" TEST_FILE1, 0,
> -		    0, 0, ENOTDIR},
> -	    /* 10. abs path at source */
> -	{
> -	&stdinfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0, 0},
> -	    /* 11. abs path at dst */
> -	{
> -	&olddirfd, TEST_FILE1, &stdinfd, dpathname, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 12. relative paths to bad */
> -	{
> -	&badfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> -		    0, 0, EBADF},
> -	    /* 13. relative paths to bad */
> -	{
> -	&olddirfd, TEST_FILE1, &badfd, TEST_DIR2 "/" TEST_FILE1, 0,
> -		    0, 0, EBADF},
> -	    /* 14. abs path at source */
> -	{
> -	&badfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0, 0},
> -	    /* 15. abs path at dst */
> -	{
> -	&olddirfd, TEST_FILE1, &badfd, dpathname, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 16. relative paths to deleted */
> -	{
> -	&deldirfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> -		    0, 0, ENOENT},
> -	    /* 17. relative paths to deleted */
> -	{
> -	&olddirfd, TEST_FILE1, &deldirfd, TEST_DIR2 "/" TEST_FILE1, 0,
> -		    0, 0, ENOENT},
> -	    /* 18. abs path at source */
> -	{
> -	&deldirfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0, 0},
> -	    /* 19. abs path at dst */
> -	{
> -	&olddirfd, TEST_FILE1, &deldirfd, dpathname, 0,
> -		    TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1, 0},
> -	    /* 20. x-device link */
> -	{
> -	&cwd_fd, "/proc/cpuinfo", &newdirfd, TEST_FILE1, 0, 0, 0, EXDEV},
> -	    /* 21. directory link */
> -	{
> -	&olddirfd, ".", &newdirfd, TEST_FILE1, 0, 0, 0, EPERM},
> -	    /* 22. invalid flag */
> -	{
> -	&olddirfd, TEST_FILE1, &newdirfd, TEST_FILE1, 1, 0, 0, EINVAL},
> -	    /* 23. fifo link */
> -	    /* XXX (garrcoop): Removed because it hangs the overall test. Need to
> -	     * find a legitimate means to exercise this functionality, if in fact
> -	     * it's a valid testcase -- which it should be.
> -	     */
> -	    /* { &olddirfd, TEST_FIFO, &newdirfd, TEST_FILE1, 0,
> -	       TEST_DIR1"/"TEST_FIFO, TEST_DIR2"/"TEST_FILE1, 0 } */
> +	{&olddirfd, TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 2. absolution path at source */
> +	{&olddirfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0},
> +	/* 3. absolution path at destination */
> +	{&olddirfd, TEST_FILE1, &newdirfd, dpathname, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 4. relative paths to cwd */
> +	{&cwd_fd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 5. cwd to relative paths */
> +	{&olddirfd, TEST_FILE1, &cwd_fd, TEST_DIR2 "/" TEST_FILE1, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 6. cwd to absolution paths */
> +	{&cwd_fd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0},
> +	/* 7. absolution paths to cwd */
> +	{&olddirfd, TEST_FILE1, &cwd_fd, dpathname, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 8. invalid directory to absolution paths */
> +	{&stdinfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0},
> +	/* 9. absolution paths to invalid directory */
> +	{&olddirfd, TEST_FILE1, &stdinfd, dpathname, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 10. bad fd to absolution paths */
> +	{&badfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0},
> +	/* 11. absolution paths to bad fd */
> +	{&olddirfd, TEST_FILE1, &badfd, dpathname, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
> +	/* 12. deleted to absolution paths */
> +	{&deldirfd, spathname, &newdirfd, TEST_FILE1, 0, 0, 0},
> +	/* 13. absolution paths to deleted */
> +	{&olddirfd, TEST_FILE1, &deldirfd, dpathname, 0,
> +		TEST_DIR1 "/" TEST_FILE1, TEST_DIR2 "/" TEST_FILE1},
>  };
>  
> -char *TCID = "linkat01";
> -int TST_TOTAL = sizeof(test_desc) / sizeof(*test_desc);
> -
> -static int mylinkat(int olddirfd, const char *oldfilename, int newdirfd,
> -		    const char *newfilename, int flags)
> +static void setup(void)
>  {
> -	return tst_syscall(__NR_linkat, olddirfd, oldfilename, newdirfd,
> -		       newfilename, flags);
> -}
> -
> -int main(int ac, char **av)
> -{
> -	int lc;
> -	int i;
> -
> -	tst_parse_opts(ac, av, NULL, NULL);
> -
> -	setup();
> -
> -	for (lc = 0; TEST_LOOPING(lc); lc++) {
> +	char *cwd;
> +	int fd;
>  
> -		tst_count = 0;
> +	cwd = get_current_dir_name();
> +	if (cwd == NULL) {
> +		tst_brk(TFAIL | TERRNO,
> +			"Failed to get current working directory.");
> +	}
>  
> -		for (i = 0; i < TST_TOTAL; i++) {
> -			setup_every_copy();
> -			mylinkat_test(&test_desc[i]);
> -		}
> +	SAFE_MKDIR(TEST_DIR1, 0700);
> +	SAFE_MKDIR(TEST_DIR3, 0700);
> +	olddirfd = SAFE_OPEN(TEST_DIR1, O_DIRECTORY);
> +	deldirfd = SAFE_OPEN(TEST_DIR3, O_DIRECTORY);
> +	fd = SAFE_OPEN(TEST_DIR1 "/" TEST_FILE1, O_CREAT | O_EXCL, 0600);
> +	SAFE_CLOSE(fd);
>  
> -	}
> +	snprintf(dpathname, sizeof(dpathname), DPATHNAME_FMT, cwd);
> +	snprintf(spathname, sizeof(spathname), SPATHNAME_FMT, cwd);
>  
> -	cleanup();
> -	tst_exit();
> +	free(cwd);
>  }
>  
> -static void setup_every_copy(void)
> +static void setup_every_case(void)
>  {
>  	close(newdirfd);
>  	unlink(dpathname);
>  	rmdir(TEST_DIR2);
>  
> -	SAFE_MKDIR(cleanup, TEST_DIR2, 0700);
> -	newdirfd = SAFE_OPEN(cleanup, TEST_DIR2, O_DIRECTORY);
> +	SAFE_MKDIR(TEST_DIR2, 0700);
> +	newdirfd = SAFE_OPEN(TEST_DIR2, O_DIRECTORY);

As far as I can tell the only thing that needs to be done on each
iteration is the unlink of dpathname. The rest could be moved into the
test setup.

>  }
>  
> -static void mylinkat_test(struct test_struct *desc)
> +static void verify_linkat(unsigned int i)
>  {
> -	int fd;
> -
> -	TEST(mylinkat
> -	     (*desc->oldfd, desc->oldfn, *desc->newfd, desc->newfn,
> -	      desc->flags));
> -
> -	if (TEST_ERRNO == desc->expected_errno) {
> -		if (TEST_RETURN == 0 && desc->referencefn1 != NULL) {
> -			int tnum = rand(), vnum = ~tnum;
> -			fd = SAFE_OPEN(cleanup, desc->referencefn1,
> -				       O_RDWR);
> -			SAFE_WRITE(cleanup, SAFE_WRITE_ALL, fd, &tnum,
> -				sizeof(tnum));
> -			SAFE_CLOSE(cleanup, fd);
> +	struct tcase *tc = &tcases[i];
>  
> -			fd = SAFE_OPEN(cleanup, desc->referencefn2,
> -				       O_RDONLY);
> -			SAFE_READ(cleanup, 1, fd, &vnum, sizeof(vnum));
> -			SAFE_CLOSE(cleanup, fd);
> +	int fd;
>  
> -			if (tnum == vnum)
> -				tst_resm(TPASS,
> -					 "linkat is functionality correct");
> -			else {
> -				tst_resm(TFAIL,
> -					 "The link file's content isn't "
> -					 "as same as the original file's "
> -					 "although linkat returned 0");
> +	setup_every_case();
> +
> +	if (tc->referencefn1 != NULL) {
> +		TST_EXP_PASS_SILENT(linkat(*tc->oldfd, tc->oldfn,
> +			*tc->newfd, tc->newfn, tc->flags));
> +		if (TST_PASS) {
> +			int tnum = rand();
> +			int vnum = ~tnum;
> +
> +			fd = SAFE_OPEN(tc->referencefn1, O_RDWR);
> +			SAFE_WRITE(SAFE_WRITE_ALL, fd, &tnum, sizeof(tnum));
> +			SAFE_CLOSE(fd);
> +
> +			fd = SAFE_OPEN(tc->referencefn2, O_RDONLY);
> +			SAFE_READ(1, fd, &vnum, sizeof(vnum));
> +			SAFE_CLOSE(fd);
> +
> +			if (tnum == vnum) {
> +				tst_res(TPASS,
> +					"linkat is functionality correct");
> +			} else {
> +				tst_res(TFAIL,
> +					"The link file's content isn't "
> +					"as same as the original file's "
> +					"although linkat returned 0");

Can we be shorter here and more to the point?

Something as:

	tst_res(TFAIL, "Read wrong file content from the hard link.");

>  			}

Can we put this check that the file content is reacheable from the link
as well to a separat function?

>  		} else {
> -			if (TEST_RETURN == 0)
> -				tst_resm(TPASS,
> -					 "linkat succeeded as expected");
> -			else
> -				tst_resm(TPASS | TTERRNO,
> -					 "linkat failed as expected");
> +			tst_res(TFAIL, "linkat failed");

If we are priting PASS/FAIL manually it would make more sense to use
TST_EXP_PASS() instead of the silent variant.

>  		}
>  	} else {
> -		if (TEST_RETURN == 0)
> -			tst_resm(TFAIL, "linkat succeeded unexpectedly");
> -		else
> -			tst_resm(TFAIL | TTERRNO,
> -				 "linkat failed unexpectedly; expected %d - %s",
> -				 desc->expected_errno,
> -				 strerror(desc->expected_errno));
> -	}
> -}
> -
> -void setup(void)
> -{
> -	char *cwd;
> -	int fd;
> -
> -	tst_sig(NOFORK, DEF_HANDLER, cleanup);
> -
> -	tst_tmpdir();
> -
> -	cwd = get_current_dir_name();
> -	if (cwd == NULL) {
> -		tst_brkm(TFAIL | TERRNO, cleanup,
> -			 "Failed to get current working directory");
> +		TST_EXP_PASS(linkat(*tc->oldfd, tc->oldfn,
> +			*tc->newfd, tc->newfn, tc->flags),
> +			"linkat");
>  	}
> -
> -	SAFE_MKDIR(cleanup, TEST_DIR1, 0700);
> -	SAFE_MKDIR(cleanup, TEST_DIR3, 0700);
> -	olddirfd = SAFE_OPEN(cleanup, TEST_DIR1, O_DIRECTORY);
> -	deldirfd = SAFE_OPEN(cleanup, TEST_DIR3, O_DIRECTORY);
> -	SAFE_RMDIR(cleanup, TEST_DIR3);
> -	fd = SAFE_OPEN(cleanup, TEST_DIR1 "/" TEST_FILE1, O_CREAT | O_EXCL, 0600);
> -	SAFE_CLOSE(cleanup, fd);
> -	SAFE_MKFIFO(cleanup, TEST_DIR1 "/" TEST_FIFO, 0600);
> -
> -	snprintf(dpathname, sizeof(dpathname), DPATHNAME_FMT, cwd);
> -	snprintf(spathname, sizeof(spathname), SPATHNAME_FMT, cwd);
> -
> -	free(cwd);
>  }
>  
> -static void cleanup(void)
> -{
> -	tst_rmdir();
> -}
> +static struct tst_test test = {
> +	.tcnt = ARRAY_SIZE(tcases),
> +	.setup = setup,
> +	.test = verify_linkat,
> +	.needs_tmpdir = 1,
> +};
> diff --git a/testcases/kernel/syscalls/linkat/linkat03.c b/testcases/kernel/syscalls/linkat/linkat03.c
> new file mode 100644
> index 000000000..4ee25106e
> --- /dev/null
> +++ b/testcases/kernel/syscalls/linkat/linkat03.c
> @@ -0,0 +1,120 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) International Business Machines  Corp., 2006
> + * Copyright (c) Linux Test Project, 2006-2024
> + */
> +
> +/*\
> + * [Description]
> + *
> + * Check the basic function of linkat that returns error.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include "tst_test.h"
> +#include "tst_safe_macros.h"
> +#include "lapi/syscalls.h"
> +#include "lapi/fcntl.h"
> +
> +#define TEST_DIR1 "olddir"
> +#define TEST_DIR2 "newdir"
> +#define TEST_DIR3 "deldir"
> +#define TEST_FILE1 "oldfile"
> +
> +static int olddirfd;
> +static int newdirfd = -1;
> +static int cwd_fd = AT_FDCWD;
> +static int stdinfd;
> +static int badfd = -1;
> +static int deldirfd;
> +
> +static struct tcase {
> +	int *oldfd;
> +	const char *oldfn;
> +	int *newfd;
> +	const char *newfn;
> +	int flags;
> +	const char *referencefn1;
> +	const char *referencefn2;

These two pointers are not used for anything.

> +	int expected_errno;
> +} tcases[] = {
> +	/* 1. invalid directory to relative paths */
> +	{&stdinfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> +		0, 0, ENOTDIR},
> +	/* 2. relative paths to invalid directory */
> +	{&olddirfd, TEST_FILE1, &stdinfd, TEST_DIR2 "/" TEST_FILE1, 0,
> +		0, 0, ENOTDIR},
> +	/* 3. bad fd to relative paths */
> +	{&badfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> +		0, 0, EBADF},
> +	/* 4. relative paths to bad fd */
> +	{&olddirfd, TEST_FILE1, &badfd, TEST_DIR2 "/" TEST_FILE1, 0,
> +		0, 0, EBADF},
> +	/* 5. deleted to relative paths */
> +	{&deldirfd, TEST_DIR1 "/" TEST_FILE1, &newdirfd, TEST_FILE1, 0,
> +		0, 0, ENOENT},
> +	/* 6. relative paths to deleted */
> +	{&olddirfd, TEST_FILE1, &deldirfd, TEST_DIR2 "/" TEST_FILE1, 0,
> +		0, 0, ENOENT},
> +	/* 7. x-device link */
> +	{&cwd_fd, "/proc/cpuinfo", &newdirfd, TEST_FILE1, 0, 0, 0, EXDEV},
> +	/* 8. directory link */
> +	{&olddirfd, ".", &newdirfd, TEST_FILE1, 0, 0, 0, EPERM},
> +	/* 9. invalid flag */
> +	{&olddirfd, TEST_FILE1, &newdirfd, TEST_FILE1, 1, 0, 0, EINVAL},
> +};
> +
> +static void setup(void)
> +{
> +	char *cwd;
> +	int fd;
> +
> +	cwd = get_current_dir_name();
> +	if (cwd == NULL) {
> +		tst_brk(TFAIL | TERRNO,
> +			"Failed to get current working directory.");
> +	}
> +
> +	SAFE_MKDIR(TEST_DIR1, 0700);
> +	SAFE_MKDIR(TEST_DIR3, 0700);
> +	olddirfd = SAFE_OPEN(TEST_DIR1, O_DIRECTORY);
> +	deldirfd = SAFE_OPEN(TEST_DIR3, O_DIRECTORY);
> +	SAFE_RMDIR(TEST_DIR3);
> +	fd = SAFE_OPEN(TEST_DIR1 "/" TEST_FILE1, O_CREAT | O_EXCL, 0600);
> +	SAFE_CLOSE(fd);
> +
> +	free(cwd);

The cwd does not seem to be used at all.

> +}
> +
> +static void setup_every_case(void)
> +{
> +	close(newdirfd);
> +	rmdir(TEST_DIR2);
> +
> +	SAFE_MKDIR(TEST_DIR2, 0700);
> +	newdirfd = SAFE_OPEN(TEST_DIR2, O_DIRECTORY);

As far as I can tell this can be done once in the test setup()

> +}
> +
> +static void verify_linkat(unsigned int i)
> +{
> +	struct tcase *tc = &tcases[i];
> +
> +	setup_every_case();
> +
> +	TST_EXP_FAIL(linkat(*tc->oldfd, tc->oldfn, *tc->newfd,
> +		tc->newfn, tc->flags),
> +		tc->expected_errno,
> +		"linkat failed as expected");
> +}
> +
> +static struct tst_test test = {
> +	.tcnt = ARRAY_SIZE(tcases),
> +	.setup = setup,
> +	.test = verify_linkat,
> +	.needs_tmpdir = 1,
> +};
> -- 
> 2.39.3
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp

-- 
Cyril Hrubis
chrubis@suse.cz

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

      reply	other threads:[~2024-07-17 11:40 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-03-25  6:01 [LTP] [PATCH] linkat01: Split into 2 cases and convert to new API Yang Xu via ltp
2024-07-17 11:43 ` Cyril Hrubis [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ZpeuUNVQd9DXJELe@rei \
    --to=chrubis@suse.cz \
    --cc=ltp@lists.linux.it \
    --cc=xuyang2018.jy@fujitsu.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.