From: "Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: jonathantanmy@google.com, dstolee@microsoft.com,
"Elijah Newren" <newren@gmail.com>,
"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
"Elijah Newren" <newren@gmail.com>,
"Elijah Newren" <newren@gmail.com>
Subject: [PATCH v2 15/20] merge-ort: step 3 of tree writing -- handling subdirectories as we go
Date: Fri, 04 Dec 2020 20:48:05 +0000 [thread overview]
Message-ID: <da4fe900496008193173cbdfb31200243b124d86.1607114891.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.923.v2.git.git.1607114890.gitgitgadget@gmail.com>
From: Elijah Newren <newren@gmail.com>
Our order for processing of entries means that if we have a tree of
files that looks like
Makefile
src/moduleA/foo.c
src/moduleA/bar.c
src/moduleB/baz.c
src/moduleB/umm.c
tokens.txt
Then we will process paths in the order of the leftmost column below. I
have added two additional columns that help explain the algorithm that
follows; the 2nd column is there to remind us we have oid & mode info we
are tracking for each of these paths (which differs between the paths
which I'm not representing well here), and the third column annotates
the parent directory of the entry:
tokens.txt <version_info> ""
src/moduleB/umm.c <version_info> src/moduleB
src/moduleB/baz.c <version_info> src/moduleB
src/moduleB <version_info> src
src/moduleA/foo.c <version_info> src/moduleA
src/moduleA/bar.c <version_info> src/moduleA
src/moduleA <version_info> src
src <version_info> ""
Makefile <version_info> ""
When the parent directory changes, if it's a subdirectory of the previous
parent directory (e.g. "" -> src/moduleB) then we can just keep appending.
If the parent directory differs from the previous parent directory and is
not a subdirectory, then we should process that directory.
So, for example, when we get to this point:
tokens.txt <version_info> ""
src/moduleB/umm.c <version_info> src/moduleB
src/moduleB/baz.c <version_info> src/moduleB
and note that the next entry (src/moduleB) has a different parent than
the last one that isn't a subdirectory, we should write out a tree for it
100644 blob <HASH> umm.c
100644 blob <HASH> baz.c
then pop all the entries under that directory while recording the new
hash for that directory, leaving us with
tokens.txt <version_info> ""
src/moduleB <new version_info> src
This process repeats until at the end we get to
tokens.txt <version_info> ""
src <new version_info> ""
Makefile <version_info> ""
and then we can write out the toplevel tree. Since we potentially have
entries in our string_list corresponding to multiple different toplevel
directories, e.g. a slightly different repository might have:
whizbang.txt <version_info> ""
tokens.txt <version_info> ""
src/moduleD <new version_info> src
src/moduleC <new version_info> src
src/moduleB <new version_info> src
src/moduleA/foo.c <version_info> src/moduleA
src/moduleA/bar.c <version_info> src/moduleA
When src/moduleA is popped off, we need to know that the "last
directory" reverts back to src, and how many entries in our string_list
are associated with that parent directory. So I use an auxiliary
offsets string_list which would have (parent_directory,offset)
information of the form
"" 0
src 2
src/moduleA 5
Whenever I write out a tree for a subdirectory, I set versions.nr to
the final offset value and then decrement offsets.nr...and then add
an entry to versions with a hash for the new directory.
The idea is relatively simple, there's just a lot of accounting to
implement this.
Signed-off-by: Elijah Newren <newren@gmail.com>
---
merge-ort.c | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 234 insertions(+), 8 deletions(-)
diff --git a/merge-ort.c b/merge-ort.c
index eec6874943..cf6f395c69 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -494,7 +494,46 @@ static int string_list_df_name_compare(const char *one, const char *two)
}
struct directory_versions {
+ /*
+ * versions: list of (basename -> version_info)
+ *
+ * The basenames are in reverse lexicographic order of full pathnames,
+ * as processed in process_entries(). This puts all entries within
+ * a directory together, and covers the directory itself after
+ * everything within it, allowing us to write subtrees before needing
+ * to record information for the tree itself.
+ */
struct string_list versions;
+
+ /*
+ * offsets: list of (full relative path directories -> integer offsets)
+ *
+ * Since versions contains basenames from files in multiple different
+ * directories, we need to know which entries in versions correspond
+ * to which directories. Values of e.g.
+ * "" 0
+ * src 2
+ * src/moduleA 5
+ * Would mean that entries 0-1 of versions are files in the toplevel
+ * directory, entries 2-4 are files under src/, and the remaining
+ * entries starting at index 5 are files under src/moduleA/.
+ */
+ struct string_list offsets;
+
+ /*
+ * last_directory: directory that previously processed file found in
+ *
+ * last_directory starts NULL, but records the directory in which the
+ * previous file was found within. As soon as
+ * directory(current_file) != last_directory
+ * then we need to start updating accounting in versions & offsets.
+ * Note that last_directory is always the last path in "offsets" (or
+ * NULL if "offsets" is empty) so this exists just for quick access.
+ */
+ const char *last_directory;
+
+ /* last_directory_len: cached computation of strlen(last_directory) */
+ unsigned last_directory_len;
};
static int tree_entry_order(const void *a_, const void *b_)
@@ -569,6 +608,181 @@ static void record_entry_for_tree(struct directory_versions *dir_metadata,
basename)->util = &mi->result;
}
+static void write_completed_directory(struct merge_options *opt,
+ const char *new_directory_name,
+ struct directory_versions *info)
+{
+ const char *prev_dir;
+ struct merged_info *dir_info = NULL;
+ unsigned int offset;
+
+ /*
+ * Some explanation of info->versions and info->offsets...
+ *
+ * process_entries() iterates over all relevant files AND
+ * directories in reverse lexicographic order, and calls this
+ * function. Thus, an example of the paths that process_entries()
+ * could operate on (along with the directories for those paths
+ * being shown) is:
+ *
+ * xtract.c ""
+ * tokens.txt ""
+ * src/moduleB/umm.c src/moduleB
+ * src/moduleB/stuff.h src/moduleB
+ * src/moduleB/baz.c src/moduleB
+ * src/moduleB src
+ * src/moduleA/foo.c src/moduleA
+ * src/moduleA/bar.c src/moduleA
+ * src/moduleA src
+ * src ""
+ * Makefile ""
+ *
+ * info->versions:
+ *
+ * always contains the unprocessed entries and their
+ * version_info information. For example, after the first five
+ * entries above, info->versions would be:
+ *
+ * xtract.c <xtract.c's version_info>
+ * token.txt <token.txt's version_info>
+ * umm.c <src/moduleB/umm.c's version_info>
+ * stuff.h <src/moduleB/stuff.h's version_info>
+ * baz.c <src/moduleB/baz.c's version_info>
+ *
+ * Once a subdirectory is completed we remove the entries in
+ * that subdirectory from info->versions, writing it as a tree
+ * (write_tree()). Thus, as soon as we get to src/moduleB,
+ * info->versions would be updated to
+ *
+ * xtract.c <xtract.c's version_info>
+ * token.txt <token.txt's version_info>
+ * moduleB <src/moduleB's version_info>
+ *
+ * info->offsets:
+ *
+ * helps us track which entries in info->versions correspond to
+ * which directories. When we are N directories deep (e.g. 4
+ * for src/modA/submod/subdir/), we have up to N+1 unprocessed
+ * directories (+1 because of toplevel dir). Corresponding to
+ * the info->versions example above, after processing five entries
+ * info->offsets will be:
+ *
+ * "" 0
+ * src/moduleB 2
+ *
+ * which is used to know that xtract.c & token.txt are from the
+ * toplevel dirctory, while umm.c & stuff.h & baz.c are from the
+ * src/moduleB directory. Again, following the example above,
+ * once we need to process src/moduleB, then info->offsets is
+ * updated to
+ *
+ * "" 0
+ * src 2
+ *
+ * which says that moduleB (and only moduleB so far) is in the
+ * src directory.
+ *
+ * One unique thing to note about info->offsets here is that
+ * "src" was not added to info->offsets until there was a path
+ * (a file OR directory) immediately below src/ that got
+ * processed.
+ *
+ * Since process_entry() just appends new entries to info->versions,
+ * write_completed_directory() only needs to do work if the next path
+ * is in a directory that is different than the last directory found
+ * in info->offsets.
+ */
+
+ /*
+ * If we are working with the same directory as the last entry, there
+ * is no work to do. (See comments above the directory_name member of
+ * struct merged_info for why we can use pointer comparison instead of
+ * strcmp here.)
+ */
+ if (new_directory_name == info->last_directory)
+ return;
+
+ /*
+ * If we are just starting (last_directory is NULL), or last_directory
+ * is a prefix of the current directory, then we can just update
+ * info->offsets to record the offset where we started this directory
+ * and update last_directory to have quick access to it.
+ */
+ if (info->last_directory == NULL ||
+ !strncmp(new_directory_name, info->last_directory,
+ info->last_directory_len)) {
+ uintptr_t offset = info->versions.nr;
+
+ info->last_directory = new_directory_name;
+ info->last_directory_len = strlen(info->last_directory);
+ /*
+ * Record the offset into info->versions where we will
+ * start recording basenames of paths found within
+ * new_directory_name.
+ */
+ string_list_append(&info->offsets,
+ info->last_directory)->util = (void*)offset;
+ return;
+ }
+
+ /*
+ * The next entry that will be processed will be within
+ * new_directory_name. Since at this point we know that
+ * new_directory_name is within a different directory than
+ * info->last_directory, we have all entries for info->last_directory
+ * in info->versions and we need to create a tree object for them.
+ */
+ dir_info = strmap_get(&opt->priv->paths, info->last_directory);
+ assert(dir_info);
+ offset = (uintptr_t)info->offsets.items[info->offsets.nr-1].util;
+ if (offset == info->versions.nr) {
+ /*
+ * Actually, we don't need to create a tree object in this
+ * case. Whenever all files within a directory disappear
+ * during the merge (e.g. unmodified on one side and
+ * deleted on the other, or files were renamed elsewhere),
+ * then we get here and the directory itself needs to be
+ * omitted from its parent tree as well.
+ */
+ dir_info->is_null = 1;
+ } else {
+ /*
+ * Write out the tree to the git object directory, and also
+ * record the mode and oid in dir_info->result.
+ */
+ dir_info->is_null = 0;
+ dir_info->result.mode = S_IFDIR;
+ write_tree(&dir_info->result.oid, &info->versions, offset,
+ opt->repo->hash_algo->rawsz);
+ }
+
+ /*
+ * We've now used several entries from info->versions and one entry
+ * from info->offsets, so we get rid of those values.
+ */
+ info->offsets.nr--;
+ info->versions.nr = offset;
+
+ /*
+ * Now we've taken care of the completed directory, but we need to
+ * prepare things since future entries will be in
+ * new_directory_name. (In particular, process_entry() will be
+ * appending new entries to info->versions.) So, we need to make
+ * sure new_directory_name is the last entry in info->offsets.
+ */
+ prev_dir = info->offsets.nr == 0 ? NULL :
+ info->offsets.items[info->offsets.nr-1].string;
+ if (new_directory_name != prev_dir) {
+ uintptr_t c = info->versions.nr;
+ string_list_append(&info->offsets,
+ new_directory_name)->util = (void*)c;
+ }
+
+ /* And, of course, we need to update last_directory to match. */
+ info->last_directory = new_directory_name;
+ info->last_directory_len = strlen(info->last_directory);
+}
+
/* Per entry merge function */
static void process_entry(struct merge_options *opt,
const char *path,
@@ -667,7 +881,9 @@ static void process_entries(struct merge_options *opt,
struct strmap_entry *e;
struct string_list plist = STRING_LIST_INIT_NODUP;
struct string_list_item *entry;
- struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP };
+ struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP,
+ STRING_LIST_INIT_NODUP,
+ NULL, 0 };
if (strmap_empty(&opt->priv->paths)) {
oidcpy(result_oid, opt->repo->hash_algo->empty_tree);
@@ -687,6 +903,11 @@ static void process_entries(struct merge_options *opt,
/*
* Iterate over the items in reverse order, so we can handle paths
* below a directory before needing to handle the directory itself.
+ *
+ * This allows us to write subtrees before we need to write trees,
+ * and it also enables sane handling of directory/file conflicts
+ * (because it allows us to know whether the directory is still in
+ * the way when it is time to process the file at the same path).
*/
for (entry = &plist.items[plist.nr-1]; entry >= plist.items; --entry) {
char *path = entry->string;
@@ -697,6 +918,8 @@ static void process_entries(struct merge_options *opt,
*/
struct merged_info *mi = entry->util;
+ write_completed_directory(opt, mi->directory_name,
+ &dir_metadata);
if (mi->clean)
record_entry_for_tree(&dir_metadata, path, mi);
else {
@@ -705,17 +928,20 @@ static void process_entries(struct merge_options *opt,
}
}
- /*
- * TODO: We can't actually write a tree yet, because dir_metadata just
- * contains all basenames of all files throughout the tree with their
- * mode and hash. Not only is that a nonsensical tree, it will have
- * lots of duplicates for paths such as "Makefile" or ".gitignore".
- */
- die("Not yet implemented; need to process subtrees separately");
+ if (dir_metadata.offsets.nr != 1 ||
+ (uintptr_t)dir_metadata.offsets.items[0].util != 0) {
+ printf("dir_metadata.offsets.nr = %d (should be 1)\n",
+ dir_metadata.offsets.nr);
+ printf("dir_metadata.offsets.items[0].util = %u (should be 0)\n",
+ (unsigned)(uintptr_t)dir_metadata.offsets.items[0].util);
+ fflush(stdout);
+ BUG("dir_metadata accounting completely off; shouldn't happen");
+ }
write_tree(result_oid, &dir_metadata.versions, 0,
opt->repo->hash_algo->rawsz);
string_list_clear(&plist, 0);
string_list_clear(&dir_metadata.versions, 0);
+ string_list_clear(&dir_metadata.offsets, 0);
}
void merge_switch_to_result(struct merge_options *opt,
--
gitgitgadget
next prev parent reply other threads:[~2020-12-04 20:50 UTC|newest]
Thread overview: 77+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-11-29 7:43 [PATCH 00/20] fundamentals of merge-ort implementation Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 01/20] merge-ort: setup basic internal data structures Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 02/20] merge-ort: add some high-level algorithm structure Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 03/20] merge-ort: port merge_start() from merge-recursive Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 04/20] merge-ort: use histogram diff Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 05/20] merge-ort: add an err() function similar to one from merge-recursive Elijah Newren via GitGitGadget
2020-11-29 10:23 ` Ævar Arnfjörð Bjarmason
2020-11-30 16:56 ` Elijah Newren
2020-11-29 10:26 ` Ævar Arnfjörð Bjarmason
2020-11-29 7:43 ` [PATCH 06/20] merge-ort: implement a very basic collect_merge_info() Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 07/20] merge-ort: avoid repeating fill_tree_descriptor() on the same tree Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 08/20] merge-ort: compute a few more useful fields for collect_merge_info Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 09/20] merge-ort: record stage and auxiliary info for every path Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 10/20] merge-ort: avoid recursing into identical trees Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 11/20] merge-ort: add a preliminary simple process_entries() implementation Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 12/20] merge-ort: have process_entries operate in a defined order Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 13/20] merge-ort: step 1 of tree writing -- record basenames, modes, and oids Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 14/20] merge-ort: step 2 of tree writing -- function to create tree object Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 15/20] merge-ort: step 3 of tree writing -- handling subdirectories as we go Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 16/20] merge-ort: basic outline for merge_switch_to_result() Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 17/20] merge-ort: add implementation of checkout() Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 18/20] tree: enable cmp_cache_name_compare() to be used elsewhere Elijah Newren via GitGitGadget
2020-11-29 7:43 ` [PATCH 19/20] merge-ort: add implementation of record_conflicted_index_entries() Elijah Newren via GitGitGadget
2020-11-29 10:20 ` Ævar Arnfjörð Bjarmason
2020-11-29 7:43 ` [PATCH 20/20] merge-ort: free data structures in merge_finalize() Elijah Newren via GitGitGadget
2020-11-29 7:47 ` [PATCH 00/20] fundamentals of merge-ort implementation Elijah Newren
2020-12-04 20:47 ` [PATCH v2 " Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 01/20] merge-ort: setup basic internal data structures Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 02/20] merge-ort: add some high-level algorithm structure Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 03/20] merge-ort: port merge_start() from merge-recursive Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 04/20] merge-ort: use histogram diff Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 05/20] merge-ort: add an err() function similar to one from merge-recursive Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 06/20] merge-ort: implement a very basic collect_merge_info() Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 07/20] merge-ort: avoid repeating fill_tree_descriptor() on the same tree Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 08/20] merge-ort: compute a few more useful fields for collect_merge_info Elijah Newren via GitGitGadget
2020-12-04 20:47 ` [PATCH v2 09/20] merge-ort: record stage and auxiliary info for every path Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 10/20] merge-ort: avoid recursing into identical trees Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 11/20] merge-ort: add a preliminary simple process_entries() implementation Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 12/20] merge-ort: have process_entries operate in a defined order Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 13/20] merge-ort: step 1 of tree writing -- record basenames, modes, and oids Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 14/20] merge-ort: step 2 of tree writing -- function to create tree object Elijah Newren via GitGitGadget
2020-12-04 20:48 ` Elijah Newren via GitGitGadget [this message]
2020-12-04 20:48 ` [PATCH v2 16/20] merge-ort: basic outline for merge_switch_to_result() Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 17/20] merge-ort: add implementation of checkout() Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 18/20] tree: enable cmp_cache_name_compare() to be used elsewhere Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 19/20] merge-ort: add implementation of record_conflicted_index_entries() Elijah Newren via GitGitGadget
2020-12-04 20:48 ` [PATCH v2 20/20] merge-ort: free data structures in merge_finalize() Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 00/20] fundamentals of merge-ort implementation Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 01/20] merge-ort: setup basic internal data structures Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 02/20] merge-ort: add some high-level algorithm structure Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 03/20] merge-ort: port merge_start() from merge-recursive Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 04/20] merge-ort: use histogram diff Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 05/20] merge-ort: add an err() function similar to one from merge-recursive Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 06/20] merge-ort: implement a very basic collect_merge_info() Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 07/20] merge-ort: avoid repeating fill_tree_descriptor() on the same tree Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 08/20] merge-ort: compute a few more useful fields for collect_merge_info Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 09/20] merge-ort: record stage and auxiliary info for every path Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 10/20] merge-ort: avoid recursing into identical trees Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 11/20] merge-ort: add a preliminary simple process_entries() implementation Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 12/20] merge-ort: have process_entries operate in a defined order Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 13/20] merge-ort: step 1 of tree writing -- record basenames, modes, and oids Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 14/20] merge-ort: step 2 of tree writing -- function to create tree object Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 15/20] merge-ort: step 3 of tree writing -- handling subdirectories as we go Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 16/20] merge-ort: basic outline for merge_switch_to_result() Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 17/20] merge-ort: add implementation of checkout() Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 18/20] tree: enable cmp_cache_name_compare() to be used elsewhere Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 19/20] merge-ort: add implementation of record_conflicted_index_entries() Elijah Newren via GitGitGadget
2020-12-13 8:04 ` [PATCH v3 20/20] merge-ort: free data structures in merge_finalize() Elijah Newren via GitGitGadget
2020-12-14 14:24 ` [PATCH v3 00/20] fundamentals of merge-ort implementation Felipe Contreras
2020-12-14 16:24 ` Elijah Newren
-- strict thread matches above, loose matches on Subject: below --
2020-11-02 20:43 [PATCH v2 " Elijah Newren
2020-11-02 20:43 ` [PATCH v2 15/20] merge-ort: step 3 of tree writing -- handling subdirectories as we go Elijah Newren
2020-11-12 20:15 ` Jonathan Tan
2020-11-12 22:30 ` Elijah Newren
2020-11-24 20:19 ` Elijah Newren
2020-11-25 2:07 ` Jonathan Tan
2020-11-26 18:13 ` Elijah Newren
2020-11-30 18:41 ` Jonathan Tan
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
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=da4fe900496008193173cbdfb31200243b124d86.1607114891.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=avarab@gmail.com \
--cc=dstolee@microsoft.com \
--cc=git@vger.kernel.org \
--cc=jonathantanmy@google.com \
--cc=newren@gmail.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.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
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).