summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/diff.c172
-rw-r--r--include/git2/diff.h33
-rw-r--r--include/git2/remote.h14
-rw-r--r--src/attr_file.c13
-rw-r--r--src/attrcache.c10
-rw-r--r--src/diff.c3
-rw-r--r--src/diff_print.c3
-rw-r--r--src/diff_stats.c299
-rw-r--r--src/ignore.c6
-rw-r--r--src/index.c5
-rw-r--r--src/path.c51
-rw-r--r--src/remote.c7
-rw-r--r--src/repository.c10
-rw-r--r--src/reset.c11
-rw-r--r--src/transports/http.c55
-rw-r--r--src/transports/ssh.c32
-rw-r--r--src/transports/winhttp.c125
-rw-r--r--src/unix/posix.h1
-rw-r--r--src/util.h9
-rw-r--r--src/win32/dir.c27
-rw-r--r--src/win32/dir.h3
-rw-r--r--src/win32/error.c36
-rw-r--r--src/win32/findfile.c93
-rw-r--r--src/win32/findfile.h11
-rw-r--r--src/win32/mingw-compat.h2
-rw-r--r--src/win32/posix.h13
-rw-r--r--src/win32/posix_w32.c540
-rw-r--r--src/win32/reparse.h57
-rw-r--r--src/win32/utf-conv.c125
-rw-r--r--src/win32/utf-conv.h76
-rw-r--r--src/win32/w32_util.c139
-rw-r--r--src/win32/w32_util.h54
-rw-r--r--tests/clar_libgit2.c53
-rw-r--r--tests/clar_libgit2.h11
-rw-r--r--tests/core/env.c2
-rw-r--r--tests/core/link.c602
-rw-r--r--tests/diff/format_email.c193
-rw-r--r--tests/diff/stats.c357
-rw-r--r--tests/reset/default.c39
39 files changed, 2090 insertions, 1202 deletions
diff --git a/examples/diff.c b/examples/diff.c
index 6f68e8305..76ac2f311 100644
--- a/examples/diff.c
+++ b/examples/diff.c
@@ -33,14 +33,27 @@ static const char *colors[] = {
"\033[36m" /* cyan */
};
+enum {
+ OUTPUT_DIFF = (1 << 0),
+ OUTPUT_STAT = (1 << 1),
+ OUTPUT_SHORTSTAT = (1 << 2),
+ OUTPUT_NUMSTAT = (1 << 3),
+ OUTPUT_SUMMARY = (1 << 4)
+};
+
+enum {
+ CACHE_NORMAL = 0,
+ CACHE_ONLY = 1,
+ CACHE_NONE = 2
+};
+
/** The 'opts' struct captures all the various parsed command line options. */
struct opts {
git_diff_options diffopts;
git_diff_find_options findopts;
int color;
- int cached;
- int numstat;
- int shortstat;
+ int cache;
+ int output;
git_diff_format_t format;
const char *treeish1;
const char *treeish2;
@@ -48,11 +61,11 @@ struct opts {
};
/** These functions are implemented at the end */
+static void usage(const char *message, const char *arg);
static void parse_opts(struct opts *o, int argc, char *argv[]);
static int color_printer(
const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
-static void diff_print_numstat(git_diff *diff);
-static void diff_print_shortstat(git_diff *diff);
+static void diff_print_stats(git_diff *diff, struct opts *o);
int main(int argc, char *argv[])
{
@@ -61,7 +74,7 @@ int main(int argc, char *argv[])
git_diff *diff;
struct opts o = {
GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
- -1, 0, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
+ -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
};
git_threads_init();
@@ -78,6 +91,7 @@ int main(int argc, char *argv[])
* * &lt;sha1&gt; --cached
* * &lt;sha1&gt;
* * --cached
+ * * --nocache (don't use index data in diff at all)
* * nothing
*
* Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
@@ -93,20 +107,23 @@ int main(int argc, char *argv[])
check_lg2(
git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
"diff trees", NULL);
- else if (t1 && o.cached)
- check_lg2(
- git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
- "diff tree to index", NULL);
+ else if (o.cache != CACHE_NORMAL) {
+ if (!t1)
+ treeish_to_tree(&t1, repo, "HEAD");
+
+ if (o.cache == CACHE_NONE)
+ check_lg2(
+ git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts),
+ "diff tree to working directory", NULL);
+ else
+ check_lg2(
+ git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
+ "diff tree to index", NULL);
+ }
else if (t1)
check_lg2(
git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
"diff tree to working directory", NULL);
- else if (o.cached) {
- treeish_to_tree(&t1, repo, "HEAD");
- check_lg2(
- git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
- "diff tree to index", NULL);
- }
else
check_lg2(
git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
@@ -121,11 +138,13 @@ int main(int argc, char *argv[])
/** Generate simple output using libgit2 display helper. */
- if (o.numstat == 1)
- diff_print_numstat(diff);
- else if (o.shortstat == 1)
- diff_print_shortstat(diff);
- else {
+ if (!o.output)
+ o.output = OUTPUT_DIFF;
+
+ if (o.output != OUTPUT_DIFF)
+ diff_print_stats(diff, &o);
+
+ if ((o.output & OUTPUT_DIFF) != 0) {
if (o.color >= 0)
fputs(colors[0], stdout);
@@ -210,16 +229,25 @@ static void parse_opts(struct opts *o, int argc, char *argv[])
usage("Only one or two tree identifiers can be provided", NULL);
}
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
- !strcmp(a, "--patch"))
+ !strcmp(a, "--patch")) {
+ o->output |= OUTPUT_DIFF;
o->format = GIT_DIFF_FORMAT_PATCH;
+ }
else if (!strcmp(a, "--cached"))
- o->cached = 1;
- else if (!strcmp(a, "--name-only"))
+ o->cache = CACHE_ONLY;
+ else if (!strcmp(a, "--nocache"))
+ o->cache = CACHE_NONE;
+ else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name"))
o->format = GIT_DIFF_FORMAT_NAME_ONLY;
- else if (!strcmp(a, "--name-status"))
+ else if (!strcmp(a, "--name-status") ||
+ !strcmp(a, "--format=name-status"))
o->format = GIT_DIFF_FORMAT_NAME_STATUS;
- else if (!strcmp(a, "--raw"))
+ else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw"))
o->format = GIT_DIFF_FORMAT_RAW;
+ else if (!strcmp(a, "--format=diff-index")) {
+ o->format = GIT_DIFF_FORMAT_RAW;
+ o->diffopts.id_abbrev = 40;
+ }
else if (!strcmp(a, "--color"))
o->color = 0;
else if (!strcmp(a, "--no-color"))
@@ -242,10 +270,14 @@ static void parse_opts(struct opts *o, int argc, char *argv[])
o->diffopts.flags |= GIT_DIFF_PATIENCE;
else if (!strcmp(a, "--minimal"))
o->diffopts.flags |= GIT_DIFF_MINIMAL;
+ else if (!strcmp(a, "--stat"))
+ o->output |= OUTPUT_STAT;
else if (!strcmp(a, "--numstat"))
- o->numstat = 1;
+ o->output |= OUTPUT_NUMSTAT;
else if (!strcmp(a, "--shortstat"))
- o->shortstat = 1;
+ o->output |= OUTPUT_SHORTSTAT;
+ else if (!strcmp(a, "--summary"))
+ o->output |= OUTPUT_SUMMARY;
else if (match_uint16_arg(
&o->findopts.rename_threshold, &args, "-M") ||
match_uint16_arg(
@@ -267,6 +299,8 @@ static void parse_opts(struct opts *o, int argc, char *argv[])
&o->diffopts.context_lines, &args, "--unified") &&
!match_uint16_arg(
&o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
+ !match_uint16_arg(
+ &o->diffopts.id_abbrev, &args, "--abbrev") &&
!match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
!match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
!match_str_arg(&o->dir, &args, "--git-dir"))
@@ -274,72 +308,30 @@ static void parse_opts(struct opts *o, int argc, char *argv[])
}
}
-/** Display diff output with "--numstat".*/
-static void diff_print_numstat(git_diff *diff)
+/** Display diff output with "--stat", "--numstat", or "--shortstat" */
+static void diff_print_stats(git_diff *diff, struct opts *o)
{
- git_patch *patch;
- const git_diff_delta *delta;
- size_t d, ndeltas = git_diff_num_deltas(diff);
- size_t nadditions, ndeletions;
+ git_diff_stats *stats;
+ git_buf b = GIT_BUF_INIT_CONST(NULL, 0);
+ git_diff_stats_format_t format = 0;
- for (d = 0; d < ndeltas; d++){
- check_lg2(
- git_patch_from_diff(&patch, diff, d),
- "generating patch from diff", NULL);
+ check_lg2(
+ git_diff_get_stats(&stats, diff), "generating stats for diff", NULL);
- check_lg2(
- git_patch_line_stats(NULL, &nadditions, &ndeletions, patch),
- "generating the number of additions and deletions", NULL);
+ if (o->output & OUTPUT_STAT)
+ format |= GIT_DIFF_STATS_FULL;
+ if (o->output & OUTPUT_SHORTSTAT)
+ format |= GIT_DIFF_STATS_SHORT;
+ if (o->output & OUTPUT_NUMSTAT)
+ format |= GIT_DIFF_STATS_NUMBER;
+ if (o->output & OUTPUT_SUMMARY)
+ format |= GIT_DIFF_STATS_INCLUDE_SUMMARY;
- delta = git_patch_get_delta(patch);
+ check_lg2(
+ git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL);
- printf("%ld\t%ld\t%s\n",
- (long)nadditions, (long)ndeletions, delta->new_file.path);
+ fputs(b.ptr, stdout);
- git_patch_free(patch);
- }
-}
-
-/** Display diff output with "--shortstat".*/
-static void diff_print_shortstat(git_diff *diff)
-{
- git_patch *patch;
- size_t d, ndeltas = git_diff_num_deltas(diff);
- size_t nadditions, ndeletions;
- long nadditions_sum, ndeletions_sum;
-
- nadditions_sum = 0;
- ndeletions_sum = 0;
-
- for (d = 0; d < ndeltas; d++){
- check_lg2(
- git_patch_from_diff(&patch, diff, d),
- "generating patch from diff", NULL);
-
- check_lg2(
- git_patch_line_stats(NULL, &nadditions, &ndeletions, patch),
- "generating the number of additions and deletions", NULL);
-
- nadditions_sum += nadditions;
- ndeletions_sum += ndeletions;
-
- git_patch_free(patch);
- }
-
- if (ndeltas) {
-
- printf(" %ld ", (long)ndeltas);
- printf("%s", 1==ndeltas ? "file changed" : "files changed");
-
- if(nadditions_sum) {
- printf(", %ld ",nadditions_sum);
- printf("%s", 1==nadditions_sum ? "insertion(+)" : "insertions(+)");
- }
-
- if(ndeletions_sum) {
- printf(", %ld ",ndeletions_sum);
- printf("%s", 1==ndeletions_sum ? "deletion(-)" : "deletions(-)");
- }
- printf("\n");
- }
+ git_buf_free(&b);
+ git_diff_stats_free(stats);
}
diff --git a/include/git2/diff.h b/include/git2/diff.h
index bc8b250c3..273f471b6 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -729,24 +729,17 @@ GIT_EXTERN(int) git_diff_index_to_workdir(
* The tree you provide will be used for the "old_file" side of the delta,
* and the working directory will be used for the "new_file" side.
*
- * Please note: this is *NOT* the same as `git diff <treeish>`. Running
- * `git diff HEAD` or the like actually uses information from the index,
- * along with the tree and working directory info.
- *
- * This function returns strictly the differences between the tree and the
- * files contained in the working directory, regardless of the state of
- * files in the index. It may come as a surprise, but there is no direct
- * equivalent in core git.
- *
- * To emulate `git diff <tree>`, use `git_diff_tree_to_workdir_with_index`
- * (or `git_diff_tree_to_index` and `git_diff_index_to_workdir`, then call
- * `git_diff_merge` on the results). That will yield a `git_diff` that
- * matches the git output.
- *
- * If this seems confusing, take the case of a file with a staged deletion
- * where the file has then been put back into the working dir and modified.
- * The tree-to-workdir diff for that file is 'modified', but core git would
- * show status 'deleted' since there is a pending deletion in the index.
+ * This is not the same as `git diff <treeish>` or `git diff-index
+ * <treeish>`. Those commands use information from the index, whereas this
+ * function strictly returns the differences between the tree and the files
+ * in the working directory, regardless of the state of the index. Use
+ * `git_diff_tree_to_workdir_with_index` to emulate those commands.
+ *
+ * To see difference between this and `git_diff_tree_to_workdir_with_index`,
+ * consider the example of a staged file deletion where the file has then
+ * been put back into the working dir and further modified. The
+ * tree-to-workdir diff for that file is 'modified', but `git diff` would
+ * show status 'deleted' since there is a staged delete.
*
* @param diff A pointer to a git_diff pointer that will be allocated.
* @param repo The repository containing the tree.
@@ -1147,12 +1140,14 @@ GIT_EXTERN(size_t) git_diff_stats_deletions(
* @param out buffer to store the formatted diff statistics in.
* @param stats A `git_diff_stats` generated by one of the above functions.
* @param format Formatting option.
+ * @param width Target width for output (only affects GIT_DIFF_STATS_FULL)
* @return 0 on success; non-zero on error
*/
GIT_EXTERN(int) git_diff_stats_to_buf(
git_buf *out,
const git_diff_stats *stats,
- git_diff_stats_format_t format);
+ git_diff_stats_format_t format,
+ size_t width);
/**
* Deallocate a `git_diff_stats`.
diff --git a/include/git2/remote.h b/include/git2/remote.h
index d57321f03..ddde3e85e 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -468,6 +468,9 @@ struct git_remote_callbacks {
/**
* This will be called if the remote host requires
* authentication in order to connect to it.
+ *
+ * Returning GIT_PASSTHROUGH will make libgit2 behave as
+ * though this field isn't set.
*/
int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data);
@@ -520,6 +523,17 @@ GIT_EXTERN(int) git_remote_init_callbacks(
GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks);
/**
+ * Retrieve the current callback structure
+ *
+ * This provides read access to the callbacks structure as the remote
+ * sees it.
+ *
+ * @param remote the remote to query
+ * @return a pointer to the callbacks structure
+ */
+GIT_EXTERN(const git_remote_callbacks *) git_remote_get_callbacks(git_remote *remote);
+
+/**
* Get the statistics structure that is filled in by the fetch operation.
*/
GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote);
diff --git a/src/attr_file.c b/src/attr_file.c
index d107b5ab0..156a23d91 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -232,15 +232,14 @@ int git_attr_file__parse_buffer(
while (!error && *scan) {
/* allocate rule if needed */
- if (!rule) {
- if (!(rule = git__calloc(1, sizeof(*rule)))) {
- error = -1;
- break;
- }
- rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG |
- GIT_ATTR_FNMATCH_ALLOWMACRO;
+ if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
+ error = -1;
+ break;
}
+ rule->match.flags =
+ GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
+
/* parse the next "pattern attr attr attr" line */
if (!(error = git_attr_fnmatch__parse(
&rule->match, &attrs->pool, context, &scan)) &&
diff --git a/src/attrcache.c b/src/attrcache.c
index a750154ce..f1bc70467 100644
--- a/src/attrcache.c
+++ b/src/attrcache.c
@@ -176,10 +176,9 @@ static int attr_cache_lookup(
goto cleanup;
entry = attr_cache_lookup_entry(cache, relfile);
- if (!entry) {
- if ((error = attr_cache_make_entry(&entry, repo, relfile)) < 0)
- goto cleanup;
- } else if (entry->file[source] != NULL) {
+ if (!entry)
+ error = attr_cache_make_entry(&entry, repo, relfile);
+ else if (entry->file[source] != NULL) {
file = entry->file[source];
GIT_REFCOUNT_INC(file);
}
@@ -254,8 +253,7 @@ bool git_attr_cache__is_cached(
khiter_t pos;
git_attr_file_entry *entry;
- if (!(cache = git_repository_attr_cache(repo)) ||
- !(files = cache->files))
+ if (!cache || !(files = cache->files))
return false;
pos = git_strmap_lookup_index(files, filename);
diff --git a/src/diff.c b/src/diff.c
index 0d1aed4ad..fd881c6f6 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1590,7 +1590,8 @@ int git_diff_format_email(
if ((error = git_buf_puts(out, "---\n")) < 0 ||
(error = git_diff_get_stats(&stats, diff)) < 0 ||
- (error = git_diff_stats_to_buf(out, stats, format_flags)) < 0 ||
+ (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
+ (error = git_buf_putc(out, '\n')) < 0 ||
(error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0)
goto on_error;
diff --git a/src/diff_print.c b/src/diff_print.c
index e54155458..07c1f8577 100644
--- a/src/diff_print.c
+++ b/src/diff_print.c
@@ -180,7 +180,8 @@ static int diff_print_one_raw(
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
git_buf_printf(
- out, ":%06o %06o %s... %s... %c",
+ out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
+ ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
if (delta->similarity > 0)
diff --git a/src/diff_stats.c b/src/diff_stats.c
index bb436bf7b..6ad670c42 100644
--- a/src/diff_stats.c
+++ b/src/diff_stats.c
@@ -10,149 +10,134 @@
#include "diff_patch.h"
#define DIFF_RENAME_FILE_SEPARATOR " => "
+#define STATS_FULL_MIN_SCALE 7
+
+typedef struct {
+ size_t insertions;
+ size_t deletions;
+} diff_file_stats;
struct git_diff_stats {
- git_vector patches;
+ git_diff *diff;
+ diff_file_stats *filestats;
size_t files_changed;
size_t insertions;
size_t deletions;
+ size_t renames;
+
+ size_t max_name;
+ size_t max_filestat;
+ int max_digits;
};
-static size_t diff_get_filename_padding(
- int has_renames,
- const git_diff_stats *stats)
+static int digits_for_value(size_t val)
{
- const git_patch *patch = NULL;
- size_t i, max_padding = 0;
-
- if (has_renames) {
- git_vector_foreach(&stats->patches, i, patch) {
- const git_diff_delta *delta = NULL;
- size_t len;
+ int count = 1;
+ size_t placevalue = 10;
- delta = git_patch_get_delta(patch);
- if (strcmp(delta->old_file.path, delta->new_file.path) == 0)
- continue;
-
- if ((len = strlen(delta->old_file.path) + strlen(delta->new_file.path)) > max_padding)
- max_padding = len;
- }
+ while (val >= placevalue) {
+ ++count;
+ placevalue *= 10;
}
- git_vector_foreach(&stats->patches, i, patch) {
- const git_diff_delta *delta = NULL;
- size_t len;
-
- delta = git_patch_get_delta(patch);
- len = strlen(delta->new_file.path);
-
- if (strcmp(delta->old_file.path, delta->new_file.path) != 0)
- continue;
-
- if (len > max_padding)
- max_padding = len;
- }
-
- return max_padding;
+ return count;
}
int git_diff_file_stats__full_to_buf(
git_buf *out,
- size_t max_padding,
- int has_renames,
- const git_patch *patch)
+ const git_diff_delta *delta,
+ const diff_file_stats *filestat,
+ const git_diff_stats *stats,
+ size_t width)
{
const char *old_path = NULL, *new_path = NULL;
- const git_diff_delta *delta = NULL;
size_t padding, old_size, new_size;
- int error;
-
- delta = git_patch_get_delta(patch);
old_path = delta->old_file.path;
new_path = delta->new_file.path;
old_size = delta->old_file.size;
new_size = delta->new_file.size;
- if ((error = git_buf_printf(out, " %s", old_path)) < 0)
+ if (git_buf_printf(out, " %s", old_path) < 0)
goto on_error;
if (strcmp(old_path, new_path) != 0) {
- padding = max_padding - strlen(old_path) - strlen(new_path);
+ padding = stats->max_name - strlen(old_path) - strlen(new_path);
- if ((error = git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path)) < 0)
+ if (git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path) < 0)
goto on_error;
- }
- else {
- padding = max_padding - strlen(old_path);
+ } else {
+ padding = stats->max_name - strlen(old_path);
- if (has_renames)
+ if (stats->renames > 0)
padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
}
- if ((error = git_buf_putcn(out, ' ', padding)) < 0 ||
- (error = git_buf_puts(out, " | ")) < 0)
- goto on_error;
+ if (git_buf_putcn(out, ' ', padding) < 0 ||
+ git_buf_puts(out, " | ") < 0)
+ goto on_error;
if (delta->flags & GIT_DIFF_FLAG_BINARY) {
- if ((error = git_buf_printf(out, "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size)) < 0)
+ if (git_buf_printf(out,
+ "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size) < 0)
goto on_error;
}
else {
- size_t insertions, deletions;
-
- if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0)
+ if (git_buf_printf(out,
+ "%*" PRIuZ, stats->max_digits,
+ filestat->insertions + filestat->deletions) < 0)
goto on_error;
- if ((error = git_buf_printf(out, "%" PRIuZ, insertions + deletions)) < 0)
- goto on_error;
+ if (filestat->insertions || filestat->deletions) {
+ if (git_buf_putc(out, ' ') < 0)
+ goto on_error;
- if (insertions || deletions) {
- if ((error = git_buf_putc(out, ' ')) < 0 ||
- (error = git_buf_putcn(out, '+', insertions)) < 0 ||
- (error = git_buf_putcn(out, '-', deletions)) < 0)
+ if (!width) {
+ if (git_buf_putcn(out, '+', filestat->insertions) < 0 ||
+ git_buf_putcn(out, '-', filestat->deletions) < 0)
+ goto on_error;
+ } else {
+ size_t total = filestat->insertions + filestat->deletions;
+ size_t full = (total * width + stats->max_filestat / 2) /
+ stats->max_filestat;
+ size_t plus = full * filestat->insertions / total;
+ size_t minus = full - plus;
+
+ if (git_buf_putcn(out, '+', max(plus, 1)) < 0 ||
+ git_buf_putcn(out, '-', max(minus, 1)) < 0)
goto on_error;
+ }
}
}
- error = git_buf_putc(out, '\n');
+ git_buf_putc(out, '\n');
on_error:
- return error;
+ return (git_buf_oom(out) ? -1 : 0);
}
int git_diff_file_stats__number_to_buf(
git_buf *out,
- const git_patch *patch)
+ const git_diff_delta *delta,
+ const diff_file_stats *filestats)
{
- const git_diff_delta *delta = NULL;
- const char *path = NULL;
- size_t insertions, deletions;
int error;
-
- delta = git_patch_get_delta(patch);
- path = delta->new_file.path;
-
- if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0)
- return error;
+ const char *path = delta->new_file.path;
if (delta->flags & GIT_DIFF_FLAG_BINARY)
error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
else
- error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", insertions, deletions, path);
+ error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n",
+ filestats->insertions, filestats->deletions, path);
return error;
}
int git_diff_file_stats__summary_to_buf(
git_buf *out,
- const git_patch *patch)
+ const git_diff_delta *delta)
{
- const git_diff_delta *delta = NULL;
-
- delta = git_patch_get_delta(patch);
-
if (delta->old_file.mode != delta->new_file.mode) {
if (delta->old_file.mode == 0) {
git_buf_printf(out, " create mode %06o %s\n",
@@ -171,39 +156,6 @@ int git_diff_file_stats__summary_to_buf(
return 0;
}
-int git_diff_stats__has_renames(
- const git_diff_stats *stats)
-{
- git_patch *patch = NULL;
- size_t i;
-
- git_vector_foreach(&stats->patches, i, patch) {
- const git_diff_delta *delta = git_patch_get_delta(patch);
-
- if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
- return 1;
- }
- }
-
- return 0;
-}
-
-int git_diff_stats__add_file_stats(
- git_diff_stats *stats,
- git_patch *patch)
-{
- const git_diff_delta *delta = NULL;
- int error = 0;
-
- if ((delta = git_patch_get_delta(patch)) == NULL)
- return -1;
-
- if ((error = git_vector_insert(&stats->patches, patch)) < 0)
- return error;
-
- return error;
-}
-
int git_diff_get_stats(
git_diff_stats **out,
git_diff *diff)
@@ -220,35 +172,60 @@ int git_diff_get_stats(
deltas = git_diff_num_deltas(diff);
- for (i = 0; i < deltas; ++i) {
+ stats->filestats = git__calloc(deltas, sizeof(diff_file_stats));
+ if (!stats->filestats) {
+ git__free(stats);
+ return -1;
+ }
+
+ stats->diff = diff;
+ GIT_REFCOUNT_INC(diff);
+
+ for (i = 0; i < deltas && !error; ++i) {
git_patch *patch = NULL;
- size_t add, remove;
+ size_t add = 0, remove = 0, namelen;
+ const git_diff_delta *delta;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
- goto on_error;
+ break;
- if ((error = git_patch_line_stats(NULL, &add, &remove, patch)) < 0 ||
- (error = git_diff_stats__add_file_stats(stats, patch)) < 0) {
- git_patch_free(patch);
- goto on_error;
+ /* keep a count of renames because it will affect formatting */
+ delta = git_patch_get_delta(patch);
+
+ namelen = strlen(delta->new_file.path);
+ if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
+ namelen += strlen(delta->old_file.path);
+ stats->renames++;
}
+ /* and, of course, count the line stats */
+ error = git_patch_line_stats(NULL, &add, &remove, patch);
+
+ git_patch_free(patch);
+
+ stats->filestats[i].insertions = add;
+ stats->filestats[i].deletions = remove;
+
total_insertions += add;
total_deletions += remove;
+
+ if (stats->max_name < namelen)
+ stats->max_name = namelen;
+ if (stats->max_filestat < add + remove)
+ stats->max_filestat = add + remove;
}
stats->files_changed = deltas;
stats->insertions = total_insertions;
stats->deletions = total_deletions;
+ stats->max_digits = digits_for_value(stats->max_filestat + 1);
- *out = stats;
-
- goto done;
-
-on_error:
- git_diff_stats_free(stats);
+ if (error < 0) {
+ git_diff_stats_free(stats);
+ stats = NULL;
+ }
-done:
+ *out = stats;
return error;
}
@@ -279,48 +256,67 @@ size_t git_diff_stats_deletions(
int git_diff_stats_to_buf(
git_buf *out,
const git_diff_stats *stats,
- git_diff_stats_format_t format)
+ git_diff_stats_format_t format,
+ size_t width)
{
- git_patch *patch = NULL;
+ int error = 0;
size_t i;
- int has_renames = 0, error = 0;
+ const git_diff_delta *delta;
assert(out && stats);
- /* check if we have renames, it affects the padding */
- has_renames = git_diff_stats__has_renames(stats);
-
- git_vector_foreach(&stats->patches, i, patch) {
- if (format & GIT_DIFF_STATS_FULL) {
- size_t max_padding = diff_get_filename_padding(has_renames, stats);
+ if (format & GIT_DIFF_STATS_NUMBER) {
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
- error = git_diff_file_stats__full_to_buf(out, max_padding, has_renames, patch);
+ error = git_diff_file_stats__number_to_buf(
+ out, delta, &stats->filestats[i]);
+ if (error < 0)
+ return error;
}
- else if (format & GIT_DIFF_STATS_NUMBER) {
- error = git_diff_file_stats__number_to_buf(out, patch);
+ }
+
+ if (format & GIT_DIFF_STATS_FULL) {
+ if (width > 0) {
+ if (width > stats->max_name + stats->max_digits + 5)
+ width -= (stats->max_name + stats->max_digits + 5);
+ if (width < STATS_FULL_MIN_SCALE)
+ width = STATS_FULL_MIN_SCALE;
}
- if (error < 0)
- return error;
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__full_to_buf(
+ out, delta, &stats->filestats[i], stats, width);
+ if (error < 0)
+ return error;
+ }
}
if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
- error = git_buf_printf(out, " %" PRIuZ " file%s changed, %" PRIuZ " insertions(+), %" PRIuZ " deletions(-)\n",
- stats->files_changed, stats->files_changed > 1 ? "s" : "",
- stats->insertions, stats->deletions);
+ error = git_buf_printf(
+ out, " %" PRIuZ " file%s changed, %" PRIuZ
+ " insertion%s(+), %" PRIuZ " deletion%s(-)\n",
+ stats->files_changed, stats->files_changed != 1 ? "s" : "",
+ stats->insertions, stats->insertions != 1 ? "s" : "",
+ stats->deletions, stats->deletions != 1 ? "s" : "");
if (error < 0)
return error;
}
if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
- git_vector_foreach(&stats->patches, i, patch) {
- if ((error = git_diff_file_stats__summary_to_buf(out, patch)) < 0)
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__summary_to_buf(out, delta);
+ if (error < 0)
return error;
}
-
- if (git_vector_length(&stats->patches) > 0)
- git_buf_putc(out, '\n');
}
return error;
@@ -328,16 +324,11 @@ int git_diff_stats_to_buf(
void git_diff_stats_free(git_diff_stats *stats)
{
- size_t i;
- git_patch *patch;
-
if (stats == NULL)
return;
- git_vector_foreach(&stats->patches, i, patch)
- git_patch_free(patch);
-
- git_vector_free(&stats->patches);
+ git_diff_free(stats->diff); /* bumped refcount in constructor */
+ git__free(stats->filestats);
git__free(stats);
}
diff --git a/src/ignore.c b/src/ignore.c
index b08ff2200..f373c9482 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -32,9 +32,9 @@ static int parse_ignore_file(
}
while (!error && *scan) {
- if (!match) {
- match = git__calloc(1, sizeof(*match));
- GITERR_CHECK_ALLOC(match);
+ if (!match && !(match = git__calloc(1, sizeof(*match)))) {
+ error = -1;
+ break;
}
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
diff --git a/src/index.c b/src/index.c
index 083c01fe4..c044af402 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1880,8 +1880,9 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
git_oid checksum_calculated, checksum_expected;
#define seek_forward(_increase) { \
- if (_increase >= buffer_size) \
- return index_error_invalid("ran out of data while parsing"); \
+ if (_increase >= buffer_size) { \
+ error = index_error_invalid("ran out of data while parsing"); \
+ goto done; } \
buffer += _increase; \
buffer_size -= _increase;\
}
diff --git a/src/path.c b/src/path.c
index a990b005f..2690cd8e8 100644
--- a/src/path.c
+++ b/src/path.c
@@ -9,6 +9,7 @@
#include "posix.h"
#ifdef GIT_WIN32
#include "win32/posix.h"
+#include "win32/w32_util.h"
#else
#include <dirent.h>
#endif
@@ -486,33 +487,33 @@ bool git_path_isfile(const char *path)
bool git_path_is_empty_dir(const char *path)
{
- HANDLE hFind = INVALID_HANDLE_VALUE;
- git_win32_path wbuf;
- int wbufsz;
- WIN32_FIND_DATAW ffd;
- bool retval = true;
-
- if (!git_path_isdir(path))
- return false;
-
- wbufsz = git_win32_path_from_c(wbuf, path);
- if (!wbufsz || wbufsz + 2 > GIT_WIN_PATH_UTF16)
- return false;
- memcpy(&wbuf[wbufsz - 1], L"\\*", 3 * sizeof(wchar_t));
-
- hFind = FindFirstFileW(wbuf, &ffd);
- if (INVALID_HANDLE_VALUE == hFind)
- return false;
-
- do {
- if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
- retval = false;
- break;
+ git_win32_path filter_w;
+ bool empty = false;
+
+ if (git_win32__findfirstfile_filter(filter_w, path)) {
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(filter_w, &findData);
+
+ /* If the find handle was created successfully, then it's a directory */
+ if (hFind != INVALID_HANDLE_VALUE) {
+ empty = true;
+
+ do {
+ /* Allow the enumeration to return . and .. and still be considered
+ * empty. In the special case of drive roots (i.e. C:\) where . and
+ * .. do not occur, we can still consider the path to be an empty
+ * directory if there's nothing there. */
+ if (!git_path_is_dot_or_dotdotW(findData.cFileName)) {
+ empty = false;
+ break;
+ }
+ } while (FindNextFileW(hFind, &findData));
+
+ FindClose(hFind);
}
- } while (FindNextFileW(hFind, &ffd) != 0);
+ }
- FindClose(hFind);
- return retval;
+ return empty;
}
#else
diff --git a/src/remote.c b/src/remote.c
index 243086bf9..c23a4643e 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1253,6 +1253,13 @@ int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *cal
return 0;
}
+const git_remote_callbacks *git_remote_get_callbacks(git_remote *remote)
+{
+ assert(remote);
+
+ return &remote->callbacks;
+}
+
int git_remote_set_transport(git_remote *remote, git_transport *transport)
{
assert(remote && transport);
diff --git a/src/repository.c b/src/repository.c
index 6b2705bfa..8daa04d5d 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -27,6 +27,10 @@
#include "merge.h"
#include "diff_driver.h"
+#ifdef GIT_WIN32
+# include "win32/w32_util.h"
+#endif
+
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
#define GIT_BRANCH_MASTER "master"
@@ -1149,7 +1153,7 @@ static int repo_write_template(
#ifdef GIT_WIN32
if (!error && hidden) {
- if (p_hide_directory__w32(path.ptr) < 0)
+ if (git_win32__sethidden(path.ptr) < 0)
error = -1;
}
#else
@@ -1234,8 +1238,8 @@ static int repo_init_structure(
/* Hide the ".git" directory */
#ifdef GIT_WIN32
if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {
- if (p_hide_directory__w32(repo_dir) < 0) {
- giterr_set(GITERR_REPOSITORY,
+ if (git_win32__sethidden(repo_dir) < 0) {
+ giterr_set(GITERR_OS,
"Failed to mark Git repository folder as hidden");
return -1;
}
diff --git a/src/reset.c b/src/reset.c
index e403e2a9a..248c91d3a 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -60,13 +60,18 @@ int git_reset_default(
for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) {
const git_diff_delta *delta = git_diff_get_delta(diff, i);
- if ((error = git_index_conflict_remove(index, delta->old_file.path)) < 0)
- goto cleanup;
-
assert(delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_MODIFIED ||
delta->status == GIT_DELTA_DELETED);
+ error = git_index_conflict_remove(index, delta->old_file.path);
+ if (error < 0) {
+ if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND)
+ giterr_clear();
+ else
+ goto cleanup;
+ }
+
if (delta->status == GIT_DELTA_DELETED) {
if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
goto cleanup;
diff --git a/src/transports/http.c b/src/transports/http.c
index c6aaeb9cf..a7eff7365 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -248,6 +248,7 @@ static int on_headers_complete(http_parser *parser)
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
+ int error = 0, no_callback = 0;
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
@@ -256,29 +257,43 @@ static int on_headers_complete(http_parser *parser)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Check for an authentication failure. */
+
if (parser->status_code == 401 &&
- get_verb == s->verb &&
- t->owner->cred_acquire_cb) {
- int allowed_types = 0;
+ get_verb == s->verb) {
+ if (!t->owner->cred_acquire_payload) {
+ no_callback = 1;
+ } else {
+ int allowed_types = 0;
+
+ if (parse_unauthorized_response(&t->www_authenticate,
+ &allowed_types, &t->auth_mechanism) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+
+ error = t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ t->connection_data.user,
+ allowed_types,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH) {
+ no_callback = 1;
+ } else if (error < 0) {
+ return PARSE_ERROR_GENERIC;
+ } else {
+ assert(t->cred);
+
+ /* Successfully acquired a credential. */
+ return t->parse_error = PARSE_ERROR_REPLAY;
+ }
+ }
+ }
- if (parse_unauthorized_response(&t->www_authenticate,
- &allowed_types, &t->auth_mechanism) < 0)
+ if (no_callback) {
+ giterr_set(GITERR_NET, "authentication required but no callback set");
return t->parse_error = PARSE_ERROR_GENERIC;
-
- if (allowed_types &&
- (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
-
- if (t->owner->cred_acquire_cb(&t->cred,
- t->owner->url,
- t->connection_data.user,
- allowed_types,
- t->owner->cred_acquire_payload) < 0)
- return PARSE_ERROR_GENERIC;
-
- assert(t->cred);
-
- /* Successfully acquired a credential. */
- return t->parse_error = PARSE_ERROR_REPLAY;
}
}
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index dea990275..b403727c9 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -387,6 +387,7 @@ static int _git_ssh_setup_conn(
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
const char *default_port="22";
+ int no_callback = 0;
ssh_stream *s;
LIBSSH2_SESSION* session=NULL;
LIBSSH2_CHANNEL* channel=NULL;
@@ -413,24 +414,31 @@ static int _git_ssh_setup_conn(
if (user && pass) {
if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
goto on_error;
- } else if (t->owner->cred_acquire_cb) {
- if (t->owner->cred_acquire_cb(
- &t->cred, t->owner->url, user,
- GIT_CREDTYPE_USERPASS_PLAINTEXT |
- GIT_CREDTYPE_SSH_KEY |
- GIT_CREDTYPE_SSH_INTERACTIVE |
- GIT_CREDTYPE_SSH_CUSTOM,
- t->owner->cred_acquire_payload) < 0)
+ } else if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ int error;
+ error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT |
+ GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
+ GIT_CREDTYPE_SSH_INTERACTIVE,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH)
+ no_callback = 1;
+ else if (error < 0)
goto on_error;
-
- if (!t->cred) {
+ else if (!t->cred) {
giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
goto on_error;
}
- } else {
- giterr_set(GITERR_SSH, "Cannot set up SSH connection without credentials");
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_SSH, "authentication required but no callback set");
goto on_error;
}
+
assert(t->cred);
if (_git_ssh_session_create(&session, s->socket) < 0)
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 7ad2a1636..bd9509cd4 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -91,7 +91,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred)
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;
wchar_t *wide = NULL;
- int error = -1, wide_len = 0;
+ int error = -1, wide_len;
git_buf_printf(&raw, "%s:%s", c->username, c->password);
@@ -100,21 +100,7 @@ static int apply_basic_credential(HINTERNET request, git_cred *cred)
git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
goto on_error;
- wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- git_buf_cstr(&buf), -1, NULL, 0);
-
- if (!wide_len) {
- giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
- goto on_error;
- }
-
- wide = git__malloc(wide_len * sizeof(wchar_t));
-
- if (!wide)
- goto on_error;
-
- if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- git_buf_cstr(&buf), -1, wide, wide_len)) {
+ if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
goto on_error;
}
@@ -171,23 +157,11 @@ static int fallback_cred_acquire_cb(
/* If the target URI supports integrated Windows authentication
* as an authentication mechanism */
if (GIT_CREDTYPE_DEFAULT & allowed_types) {
- LPWSTR wide_url;
- DWORD wide_len;
+ wchar_t *wide_url;
/* Convert URL to wide characters */
- wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, NULL, 0);
-
- if (!wide_len) {
- giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
- return -1;
- }
-
- wide_url = git__malloc(wide_len * sizeof(WCHAR));
- GITERR_CHECK_ALLOC(wide_url);
-
- if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, wide_url, wide_len)) {
+ if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
- git__free(wide_url);
return -1;
}
@@ -232,7 +206,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
wchar_t ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE;
- int error = -1, wide_len;
+ int error = -1;
unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
/* Prepare URL */
@@ -242,21 +216,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
return -1;
/* Convert URL to wide characters */
- wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- git_buf_cstr(&buf), -1, NULL, 0);
-
- if (!wide_len) {
- giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
- goto on_error;
- }
-
- s->request_uri = git__malloc(wide_len * sizeof(wchar_t));
-
- if (!s->request_uri)
- goto on_error;
-
- if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- git_buf_cstr(&buf), -1, s->request_uri, wide_len)) {
+ if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
goto on_error;
}
@@ -285,30 +245,17 @@ static int winhttp_stream_connect(winhttp_stream *s)
wchar_t *proxy_wide;
/* Convert URL to wide characters */
- wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- proxy_url, -1, NULL, 0);
-
- if (!wide_len) {
- giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
- goto on_error;
- }
-
- proxy_wide = git__malloc(wide_len * sizeof(wchar_t));
-
- if (!proxy_wide)
- goto on_error;
+ int proxy_wide_len = git__utf8_to_16_alloc(&proxy_wide, proxy_url);
- if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
- proxy_url, -1, proxy_wide, wide_len)) {
+ if (proxy_wide_len < 0) {
giterr_set(GITERR_OS, "Failed to convert string to wide form");
- git__free(proxy_wide);
goto on_error;
}
/* Strip any trailing forward slash on the proxy URL;
* WinHTTP doesn't like it if one is present */
- if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2])
- proxy_wide[wide_len - 2] = L'\0';
+ if (proxy_wide_len > 1 && L'/' == proxy_wide[proxy_wide_len - 2])
+ proxy_wide[proxy_wide_len - 2] = L'\0';
proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy_info.lpszProxy = proxy_wide;
@@ -359,7 +306,10 @@ static int winhttp_stream_connect(winhttp_stream *s)
s->service) < 0)
goto on_error;
- git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert content-type to wide characters");
+ goto on_error;
+ }
if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
@@ -373,7 +323,10 @@ static int winhttp_stream_connect(winhttp_stream *s)
s->service) < 0)
goto on_error;
- git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert accept header to wide characters");
+ goto on_error;
+ }
if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
@@ -506,16 +459,20 @@ static int winhttp_connect(
const char *url)
{
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
- git_win32_path host;
+ wchar_t *wide_host;
int32_t port;
const char *default_port = "80";
+ int error = -1;
/* Prepare port */
if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0)
return -1;
/* Prepare host */
- git_win32_path_from_c(host, t->connection_data.host);
+ if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert host to wide characters");
+ return -1;
+ }
/* Establish session */
t->session = WinHttpOpen(
@@ -527,22 +484,27 @@ static int winhttp_connect(
if (!t->session) {
giterr_set(GITERR_OS, "Failed to init WinHTTP");
- return -1;
+ goto on_error;
}
/* Establish connection */
t->connection = WinHttpConnect(
t->session,
- host,
+ wide_host,
(INTERNET_PORT) port,
0);
if (!t->connection) {
giterr_set(GITERR_OS, "Failed to connect to host");
- return -1;
+ goto on_error;
}
- return 0;
+ error = 0;
+
+on_error:
+ git__free(wide_host);
+
+ return error;
}
static int winhttp_stream_read(
@@ -693,7 +655,6 @@ replay:
}
location = git__malloc(location_length);
- location8 = git__malloc(location_length);
GITERR_CHECK_ALLOC(location);
if (!WinHttpQueryHeaders(s->request,
@@ -706,7 +667,14 @@ replay:
git__free(location);
return -1;
}
- git__utf16_to_8(location8, location_length, location);
+
+ /* Convert the Location header to UTF-8 */
+ if (git__utf16_to_8_alloc(&location8, location) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert Location header to UTF-8");
+ git__free(location);
+ return -1;
+ }
+
git__free(location);
/* Replay the request */
@@ -716,8 +684,11 @@ replay:
if (!git__prefixcmp_icase(location8, prefix_https)) {
/* Upgrade to secure connection; disconnect and start over */
- if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0)
+ if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
+ git__free(location8);
return -1;
+ }
+
winhttp_connect(t, location8);
}
@@ -778,7 +749,11 @@ replay:
else
snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
- git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8);
+ if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters");
+ return -1;
+ }
+
content_type_length = sizeof(content_type);
if (!WinHttpQueryHeaders(s->request,
diff --git a/src/unix/posix.h b/src/unix/posix.h
index 9c9f837b9..1e41bcf18 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -28,7 +28,6 @@ char *p_realpath(const char *, char *);
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
#define p_mkstemp(p) mkstemp(p)
-#define p_setenv(n,v,o) setenv(n,v,o)
#define p_inet_pton(a, b, c) inet_pton(a, b, c)
/* see win32/posix.h for explanation about why this exists */
diff --git a/src/util.h b/src/util.h
index d94463c65..6fb2dc0f4 100644
--- a/src/util.h
+++ b/src/util.h
@@ -22,6 +22,15 @@
#define GIT_DATE_RFC2822_SZ 32
+/**
+ * Return the length of a constant string.
+ * We are aware that `strlen` performs the same task and is usually
+ * optimized away by the compiler, whilst being safer because it returns
+ * valid values when passed a pointer instead of a constant string; however
+ * this macro will transparently work with wide-char and single-char strings.
+ */
+#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
+
/*
* Custom memory allocation wrappers
* that set error code and error message
diff --git a/src/win32/dir.c b/src/win32/dir.c
index f7859b73f..c7427ea54 100644
--- a/src/win32/dir.c
+++ b/src/win32/dir.c
@@ -7,29 +7,13 @@
#define GIT__WIN32_NO_WRAP_DIR
#include "posix.h"
-static int init_filter(char *filter, size_t n, const char *dir)
-{
- size_t len = strlen(dir);
-
- if (len+3 >= n)
- return 0;
-
- strcpy(filter, dir);
- if (len && dir[len-1] != '/')
- strcat(filter, "/");
- strcat(filter, "*");
-
- return 1;
-}
-
git__DIR *git__opendir(const char *dir)
{
- git_win32_path_as_utf8 filter;
git_win32_path filter_w;
git__DIR *new = NULL;
size_t dirlen;
- if (!dir || !init_filter(filter, sizeof(filter), dir))
+ if (!dir || !git_win32__findfirstfile_filter(filter_w, dir))
return NULL;
dirlen = strlen(dir);
@@ -39,7 +23,6 @@ git__DIR *git__opendir(const char *dir)
return NULL;
memcpy(new->dir, dir, dirlen);
- git_win32_path_from_c(filter_w, filter);
new->h = FindFirstFileW(filter_w, &new->f);
if (new->h == INVALID_HANDLE_VALUE) {
@@ -72,10 +55,10 @@ int git__readdir_ext(
return -1;
}
- if (wcslen(d->f.cFileName) >= sizeof(entry->d_name))
+ /* Convert the path to UTF-8 */
+ if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0)
return -1;
- git_win32_path_to_c(entry->d_name, d->f.cFileName);
entry->d_ino = 0;
*result = entry;
@@ -96,7 +79,6 @@ struct git__dirent *git__readdir(git__DIR *d)
void git__rewinddir(git__DIR *d)
{
- git_win32_path_as_utf8 filter;
git_win32_path filter_w;
if (!d)
@@ -108,10 +90,9 @@ void git__rewinddir(git__DIR *d)
d->first = 0;
}
- if (!init_filter(filter, sizeof(filter), d->dir))
+ if (!git_win32__findfirstfile_filter(filter_w, d->dir))
return;
- git_win32_path_from_c(filter_w, filter);
d->h = FindFirstFileW(filter_w, &d->f);
if (d->h == INVALID_HANDLE_VALUE)
diff --git a/src/win32/dir.h b/src/win32/dir.h
index 24d48f6ba..bef39d774 100644
--- a/src/win32/dir.h
+++ b/src/win32/dir.h
@@ -8,10 +8,11 @@
#define INCLUDE_dir_h__
#include "common.h"
+#include "w32_util.h"
struct git__dirent {
int d_ino;
- git_win32_path_as_utf8 d_name;
+ git_win32_utf8_path d_name;
};
typedef struct {
diff --git a/src/win32/error.c b/src/win32/error.c
index bc598ae32..6b450093f 100644
--- a/src/win32/error.c
+++ b/src/win32/error.c
@@ -7,21 +7,17 @@
#include "common.h"
#include "error.h"
+#include "utf-conv.h"
#ifdef GIT_WINHTTP
# include <winhttp.h>
#endif
-#ifndef WC_ERR_INVALID_CHARS
-#define WC_ERR_INVALID_CHARS 0x80
-#endif
-
char *git_win32_get_error_message(DWORD error_code)
{
LPWSTR lpMsgBuf = NULL;
HMODULE hModule = NULL;
char *utf8_msg = NULL;
- int utf8_size;
DWORD dwFlags =
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
@@ -45,33 +41,11 @@ char *git_win32_get_error_message(DWORD error_code)
if (FormatMessageW(dwFlags, hModule, error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&lpMsgBuf, 0, NULL)) {
+ /* Convert the message to UTF-8. If this fails, we will
+ * return NULL, which is a condition expected by the caller */
+ if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0)
+ utf8_msg = NULL;
- /* Invalid code point check supported on Vista+ only */
- if (git_has_win32_version(6, 0, 0))
- dwFlags = WC_ERR_INVALID_CHARS;
- else
- dwFlags = 0;
-
- utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags,
- lpMsgBuf, -1, NULL, 0, NULL, NULL);
-
- if (!utf8_size) {
- assert(0);
- goto on_error;
- }
-
- utf8_msg = git__malloc(utf8_size);
-
- if (!utf8_msg)
- goto on_error;
-
- if (!WideCharToMultiByte(CP_UTF8, dwFlags,
- lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) {
- git__free(utf8_msg);
- goto on_error;
- }
-
-on_error:
LocalFree(lpMsgBuf);
}
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
index a9e812e28..86d4ef5bd 100644
--- a/src/win32/findfile.c
+++ b/src/win32/findfile.c
@@ -17,54 +17,34 @@
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#endif
-int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ)
-{
- s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
- return s_root->len ? 0 : -1;
-}
+typedef struct {
+ git_win32_path path;
+ DWORD len;
+} _findfile_path;
-static int win32_path_to_8(git_buf *path_utf8, const wchar_t *path)
+static int git_win32__expand_path(_findfile_path *dest, const wchar_t *src)
{
- char temp_utf8[GIT_PATH_MAX];
+ dest->len = ExpandEnvironmentStringsW(src, dest->path, ARRAY_SIZE(dest->path));
- git__utf16_to_8(temp_utf8, GIT_PATH_MAX, path);
- git_path_mkposix(temp_utf8);
+ if (!dest->len || dest->len > ARRAY_SIZE(dest->path))
+ return -1;
- return git_buf_sets(path_utf8, temp_utf8);
+ return 0;
}
-int git_win32__find_file(
- git_buf *path, const struct git_win32__path *root, const char *filename)
+static int win32_path_to_8(git_buf *dest, const wchar_t *src)
{
- size_t len, alloc_len;
- wchar_t *file_utf16 = NULL;
-
- if (!root || !filename || (len = strlen(filename)) == 0)
- return GIT_ENOTFOUND;
-
- /* allocate space for wchar_t path to file */
- alloc_len = root->len + len + 2;
- file_utf16 = git__calloc(alloc_len, sizeof(wchar_t));
- GITERR_CHECK_ALLOC(file_utf16);
+ git_win32_utf8_path utf8_path;
- /* append root + '\\' + filename as wchar_t */
- memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
-
- if (*filename == '/' || *filename == '\\')
- filename++;
-
- git__utf8_to_16(file_utf16 + root->len - 1, alloc_len - root->len, filename);
-
- /* check access */
- if (_waccess(file_utf16, F_OK) < 0) {
- git__free(file_utf16);
- return GIT_ENOTFOUND;
+ if (git_win32_path_to_utf8(utf8_path, src) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert path to UTF-8");
+ return -1;
}
- win32_path_to_8(path, file_utf16);
- git__free(file_utf16);
+ /* Convert backslashes to forward slashes */
+ git_path_mkposix(utf8_path);
- return 0;
+ return git_buf_sets(dest, utf8_path);
}
static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
@@ -89,7 +69,7 @@ static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir)
{
wchar_t *env = _wgetenv(L"PATH"), lastch;
- struct git_win32__path root;
+ _findfile_path root;
size_t gitexe_len = wcslen(gitexe);
if (!env)
@@ -122,43 +102,44 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wch
}
static int win32_find_git_in_registry(
- git_buf *buf, const HKEY hieve, const wchar_t *key, const wchar_t *subdir)
+ git_buf *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir)
{
HKEY hKey;
- DWORD dwType = REG_SZ;
- struct git_win32__path path16;
+ int error = GIT_ENOTFOUND;
assert(buf);
- path16.len = MAX_PATH;
+ if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) {
+ DWORD dwType, cbData;
+ git_win32_path path;
- if (RegOpenKeyExW(hieve, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
- if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,
- (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS)
- {
- /* InstallLocation points to the root of the git directory */
+ /* Ensure that the buffer is big enough to have the suffix attached
+ * after we receive the result. */
+ cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t));
- if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */
- giterr_set(GITERR_OS, "Cannot locate git - path too long");
- return -1;
- }
+ /* InstallLocation points to the root of the git directory */
+ if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) &&
+ dwType == REG_SZ) {
- wcscat(path16.path, subdir);
- path16.len += 4;
+ /* Append the suffix */
+ wcscat(path, subdir);
- win32_path_to_8(buf, path16.path);
+ /* Convert to UTF-8, with forward slashes, and output the path
+ * to the provided buffer */
+ if (!win32_path_to_8(buf, path))
+ error = 0;
}
RegCloseKey(hKey);
}
- return path16.len ? 0 : GIT_ENOTFOUND;
+ return error;
}
static int win32_find_existing_dirs(
git_buf *out, const wchar_t *tmpl[])
{
- struct git_win32__path path16;
+ _findfile_path path16;
git_buf buf = GIT_BUF_INIT;
git_buf_clear(out);
diff --git a/src/win32/findfile.h b/src/win32/findfile.h
index 11bf7e620..a50319b9a 100644
--- a/src/win32/findfile.h
+++ b/src/win32/findfile.h
@@ -8,17 +8,6 @@
#ifndef INCLUDE_git_findfile_h__
#define INCLUDE_git_findfile_h__
-struct git_win32__path {
- wchar_t path[MAX_PATH];
- DWORD len;
-};
-
-extern int git_win32__expand_path(
- struct git_win32__path *s_root, const wchar_t *templ);
-
-extern int git_win32__find_file(
- git_buf *path, const struct git_win32__path *root, const char *filename);
-
extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath);
extern int git_win32__find_global_dirs(git_buf *out);
extern int git_win32__find_xdg_dirs(git_buf *out);
diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h
index 7b97b48db..8f51d6f5a 100644
--- a/src/win32/mingw-compat.h
+++ b/src/win32/mingw-compat.h
@@ -11,7 +11,9 @@
/* use a 64-bit file offset type */
# define lseek _lseeki64
+# undef stat
# define stat _stati64
+# undef fstat
# define fstat _fstati64
/* stat: file mode type testing macros */
diff --git a/src/win32/posix.h b/src/win32/posix.h
index 24cba23e0..7f9d57cc3 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -27,24 +27,15 @@ GIT_INLINE(int) p_link(const char *old, const char *new)
return -1;
}
-GIT_INLINE(int) p_mkdir(const char *path, mode_t mode)
-{
- git_win32_path buf;
- GIT_UNUSED(mode);
- git_win32_path_from_c(buf, path);
- return _wmkdir(buf);
-}
-
+extern int p_mkdir(const char *path, mode_t mode);
extern int p_unlink(const char *path);
extern int p_lstat(const char *file_name, struct stat *buf);
-extern int p_readlink(const char *link, char *target, size_t target_len);
+extern int p_readlink(const char *path, char *buf, size_t bufsiz);
extern int p_symlink(const char *old, const char *new);
-extern int p_hide_directory__w32(const char *path);
extern char *p_realpath(const char *orig_path, char *buffer);
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4);
extern int p_mkstemp(char *tmp_path);
-extern int p_setenv(const char* name, const char* value, int overwrite);
extern int p_stat(const char* path, struct stat* buf);
extern int p_chdir(const char* path);
extern int p_chmod(const char* path, mode_t mode);
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 6f2931880..0d070f6b5 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -9,17 +9,65 @@
#include "path.h"
#include "utf-conv.h"
#include "repository.h"
+#include "reparse.h"
#include <errno.h>
#include <io.h>
#include <fcntl.h>
#include <ws2tcpip.h>
+#ifndef FILE_NAME_NORMALIZED
+# define FILE_NAME_NORMALIZED 0
+#endif
+
+/* GetFinalPathNameByHandleW signature */
+typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
+
+/* Helper function which converts UTF-8 paths to UTF-16.
+ * On failure, errno is set. */
+static int utf8_to_16_with_errno(git_win32_path dest, const char *src)
+{
+ int len = git_win32_path_from_utf8(dest, src);
+
+ if (len < 0) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL; /* Bad code point, presumably */
+ }
+
+ return len;
+}
+
+int p_mkdir(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ GIT_UNUSED(mode);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _wmkdir(buf);
+}
+
int p_unlink(const char *path)
{
git_win32_path buf;
- git_win32_path_from_c(buf, path);
- _wchmod(buf, 0666);
- return _wunlink(buf);
+ int error;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ error = _wunlink(buf);
+
+ /* If the file could not be deleted because it was
+ * read-only, clear the bit and try again */
+ if (error == -1 && errno == EACCES) {
+ _wchmod(buf, 0666);
+ error = _wunlink(buf);
+ }
+
+ return error;
}
int p_fsync(int fd)
@@ -53,28 +101,79 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
return (time_t)winTime;
}
+/* On success, returns the length, in characters, of the path stored in dest.
+ * On failure, returns a negative value. */
+static int readlink_w(
+ git_win32_path dest,
+ const git_win32_path path)
+{
+ BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
+ HANDLE handle = NULL;
+ DWORD ioctl_ret;
+ wchar_t *target;
+ size_t target_len;
+
+ int error = -1;
+
+ handle = CreateFileW(path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ switch (reparse_buf->ReparseTag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
+ (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ target = reparse_buf->MountPointReparseBuffer.PathBuffer +
+ (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ default:
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ if (target_len) {
+ /* The path may need to have a prefix removed. */
+ target_len = git_win32__canonicalize_path(target, target_len);
+
+ /* Need one additional character in the target buffer
+ * for the terminating NULL. */
+ if (GIT_WIN_PATH_UTF16 > target_len) {
+ wcscpy(dest, target);
+ error = (int)target_len;
+ }
+ }
+
+on_error:
+ CloseHandle(handle);
+ return error;
+}
+
#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
-static int do_lstat(
- const char *file_name, struct stat *buf, int posix_enotdir)
+static int lstat_w(
+ wchar_t *path,
+ struct stat *buf,
+ bool posix_enotdir)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
- git_win32_path fbuf;
- wchar_t lastch;
- int flen;
-
- flen = git_win32_path_from_c(fbuf, file_name);
-
- /* truncate trailing slashes */
- for (; flen > 0; --flen) {
- lastch = fbuf[flen - 1];
- if (WIN32_IS_WSEP(lastch))
- fbuf[flen - 1] = L'\0';
- else if (lastch != L'\0')
- break;
- }
- if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
+ if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
int fMode = S_IREAD;
if (!buf)
@@ -88,12 +187,6 @@ static int do_lstat(
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
fMode |= S_IWRITE;
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
- fMode |= S_IFLNK;
-
- if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction
- fMode ^= S_IFLNK;
-
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
@@ -105,19 +198,17 @@ static int do_lstat(
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
- /* Windows symlinks have zero file size, call readlink to determine
- * the length of the path pointed to, which we expect everywhere else
- */
- if (S_ISLNK(fMode)) {
- git_win32_path_as_utf8 target;
- int readlink_result;
+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ git_win32_path target;
- readlink_result = p_readlink(file_name, target, sizeof(target));
+ if (readlink_w(target, path) >= 0) {
+ buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFLNK;
- if (readlink_result == -1)
- return -1;
-
- buf->st_size = strlen(target);
+ /* st_size gets the UTF-8 length of the target name, in bytes,
+ * not counting the NULL terminator */
+ if ((buf->st_size = git__utf16_to_8(NULL, 0, target)) < 0)
+ return -1;
+ }
}
return 0;
@@ -129,18 +220,23 @@ static int do_lstat(
* file path is a regular file, otherwise set ENOENT.
*/
if (posix_enotdir) {
+ size_t path_len = wcslen(path);
+
/* scan up path until we find an existing item */
while (1) {
+ DWORD attrs;
+
/* remove last directory component */
- for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen);
+ for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
- if (flen <= 0)
+ if (path_len <= 0)
break;
- fbuf[flen] = L'\0';
+ path[path_len] = L'\0';
+ attrs = GetFileAttributesW(path);
- if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
- if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
errno = ENOTDIR;
break;
}
@@ -150,108 +246,51 @@ static int do_lstat(
return -1;
}
+static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
+ return -1;
+
+ git_win32__path_trim_end(path_w, len);
+
+ return lstat_w(path_w, buf, posixly_correct);
+}
+
int p_lstat(const char *filename, struct stat *buf)
{
- return do_lstat(filename, buf, 0);
+ return do_lstat(filename, buf, false);
}
int p_lstat_posixly(const char *filename, struct stat *buf)
{
- return do_lstat(filename, buf, 1);
+ return do_lstat(filename, buf, true);
}
-
-/*
- * Parts of the The p_readlink function are heavily inspired by the php
- * readlink function in link_win32.c
- *
- * Copyright (c) 1999 - 2012 The PHP Group. All rights reserved.
- *
- * For details of the PHP license see http://www.php.net/license/3_01.txt
- */
-int p_readlink(const char *link, char *target, size_t target_len)
+int p_readlink(const char *path, char *buf, size_t bufsiz)
{
- typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD);
- static fpath_func pGetFinalPath = NULL;
- HANDLE hFile;
- DWORD dwRet;
- git_win32_path link_w;
- wchar_t* target_w;
- int error = 0;
-
- assert(link && target && target_len > 0);
-
- /*
- * Try to load the pointer to pGetFinalPath dynamically, because
- * it is not available in platforms older than Vista
- */
- if (pGetFinalPath == NULL) {
- HMODULE module = GetModuleHandle("kernel32");
-
- if (module != NULL)
- pGetFinalPath = (fpath_func)GetProcAddress(module, "GetFinalPathNameByHandleW");
-
- if (pGetFinalPath == NULL) {
- giterr_set(GITERR_OS,
- "'GetFinalPathNameByHandleW' is not available in this platform");
- return -1;
- }
- }
-
- git_win32_path_from_c(link_w, link);
+ git_win32_path path_w, target_w;
+ git_win32_utf8_path target;
+ int len;
- hFile = CreateFileW(link_w, // file to open
- GENERIC_READ, // open for reading
- FILE_SHARE_READ, // share for reading
- NULL, // default security
- OPEN_EXISTING, // existing file only
- FILE_FLAG_BACKUP_SEMANTICS, // normal file
- NULL); // no attr. template
+ /* readlink(2) does not NULL-terminate the string written
+ * to the target buffer. Furthermore, the target buffer need
+ * not be large enough to hold the entire result. A truncated
+ * result should be written in this case. Since this truncation
+ * could occur in the middle of the encoding of a code point,
+ * we need to buffer the result on the stack. */
- if (hFile == INVALID_HANDLE_VALUE) {
- giterr_set(GITERR_OS, "Cannot open '%s' for reading", link);
+ if (utf8_to_16_with_errno(path_w, path) < 0 ||
+ readlink_w(target_w, path_w) < 0 ||
+ (len = git_win32_path_to_utf8(target, target_w)) < 0)
return -1;
- }
-
- target_w = (wchar_t*)git__malloc(target_len * sizeof(wchar_t));
- GITERR_CHECK_ALLOC(target_w);
- dwRet = pGetFinalPath(hFile, target_w, (DWORD)target_len, 0x0);
- if (dwRet == 0 ||
- dwRet >= target_len ||
- !WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target,
- (int)(target_len * sizeof(char)), NULL, NULL))
- error = -1;
+ bufsiz = min((size_t)len, bufsiz);
+ memcpy(buf, target, bufsiz);
- git__free(target_w);
- CloseHandle(hFile);
-
- if (error)
- return error;
-
- /* Skip first 4 characters if they are "\\?\" */
- if (dwRet > 4 &&
- target[0] == '\\' && target[1] == '\\' &&
- target[2] == '?' && target[3] == '\\')
- {
- unsigned int offset = 4;
- dwRet -= 4;
-
- /* \??\UNC\ */
- if (dwRet > 7 &&
- target[4] == 'U' && target[5] == 'N' && target[6] == 'C')
- {
- offset += 2;
- dwRet -= 2;
- target[offset] = '\\';
- }
-
- memmove(target, target + offset, dwRet);
- }
-
- target[dwRet] = '\0';
-
- return dwRet;
+ return (int)bufsiz;
}
int p_symlink(const char *old, const char *new)
@@ -267,7 +306,8 @@ int p_open(const char *path, int flags, ...)
git_win32_path buf;
mode_t mode = 0;
- git_win32_path_from_c(buf, path);
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
if (flags & O_CREAT) {
va_list arg_list;
@@ -283,125 +323,211 @@ int p_open(const char *path, int flags, ...)
int p_creat(const char *path, mode_t mode)
{
git_win32_path buf;
- git_win32_path_from_c(buf, path);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode);
}
int p_getcwd(char *buffer_out, size_t size)
{
- int ret;
- wchar_t *buf;
+ git_win32_path buf;
+ wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
+
+ if (!cwd)
+ return -1;
+
+ /* Convert the working directory back to UTF-8 */
+ if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INSUFFICIENT_BUFFER)
+ errno = ERANGE;
+ else
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Returns the address of the GetFinalPathNameByHandleW function.
+ * This function is available on Windows Vista and higher.
+ */
+static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
+{
+ static PFGetFinalPathNameByHandleW pFunc = NULL;
+ PFGetFinalPathNameByHandleW toReturn = pFunc;
+
+ if (!toReturn) {
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule)
+ toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
+
+ pFunc = toReturn;
+ }
+
+ assert(toReturn);
+
+ return toReturn;
+}
+
+static int getfinalpath_w(
+ git_win32_path dest,
+ const wchar_t *path)
+{
+ PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
+ HANDLE hFile;
+ DWORD dwChars;
+
+ if (!pgfp)
+ return -1;
- if ((size_t)((int)size) != size)
+ /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
+ * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
+ * target of the link. */
+ hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (INVALID_HANDLE_VALUE == hFile)
return -1;
- buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size);
- GITERR_CHECK_ALLOC(buf);
+ /* Call GetFinalPathNameByHandle */
+ dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
+ CloseHandle(hFile);
+
+ if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
+ return -1;
- _wgetcwd(buf, (int)size);
+ /* The path may be delivered to us with a prefix; canonicalize */
+ return (int)git_win32__canonicalize_path(dest, dwChars);
+}
- ret = WideCharToMultiByte(
- CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL);
+static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
+{
+ git_win32_path target_w;
- git__free(buf);
- return !ret ? -1 : 0;
+ if (getfinalpath_w(target_w, path) < 0)
+ return -1;
+
+ return lstat_w(target_w, buf, false);
}
int p_stat(const char* path, struct stat* buf)
{
- git_win32_path_as_utf8 target;
- int error = 0;
+ git_win32_path path_w;
+ int len;
+
+ if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
+ return -1;
- error = do_lstat(path, buf, 0);
+ git_win32__path_trim_end(path_w, len);
- /* We need not do this in a loop to unwind chains of symlinks since
- * p_readlink calls GetFinalPathNameByHandle which does it for us. */
- if (error >= 0 && S_ISLNK(buf->st_mode) &&
- (error = p_readlink(path, target, sizeof(target))) >= 0)
- error = do_lstat(target, buf, 0);
+ if (lstat_w(path_w, buf, false) < 0)
+ return -1;
- return error;
+ /* The item is a symbolic link or mount point. No need to iterate
+ * to follow multiple links; use GetFinalPathNameFromHandle. */
+ if (S_ISLNK(buf->st_mode))
+ return follow_and_lstat_link(path_w, buf);
+
+ return 0;
}
int p_chdir(const char* path)
{
git_win32_path buf;
- git_win32_path_from_c(buf, path);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
return _wchdir(buf);
}
int p_chmod(const char* path, mode_t mode)
{
git_win32_path buf;
- git_win32_path_from_c(buf, path);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
return _wchmod(buf, mode);
}
int p_rmdir(const char* path)
{
- int error;
git_win32_path buf;
- git_win32_path_from_c(buf, path);
+ int error;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
error = _wrmdir(buf);
- /* _wrmdir() is documented to return EACCES if "A program has an open
- * handle to the directory." This sounds like what everybody else calls
- * EBUSY. Let's convert appropriate error codes.
- */
- if (GetLastError() == ERROR_SHARING_VIOLATION)
- errno = EBUSY;
+ if (error == -1) {
+ switch (GetLastError()) {
+ /* _wrmdir() is documented to return EACCES if "A program has an open
+ * handle to the directory." This sounds like what everybody else calls
+ * EBUSY. Let's convert appropriate error codes.
+ */
+ case ERROR_SHARING_VIOLATION:
+ errno = EBUSY;
+ break;
- return error;
-}
+ /* This error can be returned when trying to rmdir an extant file. */
+ case ERROR_DIRECTORY:
+ errno = ENOTDIR;
+ break;
+ }
+ }
-int p_hide_directory__w32(const char *path)
-{
- git_win32_path buf;
- git_win32_path_from_c(buf, path);
- return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1;
+ return error;
}
char *p_realpath(const char *orig_path, char *buffer)
{
- int ret;
- git_win32_path orig_path_w;
- git_win32_path buffer_w;
+ git_win32_path orig_path_w, buffer_w;
- git_win32_path_from_c(orig_path_w, orig_path);
+ if (utf8_to_16_with_errno(orig_path_w, orig_path) < 0)
+ return NULL;
- /* Implicitly use GetCurrentDirectory which can be a threading issue */
- ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL);
+ /* Note that if the path provided is a relative path, then the current directory
+ * is used to resolve the path -- which is a concurrency issue because the current
+ * directory is a process-wide variable. */
+ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
- /* According to MSDN, a return value equals to zero means a failure. */
- if (ret == 0 || ret > GIT_WIN_PATH_UTF16)
- buffer = NULL;
+ return NULL;
+ }
- else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
- buffer = NULL;
+ /* The path must exist. */
+ if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
errno = ENOENT;
+ return NULL;
}
- else if (buffer == NULL) {
- int buffer_sz = WideCharToMultiByte(
- CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
-
- if (!buffer_sz ||
- !(buffer = (char *)git__malloc(buffer_sz)) ||
- !WideCharToMultiByte(
- CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
- {
- git__free(buffer);
- buffer = NULL;
- }
+ /* Convert the path to UTF-8. */
+ if (buffer) {
+ /* If the caller provided a buffer, then it is assumed to be GIT_WIN_PATH_UTF8
+ * characters in size. If it isn't, then we may overflow. */
+ if (git__utf16_to_8(buffer, GIT_WIN_PATH_UTF8, buffer_w) < 0)
+ return NULL;
+ } else {
+ /* If the caller did not provide a buffer, then we allocate one for the caller
+ * from the heap. */
+ if (git__utf16_to_8_alloc(&buffer, buffer_w) < 0)
+ return NULL;
}
- else if (!WideCharToMultiByte(
- CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL))
- buffer = NULL;
-
- if (buffer)
- git_path_mkposix(buffer);
+ /* Convert backslashes to forward slashes */
+ git_path_mkposix(buffer);
return buffer;
}
@@ -433,8 +559,6 @@ int p_snprintf(char *buffer, size_t count, const char *format, ...)
return r;
}
-extern int p_creat(const char *path, mode_t mode);
-
int p_mkstemp(char *tmp_path)
{
#if defined(_MSC_VER)
@@ -448,18 +572,13 @@ int p_mkstemp(char *tmp_path)
return p_creat(tmp_path, 0744); //-V536
}
-int p_setenv(const char* name, const char* value, int overwrite)
-{
- if (overwrite != 1)
- return -1;
-
- return (SetEnvironmentVariableA(name, value) == 0 ? -1 : 0);
-}
-
int p_access(const char* path, mode_t mode)
{
git_win32_path buf;
- git_win32_path_from_c(buf, path);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
return _waccess(buf, mode);
}
@@ -471,8 +590,9 @@ int p_rename(const char *from, const char *to)
int rename_succeeded;
int error;
- git_win32_path_from_c(wfrom, from);
- git_win32_path_from_c(wto, to);
+ if (utf8_to_16_with_errno(wfrom, from) < 0 ||
+ utf8_to_16_with_errno(wto, to) < 0)
+ return -1;
/* wait up to 50ms if file is locked by another thread or process */
rename_tries = 0;
diff --git a/src/win32/reparse.h b/src/win32/reparse.h
new file mode 100644
index 000000000..70f9fd652
--- /dev/null
+++ b/src/win32/reparse.h
@@ -0,0 +1,57 @@
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+
+#ifndef INCLUDE_git_win32_reparse_h__
+#define INCLUDE_git_win32_reparse_h__
+
+/* This structure is defined on MSDN at
+* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+*
+* It was formerly included in the Windows 2000 SDK and remains defined in
+* MinGW, so we must define it with a silly name to avoid conflicting.
+*/
+typedef struct _GIT_REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} GIT_REPARSE_DATA_BUFFER;
+
+#define REPARSE_DATA_HEADER_SIZE 8
+#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
+#define REPARSE_DATA_UNION_SIZE 12
+
+/* Missing in MinGW */
+#ifndef FSCTL_GET_REPARSE_POINT
+# define FSCTL_GET_REPARSE_POINT 0x000900a8
+#endif
+
+/* Missing in MinGW */
+#ifndef FSCTL_SET_REPARSE_POINT
+# define FSCTL_SET_REPARSE_POINT 0x000900a4
+#endif
+
+#endif
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index a96385f10..b9ccfb5e5 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -8,12 +8,131 @@
#include "common.h"
#include "utf-conv.h"
-int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src)
+#ifndef WC_ERR_INVALID_CHARS
+# define WC_ERR_INVALID_CHARS 0x80
+#endif
+
+GIT_INLINE(DWORD) get_wc_flags(void)
{
- return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)dest_size);
+ static char inited = 0;
+ static DWORD flags;
+
+ /* Invalid code point check supported on Vista+ only */
+ if (!inited) {
+ flags = git_has_win32_version(6, 0, 0) ? WC_ERR_INVALID_CHARS : 0;
+ inited = 1;
+ }
+
+ return flags;
}
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
+ return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
{
- return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, (int)dest_size, NULL, NULL);
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
+ return WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1;
+}
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
+{
+ int utf16_size;
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
+
+ if (!utf16_size)
+ return -1;
+
+ *dest = git__malloc(utf16_size * sizeof(wchar_t));
+
+ if (!*dest)
+ return -1;
+
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
+
+ if (!utf16_size) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf16_size - 1;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
+{
+ int utf8_size;
+ DWORD dwFlags = get_wc_flags();
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, NULL, 0, NULL, NULL);
+
+ if (!utf8_size)
+ return -1;
+
+ *dest = git__malloc(utf8_size);
+
+ if (!*dest)
+ return -1;
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL);
+
+ if (!utf8_size) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf8_size - 1;
}
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index 3af77580e..a480cd93e 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -10,27 +10,83 @@
#include <wchar.h>
#include "common.h"
-/* Maximum characters in a Windows path plus one for NUL byte */
-#define GIT_WIN_PATH_UTF16 (260 + 1)
+/* Equal to the Win32 MAX_PATH constant. The maximum path length is 259
+ * characters plus a NULL terminator. */
+#define GIT_WIN_PATH_UTF16 260
-/* Maximum bytes necessary to convert a full-length UTF16 path to UTF8 */
-#define GIT_WIN_PATH_UTF8 (260 * 4 + 1)
+/* Maximum size of a UTF-8 Win32 path. UTF-8 does have 4-byte sequences,
+ * but they are encoded in UTF-16 using surrogate pairs, which takes up
+ * the space of two characters. Two characters in the range U+0800 ->
+ * U+FFFF take up more space in UTF-8 (6 bytes) than one surrogate pair
+ * (4 bytes). */
+#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
+/* Win32 path types */
typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
+typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
-typedef char git_win32_path_as_utf8[GIT_WIN_PATH_UTF8];
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
-/* dest_size is the size of dest in wchar_t's */
-int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src);
-/* dest_size is the size of dest in char's */
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src);
-GIT_INLINE(int) git_win32_path_from_c(git_win32_path dest, const char *src)
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
+
+/**
+ * Converts a UTF-8 Win32 path to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+GIT_INLINE(int) git_win32_path_from_utf8(git_win32_path dest, const char *src)
{
return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src);
}
-GIT_INLINE(int) git_win32_path_to_c(git_win32_path_as_utf8 dest, const wchar_t *src)
+/**
+ * Converts a wide Win32 path to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+GIT_INLINE(int) git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
{
return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src);
}
diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c
new file mode 100644
index 000000000..2e52525d5
--- /dev/null
+++ b/src/win32/w32_util.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "w32_util.h"
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src)
+{
+ static const wchar_t suffix[] = L"\\*";
+ int len = git_win32_path_from_utf8(dest, src);
+
+ /* Ensure the path was converted */
+ if (len < 0)
+ return false;
+
+ /* Ensure that the path does not end with a trailing slash,
+ * because we're about to add one. Don't rely our trim_end
+ * helper, because we want to remove the backslash even for
+ * drive letter paths, in this case. */
+ if (len > 0 &&
+ (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) {
+ dest[len - 1] = L'\0';
+ len--;
+ }
+
+ /* Ensure we have enough room to add the suffix */
+ if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix))
+ return false;
+
+ wcscat(dest, suffix);
+ return true;
+}
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__sethidden(const char *path)
+{
+ git_win32_path buf;
+ DWORD attrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ /* If the item isn't already +H, add the bit */
+ if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 &&
+ !SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len)
+{
+ while (1) {
+ if (!len || str[len - 1] != L'\\')
+ break;
+
+ /* Don't trim backslashes from drive letter paths, which
+ * are 3 characters long and of the form C:\, D:\, etc. */
+ if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+ break;
+
+ len--;
+ }
+
+ str[len] = L'\0';
+
+ return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len)
+{
+ static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
+ static const wchar_t nt_prefix[] = L"\\\\?\\";
+ static const wchar_t unc_prefix[] = L"UNC\\";
+ size_t to_advance = 0;
+
+ /* "\??\" -- DOS Devices prefix */
+ if (len >= CONST_STRLEN(dosdevices_prefix) &&
+ !wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
+ to_advance += CONST_STRLEN(dosdevices_prefix);
+ len -= CONST_STRLEN(dosdevices_prefix);
+ }
+ /* "\\?\" -- NT namespace prefix */
+ else if (len >= CONST_STRLEN(nt_prefix) &&
+ !wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
+ to_advance += CONST_STRLEN(nt_prefix);
+ len -= CONST_STRLEN(nt_prefix);
+ }
+
+ /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+ if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
+ !wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
+ to_advance += CONST_STRLEN(unc_prefix);
+ len -= CONST_STRLEN(unc_prefix);
+ }
+
+ if (to_advance) {
+ memmove(str, str + to_advance, len * sizeof(wchar_t));
+ str[len] = L'\0';
+ }
+
+ return git_win32__path_trim_end(str, len);
+}
diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
new file mode 100644
index 000000000..a1d388af5
--- /dev/null
+++ b/src/win32/w32_util.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_w32_util_h__
+#define INCLUDE_w32_util_h__
+
+#include "utf-conv.h"
+
+GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
+{
+ return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
+}
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__sethidden(const char *path);
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len);
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
+
+#endif
diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c
index 90e53c5e6..6f6143dad 100644
--- a/tests/clar_libgit2.c
+++ b/tests/clar_libgit2.c
@@ -59,47 +59,40 @@ void cl_git_rewritefile(const char *path, const char *content)
char *cl_getenv(const char *name)
{
- git_win32_path name_utf16;
- DWORD alloc_len;
- wchar_t *value_utf16;
- char *value_utf8;
+ wchar_t *wide_name, *wide_value;
+ char *utf8_value = NULL;
+ DWORD value_len;
- git_win32_path_from_c(name_utf16, name);
- alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0);
- if (alloc_len <= 0)
- return NULL;
+ cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
- cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t)));
+ value_len = GetEnvironmentVariableW(wide_name, NULL, 0);
- GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len);
-
- alloc_len = alloc_len * 4 + 1; /* worst case UTF16->UTF8 growth */
- cl_assert(value_utf8 = git__calloc(alloc_len, 1));
-
- git__utf16_to_8(value_utf8, alloc_len, value_utf16);
-
- git__free(value_utf16);
+ if (value_len) {
+ cl_assert(wide_value = git__malloc(value_len * sizeof(wchar_t)));
+ cl_assert(GetEnvironmentVariableW(wide_name, wide_value, value_len));
+ cl_assert(git__utf16_to_8_alloc(&utf8_value, wide_value) >= 0);
+ git__free(wide_value);
+ }
- return value_utf8;
+ git__free(wide_name);
+ return utf8_value;
}
int cl_setenv(const char *name, const char *value)
{
- git_win32_path name_utf16;
- git_win32_path value_utf16;
+ wchar_t *wide_name, *wide_value;
- git_win32_path_from_c(name_utf16, name);
+ cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
if (value) {
- git_win32_path_from_c(value_utf16, value);
- cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16));
+ cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
+ cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
} else {
/* Windows XP returns 0 (failed) when passing NULL for lpValue when
- * lpName does not exist in the environment block. This behavior
- * seems to have changed in later versions. Don't check return value
- * of SetEnvironmentVariable when passing NULL for lpValue.
- */
- SetEnvironmentVariableW(name_utf16, NULL);
+ * lpName does not exist in the environment block. This behavior
+ * seems to have changed in later versions. Don't check the return value
+ * of SetEnvironmentVariable when passing NULL for lpValue. */
+ SetEnvironmentVariableW(wide_name, NULL);
}
return 0;
@@ -115,8 +108,8 @@ int cl_rename(const char *source, const char *dest)
git_win32_path dest_utf16;
unsigned retries = 1;
- git_win32_path_from_c(source_utf16, source);
- git_win32_path_from_c(dest_utf16, dest);
+ cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0);
+ cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0);
while (!MoveFileW(source_utf16, dest_utf16)) {
/* Only retry if the error is ERROR_ACCESS_DENIED;
diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h
index d395bd66f..082fa9f4a 100644
--- a/tests/clar_libgit2.h
+++ b/tests/clar_libgit2.h
@@ -29,6 +29,17 @@
#define cl_git_fail_with(expr, error) cl_assert_equal_i(error,expr)
+/**
+ * Like cl_git_pass, only for Win32 error code conventions
+ */
+#define cl_win32_pass(expr) do { \
+ int _win32_res; \
+ if ((_win32_res = (expr)) == 0) { \
+ giterr_set(GITERR_OS, "Returned: %d, system error code: %d", _win32_res, GetLastError()); \
+ cl_git_report_failure(_win32_res, __FILE__, __LINE__, "System call failed: " #expr); \
+ } \
+ } while(0)
+
void cl_git_report_failure(int, const char *, int, const char *);
#define cl_assert_at_line(expr,file,line) \
diff --git a/tests/core/env.c b/tests/core/env.c
index b01ad1c24..df1d92a02 100644
--- a/tests/core/env.c
+++ b/tests/core/env.c
@@ -21,7 +21,7 @@ static char *home_values[] = {
"f\xc4\x80ke_\xc4\xa4ome", /* latin extended */
"f\xce\xb1\xce\xba\xce\xb5_h\xce\xbfm\xce\xad", /* having fun with greek */
"fa\xe0" "\xb8" "\x87" "e_\xe0" "\xb8" "\x99" "ome", /* thai characters */
- "f\xe1\x9cx80ke_\xe1\x9c\x91ome", /* tagalog characters */
+ "f\xe1\x9c\x80ke_\xe1\x9c\x91ome", /* tagalog characters */
"\xe1\xb8\x9f\xe1\xba\xa2" "ke_ho" "\xe1" "\xb9" "\x81" "e", /* latin extended additional */
"\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */
NULL
diff --git a/tests/core/link.c b/tests/core/link.c
new file mode 100644
index 000000000..1794a3893
--- /dev/null
+++ b/tests/core/link.c
@@ -0,0 +1,602 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "buffer.h"
+#include "path.h"
+
+#ifdef GIT_WIN32
+# include "win32/reparse.h"
+#endif
+
+void test_core_link__cleanup(void)
+{
+#ifdef GIT_WIN32
+ RemoveDirectory("lstat_junction");
+ RemoveDirectory("lstat_dangling");
+ RemoveDirectory("lstat_dangling_dir");
+ RemoveDirectory("lstat_dangling_junction");
+
+ RemoveDirectory("stat_junction");
+ RemoveDirectory("stat_dangling");
+ RemoveDirectory("stat_dangling_dir");
+ RemoveDirectory("stat_dangling_junction");
+#endif
+}
+
+#ifdef GIT_WIN32
+static bool is_administrator(void)
+{
+ static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
+ PSID admin_sid;
+ BOOL is_admin;
+
+ cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid));
+ cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin));
+ FreeSid(admin_sid);
+
+ return is_admin ? true : false;
+}
+#endif
+
+static void do_symlink(const char *old, const char *new, int is_dir)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(is_dir);
+
+ cl_must_pass(symlink(old, new));
+#else
+ typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
+ HMODULE module;
+ create_symlink_func pCreateSymbolicLink;
+
+ if (!is_administrator())
+ clar__skip();
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateSymbolicLink = (create_symlink_func)GetProcAddress(module, "CreateSymbolicLinkA"));
+
+ cl_win32_pass(pCreateSymbolicLink(new, old, is_dir));
+#endif
+}
+
+static void do_hardlink(const char *old, const char *new)
+{
+#ifndef GIT_WIN32
+ cl_must_pass(link(old, new));
+#else
+ typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES);
+ HMODULE module;
+ create_hardlink_func pCreateHardLink;
+
+ if (!is_administrator())
+ clar__skip();
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateHardLink = (create_hardlink_func)GetProcAddress(module, "CreateHardLinkA"));
+
+ cl_win32_pass(pCreateHardLink(new, old, 0));
+#endif
+}
+
+#ifdef GIT_WIN32
+
+static void do_junction(const char *old, const char *new)
+{
+ GIT_REPARSE_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ git_buf unparsed_buf = GIT_BUF_INIT;
+ wchar_t *subst_utf16, *print_utf16;
+ DWORD ioctl_ret;
+ int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
+ USHORT reparse_buflen;
+ size_t i;
+
+ /* Junction targets must be the unparsed name, starting with \??\, using
+ * backslashes instead of forward, and end in a trailing backslash.
+ * eg: \??\C:\Foo\
+ */
+ git_buf_puts(&unparsed_buf, "\\??\\");
+
+ for (i = 0; i < strlen(old); i++)
+ git_buf_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
+
+ git_buf_putc(&unparsed_buf, '\\');
+
+ subst_utf16_len = git__utf8_to_16(NULL, 0, git_buf_cstr(&unparsed_buf));
+ subst_byte_len = subst_utf16_len * sizeof(WCHAR);
+
+ print_utf16_len = subst_utf16_len - 4;
+ print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
+
+ /* The junction must be an empty directory before the junction attribute
+ * can be added.
+ */
+ cl_win32_pass(CreateDirectoryA(new, NULL));
+
+ handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE +
+ REPARSE_DATA_MOUNTPOINT_HEADER_SIZE +
+ subst_byte_len + sizeof(WCHAR) +
+ print_byte_len + sizeof(WCHAR));
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ subst_utf16 = reparse_buf->MountPointReparseBuffer.PathBuffer;
+ print_utf16 = subst_utf16 + subst_utf16_len + 1;
+
+ ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
+ git_buf_cstr(&unparsed_buf));
+ cl_assert_equal_i(subst_utf16_len, ret);
+
+ ret = git__utf8_to_16(print_utf16,
+ print_utf16_len + 1, git_buf_cstr(&unparsed_buf) + 4);
+ cl_assert_equal_i(print_utf16_len, ret);
+
+ reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ reparse_buf->MountPointReparseBuffer.SubstituteNameOffset = 0;
+ reparse_buf->MountPointReparseBuffer.SubstituteNameLength = subst_byte_len;
+ reparse_buf->MountPointReparseBuffer.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR));
+ reparse_buf->MountPointReparseBuffer.PrintNameLength = print_byte_len;
+ reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE;
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+}
+
+static void do_custom_reparse(const char *path)
+{
+ REPARSE_GUID_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ DWORD ioctl_ret;
+
+ const char *reparse_data = "Reparse points are silly.";
+ size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE +
+ strlen(reparse_data) + 1;
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ reparse_buf->ReparseTag = 42;
+ reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
+
+ reparse_buf->ReparseGuid.Data1 = 0xdeadbeef;
+ reparse_buf->ReparseGuid.Data2 = 0xdead;
+ reparse_buf->ReparseGuid.Data3 = 0xbeef;
+ reparse_buf->ReparseGuid.Data4[0] = 42;
+ reparse_buf->ReparseGuid.Data4[1] = 42;
+ reparse_buf->ReparseGuid.Data4[2] = 42;
+ reparse_buf->ReparseGuid.Data4[3] = 42;
+ reparse_buf->ReparseGuid.Data4[4] = 42;
+ reparse_buf->ReparseGuid.Data4[5] = 42;
+ reparse_buf->ReparseGuid.Data4[6] = 42;
+ reparse_buf->ReparseGuid.Data4[7] = 42;
+ reparse_buf->ReparseGuid.Data4[8] = 42;
+
+ memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
+ reparse_data, strlen(reparse_data) + 1);
+
+ handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf,
+ reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
+ NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+}
+
+#endif
+
+git_buf *unslashify(git_buf *buf)
+{
+#ifdef GIT_WIN32
+ size_t i;
+
+ for (i = 0; i < buf->size; i++)
+ if (buf->ptr[i] == '/')
+ buf->ptr[i] = '\\';
+#endif
+
+ return buf;
+}
+
+void test_core_link__stat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("stat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_core_link__lstat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("lstat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_core_link__stat_symlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
+ do_symlink("stat_target", "stat_symlink", 0);
+
+ cl_must_pass(p_stat("stat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_stat("stat_symlink", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_core_link__stat_symlink_directory(void)
+{
+ struct stat st;
+
+ p_mkdir("stat_dirtarget", 0777);
+ do_symlink("stat_dirtarget", "stat_dirlink", 1);
+
+ cl_must_pass(p_stat("stat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_dirlink", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+}
+
+void test_core_link__stat_symlink_chain(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
+ do_symlink("stat_final_target", "stat_chain_3", 0);
+ do_symlink("stat_chain_3", "stat_chain_2", 0);
+ do_symlink("stat_chain_2", "stat_chain_1", 0);
+
+ cl_must_pass(p_stat("stat_chain_1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_core_link__stat_dangling_symlink(void)
+{
+ struct stat st;
+
+ do_symlink("stat_nonexistent", "stat_dangling", 0);
+
+ cl_must_fail(p_stat("stat_nonexistent", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_core_link__stat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
+
+ cl_must_fail(p_stat("stat_nonexistent_dir", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_core_link__lstat_symlink(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ /* Windows always writes the canonical path as the link target, so
+ * write the full path on all platforms.
+ */
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
+
+ cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
+ do_symlink(git_buf_cstr(&target_path), "lstat_symlink", 0);
+
+ cl_must_pass(p_lstat("lstat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_symlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__lstat_symlink_directory(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
+
+ p_mkdir("lstat_dirtarget", 0777);
+ do_symlink(git_buf_cstr(&target_path), "lstat_dirlink", 1);
+
+ cl_must_pass(p_lstat("lstat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_dirlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__lstat_dangling_symlink(void)
+{
+ struct stat st;
+
+ do_symlink("lstat_nonexistent", "lstat_dangling", 0);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_core_link__lstat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_dir", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_core_link__stat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
+
+ p_mkdir("stat_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "stat_junction");
+
+ cl_must_pass(p_stat("stat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_junction", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__stat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
+
+ p_mkdir("stat_nonexistent_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "stat_dangling_junction");
+
+ RemoveDirectory("stat_nonexistent_junctarget");
+
+ cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
+ cl_must_fail(p_stat("stat_dangling_junction", &st));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__lstat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
+
+ p_mkdir("lstat_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "lstat_junction");
+
+ cl_must_pass(p_lstat("lstat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__lstat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
+
+ p_mkdir("lstat_nonexistent_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "lstat_dangling_junction");
+
+ RemoveDirectory("lstat_nonexistent_junctarget");
+
+ cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__stat_hardlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
+ do_hardlink("stat_hardlink1", "stat_hardlink2");
+
+ cl_must_pass(p_stat("stat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_stat("stat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_core_link__lstat_hardlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
+ do_hardlink("lstat_hardlink1", "lstat_hardlink2");
+
+ cl_must_pass(p_lstat("lstat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_core_link__stat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ /* Generic reparse points should be treated as regular files, only
+ * symlinks and junctions should be treated as links.
+ */
+
+ cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("stat_reparse");
+
+ cl_must_pass(p_lstat("stat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_core_link__lstat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("lstat_reparse");
+
+ cl_must_pass(p_lstat("lstat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_core_link__readlink_nonexistent_file(void)
+{
+ char buf[2048];
+
+ cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
+ cl_assert_equal_i(ENOENT, errno);
+}
+
+void test_core_link__readlink_normal_file(void)
+{
+ char buf[2048];
+
+ cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
+ cl_must_fail(p_readlink("readlink_regfile", buf, 2048));
+ cl_assert_equal_i(EINVAL, errno);
+}
+
+void test_core_link__readlink_symlink(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
+
+ cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
+ do_symlink(git_buf_cstr(&target_path), "readlink_link", 0);
+
+ len = p_readlink("readlink_link", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__readlink_dangling(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
+
+ do_symlink(git_buf_cstr(&target_path), "readlink_dangling", 0);
+
+ len = p_readlink("readlink_dangling", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__readlink_multiple(void)
+{
+ git_buf target_path = GIT_BUF_INIT,
+ path3 = GIT_BUF_INIT, path2 = GIT_BUF_INIT, path1 = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_final");
+ git_buf_join(&path3, '/', clar_sandbox_path(), "readlink_3");
+ git_buf_join(&path2, '/', clar_sandbox_path(), "readlink_2");
+ git_buf_join(&path1, '/', clar_sandbox_path(), "readlink_1");
+
+ do_symlink(git_buf_cstr(&target_path), git_buf_cstr(&path3), 0);
+ do_symlink(git_buf_cstr(&path3), git_buf_cstr(&path2), 0);
+ do_symlink(git_buf_cstr(&path2), git_buf_cstr(&path1), 0);
+
+ len = p_readlink("readlink_1", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf);
+
+ git_buf_free(&path1);
+ git_buf_free(&path2);
+ git_buf_free(&path3);
+ git_buf_free(&target_path);
+}
diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c
index 3260fdea8..18ad99bd5 100644
--- a/tests/diff/format_email.c
+++ b/tests/diff/format_email.c
@@ -17,15 +17,44 @@ void test_diff_format_email__cleanup(void)
cl_git_sandbox_cleanup();
}
-void test_diff_format_email__simple(void)
+static void assert_email_match(
+ const char *expected,
+ const char *oidstr,
+ git_diff_format_email_options *opts)
{
git_oid oid;
git_commit *commit = NULL;
git_diff *diff = NULL;
- git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
git_buf buf = GIT_BUF_INIT;
- const char *email =
+ git_oid_fromstr(&oid, oidstr);
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+
+ opts->id = git_commit_id(commit);
+ opts->author = git_commit_author(commit);
+ if (!opts->summary)
+ opts->summary = git_commit_summary(commit);
+
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_format_email(&buf, diff, opts));
+
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_diff_commit_as_email(
+ &buf, repo, commit, 1, 1, opts->flags, NULL));
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_format_email__simple(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
"From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
"Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \
@@ -64,25 +93,8 @@ void test_diff_format_email__simple(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
- git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
- opts.summary = git_commit_summary(commit);
-
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_buf_clear(&buf);
- cl_git_pass(git_diff_commit_as_email(&buf, repo, commit, 1, 1, 0, NULL));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
}
void test_diff_format_email__multiple(void)
@@ -90,10 +102,10 @@ void test_diff_format_email__multiple(void)
git_oid oid;
git_commit *commit = NULL;
git_diff *diff = NULL;
- git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
git_buf buf = GIT_BUF_INIT;
- const char *email =
+ const char *email =
"From 10808fe9c9be5a190c0ba68d1a002233fb363508 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
"Date: Thu, 10 Apr 2014 19:37:05 +0200\n" \
@@ -167,6 +179,7 @@ void test_diff_format_email__multiple(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
+
git_oid_fromstr(&oid, "10808fe9c9be5a190c0ba68d1a002233fb363508");
cl_git_pass(git_commit_lookup(&commit, repo, &oid));
@@ -196,7 +209,7 @@ void test_diff_format_email__multiple(void)
cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
+ cl_assert_equal_s(email, git_buf_cstr(&buf));
git_diff_free(diff);
git_commit_free(commit);
@@ -205,13 +218,8 @@ void test_diff_format_email__multiple(void)
void test_diff_format_email__exclude_marker(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- git_buf buf = GIT_BUF_INIT;
-
- const char *email =
+ const char *email =
"From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
"Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \
@@ -250,27 +258,10 @@ void test_diff_format_email__exclude_marker(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
- git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
- opts.summary = git_commit_summary(commit);
-
opts.flags |= GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER;
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_buf_clear(&buf);
- cl_git_pass(git_diff_commit_as_email(&buf, repo, commit, 1, 1,
- GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER, NULL));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
}
void test_diff_format_email__invalid_no(void)
@@ -303,13 +294,8 @@ void test_diff_format_email__invalid_no(void)
void test_diff_format_email__mode_change(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- git_buf buf = GIT_BUF_INIT;
-
- const char *email =
+ const char *email =
"From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
"Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \
@@ -330,36 +316,14 @@ void test_diff_format_email__mode_change(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
- git_oid_fromstr(&oid, "7ade76dd34bba4733cf9878079f9fd4a456a9189");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
- opts.summary = git_commit_summary(commit);
-
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_buf_clear(&buf);
- cl_git_pass(git_diff_commit_as_email(&buf, repo, commit, 1, 1, 0, NULL));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "7ade76dd34bba4733cf9878079f9fd4a456a9189", &opts);
}
void test_diff_format_email__rename_add_remove(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- git_buf buf = GIT_BUF_INIT;
-
- const char *email =
+ const char *email =
"From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
"Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \
@@ -422,35 +386,13 @@ void test_diff_format_email__rename_add_remove(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
- git_oid_fromstr(&oid, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
- opts.summary = git_commit_summary(commit);
-
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_buf_clear(&buf);
- cl_git_pass(git_diff_commit_as_email(&buf, repo, commit, 1, 1, 0, NULL));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts);
}
void test_diff_format_email__multiline_summary(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- git_buf buf = GIT_BUF_INIT;
-
const char *email =
"From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
@@ -490,36 +432,15 @@ void test_diff_format_email__multiline_summary(void)
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
- git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
opts.summary = "Modify some content\nSome extra stuff here";
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_buf_clear(&buf);
- cl_git_pass(git_diff_commit_as_email(&buf, repo, commit, 1, 1, 0, NULL));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
}
void test_diff_format_email__binary(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
- git_buf buf = GIT_BUF_INIT;
-
- /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
const char *email =
"From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \
"From: Jacques Germishuys <jacquesg@striata.com>\n" \
@@ -536,21 +457,11 @@ void test_diff_format_email__binary(void)
"--\n" \
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
+ /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
- git_oid_fromstr(&oid, "8d7523f6fcb2404257889abe0d96f093d9f524f9");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
-
- opts.id = git_commit_id(commit);
- opts.author = git_commit_author(commit);
opts.summary = "Modified binary file";
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_format_email(&buf, diff, &opts));
- cl_assert(strcmp(git_buf_cstr(&buf), email) == 0);
-
- git_diff_free(diff);
- git_commit_free(commit);
- git_buf_free(&buf);
+ assert_email_match(
+ email, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts);
}
diff --git a/tests/diff/stats.c b/tests/diff/stats.c
index 131b7681d..055019f69 100644
--- a/tests/diff/stats.c
+++ b/tests/diff/stats.c
@@ -5,246 +5,173 @@
#include "commit.h"
#include "diff.h"
-static git_repository *repo;
+static git_repository *_repo;
+static git_diff_stats *_stats;
void test_diff_stats__initialize(void)
{
- repo = cl_git_sandbox_init("diff_format_email");
+ _repo = cl_git_sandbox_init("diff_format_email");
}
void test_diff_stats__cleanup(void)
{
+ git_diff_stats_free(_stats); _stats = NULL;
cl_git_sandbox_cleanup();
}
-void test_diff_stats__stat(void)
+static void diff_stats_from_commit_oid(
+ git_diff_stats **stats, const char *oidstr, bool rename)
{
git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
- git_buf buf = GIT_BUF_INIT;
+ git_commit *commit;
+ git_diff *diff;
+
+ git_oid_fromstr(&oid, oidstr);
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, _repo, commit, NULL));
+ if (rename)
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+ cl_git_pass(git_diff_get_stats(stats, diff));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+}
+void test_diff_stats__stat(void)
+{
+ git_buf buf = GIT_BUF_INIT;
const char *stat =
" file1.txt | 8 +++++---\n" \
" 1 file changed, 5 insertions(+), 3 deletions(-)\n";
- git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
+ diff_stats_from_commit_oid(
+ &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false);
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(5, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(3, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 1);
- cl_assert(git_diff_stats_insertions(stats) == 5);
- cl_assert(git_diff_stats_deletions(stats) == 3);
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
git_buf_free(&buf);
}
void test_diff_stats__multiple_hunks(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt | 5 +++--\n" \
" file3.txt | 6 ++++--\n" \
" 2 files changed, 7 insertions(+), 4 deletions(-)\n";
- git_oid_fromstr(&oid, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ diff_stats_from_commit_oid(
+ &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false);
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 2);
- cl_assert(git_diff_stats_insertions(stats) == 7);
- cl_assert(git_diff_stats_deletions(stats) == 4);
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(7, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(4, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__numstat(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
"3 2 file2.txt\n"
"4 2 file3.txt\n";
- git_oid_fromstr(&oid, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
-
- cl_git_pass(git_diff_get_stats(&stats, diff));
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_NUMBER));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ diff_stats_from_commit_oid(
+ &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false);
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__shortstat(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" 1 file changed, 5 insertions(+), 3 deletions(-)\n";
- git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ diff_stats_from_commit_oid(
+ &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false);
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 1);
- cl_assert(git_diff_stats_insertions(stats) == 5);
- cl_assert(git_diff_stats_deletions(stats) == 3);
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(5, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(3, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_SHORT));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt => file2.txt.renamed | 1 +\n"
" file3.txt => file3.txt.renamed | 4 +++-\n"
- " 2 files changed, 4 insertions(+), 1 deletions(-)\n";
-
- git_oid_fromstr(&oid, "8947a46e2097638ca6040ad4877246f4186ec3bd");
+ " 2 files changed, 4 insertions(+), 1 deletion(-)\n";
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_find_similar(diff, NULL));
+ diff_stats_from_commit_oid(
+ &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", true);
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 2);
- cl_assert(git_diff_stats_insertions(stats) == 4);
- cl_assert(git_diff_stats_deletions(stats) == 1);
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(4, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename_nochanges(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt.renamed => file2.txt.renamed2 | 0\n"
" file3.txt.renamed => file3.txt.renamed2 | 0\n"
" 2 files changed, 0 insertions(+), 0 deletions(-)\n";
- git_oid_fromstr(&oid, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_find_similar(diff, NULL));
+ diff_stats_from_commit_oid(
+ &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", true);
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 2);
- cl_assert(git_diff_stats_insertions(stats) == 0);
- cl_assert(git_diff_stats_deletions(stats) == 0);
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename_and_modifiy(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt.renamed2 | 2 +-\n"
" file3.txt.renamed2 => file3.txt.renamed | 0\n"
- " 2 files changed, 1 insertions(+), 1 deletions(-)\n";
+ " 2 files changed, 1 insertion(+), 1 deletion(-)\n";
- git_oid_fromstr(&oid, "4ca10087e696d2ba78d07b146a118e9a7096ed4f");
+ diff_stats_from_commit_oid(
+ &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", true);
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
- cl_git_pass(git_diff_find_similar(diff, NULL));
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 2);
- cl_assert(git_diff_stats_insertions(stats) == 1);
- cl_assert(git_diff_stats_deletions(stats) == 1);
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename_no_find(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt | 5 -----\n"
" file2.txt.renamed | 6 ++++++\n"
@@ -252,33 +179,21 @@ void test_diff_stats__rename_no_find(void)
" file3.txt.renamed | 7 +++++++\n"
" 4 files changed, 13 insertions(+), 10 deletions(-)\n";
- git_oid_fromstr(&oid, "8947a46e2097638ca6040ad4877246f4186ec3bd");
+ diff_stats_from_commit_oid(
+ &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", false);
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(10, git_diff_stats_deletions(_stats));
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 4);
- cl_assert(git_diff_stats_insertions(stats) == 13);
- cl_assert(git_diff_stats_deletions(stats) == 10);
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename_nochanges_no_find(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt.renamed | 6 ------\n"
" file2.txt.renamed2 | 6 ++++++\n"
@@ -286,143 +201,85 @@ void test_diff_stats__rename_nochanges_no_find(void)
" file3.txt.renamed2 | 7 +++++++\n"
" 4 files changed, 13 insertions(+), 13 deletions(-)\n";
- git_oid_fromstr(&oid, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ diff_stats_from_commit_oid(
+ &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", false);
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 4);
- cl_assert(git_diff_stats_insertions(stats) == 13);
- cl_assert(git_diff_stats_deletions(stats) == 13);
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_deletions(_stats));
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__rename_and_modifiy_no_find(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file2.txt.renamed2 | 2 +-\n"
" file3.txt.renamed | 7 +++++++\n"
" file3.txt.renamed2 | 7 -------\n"
" 3 files changed, 8 insertions(+), 8 deletions(-)\n";
- git_oid_fromstr(&oid, "4ca10087e696d2ba78d07b146a118e9a7096ed4f");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
-
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 3);
- cl_assert(git_diff_stats_insertions(stats) == 8);
- cl_assert(git_diff_stats_deletions(stats) == 8);
+ diff_stats_from_commit_oid(
+ &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", false);
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ cl_assert_equal_sz(3, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(8, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(8, git_diff_stats_deletions(_stats));
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__binary(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
- /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
const char *stat =
" binary.bin | Bin 3 -> 0 bytes\n"
" 1 file changed, 0 insertions(+), 0 deletions(-)\n";
+ /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
- git_oid_fromstr(&oid, "8d7523f6fcb2404257889abe0d96f093d9f524f9");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
-
- cl_git_pass(git_diff_get_stats(&stats, diff));
- cl_assert(git_diff_stats_files_changed(stats) == 1);
- cl_assert(git_diff_stats_insertions(stats) == 0);
- cl_assert(git_diff_stats_deletions(stats) == 0);
+ diff_stats_from_commit_oid(
+ &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false);
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_deletions(_stats));
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__binary_numstat(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
"- - binary.bin\n";
- git_oid_fromstr(&oid, "8d7523f6fcb2404257889abe0d96f093d9f524f9");
-
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
-
- cl_git_pass(git_diff_get_stats(&stats, diff));
+ diff_stats_from_commit_oid(
+ &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false);
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_NUMBER));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
void test_diff_stats__mode_change(void)
{
- git_oid oid;
- git_commit *commit = NULL;
- git_diff *diff = NULL;
- git_diff_stats *stats = NULL;
git_buf buf = GIT_BUF_INIT;
-
const char *stat =
" file1.txt.renamed | 0\n" \
" 1 file changed, 0 insertions(+), 0 deletions(-)\n" \
- " mode change 100644 => 100755 file1.txt.renamed\n" \
- "\n";
+ " mode change 100644 => 100755 file1.txt.renamed\n";
- git_oid_fromstr(&oid, "7ade76dd34bba4733cf9878079f9fd4a456a9189");
+ diff_stats_from_commit_oid(
+ &_stats, "7ade76dd34bba4733cf9878079f9fd4a456a9189", false);
- cl_git_pass(git_commit_lookup(&commit, repo, &oid));
- cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
-
- cl_git_pass(git_diff_get_stats(&stats, diff));
-
- cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY));
- cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
-
- git_diff_stats_free(stats);
- git_diff_free(diff);
- git_commit_free(commit);
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
git_buf_free(&buf);
}
diff --git a/tests/reset/default.c b/tests/reset/default.c
index 57a3f7c51..ecb3e7f9f 100644
--- a/tests/reset/default.c
+++ b/tests/reset/default.c
@@ -21,7 +21,6 @@ static void initialize(const char *repo_name)
void test_reset_default__initialize(void)
{
- initialize("status");
}
void test_reset_default__cleanup(void)
@@ -67,6 +66,8 @@ void test_reset_default__resetting_filepaths_against_a_null_target_removes_them_
{
char *paths[] = { "staged_changes", "staged_new_file" };
+ initialize("status");
+
_pathspecs.strings = paths;
_pathspecs.count = 2;
@@ -102,6 +103,8 @@ void test_reset_default__resetting_filepaths_replaces_their_corresponding_index_
char *after_shas[] = { "32504b727382542f9f089e24fddac5e78533e96c",
"061d42a44cacde5726057b67558821d95db96f19" };
+ initialize("status");
+
_pathspecs.strings = paths;
_pathspecs.count = 2;
before.strings = before_shas;
@@ -139,7 +142,6 @@ void test_reset_default__resetting_filepaths_clears_previous_conflicts(void)
char *paths[] = { "conflicts-one.txt" };
char *after_shas[] = { "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" };
- test_reset_default__cleanup();
initialize("mergedrepo");
_pathspecs.strings = paths;
@@ -168,6 +170,8 @@ void test_reset_default__resetting_unknown_filepaths_does_not_fail(void)
{
char *paths[] = { "I_am_not_there.txt", "me_neither.txt" };
+ initialize("status");
+
_pathspecs.strings = paths;
_pathspecs.count = 2;
@@ -178,3 +182,34 @@ void test_reset_default__resetting_unknown_filepaths_does_not_fail(void)
assert_content_in_index(&_pathspecs, false, NULL);
}
+
+void test_reset_default__staged_rename_reset_delete(void)
+{
+ git_index *idx;
+ git_index_entry entry;
+ const git_index_entry *existing;
+ char *paths[] = { "new.txt" };
+
+ initialize("testrepo2");
+
+ cl_git_pass(git_repository_index(&idx, _repo));
+
+ existing = git_index_get_bypath(idx, "new.txt", 0);
+ cl_assert(existing);
+ memcpy(&entry, existing, sizeof(entry));
+
+ cl_git_pass(git_index_remove_bypath(idx, "new.txt"));
+
+ entry.path = "renamed.txt";
+ cl_git_pass(git_index_add(idx, &entry));
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 1;
+
+ assert_content_in_index(&_pathspecs, false, NULL);
+
+ cl_git_pass(git_revparse_single(&_target, _repo, "HEAD"));
+ cl_git_pass(git_reset_default(_repo, _target, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, true, NULL);
+}