From: Ben Peart <Ben.Peart@microsoft.com>
To: "git@vger.kernel.org" <git@vger.kernel.org>
Cc: "gitster@pobox.com" <gitster@pobox.com>,
Ben Peart <Ben.Peart@microsoft.com>
Subject: [PATCH v1 3/3] Add initial parallel version of unpack_trees()
Date: Wed, 18 Jul 2018 20:45:17 +0000 [thread overview]
Message-ID: <20180718204458.20936-4-benpeart@microsoft.com> (raw)
In-Reply-To: <20180718204458.20936-1-benpeart@microsoft.com>
Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
cache.h | 1 +
config.c | 5 +
environment.c | 1 +
unpack-trees.c | 313 ++++++++++++++++++++++++++++++++++++++++++++++++-
unpack-trees.h | 30 +++++
5 files changed, 348 insertions(+), 2 deletions(-)
diff --git a/cache.h b/cache.h
index d49092d94d..4bfa35c497 100644
--- a/cache.h
+++ b/cache.h
@@ -815,6 +815,7 @@ extern int fsync_object_files;
extern int core_preload_index;
extern int core_commit_graph;
extern int core_apply_sparse_checkout;
+extern int core_parallel_unpack_trees;
extern int precomposed_unicode;
extern int protect_hfs;
extern int protect_ntfs;
diff --git a/config.c b/config.c
index f4a208a166..34d5506588 100644
--- a/config.c
+++ b/config.c
@@ -1346,6 +1346,11 @@ static int git_default_core_config(const char *var, const char *value)
var, value);
}
+ if (!strcmp(var, "core.parallelunpacktrees")) {
+ core_parallel_unpack_trees = git_config_bool(var, value);
+ return 0;
+ }
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
diff --git a/environment.c b/environment.c
index 2a6de2330b..1eb0a05074 100644
--- a/environment.c
+++ b/environment.c
@@ -68,6 +68,7 @@ char *notes_ref_name;
int grafts_replace_parents = 1;
int core_commit_graph;
int core_apply_sparse_checkout;
+int core_parallel_unpack_trees;
int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg;
diff --git a/unpack-trees.c b/unpack-trees.c
index 1f58efc6bb..2333626efd 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -17,6 +17,7 @@
#include "submodule-config.h"
#include "fsmonitor.h"
#include "fetch-object.h"
+#include "thread-utils.h"
/*
* Error messages expected by scripts out of plumbing commands such as
@@ -641,6 +642,98 @@ static inline int are_same_oid(struct name_entry *name_j, struct name_entry *nam
return name_j->oid && name_k->oid && !oidcmp(name_j->oid, name_k->oid);
}
+#ifndef NO_PTHREADS
+
+struct traverse_info_parallel {
+ struct mpmcq_entry entry;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ void *buf[MAX_UNPACK_TREES];
+ struct traverse_info info;
+ int n;
+ int nr_buf;
+ int ret;
+};
+
+static int traverse_trees_parallel(int n, unsigned long dirmask,
+ unsigned long df_conflicts,
+ struct name_entry *names,
+ struct traverse_info *info)
+{
+ int i;
+ struct name_entry *p;
+ struct unpack_trees_options *o = info->data;
+ struct traverse_info_parallel *newinfo;
+
+ p = names;
+ while (!p->mode)
+ p++;
+
+ newinfo = xmalloc(sizeof(struct traverse_info_parallel));
+ mpmcq_entry_init(&newinfo->entry);
+ newinfo->info = *info;
+ newinfo->info.prev = info;
+ newinfo->info.pathspec = info->pathspec;
+ newinfo->info.name = *p;
+ newinfo->info.pathlen += tree_entry_len(p) + 1;
+ newinfo->info.df_conflicts |= df_conflicts;
+ newinfo->nr_buf = 0;
+ newinfo->n = n;
+
+ /*
+ * Fetch the tree from the ODB for each peer directory in the
+ * n commits.
+ *
+ * For 2- and 3-way traversals, we try to avoid hitting the
+ * ODB twice for the same OID. This should yield a nice speed
+ * up in checkouts and merges when the commits are similar.
+ *
+ * We don't bother doing the full O(n^2) search for larger n,
+ * because wider traversals don't happen that often and we
+ * avoid the search setup.
+ *
+ * When 2 peer OIDs are the same, we just copy the tree
+ * descriptor data. This implicitly borrows the buffer
+ * data from the earlier cell.
+ */
+ for (i = 0; i < n; i++, dirmask >>= 1) {
+ if (i > 0 && are_same_oid(&names[i], &names[i - 1]))
+ newinfo->t[i] = newinfo->t[i - 1];
+ else if (i > 1 && are_same_oid(&names[i], &names[i - 2]))
+ newinfo->t[i] = newinfo->t[i - 2];
+ else {
+ const struct object_id *oid = NULL;
+ if (dirmask & 1)
+ oid = names[i].oid;
+
+ /*
+ * fill_tree_descriptor() will load the tree from the
+ * ODB. Accessing the ODB is not thread safe so
+ * serialize access using the odb_mutex.
+ */
+ pthread_mutex_lock(&o->odb_mutex);
+ newinfo->buf[newinfo->nr_buf++] =
+ fill_tree_descriptor(newinfo->t + i, oid);
+ pthread_mutex_unlock(&o->odb_mutex);
+ }
+ }
+
+ /*
+ * We can't play games with the cache bottom as we are processing
+ * the tree objects in parallel.
+ * newinfo->bottom = switch_cache_bottom(&newinfo->info);
+ */
+
+ /* All I really need here is fetch_and_add() */
+ pthread_mutex_lock(&o->work_mutex);
+ o->remaining_work++;
+ pthread_mutex_unlock(&o->work_mutex);
+ mpmcq_push(&o->queue, &newinfo->entry);
+
+ return 0;
+}
+
+#endif
+
static int traverse_trees_recursive(int n, unsigned long dirmask,
unsigned long df_conflicts,
struct name_entry *names,
@@ -995,6 +1088,108 @@ static void debug_unpack_callback(int n,
debug_name_entry(i, names + i);
}
+static int unpack_callback_parallel(int n, unsigned long mask,
+ unsigned long dirmask,
+ struct name_entry *names,
+ struct traverse_info *info)
+{
+ struct cache_entry *src[MAX_UNPACK_TREES + 1] = {
+ NULL,
+ };
+ struct unpack_trees_options *o = info->data;
+ const struct name_entry *p = names;
+
+ /* Find first entry with a real name (we could use "mask" too) */
+ while (!p->mode)
+ p++;
+
+ if (o->debug_unpack)
+ debug_unpack_callback(n, mask, dirmask, names, info);
+
+ /* Are we supposed to look at the index too? */
+ if (o->merge) {
+ while (1) {
+ int cmp;
+ struct cache_entry *ce;
+
+ if (o->diff_index_cached)
+ ce = next_cache_entry(o);
+ else
+ ce = find_cache_entry(info, p);
+
+ if (!ce)
+ break;
+ cmp = compare_entry(ce, info, p);
+ if (cmp < 0) {
+ int ret;
+
+ pthread_mutex_lock(&o->unpack_index_entry_mutex);
+ ret = unpack_index_entry(ce, o);
+ pthread_mutex_unlock(&o->unpack_index_entry_mutex);
+ if (ret < 0)
+ return unpack_failed(o, NULL);
+ continue;
+ }
+ if (!cmp) {
+ if (ce_stage(ce)) {
+ /*
+ * If we skip unmerged index
+ * entries, we'll skip this
+ * entry *and* the tree
+ * entries associated with it!
+ */
+ if (o->skip_unmerged) {
+ add_same_unmerged(ce, o);
+ return mask;
+ }
+ }
+ src[0] = ce;
+ }
+ break;
+ }
+ }
+
+ pthread_mutex_lock(&o->unpack_nondirectories_mutex);
+ int ret = unpack_nondirectories(n, mask, dirmask, src, names, info);
+ pthread_mutex_unlock(&o->unpack_nondirectories_mutex);
+ if (ret < 0)
+ return -1;
+
+ if (o->merge && src[0]) {
+ if (ce_stage(src[0]))
+ mark_ce_used_same_name(src[0], o);
+ else
+ mark_ce_used(src[0], o);
+ }
+
+ /* Now handle any directories.. */
+ if (dirmask) {
+ /* special case: "diff-index --cached" looking at a tree */
+ if (o->diff_index_cached && n == 1 && dirmask == 1 &&
+ S_ISDIR(names->mode)) {
+ int matches;
+ matches = cache_tree_matches_traversal(
+ o->src_index->cache_tree, names, info);
+ /*
+ * Everything under the name matches; skip the
+ * entire hierarchy. diff_index_cached codepath
+ * special cases D/F conflicts in such a way that
+ * it does not do any look-ahead, so this is safe.
+ */
+ if (matches) {
+ o->cache_bottom += matches;
+ return mask;
+ }
+ }
+
+ if (traverse_trees_parallel(n, dirmask, mask & ~dirmask, names, info) < 0)
+ return -1;
+ return mask;
+ }
+
+ return mask;
+}
+
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@@ -1263,6 +1458,116 @@ static void mark_new_skip_worktree(struct exclude_list *el,
static int verify_absent(const struct cache_entry *,
enum unpack_trees_error_types,
struct unpack_trees_options *);
+
+#ifndef NO_PTHREADS
+static void *traverse_trees_parallel_thread_proc(void *_data)
+{
+ struct unpack_trees_options *o = _data;
+ struct traverse_info_parallel *info;
+ int i;
+
+ while (1) {
+ info = (struct traverse_info_parallel *)mpmcq_pop(&o->queue);
+ if (!info)
+ break;
+
+ info->ret = traverse_trees(info->n, info->t, &info->info);
+ /*
+ * We can't play games with the cache bottom as we are processing
+ * the tree objects in parallel.
+ * restore_cache_bottom(&info->info, info->bottom);
+ */
+
+ for (i = 0; i < info->nr_buf; i++)
+ free(info->buf[i]);
+ /*
+ * TODO: Can't free "info" when thread is done because it can be used
+ * as ->prev link in child info objects. Ref count? Free all at end?
+ free(info);
+ */
+
+ /* All I really need here is fetch_and_add() */
+ pthread_mutex_lock(&o->work_mutex);
+ o->remaining_work--;
+ if (o->remaining_work == 0)
+ mpmcq_cancel(&o->queue);
+ pthread_mutex_unlock(&o->work_mutex);
+ }
+
+ return NULL;
+}
+
+static void init_parallel_traverse(struct unpack_trees_options *o,
+ struct traverse_info *info)
+{
+ /*
+ * TODO: Add logic to bypass parallel path when not needed.
+ * - not enough CPU cores to help
+ * - 'git status' is always fast - how to detect?
+ * - small trees (may be able to use index size as proxy, small index likely means small commit tree)
+ */
+ if (core_parallel_unpack_trees) {
+ int t;
+
+ mpmcq_init(&o->queue);
+ o->remaining_work = 0;
+ pthread_mutex_init(&o->unpack_nondirectories_mutex, NULL);
+ pthread_mutex_init(&o->unpack_index_entry_mutex, NULL);
+ pthread_mutex_init(&o->odb_mutex, NULL);
+ pthread_mutex_init(&o->work_mutex, NULL);
+ o->nr_threads = online_cpus();
+ o->pthreads = xcalloc(o->nr_threads, sizeof(pthread_t));
+ info->fn = unpack_callback_parallel;
+
+ for (t = 0; t < o->nr_threads; t++) {
+ if (pthread_create(&o->pthreads[t], NULL,
+ traverse_trees_parallel_thread_proc,
+ o))
+ die("unable to create traverse_trees_parallel_thread");
+ }
+ }
+}
+
+static void wait_parallel_traverse(struct unpack_trees_options *o)
+{
+ /*
+ * The first tree (root directory) is processed on the main thread.
+ * This function is called after it has completed. If there is no
+ * remaining work, we know we are finished.
+ */
+ if (core_parallel_unpack_trees) {
+ int t;
+
+ pthread_mutex_lock(&o->work_mutex);
+ if (o->remaining_work == 0)
+ mpmcq_cancel(&o->queue);
+ pthread_mutex_unlock(&o->work_mutex);
+
+ for (t = 0; t < o->nr_threads; t++) {
+ if (pthread_join(o->pthreads[t], NULL))
+ die("unable to join traverse_trees_parallel_thread");
+ }
+
+ free(o->pthreads);
+ pthread_mutex_destroy(&o->work_mutex);
+ pthread_mutex_destroy(&o->odb_mutex);
+ pthread_mutex_destroy(&o->unpack_index_entry_mutex);
+ pthread_mutex_destroy(&o->unpack_nondirectories_mutex);
+ mpmcq_destroy(&o->queue);
+ }
+}
+#else
+static void init_parallel_traverse(struct unpack_trees_options *o)
+{
+ return;
+}
+
+static void wait_parallel_traverse(struct unpack_trees_options *o)
+{
+ return;
+}
+#endif
+
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
@@ -1327,6 +1632,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
const char *prefix = o->prefix ? o->prefix : "";
struct traverse_info info;
uint64_t start;
+ int ret;
setup_traverse_info(&info, prefix);
info.fn = unpack_callback;
@@ -1352,9 +1658,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
}
start = getnanotime();
- if (traverse_trees(len, t, &info) < 0)
- goto return_failed;
+ init_parallel_traverse(o, &info);
+ ret = traverse_trees(len, t, &info);
+ wait_parallel_traverse(o);
trace_performance_since(start, "traverse_trees");
+ if (ret < 0)
+ goto return_failed;
}
/* Any left-over entries in the index? */
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..b7140099fa 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -3,6 +3,11 @@
#include "tree-walk.h"
#include "argv-array.h"
+#ifndef NO_PTHREADS
+#include "git-compat-util.h"
+#include <pthread.h>
+#include "mpmcqueue.h"
+#endif
#define MAX_UNPACK_TREES 8
@@ -80,6 +85,31 @@ struct unpack_trees_options {
struct index_state result;
struct exclude_list *el; /* for internal use */
+#ifndef NO_PTHREADS
+ /*
+ * Speed up the tree traversal by adding all discovered tree objects
+ * into a queue and have a pool of worker threads process them in
+ * parallel. Since there is no upper bound on the size of a tree and
+ * each worker thread will be adding discovered tree objects to the
+ * queue, we need an unbounded multi-producer-multi-consumer queue.
+ */
+ struct mpmcq queue;
+
+ int nr_threads;
+ pthread_t *pthreads;
+
+ /* need a mutex as we don't have fetch_and_add() */
+ int remaining_work;
+ pthread_mutex_t work_mutex;
+
+ /* The ODB is not thread safe so we must serialize access to it */
+ pthread_mutex_t odb_mutex;
+
+ /* various functions that are not thread safe and must be serialized for now */
+ pthread_mutex_t unpack_index_entry_mutex;
+ pthread_mutex_t unpack_nondirectories_mutex;
+
+#endif
};
extern int unpack_trees(unsigned n, struct tree_desc *t,
--
2.17.0.gvfs.1.123.g449c066
next prev parent reply other threads:[~2018-07-18 20:45 UTC|newest]
Thread overview: 121+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-07-18 20:45 [PATCH v1 0/3] [RFC] Speeding up checkout (and merge, rebase, etc) Ben Peart
2018-07-18 20:45 ` [PATCH v1 1/3] add unbounded Multi-Producer-Multi-Consumer queue Ben Peart
2018-07-18 20:57 ` Stefan Beller
2018-07-19 19:11 ` Junio C Hamano
2018-07-18 20:45 ` [PATCH v1 2/3] add performance tracing around traverse_trees() in unpack_trees() Ben Peart
2018-07-18 20:45 ` Ben Peart [this message]
2018-07-18 22:56 ` [PATCH v1 3/3] Add initial parallel version of unpack_trees() Junio C Hamano
2018-07-18 21:02 ` [PATCH v1 0/3] [RFC] Speeding up checkout (and merge, rebase, etc) Stefan Beller
2018-07-18 21:34 ` Jeff King
2018-07-23 15:48 ` Ben Peart
2018-07-23 17:03 ` Duy Nguyen
2018-07-23 20:51 ` Ben Peart
2018-07-24 4:20 ` Jeff King
2018-07-24 15:33 ` Duy Nguyen
2018-07-25 20:56 ` Ben Peart
2018-07-26 5:30 ` Duy Nguyen
2018-07-26 16:30 ` Duy Nguyen
2018-07-26 19:40 ` Junio C Hamano
2018-07-27 15:42 ` Duy Nguyen
2018-07-27 16:22 ` Ben Peart
2018-07-27 18:00 ` Duy Nguyen
2018-07-27 17:14 ` Junio C Hamano
2018-07-27 17:52 ` Duy Nguyen
2018-07-29 6:24 ` Duy Nguyen
2018-07-29 10:33 ` [PATCH v2 0/4] Speed up unpack_trees() Nguyễn Thái Ngọc Duy
2018-07-29 10:33 ` [PATCH v2 1/4] unpack-trees.c: add performance tracing Nguyễn Thái Ngọc Duy
2018-07-30 20:16 ` Ben Peart
2018-07-29 10:33 ` [PATCH v2 2/4] unpack-trees: optimize walking same trees with cache-tree Nguyễn Thái Ngọc Duy
2018-07-30 20:52 ` Ben Peart
2018-07-29 10:33 ` [PATCH v2 3/4] unpack-trees: reduce malloc in cache-tree walk Nguyễn Thái Ngọc Duy
2018-07-30 20:58 ` Ben Peart
2018-07-29 10:33 ` [PATCH v2 4/4] unpack-trees: cheaper index update when walking by cache-tree Nguyễn Thái Ngọc Duy
2018-08-08 18:46 ` Elijah Newren
2018-08-10 16:39 ` Duy Nguyen
2018-08-10 18:39 ` Elijah Newren
2018-08-10 19:30 ` Duy Nguyen
2018-08-10 19:40 ` Elijah Newren
2018-08-10 19:48 ` Duy Nguyen
2018-07-30 18:10 ` [PATCH v2 0/4] Speed up unpack_trees() Ben Peart
2018-07-31 15:31 ` Duy Nguyen
2018-07-31 16:50 ` Ben Peart
2018-07-31 17:31 ` Ben Peart
2018-08-01 16:38 ` Duy Nguyen
2018-08-08 20:53 ` Ben Peart
2018-08-09 8:16 ` Ben Peart
2018-08-10 16:08 ` Duy Nguyen
2018-08-10 15:51 ` Duy Nguyen
2018-07-30 21:04 ` Ben Peart
2018-08-04 5:37 ` [PATCH v3 " Nguyễn Thái Ngọc Duy
2018-08-04 5:37 ` [PATCH v3 1/4] unpack-trees: add performance tracing Nguyễn Thái Ngọc Duy
2018-08-04 5:37 ` [PATCH v3 2/4] unpack-trees: optimize walking same trees with cache-tree Nguyễn Thái Ngọc Duy
2018-08-08 18:23 ` Elijah Newren
2018-08-10 16:29 ` Duy Nguyen
2018-08-10 18:48 ` Elijah Newren
2018-08-04 5:37 ` [PATCH v3 3/4] unpack-trees: reduce malloc in cache-tree walk Nguyễn Thái Ngọc Duy
2018-08-08 18:30 ` Elijah Newren
2018-08-04 5:37 ` [PATCH v3 4/4] unpack-trees: cheaper index update when walking by cache-tree Nguyễn Thái Ngọc Duy
2018-08-06 15:48 ` [PATCH v3 0/4] Speed up unpack_trees() Junio C Hamano
2018-08-06 15:59 ` Duy Nguyen
2018-08-06 18:59 ` Junio C Hamano
2018-08-08 17:00 ` Ben Peart
2018-08-08 17:46 ` Junio C Hamano
2018-08-08 18:12 ` Junio C Hamano
2018-08-08 18:39 ` Junio C Hamano
2018-08-10 16:53 ` Duy Nguyen
2018-08-12 8:15 ` [PATCH v4 0/5] " Nguyễn Thái Ngọc Duy
2018-08-12 8:15 ` [PATCH v4 1/5] trace.h: support nested performance tracing Nguyễn Thái Ngọc Duy
2018-08-13 18:39 ` Ben Peart
2018-08-12 8:15 ` [PATCH v4 2/5] unpack-trees: add " Nguyễn Thái Ngọc Duy
2018-08-12 10:05 ` Thomas Adam
2018-08-13 18:50 ` Junio C Hamano
2018-08-13 18:44 ` Ben Peart
2018-08-13 19:25 ` Jeff King
2018-08-13 19:36 ` Stefan Beller
2018-08-13 20:11 ` Ben Peart
2018-08-13 19:52 ` Duy Nguyen
2018-08-13 21:47 ` Jeff King
2018-08-13 22:41 ` Junio C Hamano
2018-08-14 18:19 ` Jeff Hostetler
2018-08-14 18:32 ` Duy Nguyen
2018-08-14 18:44 ` Stefan Beller
2018-08-14 18:51 ` Duy Nguyen
2018-08-14 19:54 ` Jeff King
2018-08-14 20:52 ` Junio C Hamano
2018-08-15 16:32 ` Duy Nguyen
2018-08-15 18:28 ` Junio C Hamano
2018-08-14 20:14 ` Jeff Hostetler
2018-08-12 8:15 ` [PATCH v4 3/5] unpack-trees: optimize walking same trees with cache-tree Nguyễn Thái Ngọc Duy
2018-08-13 18:58 ` Ben Peart
2018-08-15 16:38 ` Duy Nguyen
2018-08-12 8:15 ` [PATCH v4 4/5] unpack-trees: reduce malloc in cache-tree walk Nguyễn Thái Ngọc Duy
2018-08-12 8:15 ` [PATCH v4 5/5] unpack-trees: reuse (still valid) cache-tree from src_index Nguyễn Thái Ngọc Duy
2018-08-13 15:48 ` Elijah Newren
2018-08-13 15:57 ` Duy Nguyen
2018-08-13 16:05 ` Ben Peart
2018-08-13 16:25 ` Duy Nguyen
2018-08-13 17:15 ` Ben Peart
2018-08-13 19:01 ` [PATCH v4 0/5] Speed up unpack_trees() Junio C Hamano
2018-08-14 19:19 ` Ben Peart
2018-08-18 14:41 ` [PATCH v5 0/7] " Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 1/7] trace.h: support nested performance tracing Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 2/7] unpack-trees: add " Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 3/7] unpack-trees: optimize walking same trees with cache-tree Nguyễn Thái Ngọc Duy
2018-08-20 12:43 ` Ben Peart
2018-08-18 14:41 ` [PATCH v5 4/7] unpack-trees: reduce malloc in cache-tree walk Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 5/7] unpack-trees: reuse (still valid) cache-tree from src_index Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 6/7] unpack-trees: add missing cache invalidation Nguyễn Thái Ngọc Duy
2018-08-18 14:41 ` [PATCH v5 7/7] cache-tree: verify valid cache-tree in the test suite Nguyễn Thái Ngọc Duy
2018-08-18 21:45 ` Elijah Newren
2018-08-18 22:01 ` [PATCH v5 0/7] Speed up unpack_trees() Elijah Newren
2018-08-19 5:09 ` Duy Nguyen
2018-08-25 12:18 ` [PATCH] Document update for nd/unpack-trees-with-cache-tree Nguyễn Thái Ngọc Duy
2018-08-25 12:31 ` Martin Ågren
2018-08-25 13:02 ` [PATCH v2] " Nguyễn Thái Ngọc Duy
2018-07-27 15:50 ` [PATCH v1 0/3] [RFC] Speeding up checkout (and merge, rebase, etc) Ben Peart
2018-07-26 16:35 ` Duy Nguyen
2018-07-24 5:54 ` Junio C Hamano
2018-07-24 15:13 ` Duy Nguyen
2018-07-24 21:21 ` Jeff King
2018-07-25 16:09 ` Ben Peart
2018-07-24 4:27 ` Jeff King
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=20180718204458.20936-4-benpeart@microsoft.com \
--to=ben.peart@microsoft.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.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.