From: Alejandro Colomar <alx.manpages@gmail.com>
To: linux-man <linux-man@vger.kernel.org>
Subject: Re: string_copy(7): New manual page documenting string copying functions.
Date: Mon, 12 Dec 2022 01:25:10 +0100 [thread overview]
Message-ID: <632e0d82-b37d-67a7-81ef-1c4eb90f0d34@gmail.com> (raw)
In-Reply-To: <176225b0-4b0e-698b-b79e-f8ed78b4cf8c@gmail.com>
[-- Attachment #1.1: Type: text/plain, Size: 31166 bytes --]
On 12/12/22 00:59, Alejandro Colomar wrote:
> Hi all!
>
> I'm planning to add a new manual page that documents all string copying
> functions. It covers more detail than any of the existing manual pages (and in
> fact, I've discovered some properties of the functions documented while working
> on this page). The intention is to remove the existing separate manual pages
> for all string copying functions, and make them links to this new page. It
> intends to be the only reference documentation for copying strings in C, and
> hopefully fix the half century of suboptimal string copying library with which
> we've lived. (Say goodbye to std::string, here come back C strings ;)
>
> The formatted manual page is below.
>
> Alex
>
> P.S.: I'm sorry for your beloved string copying function(s); it has high chances
> of being dreaded by the page below. Not sorry. Oh well, at least I justified
> it, or I tried :-)
>
> ---
>
> string_copy(7) Miscellaneous Information Manual string_copy(7)
>
> NAME
> stpcpy, stpecpy, stpecpyx, strlcpy, strlcat, strscpy, strcpy, strcat,
> stpncpy, ustr2stp, strncpy, strncat, mempcpy - copy strings
>
> SYNOPSIS
> (Null‐terminated) strings
> // Chain‐copy a string.
> char *stpcpy(char *restrict dst, const char *restrict src);
>
> // Chain‐copy a string with truncation (not in libc).
> char *stpecpy(char *dst, char past_end[0], const char *restrict src);
>
> // Chain‐copy a string with truncation and SIGSEGV on invalid input.
> char *stpecpyx(char *dst, char past_end[0], const char *restrict src);
>
> // Copy a string with truncation and SIGSEGV on invalid input.
> [[deprecated]] // Use stpecpyx() instead.
> size_t strlcpy(char dst[restrict .sz], const char *restrict src,
> size_t sz);
>
> // Concatenate a string with truncation.
> [[deprecated]] // Use stpecpyx() instead.
> size_t strlcat(char dst[restrict .sz], const char *restrict src,
> size_t sz);
>
> // Copy a string with truncation (not in libc).
> [[deprecated]] // Use stpecpy() instead.
> ssize_t strscpy(char dst[restrict .sz], const char src[restrict .sz],
> size_t sz);
>
> // Copy a string.
> [[deprecated]] // Use stpcpy(3) instead.
> char *strcpy(char *restrict dst, const char *restrict src);
>
> // Concatenate a string.
> [[deprecated]] // Use stpcpy(3) instead.
> char *strcat(char *restrict dst, const char *restrict src);
>
> Unterminated strings (null‐padded fixed‐width buffers)
> // Zero a fixed‐width buffer, and
> // copy a string with truncation into an unterminated string.
> char *stpncpy(char dst[restrict .sz], const char *restrict src,
> size_t sz);
>
> // Chain‐copy an unterminated string into a string (not in libc).
> char *ustr2stp(char *restrict dst, const char src[restrict .sz],
> size_t sz);
>
> // Zero a fixed‐width buffer, and
> // copy a string with truncation into an unterminated string
> [[deprecated]] // Use stpncpy(3) instead.
> char *strncpy(char dest[restrict .sz], const char *restrict src,
> size_t sz);
>
> // Concatenate an unterminated string into a string.
> [[deprecated]] // Use ustr2stp() instead.
> char *strncat(char *restrict dst, const char src[restrict .sz],
> size_t sz);
>
> String structures
> // (Null‐terminated) string structure.
> struct str_s {
> size_t len;
> char *str;
> };
>
> // Unterminated string structure (overlapping strings).
> struct ustr_s {
> size_t len;
> char *ustr;
> };
>
> // Chain‐copy a string structure into an unterminated string.
> void *mempcpy(void *restrict dst, const void src[restrict len],
> size_t len);
>
> DESCRIPTION
> Terms (and abbreviations)
> string (str)
> is a sequence of zero or more non‐null characters, followed by a
> null byte.
>
> unterminated string (ustr)
> is a sequence of zero or more non‐null characters. They are
> sometimes contained in fixed‐width buffers, which usually con‐
> tain padding null bytes after the unterminated string, to fill
> the rest of the buffer without affecting the unterminated
> string; however, those padding null bytes are not part of the
> unterminated string.
>
> length (len)
> is the number of non‐null characters in a string. It is the re‐
> turn value of strlen(str) and of strnlen(ustr, sz).
>
> size (sz)
> refers to the entire buffer where the string is contained.
>
> end is the name of a pointer to the terminating null byte of a
> string, or a pointer to one past the last character of an unter‐
> minated string. This is the return value of functions that al‐
> low chaining. It is equivalent to &str[len].
>
> past_end
> is the name of a pointer to one past the end of the buffer that
> contains a string. It is equivalent to &str[sz]. It is used as
> a sentinel value, to be able to truncate strings instead of
> overrunning a buffer.
>
> string structure
> unterminated string structure
> Structure that contains the length of a string, as well as the
> string or the unterminated string.
>
> Types of functions
> Copy, concatenate, and chain‐copy
> Originally, there was a distinction between functions that copy
> and those that concatenate. However, newer functions that copy
> while allowing chaining cover both use cases with a single API.
> They are also algorithmically faster, since they don’t need to
> search for the end of the existing string.
>
> To chain copy functions, they need to return a pointer to the
> end. That’s a byproduct of the copy operation, so it has no
> performance costs. These functions are preferred over copy or
> concatenation functions. Functions that return such a pointer,
> and thus can be chained, have names of the form *stp*(), since
> it’s also common to name the pointer just p.
>
> Truncate or not?
> The first thing to note is that programmers should be careful
> with buffers, so they always have the correct size, and trunca‐
> tion is not necessary.
>
> In most cases, truncation is not desired, and it is simpler to
> just do the copy. Simpler code is safer code. Programming
> against programming mistakes by adding more code just adds more
> points where mistakes can be made.
>
> Nowadays, compilers can detect most programmer errors with fea‐
> tures like compiler warnings, static analyzers, and
> _FORTIFY_SOURCE (see ftm(7)). Keeping the code simple helps
> these error‐detection features be more precise.
>
> When validating user input, however, it makes sense to truncate.
> Remember to check the return value of such function calls.
>
> Functions that truncate:
>
> • stpecpy() is the most efficient string copy function that
> performs truncation. It only requires to check for trunca‐
> tion once after all chained calls.
>
> • stpecpyx() is a variant of stpecpy() that consumes the entire
> source string, to catch bugs in the program by forcing a seg‐
> mentation fault (as strlcpy(3bsd) and strlcat(3bsd) do).
>
> • strlcpy(3bsd) and strlcat(3bsd), which originated in OpenBSD,
> are designed to crash if the input string is invalid (doesn’t
> contain a null byte).
>
> • strscpy(9) is a function in the Linux kernel which reports an
> error instead of crashing.
>
> • stpncpy(3) and strncpy(3) also truncate, but they don’t write
> strings, but rather unterminated strings.
>
> Unterminated strings (null‐padded fixed‐width buffers)
> For historic reasons, some standard APIs, such as utmpx(5), use unter‐
> minated strings in fixed‐width buffers. To interface with them, spe‐
> cialized functions need to be used.
>
> To copy strings into them, use stpncpy(3).
>
> To copy from an unterminated string within a fixed‐width buffer into a
> string, ignoring any trailing null bytes in the source fixed‐width
> buffer, you should use ustr2stp().
>
> String structures
> The simplest string copying function is mempcpy(3). It requires always
> knowing the length of your strings, for which string structures can be
> used. It makes the code simpler, since you always know the length of
> your strings, and it’s also faster, since it doesn’t need to repeatedly
> calculate those lengths. mempcpy(3) always creates an unterminated
> string, so you need to explicitly set the terminating null byte.
>
> String structure
> The following code can be used to chain‐copy from a string
> structure into a string:
>
> p = mempcpy(p, src->str, src->len);
> *p = '\0';
>
> The following code can be used to chain‐copy from a string
> structure into an unterminated string:
>
> p = mempcpy(p, src->str, src->len);
>
> Unterminated string structure (overlapping strings)
> In programs that make considerable use of strings, and need the
> best performance, using overlapping strings can make a big dif‐
> ference. It allows holding substrings of a bigger string while
> not duplicating memory nor using time to do a copy.
>
> However, this is delicate, since it requires using unterminated
> strings. C library APIs use strings, so programs that use un‐
> terminated strings will have to take care to differentiate
> strings from unterminated strings.
>
> The following code can be used to chain‐copy from an untermi‐
> nated string structure to a string:
>
> p = mempcpy(p, src->ustr, src->len);
> *p = '\0';
>
> The following code can be used to chain‐copy from an untermi‐
> nated string structure to an unterminated string:
>
> p = mempcpy(p, src->ustr, src->len);
>
> Functions
> stpcpy(3)
> This function copies the input string into a destination string.
> The programmer is responsible for allocating a buffer large
> enough. It returns a pointer suitable for chaining.
>
> stpecpy()
> stpecpyx()
> These functions copy the input string into a destination string.
> If the destination buffer, limited by a pointer to one past the
> end of it, isn’t large enough to hold the copy, the resulting
> string is truncated (but it is guaranteed to be null‐termi‐
> nated). They return a pointer suitable for chaining. Trunca‐
> tion needs to be detected only once after the last chained call.
> stpecpyx() has identical semantics to stpecpy(), except that it
> forces a SIGSEGV on Undefined Behavior.
>
> These functions are not provided by any library, but you can de‐
> fine them with the following reference implementations:
>
> /* This code is in the public domain. */
> char *
> stpecpy(char *dst, char past_end[0],
> const char *restrict src)
> {
> char *p;
>
> if (dst == past_end)
> return past_end;
>
> p = memccpy(dst, src, '\0', past_end - dst);
> if (p != NULL)
> return p - 1;
>
> /* truncation detected */
> past_end[-1] = '\0';
> return past_end;
> }
>
> /* This code is in the public domain. */
> char *
> stpecpyx(char *dst, char past_end[0],
> const char *restrict src)
> {
> if (src[strlen(src)] != '\0')
> raise(SIGSEGV);
>
> return stpecpy(dst, past_end, src);
> }
>
> stpncpy(3)
> This function copies the input string into a destination null‐
> padded fixed‐width unterminated string. If the destination
> buffer, limited by its size, isn’t large enough to hold the
> copy, the resulting string is truncated. Since it creates an
> unterminated string, it doesn’t need to write a terminating null
> byte. It returns a pointer suitable for chaining, but it’s not
> ideal for that. Truncation needs to be detected only once after
> the last chained call.
>
> If you’re going to use this function in chained calls, it would
> probably be useful to develop a function similar to stpecpy().
>
> ustr2stp()
> This function copies the input unterminated string contained in
> a null‐padded wixed‐width buffer, into a destination (null‐ter‐
> minated) string. The programmer is responsible for allocating a
> buffer large enough. It returns a pointer suitable for chain‐
> ing.
>
> This function is not provided by any library, but you can write
> it with the definition above in this page.
>
> A truncating version of this function doesn’t exist, since the
> size of the original string is always known, so it wouldn’t be
> very useful.
>
> This function is not provided by any library, but you can define
> it with the following reference implementation:
>
> /* This code is in the public domain. */
> char *
> ustr2stp(char *restrict dst, const char *restrict src,
> size_t sz)
> {
> char *end;
>
> end = memccpy(dst, src, '\0', sz)) ?: dst + sz;
> *end = '\0';
>
> return end;
> }
>
> mempcpy(3)
> This function copies the input string, limited by its length,
> into a destination unterminated string. The programmer is re‐
> sponsible for allocating a buffer large enough. It returns a
> pointer suitable for chaining.
>
> Deprecated functions
> strlcpy(3bsd)
> strlcat(3bsd)
> Deprecated. These functions copy the input string into a desti‐
> nation string. If the destination buffer, limited by its size,
> isn’t large enough to hold the copy, the resulting string is
> truncated (but it is guaranteed to be null‐terminated). They
> return the length of the total string they tried to create.
> These functions force a SIGSEGV on Undefined Behavior.
>
> stpecpyx() is a better replacement for these functions for the
> following reasons:
>
> • Better performance (chain copy instead of concatenating).
>
> • Only requires detecting truncation once per chain of calls.
>
> strscpy(9)
> Deprecated. This function copies the input string into a desti‐
> nation string. If the destination buffer, limited by its size,
> isn’t large enough to hold the copy, the resulting string is
> truncated (but it is guaranteed to be null‐terminated). It re‐
> turns the length of the destination string, or -E2BIG on trunca‐
> tion.
>
> stpecpy() is a better replacement for this function, since it
> has a much simpler interface.
>
> strcpy(3)
> strcat(3)
> Deprecated. These functions copy the input string into a desti‐
> nation string. The programmer is responsible for allocating a
> buffer large enough. The return value is useless.
>
> strcpy(3) is identical to stpcpy(3) except for the useless re‐
> turn value.
>
> stpcpy(3) is a better replacement for these functions for the
> following reasons:
>
> • Better performance (chain copy instead of concatenating).
>
> • No need to call strlen(3), thanks to the useful return value.
>
> strncpy(3)
> Deprecated. strncpy(3) is identical to stpncpy(3) except for
> the useless return value. Due to the return value, with this
> function it’s hard to correctly check for truncation. Use stp‐
> ncpy(3) instead.
>
> strncat(3)
> Deprecated. Do not confuse this function with strncpy(3); they
> are not related at all.
>
> This function concatenates the input unterminated string con‐
> tained in a null‐padded wixed‐width buffer, into a destination
> (null‐terminated) string. The programmer is responsible for al‐
> locating a buffer large enough. The return value is useless.
>
> ustr2stp() is a better replacement for this function for the
> following reasons:
>
> • Better performance (chain copy instead of concatenating).
>
> • No need to call strlen(3), thanks to the useful return value.
>
> • Function name that is not actively confusing.
>
> RETURN VALUE
> The following functions return a pointer to the terminating null byte
> in the destination string (they never truncate).
>
> • stpcpy(3)
>
> • ustr2stp()
>
> • mempcpy(3)
>
> The following functions return a pointer to the terminating null byte
> in the destination string, except when truncation occurs; if truncation
> occurs, they return a pointer to one past the end of the destination
> buffer.
>
> • stpecpy()
>
> • stpecpyx()
>
> The following function returns a pointer to one after the last charac‐
> ter in the destination unterminated string; if truncation occurs, that
> pointer is equivalent to a pointer to one past the end of the destina‐
> tion buffer.
>
> • stpncpy(3)
>
> Deprecated
> The following functions return the length of the total string that they
> tried to create (as if truncation didn’t occur).
>
> • strlcpy(3bsd)
>
> • strlcat(3bsd)
>
> The following function returns the length of the destination string, or
> -E2BIG on truncation.
>
> • strscpy(9)
>
> The following functions return the dst pointer, which is useless.
>
> • strcpy(3)
>
> • strcat(3)
>
> • strncpy(3)
>
> • strncat(3)
>
> CAVEATS
> Some of the functions described here are not provided by any library;
> you should write your own copy if you want to use them.
>
> The deprecated status of these functions varies from system to system.
> This page declares as deprecated those functions that have a better re‐
> placement documented in this same page.
>
> EXAMPLES
> The following are examples of correct use of each of these functions.
>
> stpcpy(3)
> p = buf;
> p = stpcpy(p, "Hello ");
> p = stpcpy(p, "world");
> p = stpcpy(p, "!");
> len = p - buf;
> puts(buf);
>
> stpecpy()
> stpecpyx()
> past_end = buf + sizeof(buf);
> p = buf;
> p = stpecpy(p, past_end, "Hello ");
> p = stpecpy(p, past_end, "world");
> p = stpecpy(p, past_end, "!");
> if (p == past_end) {
> p--;
> goto toolong;
> }
> len = p - buf;
> puts(buf);
>
> stpncpy(3)
> past_end = buf + sizeof(buf);
> end = stpncpy(buf, "Hello world!", sizeof(buf));
> if (end == past_end)
> goto toolong;
> len = end - buf;
> for (size_t i = 0; i < sizeof(buf); i++)
> putchar(buf[i]);
>
> ustr2stp()
> p = buf;
> p = ustr2stp(p, "Hello ", 6);
> p = ustr2stp(p, "world", 42); // Padding null bytes ignored.
> p = ustr2stp(p, "!", 1);
> len = p - buf;
> puts(buf);
>
> mempcpy(3)
> p = buf;
> p = mempcpy(p, "Hello ", 6);
> p = mempcpy(p, "world", 5);
> p = mempcpy(p, "!", 1);
> p = '\0';
> len = p - buf;
> puts(buf);
>
> Deprecated
> strlcpy(3bsd)
> strlcat(3bsd)
> if (strlcpy(buf, "Hello ", sizeof(buf)) >= sizeof(buf))
> goto toolong;
> if (strlcat(buf, "world", sizeof(buf)) >= sizeof(buf))
> goto toolong;
> len = strlcat(buf, "!", sizeof(buf));
> if (len >= sizeof(buf))
> goto toolong;
> puts(buf);
>
> strscpy(9)
> len = strscpy(buf, "Hello world!", sizeof(buf));
> if (len == -E2BIG)
> goto toolong;
> puts(buf);
>
> strcpy(3)
> strcat(3)
> strcpy(buf, "Hello ");
> strcat(buf, "world");
> strcat(buf, "!");
> len = strlen(buf);
> puts(buf);
>
> strncpy(3)
> strncpy(buf, "Hello world!", sizeof(buf));
> if (buf + sizeof(buf) - 1 == '\0')
> goto toolong;
> len = strnlen(buf, sizeof(buf));
> for (size_t i = 0; i < sizeof(buf); i++)
> putchar(buf[i]);
>
> strncat(3)
> strncpy(buf, "Hello ", 6);
> strncat(buf, "world", 42); // Padding null bytes ignored.
> strncat(buf, "!", 1);
> puts(buf);
Oops, that example was mistaken; too much cut and paste.
strncat(3)
buf[0] = '\0';
strncat(buf, "Hello ", 6);
strncat(buf, "world", 42); // Padding null bytes ignored.
strncat(buf, "!", 1);
len = strlen(buf);
puts(buf);
>
> SEE ALSO
> memcpy(3), memccpy(3), mempcpy(3), string(3)
>
> Linux man‐pages (unreleased) (date) string_copy(7)
>
>
>
--
<http://www.alejandro-colomar.es/>
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2022-12-12 0:25 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-12-11 23:59 string_copy(7): New manual page documenting string copying functions Alejandro Colomar
2022-12-12 0:17 ` Alejandro Colomar
2022-12-12 0:25 ` Alejandro Colomar [this message]
2022-12-12 0:32 ` Alejandro Colomar
2022-12-12 14:24 ` [PATCH 1/3] strcpy.3: Rewrite page to document all string-copying functions Alejandro Colomar
2022-12-12 17:33 ` Alejandro Colomar
2022-12-12 18:38 ` groff man(7) extensions (was: [PATCH 1/3] strcpy.3: Rewrite page to document all string-copying functions) G. Branden Robinson
2022-12-13 15:45 ` a Q quotation macro for man(7) (was: groff man(7) extensions) G. Branden Robinson
2022-12-12 23:00 ` [PATCH v2 0/3] Rewrite strcpy(3) Alejandro Colomar
2022-12-13 20:56 ` Jakub Wilk
2022-12-13 20:57 ` Alejandro Colomar
2022-12-13 22:05 ` Alejandro Colomar
2022-12-13 22:46 ` Alejandro Colomar
2022-12-14 0:03 ` [PATCH v3 0/1] Rewritten page for string-copying functions Alejandro Colomar
2022-12-14 0:14 ` Alejandro Colomar
2022-12-14 0:16 ` Alejandro Colomar
2022-12-14 16:17 ` [PATCH v4 " Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 0/5] Rewrite pages about " Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 0/5] Rewrite documentation for " Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 1/5] string_copy.7: Add page to document all " Alejandro Colomar
2022-12-20 15:00 ` Stefan Puiu
2022-12-20 15:03 ` Alejandro Colomar
2023-01-20 3:43 ` Eric Biggers
2023-01-20 12:55 ` Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 2/5] stpecpy.3, stpecpyx.3, ustpcpy.3, ustr2stp.3, zustr2stp.3, zustr2ustp.3: Add new links to string_copy(7) Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 3/5] stpcpy.3, strcpy.3, strcat.3: Document in a single page Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 4/5] stpncpy.3, strncpy.3: " Alejandro Colomar
2022-12-19 21:02 ` [PATCH v6 5/5] strncat.3: Rewrite to be consistent with string_copy.7 Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 1/5] string_copy.7: Add page to document all string-copying functions Alejandro Colomar
2022-12-15 0:30 ` Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 2/5] stpecpy.3, stpecpyx.3, ustpcpy.3, ustr2stp.3, zustr2stp.3, zustr2ustp.3: Add new links to string_copy(7) Alejandro Colomar
2022-12-15 0:27 ` Alejandro Colomar
2022-12-16 18:47 ` Stefan Puiu
2022-12-16 19:03 ` Alejandro Colomar
2022-12-16 19:09 ` Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 3/5] stpcpy.3, strcpy.3, strcat.3: Document in a single page Alejandro Colomar
2022-12-16 14:46 ` Alejandro Colomar
2022-12-16 14:47 ` Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 4/5] stpncpy.3, strncpy.3: " Alejandro Colomar
2022-12-15 0:28 ` Alejandro Colomar
2022-12-15 0:26 ` [PATCH v5 5/5] strncat.3: Rewrite to be consistent with string_copy.7 Alejandro Colomar
2022-12-15 0:29 ` Alejandro Colomar
2022-12-14 16:17 ` [PATCH v4 1/1] strcpy.3: Rewrite page to document all string-copying functions Alejandro Colomar
2022-12-14 0:03 ` [PATCH v3 " Alejandro Colomar
2022-12-14 16:22 ` Douglas McIlroy
2022-12-14 16:36 ` Alejandro Colomar
2022-12-14 17:11 ` Alejandro Colomar
2022-12-14 17:19 ` Alejandro Colomar
2022-12-12 23:00 ` [PATCH v2 1/3] " Alejandro Colomar
2022-12-12 23:00 ` [PATCH v2 2/3] stpcpy.3, stpncpy.3, strcat.3, strncat.3, strncpy.3: Transform the old pages into links to strcpy(3) Alejandro Colomar
2022-12-12 23:00 ` [PATCH v2 3/3] stpecpy.3, stpecpyx.3, strlcat.3, strlcpy.3, strscpy.3: Add new " Alejandro Colomar
2022-12-12 14:24 ` [PATCH 2/3] stpcpy.3, stpncpy.3, strcat.3, strncat.3, strncpy.3: Transform the old pages into " Alejandro Colomar
2022-12-12 14:24 ` [PATCH 3/3] stpecpy.3, stpecpyx.3, strlcat.3, strlcpy.3, strscpy.3: Add new " Alejandro Colomar
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=632e0d82-b37d-67a7-81ef-1c4eb90f0d34@gmail.com \
--to=alx.manpages@gmail.com \
--cc=linux-man@vger.kernel.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