public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
From: JeanHeyd Meneide <phdofthehouse@gmail.com>
To: Jakub Jelinek <jakub@redhat.com>
Cc: gcc-patches@gcc.gnu.org, "libstdc++" <libstdc++@gcc.gnu.org>,
		Jonathan Wakely <jwakely@redhat.com>
Subject: Re: [ PATCH ] [ C++ ] [ libstdc++ ] P1208r6 Merge source_location
Date: Sat, 28 Dec 2019 03:28:00 -0000	[thread overview]
Message-ID: <CANHA4Ohhgv2-cF0Q-r0Y1UfM5Wo=12dFj_PrQKMQtS0VLdP5OQ@mail.gmail.com> (raw)
In-Reply-To: <20191227193329.GM10088@tucnak>

[-- Attachment #1: Type: text/plain, Size: 1301 bytes --]

On Fri, Dec 27, 2019 at 2:33 PM Jakub Jelinek <jakub@redhat.com> wrote:
>
> This will be ABI incompatible between GCC and Clang, that doesn't look like
> a good idea to me.  I thought the plan is to use what you have in the
> _GLIBCXX_HAVE_BUILTIN_SOURCE_LOCATION case always, except that if
> __builtin_source_location isn't available, initialize _M_data to nullptr.

You're right, it's probably bad to have an implementation that
switches. I guess that means clang folks will have to implement the
builtin too at some point (but Jonathan's comment in the
__builtin_source_location patch said they might not, so I'm not sure
what to do for Clang here I guess?).

To more clearly explain the test failures and the static_assert
failures, there are cases where -- when the default argument can be
manifestly constant evaluated -- it does not report the a function
name or anything at all. It also uses the line of where the Nonstatic
Data Member Initializer is for initialization of class members, rather
than the line of the constructor where the initialization happens.
These are things that the Standard's examples say *should* work, but I
don't know how normative Examples are in the standard that use
comments to explain things:
http://eel.is/c++draft/support.srcloc#cons-4

Best Wishes,
JeanHeyd

[-- Attachment #2: p1208r6.patch.txt --]
[-- Type: text/plain, Size: 18280 bytes --]

diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 6300de9e96d..fc593c7c461 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -70,6 +70,7 @@ std_headers = \
 	${std_srcdir}/scoped_allocator \
 	${std_srcdir}/set \
 	${std_srcdir}/shared_mutex \
+	${std_srcdir}/source_location \
 	${std_srcdir}/span \
 	${std_srcdir}/sstream \
 	${std_srcdir}/stack \
diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
index ae4a493ea65..c104ffb28ab 100644
--- a/libstdc++-v3/include/Makefile.in
+++ b/libstdc++-v3/include/Makefile.in
@@ -414,6 +414,7 @@ std_headers = \
 	${std_srcdir}/scoped_allocator \
 	${std_srcdir}/set \
 	${std_srcdir}/shared_mutex \
+	${std_srcdir}/source_location \
 	${std_srcdir}/span \
 	${std_srcdir}/sstream \
 	${std_srcdir}/stack \
diff --git a/libstdc++-v3/include/bits/c++config b/libstdc++-v3/include/bits/c++config
index 7ccfc5f199d..2d2ec96a925 100644
--- a/libstdc++-v3/include/bits/c++config
+++ b/libstdc++-v3/include/bits/c++config
@@ -630,6 +630,7 @@ namespace std
 
 #if __GNUC__ >= 7
 // Assume these are available if the compiler claims to be a recent GCC:
+# define _GLIBCXX_HAVE_BUILTIN_LINE 1
 # define _GLIBCXX_HAVE_BUILTIN_HAS_UNIQ_OBJ_REP 1
 # define _GLIBCXX_HAVE_BUILTIN_IS_AGGREGATE 1
 # define _GLIBCXX_HAVE_BUILTIN_LAUNDER 1
@@ -637,6 +638,9 @@ namespace std
 # if __GNUC__ >= 9
 #  define _GLIBCXX_HAVE_BUILTIN_IS_CONSTANT_EVALUATED 1
 # endif
+# if __GNUC__ >= 10
+#  define _GLIBCXX_HAVE_BUILTIN_SOURCE_LOCATION 1
+# endif
 #elif defined(__is_identifier) && defined(__has_builtin)
 // For non-GNU compilers:
 # if ! __is_identifier(__has_unique_object_representations)
@@ -654,6 +658,15 @@ namespace std
 # if ! __is_identifier(__is_same)
 #  define _GLIBCXX_BUILTIN_IS_SAME_AS(T, U) __is_same(T, U)
 # endif
+# if __has_builtin(__builtin_source_location)
+#  define _GLIBCXX_HAVE_BUILTIN_SOURCE_LOCATION 1
+# endif
+# if __has_builtin(__builtin_LINE)
+#  define _GLIBCXX_HAVE_BUILTIN_LINE 1
+# endif
+# if __has_builtin(__builtin_COLUMN)
+#  define _GLIBCXX_HAVE_BUILTIN_COLUMN 1
+# endif
 #endif // GCC
 
 // PSTL configuration
diff --git a/libstdc++-v3/include/std/source_location b/libstdc++-v3/include/std/source_location
new file mode 100644
index 00000000000..e2727ebd196
--- /dev/null
+++ b/libstdc++-v3/include/std/source_location
@@ -0,0 +1,103 @@
+// Component for retrieving function, line and column source info. -*- C++ -*-
+
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file source_location
+ *  This is a Standard C++ Library header.
+ */
+
+//
+// P1208 source_location library
+// Contributed by ThePhD with <3
+//
+
+#ifndef _GLIBCXX_SOURCE_LOCATION
+#define _GLIBCXX_SOURCE_LOCATION 1
+
+#pragma GCC system_header
+
+#if __cplusplus > 201703L
+
+#include <bits/c++config.h>
+#include <cstdint>
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+#define __cpp_lib_source_location 201907L
+
+  struct source_location
+  {
+  private:
+    struct __impl
+    {
+      const char *_M_file_name;
+      const char *_M_function_name;
+      unsigned int _M_line, _M_column;
+    };
+
+  public:
+    static consteval source_location
+    current (const void *__src_loc_impl =
+#ifdef _GLIBCXX_HAVE_BUILTIN_SOURCE_LOCATION
+	       __builtin_source_location ()
+#else
+	       nullptr
+#endif
+    ) noexcept
+    {
+      source_location __ret;
+      __ret._M_data = static_cast<const __impl *>(__src_loc_impl);
+      return __ret;
+    }
+
+    constexpr
+    source_location () noexcept
+    : _M_data ()
+    { }
+
+    constexpr const char *
+    file_name () const noexcept
+    { return _M_data ? _M_data->_M_file_name : ""; }
+
+    constexpr const char *
+    function_name () const noexcept
+    { return _M_data ? _M_data->_M_function_name : ""; }
+
+    constexpr uint_least32_t
+    line () const noexcept
+    { return _M_data ? _M_data->_M_line : 0; }
+  
+    constexpr uint_least32_t
+    column () const noexcept
+    { return _M_data ? _M_data->_M_column : 0; }
+
+  private:
+    const __impl *_M_data;
+  };
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // C++20
+#endif // _GLIBCXX_SOURCE_LOCATION
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index e55a092eb89..4357d7de71c 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -196,6 +196,11 @@
 #endif
 #define __cpp_lib_to_array 201907L
 #endif
+#if defined(_GLIBCXX_HAVE_BUILTIN_SOURCE_LOCATION)
+# define __cpp_lib_is_constant_evaluated 201907L
+#elif defined(_GLIBCXX_HAVE_BUILTIN_LINE) && defined(_GLIBCXX_HAVE_BUILTIN_COLUMN)
+# define __cpp_lib_is_constant_evaluated 201907L
+#endif
 #endif // C++2a
 #endif // C++17
 #endif // C++14
diff --git a/libstdc++-v3/testsuite/std/support/srcloc/consteval.std.n4842.C b/libstdc++-v3/testsuite/std/support/srcloc/consteval.std.n4842.C
new file mode 100644
index 00000000000..7ae135394d5
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/support/srcloc/consteval.std.n4842.C
@@ -0,0 +1,142 @@
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// Example from C++ Standard Working Draft N4842, November 2019 Mailing
+// Adapted for testing.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <source_location>
+#include <string_view>
+
+struct s {
+  std::source_location member = std::source_location::current();
+  int other_member = 1;
+  
+  constexpr s(std::source_location loc = std::source_location::current())
+    : member(loc) // values of member refer to calling function
+  { }
+
+  constexpr s(int blather) : // values of member refer to this location
+    other_member(blather)
+  { }
+};
+
+constexpr std::source_location
+f(std::source_location a = std::source_location::current())
+{ return a; }
+
+constexpr std::source_location
+g()
+{
+  std::source_location c = std::source_location::current();
+  return f(c);
+}
+
+#include "std.n4842.h"
+
+int main ()
+{
+  constexpr std::source_location main_sl = std::source_location::current();
+  constexpr std::source_location f_arg_sl = f(main_sl);
+  constexpr std::source_location g_sl = g();
+  constexpr std::source_location f_sl = f();
+  constexpr std::source_location h_sl = h();
+  constexpr s member_main_sl(main_sl);
+  constexpr s member_defaulted_sl(1);
+  constexpr s member_sl = s{};
+
+  using namespace std::string_view_literals;
+
+
+  static_assert (std::source_location::current ().line () == __LINE__);
+  static_assert (std::source_location::current ().column () == 49);
+
+
+  constexpr std::string_view main_sl_fn_name(main_sl.function_name());
+  constexpr std::string_view main_sl_fi_name(main_sl.file_name());
+  static_assert(main_sl.line() == 55);
+  // closing paren of call
+  static_assert(main_sl.column() == 74);
+  static_assert(main_sl_fn_name.ends_with("main"sv));
+  static_assert(main_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  constexpr std::string_view f_arg_sl_fn_name(f_arg_sl.function_name());
+  constexpr std::string_view f_arg_sl_fi_name(f_arg_sl.file_name());
+  static_assert(f_arg_sl.line() == 55);
+  // closing paren of call
+  static_assert(f_arg_sl.column() == 74);
+  static_assert(f_arg_sl_fn_name.ends_with("main"sv));
+  static_assert(f_arg_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  constexpr std::string_view g_sl_fn_name(g_sl.function_name());
+  constexpr std::string_view g_sl_fi_name(g_sl.file_name());
+  static_assert(g_sl.line() == 47);
+  static_assert(g_sl.column() == 58); // closing paren of call
+  static_assert(g_sl_fn_name.ends_with("g"sv));
+  static_assert(g_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  constexpr std::string_view h_sl_fn_name(h_sl.function_name());
+  constexpr std::string_view h_sl_fi_name(h_sl.file_name());
+  static_assert(h_sl.line() == 23);
+  static_assert(h_sl.column() == 58); // closing paren of call
+  static_assert(h_sl_fn_name.ends_with("h"sv));
+  static_assert(h_sl_fi_name.ends_with("std.n4842.h"sv));
+
+  constexpr std::string_view member_main_sl_fn_name(member_main_sl.member.function_name());
+  constexpr std::string_view member_main_sl_fi_name(member_main_sl.member.file_name());
+  static_assert(member_main_sl.member.line() == 55);
+  static_assert(member_main_sl.member.column() == 74);
+  static_assert(member_main_sl_fn_name.ends_with("main"sv));
+  static_assert(member_main_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  // FIXME: Jakub's patch does not
+  // properly get the name of the calling function
+  // when used as a default argument,
+  // nor does it follow the example in the std
+  // FIXME: All 3 cases below are busted
+  constexpr std::string_view member_defaulted_sl_fn_name(
+    member_defaulted_sl.member.function_name());
+  constexpr std::string_view member_defaulted_sl_fi_name(
+    member_defaulted_sl.member.file_name());
+  static_assert(member_defaulted_sl.member.line() == 35);
+  // closing paren of constructor declaration
+  static_assert(member_defaulted_sl.member.column() == 26);
+  static_assert(member_defaulted_sl_fn_name.ends_with("s::s"sv));
+  static_assert(member_defaulted_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  constexpr std::string_view member_sl_fn_name(
+    member_sl.member.function_name());
+  constexpr std::string_view member_sl_fi_name(
+    member_sl.member.file_name());
+  static_assert(member_sl.member.line() == 62);
+  // closing brace/paren of constructor
+  static_assert(member_sl.member.column() == 29);
+  static_assert(member_sl_fn_name.ends_with("main"sv));
+  static_assert(member_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  constexpr std::string_view f_sl_fn_name(f_sl.function_name());
+  constexpr std::string_view f_sl_fi_name(f_sl.file_name());
+  static_assert(f_sl.line() == 58);
+  // closing paren of call
+  static_assert(f_sl.column() == 43);
+  static_assert(f_sl_fn_name.ends_with("main"sv));
+  static_assert(f_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  return 0;
+}
diff --git a/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.C b/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.C
new file mode 100644
index 00000000000..78b6da0c88e
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.C
@@ -0,0 +1,139 @@
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// Example from C++ Standard Working Draft N4842, November 2019 Mailing
+// Adapted for testing.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do run { target c++2a } }
+
+#include <source_location>
+#include <string_view>
+#include <testsuite_hooks.h>
+#include "std.n4842.h"
+
+struct s {
+  std::source_location member = std::source_location::current();
+  int other_member = 1;
+  
+  s(std::source_location loc = std::source_location::current())
+    : member(loc) // values of member refer to calling function
+  { }
+
+  s(int blather) : // values of member refer to this location
+    other_member(blather)
+  { }
+};
+
+std::source_location
+f(std::source_location a = std::source_location::current());
+
+std::source_location
+f(std::source_location a)
+{ return a; }
+
+std::source_location
+g()
+{
+  std::source_location c = std::source_location::current();
+  return f(c);
+}
+#include <iostream>
+int main ()
+{
+  std::source_location main_sl = std::source_location::current();
+  std::source_location f_arg_sl = f(main_sl);
+  std::source_location g_sl = g();
+  std::source_location f_sl = f();
+  std::source_location h_sl = h();
+  s member_main_sl(main_sl);
+  s member_defaulted_sl(1);
+  s member_sl = s{};
+
+  using namespace std::string_view_literals;
+
+  std::string_view main_sl_fn_name(main_sl.function_name());
+  std::string_view main_sl_fi_name(main_sl.file_name());
+  VERIFY(main_sl.line() == 58);
+  // closing paren of call
+  VERIFY(main_sl.column() == 64);
+  VERIFY(main_sl_fn_name.ends_with("main"sv));
+  VERIFY(main_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view f_arg_sl_fn_name(f_arg_sl.function_name());
+  std::string_view f_arg_sl_fi_name(f_arg_sl.file_name());
+  VERIFY(f_arg_sl.line() == 58);
+  // closing paren of call
+  VERIFY(f_arg_sl.column() == 64);
+  VERIFY(f_arg_sl_fn_name.ends_with("main"sv));
+  VERIFY(f_arg_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view g_sl_fn_name(g_sl.function_name());
+  std::string_view g_sl_fi_name(g_sl.file_name());
+  VERIFY(g_sl.line() == 52);
+  VERIFY(g_sl.column() == 58); // closing paren of call
+  VERIFY(g_sl_fn_name.ends_with("g"sv));
+  VERIFY(g_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view h_sl_fn_name(h_sl.function_name());
+  std::string_view h_sl_fi_name(h_sl.file_name());
+  VERIFY(h_sl.line() == 23);
+  VERIFY(h_sl.column() == 58); // closing paren of call
+  VERIFY(h_sl_fn_name.ends_with("h"sv));
+  VERIFY(h_sl_fi_name.ends_with("std.n4842.h"sv));
+
+  std::string_view member_main_sl_fn_name(member_main_sl.member.function_name());
+  std::string_view member_main_sl_fi_name(member_main_sl.member.file_name());
+  VERIFY(member_main_sl.member.line() == 58);
+  VERIFY(member_main_sl.member.column() == 64);
+  VERIFY(member_main_sl_fn_name.ends_with("main"sv));
+  VERIFY(member_main_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view member_defaulted_sl_fn_name(
+    member_defaulted_sl.member.function_name());
+  std::string_view member_defaulted_sl_fi_name(
+    member_defaulted_sl.member.file_name());
+  std::cout << member_defaulted_sl.member.line() << std::endl;
+  std::cout << member_defaulted_sl.member.column() << std::endl;
+  std::cout << member_defaulted_sl_fn_name << std::endl;
+  std::cout << member_defaulted_sl_fi_name << std::endl;
+  VERIFY(member_defaulted_sl.member.line() == 37);
+  // closing paren of constructor declaration
+  VERIFY(member_defaulted_sl.member.column() == 26);
+  VERIFY(member_defaulted_sl_fn_name.ends_with("s::s"sv));
+  VERIFY(member_defaulted_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view member_sl_fn_name(
+    member_sl.member.function_name());
+  std::string_view member_sl_fi_name(
+    member_sl.member.file_name());
+  VERIFY(member_sl.member.line() == 62);
+  // closing brace/paren of constructor
+  VERIFY(member_sl.member.column() == 29);
+  VERIFY(member_sl_fn_name.ends_with("main"sv));
+  VERIFY(member_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  std::string_view f_sl_fn_name(f_sl.function_name());
+  std::string_view f_sl_fi_name(f_sl.file_name());
+  VERIFY(f_sl.line() == 58);
+  // closing paren of call
+  VERIFY(f_sl.column() == 43);
+  VERIFY(f_sl_fn_name.ends_with("main"sv));
+  VERIFY(f_sl_fi_name.ends_with("std.n4842.C"sv));
+
+  return 0;
+}
diff --git a/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.h b/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.h
new file mode 100644
index 00000000000..50ff5c41bea
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/support/srcloc/std.n4842.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <source_location>
+
+constexpr std::source_location
+h ()
+{
+  std::source_location a = std::source_location::current();
+  return a;
+}

  reply	other threads:[~2019-12-28  3:28 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-27 19:27 JeanHeyd Meneide
2019-12-27 19:33 ` Jakub Jelinek
2019-12-28  3:28   ` JeanHeyd Meneide [this message]
2020-01-02 10:28     ` Jonathan Wakely
2020-01-02 21:57       ` JeanHeyd Meneide
2020-01-02 22:07         ` Jakub Jelinek
2020-01-02 22:21           ` JeanHeyd Meneide
2020-12-03 19:25             ` Jonathan Wakely

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='CANHA4Ohhgv2-cF0Q-r0Y1UfM5Wo=12dFj_PrQKMQtS0VLdP5OQ@mail.gmail.com' \
    --to=phdofthehouse@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jakub@redhat.com \
    --cc=jwakely@redhat.com \
    --cc=libstdc++@gcc.gnu.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 read-only IMAP folder(s) and NNTP newsgroup(s).