Merge branch 'mt/parallel-checkout-part-1'

Preparatory API changes for parallel checkout.

* mt/parallel-checkout-part-1:
  entry: add checkout_entry_ca() taking preloaded conv_attrs
  entry: move conv_attrs lookup up to checkout_entry()
  entry: extract update_ce_after_write() from write_entry()
  entry: make fstat_output() and read_blob_entry() public
  entry: extract a header file for entry.c functions
  convert: add classification for conv_attrs struct
  convert: add get_stream_filter_ca() variant
  convert: add [async_]convert_to_working_tree_ca() variants
  convert: make convert_attrs() and convert structs public
This commit is contained in:
Junio C Hamano 2021-04-02 14:43:14 -07:00
Родитель a65ce7f831 ae22751f9b
Коммит c47679d040
11 изменённых файлов: 277 добавлений и 137 удалений

Просмотреть файл

@ -21,6 +21,7 @@
#include "quote.h"
#include "rerere.h"
#include "apply.h"
#include "entry.h"
struct gitdiff_data {
struct strbuf *root;

Просмотреть файл

@ -11,6 +11,7 @@
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
#include "entry.h"
#define CHECKOUT_ALL 4
static int nul_term_line;

Просмотреть файл

@ -26,6 +26,7 @@
#include "unpack-trees.h"
#include "wt-status.h"
#include "xdiff-interface.h"
#include "entry.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),

Просмотреть файл

@ -23,6 +23,7 @@
#include "lockfile.h"
#include "object-store.h"
#include "dir.h"
#include "entry.h"
static int trust_exit_code;

Просмотреть файл

@ -10,11 +10,13 @@
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
#include "entry.h"
#include "rerere.h"
#include "revision.h"
#include "log-tree.h"
#include "diffcore.h"
#include "exec-cmd.h"
#include "entry.h"
#define INCLUDE_ALL_FILES 2

24
cache.h
Просмотреть файл

@ -1621,30 +1621,6 @@ const char *show_ident_date(const struct ident_split *id,
*/
int ident_cmp(const struct ident_split *, const struct ident_split *);
struct checkout {
struct index_state *istate;
const char *base_dir;
int base_dir_len;
struct delayed_checkout *delayed_checkout;
struct checkout_metadata meta;
unsigned force:1,
quiet:1,
not_new:1,
clone:1,
refresh_cache:1;
};
#define CHECKOUT_INIT { NULL, "" }
#define TEMPORARY_FILENAME_LENGTH 25
int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
void enable_delayed_checkout(struct checkout *state);
int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
/*
* Unlink the last component and schedule the leading directories for
* removal, such that empty directories get removed.
*/
void unlink_entry(const struct cache_entry *ce);
struct cache_def {
struct strbuf path;
int flags;

143
convert.c
Просмотреть файл

@ -24,17 +24,6 @@
#define CONVERT_STAT_BITS_TXT_CRLF 0x2
#define CONVERT_STAT_BITS_BIN 0x4
enum crlf_action {
CRLF_UNDEFINED,
CRLF_BINARY,
CRLF_TEXT,
CRLF_TEXT_INPUT,
CRLF_TEXT_CRLF,
CRLF_AUTO,
CRLF_AUTO_INPUT,
CRLF_AUTO_CRLF
};
struct text_stat {
/* NUL, CR, LF and CRLF counts */
unsigned nul, lonecr, lonelf, crlf;
@ -172,7 +161,7 @@ static int text_eol_is_crlf(void)
return 0;
}
static enum eol output_eol(enum crlf_action crlf_action)
static enum eol output_eol(enum convert_crlf_action crlf_action)
{
switch (crlf_action) {
case CRLF_BINARY:
@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path)
}
static int will_convert_lf_to_crlf(struct text_stat *stats,
enum crlf_action crlf_action)
enum convert_crlf_action crlf_action)
{
if (output_eol(crlf_action) != EOL_CRLF)
return 0;
@ -499,7 +488,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len,
static int crlf_to_git(const struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *buf,
enum crlf_action crlf_action, int conv_flags)
enum convert_crlf_action crlf_action, int conv_flags)
{
struct text_stat stats;
char *dst;
@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate,
return 1;
}
static int crlf_to_worktree(const char *src, size_t len,
struct strbuf *buf, enum crlf_action crlf_action)
static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf,
enum convert_crlf_action crlf_action)
{
char *to_free = NULL;
struct text_stat stats;
@ -1247,7 +1236,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check)
return value;
}
static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check)
{
const char *value = check->value;
@ -1297,18 +1286,10 @@ static int git_path_check_ident(struct attr_check_item *check)
return !!ATTR_TRUE(value);
}
struct conv_attrs {
struct convert_driver *drv;
enum crlf_action attr_action; /* What attr says */
enum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */
int ident;
const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
};
static struct attr_check *check;
static void convert_attrs(const struct index_state *istate,
struct conv_attrs *ca, const char *path)
void convert_attrs(const struct index_state *istate,
struct conv_attrs *ca, const char *path)
{
struct attr_check_item *ccheck = NULL;
@ -1465,19 +1446,16 @@ void convert_to_git_filter_fd(const struct index_state *istate,
ident_to_git(dst->buf, dst->len, dst, ca.ident);
}
static int convert_to_working_tree_internal(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
int normalizing,
const struct checkout_metadata *meta,
struct delayed_checkout *dco)
static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,
const char *path, const char *src,
size_t len, struct strbuf *dst,
int normalizing,
const struct checkout_metadata *meta,
struct delayed_checkout *dco)
{
int ret = 0, ret_filter = 0;
struct conv_attrs ca;
convert_attrs(istate, &ca, path);
ret |= ident_to_worktree(src, len, dst, ca.ident);
ret |= ident_to_worktree(src, len, dst, ca->ident);
if (ret) {
src = dst->buf;
len = dst->len;
@ -1487,49 +1465,56 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
* is a smudge or process filter (even if the process filter doesn't
* support smudge). The filters might expect CRLFs.
*/
if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {
ret |= crlf_to_worktree(src, len, dst, ca.crlf_action);
if ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) {
ret |= crlf_to_worktree(src, len, dst, ca->crlf_action);
if (ret) {
src = dst->buf;
len = dst->len;
}
}
ret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);
ret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding);
if (ret) {
src = dst->buf;
len = dst->len;
}
ret_filter = apply_filter(
path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
if (!ret_filter && ca.drv && ca.drv->required)
die(_("%s: smudge filter %s failed"), path, ca.drv->name);
path, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco);
if (!ret_filter && ca->drv && ca->drv->required)
die(_("%s: smudge filter %s failed"), path, ca->drv->name);
return ret | ret_filter;
}
int async_convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta,
void *dco)
int async_convert_to_working_tree_ca(const struct conv_attrs *ca,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta,
void *dco)
{
return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,
meta, dco);
}
int convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta)
int convert_to_working_tree_ca(const struct conv_attrs *ca,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta)
{
return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0,
meta, NULL);
}
int renormalize_buffer(const struct index_state *istate, const char *path,
const char *src, size_t len, struct strbuf *dst)
{
int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
struct conv_attrs ca;
int ret;
convert_attrs(istate, &ca, path);
ret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1,
NULL, NULL);
if (ret) {
src = dst->buf;
len = dst->len;
@ -1956,34 +1941,25 @@ static struct stream_filter *ident_filter(const struct object_id *oid)
}
/*
* Return an appropriately constructed filter for the path, or NULL if
* Return an appropriately constructed filter for the given ca, or NULL if
* the contents cannot be filtered without reading the whole thing
* in-core.
*
* Note that you would be crazy to set CRLF, smudge/clean or ident to a
* large binary blob you would want us not to slurp into the memory!
*/
struct stream_filter *get_stream_filter(const struct index_state *istate,
const char *path,
const struct object_id *oid)
struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
const struct object_id *oid)
{
struct conv_attrs ca;
struct stream_filter *filter = NULL;
convert_attrs(istate, &ca, path);
if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))
if (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)
return NULL;
if (ca.working_tree_encoding)
return NULL;
if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)
return NULL;
if (ca.ident)
if (ca->ident)
filter = ident_filter(oid);
if (output_eol(ca.crlf_action) == EOL_CRLF)
if (output_eol(ca->crlf_action) == EOL_CRLF)
filter = cascade_filter(filter, lf_to_crlf_filter());
else
filter = cascade_filter(filter, &null_filter_singleton);
@ -1991,6 +1967,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate,
return filter;
}
struct stream_filter *get_stream_filter(const struct index_state *istate,
const char *path,
const struct object_id *oid)
{
struct conv_attrs ca;
convert_attrs(istate, &ca, path);
return get_stream_filter_ca(&ca, oid);
}
void free_stream_filter(struct stream_filter *filter)
{
filter->vtbl->free(filter);
@ -2024,3 +2009,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst,
if (blob)
oidcpy(&dst->blob, blob);
}
enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca)
{
if (ca->drv) {
if (ca->drv->process)
return CA_CLASS_INCORE_PROCESS;
if (ca->drv->smudge || ca->drv->clean)
return CA_CLASS_INCORE_FILTER;
}
if (ca->working_tree_encoding)
return CA_CLASS_INCORE;
if (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF)
return CA_CLASS_INCORE;
return CA_CLASS_STREAMABLE;
}

Просмотреть файл

@ -63,6 +63,30 @@ struct checkout_metadata {
struct object_id blob;
};
enum convert_crlf_action {
CRLF_UNDEFINED,
CRLF_BINARY,
CRLF_TEXT,
CRLF_TEXT_INPUT,
CRLF_TEXT_CRLF,
CRLF_AUTO,
CRLF_AUTO_INPUT,
CRLF_AUTO_CRLF
};
struct convert_driver;
struct conv_attrs {
struct convert_driver *drv;
enum convert_crlf_action attr_action; /* What attr says */
enum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */
int ident;
const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
};
void convert_attrs(const struct index_state *istate,
struct conv_attrs *ca, const char *path);
extern enum eol core_eol;
extern char *check_roundtrip_encoding;
const char *get_cached_convert_stats_ascii(const struct index_state *istate,
@ -75,15 +99,34 @@ const char *get_convert_attr_ascii(const struct index_state *istate,
int convert_to_git(const struct index_state *istate,
const char *path, const char *src, size_t len,
struct strbuf *dst, int conv_flags);
int convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta);
int async_convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta,
void *dco);
int convert_to_working_tree_ca(const struct conv_attrs *ca,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta);
int async_convert_to_working_tree_ca(const struct conv_attrs *ca,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta,
void *dco);
static inline int convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta)
{
struct conv_attrs ca;
convert_attrs(istate, &ca, path);
return convert_to_working_tree_ca(&ca, path, src, len, dst, meta);
}
static inline int async_convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
const struct checkout_metadata *meta,
void *dco)
{
struct conv_attrs ca;
convert_attrs(istate, &ca, path);
return async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco);
}
int async_query_available_blobs(const char *cmd,
struct string_list *available_paths);
int renormalize_buffer(const struct index_state *istate,
@ -136,6 +179,8 @@ struct stream_filter; /* opaque */
struct stream_filter *get_stream_filter(const struct index_state *istate,
const char *path,
const struct object_id *);
struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,
const struct object_id *oid);
void free_stream_filter(struct stream_filter *);
int is_null_stream_filter(struct stream_filter *);
@ -155,4 +200,37 @@ int stream_filter(struct stream_filter *,
const char *input, size_t *isize_p,
char *output, size_t *osize_p);
enum conv_attrs_classification {
/*
* The blob must be loaded into a buffer before it can be
* smudged. All smudging is done in-proc.
*/
CA_CLASS_INCORE,
/*
* The blob must be loaded into a buffer, but uses a
* single-file driver filter, such as rot13.
*/
CA_CLASS_INCORE_FILTER,
/*
* The blob must be loaded into a buffer, but uses a
* long-running driver process, such as LFS. This might or
* might not use delayed operations. (The important thing is
* that there is a single subordinate long-running process
* handling all associated blobs and in case of delayed
* operations, may hold per-blob state.)
*/
CA_CLASS_INCORE_PROCESS,
/*
* The blob can be streamed and smudged without needing to
* completely read it into a buffer.
*/
CA_CLASS_STREAMABLE,
};
enum conv_attrs_classification classify_conv_attrs(
const struct conv_attrs *ca);
#endif /* CONVERT_H */

85
entry.c
Просмотреть файл

@ -6,6 +6,7 @@
#include "submodule.h"
#include "progress.h"
#include "fsmonitor.h"
#include "entry.h"
static void create_directories(const char *path, int path_len,
const struct checkout *state)
@ -83,7 +84,7 @@ static int create_file(const char *path, unsigned int mode)
return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
}
static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)
void *read_blob_entry(const struct cache_entry *ce, unsigned long *size)
{
enum object_type type;
void *blob_data = read_object_file(&ce->oid, &type, size);
@ -108,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf
}
}
static int fstat_output(int fd, const struct checkout *state, struct stat *st)
int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st)
{
/* use fstat() only when path == ce->name */
if (fstat_is_reliable() &&
@ -131,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,
return -1;
result |= stream_blob_to_fd(fd, &ce->oid, filter, 1);
*fstat_done = fstat_output(fd, state, statbuf);
*fstat_done = fstat_checkout_output(fd, state, statbuf);
result |= close(fd);
if (result)
@ -250,8 +251,21 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
return errs;
}
static int write_entry(struct cache_entry *ce,
char *path, const struct checkout *state, int to_tempfile)
void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,
struct stat *st)
{
if (state->refresh_cache) {
assert(state->istate);
fill_stat_cache_info(state->istate, ce, st);
ce->ce_flags |= CE_UPDATE_IN_BASE;
mark_fsmonitor_invalid(state->istate, ce);
state->istate->cache_changed |= CE_ENTRY_CHANGED;
}
}
/* Note: ca is used (and required) iff the entry refers to a regular file. */
static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca,
const struct checkout *state, int to_tempfile)
{
unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
struct delayed_checkout *dco = state->delayed_checkout;
@ -268,8 +282,7 @@ static int write_entry(struct cache_entry *ce,
clone_checkout_metadata(&meta, &state->meta, &ce->oid);
if (ce_mode_s_ifmt == S_IFREG) {
struct stream_filter *filter = get_stream_filter(state->istate, ce->name,
&ce->oid);
struct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid);
if (filter &&
!streaming_write_entry(ce, path, filter,
state, to_tempfile,
@ -316,14 +329,17 @@ static int write_entry(struct cache_entry *ce,
* Convert from git internal format to working tree format
*/
if (dco && dco->state != CE_NO_DELAY) {
ret = async_convert_to_working_tree(state->istate, ce->name, new_blob,
size, &buf, &meta, dco);
ret = async_convert_to_working_tree_ca(ca, ce->name,
new_blob, size,
&buf, &meta, dco);
if (ret && string_list_has_string(&dco->paths, ce->name)) {
free(new_blob);
goto delayed;
}
} else
ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);
} else {
ret = convert_to_working_tree_ca(ca, ce->name, new_blob,
size, &buf, &meta);
}
if (ret) {
free(new_blob);
@ -345,7 +361,7 @@ static int write_entry(struct cache_entry *ce,
wrote = write_in_full(fd, new_blob, size);
if (!to_tempfile)
fstat_done = fstat_output(fd, state, &st);
fstat_done = fstat_checkout_output(fd, state, &st);
close(fd);
free(new_blob);
if (wrote < 0)
@ -370,15 +386,10 @@ static int write_entry(struct cache_entry *ce,
finish:
if (state->refresh_cache) {
assert(state->istate);
if (!fstat_done)
if (lstat(ce->name, &st) < 0)
return error_errno("unable to stat just-written file %s",
ce->name);
fill_stat_cache_info(state->istate, ce, &st);
ce->ce_flags |= CE_UPDATE_IN_BASE;
mark_fsmonitor_invalid(state->istate, ce);
state->istate->cache_changed |= CE_ENTRY_CHANGED;
if (!fstat_done && lstat(ce->name, &st) < 0)
return error_errno("unable to stat just-written file %s",
ce->name);
update_ce_after_write(state, ce , &st);
}
delayed:
return 0;
@ -429,19 +440,13 @@ static void mark_colliding_entries(const struct checkout *state,
}
}
/*
* Write the contents from ce out to the working tree.
*
* When topath[] is not NULL, instead of writing to the working tree
* file named by ce, a temporary file is created by this function and
* its name is returned in topath[], which must be able to hold at
* least TEMPORARY_FILENAME_LENGTH bytes long.
*/
int checkout_entry(struct cache_entry *ce, const struct checkout *state,
char *topath, int *nr_checkouts)
int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
const struct checkout *state, char *topath,
int *nr_checkouts)
{
static struct strbuf path = STRBUF_INIT;
struct stat st;
struct conv_attrs ca_buf;
if (ce->ce_flags & CE_WT_REMOVE) {
if (topath)
@ -454,8 +459,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
return 0;
}
if (topath)
return write_entry(ce, topath, state, 1);
if (topath) {
if (S_ISREG(ce->ce_mode) && !ca) {
convert_attrs(state->istate, &ca_buf, ce->name);
ca = &ca_buf;
}
return write_entry(ce, topath, ca, state, 1);
}
strbuf_reset(&path);
strbuf_add(&path, state->base_dir, state->base_dir_len);
@ -517,9 +527,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state,
return 0;
create_directories(path.buf, path.len, state);
if (nr_checkouts)
(*nr_checkouts)++;
return write_entry(ce, path.buf, state, 0);
if (S_ISREG(ce->ce_mode) && !ca) {
convert_attrs(state->istate, &ca_buf, ce->name);
ca = &ca_buf;
}
return write_entry(ce, path.buf, ca, state, 0);
}
void unlink_entry(const struct cache_entry *ce)

59
entry.h Normal file
Просмотреть файл

@ -0,0 +1,59 @@
#ifndef ENTRY_H
#define ENTRY_H
#include "cache.h"
#include "convert.h"
struct checkout {
struct index_state *istate;
const char *base_dir;
int base_dir_len;
struct delayed_checkout *delayed_checkout;
struct checkout_metadata meta;
unsigned force:1,
quiet:1,
not_new:1,
clone:1,
refresh_cache:1;
};
#define CHECKOUT_INIT { NULL, "" }
#define TEMPORARY_FILENAME_LENGTH 25
/*
* Write the contents from ce out to the working tree.
*
* When topath[] is not NULL, instead of writing to the working tree
* file named by ce, a temporary file is created by this function and
* its name is returned in topath[], which must be able to hold at
* least TEMPORARY_FILENAME_LENGTH bytes long.
*
* With checkout_entry_ca(), callers can optionally pass a preloaded
* conv_attrs struct (to avoid reloading it), when ce refers to a
* regular file. If ca is NULL, the attributes will be loaded
* internally when (and if) needed.
*/
int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
const struct checkout *state, char *topath,
int *nr_checkouts);
static inline int checkout_entry(struct cache_entry *ce,
const struct checkout *state, char *topath,
int *nr_checkouts)
{
return checkout_entry_ca(ce, NULL, state, topath, nr_checkouts);
}
void enable_delayed_checkout(struct checkout *state);
int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
/*
* Unlink the last component and schedule the leading directories for
* removal, such that empty directories get removed.
*/
void unlink_entry(const struct cache_entry *ce);
void *read_blob_entry(const struct cache_entry *ce, unsigned long *size);
int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st);
void update_ce_after_write(const struct checkout *state, struct cache_entry *ce,
struct stat *st);
#endif /* ENTRY_H */

Просмотреть файл

@ -16,6 +16,7 @@
#include "fsmonitor.h"
#include "object-store.h"
#include "promisor-remote.h"
#include "entry.h"
/*
* Error messages expected by scripts out of plumbing commands such as