From: Andreas Rohner <andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
To: linux-nilfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: Andreas Rohner <andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
Subject: [PATCH 1/6] nilfs-utils: extend SUFILE on-disk format to enable track live blocks
Date: Tue, 24 Feb 2015 20:04:14 +0100 [thread overview]
Message-ID: <1424804659-10986-1-git-send-email-andreas.rohner@gmx.net> (raw)
In-Reply-To: <1424804504-10914-1-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
This patch extends the nilfs_segment_usage structure with two extra
fields. This changes the on-disk format of the SUFILE, but the nilfs2
metadata files are flexible enough, so that there are no compatibility
issues. The extension is fully backwards compatible. Nevertheless a
feature compatibility flag was added to indicate the on-disk format
change.
The new field su_nlive_blks is used to track the number of live blocks
in the corresponding segment. Its value should always be smaller than
su_nblocks, which contains the total number of blocks in the segment.
The field su_nlive_lastmod is necessary because of the protection period
used by the GC. It is a timestamp, which contains the last time
su_nlive_blks was modified. For example if a file is deleted, its
blocks are subtracted from su_nlive_blks and are therefore
considered to be reclaimable by the kernel. But the GC additionally
protects them with the protection period. So while su_nilve_blks
contains the number of potentially reclaimable blocks, the actual number
depends on the protection period. To enable GC policies to
effectively choose or prefer segments with unprotected blocks, the
timestamp in su_nlive_lastmod is necessary.
Since the changes to the disk layout are fully backwards compatible and
the feature flag cannot be set after file system creation time,
NILFS_FEATURE_COMPAT_SUFILE_EXTENSION is set by default. It can however
be disabled by mkfs.nilfs2 -O ^sufile_ext
Signed-off-by: Andreas Rohner <andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
---
bin/lssu.c | 14 +++++++----
include/nilfs2_fs.h | 46 +++++++++++++++++++++++++++++------
lib/feature.c | 2 ++
man/mkfs.nilfs2.8 | 8 +++++++
sbin/mkfs/mkfs.c | 69 +++++++++++++++++++++++++++++++++++++++--------------
5 files changed, 109 insertions(+), 30 deletions(-)
diff --git a/bin/lssu.c b/bin/lssu.c
index 09ed973..e50e628 100644
--- a/bin/lssu.c
+++ b/bin/lssu.c
@@ -104,8 +104,8 @@ static const struct lssu_format lssu_format[] = {
},
{
" SEGNUM DATE TIME STAT NBLOCKS" \
- " NLIVEBLOCKS",
- "%17llu %s %c%c%c%c %10u %10u (%3u%%)\n"
+ " NLIVEBLOCKS NPREDLIVEBLOCKS",
+ "%17llu %s %c%c%c%c %10u %10u (%3u%%) %10u (%3u%%)\n"
}
};
@@ -164,9 +164,9 @@ static ssize_t lssu_print_suinfo(struct nilfs *nilfs, __u64 segnum,
time_t t;
char timebuf[LSSU_BUFSIZE];
ssize_t i, n = 0, ret;
- int ratio;
+ int ratio, predratio;
int protected;
- size_t nliveblks;
+ size_t nliveblks, npredliveblks;
for (i = 0; i < nsi; i++, segnum++) {
if (!all && nilfs_suinfo_clean(&suinfos[i]))
@@ -192,7 +192,10 @@ static ssize_t lssu_print_suinfo(struct nilfs *nilfs, __u64 segnum,
break;
case LSSU_MODE_LATEST_USAGE:
nliveblks = 0;
+ npredliveblks = suinfos[i].sui_nlive_blks;
ratio = 0;
+ predratio = (npredliveblks * 100 + 99) /
+ blocks_per_segment;
protected = suinfos[i].sui_lastmod >= prottime;
if (!nilfs_suinfo_dirty(&suinfos[i]) ||
@@ -223,7 +226,8 @@ skip_scan:
nilfs_suinfo_dirty(&suinfos[i]) ? 'd' : '-',
nilfs_suinfo_error(&suinfos[i]) ? 'e' : '-',
protected ? 'p' : '-',
- suinfos[i].sui_nblocks, nliveblks, ratio);
+ suinfos[i].sui_nblocks, nliveblks, ratio,
+ npredliveblks, predratio);
break;
}
n++;
diff --git a/include/nilfs2_fs.h b/include/nilfs2_fs.h
index a16ad4c..9137824 100644
--- a/include/nilfs2_fs.h
+++ b/include/nilfs2_fs.h
@@ -219,9 +219,11 @@ struct nilfs_super_block {
* If there is a bit set in the incompatible feature set that the kernel
* doesn't know about, it should refuse to mount the filesystem.
*/
-#define NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT 0x00000001ULL
+#define NILFS_FEATURE_COMPAT_SUFILE_EXTENSION (1ULL << 0)
-#define NILFS_FEATURE_COMPAT_SUPP 0ULL
+#define NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT (1ULL << 0)
+
+#define NILFS_FEATURE_COMPAT_SUPP NILFS_FEATURE_COMPAT_SUFILE_EXTENSION
#define NILFS_FEATURE_COMPAT_RO_SUPP NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT
#define NILFS_FEATURE_INCOMPAT_SUPP 0ULL
@@ -607,18 +609,35 @@ struct nilfs_cpfile_header {
sizeof(struct nilfs_checkpoint) - 1) / \
sizeof(struct nilfs_checkpoint))
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+#define sub_sizeof(TYPE, MEMBER) (offsetof(TYPE, MEMBER) + \
+ sizeof(((TYPE *)0)->MEMBER))
/**
* struct nilfs_segment_usage - segment usage
* @su_lastmod: last modified timestamp
* @su_nblocks: number of blocks in segment
* @su_flags: flags
+ * @su_nlive_blks: number of live blocks in the segment
+ * @su_pad: padding bytes
+ * @su_nlive_lastmod: timestamp nlive_blks was last modified
*/
struct nilfs_segment_usage {
__le64 su_lastmod;
__le32 su_nblocks;
__le32 su_flags;
+ __le32 su_nlive_blks;
+ __le32 su_pad;
+ __le64 su_nlive_lastmod;
};
+#define NILFS_MIN_SEGMENT_USAGE_SIZE \
+ sub_sizeof(struct nilfs_segment_usage, su_flags)
+
+#define NILFS_EXT_SEGMENT_USAGE_SIZE \
+ sub_sizeof(struct nilfs_segment_usage, su_nlive_lastmod)
+
/* segment usage flag */
enum {
NILFS_SEGMENT_USAGE_ACTIVE,
@@ -654,11 +673,16 @@ NILFS_SEGMENT_USAGE_FNS(DIRTY, dirty)
NILFS_SEGMENT_USAGE_FNS(ERROR, error)
static inline void
-nilfs_segment_usage_set_clean(struct nilfs_segment_usage *su)
+nilfs_segment_usage_set_clean(struct nilfs_segment_usage *su, size_t susz)
{
su->su_lastmod = cpu_to_le64(0);
su->su_nblocks = cpu_to_le32(0);
su->su_flags = cpu_to_le32(0);
+ if (susz >= NILFS_EXT_SEGMENT_USAGE_SIZE) {
+ su->su_nlive_blks = cpu_to_le32(0);
+ su->su_pad = cpu_to_le32(0);
+ su->su_nlive_lastmod = cpu_to_le64(0);
+ }
}
static inline int
@@ -680,21 +704,25 @@ struct nilfs_sufile_header {
/* ... */
};
-#define NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET \
- ((sizeof(struct nilfs_sufile_header) + \
- sizeof(struct nilfs_segment_usage) - 1) / \
- sizeof(struct nilfs_segment_usage))
+#define NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET(susz) \
+ ((sizeof(struct nilfs_sufile_header) + (susz) - 1) / (susz))
/**
* nilfs_suinfo - segment usage information
* @sui_lastmod: timestamp of last modification
* @sui_nblocks: number of written blocks in segment
* @sui_flags: segment usage flags
+ * @sui_nlive_blks: number of live blocks in the segment
+ * @sui_pad: padding bytes
+ * @sui_nlive_lastmod: timestamp nlive_blks was last modified
*/
struct nilfs_suinfo {
__u64 sui_lastmod;
__u32 sui_nblocks;
__u32 sui_flags;
+ __u32 sui_nlive_blks;
+ __u32 sui_pad;
+ __u64 sui_nlive_lastmod;
};
#define NILFS_SUINFO_FNS(flag, name) \
@@ -732,6 +760,8 @@ enum {
NILFS_SUINFO_UPDATE_LASTMOD,
NILFS_SUINFO_UPDATE_NBLOCKS,
NILFS_SUINFO_UPDATE_FLAGS,
+ NILFS_SUINFO_UPDATE_NLIVE_BLKS,
+ NILFS_SUINFO_UPDATE_NLIVE_LASTMOD,
__NR_NILFS_SUINFO_UPDATE_FIELDS,
};
@@ -755,6 +785,8 @@ nilfs_suinfo_update_##name(const struct nilfs_suinfo_update *sup) \
NILFS_SUINFO_UPDATE_FNS(LASTMOD, lastmod)
NILFS_SUINFO_UPDATE_FNS(NBLOCKS, nblocks)
NILFS_SUINFO_UPDATE_FNS(FLAGS, flags)
+NILFS_SUINFO_UPDATE_FNS(NLIVE_BLKS, nlive_blks)
+NILFS_SUINFO_UPDATE_FNS(NLIVE_LASTMOD, nlive_lastmod)
enum {
NILFS_CHECKPOINT,
diff --git a/lib/feature.c b/lib/feature.c
index b3317b7..d954cda 100644
--- a/lib/feature.c
+++ b/lib/feature.c
@@ -55,6 +55,8 @@ struct nilfs_feature {
static const struct nilfs_feature features[] = {
/* Compat features */
+ { NILFS_FEATURE_TYPE_COMPAT,
+ NILFS_FEATURE_COMPAT_SUFILE_EXTENSION, "sufile_ext" },
/* Read-only compat features */
{ NILFS_FEATURE_TYPE_COMPAT_RO,
NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT, "block_count" },
diff --git a/man/mkfs.nilfs2.8 b/man/mkfs.nilfs2.8
index 0ff2fbe..6c9a644 100644
--- a/man/mkfs.nilfs2.8
+++ b/man/mkfs.nilfs2.8
@@ -168,6 +168,14 @@ pseudo-filesystem feature "none" will clear all filesystem features.
.TP
.B block_count
Enable block count per checkpoint.
+.TP
+.B sufile_ext
+Enable SUFILE extension with extra fields. This is necessary for the
+track_live_blks and track_snapshots features to work. Once enabled it
+cannot be disabled, because it changes the ondisk format. Nevertheless it
+is fully compatible with older versions of the file system. This feature
+is on by default, because it is fully backwards compatible and can only
+be set at file system creation time.
.RE
.TP
.B \-q
diff --git a/sbin/mkfs/mkfs.c b/sbin/mkfs/mkfs.c
index f5f7dbb..3985262 100644
--- a/sbin/mkfs/mkfs.c
+++ b/sbin/mkfs/mkfs.c
@@ -116,7 +116,12 @@ static time_t creation_time;
static char volume_label[80];
static __u64 compat_array[NILFS_MAX_FEATURE_TYPES] = {
/* Compat */
- 0,
+ /*
+ * SUFILE_EXTENSION is set by default, because
+ * it is fully compatible with previous versions and it
+ * cannot be enabled later with nilfs-tune
+ */
+ NILFS_FEATURE_COMPAT_SUFILE_EXTENSION,
/* Read-only compat */
0,
/* Incompat */
@@ -375,12 +380,33 @@ static unsigned count_ifile_blocks(void)
return nblocks;
}
+static inline int sufile_extension_enabled(void)
+{
+ return compat_array[NILFS_FEATURE_TYPE_COMPAT] &
+ NILFS_FEATURE_COMPAT_SUFILE_EXTENSION;
+}
+
+static unsigned get_sufile_entry_size(void)
+{
+ if (sufile_extension_enabled())
+ return NILFS_EXT_SEGMENT_USAGE_SIZE;
+ else
+ return NILFS_MIN_SEGMENT_USAGE_SIZE;
+}
+
+static unsigned get_sufile_first_entry_offset(void)
+{
+ unsigned susz = get_sufile_entry_size();
+
+ return NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET(susz);
+}
+
static unsigned count_sufile_blocks(void)
{
unsigned long sufile_segment_usages_per_block
- = blocksize / sizeof(struct nilfs_segment_usage);
+ = blocksize / get_sufile_entry_size();
return DIV_ROUND_UP(nr_initial_segments +
- NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET,
+ get_sufile_first_entry_offset(),
sufile_segment_usages_per_block);
}
@@ -1056,7 +1082,7 @@ static inline void check_ctime(time_t ctime)
static const __u64 ok_features[NILFS_MAX_FEATURE_TYPES] = {
/* Compat */
- 0,
+ NILFS_FEATURE_COMPAT_SUFILE_EXTENSION,
/* Read-only compat */
NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT,
/* Incompat */
@@ -1499,8 +1525,8 @@ static void commit_cpfile(void)
static void prepare_sufile(void)
{
struct nilfs_file_info *fi = nilfs.files[NILFS_SUFILE_INO];
- const unsigned entries_per_block
- = blocksize / sizeof(struct nilfs_segment_usage);
+ const size_t susz = get_sufile_entry_size();
+ const unsigned entries_per_block = blocksize / susz;
blocknr_t blocknr = fi->start;
blocknr_t entry_block = blocknr;
struct nilfs_sufile_header *header;
@@ -1516,10 +1542,10 @@ static void prepare_sufile(void)
for (entry_block = blocknr;
entry_block < blocknr + fi->nblocks; entry_block++) {
i = (entry_block == blocknr) ?
- NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET : 0;
- su = (struct nilfs_segment_usage *)
- map_disk_buffer(entry_block, 1) + i;
- for (; i < entries_per_block; i++, su++, segnum++) {
+ get_sufile_first_entry_offset() : 0;
+ su = map_disk_buffer(entry_block, 1) + i * susz;
+ for (; i < entries_per_block; i++, su = (void *)su + susz,
+ segnum++) {
#if 0 /* these fields are cleared when mapped first */
su->su_lastmod = 0;
su->su_nblocks = 0;
@@ -1529,7 +1555,7 @@ static void prepare_sufile(void)
nilfs_segment_usage_set_active(su);
nilfs_segment_usage_set_dirty(su);
} else
- nilfs_segment_usage_set_clean(su);
+ nilfs_segment_usage_set_clean(su, susz);
}
}
init_inode(NILFS_SUFILE_INO, DT_REG, 0, 0);
@@ -1538,19 +1564,26 @@ static void prepare_sufile(void)
static void commit_sufile(void)
{
struct nilfs_file_info *fi = nilfs.files[NILFS_SUFILE_INO];
- const unsigned entries_per_block
- = blocksize / sizeof(struct nilfs_segment_usage);
+ const size_t susz = get_sufile_entry_size();
+ const unsigned entries_per_block = blocksize / susz;
struct nilfs_segment_usage *su;
unsigned segnum = fi->start / nilfs.diskinfo->blocks_per_segment;
blocknr_t blocknr = fi->start +
- (segnum + NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET) /
+ (segnum + get_sufile_first_entry_offset()) /
entries_per_block;
-
- su = map_disk_buffer(blocknr, 1);
- su += (segnum + NILFS_SUFILE_FIRST_SEGMENT_USAGE_OFFSET) %
+ size_t entry_off = (segnum + get_sufile_first_entry_offset()) %
entries_per_block;
+
+ su = map_disk_buffer(blocknr, 1) + entry_off * susz;
+
su->su_lastmod = cpu_to_le64(nilfs.diskinfo->ctime);
su->su_nblocks = cpu_to_le32(nilfs.current_segment->nblocks);
+ if (sufile_extension_enabled()) {
+ /* nlive_blks = nblocks - (nsummary_blks + nsuperroot_blks) */
+ su->su_nlive_blks = cpu_to_le32(nilfs.current_segment->nblocks -
+ (nilfs.current_segment->nblk_sum + 1));
+ su->su_nlive_lastmod = su->su_lastmod;
+ }
}
static void prepare_dat(void)
@@ -1756,7 +1789,7 @@ static void prepare_super_block(struct nilfs_disk_info *di)
raw_sb->s_checkpoint_size =
cpu_to_le16(sizeof(struct nilfs_checkpoint));
raw_sb->s_segment_usage_size =
- cpu_to_le16(sizeof(struct nilfs_segment_usage));
+ cpu_to_le16(get_sufile_entry_size());
raw_sb->s_feature_compat =
cpu_to_le64(compat_array[NILFS_FEATURE_TYPE_COMPAT]);
--
2.3.0
--
To unsubscribe from this list: send the line "unsubscribe linux-nilfs" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
next prev parent reply other threads:[~2015-02-24 19:04 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-02-24 19:01 [PATCH 0/9] nilfs2: implementation of cost-benefit GC policy Andreas Rohner
[not found] ` <1424804504-10914-1-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-02-24 19:01 ` [PATCH 1/9] nilfs2: refactor nilfs_sufile_updatev() Andreas Rohner
[not found] ` <1424804504-10914-2-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-10 15:52 ` Ryusuke Konishi
[not found] ` <20150311.005220.1374468405510151934.konishi.ryusuke-Zyj7fXuS5i5L9jVzuh4AOg@public.gmane.org>
2015-03-10 20:40 ` Andreas Rohner
2015-02-24 19:01 ` [PATCH 2/9] nilfs2: add simple cache for modifications to SUFILE Andreas Rohner
[not found] ` <1424804504-10914-3-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 0:45 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 3/9] nilfs2: extend SUFILE on-disk format to enable counting of live blocks Andreas Rohner
[not found] ` <1424804504-10914-4-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 4:05 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 4/9] nilfs2: add function to modify su_nlive_blks Andreas Rohner
[not found] ` <1424804504-10914-5-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 4:57 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 5/9] nilfs2: add simple tracking of block deletions and updates Andreas Rohner
[not found] ` <1424804504-10914-6-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 3:46 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 6/9] nilfs2: use modification cache to improve performance Andreas Rohner
[not found] ` <1424804504-10914-7-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 1:04 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 7/9] nilfs2: add additional flags for nilfs_vdesc Andreas Rohner
[not found] ` <1424804504-10914-8-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 3:21 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 8/9] nilfs2: improve accuracy and correct for invalid GC values Andreas Rohner
[not found] ` <1424804504-10914-9-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 2:50 ` Ryusuke Konishi
2015-02-24 19:01 ` [PATCH 9/9] nilfs2: prevent starvation of segments protected by snapshots Andreas Rohner
[not found] ` <1424804504-10914-10-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 3:51 ` Ryusuke Konishi
[not found] ` <20150314.125109.1017248837083480553.konishi.ryusuke-Zyj7fXuS5i5L9jVzuh4AOg@public.gmane.org>
2015-03-14 12:36 ` Andreas Rohner
[not found] ` <55042B53.5000101-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 12:49 ` Ryusuke Konishi
2015-03-14 14:32 ` Ryusuke Konishi
2015-02-24 19:04 ` Andreas Rohner [this message]
[not found] ` <1424804659-10986-1-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-02-24 19:04 ` [PATCH 2/6] nilfs-utils: add additional flags for nilfs_vdesc Andreas Rohner
2015-02-24 19:04 ` [PATCH 3/6] nilfs-utils: add support for tracking live blocks Andreas Rohner
[not found] ` <1424804659-10986-3-git-send-email-andreas.rohner-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 5:52 ` Ryusuke Konishi
2015-02-24 19:04 ` [PATCH 4/6] nilfs-utils: implement the tracking of live blocks for set_suinfo Andreas Rohner
2015-02-24 19:04 ` [PATCH 5/6] nilfs-utils: add support for greedy/cost-benefit policies Andreas Rohner
2015-02-24 19:04 ` [PATCH 6/6] nilfs-utils: add su_nsnapshot_blks field to indicate starvation Andreas Rohner
2015-02-25 0:18 ` [PATCH 0/9] nilfs2: implementation of cost-benefit GC policy Ryusuke Konishi
[not found] ` <20150225.091804.1850885506186316087.konishi.ryusuke-Zyj7fXuS5i5L9jVzuh4AOg@public.gmane.org>
2015-03-10 5:21 ` Ryusuke Konishi
[not found] ` <20150310.142119.813265940569588216.konishi.ryusuke-Zyj7fXuS5i5L9jVzuh4AOg@public.gmane.org>
2015-03-10 20:37 ` Andreas Rohner
[not found] ` <54FF561E.7030409-hi6Y0CQ0nG0@public.gmane.org>
2015-03-12 12:54 ` Ryusuke Konishi
[not found] ` <20150312.215431.324210374799651841.konishi.ryusuke-Zyj7fXuS5i5L9jVzuh4AOg@public.gmane.org>
2015-03-14 12:24 ` Andreas Rohner
[not found] ` <55042879.90701-hi6Y0CQ0nG0@public.gmane.org>
2015-03-14 15:40 ` Ryusuke Konishi
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=1424804659-10986-1-git-send-email-andreas.rohner@gmx.net \
--to=andreas.rohner-hi6y0cq0ng0@public.gmane.org \
--cc=linux-nilfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).