#include "cache.h"
+#include "config.h"
+#include "object-store.h"
#include "attr.h"
#include "run-command.h"
#include "quote.h"
#include "sigchain.h"
#include "pkt-line.h"
+#include "sub-process.h"
+#include "utf8.h"
/*
* convert.c - convert a file when checking it out and checking it in.
* The same heuristics as diff.c::mmfile_is_binary()
* We treat files with bare CR as binary
*/
-static int convert_is_binary(unsigned long size, const struct text_stat *stats)
+static int convert_is_binary(const struct text_stat *stats)
{
if (stats->lonecr)
return 1;
if (!data || !size)
return 0;
gather_stats(data, size, &stats);
- if (convert_is_binary(size, &stats))
+ if (convert_is_binary(&stats))
ret |= CONVERT_STAT_BITS_BIN;
if (stats.crlf)
ret |= CONVERT_STAT_BITS_TXT_CRLF;
}
}
-const char *get_cached_convert_stats_ascii(const char *path)
+const char *get_cached_convert_stats_ascii(const struct index_state *istate,
+ const char *path)
{
const char *ret;
unsigned long sz;
- void *data = read_blob_data_from_cache(path, &sz);
+ void *data = read_blob_data_from_index(istate, path, &sz);
ret = gather_convert_stats_ascii(data, sz);
free(data);
return ret;
/* fall through */
return text_eol_is_crlf() ? EOL_CRLF : EOL_LF;
}
- warning("Illegal crlf_action %d\n", (int)crlf_action);
+ warning(_("illegal crlf_action %d"), (int)crlf_action);
return core_eol;
}
-static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
+static void check_global_conv_flags_eol(const char *path, enum crlf_action crlf_action,
struct text_stat *old_stats, struct text_stat *new_stats,
- enum safe_crlf checksafe)
+ int conv_flags)
{
if (old_stats->crlf && !new_stats->crlf ) {
/*
* CRLFs would not be restored by checkout
*/
- if (checksafe == SAFE_CRLF_WARN)
+ if (conv_flags & CONV_EOL_RNDTRP_DIE)
+ die(_("CRLF would be replaced by LF in %s"), path);
+ else if (conv_flags & CONV_EOL_RNDTRP_WARN)
warning(_("CRLF will be replaced by LF in %s.\n"
"The file will have its original line"
- " endings in your working directory."), path);
- else /* i.e. SAFE_CRLF_FAIL */
- die(_("CRLF would be replaced by LF in %s."), path);
+ " endings in your working directory"), path);
} else if (old_stats->lonelf && !new_stats->lonelf ) {
/*
* CRLFs would be added by checkout
*/
- if (checksafe == SAFE_CRLF_WARN)
+ if (conv_flags & CONV_EOL_RNDTRP_DIE)
+ die(_("LF would be replaced by CRLF in %s"), path);
+ else if (conv_flags & CONV_EOL_RNDTRP_WARN)
warning(_("LF will be replaced by CRLF in %s.\n"
"The file will have its original line"
- " endings in your working directory."), path);
- else /* i.e. SAFE_CRLF_FAIL */
- die(_("LF would be replaced by CRLF in %s"), path);
+ " endings in your working directory"), path);
}
}
-static int has_cr_in_index(const char *path)
+static int has_crlf_in_index(const struct index_state *istate, const char *path)
{
unsigned long sz;
void *data;
- int has_cr;
+ const char *crp;
+ int has_crlf = 0;
- data = read_blob_data_from_cache(path, &sz);
+ data = read_blob_data_from_index(istate, path, &sz);
if (!data)
return 0;
- has_cr = memchr(data, '\r', sz) != NULL;
+
+ crp = memchr(data, '\r', sz);
+ if (crp) {
+ unsigned int ret_stats;
+ ret_stats = gather_convert_stats(data, sz);
+ if (!(ret_stats & CONVERT_STAT_BITS_BIN) &&
+ (ret_stats & CONVERT_STAT_BITS_TXT_CRLF))
+ has_crlf = 1;
+ }
free(data);
- return has_cr;
+ return has_crlf;
}
-static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats,
+static int will_convert_lf_to_crlf(struct text_stat *stats,
enum crlf_action crlf_action)
{
if (output_eol(crlf_action) != EOL_CRLF)
if (stats->lonecr || stats->crlf)
return 0;
- if (convert_is_binary(len, stats))
+ if (convert_is_binary(stats))
return 0;
}
return 1;
}
-static int crlf_to_git(const char *path, const char *src, size_t len,
+static int validate_encoding(const char *path, const char *enc,
+ const char *data, size_t len, int die_on_error)
+{
+ /* We only check for UTF here as UTF?? can be an alias for UTF-?? */
+ if (istarts_with(enc, "UTF")) {
+ /*
+ * Check for detectable errors in UTF encodings
+ */
+ if (has_prohibited_utf_bom(enc, data, len)) {
+ const char *error_msg = _(
+ "BOM is prohibited in '%s' if encoded as %s");
+ /*
+ * This advice is shown for UTF-??BE and UTF-??LE encodings.
+ * We cut off the last two characters of the encoding name
+ * to generate the encoding name suitable for BOMs.
+ */
+ const char *advise_msg = _(
+ "The file '%s' contains a byte order "
+ "mark (BOM). Please use UTF-%s as "
+ "working-tree-encoding.");
+ const char *stripped = NULL;
+ char *upper = xstrdup_toupper(enc);
+ upper[strlen(upper)-2] = '\0';
+ if (!skip_prefix(upper, "UTF-", &stripped))
+ skip_prefix(stripped, "UTF", &stripped);
+ advise(advise_msg, path, stripped);
+ free(upper);
+ if (die_on_error)
+ die(error_msg, path, enc);
+ else {
+ return error(error_msg, path, enc);
+ }
+
+ } else if (is_missing_required_utf_bom(enc, data, len)) {
+ const char *error_msg = _(
+ "BOM is required in '%s' if encoded as %s");
+ const char *advise_msg = _(
+ "The file '%s' is missing a byte order "
+ "mark (BOM). Please use UTF-%sBE or UTF-%sLE "
+ "(depending on the byte order) as "
+ "working-tree-encoding.");
+ const char *stripped = NULL;
+ char *upper = xstrdup_toupper(enc);
+ if (!skip_prefix(upper, "UTF-", &stripped))
+ skip_prefix(stripped, "UTF", &stripped);
+ advise(advise_msg, path, stripped, stripped);
+ free(upper);
+ if (die_on_error)
+ die(error_msg, path, enc);
+ else {
+ return error(error_msg, path, enc);
+ }
+ }
+
+ }
+ return 0;
+}
+
+static void trace_encoding(const char *context, const char *path,
+ const char *encoding, const char *buf, size_t len)
+{
+ static struct trace_key coe = TRACE_KEY_INIT(WORKING_TREE_ENCODING);
+ struct strbuf trace = STRBUF_INIT;
+ int i;
+
+ strbuf_addf(&trace, "%s (%s, considered %s):\n", context, path, encoding);
+ for (i = 0; i < len && buf; ++i) {
+ strbuf_addf(
+ &trace, "| \033[2m%2i:\033[0m %2x \033[2m%c\033[0m%c",
+ i,
+ (unsigned char) buf[i],
+ (buf[i] > 32 && buf[i] < 127 ? buf[i] : ' '),
+ ((i+1) % 8 && (i+1) < len ? ' ' : '\n')
+ );
+ }
+ strbuf_addchars(&trace, '\n', 1);
+
+ trace_strbuf(&coe, &trace);
+ strbuf_release(&trace);
+}
+
+static int check_roundtrip(const char *enc_name)
+{
+ /*
+ * check_roundtrip_encoding contains a string of comma and/or
+ * space separated encodings (eg. "UTF-16, ASCII, CP1125").
+ * Search for the given encoding in that string.
+ */
+ const char *found = strcasestr(check_roundtrip_encoding, enc_name);
+ const char *next;
+ int len;
+ if (!found)
+ return 0;
+ next = found + strlen(enc_name);
+ len = strlen(check_roundtrip_encoding);
+ return (found && (
+ /*
+ * check that the found encoding is at the
+ * beginning of check_roundtrip_encoding or
+ * that it is prefixed with a space or comma
+ */
+ found == check_roundtrip_encoding || (
+ (isspace(found[-1]) || found[-1] == ',')
+ )
+ ) && (
+ /*
+ * check that the found encoding is at the
+ * end of check_roundtrip_encoding or
+ * that it is suffixed with a space or comma
+ */
+ next == check_roundtrip_encoding + len || (
+ next < check_roundtrip_encoding + len &&
+ (isspace(next[0]) || next[0] == ',')
+ )
+ ));
+}
+
+static const char *default_encoding = "UTF-8";
+
+static int encode_to_git(const char *path, const char *src, size_t src_len,
+ struct strbuf *buf, const char *enc, int conv_flags)
+{
+ char *dst;
+ size_t dst_len;
+ int die_on_error = conv_flags & CONV_WRITE_OBJECT;
+
+ /*
+ * No encoding is specified or there is nothing to encode.
+ * Tell the caller that the content was not modified.
+ */
+ if (!enc || (src && !src_len))
+ return 0;
+
+ /*
+ * Looks like we got called from "would_convert_to_git()".
+ * This means Git wants to know if it would encode (= modify!)
+ * the content. Let's answer with "yes", since an encoding was
+ * specified.
+ */
+ if (!buf && !src)
+ return 1;
+
+ if (validate_encoding(path, enc, src, src_len, die_on_error))
+ return 0;
+
+ trace_encoding("source", path, enc, src, src_len);
+ dst = reencode_string_len(src, src_len, default_encoding, enc,
+ &dst_len);
+ if (!dst) {
+ /*
+ * We could add the blob "as-is" to Git. However, on checkout
+ * we would try to reencode to the original encoding. This
+ * would fail and we would leave the user with a messed-up
+ * working tree. Let's try to avoid this by screaming loud.
+ */
+ const char* msg = _("failed to encode '%s' from %s to %s");
+ if (die_on_error)
+ die(msg, path, enc, default_encoding);
+ else {
+ error(msg, path, enc, default_encoding);
+ return 0;
+ }
+ }
+ trace_encoding("destination", path, default_encoding, dst, dst_len);
+
+ /*
+ * UTF supports lossless conversion round tripping [1] and conversions
+ * between UTF and other encodings are mostly round trip safe as
+ * Unicode aims to be a superset of all other character encodings.
+ * However, certain encodings (e.g. SHIFT-JIS) are known to have round
+ * trip issues [2]. Check the round trip conversion for all encodings
+ * listed in core.checkRoundtripEncoding.
+ *
+ * The round trip check is only performed if content is written to Git.
+ * This ensures that no information is lost during conversion to/from
+ * the internal UTF-8 representation.
+ *
+ * Please note, the code below is not tested because I was not able to
+ * generate a faulty round trip without an iconv error. Iconv errors
+ * are already caught above.
+ *
+ * [1] http://unicode.org/faq/utf_bom.html#gen2
+ * [2] https://support.microsoft.com/en-us/help/170559/prb-conversion-problem-between-shift-jis-and-unicode
+ */
+ if (die_on_error && check_roundtrip(enc)) {
+ char *re_src;
+ size_t re_src_len;
+
+ re_src = reencode_string_len(dst, dst_len,
+ enc, default_encoding,
+ &re_src_len);
+
+ trace_printf("Checking roundtrip encoding for %s...\n", enc);
+ trace_encoding("reencoded source", path, enc,
+ re_src, re_src_len);
+
+ if (!re_src || src_len != re_src_len ||
+ memcmp(src, re_src, src_len)) {
+ const char* msg = _("encoding '%s' from %s to %s and "
+ "back is not the same");
+ die(msg, path, enc, default_encoding);
+ }
+
+ free(re_src);
+ }
+
+ strbuf_attach(buf, dst, dst_len, dst_len + 1);
+ return 1;
+}
+
+static int encode_to_worktree(const char *path, const char *src, size_t src_len,
+ struct strbuf *buf, const char *enc)
+{
+ char *dst;
+ size_t dst_len;
+
+ /*
+ * No encoding is specified or there is nothing to encode.
+ * Tell the caller that the content was not modified.
+ */
+ if (!enc || (src && !src_len))
+ return 0;
+
+ dst = reencode_string_len(src, src_len, enc, default_encoding,
+ &dst_len);
+ if (!dst) {
+ error(_("failed to encode '%s' from %s to %s"),
+ path, default_encoding, enc);
+ return 0;
+ }
+
+ strbuf_attach(buf, dst, dst_len, dst_len + 1);
+ return 1;
+}
+
+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, enum safe_crlf checksafe)
+ enum crlf_action crlf_action, int conv_flags)
{
struct text_stat stats;
char *dst;
convert_crlf_into_lf = !!stats.crlf;
if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
- if (convert_is_binary(len, &stats))
+ if (convert_is_binary(&stats))
return 0;
/*
* If the file in the index has any CR in it, do not
* unless we want to renormalize in a merge or
* cherry-pick.
*/
- if ((checksafe != SAFE_CRLF_RENORMALIZE) && has_cr_in_index(path))
+ if ((!(conv_flags & CONV_EOL_RENORMALIZE)) &&
+ has_crlf_in_index(istate, path))
convert_crlf_into_lf = 0;
}
- if ((checksafe == SAFE_CRLF_WARN ||
- (checksafe == SAFE_CRLF_FAIL)) && len) {
+ if (((conv_flags & CONV_EOL_RNDTRP_WARN) ||
+ ((conv_flags & CONV_EOL_RNDTRP_DIE) && len))) {
struct text_stat new_stats;
memcpy(&new_stats, &stats, sizeof(new_stats));
/* simulate "git add" */
new_stats.crlf = 0;
}
/* simulate "git checkout" */
- if (will_convert_lf_to_crlf(len, &new_stats, crlf_action)) {
+ if (will_convert_lf_to_crlf(&new_stats, crlf_action)) {
new_stats.crlf += new_stats.lonelf;
new_stats.lonelf = 0;
}
- check_safe_crlf(path, crlf_action, &stats, &new_stats, checksafe);
+ check_global_conv_flags_eol(path, crlf_action, &stats, &new_stats, conv_flags);
}
if (!convert_crlf_into_lf)
return 0;
return 1;
}
-static int crlf_to_worktree(const char *path, const char *src, size_t len,
+static int crlf_to_worktree(const char *src, size_t len,
struct strbuf *buf, enum crlf_action crlf_action)
{
char *to_free = NULL;
return 0;
gather_stats(src, len, &stats);
- if (!will_convert_lf_to_crlf(len, &stats, crlf_action))
+ if (!will_convert_lf_to_crlf(&stats, crlf_action))
return 0;
/* are we "faking" in place editing ? */
child_process.in = -1;
child_process.out = out;
- if (start_command(&child_process))
- return error("cannot fork to run external filter '%s'", params->cmd);
+ if (start_command(&child_process)) {
+ strbuf_release(&cmd);
+ return error(_("cannot fork to run external filter '%s'"),
+ params->cmd);
+ }
sigchain_push(SIGPIPE, SIG_IGN);
if (close(child_process.in))
write_err = 1;
if (write_err)
- error("cannot feed the input to external filter '%s'", params->cmd);
+ error(_("cannot feed the input to external filter '%s'"),
+ params->cmd);
sigchain_pop(SIGPIPE);
status = finish_command(&child_process);
if (status)
- error("external filter '%s' failed %d", params->cmd, status);
+ error(_("external filter '%s' failed %d"), params->cmd, status);
strbuf_release(&cmd);
return (write_err || status);
}
static int apply_single_file_filter(const char *path, const char *src, size_t len, int fd,
- struct strbuf *dst, const char *cmd)
+ struct strbuf *dst, const char *cmd)
{
/*
* Create a pipeline to have the command filter the buffer's
if (start_async(&async))
return 0; /* error was already reported */
- if (strbuf_read(&nbuf, async.out, len) < 0) {
- err = error("read from external filter '%s' failed", cmd);
+ if (strbuf_read(&nbuf, async.out, 0) < 0) {
+ err = error(_("read from external filter '%s' failed"), cmd);
}
if (close(async.out)) {
- err = error("read from external filter '%s' failed", cmd);
+ err = error(_("read from external filter '%s' failed"), cmd);
}
if (finish_async(&async)) {
- err = error("external filter '%s' failed", cmd);
+ err = error(_("external filter '%s' failed"), cmd);
}
if (!err) {
#define CAP_CLEAN (1u<<0)
#define CAP_SMUDGE (1u<<1)
+#define CAP_DELAY (1u<<2)
struct cmd2process {
- struct hashmap_entry ent; /* must be the first member! */
+ struct subprocess_entry subprocess; /* must be the first member! */
unsigned int supported_capabilities;
- const char *cmd;
- struct child_process process;
};
-static int cmd_process_map_initialized;
-static struct hashmap cmd_process_map;
-
-static int cmd2process_cmp(const struct cmd2process *e1,
- const struct cmd2process *e2,
- const void *unused)
-{
- return strcmp(e1->cmd, e2->cmd);
-}
+static int subprocess_map_initialized;
+static struct hashmap subprocess_map;
-static struct cmd2process *find_multi_file_filter_entry(struct hashmap *hashmap, const char *cmd)
+static int start_multi_file_filter_fn(struct subprocess_entry *subprocess)
{
- struct cmd2process key;
- hashmap_entry_init(&key, strhash(cmd));
- key.cmd = cmd;
- return hashmap_get(hashmap, &key, NULL);
-}
-
-static int packet_write_list(int fd, const char *line, ...)
-{
- va_list args;
- int err;
- va_start(args, line);
- for (;;) {
- if (!line)
- break;
- if (strlen(line) > LARGE_PACKET_DATA_MAX)
- return -1;
- err = packet_write_fmt_gently(fd, "%s\n", line);
- if (err)
- return err;
- line = va_arg(args, const char*);
- }
- va_end(args);
- return packet_flush_gently(fd);
-}
-
-static void read_multi_file_filter_status(int fd, struct strbuf *status)
-{
- struct strbuf **pair;
- char *line;
- for (;;) {
- line = packet_read_line(fd, NULL);
- if (!line)
- break;
- pair = strbuf_split_str(line, '=', 2);
- if (pair[0] && pair[0]->len && pair[1]) {
- /* the last "status=<foo>" line wins */
- if (!strcmp(pair[0]->buf, "status=")) {
- strbuf_reset(status);
- strbuf_addbuf(status, pair[1]);
- }
- }
- strbuf_list_free(pair);
- }
-}
-
-static void kill_multi_file_filter(struct hashmap *hashmap, struct cmd2process *entry)
-{
- if (!entry)
- return;
-
- entry->process.clean_on_exit = 0;
- kill(entry->process.pid, SIGTERM);
- finish_command(&entry->process);
-
- hashmap_remove(hashmap, entry, NULL);
- free(entry);
-}
-
-static void stop_multi_file_filter(struct child_process *process)
-{
- sigchain_push(SIGPIPE, SIG_IGN);
- /* Closing the pipe signals the filter to initiate a shutdown. */
- close(process->in);
- close(process->out);
- sigchain_pop(SIGPIPE);
- /* Finish command will wait until the shutdown is complete. */
- finish_command(process);
+ static int versions[] = {2, 0};
+ static struct subprocess_capability capabilities[] = {
+ { "clean", CAP_CLEAN },
+ { "smudge", CAP_SMUDGE },
+ { "delay", CAP_DELAY },
+ { NULL, 0 }
+ };
+ struct cmd2process *entry = (struct cmd2process *)subprocess;
+ return subprocess_handshake(subprocess, "git-filter", versions, NULL,
+ capabilities,
+ &entry->supported_capabilities);
}
-static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, const char *cmd)
+static void handle_filter_error(const struct strbuf *filter_status,
+ struct cmd2process *entry,
+ const unsigned int wanted_capability)
{
- int err;
- struct cmd2process *entry;
- struct child_process *process;
- const char *argv[] = { cmd, NULL };
- struct string_list cap_list = STRING_LIST_INIT_NODUP;
- char *cap_buf;
- const char *cap_name;
-
- entry = xmalloc(sizeof(*entry));
- entry->cmd = cmd;
- entry->supported_capabilities = 0;
- process = &entry->process;
-
- child_process_init(process);
- process->argv = argv;
- process->use_shell = 1;
- process->in = -1;
- process->out = -1;
- process->clean_on_exit = 1;
- process->clean_on_exit_handler = stop_multi_file_filter;
-
- if (start_command(process)) {
- error("cannot fork to run external filter '%s'", cmd);
- return NULL;
- }
-
- hashmap_entry_init(entry, strhash(cmd));
-
- sigchain_push(SIGPIPE, SIG_IGN);
-
- err = packet_write_list(process->in, "git-filter-client", "version=2", NULL);
- if (err)
- goto done;
-
- err = strcmp(packet_read_line(process->out, NULL), "git-filter-server");
- if (err) {
- error("external filter '%s' does not support filter protocol version 2", cmd);
- goto done;
- }
- err = strcmp(packet_read_line(process->out, NULL), "version=2");
- if (err)
- goto done;
- err = packet_read_line(process->out, NULL) != NULL;
- if (err)
- goto done;
-
- err = packet_write_list(process->in, "capability=clean", "capability=smudge", NULL);
-
- for (;;) {
- cap_buf = packet_read_line(process->out, NULL);
- if (!cap_buf)
- break;
- string_list_split_in_place(&cap_list, cap_buf, '=', 1);
-
- if (cap_list.nr != 2 || strcmp(cap_list.items[0].string, "capability"))
- continue;
-
- cap_name = cap_list.items[1].string;
- if (!strcmp(cap_name, "clean")) {
- entry->supported_capabilities |= CAP_CLEAN;
- } else if (!strcmp(cap_name, "smudge")) {
- entry->supported_capabilities |= CAP_SMUDGE;
- } else {
- warning(
- "external filter '%s' requested unsupported filter capability '%s'",
- cmd, cap_name
- );
- }
-
- string_list_clear(&cap_list, 0);
- }
-
-done:
- sigchain_pop(SIGPIPE);
-
- if (err || errno == EPIPE) {
- error("initialization for external filter '%s' failed", cmd);
- kill_multi_file_filter(hashmap, entry);
- return NULL;
+ if (!strcmp(filter_status->buf, "error"))
+ ; /* The filter signaled a problem with the file. */
+ else if (!strcmp(filter_status->buf, "abort") && wanted_capability) {
+ /*
+ * The filter signaled a permanent problem. Don't try to filter
+ * files with the same command for the lifetime of the current
+ * Git process.
+ */
+ entry->supported_capabilities &= ~wanted_capability;
+ } else {
+ /*
+ * Something went wrong with the protocol filter.
+ * Force shutdown and restart if another blob requires filtering.
+ */
+ error(_("external filter '%s' failed"), entry->subprocess.cmd);
+ subprocess_stop(&subprocess_map, &entry->subprocess);
+ free(entry);
}
-
- hashmap_add(hashmap, entry);
- return entry;
}
static int apply_multi_file_filter(const char *path, const char *src, size_t len,
int fd, struct strbuf *dst, const char *cmd,
- const unsigned int wanted_capability)
+ const unsigned int wanted_capability,
+ struct delayed_checkout *dco)
{
int err;
+ int can_delay = 0;
struct cmd2process *entry;
struct child_process *process;
struct strbuf nbuf = STRBUF_INIT;
struct strbuf filter_status = STRBUF_INIT;
const char *filter_type;
- if (!cmd_process_map_initialized) {
- cmd_process_map_initialized = 1;
- hashmap_init(&cmd_process_map, (hashmap_cmp_fn) cmd2process_cmp, 0);
+ if (!subprocess_map_initialized) {
+ subprocess_map_initialized = 1;
+ hashmap_init(&subprocess_map, cmd2process_cmp, NULL, 0);
entry = NULL;
} else {
- entry = find_multi_file_filter_entry(&cmd_process_map, cmd);
+ entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
}
fflush(NULL);
if (!entry) {
- entry = start_multi_file_filter(&cmd_process_map, cmd);
- if (!entry)
+ entry = xmalloc(sizeof(*entry));
+ entry->supported_capabilities = 0;
+
+ if (subprocess_start(&subprocess_map, &entry->subprocess, cmd, start_multi_file_filter_fn)) {
+ free(entry);
return 0;
+ }
}
- process = &entry->process;
+ process = &entry->subprocess.process;
- if (!(wanted_capability & entry->supported_capabilities))
+ if (!(entry->supported_capabilities & wanted_capability))
return 0;
- if (CAP_CLEAN & wanted_capability)
+ if (wanted_capability & CAP_CLEAN)
filter_type = "clean";
- else if (CAP_SMUDGE & wanted_capability)
+ else if (wanted_capability & CAP_SMUDGE)
filter_type = "smudge";
else
- die("unexpected filter type");
+ die(_("unexpected filter type"));
sigchain_push(SIGPIPE, SIG_IGN);
err = strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n");
if (err) {
- error("path name too long for external filter");
+ error(_("path name too long for external filter"));
goto done;
}
if (err)
goto done;
+ if ((entry->supported_capabilities & CAP_DELAY) &&
+ dco && dco->state == CE_CAN_DELAY) {
+ can_delay = 1;
+ err = packet_write_fmt_gently(process->in, "can-delay=1\n");
+ if (err)
+ goto done;
+ }
+
err = packet_flush_gently(process->in);
if (err)
goto done;
if (err)
goto done;
- read_multi_file_filter_status(process->out, &filter_status);
- err = strcmp(filter_status.buf, "success");
+ err = subprocess_read_status(process->out, &filter_status);
+ if (err)
+ goto done;
+
+ if (can_delay && !strcmp(filter_status.buf, "delayed")) {
+ string_list_insert(&dco->filters, cmd);
+ string_list_insert(&dco->paths, path);
+ } else {
+ /* The filter got the blob and wants to send us a response. */
+ err = strcmp(filter_status.buf, "success");
+ if (err)
+ goto done;
+
+ err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
+ if (err)
+ goto done;
+
+ err = subprocess_read_status(process->out, &filter_status);
+ if (err)
+ goto done;
+
+ err = strcmp(filter_status.buf, "success");
+ }
+
+done:
+ sigchain_pop(SIGPIPE);
+
+ if (err)
+ handle_filter_error(&filter_status, entry, wanted_capability);
+ else
+ strbuf_swap(dst, &nbuf);
+ strbuf_release(&nbuf);
+ return !err;
+}
+
+
+int async_query_available_blobs(const char *cmd, struct string_list *available_paths)
+{
+ int err;
+ char *line;
+ struct cmd2process *entry;
+ struct child_process *process;
+ struct strbuf filter_status = STRBUF_INIT;
+
+ assert(subprocess_map_initialized);
+ entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
+ if (!entry) {
+ error(_("external filter '%s' is not available anymore although "
+ "not all paths have been filtered"), cmd);
+ return 0;
+ }
+ process = &entry->subprocess.process;
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ err = packet_write_fmt_gently(
+ process->in, "command=list_available_blobs\n");
if (err)
goto done;
- err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
+ err = packet_flush_gently(process->in);
+ if (err)
+ goto done;
+
+ while ((line = packet_read_line(process->out, NULL))) {
+ const char *path;
+ if (skip_prefix(line, "pathname=", &path))
+ string_list_insert(available_paths, xstrdup(path));
+ else
+ ; /* ignore unknown keys */
+ }
+
+ err = subprocess_read_status(process->out, &filter_status);
if (err)
goto done;
- read_multi_file_filter_status(process->out, &filter_status);
err = strcmp(filter_status.buf, "success");
done:
sigchain_pop(SIGPIPE);
- if (err || errno == EPIPE) {
- if (!strcmp(filter_status.buf, "error")) {
- /* The filter signaled a problem with the file. */
- } else if (!strcmp(filter_status.buf, "abort")) {
- /*
- * The filter signaled a permanent problem. Don't try to filter
- * files with the same command for the lifetime of the current
- * Git process.
- */
- entry->supported_capabilities &= ~wanted_capability;
- } else {
- /*
- * Something went wrong with the protocol filter.
- * Force shutdown and restart if another blob requires filtering.
- */
- error("external filter '%s' failed", cmd);
- kill_multi_file_filter(&cmd_process_map, entry);
- }
- } else {
- strbuf_swap(dst, &nbuf);
- }
- strbuf_release(&nbuf);
+ if (err)
+ handle_filter_error(&filter_status, entry, 0);
return !err;
}
static int apply_filter(const char *path, const char *src, size_t len,
int fd, struct strbuf *dst, struct convert_driver *drv,
- const unsigned int wanted_capability)
+ const unsigned int wanted_capability,
+ struct delayed_checkout *dco)
{
const char *cmd = NULL;
if (!dst)
return 1;
- if ((CAP_CLEAN & wanted_capability) && !drv->process && drv->clean)
+ if ((wanted_capability & CAP_CLEAN) && !drv->process && drv->clean)
cmd = drv->clean;
- else if ((CAP_SMUDGE & wanted_capability) && !drv->process && drv->smudge)
+ else if ((wanted_capability & CAP_SMUDGE) && !drv->process && drv->smudge)
cmd = drv->smudge;
if (cmd && *cmd)
return apply_single_file_filter(path, src, len, fd, dst, cmd);
else if (drv->process && *drv->process)
- return apply_multi_file_filter(path, src, len, fd, dst, drv->process, wanted_capability);
+ return apply_multi_file_filter(path, src, len, fd, dst,
+ drv->process, wanted_capability, dco);
return 0;
}
return cnt;
}
-static int ident_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+static int ident_to_git(const char *src, size_t len,
+ struct strbuf *buf, int ident)
{
char *dst, *dollar;
return 1;
}
-static int ident_to_worktree(const char *path, const char *src, size_t len,
- struct strbuf *buf, int ident)
+static int ident_to_worktree(const char *src, size_t len,
+ struct strbuf *buf, int ident)
{
- unsigned char sha1[20];
+ struct object_id oid;
char *to_free = NULL, *dollar, *spc;
int cnt;
/* are we "faking" in place editing ? */
if (src == buf->buf)
to_free = strbuf_detach(buf, NULL);
- hash_sha1_file(src, len, "blob", sha1);
+ hash_object_file(src, len, "blob", &oid);
- strbuf_grow(buf, len + cnt * 43);
+ strbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));
for (;;) {
/* step 1: run to the next '$' */
dollar = memchr(src, '$', len);
/* step 4: substitute */
strbuf_addstr(buf, "Id: ");
- strbuf_add(buf, sha1_to_hex(sha1), 40);
+ strbuf_addstr(buf, oid_to_hex(&oid));
strbuf_addstr(buf, " $");
}
strbuf_add(buf, src, len);
return 1;
}
+static const char *git_path_check_encoding(struct attr_check_item *check)
+{
+ const char *value = check->value;
+
+ if (ATTR_UNSET(value) || !strlen(value))
+ return NULL;
+
+ if (ATTR_TRUE(value) || ATTR_FALSE(value)) {
+ die(_("true/false are no valid working-tree-encodings"));
+ }
+
+ /* Don't encode to the default encoding */
+ if (same_encoding(value, default_encoding))
+ return NULL;
+
+ return value;
+}
+
static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
{
const char *value = check->value;
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 void convert_attrs(struct conv_attrs *ca, const char *path)
+static void convert_attrs(const struct index_state *istate,
+ struct conv_attrs *ca, const char *path)
{
static struct attr_check *check;
+ struct attr_check_item *ccheck = NULL;
if (!check) {
check = attr_check_initl("crlf", "ident", "filter",
- "eol", "text", NULL);
+ "eol", "text", "working-tree-encoding",
+ NULL);
user_convert_tail = &user_convert;
git_config(read_convert_config, NULL);
}
- if (!git_check_attr(path, check)) {
- struct attr_check_item *ccheck = check->items;
- ca->crlf_action = git_path_check_crlf(ccheck + 4);
- if (ca->crlf_action == CRLF_UNDEFINED)
- ca->crlf_action = git_path_check_crlf(ccheck + 0);
- ca->attr_action = ca->crlf_action;
- ca->ident = git_path_check_ident(ccheck + 1);
- ca->drv = git_path_check_convert(ccheck + 2);
- if (ca->crlf_action != CRLF_BINARY) {
- enum eol eol_attr = git_path_check_eol(ccheck + 3);
- if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
- ca->crlf_action = CRLF_AUTO_INPUT;
- else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
- ca->crlf_action = CRLF_AUTO_CRLF;
- else if (eol_attr == EOL_LF)
- ca->crlf_action = CRLF_TEXT_INPUT;
- else if (eol_attr == EOL_CRLF)
- ca->crlf_action = CRLF_TEXT_CRLF;
- }
- ca->attr_action = ca->crlf_action;
- } else {
- ca->drv = NULL;
- ca->crlf_action = CRLF_UNDEFINED;
- ca->ident = 0;
+ git_check_attr(istate, path, check);
+ ccheck = check->items;
+ ca->crlf_action = git_path_check_crlf(ccheck + 4);
+ if (ca->crlf_action == CRLF_UNDEFINED)
+ ca->crlf_action = git_path_check_crlf(ccheck + 0);
+ ca->ident = git_path_check_ident(ccheck + 1);
+ ca->drv = git_path_check_convert(ccheck + 2);
+ if (ca->crlf_action != CRLF_BINARY) {
+ enum eol eol_attr = git_path_check_eol(ccheck + 3);
+ if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
+ ca->crlf_action = CRLF_AUTO_INPUT;
+ else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
+ ca->crlf_action = CRLF_AUTO_CRLF;
+ else if (eol_attr == EOL_LF)
+ ca->crlf_action = CRLF_TEXT_INPUT;
+ else if (eol_attr == EOL_CRLF)
+ ca->crlf_action = CRLF_TEXT_CRLF;
}
+ ca->working_tree_encoding = git_path_check_encoding(ccheck + 5);
+
+ /* Save attr and make a decision for action */
+ ca->attr_action = ca->crlf_action;
if (ca->crlf_action == CRLF_TEXT)
ca->crlf_action = text_eol_is_crlf() ? CRLF_TEXT_CRLF : CRLF_TEXT_INPUT;
if (ca->crlf_action == CRLF_UNDEFINED && auto_crlf == AUTO_CRLF_FALSE)
ca->crlf_action = CRLF_AUTO_INPUT;
}
-int would_convert_to_git_filter_fd(const char *path)
+int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path)
{
struct conv_attrs ca;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
if (!ca.drv)
return 0;
if (!ca.drv->required)
return 0;
- return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN);
+ return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
}
-const char *get_convert_attr_ascii(const char *path)
+const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
{
struct conv_attrs ca;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
switch (ca.attr_action) {
case CRLF_UNDEFINED:
return "";
return "";
}
-int convert_to_git(const char *path, const char *src, size_t len,
- struct strbuf *dst, enum safe_crlf checksafe)
+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 ret = 0;
struct conv_attrs ca;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
- ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN);
+ ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
if (!ret && ca.drv && ca.drv->required)
- die("%s: clean filter '%s' failed", path, ca.drv->name);
+ die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
if (ret && dst) {
src = dst->buf;
len = dst->len;
}
- ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
+
+ ret |= encode_to_git(path, src, len, dst, ca.working_tree_encoding, conv_flags);
if (ret && dst) {
src = dst->buf;
len = dst->len;
}
- return ret | ident_to_git(path, src, len, dst, ca.ident);
+
+ if (!(conv_flags & CONV_EOL_KEEP_CRLF)) {
+ ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, conv_flags);
+ if (ret && dst) {
+ src = dst->buf;
+ len = dst->len;
+ }
+ }
+ return ret | ident_to_git(src, len, dst, ca.ident);
}
-void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst,
- enum safe_crlf checksafe)
+void convert_to_git_filter_fd(const struct index_state *istate,
+ const char *path, int fd, struct strbuf *dst,
+ int conv_flags)
{
struct conv_attrs ca;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
assert(ca.drv);
assert(ca.drv->clean || ca.drv->process);
- if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN))
- die("%s: clean filter '%s' failed", path, ca.drv->name);
+ if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+ die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
- crlf_to_git(path, dst->buf, dst->len, dst, ca.crlf_action, checksafe);
- ident_to_git(path, dst->buf, dst->len, dst, ca.ident);
+ encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
+ crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);
+ ident_to_git(dst->buf, dst->len, dst, ca.ident);
}
-static int convert_to_working_tree_internal(const char *path, const char *src,
+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)
+ int normalizing, struct delayed_checkout *dco)
{
int ret = 0, ret_filter = 0;
struct conv_attrs ca;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
- ret |= ident_to_worktree(path, src, len, dst, ca.ident);
+ ret |= ident_to_worktree(src, len, dst, ca.ident);
if (ret) {
src = dst->buf;
len = dst->len;
* support smudge). The filters might expect CRLFs.
*/
if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {
- ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action);
+ ret |= crlf_to_worktree(src, len, dst, ca.crlf_action);
if (ret) {
src = dst->buf;
len = dst->len;
}
}
- ret_filter = apply_filter(path, src, len, -1, dst, ca.drv, CAP_SMUDGE);
+ 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, dco);
if (!ret_filter && ca.drv && ca.drv->required)
- die("%s: smudge filter %s failed", path, ca.drv->name);
+ die(_("%s: smudge filter %s failed"), path, ca.drv->name);
return ret | ret_filter;
}
-int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+int async_convert_to_working_tree(const struct index_state *istate,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ void *dco)
{
- return convert_to_working_tree_internal(path, src, len, dst, 0);
+ return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
}
-int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst)
+int convert_to_working_tree(const struct index_state *istate,
+ const char *path, const char *src,
+ size_t len, struct strbuf *dst)
{
- int ret = convert_to_working_tree_internal(path, src, len, dst, 1);
+ return convert_to_working_tree_internal(istate, path, src, len, dst, 0, 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);
if (ret) {
src = dst->buf;
len = dst->len;
}
- return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_RENORMALIZE);
+ return ret | convert_to_git(istate, path, src, len, dst, CONV_EOL_RENORMALIZE);
}
/*****************************************************************
struct stream_filter filter;
struct strbuf left;
int state;
- char ident[45]; /* ": x40 $" */
+ char ident[GIT_MAX_HEXSZ + 5]; /* ": x40 $" */
};
static int is_foreign_ident(const char *str)
switch (ident->state) {
default:
strbuf_add(&ident->left, head, ident->state);
+ /* fallthrough */
case IDENT_SKIPPING:
- /* fallthru */
+ /* fallthrough */
case IDENT_DRAINING:
ident_drain(ident, &output, osize_p);
}
ident_free_fn,
};
-static struct stream_filter *ident_filter(const unsigned char *sha1)
+static struct stream_filter *ident_filter(const struct object_id *oid)
{
struct ident_filter *ident = xmalloc(sizeof(*ident));
xsnprintf(ident->ident, sizeof(ident->ident),
- ": %s $", sha1_to_hex(sha1));
+ ": %s $", oid_to_hex(oid));
strbuf_init(&ident->left, 0);
ident->filter.vtbl = &ident_vtbl;
ident->state = 0;
* Note that you would be crazy to set CRLF, smuge/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 char *path, const unsigned char *sha1)
+struct stream_filter *get_stream_filter(const struct index_state *istate,
+ const char *path,
+ const struct object_id *oid)
{
struct conv_attrs ca;
struct stream_filter *filter = NULL;
- convert_attrs(&ca, path);
+ convert_attrs(istate, &ca, path);
if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))
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)
- filter = ident_filter(sha1);
+ filter = ident_filter(oid);
if (output_eol(ca.crlf_action) == EOL_CRLF)
filter = cascade_filter(filter, lf_to_crlf_filter());