diff options
| author | Ben Straub <bs@github.com> | 2013-10-28 11:04:58 -0700 |
|---|---|---|
| committer | Ben Straub <bs@github.com> | 2013-10-28 11:04:58 -0700 |
| commit | 42c8f8f807fe986534e0cbabbfabc32cb4eb9077 (patch) | |
| tree | fa76cb66e6d49855be344026b1cbe31d03962916 /src | |
| parent | a7d28f40a2a01382b76c55ca0a0672c177adaf69 (diff) | |
| parent | 5c50f22a93c78190fb7d81802199ff9defc8cf55 (diff) | |
| download | libgit2-42c8f8f807fe986534e0cbabbfabc32cb4eb9077.tar.gz | |
Merge remote-tracking branch 'libgit2/development' into blame
Diffstat (limited to 'src')
60 files changed, 2908 insertions, 1429 deletions
diff --git a/src/attr.c b/src/attr.c index 7946db4d6..98a328a55 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,3 +1,4 @@ +#include "common.h" #include "repository.h" #include "fileops.h" #include "config.h" diff --git a/src/checkout.c b/src/checkout.c index 5d741d393..662803725 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -26,6 +26,8 @@ #include "diff.h" #include "pathspec.h" #include "buf_text.h" +#include "merge_file.h" +#include "path.h" /* See docs/checkout-internals.md for more information */ @@ -35,21 +37,23 @@ enum { CHECKOUT_ACTION__UPDATE_BLOB = 2, CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__MAX = 8, - CHECKOUT_ACTION__DEFER_REMOVE = 16, + CHECKOUT_ACTION__UPDATE_CONFLICT = 16, + CHECKOUT_ACTION__MAX = 16, + CHECKOUT_ACTION__DEFER_REMOVE = 32, CHECKOUT_ACTION__REMOVE_AND_UPDATE = (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), }; typedef struct { git_repository *repo; - git_diff_list *diff; + git_diff *diff; git_checkout_opts opts; bool opts_free_baseline; char *pfx; git_index *index; git_pool pool; git_vector removes; + git_vector conflicts; git_buf path; size_t workdir_len; unsigned int strategy; @@ -59,6 +63,16 @@ typedef struct { size_t completed_steps; } checkout_data; +typedef struct { + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; + + int name_collision:1, + directoryfile:1, + one_to_two:1; +} checkout_conflictdata; + static int checkout_notify( checkout_data *data, git_checkout_notify_t why, @@ -592,6 +606,359 @@ static int checkout_remaining_wd_items( return error; } +GIT_INLINE(int) checkout_idxentry_cmp( + const git_index_entry *a, + const git_index_entry *b) +{ + if (!a && !b) + return 0; + else if (!a && b) + return -1; + else if(a && !b) + return 1; + else + return strcmp(a->path, b->path); +} + +static int checkout_conflictdata_cmp(const void *a, const void *b) +{ + const checkout_conflictdata *ca = a; + const checkout_conflictdata *cb = b; + int diff; + + if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && + (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) + diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); + + return diff; +} + +int checkout_conflictdata_empty(const git_vector *conflicts, size_t idx) +{ + checkout_conflictdata *conflict; + + if ((conflict = git_vector_get(conflicts, idx)) == NULL) + return -1; + + if (conflict->ancestor || conflict->ours || conflict->theirs) + return 0; + + git__free(conflict); + return 1; +} + +GIT_INLINE(bool) conflict_pathspec_match( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs) +{ + /* if the pathspec matches ours *or* theirs, proceed */ + if (ours && git_pathspec__match(pathspec, ours->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (theirs && git_pathspec__match(pathspec, theirs->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (ancestor && git_pathspec__match(pathspec, ancestor->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + return false; +} + +static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) +{ + git_index_conflict_iterator *iterator = NULL; + const git_index_entry *ancestor, *ours, *theirs; + checkout_conflictdata *conflict; + int error = 0; + + if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0) + goto done; + + data->conflicts._cmp = checkout_conflictdata_cmp; + + /* Collect the conflicts */ + while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { + if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) + continue; + + conflict = git__calloc(1, sizeof(checkout_conflictdata)); + GITERR_CHECK_ALLOC(conflict); + + conflict->ancestor = ancestor; + conflict->ours = ours; + conflict->theirs = theirs; + + git_vector_insert(&data->conflicts, conflict); + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_index_conflict_iterator_free(iterator); + + return error; +} + +GIT_INLINE(int) checkout_conflicts_cmp_entry( + const char *path, + const git_index_entry *entry) +{ + return strcmp((const char *)path, entry->path); +} + +static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) +{ + const char *path = p; + const checkout_conflictdata *conflict = c; + + if (!conflict->ancestor) + return 1; + + return checkout_conflicts_cmp_entry(path, conflict->ancestor); +} + +static checkout_conflictdata *checkout_conflicts_search_ancestor( + checkout_data *data, + const char *path) +{ + size_t pos; + + if (git_vector_bsearch2(&pos, &data->conflicts, checkout_conflicts_cmp_ancestor, path) < 0) + return NULL; + + return git_vector_get(&data->conflicts, pos); +} + +static checkout_conflictdata *checkout_conflicts_search_branch( + checkout_data *data, + const char *path) +{ + checkout_conflictdata *conflict; + size_t i; + + git_vector_foreach(&data->conflicts, i, conflict) { + int cmp = -1; + + if (conflict->ancestor) + break; + + if (conflict->ours) + cmp = checkout_conflicts_cmp_entry(path, conflict->ours); + else if (conflict->theirs) + cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); + + if (cmp == 0) + return conflict; + } + + return NULL; +} + +static int checkout_conflicts_load_byname_entry( + checkout_conflictdata **ancestor_out, + checkout_conflictdata **ours_out, + checkout_conflictdata **theirs_out, + checkout_data *data, + const git_index_name_entry *name_entry) +{ + checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; + int error = 0; + + *ancestor_out = NULL; + *ours_out = NULL; + *theirs_out = NULL; + + if (!name_entry->ancestor) { + giterr_set(GITERR_INDEX, "A NAME entry exists without an ancestor"); + error = -1; + goto done; + } + + if (!name_entry->ours && !name_entry->theirs) { + giterr_set(GITERR_INDEX, "A NAME entry exists without an ours or theirs"); + error = -1; + goto done; + } + + if ((ancestor = checkout_conflicts_search_ancestor(data, + name_entry->ancestor)) == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced ancestor entry '%s' which does not exist in the main index", + name_entry->ancestor); + error = -1; + goto done; + } + + if (name_entry->ours) { + if (strcmp(name_entry->ancestor, name_entry->ours) == 0) + ours = ancestor; + else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || + ours->ours == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced our entry '%s' which does not exist in the main index", + name_entry->ours); + error = -1; + goto done; + } + } + + if (name_entry->theirs) { + if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) + theirs = ancestor; + else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) + theirs = ours; + else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || + theirs->theirs == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced their entry '%s' which does not exist in the main index", + name_entry->theirs); + error = -1; + goto done; + } + } + + *ancestor_out = ancestor; + *ours_out = ours; + *theirs_out = theirs; + +done: + return error; +} + +static int checkout_conflicts_coalesce_renames( + checkout_data *data) +{ + const git_index_name_entry *name_entry; + checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; + size_t i, names; + int error = 0; + + /* Juggle entries based on renames */ + names = git_index_name_entrycount(data->index); + + for (i = 0; i < names; i++) { + name_entry = git_index_name_get_byindex(data->index, i); + + if ((error = checkout_conflicts_load_byname_entry( + &ancestor_conflict, &our_conflict, &their_conflict, + data, name_entry)) < 0) + goto done; + + if (our_conflict && our_conflict != ancestor_conflict) { + ancestor_conflict->ours = our_conflict->ours; + our_conflict->ours = NULL; + + if (our_conflict->theirs) + our_conflict->name_collision = 1; + + if (our_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (their_conflict && their_conflict != ancestor_conflict) { + ancestor_conflict->theirs = their_conflict->theirs; + their_conflict->theirs = NULL; + + if (their_conflict->ours) + their_conflict->name_collision = 1; + + if (their_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (our_conflict && our_conflict != ancestor_conflict && + their_conflict && their_conflict != ancestor_conflict) + ancestor_conflict->one_to_two = 1; + } + + git_vector_remove_matching(&data->conflicts, checkout_conflictdata_empty); + +done: + return error; +} + +static int checkout_conflicts_mark_directoryfile( + checkout_data *data) +{ + checkout_conflictdata *conflict; + const git_index_entry *entry; + size_t i, j, len; + const char *path; + int prefixed, error = 0; + + len = git_index_entrycount(data->index); + + /* Find d/f conflicts */ + git_vector_foreach(&data->conflicts, i, conflict) { + if ((conflict->ours && conflict->theirs) || + (!conflict->ours && !conflict->theirs)) + continue; + + path = conflict->ours ? + conflict->ours->path : conflict->theirs->path; + + if ((error = git_index_find(&j, data->index, path)) < 0) { + if (error == GIT_ENOTFOUND) + giterr_set(GITERR_INDEX, + "Index inconsistency, could not find entry for expected conflict '%s'", path); + + goto done; + } + + for (; j < len; j++) { + if ((entry = git_index_get_byindex(data->index, j)) == NULL) { + giterr_set(GITERR_INDEX, + "Index inconsistency, truncated index while loading expected conflict '%s'", path); + error = -1; + goto done; + } + + prefixed = git_path_equal_or_prefixed(path, entry->path); + + if (prefixed == GIT_PATH_EQUAL) + continue; + + if (prefixed == GIT_PATH_PREFIX) + conflict->directoryfile = 1; + + break; + } + } + +done: + return error; +} + +static int checkout_get_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + int error = 0; + + if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) + return 0; + + if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || + (error = checkout_conflicts_coalesce_renames(data)) < 0 || + (error = checkout_conflicts_mark_directoryfile(data)) < 0) + goto done; + +done: + return error; +} + static int checkout_get_actions( uint32_t **actions_ptr, size_t **counts_ptr, @@ -659,6 +1026,12 @@ static int checkout_get_actions( goto fail; } + + if ((error = checkout_get_conflicts(data, workdir, &pathspec)) < 0) + goto fail; + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); @@ -707,6 +1080,7 @@ static int blob_content_to_file( struct stat *st, git_blob *blob, const char *path, + const char * hint_path, mode_t entry_filemode, git_checkout_opts *opts) { @@ -715,9 +1089,12 @@ static int blob_content_to_file( git_buf out = GIT_BUF_INIT; git_filter_list *fl = NULL; + if (hint_path == NULL) + hint_path = path; + if (!opts->disable_filters) error = git_filter_list_load( - &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE); + &fl, git_blob_owner(blob), blob, hint_path, GIT_FILTER_TO_WORKTREE); if (!error) error = git_filter_list_apply_to_blob(&out, fl, blob); @@ -783,7 +1160,8 @@ static int checkout_update_index( memset(&entry, 0, sizeof(entry)); entry.path = (char *)file->path; /* cast to prevent warning */ - git_index_entry__init_from_stat(&entry, st); + git_index_entry__init_from_stat( + &entry, st, !(git_index_caps(data->index) & GIT_INDEXCAP_NO_FILEMODE)); git_oid_cpy(&entry.oid, &file->oid); return git_index_add(data->index, &entry); @@ -885,34 +1263,26 @@ static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) return 0; } -static int checkout_blob( +static int checkout_write_content( checkout_data *data, - const git_diff_file *file) + const git_oid *oid, + const char *full_path, + const char *hint_path, + unsigned int mode, + struct stat *st) { int error = 0; git_blob *blob; - struct stat st; - - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, file->path) < 0) - return -1; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( - git_buf_cstr(&data->path), file->mode); - if (rval <= 0) - return rval; - } - if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) + if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) return error; - if (S_ISLNK(file->mode)) + if (S_ISLNK(mode)) error = blob_content_to_link( - &st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink); + st, blob, full_path, data->opts.dir_mode, data->can_symlink); else error = blob_content_to_file( - &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts); + st, blob, full_path, hint_path, mode, &data->opts); git_blob_free(blob); @@ -927,6 +1297,30 @@ static int checkout_blob( error = 0; } + return error; +} + +static int checkout_blob( + checkout_data *data, + const git_diff_file *file) +{ + int error = 0; + struct stat st; + + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, file->path) < 0) + return -1; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { + int rval = checkout_safe_for_update_only( + git_buf_cstr(&data->path), file->mode); + if (rval <= 0) + return rval; + } + + error = checkout_write_content( + data, &file->oid, git_buf_cstr(&data->path), NULL, file->mode, &st); + /* update the index unless prevented */ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) error = checkout_update_index(data, file, &st); @@ -1097,8 +1491,279 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) return error; } + +static int conflict_entry_name( + git_buf *out, + const char *side_name, + const char *filename) +{ + if (git_buf_puts(out, side_name) < 0 || + git_buf_putc(out, ':') < 0 || + git_buf_puts(out, filename) < 0) + return -1; + + return 0; +} + +static int checkout_path_suffixed(git_buf *path, const char *suffix) +{ + size_t path_len; + int i = 0, error = 0; + + if ((error = git_buf_putc(path, '~')) < 0 || (error = git_buf_puts(path, suffix)) < 0) + return -1; + + path_len = git_buf_len(path); + + while (git_path_exists(git_buf_cstr(path)) && i < INT_MAX) { + git_buf_truncate(path, path_len); + + if ((error = git_buf_putc(path, '_')) < 0 || + (error = git_buf_printf(path, "%d", i)) < 0) + return error; + + i++; + } + + if (i == INT_MAX) { + git_buf_truncate(path, path_len); + + giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path); + return GIT_EEXISTS; + } + + return 0; +} + +static int checkout_write_entry( + checkout_data *data, + checkout_conflictdata *conflict, + const git_index_entry *side) +{ + const char *hint_path = NULL, *suffix; + struct stat st; + int error; + + assert (side == conflict->ours || side == conflict->theirs); + + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, side->path) < 0) + return -1; + + if ((conflict->name_collision || conflict->directoryfile) && + (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && + (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { + + if (side == conflict->ours) + suffix = data->opts.our_label ? data->opts.our_label : + "ours"; + else + suffix = data->opts.their_label ? data->opts.their_label : + "theirs"; + + if (checkout_path_suffixed(&data->path, suffix) < 0) + return -1; + + hint_path = side->path; + } + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + return error; + + return checkout_write_content(data, + &side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st); +} + +static int checkout_write_entries( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) + error = checkout_write_entry(data, conflict, conflict->theirs); + + return error; +} + +static int checkout_merge_path( + git_buf *out, + checkout_data *data, + checkout_conflictdata *conflict, + git_merge_file_result *result) +{ + const char *our_label_raw, *their_label_raw, *suffix; + int error = 0; + + if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0) + return error; + + /* Most conflicts simply use the filename in the index */ + if (!conflict->name_collision) + return 0; + + /* Rename 2->1 conflicts need the branch name appended */ + our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; + their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; + suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; + + if ((error = checkout_path_suffixed(out, suffix)) < 0) + return error; + + return 0; +} + +static int checkout_write_merge( + checkout_data *data, + checkout_conflictdata *conflict) +{ + git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT, + path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT; + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_result result = GIT_MERGE_FILE_RESULT_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + int error = 0; + + if ((conflict->ancestor && + (error = git_merge_file_input_from_index_entry( + &ancestor, data->repo, conflict->ancestor)) < 0) || + (error = git_merge_file_input_from_index_entry( + &ours, data->repo, conflict->ours)) < 0 || + (error = git_merge_file_input_from_index_entry( + &theirs, data->repo, conflict->theirs)) < 0) + goto done; + + ancestor.label = NULL; + ours.label = data->opts.our_label ? data->opts.our_label : "ours"; + theirs.label = data->opts.their_label ? data->opts.their_label : "theirs"; + + /* If all the paths are identical, decorate the diff3 file with the branch + * names. Otherwise, append branch_name:path. + */ + if (conflict->ours && conflict->theirs && + strcmp(conflict->ours->path, conflict->theirs->path) != 0) { + + if ((error = conflict_entry_name( + &our_label, ours.label, conflict->ours->path)) < 0 || + (error = conflict_entry_name( + &their_label, theirs.label, conflict->theirs->path)) < 0) + goto done; + + ours.label = git_buf_cstr(&our_label); + theirs.label = git_buf_cstr(&their_label); + } + + if ((error = git_merge_files(&result, &ancestor, &ours, &theirs, 0)) < 0) + goto done; + + if (result.path == NULL || result.mode == 0) { + giterr_set(GITERR_CHECKOUT, "Could not merge contents of file"); + error = GIT_EMERGECONFLICT; + goto done; + } + + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0) + goto done; + + if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || + (error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER)) < 0 || + (error = git_filebuf_write(&output, result.data, result.len)) < 0 || + (error = git_filebuf_commit(&output, result.mode)) < 0) + goto done; + +done: + git_buf_free(&our_label); + git_buf_free(&their_label); + + git_merge_file_input_free(&ancestor); + git_merge_file_input_free(&ours); + git_merge_file_input_free(&theirs); + git_merge_file_result_free(&result); + git_buf_free(&path_workdir); + git_buf_free(&path_suffixed); + + return error; +} + +static int checkout_create_conflicts(checkout_data *data) +{ + git_vector conflicts = GIT_VECTOR_INIT; + checkout_conflictdata *conflict; + size_t i; + int error = 0; + + git_vector_foreach(&data->conflicts, i, conflict) { + /* Both deleted: nothing to do */ + if (conflict->ours == NULL && conflict->theirs == NULL) + error = 0; + + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + conflict->ours) + error = checkout_write_entry(data, conflict, conflict->ours); + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + conflict->theirs) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Ignore the other side of name collisions. */ + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + !conflict->ours && conflict->name_collision) + error = 0; + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + !conflict->theirs && conflict->name_collision) + error = 0; + + /* For modify/delete, name collisions and d/f conflicts, write + * the file (potentially with the name mangled. + */ + else if (conflict->ours != NULL && conflict->theirs == NULL) + error = checkout_write_entry(data, conflict, conflict->ours); + else if (conflict->ours == NULL && conflict->theirs != NULL) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Add/add conflicts and rename 1->2 conflicts, write the + * ours/theirs sides (potentially name mangled). + */ + else if (conflict->one_to_two) + error = checkout_write_entries(data, conflict); + + /* If all sides are links, write the ours side */ + else if (S_ISLNK(conflict->ours->mode) && + S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + /* Link/file conflicts, write the file side */ + else if (S_ISLNK(conflict->ours->mode)) + error = checkout_write_entry(data, conflict, conflict->theirs); + else if (S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + + else + error = checkout_write_merge(data, conflict); + + if (error) + break; + + data->completed_steps++; + report_progress(data, + conflict->ours ? conflict->ours->path : + (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); + } + + return error; +} + + static void checkout_data_clear(checkout_data *data) { + checkout_conflictdata *conflict; + size_t i; + if (data->opts_free_baseline) { git_tree_free(data->opts.baseline); data->opts.baseline = NULL; @@ -1107,6 +1772,11 @@ static void checkout_data_clear(checkout_data *data) git_vector_free(&data->removes); git_pool_clear(&data->pool); + git_vector_foreach(&data->conflicts, i, conflict) + git__free(conflict); + + git_vector_free(&data->conflicts); + git__free(data->pfx); data->pfx = NULL; @@ -1171,7 +1841,17 @@ static int checkout_data_init( (error = git_index_read(data->index)) < 0) goto cleanup; - /* clear the REUC when doing a tree or commit checkout */ + /* cannot checkout if unresolved conflicts exist */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 && + git_index_has_conflicts(data->index)) { + error = GIT_EMERGECONFLICT; + giterr_set(GITERR_CHECKOUT, + "unresolved conflicts exist in the index"); + goto cleanup; + } + + /* clean conflict data when doing a tree or commit checkout */ + git_index_name_clear(data->index); git_index_reuc_clear(data->index); } } @@ -1213,6 +1893,7 @@ static int checkout_data_init( } if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || + (error = git_vector_init(&data->conflicts, 0, NULL)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 || (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || (error = git_path_to_dir(&data->path)) < 0) @@ -1283,14 +1964,16 @@ int git_checkout_iterator( goto cleanup; /* Loop through diff (and working directory iterator) building a list of - * actions to be taken, plus look for conflicts and send notifications. + * actions to be taken, plus look for conflicts and send notifications, + * then loop through conflicts. */ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0) goto cleanup; data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + counts[CHECKOUT_ACTION__UPDATE_BLOB] + - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; report_progress(&data, NULL); /* establish 0 baseline */ @@ -1309,6 +1992,10 @@ int git_checkout_iterator( (error = checkout_create_submodules(actions, &data)) < 0) goto cleanup; + if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && + (error = checkout_create_conflicts(&data)) < 0) + goto cleanup; + assert(data.completed_steps == data.total_steps); cleanup: @@ -1319,7 +2006,7 @@ cleanup: (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) error = git_index_write(data.index); - git_diff_list_free(data.diff); + git_diff_free(data.diff); git_iterator_free(workdir); git_iterator_free(baseline); git__free(actions); diff --git a/src/clone.c b/src/clone.c index f3e365c07..657243945 100644 --- a/src/clone.c +++ b/src/clone.c @@ -391,11 +391,11 @@ int git_clone( const char *local_path, const git_clone_options *_options) { - int retcode = GIT_ERROR; + int error = 0; git_repository *repo = NULL; git_remote *origin; git_clone_options options = GIT_CLONE_OPTIONS_INIT; - int remove_directory_on_failure = 0; + uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; assert(out && url && local_path); @@ -408,33 +408,29 @@ int git_clone( if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) { giterr_set(GITERR_INVALID, "'%s' exists and is not an empty directory", local_path); - return GIT_ERROR; + return GIT_EEXISTS; } - /* Only remove the directory on failure if we create it */ - remove_directory_on_failure = !git_path_exists(local_path); + /* Only remove the root directory on failure if we create it */ + if (git_path_exists(local_path)) + rmdir_flags |= GIT_RMDIR_SKIP_ROOT; - if ((retcode = git_repository_init(&repo, local_path, options.bare)) < 0) - return retcode; + if ((error = git_repository_init(&repo, local_path, options.bare)) < 0) + return error; - if ((retcode = create_and_configure_origin(&origin, repo, url, &options)) < 0) - goto cleanup; + if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { + error = git_clone_into( + repo, origin, &options.checkout_opts, options.checkout_branch); - retcode = git_clone_into(repo, origin, &options.checkout_opts, options.checkout_branch); - git_remote_free(origin); + git_remote_free(origin); + } - if (retcode < 0) - goto cleanup; + if (error < 0) { + git_repository_free(repo); + repo = NULL; + (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); + } *out = repo; - return 0; - -cleanup: - git_repository_free(repo); - if (remove_directory_on_failure) - git_futils_rmdir_r(local_path, NULL, GIT_RMDIR_REMOVE_FILES); - else - git_futils_cleanupdir_r(local_path); - - return retcode; + return error; } diff --git a/src/config.h b/src/config.h index 85db5e3e1..01e8465cc 100644 --- a/src/config.h +++ b/src/config.h @@ -47,7 +47,7 @@ extern int git_config_rename_section( * @param out the new backend * @param path where the config file is located */ -extern int git_config_file__ondisk(struct git_config_backend **out, const char *path); +extern int git_config_file__ondisk(git_config_backend **out, const char *path); extern int git_config__normalize_name(const char *in, char **out); diff --git a/src/config_cache.c b/src/config_cache.c index 84de3a5ed..6808521a3 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -67,6 +67,7 @@ static struct map_data _cvar_maps[] = { {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, + {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, }; int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) diff --git a/src/diff.c b/src/diff.c index 39facce60..37bc737d6 100644 --- a/src/diff.c +++ b/src/diff.c @@ -21,7 +21,7 @@ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL)) static git_diff_delta *diff_delta__alloc( - git_diff_list *diff, + git_diff *diff, git_delta_t status, const char *path) { @@ -50,7 +50,7 @@ static git_diff_delta *diff_delta__alloc( } static int diff_notify( - const git_diff_list *diff, + const git_diff *diff, const git_diff_delta *delta, const char *matched_pathspec) { @@ -62,7 +62,7 @@ static int diff_notify( } static int diff_delta__from_one( - git_diff_list *diff, + git_diff *diff, git_delta_t status, const git_index_entry *entry) { @@ -81,7 +81,7 @@ static int diff_delta__from_one( if (!git_pathspec__match( &diff->pathspec, entry->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), &matched_pathspec, NULL)) return 0; @@ -90,6 +90,7 @@ static int diff_delta__from_one( /* This fn is just for single-sided diffs */ assert(status != GIT_DELTA_MODIFIED); + delta->nfiles = 1; if (delta->status == GIT_DELTA_DELETED) { delta->old_file.mode = entry->mode; @@ -120,7 +121,7 @@ static int diff_delta__from_one( } static int diff_delta__from_two( - git_diff_list *diff, + git_diff *diff, git_delta_t status, const git_index_entry *old_entry, uint32_t old_mode, @@ -148,6 +149,7 @@ static int diff_delta__from_two( delta = diff_delta__alloc(diff, status, canonical_path); GITERR_CHECK_ALLOC(delta); + delta->nfiles = 2; git_oid_cpy(&delta->old_file.oid, &old_entry->oid); delta->old_file.size = old_entry->file_size; @@ -181,7 +183,7 @@ static int diff_delta__from_two( } static git_diff_delta *diff_delta__last_for_item( - git_diff_list *diff, + git_diff *diff, const git_index_entry *item) { git_diff_delta *delta = git_vector_last(&diff->deltas); @@ -340,13 +342,13 @@ static const char *diff_mnemonic_prefix( return pfx; } -static git_diff_list *diff_list_alloc( +static git_diff *diff_list_alloc( git_repository *repo, git_iterator *old_iter, git_iterator *new_iter) { git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); + git_diff *diff = git__calloc(1, sizeof(git_diff)); if (!diff) return NULL; @@ -360,7 +362,7 @@ static git_diff_list *diff_list_alloc( if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 || git_pool_init(&diff->pool, 1, 0) < 0) { - git_diff_list_free(diff); + git_diff_free(diff); return NULL; } @@ -368,14 +370,14 @@ static git_diff_list *diff_list_alloc( * the ignore_case bit set */ if (!git_iterator_ignore_case(old_iter) && !git_iterator_ignore_case(new_iter)) { - diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; + diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; diff->strcomp = git__strcmp; diff->strncomp = git__strncmp; diff->pfxcomp = git__prefixcmp; diff->entrycomp = git_index_entry__cmp; } else { - diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + diff->opts.flags |= GIT_DIFF_IGNORE_CASE; diff->strcomp = git__strcasecmp; diff->strncomp = git__strncasecmp; @@ -389,7 +391,7 @@ static git_diff_list *diff_list_alloc( } static int diff_list_apply_options( - git_diff_list *diff, + git_diff *diff, const git_diff_options *opts) { git_config *cfg; @@ -399,9 +401,9 @@ static int diff_list_apply_options( if (opts) { /* copy user options (except case sensitivity info from iterators) */ - bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE); + bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); memcpy(&diff->opts, opts, sizeof(diff->opts)); - DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase); + DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); /* initialize pathspec from options */ if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) @@ -413,7 +415,7 @@ static int diff_list_apply_options( diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT)) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* load config values that affect diff behavior */ @@ -490,7 +492,7 @@ static int diff_list_apply_options( return 0; } -static void diff_list_free(git_diff_list *diff) +static void diff_list_free(git_diff *diff) { git_diff_delta *delta; unsigned int i; @@ -508,7 +510,7 @@ static void diff_list_free(git_diff_list *diff) git__free(diff); } -void git_diff_list_free(git_diff_list *diff) +void git_diff_free(git_diff *diff) { if (!diff) return; @@ -516,7 +518,7 @@ void git_diff_list_free(git_diff_list *diff) GIT_REFCOUNT_DEC(diff, diff_list_free); } -void git_diff_list_addref(git_diff_list *diff) +void git_diff_addref(git_diff *diff) { GIT_REFCOUNT_INC(diff); } @@ -612,7 +614,7 @@ typedef struct { static int maybe_modified_submodule( git_delta_t *status, git_oid *found_oid, - git_diff_list *diff, + git_diff *diff, diff_in_progress *info) { int error = 0; @@ -659,7 +661,7 @@ static int maybe_modified_submodule( } static int maybe_modified( - git_diff_list *diff, + git_diff *diff, diff_in_progress *info) { git_oid noid; @@ -674,7 +676,7 @@ static int maybe_modified( if (!git_pathspec__match( &diff->pathspec, oitem->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), &matched_pathspec, NULL)) return 0; @@ -778,7 +780,7 @@ static int maybe_modified( } static bool entry_is_prefixed( - git_diff_list *diff, + git_diff *diff, const git_index_entry *item, const git_index_entry *prefix_item) { @@ -795,7 +797,7 @@ static bool entry_is_prefixed( } static int diff_scan_inside_untracked_dir( - git_diff_list *diff, diff_in_progress *info, git_delta_t *delta_type) + git_diff *diff, diff_in_progress *info, git_delta_t *delta_type) { int error = 0; git_buf base = GIT_BUF_INIT; @@ -861,7 +863,7 @@ done: } static int handle_unmatched_new_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = 0; const git_index_entry *nitem = info->nitem; @@ -910,7 +912,7 @@ static int handle_unmatched_new_item( */ if (!recurse_into_dir && delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_FAST_UNTRACKED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) { git_diff_delta *last; @@ -1016,7 +1018,7 @@ static int handle_unmatched_new_item( } static int handle_unmatched_old_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem); if (error < 0) @@ -1048,7 +1050,7 @@ static int handle_unmatched_old_item( } static int handle_matched_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = 0; @@ -1063,7 +1065,7 @@ static int handle_matched_item( } int git_diff__from_iterators( - git_diff_list **diff_ptr, + git_diff **diff_ptr, git_repository *repo, git_iterator *old_iter, git_iterator *new_iter, @@ -1071,7 +1073,7 @@ int git_diff__from_iterators( { int error = 0; diff_in_progress info; - git_diff_list *diff; + git_diff *diff; *diff_ptr = NULL; @@ -1084,7 +1086,7 @@ int git_diff__from_iterators( git_buf_init(&info.ignore_prefix, 0); /* make iterators have matching icase behavior */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || (error = git_iterator_set_ignore_case(new_iter, true)) < 0) goto cleanup; @@ -1132,7 +1134,7 @@ cleanup: if (!error) *diff_ptr = diff; else - git_diff_list_free(diff); + git_diff_free(diff); git_buf_free(&info.ignore_prefix); @@ -1149,7 +1151,7 @@ cleanup: } while (0) int git_diff_tree_to_tree( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_tree *new_tree, @@ -1164,7 +1166,7 @@ int git_diff_tree_to_tree( * currently case insensitive, unless the user explicitly asked * for case insensitivity */ - if (opts && (opts->flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) + if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) iflag = GIT_ITERATOR_IGNORE_CASE; DIFF_FROM_ITERATORS( @@ -1176,7 +1178,7 @@ int git_diff_tree_to_tree( } int git_diff_tree_to_index( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, @@ -1204,9 +1206,9 @@ int git_diff_tree_to_index( git_index__set_ignore_case(index, true); if (!error) { - git_diff_list *d = *diff; + git_diff *d = *diff; - d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + d->opts.flags |= GIT_DIFF_IGNORE_CASE; d->strcomp = git__strcasecmp; d->strncomp = git__strncasecmp; d->pfxcomp = git__prefixcmp_icase; @@ -1221,7 +1223,7 @@ int git_diff_tree_to_index( } int git_diff_index_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts) @@ -1242,9 +1244,8 @@ int git_diff_index_to_workdir( return error; } - int git_diff_tree_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, const git_diff_options *opts) @@ -1262,16 +1263,43 @@ int git_diff_tree_to_workdir( return error; } -size_t git_diff_num_deltas(git_diff_list *diff) +int git_diff_tree_to_workdir_with_index( + git_diff **diff, + git_repository *repo, + git_tree *old_tree, + const git_diff_options *opts) +{ + int error = 0; + git_diff *d1 = NULL, *d2 = NULL; + + assert(diff && repo); + + if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, NULL, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, NULL, opts))) + error = git_diff_merge(d1, d2); + + git_diff_free(d2); + + if (error) { + git_diff_free(d1); + d1 = NULL; + } + + *diff = d1; + return error; +} + + +size_t git_diff_num_deltas(const git_diff *diff) { assert(diff); - return (size_t)diff->deltas.length; + return diff->deltas.length; } -size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) +size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) { size_t i, count = 0; - git_diff_delta *delta; + const git_diff_delta *delta; assert(diff); @@ -1282,14 +1310,20 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) return count; } -int git_diff_is_sorted_icase(const git_diff_list *diff) +const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) +{ + assert(diff); + return git_vector_get(&diff->deltas, idx); +} + +int git_diff_is_sorted_icase(const git_diff *diff) { - return (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; + return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; } int git_diff__paired_foreach( - git_diff_list *head2idx, - git_diff_list *idx2wd, + git_diff *head2idx, + git_diff *idx2wd, int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), void *payload) { @@ -1318,10 +1352,10 @@ int git_diff__paired_foreach( * always sort by the old name in the i2w list. */ h2i_icase = head2idx != NULL && - (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; + (head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; i2w_icase = idx2wd != NULL && - (idx2wd->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; + (idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; icase_mismatch = (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); diff --git a/src/diff.h b/src/diff.h index bec7e27d7..270bea071 100644 --- a/src/diff.h +++ b/src/diff.h @@ -52,7 +52,7 @@ enum { #define GIT_DIFF__VERBOSE (1 << 30) -struct git_diff_list { +struct git_diff { git_refcount rc; git_repository *repo; git_diff_options opts; @@ -72,7 +72,7 @@ struct git_diff_list { extern void git_diff__cleanup_modes( uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); -extern void git_diff_list_addref(git_diff_list *diff); +extern void git_diff_addref(git_diff *diff); extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__casecmp(const void *a, const void *b); @@ -93,15 +93,15 @@ extern int git_diff__oid_for_file( git_repository *, const char *, uint16_t, git_off_t, git_oid *); extern int git_diff__from_iterators( - git_diff_list **diff_ptr, + git_diff **diff_ptr, git_repository *repo, git_iterator *old_iter, git_iterator *new_iter, const git_diff_options *opts); extern int git_diff__paired_foreach( - git_diff_list *idx2head, - git_diff_list *wd2idx, + git_diff *idx2head, + git_diff *wd2idx, int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), void *payload); diff --git a/src/diff_file.c b/src/diff_file.c index 5939ee8b8..a4c8641bc 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -88,7 +88,7 @@ static int diff_file_content_init_common( int git_diff_file_content__init_from_diff( git_diff_file_content *fc, - git_diff_list *diff, + git_diff *diff, size_t delta_index, bool use_old) { @@ -110,7 +110,7 @@ int git_diff_file_content__init_from_diff( has_data = use_old; break; case GIT_DELTA_UNTRACKED: has_data = !use_old && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) != 0; + (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; break; case GIT_DELTA_MODIFIED: case GIT_DELTA_COPIED: diff --git a/src/diff_file.h b/src/diff_file.h index fb08cca6a..84bf255aa 100644 --- a/src/diff_file.h +++ b/src/diff_file.h @@ -27,7 +27,7 @@ typedef struct { extern int git_diff_file_content__init_from_diff( git_diff_file_content *fc, - git_diff_list *diff, + git_diff *diff, size_t delta_index, bool use_old); diff --git a/src/diff_patch.c b/src/diff_patch.c index cc45b6ddb..cc49d68eb 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -12,36 +12,24 @@ #include "diff_xdiff.h" #include "fileops.h" -/* cached information about a single span in a diff */ -typedef struct diff_patch_line diff_patch_line; -struct diff_patch_line { - const char *ptr; - size_t len; - size_t lines, oldno, newno; - char origin; -}; - /* cached information about a hunk in a diff */ typedef struct diff_patch_hunk diff_patch_hunk; struct diff_patch_hunk { - git_diff_range range; - char header[128]; - size_t header_len; + git_diff_hunk hunk; size_t line_start; size_t line_count; }; -struct git_diff_patch { +struct git_patch { git_refcount rc; - git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */ + git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ git_diff_delta *delta; size_t delta_index; git_diff_file_content ofile; git_diff_file_content nfile; uint32_t flags; git_array_t(diff_patch_hunk) hunks; - git_array_t(diff_patch_line) lines; - size_t oldno, newno; + git_array_t(git_diff_line) lines; size_t content_size, context_size, header_size; git_pool flattened; }; @@ -55,12 +43,13 @@ enum { GIT_DIFF_PATCH_FLATTENED = (1 << 5), }; -static void diff_output_init(git_diff_output*, const git_diff_options*, - git_diff_file_cb, git_diff_hunk_cb, git_diff_data_cb, void*); +static void diff_output_init( + git_diff_output*, const git_diff_options*, + git_diff_file_cb, git_diff_hunk_cb, git_diff_line_cb, void*); -static void diff_output_to_patch(git_diff_output *, git_diff_patch *); +static void diff_output_to_patch(git_diff_output *, git_patch *); -static void diff_patch_update_binary(git_diff_patch *patch) +static void diff_patch_update_binary(git_patch *patch) { if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) return; @@ -74,21 +63,21 @@ static void diff_patch_update_binary(git_diff_patch *patch) patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; } -static void diff_patch_init_common(git_diff_patch *patch) +static void diff_patch_init_common(git_patch *patch) { diff_patch_update_binary(patch); if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) - patch->flags |= GIT_DIFF_PATCH_LOADED; /* set LOADED but not DIFFABLE */ + patch->flags |= GIT_DIFF_PATCH_LOADED; /* LOADED but not DIFFABLE */ patch->flags |= GIT_DIFF_PATCH_INITIALIZED; if (patch->diff) - git_diff_list_addref(patch->diff); + git_diff_addref(patch->diff); } static int diff_patch_init_from_diff( - git_diff_patch *patch, git_diff_list *diff, size_t delta_index) + git_patch *patch, git_diff *diff, size_t delta_index) { int error = 0; @@ -109,12 +98,10 @@ static int diff_patch_init_from_diff( } static int diff_patch_alloc_from_diff( - git_diff_patch **out, - git_diff_list *diff, - size_t delta_index) + git_patch **out, git_diff *diff, size_t delta_index) { int error; - git_diff_patch *patch = git__calloc(1, sizeof(git_diff_patch)); + git_patch *patch = git__calloc(1, sizeof(git_patch)); GITERR_CHECK_ALLOC(patch); if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { @@ -129,7 +116,7 @@ static int diff_patch_alloc_from_diff( return error; } -static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) +static int diff_patch_load(git_patch *patch, git_diff_output *output) { int error = 0; bool incomplete_data; @@ -207,7 +194,7 @@ cleanup: } static int diff_patch_file_callback( - git_diff_patch *patch, git_diff_output *output) + git_patch *patch, git_diff_output *output) { float progress; @@ -223,7 +210,7 @@ static int diff_patch_file_callback( return output->error; } -static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output) +static int diff_patch_generate(git_patch *patch, git_diff_output *output) { int error = 0; @@ -248,7 +235,7 @@ static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output) return error; } -static void diff_patch_free(git_diff_patch *patch) +static void diff_patch_free(git_patch *patch) { git_diff_file_content__clear(&patch->ofile); git_diff_file_content__clear(&patch->nfile); @@ -256,7 +243,7 @@ static void diff_patch_free(git_diff_patch *patch) git_array_clear(patch->lines); git_array_clear(patch->hunks); - git_diff_list_free(patch->diff); /* decrements refcount */ + git_diff_free(patch->diff); /* decrements refcount */ patch->diff = NULL; git_pool_clear(&patch->flattened); @@ -265,7 +252,7 @@ static void diff_patch_free(git_diff_patch *patch) git__free(patch); } -static int diff_required(git_diff_list *diff, const char *action) +static int diff_required(git_diff *diff, const char *action) { if (diff) return 0; @@ -274,16 +261,16 @@ static int diff_required(git_diff_list *diff, const char *action) } int git_diff_foreach( - git_diff_list *diff, + git_diff *diff, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; git_xdiff_output xo; size_t idx; - git_diff_patch patch; + git_patch patch; if (diff_required(diff, "git_diff_foreach") < 0) return -1; @@ -305,7 +292,7 @@ int git_diff_foreach( if (!error) error = diff_patch_generate(&patch, &xo.output); - git_diff_patch_free(&patch); + git_patch_free(&patch); } if (error < 0) @@ -318,7 +305,7 @@ int git_diff_foreach( } typedef struct { - git_diff_patch patch; + git_patch patch; git_diff_delta delta; char paths[GIT_FLEX_ARRAY]; } diff_patch_with_delta; @@ -326,7 +313,7 @@ typedef struct { static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) { int error = 0; - git_diff_patch *patch = &pd->patch; + git_patch *patch = &pd->patch; bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); @@ -430,7 +417,7 @@ int git_diff_blobs( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; @@ -452,13 +439,13 @@ int git_diff_blobs( error = diff_patch_from_blobs( &pd, &xo, old_blob, old_path, new_blob, new_path, opts); - git_diff_patch_free(&pd.patch); + git_patch_free(&pd.patch); return error; } -int git_diff_patch_from_blobs( - git_diff_patch **out, +int git_patch_from_blobs( + git_patch **out, const git_blob *old_blob, const char *old_path, const git_blob *new_blob, @@ -484,9 +471,9 @@ int git_diff_patch_from_blobs( pd, &xo, old_blob, old_path, new_blob, new_path, opts); if (!error) - *out = (git_diff_patch *)pd; + *out = (git_patch *)pd; else - git_diff_patch_free((git_diff_patch *)pd); + git_patch_free((git_patch *)pd); return error; } @@ -542,7 +529,7 @@ int git_diff_blob_to_buffer( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; @@ -564,13 +551,13 @@ int git_diff_blob_to_buffer( error = diff_patch_from_blob_and_buffer( &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); - git_diff_patch_free(&pd.patch); + git_patch_free(&pd.patch); return error; } -int git_diff_patch_from_blob_and_buffer( - git_diff_patch **out, +int git_patch_from_blob_and_buffer( + git_patch **out, const git_blob *old_blob, const char *old_path, const char *buf, @@ -597,28 +584,24 @@ int git_diff_patch_from_blob_and_buffer( pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); if (!error) - *out = (git_diff_patch *)pd; + *out = (git_patch *)pd; else - git_diff_patch_free((git_diff_patch *)pd); + git_patch_free((git_patch *)pd); return error; } -int git_diff_get_patch( - git_diff_patch **patch_ptr, - const git_diff_delta **delta_ptr, - git_diff_list *diff, - size_t idx) +int git_patch_from_diff( + git_patch **patch_ptr, git_diff *diff, size_t idx) { int error = 0; git_xdiff_output xo; git_diff_delta *delta = NULL; - git_diff_patch *patch = NULL; + git_patch *patch = NULL; if (patch_ptr) *patch_ptr = NULL; - if (delta_ptr) *delta_ptr = NULL; - if (diff_required(diff, "git_diff_get_patch") < 0) + if (diff_required(diff, "git_patch_from_diff") < 0) return -1; delta = git_vector_get(&diff->deltas, idx); @@ -627,9 +610,6 @@ int git_diff_get_patch( return GIT_ENOTFOUND; } - if (delta_ptr) - *delta_ptr = delta; - if (git_diff_delta__should_skip(&diff->opts, delta)) return 0; @@ -656,7 +636,7 @@ int git_diff_get_patch( } if (error || !patch_ptr) - git_diff_patch_free(patch); + git_patch_free(patch); else *patch_ptr = patch; @@ -665,36 +645,36 @@ int git_diff_get_patch( return error; } -void git_diff_patch_free(git_diff_patch *patch) +void git_patch_free(git_patch *patch) { if (patch) GIT_REFCOUNT_DEC(patch, diff_patch_free); } -const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch) +const git_diff_delta *git_patch_get_delta(git_patch *patch) { assert(patch); return patch->delta; } -size_t git_diff_patch_num_hunks(git_diff_patch *patch) +size_t git_patch_num_hunks(git_patch *patch) { assert(patch); return git_array_size(patch->hunks); } -int git_diff_patch_line_stats( +int git_patch_line_stats( size_t *total_ctxt, size_t *total_adds, size_t *total_dels, - const git_diff_patch *patch) + const git_patch *patch) { size_t totals[3], idx; memset(totals, 0, sizeof(totals)); for (idx = 0; idx < git_array_size(patch->lines); ++idx) { - diff_patch_line *line = git_array_get(patch->lines, idx); + git_diff_line *line = git_array_get(patch->lines, idx); if (!line) continue; @@ -726,12 +706,10 @@ static int diff_error_outofrange(const char *thing) return GIT_ENOTFOUND; } -int git_diff_patch_get_hunk( - const git_diff_range **range, - const char **header, - size_t *header_len, +int git_patch_get_hunk( + const git_diff_hunk **out, size_t *lines_in_hunk, - git_diff_patch *patch, + git_patch *patch, size_t hunk_idx) { diff_patch_hunk *hunk; @@ -740,21 +718,17 @@ int git_diff_patch_get_hunk( hunk = git_array_get(patch->hunks, hunk_idx); if (!hunk) { - if (range) *range = NULL; - if (header) *header = NULL; - if (header_len) *header_len = 0; + if (out) *out = NULL; if (lines_in_hunk) *lines_in_hunk = 0; return diff_error_outofrange("hunk"); } - if (range) *range = &hunk->range; - if (header) *header = hunk->header; - if (header_len) *header_len = hunk->header_len; + if (out) *out = &hunk->hunk; if (lines_in_hunk) *lines_in_hunk = hunk->line_count; return 0; } -int git_diff_patch_num_lines_in_hunk(git_diff_patch *patch, size_t hunk_idx) +int git_patch_num_lines_in_hunk(git_patch *patch, size_t hunk_idx) { diff_patch_hunk *hunk; assert(patch); @@ -764,54 +738,35 @@ int git_diff_patch_num_lines_in_hunk(git_diff_patch *patch, size_t hunk_idx) return (int)hunk->line_count; } -int git_diff_patch_get_line_in_hunk( - char *line_origin, - const char **content, - size_t *content_len, - int *old_lineno, - int *new_lineno, - git_diff_patch *patch, +int git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, size_t hunk_idx, size_t line_of_hunk) { diff_patch_hunk *hunk; - diff_patch_line *line; - const char *thing; + git_diff_line *line; assert(patch); if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { - thing = "hunk"; - goto notfound; + if (out) *out = NULL; + return diff_error_outofrange("hunk"); } if (line_of_hunk >= hunk->line_count || !(line = git_array_get( patch->lines, hunk->line_start + line_of_hunk))) { - thing = "line"; - goto notfound; + if (out) *out = NULL; + return diff_error_outofrange("line"); } - if (line_origin) *line_origin = line->origin; - if (content) *content = line->ptr; - if (content_len) *content_len = line->len; - if (old_lineno) *old_lineno = (int)line->oldno; - if (new_lineno) *new_lineno = (int)line->newno; - + if (out) *out = line; return 0; - -notfound: - if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; - if (content) *content = NULL; - if (content_len) *content_len = 0; - if (old_lineno) *old_lineno = -1; - if (new_lineno) *new_lineno = -1; - - return diff_error_outofrange(thing); } -size_t git_diff_patch_size( - git_diff_patch *patch, +size_t git_patch_size( + git_patch *patch, int include_context, int include_hunk_headers, int include_file_headers) @@ -843,36 +798,36 @@ size_t git_diff_patch_size( return out; } -git_diff_list *git_diff_patch__diff(git_diff_patch *patch) +git_diff *git_patch__diff(git_patch *patch) { return patch->diff; } -git_diff_driver *git_diff_patch__driver(git_diff_patch *patch) +git_diff_driver *git_patch__driver(git_patch *patch) { /* ofile driver is representative for whole patch */ return patch->ofile.driver; } -void git_diff_patch__old_data( - char **ptr, size_t *len, git_diff_patch *patch) +void git_patch__old_data( + char **ptr, size_t *len, git_patch *patch) { *ptr = patch->ofile.map.data; *len = patch->ofile.map.len; } -void git_diff_patch__new_data( - char **ptr, size_t *len, git_diff_patch *patch) +void git_patch__new_data( + char **ptr, size_t *len, git_patch *patch) { *ptr = patch->nfile.map.data; *len = patch->nfile.map.len; } -int git_diff_patch__invoke_callbacks( - git_diff_patch *patch, +int git_patch__invoke_callbacks( + git_patch *patch, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *payload) { int error = 0; @@ -887,18 +842,16 @@ int git_diff_patch__invoke_callbacks( for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { diff_patch_hunk *h = git_array_get(patch->hunks, i); - error = hunk_cb( - patch->delta, &h->range, h->header, h->header_len, payload); + error = hunk_cb(patch->delta, &h->hunk, payload); if (!line_cb) continue; for (j = 0; !error && j < h->line_count; ++j) { - diff_patch_line *l = + git_diff_line *l = git_array_get(patch->lines, h->line_start + j); - error = line_cb( - patch->delta, &h->range, l->origin, l->ptr, l->len, payload); + error = line_cb(patch->delta, &h->hunk, l, payload); } } @@ -917,12 +870,10 @@ static int diff_patch_file_cb( static int diff_patch_hunk_cb( const git_diff_delta *delta, - const git_diff_range *range, - const char *header, - size_t header_len, + const git_diff_hunk *hunk_, void *payload) { - git_diff_patch *patch = payload; + git_patch *patch = payload; diff_patch_hunk *hunk; GIT_UNUSED(delta); @@ -930,39 +881,28 @@ static int diff_patch_hunk_cb( hunk = git_array_alloc(patch->hunks); GITERR_CHECK_ALLOC(hunk); - memcpy(&hunk->range, range, sizeof(hunk->range)); - - assert(header_len + 1 < sizeof(hunk->header)); - memcpy(&hunk->header, header, header_len); - hunk->header[header_len] = '\0'; - hunk->header_len = header_len; + memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); - patch->header_size += header_len; + patch->header_size += hunk_->header_len; hunk->line_start = git_array_size(patch->lines); hunk->line_count = 0; - patch->oldno = range->old_start; - patch->newno = range->new_start; - return 0; } static int diff_patch_line_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk_, + const git_diff_line *line_, void *payload) { - git_diff_patch *patch = payload; + git_patch *patch = payload; diff_patch_hunk *hunk; - diff_patch_line *line; - const char *content_end = content + content_len; + git_diff_line *line; GIT_UNUSED(delta); - GIT_UNUSED(range); + GIT_UNUSED(hunk_); hunk = git_array_last(patch->hunks); GITERR_CHECK_ALLOC(hunk); @@ -970,48 +910,20 @@ static int diff_patch_line_cb( line = git_array_alloc(patch->lines); GITERR_CHECK_ALLOC(line); - line->ptr = content; - line->len = content_len; - line->origin = line_origin; + memcpy(line, line_, sizeof(*line)); /* do some bookkeeping so we can provide old/new line numbers */ - line->lines = 0; - while (content < content_end) - if (*content++ == '\n') - ++line->lines; + patch->content_size += line->content_len; - patch->content_size += content_len; - - switch (line_origin) { - case GIT_DIFF_LINE_ADDITION: - patch->content_size += 1; - case GIT_DIFF_LINE_DEL_EOFNL: - line->oldno = -1; - line->newno = patch->newno; - patch->newno += line->lines; - break; - case GIT_DIFF_LINE_DELETION: + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) patch->content_size += 1; - case GIT_DIFF_LINE_ADD_EOFNL: - line->oldno = patch->oldno; - line->newno = -1; - patch->oldno += line->lines; - break; - case GIT_DIFF_LINE_CONTEXT: + else if (line->origin == GIT_DIFF_LINE_CONTEXT) { patch->content_size += 1; - patch->context_size += 1; - case GIT_DIFF_LINE_CONTEXT_EOFNL: - patch->context_size += content_len; - line->oldno = patch->oldno; - line->newno = patch->newno; - patch->oldno += line->lines; - patch->newno += line->lines; - break; - default: - assert(false); - break; - } + patch->context_size += line->content_len + 1; + } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + patch->context_size += line->content_len; hunk->line_count++; @@ -1023,7 +935,7 @@ static void diff_output_init( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { GIT_UNUSED(opts); @@ -1036,7 +948,7 @@ static void diff_output_init( out->payload = payload; } -static void diff_output_to_patch(git_diff_output *out, git_diff_patch *patch) +static void diff_output_to_patch(git_diff_output *out, git_patch *patch) { diff_output_init( out, NULL, diff --git a/src/diff_patch.h b/src/diff_patch.h index 56af14600..df2ba4c31 100644 --- a/src/diff_patch.h +++ b/src/diff_patch.h @@ -11,19 +11,20 @@ #include "diff.h" #include "diff_file.h" #include "array.h" +#include "git2/patch.h" -extern git_diff_list *git_diff_patch__diff(git_diff_patch *); +extern git_diff *git_patch__diff(git_patch *); -extern git_diff_driver *git_diff_patch__driver(git_diff_patch *); +extern git_diff_driver *git_patch__driver(git_patch *); -extern void git_diff_patch__old_data(char **, size_t *, git_diff_patch *); -extern void git_diff_patch__new_data(char **, size_t *, git_diff_patch *); +extern void git_patch__old_data(char **, size_t *, git_patch *); +extern void git_patch__new_data(char **, size_t *, git_patch *); -extern int git_diff_patch__invoke_callbacks( - git_diff_patch *patch, +extern int git_patch__invoke_callbacks( + git_patch *patch, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *payload); typedef struct git_diff_output git_diff_output; @@ -31,7 +32,7 @@ struct git_diff_output { /* these callbacks are issued with the diff data */ git_diff_file_cb file_cb; git_diff_hunk_cb hunk_cb; - git_diff_data_cb data_cb; + git_diff_line_cb data_cb; void *payload; /* this records the actual error in cases where it may be obscured */ @@ -40,7 +41,7 @@ struct git_diff_output { /* this callback is used to do the diff and drive the other callbacks. * see diff_xdiff.h for how to use this in practice for now. */ - int (*diff_cb)(git_diff_output *output, git_diff_patch *patch); + int (*diff_cb)(git_diff_output *output, git_patch *patch); }; #endif diff --git a/src/diff_print.c b/src/diff_print.c index fd18b67e3..b04b11515 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -10,23 +10,36 @@ #include "fileops.h" typedef struct { - git_diff_list *diff; - git_diff_data_cb print_cb; + git_diff *diff; + git_diff_format_t format; + git_diff_line_cb print_cb; void *payload; git_buf *buf; + uint32_t flags; int oid_strlen; + git_diff_line line; } diff_print_info; static int diff_print_info_init( diff_print_info *pi, - git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload) + git_buf *out, + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) { pi->diff = diff; + pi->format = format; pi->print_cb = cb; pi->payload = payload; pi->buf = out; - if (!diff || !diff->repo) + if (diff) + pi->flags = diff->opts.flags; + + if (diff && diff->opts.oid_abbrev != 0) + pi->oid_strlen = diff->opts.oid_abbrev; + else if (!diff || !diff->repo) pi->oid_strlen = GIT_ABBREV_DEFAULT; else if (git_repository__cvar( &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) @@ -39,6 +52,11 @@ static int diff_print_info_init( else if (pi->oid_strlen > GIT_OID_HEXSZ + 1) pi->oid_strlen = GIT_OID_HEXSZ + 1; + memset(&pi->line, 0, sizeof(pi->line)); + pi->line.old_lineno = -1; + pi->line.new_lineno = -1; + pi->line.num_lines = 1; + return 0; } @@ -77,7 +95,35 @@ static int callback_error(void) return GIT_EUSER; } -static int diff_print_one_compact( +static int diff_print_one_name_only( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_buf *out = pi->buf; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && + delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + git_buf_clear(out); + + if (git_buf_puts(out, delta->new_file.path) < 0 || + git_buf_putc(out, '\n')) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) + return callback_error(); + + return 0; +} + +static int diff_print_one_name_status( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; @@ -88,7 +134,7 @@ static int diff_print_one_compact( GIT_UNUSED(progress); - if (code == ' ') + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') return 0; old_suffix = diff_pick_suffix(delta->old_file.mode); @@ -112,31 +158,16 @@ static int diff_print_one_compact( if (git_buf_oom(out)) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(out), git_buf_len(out), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to a print callback in compact format */ -int git_diff_print_compact( - git_diff_list *diff, - git_diff_data_cb print_cb, - void *payload) -{ - int error; - git_buf buf = GIT_BUF_INIT; - diff_print_info pi; - - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi); - - git_buf_free(&buf); - - return error; -} - static int diff_print_one_raw( const git_diff_delta *delta, float progress, void *data) { @@ -147,7 +178,7 @@ static int diff_print_one_raw( GIT_UNUSED(progress); - if (code == ' ') + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') return 0; git_buf_clear(out); @@ -173,31 +204,16 @@ static int diff_print_one_raw( if (git_buf_oom(out)) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(out), git_buf_len(out), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to a print callback in raw output format */ -int git_diff_print_raw( - git_diff_list *diff, - git_diff_data_cb print_cb, - void *payload) -{ - int error; - git_buf buf = GIT_BUF_INIT; - diff_print_info pi; - - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi); - - git_buf_free(&buf); - - return error; -} - static int diff_print_oid_range( git_buf *out, const git_diff_delta *delta, int oid_strlen) { @@ -287,7 +303,6 @@ static int diff_print_patch_file( pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; - uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; GIT_UNUSED(progress); @@ -295,15 +310,18 @@ static int diff_print_patch_file( delta->status == GIT_DELTA_UNMODIFIED || delta->status == GIT_DELTA_IGNORED || (delta->status == GIT_DELTA_UNTRACKED && - (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) + (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) return 0; if (git_diff_delta__format_file_header( pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(pi->buf); + pi->line.content_len = git_buf_len(pi->buf); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) @@ -316,8 +334,12 @@ static int diff_print_patch_file( "Binary files %s%s and %s%s differ\n") < 0) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_BINARY; + pi->line.content = git_buf_cstr(pi->buf); + pi->line.content_len = git_buf_len(pi->buf); + pi->line.num_lines = 1; + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; @@ -325,9 +347,7 @@ static int diff_print_patch_file( static int diff_print_patch_hunk( const git_diff_delta *d, - const git_diff_range *r, - const char *header, - size_t header_len, + const git_diff_hunk *h, void *data) { diff_print_info *pi = data; @@ -335,12 +355,11 @@ static int diff_print_patch_hunk( if (S_ISDIR(d->new_file.mode)) return 0; - git_buf_clear(pi->buf); - if (git_buf_put(pi->buf, header, header_len) < 0) - return -1; + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; + pi->line.content = h->header; + pi->line.content_len = h->header_len; - if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + if (pi->print_cb(d, h, &pi->line, pi->payload)) return callback_error(); return 0; @@ -348,10 +367,8 @@ static int diff_print_patch_hunk( static int diff_print_patch_line( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, /* GIT_DIFF_LINE value from above */ - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *data) { diff_print_info *pi = data; @@ -359,50 +376,63 @@ static int diff_print_patch_line( if (S_ISDIR(delta->new_file.mode)) return 0; - git_buf_clear(pi->buf); - git_buf_grow(pi->buf, content_len + 2); - - if (line_origin == GIT_DIFF_LINE_ADDITION || - line_origin == GIT_DIFF_LINE_DELETION || - line_origin == GIT_DIFF_LINE_CONTEXT) - git_buf_putc(pi->buf, line_origin); - - git_buf_put(pi->buf, content, content_len); - - if (git_buf_oom(pi->buf)) - return -1; - - if (pi->print_cb(delta, range, line_origin, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + if (pi->print_cb(delta, hunk, line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to an output callback in patch format */ -int git_diff_print_patch( - git_diff_list *diff, - git_diff_data_cb print_cb, +/* print a git_diff to an output callback */ +int git_diff_print( + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb print_cb, void *payload) { int error; git_buf buf = GIT_BUF_INIT; diff_print_info pi; + git_diff_file_cb print_file = NULL; + git_diff_hunk_cb print_hunk = NULL; + git_diff_line_cb print_line = NULL; + + switch (format) { + case GIT_DIFF_FORMAT_PATCH: + print_file = diff_print_patch_file; + print_hunk = diff_print_patch_hunk; + print_line = diff_print_patch_line; + break; + case GIT_DIFF_FORMAT_PATCH_HEADER: + print_file = diff_print_patch_file; + break; + case GIT_DIFF_FORMAT_RAW: + print_file = diff_print_one_raw; + break; + case GIT_DIFF_FORMAT_NAME_ONLY: + print_file = diff_print_one_name_only; + break; + case GIT_DIFF_FORMAT_NAME_STATUS: + print_file = diff_print_one_name_status; + break; + default: + giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format); + return -1; + } - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) + if (!(error = diff_print_info_init( + &pi, &buf, diff, format, print_cb, payload))) error = git_diff_foreach( - diff, diff_print_patch_file, diff_print_patch_hunk, - diff_print_patch_line, &pi); + diff, print_file, print_hunk, print_line, &pi); git_buf_free(&buf); return error; } -/* print a git_diff_patch to an output callback */ -int git_diff_patch_print( - git_diff_patch *patch, - git_diff_data_cb print_cb, +/* print a git_patch to an output callback */ +int git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, void *payload) { int error; @@ -412,8 +442,9 @@ int git_diff_patch_print( assert(patch && print_cb); if (!(error = diff_print_info_init( - &pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) - error = git_diff_patch__invoke_callbacks( + &pi, &temp, git_patch__diff(patch), + GIT_DIFF_FORMAT_PATCH, print_cb, payload))) + error = git_patch__invoke_callbacks( patch, diff_print_patch_file, diff_print_patch_hunk, diff_print_patch_line, &pi); @@ -424,26 +455,30 @@ int git_diff_patch_print( static int diff_print_to_buffer_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *payload) { git_buf *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); - return git_buf_put(output, content, content_len); + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION || + line->origin == GIT_DIFF_LINE_CONTEXT) + git_buf_putc(output, line->origin); + + return git_buf_put(output, line->content, line->content_len); } -/* print a git_diff_patch to a string buffer */ -int git_diff_patch_to_str( +/* print a git_patch to a string buffer */ +int git_patch_to_str( char **string, - git_diff_patch *patch) + git_patch *patch) { int error; git_buf output = GIT_BUF_INIT; - error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output); + error = git_patch_print(patch, diff_print_to_buffer_cb, &output); /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, * meaning a memory allocation failure, so just map to -1... diff --git a/src/diff_tform.c b/src/diff_tform.c index cbe8bafbd..9461ca2c8 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -97,8 +97,8 @@ static git_diff_delta *diff_delta__merge_like_cgit( } int git_diff_merge( - git_diff_list *onto, - const git_diff_list *from) + git_diff *onto, + const git_diff *from) { int error = 0; git_pool onto_pool; @@ -117,15 +117,15 @@ int git_diff_merge( git_pool_init(&onto_pool, 1, 0) < 0) return -1; - if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 || - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) + if ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0 || + (from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) { ignore_case = true; /* This function currently only supports merging diff lists that * are sorted identically. */ - assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0); + assert((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0 && + (from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); } for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { @@ -230,9 +230,9 @@ int git_diff_find_similar__calc_similarity( #define DEFAULT_RENAME_LIMIT 200 static int normalize_find_opts( - git_diff_list *diff, + git_diff *diff, git_diff_find_options *opts, - git_diff_find_options *given) + const git_diff_find_options *given) { git_config *cfg = NULL; @@ -328,7 +328,7 @@ static int normalize_find_opts( } static int apply_splits_and_deletes( - git_diff_list *diff, size_t expected_size, bool actually_split) + git_diff *diff, size_t expected_size, bool actually_split) { git_vector onto = GIT_VECTOR_INIT; size_t i; @@ -350,6 +350,7 @@ static int apply_splits_and_deletes( goto on_error; deleted->status = GIT_DELTA_DELETED; + deleted->nfiles = 1; memset(&deleted->new_file, 0, sizeof(deleted->new_file)); deleted->new_file.path = deleted->old_file.path; deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; @@ -361,6 +362,7 @@ static int apply_splits_and_deletes( delta->status = GIT_DELTA_UNTRACKED; else delta->status = GIT_DELTA_ADDED; + delta->nfiles = 1; memset(&delta->old_file, 0, sizeof(delta->old_file)); delta->old_file.path = delta->new_file.path; delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; @@ -402,7 +404,7 @@ on_error: return -1; } -GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx) +GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) { git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); return (idx & 1) ? &delta->new_file : &delta->old_file; @@ -419,7 +421,7 @@ typedef struct { } similarity_info; static int similarity_init( - similarity_info *info, git_diff_list *diff, size_t file_idx) + similarity_info *info, git_diff *diff, size_t file_idx) { info->idx = file_idx; info->src = (file_idx & 1) ? diff->new_src : diff->old_src; @@ -509,7 +511,7 @@ static void similarity_unload(similarity_info *info) */ static int similarity_measure( int *score, - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, void **cache, size_t a_idx, @@ -595,7 +597,7 @@ cleanup: } static int calc_self_similarity( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -612,7 +614,7 @@ static int calc_self_similarity( return error; if (similarity >= 0) { - delta->similarity = (uint32_t)similarity; + delta->similarity = (uint16_t)similarity; delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; } @@ -620,7 +622,7 @@ static int calc_self_similarity( } static bool is_rename_target( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -675,7 +677,7 @@ static bool is_rename_target( } static bool is_rename_source( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -745,25 +747,27 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) } GIT_INLINE(void) delta_make_rename( - git_diff_delta *to, const git_diff_delta *from, uint32_t similarity) + git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) { to->status = GIT_DELTA_RENAMED; to->similarity = similarity; + to->nfiles = 2; memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; } typedef struct { - uint32_t idx; - uint32_t similarity; + size_t idx; + uint16_t similarity; } diff_find_match; int git_diff_find_similar( - git_diff_list *diff, - git_diff_find_options *given_opts) + git_diff *diff, + const git_diff_find_options *given_opts) { size_t s, t; - int error = 0, similarity; + int error = 0, result; + uint16_t similarity; git_diff_delta *src, *tgt; git_diff_find_options opts; size_t num_deltas, num_srcs = 0, num_tgts = 0; @@ -839,17 +843,18 @@ find_best_matches: /* calculate similarity for this pair and find best match */ if (s == t) - similarity = -1; /* don't measure self-similarity here */ + result = -1; /* don't measure self-similarity here */ else if ((error = similarity_measure( - &similarity, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) + &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) goto cleanup; - if (similarity < 0) + if (result < 0) continue; + similarity = (uint16_t)result; /* is this a better rename? */ - if (tgt2src[t].similarity < (uint32_t)similarity && - src2tgt[s].similarity < (uint32_t)similarity) + if (tgt2src[t].similarity < similarity && + src2tgt[s].similarity < similarity) { /* eject old mapping */ if (src2tgt[s].similarity > 0) { @@ -862,18 +867,18 @@ find_best_matches: } /* write new mapping */ - tgt2src[t].idx = (uint32_t)s; - tgt2src[t].similarity = (uint32_t)similarity; - src2tgt[s].idx = (uint32_t)t; - src2tgt[s].similarity = (uint32_t)similarity; + tgt2src[t].idx = s; + tgt2src[t].similarity = similarity; + src2tgt[s].idx = t; + src2tgt[s].similarity = similarity; } /* keep best absolute match for copies */ if (tgt2src_copy != NULL && - tgt2src_copy[t].similarity < (uint32_t)similarity) + tgt2src_copy[t].similarity < similarity) { - tgt2src_copy[t].idx = (uint32_t)s; - tgt2src_copy[t].similarity = (uint32_t)similarity; + tgt2src_copy[t].idx = s; + tgt2src_copy[t].similarity = similarity; } if (++tried_srcs >= num_srcs) @@ -943,7 +948,7 @@ find_best_matches: delta_make_rename(tgt, src, best_match->similarity); num_rewrites--; - src->status = GIT_DELTA_DELETED; + assert(src->status == GIT_DELTA_DELETED); memcpy(&src->old_file, &swap, sizeof(src->old_file)); memset(&src->new_file, 0, sizeof(src->new_file)); src->new_file.path = src->old_file.path; @@ -953,7 +958,7 @@ find_best_matches: if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = (uint32_t)s; + tgt2src[src2tgt[t].idx].idx = s; } } } @@ -969,6 +974,7 @@ find_best_matches: src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; + src->nfiles = 1; memset(&src->old_file, 0, sizeof(src->old_file)); src->old_file.path = src->new_file.path; src->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; @@ -1006,7 +1012,7 @@ find_best_matches: /* otherwise, if we just overwrote a source, update mapping */ else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = (uint32_t)s; + tgt2src[src2tgt[t].idx].idx = s; } num_updates++; @@ -1026,6 +1032,7 @@ find_best_matches: tgt->status = GIT_DELTA_COPIED; tgt->similarity = best_match->similarity; + tgt->nfiles = 2; memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); num_updates++; diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c index 7694fb996..2aca76f87 100644 --- a/src/diff_xdiff.c +++ b/src/diff_xdiff.c @@ -24,26 +24,26 @@ static int git_xdiff_scan_int(const char **str, int *value) return (digits > 0) ? 0 : -1; } -static int git_xdiff_parse_hunk(git_diff_range *range, const char *header) +static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) { /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ if (*header != '@') return -1; - if (git_xdiff_scan_int(&header, &range->old_start) < 0) + if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) return -1; if (*header == ',') { - if (git_xdiff_scan_int(&header, &range->old_lines) < 0) + if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) return -1; } else - range->old_lines = 1; - if (git_xdiff_scan_int(&header, &range->new_start) < 0) + hunk->old_lines = 1; + if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) return -1; if (*header == ',') { - if (git_xdiff_scan_int(&header, &range->new_lines) < 0) + if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) return -1; } else - range->new_lines = 1; - if (range->old_start < 0 || range->new_start < 0) + hunk->new_lines = 1; + if (hunk->old_start < 0 || hunk->new_start < 0) return -1; return 0; @@ -51,38 +51,96 @@ static int git_xdiff_parse_hunk(git_diff_range *range, const char *header) typedef struct { git_xdiff_output *xo; - git_diff_patch *patch; - git_diff_range range; + git_patch *patch; + git_diff_hunk hunk; + int old_lineno, new_lineno; } git_xdiff_info; +static int diff_update_lines( + git_xdiff_info *info, + git_diff_line *line, + const char *content, + size_t content_len) +{ + const char *scan = content, *scan_end = content + content_len; + + for (line->num_lines = 0; scan < scan_end; ++scan) + if (*scan == '\n') + ++line->num_lines; + + line->content = content; + line->content_len = content_len; + + /* expect " "/"-"/"+", then data */ + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_DEL_EOFNL: + line->old_lineno = -1; + line->new_lineno = info->new_lineno; + info->new_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_ADD_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = -1; + info->old_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_CONTEXT_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = info->new_lineno; + info->old_lineno += (int)line->num_lines; + info->new_lineno += (int)line->num_lines; + break; + default: + giterr_set(GITERR_INVALID, "Unknown diff line origin %02x", + (unsigned int)line->origin); + return -1; + } + + return 0; +} + static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) { git_xdiff_info *info = priv; - git_diff_patch *patch = info->patch; - const git_diff_delta *delta = git_diff_patch_delta(patch); + git_patch *patch = info->patch; + const git_diff_delta *delta = git_patch_get_delta(patch); git_diff_output *output = &info->xo->output; + git_diff_line line; if (len == 1) { - output->error = git_xdiff_parse_hunk(&info->range, bufs[0].ptr); + output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); if (output->error < 0) return output->error; + info->hunk.header_len = bufs[0].size; + if (info->hunk.header_len >= sizeof(info->hunk.header)) + info->hunk.header_len = sizeof(info->hunk.header) - 1; + memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); + info->hunk.header[info->hunk.header_len] = '\0'; + if (output->hunk_cb != NULL && - output->hunk_cb(delta, &info->range, - bufs[0].ptr, bufs[0].size, output->payload)) + output->hunk_cb(delta, &info->hunk, output->payload)) output->error = GIT_EUSER; + + info->old_lineno = info->hunk.old_start; + info->new_lineno = info->hunk.new_start; } if (len == 2 || len == 3) { /* expect " "/"-"/"+", then data */ - char origin = + line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : GIT_DIFF_LINE_CONTEXT; - if (output->data_cb != NULL && - output->data_cb(delta, &info->range, - origin, bufs[1].ptr, bufs[1].size, output->payload)) + output->error = diff_update_lines( + info, &line, bufs[1].ptr, bufs[1].size); + + if (!output->error && + output->data_cb != NULL && + output->data_cb(delta, &info->hunk, &line, output->payload)) output->error = GIT_EUSER; } @@ -92,21 +150,24 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) * If we have a '-' and a third buf, then we have removed a line * with out a newline but added a blank line, so ADD_EOFNL. */ - char origin = + line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : GIT_DIFF_LINE_CONTEXT_EOFNL; - if (output->data_cb != NULL && - output->data_cb(delta, &info->range, - origin, bufs[2].ptr, bufs[2].size, output->payload)) + output->error = diff_update_lines( + info, &line, bufs[2].ptr, bufs[2].size); + + if (!output->error && + output->data_cb != NULL && + output->data_cb(delta, &info->hunk, &line, output->payload)) output->error = GIT_EUSER; } return output->error; } -static int git_xdiff(git_diff_output *output, git_diff_patch *patch) +static int git_xdiff(git_diff_output *output, git_patch *patch) { git_xdiff_output *xo = (git_xdiff_output *)output; git_xdiff_info info; @@ -120,7 +181,7 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) xo->callback.priv = &info; git_diff_find_context_init( - &xo->config.find_func, &findctxt, git_diff_patch__driver(patch)); + &xo->config.find_func, &findctxt, git_patch__driver(patch)); xo->config.find_func_priv = &findctxt; if (xo->config.find_func != NULL) @@ -132,8 +193,8 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) * updates are needed to xo->params.flags */ - git_diff_patch__old_data(&xd_old_data.ptr, &xd_old_data.size, patch); - git_diff_patch__new_data(&xd_new_data.ptr, &xd_new_data.size, patch); + git_patch__old_data(&xd_old_data.ptr, &xd_old_data.size, patch); + git_patch__new_data(&xd_new_data.ptr, &xd_new_data.size, patch); xdl_diff(&xd_old_data, &xd_new_data, &xo->params, &xo->config, &xo->callback); @@ -145,7 +206,7 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) { - uint32_t flags = opts ? opts->flags : GIT_DIFF_NORMAL; + uint32_t flags = opts ? opts->flags : 0; xo->output.diff_cb = git_xdiff; @@ -161,6 +222,11 @@ void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (flags & GIT_DIFF_PATIENCE) + xo->params.flags |= XDF_PATIENCE_DIFF; + if (flags & GIT_DIFF_MINIMAL) + xo->params.flags |= XDF_NEED_MINIMAL; + memset(&xo->callback, 0, sizeof(xo->callback)); xo->callback.outf = git_xdiff_cb; } diff --git a/src/errors.c b/src/errors.c index e2629f69e..c9d9e4e37 100644 --- a/src/errors.c +++ b/src/errors.c @@ -116,4 +116,3 @@ const git_error *giterr_last(void) { return GIT_GLOBAL->last_error; } - diff --git a/src/fileops.c b/src/fileops.c index 5a5041cc3..17007f823 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -78,11 +78,8 @@ int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, con int git_futils_open_ro(const char *path) { int fd = p_open(path, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT || errno == ENOTDIR) - fd = GIT_ENOTFOUND; - giterr_set(GITERR_OS, "Failed to open '%s'", path); - } + if (fd < 0) + return git_path_set_error(errno, path, "open"); return fd; } @@ -138,7 +135,6 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) int git_futils_readbuffer_updated( git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) { - int error = 0; git_file fd; struct stat st; bool changed = false; @@ -148,15 +144,16 @@ int git_futils_readbuffer_updated( if (updated != NULL) *updated = 0; - if (p_stat(path, &st) < 0) { - error = errno; - giterr_set(GITERR_OS, "Failed to stat '%s'", path); - if (error == ENOENT || error == ENOTDIR) - return GIT_ENOTFOUND; - return -1; + if (p_stat(path, &st) < 0) + return git_path_set_error(errno, path, "stat"); + + + if (S_ISDIR(st.st_mode)) { + giterr_set(GITERR_INVALID, "requested file is a directory"); + return GIT_ENOTFOUND; } - if (S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { + if (!git__is_sizet(st.st_size+1)) { giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); return -1; } @@ -398,8 +395,11 @@ typedef struct { size_t baselen; uint32_t flags; int error; + int depth; } futils__rmdir_data; +#define FUTILS_MAX_DEPTH 100 + static int futils__error_cannot_rmdir(const char *path, const char *filemsg) { if (filemsg) @@ -438,51 +438,60 @@ static int futils__rm_first_parent(git_buf *path, const char *ceiling) static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) { - struct stat st; futils__rmdir_data *data = opaque; + int error = data->error; + struct stat st; + + if (data->depth > FUTILS_MAX_DEPTH) + error = futils__error_cannot_rmdir( + path->ptr, "directory nesting too deep"); - if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) { + else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { if (errno == ENOENT) - data->error = 0; + error = 0; else if (errno == ENOTDIR) { /* asked to remove a/b/c/d/e and a/b is a normal file */ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) - data->error = futils__rm_first_parent(path, data->base); + error = futils__rm_first_parent(path, data->base); else futils__error_cannot_rmdir( path->ptr, "parent is not directory"); } else - futils__error_cannot_rmdir(path->ptr, "cannot access"); + error = git_path_set_error(errno, path->ptr, "rmdir"); } else if (S_ISDIR(st.st_mode)) { - int error = git_path_direach(path, futils__rmdir_recurs_foreach, data); + data->depth++; + + error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data); if (error < 0) return (error == GIT_EUSER) ? data->error : error; - data->error = p_rmdir(path->ptr); + data->depth--; + + if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) + return data->error; - if (data->error < 0) { + if ((error = p_rmdir(path->ptr)) < 0) { if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) - data->error = 0; + error = 0; else - futils__error_cannot_rmdir(path->ptr, NULL); + error = git_path_set_error(errno, path->ptr, "rmdir"); } } else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { - data->error = p_unlink(path->ptr); - - if (data->error < 0) - futils__error_cannot_rmdir(path->ptr, "cannot be removed"); + if (p_unlink(path->ptr) < 0) + error = git_path_set_error(errno, path->ptr, "remove"); } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) - data->error = futils__error_cannot_rmdir(path->ptr, "still present"); + error = futils__error_cannot_rmdir(path->ptr, "still present"); - return data->error; + data->error = error; + return error; } static int futils__rmdir_empty_parent(void *opaque, git_buf *path) @@ -505,7 +514,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path) giterr_clear(); error = GIT_ITEROVER; } else { - futils__error_cannot_rmdir(git_buf_cstr(path), NULL); + error = git_path_set_error(errno, git_buf_cstr(path), "rmdir"); } } @@ -517,7 +526,7 @@ int git_futils_rmdir_r( { int error; git_buf fullpath = GIT_BUF_INIT; - futils__rmdir_data data; + futils__rmdir_data data = { 0 }; /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) @@ -526,7 +535,6 @@ int git_futils_rmdir_r( data.base = base ? base : ""; data.baselen = base ? strlen(base) : 0; data.flags = flags; - data.error = 0; error = futils__rmdir_recurs_foreach(&data, &fullpath); @@ -544,41 +552,6 @@ int git_futils_rmdir_r( return error; } -int git_futils_cleanupdir_r(const char *path) -{ - int error; - git_buf fullpath = GIT_BUF_INIT; - futils__rmdir_data data; - - if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0) - goto clean_up; - - data.base = ""; - data.baselen = 0; - data.flags = GIT_RMDIR_REMOVE_FILES; - data.error = 0; - - if (!git_path_exists(path)) { - giterr_set(GITERR_OS, "Path does not exist: %s" , path); - error = GIT_ERROR; - goto clean_up; - } - - if (!git_path_isdir(path)) { - giterr_set(GITERR_OS, "Path is not a directory: %s" , path); - error = GIT_ERROR; - goto clean_up; - } - - error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data); - if (error == GIT_EUSER) - error = data.error; - -clean_up: - git_buf_free(&fullpath); - return error; -} - static int git_futils_guess_system_dirs(git_buf *out) { @@ -836,11 +809,8 @@ int git_futils_cp(const char *from, const char *to, mode_t filemode) return ifd; if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { - if (errno == ENOENT || errno == ENOTDIR) - ofd = GIT_ENOTFOUND; - giterr_set(GITERR_OS, "Failed to open '%s' for writing", to); p_close(ifd); - return ofd; + return git_path_set_error(errno, to, "open for writing"); } return cp_by_fd(ifd, ofd, true); @@ -923,15 +893,14 @@ static int _cp_r_callback(void *ref, git_buf *from) goto exit; } - if (p_lstat(info->to.ptr, &to_st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) { - giterr_set(GITERR_OS, - "Could not access %s while copying files", info->to.ptr); - error = -1; - goto exit; - } - } else + if (!(error = git_path_lstat(info->to.ptr, &to_st))) exists = true; + else if (error != GIT_ENOTFOUND) + goto exit; + else { + giterr_clear(); + error = 0; + } if ((error = git_path_lstat(from->ptr, &from_st)) < 0) goto exit; @@ -948,9 +917,12 @@ static int _cp_r_callback(void *ref, git_buf *from) error = _cp_r_mkdir(info, from); /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode)) && - ((error = git_path_direach(from, _cp_r_callback, info)) == GIT_EUSER)) - error = info->error; + if (!error && (!exists || S_ISDIR(to_st.st_mode))) { + error = git_path_direach(from, 0, _cp_r_callback, info); + + if (error == GIT_EUSER) + error = info->error; + } if (oldmode != 0) info->dirmode = oldmode; diff --git a/src/fileops.h b/src/fileops.h index 6bd693b99..1b2728e58 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -115,12 +115,7 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode); * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base * if removing this item leaves them empty * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR - * - * The old values translate into the new as follows: - * - * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY - * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES - * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY + * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself */ typedef enum { GIT_RMDIR_EMPTY_HIERARCHY = 0, @@ -128,6 +123,7 @@ typedef enum { GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), GIT_RMDIR_EMPTY_PARENTS = (1 << 2), GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), + GIT_RMDIR_SKIP_ROOT = (1 << 4), } git_futils_rmdir_flags; /** @@ -141,14 +137,6 @@ typedef enum { extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); /** - * Remove all files and directories beneath the specified path. - * - * @param path Path to the top level directory to process. - * @return 0 on success; -1 on error. - */ -extern int git_futils_cleanupdir_r(const char *path); - -/** * Create and open a temporary file with a `_git2_` suffix. * Writes the filename into path_out. * @return On success, an open file descriptor, else an error code < 0. diff --git a/src/global.c b/src/global.c index 4f024f17e..7d39c6fa8 100644 --- a/src/global.c +++ b/src/global.c @@ -73,47 +73,69 @@ static void git__shutdown(void) #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; -static int _tls_init = 0; +static DWORD _mutex = 0; +static DWORD _n_inits = 0; -int git_threads_init(void) +static int synchronized_threads_init() { int error; - if (_tls_init) - return 0; - _tls_index = TlsAlloc(); if (git_mutex_init(&git__mwindow_mutex)) return -1; /* Initialize any other subsystems that have global state */ - if ((error = git_hash_global_init()) >= 0 && - (error = git_futils_dirs_global_init()) >= 0) - _tls_init = 1; - - GIT_MEMORY_BARRIER; + if ((error = git_hash_global_init()) >= 0) + error = git_futils_dirs_global_init(); win32_pthread_initialize(); return error; } -void git_threads_shutdown(void) +int git_threads_init(void) +{ + int error = 0; + + /* Enter the lock */ + while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); } + + /* Only do work on a 0 -> 1 transition of the refcount */ + if (1 == ++_n_inits) + error = synchronized_threads_init(); + + /* Exit the lock */ + InterlockedExchange(&_mutex, 0); + + return error; +} + +static void synchronized_threads_shutdown() { /* Shut down any subsystems that have global state */ git__shutdown(); - TlsFree(_tls_index); - _tls_init = 0; - git_mutex_free(&git__mwindow_mutex); } +void git_threads_shutdown(void) +{ + /* Enter the lock */ + while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); } + + /* Only do work on a 1 -> 0 transition of the refcount */ + if (0 == --_n_inits) + synchronized_threads_shutdown(); + + /* Exit the lock */ + InterlockedExchange(&_mutex, 0); +} + git_global_st *git__global_state(void) { void *ptr; - assert(_tls_init); + assert(_n_inits); if ((ptr = TlsGetValue(_tls_index)) != NULL) return ptr; @@ -130,55 +152,58 @@ git_global_st *git__global_state(void) #elif defined(GIT_THREADS) && defined(_POSIX_THREADS) static pthread_key_t _tls_key; -static int _tls_init = 0; +static pthread_once_t _once_init = PTHREAD_ONCE_INIT; +static git_atomic git__n_inits; +int init_error = 0; static void cb__free_status(void *st) { git__free(st); } -int git_threads_init(void) +static void init_once(void) { - int error = 0; - - if (_tls_init) - return 0; - - if (git_mutex_init(&git__mwindow_mutex)) - return -1; + if ((init_error = git_mutex_init(&git__mwindow_mutex)) != 0) + return; pthread_key_create(&_tls_key, &cb__free_status); /* Initialize any other subsystems that have global state */ - if ((error = git_hash_global_init()) >= 0 && - (error = git_futils_dirs_global_init()) >= 0) - _tls_init = 1; + if ((init_error = git_hash_global_init()) >= 0) + init_error = git_futils_dirs_global_init(); GIT_MEMORY_BARRIER; +} - return error; +int git_threads_init(void) +{ + pthread_once(&_once_init, init_once); + git_atomic_inc(&git__n_inits); + return init_error; } void git_threads_shutdown(void) { + pthread_once_t new_once = PTHREAD_ONCE_INIT; + + if (git_atomic_dec(&git__n_inits) > 0) return; + /* Shut down any subsystems that have global state */ git__shutdown(); - if (_tls_init) { - void *ptr = pthread_getspecific(_tls_key); - pthread_setspecific(_tls_key, NULL); - git__free(ptr); - } + void *ptr = pthread_getspecific(_tls_key); + pthread_setspecific(_tls_key, NULL); + git__free(ptr); pthread_key_delete(_tls_key); - _tls_init = 0; git_mutex_free(&git__mwindow_mutex); + _once_init = new_once; } git_global_st *git__global_state(void) { void *ptr; - assert(_tls_init); + assert(git__n_inits.val); if ((ptr = pthread_getspecific(_tls_key)) != NULL) return ptr; diff --git a/src/ignore.c b/src/ignore.c index 0c35d0431..27d7c7ec4 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,4 +1,5 @@ #include "git2/ignore.h" +#include "common.h" #include "ignore.h" #include "attr.h" #include "path.h" diff --git a/src/index.c b/src/index.c index 21a8d31d1..5cdd40aaa 100644 --- a/src/index.c +++ b/src/index.c @@ -461,9 +461,10 @@ int git_index_read(git_index *index) return create_index_error(-1, "Failed to read index: The index is in-memory only"); - if (!index->on_disk || git_path_exists(index->index_file_path) == false) { + index->on_disk = git_path_exists(index->index_file_path); + + if (!index->on_disk) { git_index_clear(index); - index->on_disk = 0; return 0; } @@ -579,7 +580,8 @@ const git_index_entry *git_index_get_bypath( return git_index_get_byindex(index, pos); } -void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st) +void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode) { entry->ctime.seconds = (git_time_t)st->st_ctime; entry->mtime.seconds = (git_time_t)st->st_mtime; @@ -587,7 +589,8 @@ void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st) /* entry->ctime.nanoseconds = st->st_ctimensec; */ entry->dev = st->st_rdev; entry->ino = st->st_ino; - entry->mode = index_create_mode(st->st_mode); + entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? + index_create_mode(0666) : index_create_mode(st->st_mode); entry->uid = st->st_uid; entry->gid = st->st_gid; entry->file_size = st->st_size; @@ -631,7 +634,7 @@ static int index_entry_init( entry = git__calloc(1, sizeof(git_index_entry)); GITERR_CHECK_ALLOC(entry); - git_index_entry__init_from_stat(entry, &st); + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); entry->oid = oid; entry->path = git__strdup(rel_path); diff --git a/src/index.h b/src/index.h index 40577e105..4c448fabf 100644 --- a/src/index.h +++ b/src/index.h @@ -48,7 +48,7 @@ struct git_index_conflict_iterator { }; extern void git_index_entry__init_from_stat( - git_index_entry *entry, struct stat *st); + git_index_entry *entry, struct stat *st, bool trust_mode); extern size_t git_index__prefix_position(git_index *index, const char *path); diff --git a/src/indexer.c b/src/indexer.c index 3b160df5d..9f55c8b2c 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -18,6 +18,7 @@ #include "filebuf.h" #include "oid.h" #include "oidmap.h" +#include "compress.h" #define UINT31_MAX (0x7FFFFFFF) @@ -33,6 +34,7 @@ struct git_indexer_stream { opened_pack :1, have_stream :1, have_delta :1; + struct git_pack_header hdr; struct git_pack_file *pack; git_filebuf pack_file; git_off_t off; @@ -48,9 +50,12 @@ struct git_indexer_stream { void *progress_payload; char objbuf[8*1024]; + /* Needed to look up objects which we want to inject to fix a thin pack */ + git_odb *odb; + /* Fields for calculating the packfile trailer (hash of everything before it) */ char inbuf[GIT_OID_RAWSZ]; - int inbuf_len; + size_t inbuf_len; git_hash_ctx trailer; }; @@ -114,6 +119,7 @@ static int objects_cmp(const void *a, const void *b) int git_indexer_stream_new( git_indexer_stream **out, const char *prefix, + git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_payload) { @@ -124,6 +130,7 @@ int git_indexer_stream_new( idx = git__calloc(1, sizeof(git_indexer_stream)); GITERR_CHECK_ALLOC(idx); + idx->odb = odb; idx->progress_cb = progress_cb; idx->progress_payload = progress_payload; git_hash_ctx_init(&idx->trailer); @@ -309,17 +316,10 @@ on_error: return -1; } -static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) +static int save_entry(git_indexer_stream *idx, struct entry *entry, struct git_pack_entry *pentry, git_off_t entry_start) { int i, error; khiter_t k; - git_oid oid; - size_t entry_size; - struct entry *entry; - struct git_pack_entry *pentry; - - entry = git__calloc(1, sizeof(*entry)); - GITERR_CHECK_ALLOC(entry); if (entry_start > UINT31_MAX) { entry->offset = UINT32_MAX; @@ -328,6 +328,34 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent entry->offset = (uint32_t)entry_start; } + pentry->offset = entry_start; + k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error); + if (!error) + return -1; + + kh_value(idx->pack->idx_cache, k) = pentry; + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + return -1; + + for (i = entry->oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; +} + +static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) +{ + git_oid oid; + size_t entry_size; + struct entry *entry; + struct git_pack_entry *pentry; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + if (git_odb__hashobj(&oid, obj) < 0) { giterr_set(GITERR_INDEXER, "Failed to hash object"); goto on_error; @@ -337,15 +365,6 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent GITERR_CHECK_ALLOC(pentry); git_oid_cpy(&pentry->sha1, &oid); - pentry->offset = entry_start; - k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error); - if (!error) { - git__free(pentry); - goto on_error; - } - - kh_value(idx->pack->idx_cache, k) = pentry; - git_oid_cpy(&entry->oid, &oid); entry->crc = crc32(0L, Z_NULL, 0); @@ -353,15 +372,7 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) goto on_error; - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - goto on_error; - - for (i = oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; + return save_entry(idx, entry, pentry, entry_start); on_error: git__free(entry); @@ -378,13 +389,13 @@ static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress * /* Hash everything but the last 20B of input */ static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t size) { - int to_expell, to_keep; + size_t to_expell, to_keep; if (size == 0) return; /* Easy case, dump the buffer and the data minus the last 20 bytes */ - if (size >= 20) { + if (size >= GIT_OID_RAWSZ) { git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); @@ -402,8 +413,8 @@ static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t } /* We need to partially drain the buffer and then append */ - to_expell = abs(size - (GIT_OID_RAWSZ - idx->inbuf_len)); - to_keep = abs(idx->inbuf_len - to_expell); + to_keep = GIT_OID_RAWSZ - size; + to_expell = idx->inbuf_len - to_keep; git_hash_update(&idx->trailer, idx->inbuf, to_expell); @@ -415,8 +426,8 @@ static void hash_partially(git_indexer_stream *idx, const uint8_t *data, size_t int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats) { int error = -1; - struct git_pack_header hdr; size_t processed; + struct git_pack_header *hdr = &idx->hdr; git_mwindow_file *mwf = &idx->pack->mwf; assert(idx && data && stats); @@ -443,14 +454,14 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz if (!idx->parsed_header) { unsigned int total_objects; - if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) + if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) return 0; - if (parse_header(&hdr, idx->pack) < 0) + if (parse_header(&idx->hdr, idx->pack) < 0) return -1; idx->parsed_header = 1; - idx->nr_objects = ntohl(hdr.hdr_entries); + idx->nr_objects = ntohl(hdr->hdr_entries); idx->off = sizeof(struct git_pack_header); /* for now, limit to 2^32 objects */ @@ -471,6 +482,9 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz return -1; stats->received_objects = 0; + stats->local_objects = 0; + stats->total_deltas = 0; + stats->indexed_deltas = 0; processed = stats->indexed_objects = 0; stats->total_objects = total_objects; do_progress_callback(idx, stats); @@ -556,6 +570,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz stats->received_objects++; if (do_progress_callback(idx, stats) != 0) { + giterr_clear(); error = GIT_EUSER; goto on_error; } @@ -590,24 +605,229 @@ static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char return git_buf_oom(path) ? -1 : 0; } -static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats) +/** + * Rewind the packfile by the trailer, as we might need to fix the + * packfile by injecting objects at the tail and must overwrite it. + */ +static git_off_t seek_back_trailer(git_indexer_stream *idx) +{ + git_off_t off; + + if ((off = p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_CUR)) < 0) + return -1; + + idx->pack->mwf.size -= GIT_OID_RAWSZ; + git_mwindow_free_all(&idx->pack->mwf); + + return off; +} + +static int inject_object(git_indexer_stream *idx, git_oid *id) +{ + git_odb_object *obj; + struct entry *entry; + struct git_pack_entry *pentry; + git_oid foo = {{0}}; + unsigned char hdr[64]; + git_buf buf = GIT_BUF_INIT; + git_off_t entry_start; + const void *data; + size_t len, hdr_len; + int error; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + + entry_start = seek_back_trailer(idx); + + if (git_odb_read(&obj, idx->odb, id) < 0) + return -1; + + data = git_odb_object_data(obj); + len = git_odb_object_size(obj); + + entry->crc = crc32(0L, Z_NULL, 0); + + /* Write out the object header */ + hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj)); + git_filebuf_write(&idx->pack_file, hdr, hdr_len); + idx->pack->mwf.size += hdr_len; + entry->crc = crc32(entry->crc, hdr, hdr_len); + + if ((error = git__compress(&buf, data, len)) < 0) + goto cleanup; + + /* And then the compressed object */ + git_filebuf_write(&idx->pack_file, buf.ptr, buf.size); + idx->pack->mwf.size += buf.size; + entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, buf.size)); + git_buf_free(&buf); + + /* Write a fake trailer so the pack functions play ball */ + if ((error = git_filebuf_write(&idx->pack_file, &foo, GIT_OID_RAWSZ)) < 0) + goto cleanup; + + idx->pack->mwf.size += GIT_OID_RAWSZ; + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GITERR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, id); + git_oid_cpy(&entry->oid, id); + idx->off = entry_start + hdr_len + len; + + if ((error = save_entry(idx, entry, pentry, entry_start)) < 0) + git__free(pentry); + +cleanup: + git_odb_object_free(obj); + return error; +} + +static int fix_thin_pack(git_indexer_stream *idx, git_transfer_progress *stats) { + int error, found_ref_delta = 0; unsigned int i; struct delta_info *delta; + size_t size; + git_otype type; + git_mwindow *w = NULL; + git_off_t curpos; + unsigned char *base_info; + unsigned int left = 0; + git_oid base; + + assert(git_vector_length(&idx->deltas) > 0); + if (idx->odb == NULL) { + giterr_set(GITERR_INDEXER, "cannot fix a thin pack without an ODB"); + return -1; + } + + /* Loop until we find the first REF delta */ git_vector_foreach(&idx->deltas, i, delta) { - git_rawobj obj; + curpos = delta->delta_off; + error = git_packfile_unpack_header(&size, &type, &idx->pack->mwf, &w, &curpos); + git_mwindow_close(&w); + if (error < 0) + return error; + + if (type == GIT_OBJ_REF_DELTA) { + found_ref_delta = 1; + break; + } + } + + if (!found_ref_delta) { + giterr_set(GITERR_INDEXER, "no REF_DELTA found, cannot inject object"); + return -1; + } - idx->off = delta->delta_off; - if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) + /* curpos now points to the base information, which is an OID */ + base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); + if (base_info == NULL) { + giterr_set(GITERR_INDEXER, "failed to map delta information"); + return -1; + } + + git_oid_fromraw(&base, base_info); + git_mwindow_close(&w); + + if (inject_object(idx, &base) < 0) + return -1; + + stats->local_objects++; + + return 0; +} + +static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats) +{ + unsigned int i; + struct delta_info *delta; + int progressed = 0; + + while (idx->deltas.length > 0) { + progressed = 0; + git_vector_foreach(&idx->deltas, i, delta) { + git_rawobj obj; + + idx->off = delta->delta_off; + if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) + continue; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + continue; + + git__free(obj.data); + stats->indexed_objects++; + stats->indexed_deltas++; + progressed = 1; + do_progress_callback(idx, stats); + + /* + * Remove this delta from the list and + * decrease i so we don't skip over the next + * delta. + */ + git_vector_remove(&idx->deltas, i); + git__free(delta); + i--; + } + + if (!progressed && (fix_thin_pack(idx, stats) < 0)) { + giterr_set(GITERR_INDEXER, "missing delta bases"); return -1; + } + } + + return 0; +} + +static int update_header_and_rehash(git_indexer_stream *idx, git_transfer_progress *stats) +{ + void *ptr; + size_t chunk = 1024*1024; + git_off_t hashed = 0; + git_mwindow *w = NULL; + git_mwindow_file *mwf; + unsigned int left; + git_hash_ctx *ctx; + + mwf = &idx->pack->mwf; + ctx = &idx->trailer; + + git_hash_ctx_init(ctx); + git_mwindow_free_all(mwf); + + /* Update the header to include the numer of local objects we injected */ + idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); + if (p_lseek(idx->pack_file.fd, 0, SEEK_SET) < 0) { + giterr_set(GITERR_OS, "failed to seek to the beginning of the pack"); + return -1; + } + + if (p_write(idx->pack_file.fd, &idx->hdr, sizeof(struct git_pack_header)) < 0) { + giterr_set(GITERR_OS, "failed to update the pack header"); + return -1; + } - if (hash_and_save(idx, &obj, delta->delta_off) < 0) + /* + * We now use the same technique as before to determine the + * hash. We keep reading up to the end and let + * hash_partially() keep the existing trailer out of the + * calculation. + */ + idx->inbuf_len = 0; + while (hashed < mwf->size) { + ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); + if (ptr == NULL) return -1; - git__free(obj.data); - stats->indexed_objects++; - do_progress_callback(idx, stats); + hash_partially(idx, ptr, left); + hashed += left; + + git_mwindow_close(&w); } return 0; @@ -650,15 +870,31 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress * return -1; } - if (idx->deltas.length > 0) - if (resolve_deltas(idx, stats) < 0) - return -1; + /* Freeze the number of deltas */ + stats->total_deltas = stats->total_objects - stats->indexed_objects; + + if (resolve_deltas(idx, stats) < 0) + return -1; if (stats->indexed_objects != stats->total_objects) { giterr_set(GITERR_INDEXER, "early EOF"); return -1; } + if (stats->local_objects > 0) { + if (update_header_and_rehash(idx, stats) < 0) + return -1; + + git_hash_final(&trailer_hash, &idx->trailer); + if (p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_END) < 0) + return -1; + + if (p_write(idx->pack_file.fd, &trailer_hash, GIT_OID_RAWSZ) < 0) { + giterr_set(GITERR_OS, "failed to update pack trailer"); + return -1; + } + } + git_vector_sort(&idx->objects); git_buf_sets(&filename, idx->pack->pack_name); diff --git a/src/iterator.c b/src/iterator.c index bdc98d22b..c0d7862ff 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -893,6 +893,7 @@ struct fs_iterator { git_index_entry entry; git_buf path; size_t root_len; + uint32_t dirload_flags; int depth; int (*enter_dir_cb)(fs_iterator *self); @@ -986,7 +987,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi) GITERR_CHECK_ALLOC(ff); error = git_path_dirload_with_stat( - fi->path.ptr, fi->root_len, iterator__ignore_case(fi), + fi->path.ptr, fi->root_len, fi->dirload_flags, fi->base.start, fi->base.end, &ff->entries); if (error < 0) { @@ -1174,7 +1175,7 @@ static int fs_iterator__update_entry(fs_iterator *fi) return GIT_ITEROVER; fi->entry.path = ps->path; - git_index_entry__init_from_stat(&fi->entry, &ps->st); + git_index_entry__init_from_stat(&fi->entry, &ps->st, true); /* need different mode here to keep directories during iteration */ fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); @@ -1207,6 +1208,11 @@ static int fs_iterator__initialize( } fi->root_len = fi->path.size; + fi->dirload_flags = + (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) | + (iterator__flag(fi, PRECOMPOSE_UNICODE) ? + GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0); + if ((error = fs_iterator__expand_dir(fi)) < 0) { if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) { giterr_clear(); @@ -1329,7 +1335,7 @@ int git_iterator_for_workdir_ext( const char *start, const char *end) { - int error; + int error, precompose = 0; workdir_iterator *wi; if (!repo_workdir) { @@ -1356,6 +1362,12 @@ int git_iterator_for_workdir_ext( return error; } + /* try to look up precompose and set flag if appropriate */ + if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0) + giterr_clear(); + else if (precompose) + wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + return fs_iterator__initialize(out, &wi->fi, repo_workdir); } diff --git a/src/iterator.h b/src/iterator.h index ea88fa6a2..751e139d0 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -24,13 +24,15 @@ typedef enum { typedef enum { /** ignore case for entry sort order */ - GIT_ITERATOR_IGNORE_CASE = (1 << 0), + GIT_ITERATOR_IGNORE_CASE = (1u << 0), /** force case sensitivity for entry sort order */ - GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), + GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), /** return tree items in addition to blob items */ - GIT_ITERATOR_INCLUDE_TREES = (1 << 2), + GIT_ITERATOR_INCLUDE_TREES = (1u << 2), /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ - GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3), + GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), + /** convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), } git_iterator_flag_t; typedef struct { diff --git a/src/merge_file.c b/src/merge_file.c index c3477ccb9..48fc46e57 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -47,7 +47,7 @@ GIT_INLINE(int) merge_file_best_mode( * assume executable. Otherwise, if any mode changed from the ancestor, * use that one. */ - if (GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) { + if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) { if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE || theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE) return GIT_FILEMODE_BLOB_EXECUTABLE; @@ -124,6 +124,13 @@ git_otype git_odb_object_type(git_odb_object *object) return object->cached.type; } +int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + void git_odb_object_free(git_odb_object *object) { if (object == NULL) @@ -988,7 +995,7 @@ int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer if (b->writepack != NULL) { ++writes; - error = b->writepack(out, b, progress_cb, progress_payload); + error = b->writepack(out, b, db, progress_cb, progress_payload); } } diff --git a/src/odb_loose.c b/src/odb_loose.c index 4ff57158d..07dfae539 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -544,7 +544,7 @@ static int locate_object_short_oid( /* Explore directory to find a unique object matching short_oid */ error = git_path_direach( - object_location, fn_locate_object_short_oid, &state); + object_location, 0, fn_locate_object_short_oid, &state); if (error && error != GIT_EUSER) return error; @@ -745,7 +745,7 @@ static int foreach_cb(void *_state, git_buf *path) { struct foreach_state *state = (struct foreach_state *) _state; - return git_path_direach(path, foreach_object_dir_cb, state); + return git_path_direach(path, 0, foreach_object_dir_cb, state); } static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) @@ -768,7 +768,7 @@ static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb state.data = data; state.dir_len = git_buf_len(&buf); - error = git_path_direach(&buf, foreach_cb, &state); + error = git_path_direach(&buf, 0, foreach_cb, &state); git_buf_free(&buf); diff --git a/src/odb_pack.c b/src/odb_pack.c index cadc93a65..4c7c82253 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -331,7 +331,7 @@ static int pack_backend__refresh(git_odb_backend *_backend) git_buf_sets(&path, backend->pack_folder); /* reload all packs */ - error = git_path_direach(&path, packfile_load__cb, (void *)backend); + error = git_path_direach(&path, 0, packfile_load__cb, backend); git_buf_free(&path); @@ -541,6 +541,7 @@ static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) static int pack_backend__writepack(struct git_odb_writepack **out, git_odb_backend *_backend, + git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_payload) { @@ -557,7 +558,7 @@ static int pack_backend__writepack(struct git_odb_writepack **out, GITERR_CHECK_ALLOC(writepack); if (git_indexer_stream_new(&writepack->indexer_stream, - backend->pack_folder, progress_cb, progress_payload) < 0) { + backend->pack_folder, odb, progress_cb, progress_payload) < 0) { git__free(writepack); return -1; } diff --git a/src/pack-objects.c b/src/pack-objects.c index 2a2f36223..93541e974 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -216,52 +216,22 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, assert(ret != 0); kh_value(pb->object_ix, pos) = po; + pb->done = false; + if (pb->progress_cb) { double current_time = git__timer(); if ((current_time - pb->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) { pb->last_progress_report_time = current_time; - pb->progress_cb(GIT_PACKBUILDER_ADDING_OBJECTS, pb->nr_objects, 0, pb->progress_cb_payload); + if (pb->progress_cb(GIT_PACKBUILDER_ADDING_OBJECTS, pb->nr_objects, 0, pb->progress_cb_payload)) { + giterr_clear(); + return GIT_EUSER; + } } } - pb->done = false; return 0; } -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", - * then three bits of "type", - * with the high bit being "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int gen_pack_object_header( - unsigned char *hdr, - unsigned long size, - git_otype type) -{ - unsigned char *hdr_base; - unsigned char c; - - assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA); - - /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ - - c = (unsigned char)((type << 4) | (size & 15)); - size >>= 4; - hdr_base = hdr; - - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - } - *hdr++ = c; - - return (int)(hdr - hdr_base); -} - static int get_delta(void **out, git_odb *odb, git_pobject *po) { git_odb_object *src = NULL, *trg = NULL; @@ -323,7 +293,7 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po) } /* Write header */ - hdr_len = gen_pack_object_header(hdr, size, type); + hdr_len = git_packfile__object_header(hdr, size, type); if (git_buf_put(buf, (char *)hdr, hdr_len) < 0) goto on_error; @@ -591,49 +561,50 @@ static int write_pack(git_packbuilder *pb, enum write_one_status status; struct git_pack_header ph; unsigned int i = 0; + int error = 0; write_order = compute_write_order(pb); - if (write_order == NULL) - goto on_error; + if (write_order == NULL) { + error = -1; + goto done; + } /* Write pack header */ ph.hdr_signature = htonl(PACK_SIGNATURE); ph.hdr_version = htonl(PACK_VERSION); ph.hdr_entries = htonl(pb->nr_objects); - if (cb(&ph, sizeof(ph), data) < 0) - goto on_error; + if ((error = cb(&ph, sizeof(ph), data)) < 0) + goto done; - if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0) - goto on_error; + if ((error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) + goto done; pb->nr_remaining = pb->nr_objects; do { pb->nr_written = 0; for ( ; i < pb->nr_objects; ++i) { po = write_order[i]; - if (write_one(&buf, pb, po, &status) < 0) - goto on_error; - if (cb(buf.ptr, buf.size, data) < 0) - goto on_error; + if ((error = write_one(&buf, pb, po, &status)) < 0) + goto done; + if ((error = cb(buf.ptr, buf.size, data)) < 0) + goto done; git_buf_clear(&buf); } pb->nr_remaining -= pb->nr_written; } while (pb->nr_remaining && i < pb->nr_objects); - git__free(write_order); - git_buf_free(&buf); - if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0) - goto on_error; + if ((error = git_hash_final(&pb->pack_oid, &pb->ctx)) < 0) + goto done; - return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data); + error = cb(pb->pack_oid.id, GIT_OID_RAWSZ, data); -on_error: +done: git__free(write_order); git_buf_free(&buf); - return -1; + return error; } static int write_pack_buf(void *buf, size_t size, void *data) @@ -1287,7 +1258,7 @@ int git_packbuilder_write( PREPARE_PACK; if (git_indexer_stream_new( - &indexer, path, progress_cb, progress_cb_payload) < 0) + &indexer, path, pb->odb, progress_cb, progress_cb_payload) < 0) return -1; ctx.indexer = indexer; diff --git a/src/pack.c b/src/pack.c index e7fb9f1ae..5df0f50b9 100644 --- a/src/pack.c +++ b/src/pack.c @@ -364,6 +364,38 @@ static unsigned char *pack_window_open( return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); } +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", + * then three bits of "type", + * with the high bit being "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int git_packfile__object_header(unsigned char *hdr, unsigned long size, git_otype type) +{ + unsigned char *hdr_base; + unsigned char c; + + assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA); + + /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ + + c = (unsigned char)((type << 4) | (size & 15)); + size >>= 4; + hdr_base = hdr; + + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + } + *hdr++ = c; + + return (int)(hdr - hdr_base); +} + + static int packfile_unpack_header1( unsigned long *usedp, size_t *sizep, diff --git a/src/pack.h b/src/pack.h index aeeac9ce1..ddeefea1d 100644 --- a/src/pack.h +++ b/src/pack.h @@ -112,6 +112,8 @@ typedef struct git_packfile_stream { git_mwindow *mw; } git_packfile_stream; +int git_packfile__object_header(unsigned char *hdr, unsigned long size, git_otype type); + int git_packfile_unpack_header( size_t *size_p, git_otype *type_p, diff --git a/src/path.c b/src/path.c index 9d8e90361..d45751cd1 100644 --- a/src/path.c +++ b/src/path.c @@ -483,23 +483,23 @@ bool git_path_isfile(const char *path) bool git_path_is_empty_dir(const char *path) { - git_buf pathbuf = GIT_BUF_INIT; 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; + if (!git_path_isdir(path)) + return false; - git_buf_printf(&pathbuf, "%s\\*", path); - git_win32_path_from_c(wbuf, git_buf_cstr(&pathbuf)); + 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) { - giterr_set(GITERR_OS, "Couldn't open '%s'", path); - git_buf_free(&pathbuf); + if (INVALID_HANDLE_VALUE == hFind) return false; - } do { if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { @@ -509,50 +509,64 @@ bool git_path_is_empty_dir(const char *path) } while (FindNextFileW(hFind, &ffd) != 0); FindClose(hFind); - git_buf_free(&pathbuf); return retval; } #else -bool git_path_is_empty_dir(const char *path) +static int path_found_entry(void *payload, git_buf *path) { - DIR *dir = NULL; - struct dirent *e; - bool retval = true; + GIT_UNUSED(payload); + return !git_path_is_dot_or_dotdot(path->ptr); +} - if (!git_path_isdir(path)) return false; +bool git_path_is_empty_dir(const char *path) +{ + int error; + git_buf dir = GIT_BUF_INIT; - dir = opendir(path); - if (!dir) { - giterr_set(GITERR_OS, "Couldn't open '%s'", path); + if (!git_path_isdir(path)) return false; - } - while ((e = readdir(dir)) != NULL) { - if (!git_path_is_dot_or_dotdot(e->d_name)) { - giterr_set(GITERR_INVALID, - "'%s' exists and is not an empty directory", path); - retval = false; - break; - } - } - closedir(dir); + if (!(error = git_buf_sets(&dir, path))) + error = git_path_direach(&dir, 0, path_found_entry, NULL); - return retval; + git_buf_free(&dir); + + return !error; } + #endif -int git_path_lstat(const char *path, struct stat *st) +int git_path_set_error(int errno_value, const char *path, const char *action) { - int err = 0; - - if (p_lstat(path, st) < 0) { - err = (errno == ENOENT) ? GIT_ENOTFOUND : -1; - giterr_set(GITERR_OS, "Failed to stat file '%s'", path); + switch (errno_value) { + case ENOENT: + case ENOTDIR: + giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action); + return GIT_ENOTFOUND; + + case EINVAL: + case ENAMETOOLONG: + giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path); + return GIT_EINVALIDSPEC; + + case EEXIST: + giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path); + return GIT_EEXISTS; + + default: + giterr_set(GITERR_OS, "Could not %s '%s'", action, path); + return -1; } +} + +int git_path_lstat(const char *path, struct stat *st) +{ + if (p_lstat(path, st) == 0) + return 0; - return err; + return git_path_set_error(errno, path, "stat"); } static bool _check_dir_contents( @@ -724,14 +738,103 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +bool git_path_has_non_ascii(const char *path, size_t pathlen) +{ + const uint8_t *scan = (const uint8_t *)path, *end; + + for (end = scan + pathlen; scan < end; ++scan) + if (*scan & 0x80) + return true; + + return false; +} + +#ifdef GIT_USE_ICONV + +int git_path_iconv_init_precompose(git_path_iconv_t *ic) +{ + git_buf_init(&ic->buf, 0); + ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); + return 0; +} + +void git_path_iconv_clear(git_path_iconv_t *ic) +{ + if (ic) { + if (ic->map != (iconv_t)-1) + iconv_close(ic->map); + git_buf_free(&ic->buf); + } +} + +int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen) +{ + char *nfd = *in, *nfc; + size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv; + int retry = 1; + + if (!ic || ic->map == (iconv_t)-1 || + !git_path_has_non_ascii(*in, *inlen)) + return 0; + + while (1) { + if (git_buf_grow(&ic->buf, wantlen) < 0) + return -1; + + nfc = ic->buf.ptr + ic->buf.size; + nfclen = ic->buf.asize - ic->buf.size; + + rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); + + ic->buf.size = (nfc - ic->buf.ptr); + + if (rv != (size_t)-1) + break; + + if (errno != E2BIG) + goto fail; + + /* make space for 2x the remaining data to be converted + * (with per retry overhead to avoid infinite loops) + */ + wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + + if (retry++ > 4) + goto fail; + } + + ic->buf.ptr[ic->buf.size] = '\0'; + + *in = ic->buf.ptr; + *inlen = ic->buf.size; + + return 0; + +fail: + giterr_set(GITERR_OS, "Unable to convert unicode path data"); + return -1; +} + +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif + int git_path_direach( git_buf *path, + uint32_t flags, int (*fn)(void *, git_buf *), void *arg) { + int error = 0; ssize_t wd_len; DIR *dir; - struct dirent *de, *de_buf; + path_dirent_data de_data; + struct dirent *de, *de_buf = (struct dirent *)&de_data; + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; if (git_path_to_dir(path) < 0) return -1; @@ -743,99 +846,98 @@ int git_path_direach( return -1; } -#if defined(__sun) || defined(__GNU__) - de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else - de_buf = git__malloc(sizeof(struct dirent)); -#endif + if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_path_iconv_init_precompose(&ic); while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { - int result; + char *de_path = de->d_name; + size_t de_len = strlen(de_path); - if (git_path_is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de_path)) continue; - if (git_buf_puts(path, de->d_name) < 0) { - closedir(dir); - git__free(de_buf); - return -1; - } + if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0 || + (error = git_buf_put(path, de_path, de_len)) < 0) + break; - result = fn(arg, path); + error = fn(arg, path); git_buf_truncate(path, wd_len); /* restore path */ - if (result) { - closedir(dir); - git__free(de_buf); - return GIT_EUSER; + if (error) { + error = GIT_EUSER; + break; } } closedir(dir); - git__free(de_buf); - return 0; + git_path_iconv_clear(&ic); + + return error; } int git_path_dirload( const char *path, size_t prefix_len, size_t alloc_extra, + unsigned int flags, git_vector *contents) { int error, need_slash; DIR *dir; - struct dirent *de, *de_buf; size_t path_len; + path_dirent_data de_data; + struct dirent *de, *de_buf = (struct dirent *)&de_data; + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; + + assert(path && contents); - assert(path != NULL && contents != NULL); path_len = strlen(path); - assert(path_len > 0 && path_len >= prefix_len); + if (!path_len || path_len < prefix_len) { + giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path); + return -1; + } if ((dir = opendir(path)) == NULL) { giterr_set(GITERR_OS, "Failed to open directory '%s'", path); return -1; } -#if defined(__sun) || defined(__GNU__) - de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else - de_buf = git__malloc(sizeof(struct dirent)); -#endif + if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_path_iconv_init_precompose(&ic); path += prefix_len; path_len -= prefix_len; need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0; while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) { - char *entry_path; - size_t entry_len; + char *entry_path, *de_path = de->d_name; + size_t alloc_size, de_len = strlen(de_path); - if (git_path_is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de_path)) continue; - entry_len = strlen(de->d_name); + if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0) + break; - entry_path = git__malloc( - path_len + need_slash + entry_len + 1 + alloc_extra); - GITERR_CHECK_ALLOC(entry_path); + alloc_size = path_len + need_slash + de_len + 1 + alloc_extra; + if ((entry_path = git__calloc(alloc_size, 1)) == NULL) { + error = -1; + break; + } if (path_len) memcpy(entry_path, path, path_len); if (need_slash) entry_path[path_len] = '/'; - memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); - entry_path[path_len + need_slash + entry_len] = '\0'; + memcpy(&entry_path[path_len + need_slash], de_path, de_len); - if (git_vector_insert(contents, entry_path) < 0) { - closedir(dir); - git__free(de_buf); - return -1; - } + if ((error = git_vector_insert(contents, entry_path)) < 0) + break; } closedir(dir); - git__free(de_buf); + git_path_iconv_clear(&ic); if (error != 0) giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); @@ -858,7 +960,7 @@ int git_path_with_stat_cmp_icase(const void *a, const void *b) int git_path_dirload_with_stat( const char *path, size_t prefix_len, - bool ignore_case, + unsigned int flags, const char *start_stat, const char *end_stat, git_vector *contents) @@ -875,13 +977,14 @@ int git_path_dirload_with_stat( return -1; error = git_path_dirload( - path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents); if (error < 0) { git_buf_free(&full); return error; } - strncomp = ignore_case ? git__strncasecmp : git__strncmp; + strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? + git__strncasecmp : git__strncmp; /* stat struct at start of git_path_with_stat, so shift path text */ git_vector_foreach(contents, i, ps) { diff --git a/src/path.h b/src/path.h index b2899e97f..17f4f7726 100644 --- a/src/path.h +++ b/src/path.h @@ -242,21 +242,28 @@ extern int git_path_resolve_relative(git_buf *path, size_t ceiling); */ extern int git_path_apply_relative(git_buf *target, const char *relpath); +enum { + GIT_PATH_DIR_IGNORE_CASE = (1u << 0), + GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), +}; + /** * Walk each directory entry, except '.' and '..', calling fn(state). * - * @param pathbuf buffer the function reads the initial directory + * @param pathbuf Buffer the function reads the initial directory * path from, and updates with each successive entry's name. - * @param fn function to invoke with each entry. The first arg is - * the input state and the second arg is pathbuf. The function - * may modify the pathbuf, but only by appending new text. - * @param state to pass to fn as the first arg. + * @param flags Combination of GIT_PATH_DIR flags. + * @param callback Callback for each entry. Passed the `payload` and each + * successive path inside the directory as a full path. This may + * safely append text to the pathbuf if needed. + * @param payload Passed to callback as first argument. * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ extern int git_path_direach( git_buf *pathbuf, - int (*fn)(void *, git_buf *), - void *state); + uint32_t flags, + int (*callback)(void *payload, git_buf *path), + void *payload); /** * Sort function to order two paths @@ -276,19 +283,19 @@ extern int git_path_cmp( * @param pathbuf Buffer the function reads the directory from and * and updates with each successive name. * @param ceiling Prefix of path at which to stop walking up. If NULL, - * this will walk all the way up to the root. If not a prefix of - * pathbuf, the callback will be invoked a single time on the - * original input path. - * @param fn Function to invoke on each path. The first arg is the - * input satte and the second arg is the pathbuf. The function - * should not modify the pathbuf. + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param callback Function to invoke on each path. Passed the `payload` + * and the buffer containing the current path. The path should not + * be modified in any way. * @param state Passed to fn as the first ath. */ extern int git_path_walk_up( git_buf *pathbuf, const char *ceiling, - int (*fn)(void *state, git_buf *), - void *state); + int (*callback)(void *payload, git_buf *path), + void *payload); /** * Load all directory entries (except '.' and '..') into a vector. @@ -304,12 +311,14 @@ extern int git_path_walk_up( * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. * @param alloc_extra Extra bytes to add to each string allocation in * case you want to append anything funny. + * @param flags Combination of GIT_PATH_DIR flags. * @param contents Vector to fill with directory entry names. */ extern int git_path_dirload( const char *path, size_t prefix_len, size_t alloc_extra, + uint32_t flags, git_vector *contents); @@ -336,7 +345,7 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b); * * @param path The directory to read from * @param prefix_len The trailing part of path to prefix to entry paths - * @param ignore_case How to sort and compare paths with start/end limits + * @param flags GIT_PATH_DIR flags from above * @param start_stat As optimization, only stat values after this prefix * @param end_stat As optimization, only stat values before this prefix * @param contents Vector to fill with git_path_with_stat structures @@ -344,9 +353,88 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b); extern int git_path_dirload_with_stat( const char *path, size_t prefix_len, - bool ignore_case, + uint32_t flags, const char *start_stat, const char *end_stat, git_vector *contents); +enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 }; + +/* + * Determines if a path is equal to or potentially a child of another. + * @param parent The possible parent + * @param child The possible child + */ +GIT_INLINE(int) git_path_equal_or_prefixed( + const char *parent, + const char *child) +{ + const char *p = parent, *c = child; + + while (*p && *c) { + if (*p++ != *c++) + return GIT_PATH_NOTEQUAL; + } + + if (*p != '\0') + return GIT_PATH_NOTEQUAL; + if (*c == '\0') + return GIT_PATH_EQUAL; + if (*c == '/') + return GIT_PATH_PREFIX; + + return GIT_PATH_NOTEQUAL; +} + +/* translate errno to libgit2 error code and set error message */ +extern int git_path_set_error( + int errno_value, const char *path, const char *action); + +/* check if non-ascii characters are present in filename */ +extern bool git_path_has_non_ascii(const char *path, size_t pathlen); + +#define GIT_PATH_REPO_ENCODING "UTF-8" + +#ifdef __APPLE__ +#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" +#else +#define GIT_PATH_NATIVE_ENCODING "UTF-8" +#endif + +#ifdef GIT_USE_ICONV + +#include <iconv.h> + +typedef struct { + iconv_t map; + git_buf buf; +} git_path_iconv_t; + +#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_BUF_INIT } + +/* Init iconv data for converting decomposed UTF-8 to precomposed */ +extern int git_path_iconv_init_precompose(git_path_iconv_t *ic); + +/* Clear allocated iconv data */ +extern void git_path_iconv_clear(git_path_iconv_t *ic); + +/* + * Rewrite `in` buffer using iconv map if necessary, replacing `in` + * pointer internal iconv buffer if rewrite happened. The `in` pointer + * will be left unchanged if no rewrite was needed. + */ +extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); + +#else + +typedef struct { + int unused; +} git_path_iconv_t; +#define GIT_PATH_ICONV_INIT { 0 } +#define git_path_iconv_init_precompose(X) 0 +#define git_path_iconv_clear(X) (void)(X) +#define git_path_iconv(X,Y,Z) 0 + +#endif /* GIT_USE_ICONV */ + #endif diff --git a/src/pathspec.c b/src/pathspec.c index d56d03918..1e7e65e90 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -585,7 +585,7 @@ int git_pathspec_match_tree( int git_pathspec_match_diff( git_pathspec_match_list **out, - git_diff_list *diff, + git_diff *diff, uint32_t flags, git_pathspec *ps) { diff --git a/src/push.c b/src/push.c index 698079253..a799db8d0 100644 --- a/src/push.c +++ b/src/push.c @@ -582,7 +582,7 @@ static int calculate_work(git_push *push) static int do_push(git_push *push) { - int error; + int error = 0; git_transport *transport = push->remote->transport; if (!transport->push) { @@ -611,8 +611,6 @@ static int do_push(git_push *push) (error = transport->push(transport, push)) < 0) goto on_error; - error = 0; - on_error: git_packbuilder_free(push->pb); return error; diff --git a/src/refdb.c b/src/refdb.c index 7df61a577..adb58806e 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -16,6 +16,7 @@ #include "hash.h" #include "refdb.h" #include "refs.h" +#include "reflog.h" int git_refdb_new(git_refdb **out, git_repository *repo) { @@ -203,3 +204,18 @@ int git_refdb_delete(struct git_refdb *db, const char *ref_name) assert(db && db->backend); return db->backend->del(db->backend, ref_name); } + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) +{ + int error; + + assert(db && db->backend); + + if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} diff --git a/src/refdb.h b/src/refdb.h index 3aea37b62..0ee60d911 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -43,4 +43,8 @@ void git_refdb_iterator_free(git_reference_iterator *iter); int git_refdb_write(git_refdb *refdb, git_reference *ref, int force); int git_refdb_delete(git_refdb *refdb, const char *ref_name); +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); +int git_refdb_reflog_write(git_reflog *reflog); + + #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 0ba711eb7..7ce09ba55 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -16,12 +16,14 @@ #include "refdb_fs.h" #include "iterator.h" #include "sortedcache.h" +#include "signature.h" #include <git2/tag.h> #include <git2/object.h> #include <git2/refdb.h> #include <git2/sys/refdb_backend.h> #include <git2/sys/refs.h> +#include <git2/sys/reflog.h> GIT__USE_STRMAP; @@ -56,6 +58,8 @@ typedef struct refdb_fs_backend { git_sortedcache *refcache; int peeling_mode; + git_iterator_flag_t iterator_flags; + uint32_t direach_flags; } refdb_fs_backend; static int packref_cmp(const void *a_, const void *b_) @@ -269,7 +273,8 @@ static int _dirent_loose_load(void *data, git_buf *full_path) return 0; if (git_path_isdir(full_path->ptr)) - return git_path_direach(full_path, _dirent_loose_load, backend); + return git_path_direach( + full_path, backend->direach_flags, _dirent_loose_load, backend); file_path = full_path->ptr + strlen(backend->path); @@ -295,7 +300,8 @@ static int packed_loadloose(refdb_fs_backend *backend) * This will overwrite any old packed entries with their * updated loose versions */ - error = git_path_direach(&refs_path, _dirent_loose_load, backend); + error = git_path_direach( + &refs_path, backend->direach_flags, _dirent_loose_load, backend); git_buf_free(&refs_path); @@ -468,7 +474,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || (error = git_iterator_for_filesystem( - &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0) { + &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) { git_buf_free(&path); return error; } @@ -1067,10 +1073,350 @@ static int setup_namespace(git_buf *path, git_repository *repo) return 0; } +static int reflog_alloc(git_reflog **reflog, const char *name) +{ + git_reflog *log; + + *reflog = NULL; + + log = git__calloc(1, sizeof(git_reflog)); + GITERR_CHECK_ALLOC(log); + + log->ref_name = git__strdup(name); + GITERR_CHECK_ALLOC(log->ref_name); + + if (git_vector_init(&log->entries, 0, NULL) < 0) { + git__free(log->ref_name); + git__free(log); + return -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + const char *ptr; + git_reflog_entry *entry; + +#define seek_forward(_increase) do { \ + if (_increase >= buf_size) { \ + giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ + goto fail; \ + } \ + buf += _increase; \ + buf_size -= _increase; \ + } while (0) + + while (buf_size > GIT_REFLOG_SIZE_MIN) { + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->committer = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(entry->committer); + + if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + ptr = buf; + + /* Seek forward to the end of the signature. */ + while (*buf && *buf != '\t' && *buf != '\n') + seek_forward(1); + + if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) + goto fail; + + if (*buf == '\t') { + /* We got a message. Read everything till we reach LF. */ + seek_forward(1); + ptr = buf; + + while (*buf && *buf != '\n') + seek_forward(1); + + entry->msg = git__strndup(ptr, buf - ptr); + GITERR_CHECK_ALLOC(entry->msg); + } else + entry->msg = NULL; + + while (*buf && *buf == '\n' && buf_size > 1) + seek_forward(1); + + if (git_vector_insert(&log->entries, entry) < 0) + goto fail; + } + + return 0; + +#undef seek_forward + +fail: + if (entry) + git_reflog_entry__free(entry); + + return -1; +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT | O_TRUNC, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + +GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name) +{ + return git_buf_join_n(path, '/', 3, repo->path_repository, GIT_REFLOG_DIR, name); +} + +static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + int error = -1; + git_buf log_path = GIT_BUF_INIT; + git_buf log_file = GIT_BUF_INIT; + git_reflog *log = NULL; + git_repository *repo; + refdb_fs_backend *backend; + + assert(out && _backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (reflog_alloc(&log, name) < 0) + return -1; + + if (retrieve_reflog_path(&log_path, repo, name) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; + + *out = log; + goto success; + +cleanup: + git_reflog_free(log); + +success: + git_buf_free(&log_file); + git_buf_free(&log_path); + + return error; +} + +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) +{ + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; + + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_buf_clear(buf); + + git_buf_puts(buf, raw_old); + git_buf_putc(buf, ' '); + git_buf_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_buf_rtrim(buf); + + if (msg) { + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); + } + + git_buf_putc(buf, '\n'); + + return git_buf_oom(buf); +} + +static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + git_repository *repo; + refdb_fs_backend *backend; + git_buf log_path = GIT_BUF_INIT; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(_backend && reflog); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (retrieve_reflog_path(&log_path, repo, reflog->ref_name) < 0) + return -1; + + if (!git_path_isfile(git_buf_cstr(&log_path))) { + giterr_set(GITERR_INVALID, + "Log file for reference '%s' doesn't exist.", reflog->ref_name); + goto cleanup; + } + + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + int error = 0, fd; + git_buf old_path = GIT_BUF_INIT; + git_buf new_path = GIT_BUF_INIT; + git_buf temp_path = GIT_BUF_INIT; + git_buf normalized = GIT_BUF_INIT; + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && old_name && new_name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if ((error = git_reference__normalize_name( + &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) + return error; + + if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0) + return -1; + + if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0) + return -1; + + if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0) + return -1; + + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) + return -1; + + if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) { + error = -1; + goto cleanup; + } + + p_close(fd); + + if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + goto cleanup; + } + + if (git_path_isdir(git_buf_cstr(&new_path)) && + (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { + error = -1; + goto cleanup; + } + + if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { + error = -1; + goto cleanup; + } + + if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + } + +cleanup: + git_buf_free(&temp_path); + git_buf_free(&old_path); + git_buf_free(&new_path); + git_buf_free(&normalized); + + return error; +} + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) +{ + int error; + git_buf path = GIT_BUF_INIT; + + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + error = retrieve_reflog_path(&path, repo, name); + + if (!error && git_path_exists(path.ptr)) + error = p_unlink(path.ptr); + + git_buf_free(&path); + + return error; + +} + int git_refdb_backend_fs( git_refdb_backend **backend_out, git_repository *repository) { + int t = 0; git_buf path = GIT_BUF_INIT; refdb_fs_backend *backend; @@ -1092,6 +1438,15 @@ int git_refdb_backend_fs( git_buf_free(&path); + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { + backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; + backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE; + } + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) { + backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE; + } + backend->parent.exists = &refdb_fs_backend__exists; backend->parent.lookup = &refdb_fs_backend__lookup; backend->parent.iterator = &refdb_fs_backend__iterator; @@ -1100,6 +1455,10 @@ int git_refdb_backend_fs( backend->parent.rename = &refdb_fs_backend__rename; backend->parent.compress = &refdb_fs_backend__compress; backend->parent.free = &refdb_fs_backend__free; + backend->parent.reflog_read = &refdb_reflog_fs__read; + backend->parent.reflog_write = &refdb_reflog_fs__write; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; *backend_out = (git_refdb_backend *)backend; return 0; diff --git a/src/reflog.c b/src/reflog.c index a6752f618..cebb87d86 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -9,82 +9,16 @@ #include "repository.h" #include "filebuf.h" #include "signature.h" +#include "refdb.h" -static int reflog_init(git_reflog **reflog, const git_reference *ref) -{ - git_reflog *log; - - *reflog = NULL; - - log = git__calloc(1, sizeof(git_reflog)); - GITERR_CHECK_ALLOC(log); - - log->ref_name = git__strdup(ref->name); - GITERR_CHECK_ALLOC(log->ref_name); - - if (git_vector_init(&log->entries, 0, NULL) < 0) { - git__free(log->ref_name); - git__free(log); - return -1; - } - - log->owner = git_reference_owner(ref); - *reflog = log; +#include <git2/sys/refdb_backend.h> - return 0; -} - -static int serialize_reflog_entry( - git_buf *buf, - const git_oid *oid_old, - const git_oid *oid_new, - const git_signature *committer, - const char *msg) +git_reflog_entry *git_reflog_entry__alloc(void) { - char raw_old[GIT_OID_HEXSZ+1]; - char raw_new[GIT_OID_HEXSZ+1]; - - git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); - git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - - git_buf_clear(buf); - - git_buf_puts(buf, raw_old); - git_buf_putc(buf, ' '); - git_buf_puts(buf, raw_new); - - git_signature__writebuf(buf, " ", committer); - - /* drop trailing LF */ - git_buf_rtrim(buf); - - if (msg) { - git_buf_putc(buf, '\t'); - git_buf_puts(buf, msg); - } - - git_buf_putc(buf, '\n'); - - return git_buf_oom(buf); -} - -static int reflog_entry_new(git_reflog_entry **entry) -{ - git_reflog_entry *e; - - assert(entry); - - e = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(e); - - memset(e, 0, sizeof(git_reflog_entry)); - - *entry = e; - - return 0; + return git__calloc(1, sizeof(git_reflog_entry)); } -static void reflog_entry_free(git_reflog_entry *entry) +void git_reflog_entry__free(git_reflog_entry *entry) { git_signature_free(entry->committer); @@ -92,75 +26,6 @@ static void reflog_entry_free(git_reflog_entry *entry) git__free(entry); } -static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) -{ - const char *ptr; - git_reflog_entry *entry; - -#define seek_forward(_increase) do { \ - if (_increase >= buf_size) { \ - giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ - goto fail; \ - } \ - buf += _increase; \ - buf_size -= _increase; \ - } while (0) - - while (buf_size > GIT_REFLOG_SIZE_MIN) { - if (reflog_entry_new(&entry) < 0) - return -1; - - entry->committer = git__malloc(sizeof(git_signature)); - GITERR_CHECK_ALLOC(entry->committer); - - if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); - - if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); - - ptr = buf; - - /* Seek forward to the end of the signature. */ - while (*buf && *buf != '\t' && *buf != '\n') - seek_forward(1); - - if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) - goto fail; - - if (*buf == '\t') { - /* We got a message. Read everything till we reach LF. */ - seek_forward(1); - ptr = buf; - - while (*buf && *buf != '\n') - seek_forward(1); - - entry->msg = git__strndup(ptr, buf - ptr); - GITERR_CHECK_ALLOC(entry->msg); - } else - entry->msg = NULL; - - while (*buf && *buf == '\n' && buf_size > 1) - seek_forward(1); - - if (git_vector_insert(&log->entries, entry) < 0) - goto fail; - } - - return 0; - -#undef seek_forward - -fail: - if (entry) - reflog_entry_free(entry); - - return -1; -} - void git_reflog_free(git_reflog *reflog) { size_t i; @@ -169,10 +34,13 @@ void git_reflog_free(git_reflog *reflog) if (reflog == NULL) return; + if (reflog->db) + GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); + for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - reflog_entry_free(entry); + git_reflog_entry__free(entry); } git_vector_free(&reflog->entries); @@ -180,115 +48,30 @@ void git_reflog_free(git_reflog *reflog) git__free(reflog); } -static int retrieve_reflog_path(git_buf *path, const git_reference *ref) +int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) { - return git_buf_join_n(path, '/', 3, - git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); -} + git_refdb *refdb; + int error; -static int create_new_reflog_file(const char *filepath) -{ - int fd, error; + assert(reflog && repo && name); - if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; - if ((fd = p_open(filepath, - O_WRONLY | O_CREAT | O_TRUNC, - GIT_REFLOG_FILE_MODE)) < 0) - return -1; - - return p_close(fd); -} - -int git_reflog_read(git_reflog **reflog, const git_reference *ref) -{ - int error = -1; - git_buf log_path = GIT_BUF_INIT; - git_buf log_file = GIT_BUF_INIT; - git_reflog *log = NULL; - - assert(reflog && ref); - - *reflog = NULL; - - if (reflog_init(&log, ref) < 0) - return -1; - - if (retrieve_reflog_path(&log_path, ref) < 0) - goto cleanup; - - error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if ((error == GIT_ENOTFOUND) && - ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) - goto cleanup; - - if ((error = reflog_parse(log, - git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) - goto cleanup; - - *reflog = log; - goto success; - -cleanup: - git_reflog_free(log); - -success: - git_buf_free(&log_file); - git_buf_free(&log_path); - - return error; + return git_refdb_reflog_read(reflog, refdb, name); } int git_reflog_write(git_reflog *reflog) { - int error = -1; - unsigned int i; - git_reflog_entry *entry; - git_buf log_path = GIT_BUF_INIT; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; + git_refdb *db; - assert(reflog); + assert(reflog && reflog->db); - if (git_buf_join_n(&log_path, '/', 3, - git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) - return -1; - - if (!git_path_isfile(git_buf_cstr(&log_path))) { - giterr_set(GITERR_INVALID, - "Log file for reference '%s' doesn't exist.", reflog->ref_name); - goto cleanup; - } - - if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) - goto cleanup; - - git_vector_foreach(&reflog->entries, i, entry) { - if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) - goto cleanup; - - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - goto success; - -cleanup: - git_filebuf_cleanup(&fbuf); - -success: - git_buf_free(&log); - git_buf_free(&log_path); - return error; + db = reflog->db; + return db->backend->reflog_write(db->backend, reflog); } -int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, - const git_signature *committer, const char *msg) +int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { git_reflog_entry *entry; const git_reflog_entry *previous; @@ -296,8 +79,8 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, assert(reflog && new_oid && committer); - if (reflog_entry_new(&entry) < 0) - return -1; + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; @@ -333,94 +116,30 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, return 0; cleanup: - reflog_entry_free(entry); + git_reflog_entry__free(entry); return -1; } -int git_reflog_rename(git_reference *ref, const char *new_name) +int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) { - int error = 0, fd; - git_buf old_path = GIT_BUF_INIT; - git_buf new_path = GIT_BUF_INIT; - git_buf temp_path = GIT_BUF_INIT; - git_buf normalized = GIT_BUF_INIT; - - assert(ref && new_name); - - if ((error = git_reference__normalize_name( - &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) - return error; - - if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0) - return -1; - - if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0) - return -1; - - if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0) - return -1; + git_refdb *refdb; + int error; - /* - * Move the reflog to a temporary place. This two-phase renaming is required - * in order to cope with funny renaming use cases when one tries to move a reference - * to a partially colliding namespace: - * - a/b -> a/b/c - * - a/b/c/d -> a/b/c - */ - if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return -1; - if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) { - error = -1; - goto cleanup; - } - - p_close(fd); - - if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) { - giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); - error = -1; - goto cleanup; - } - - if (git_path_isdir(git_buf_cstr(&new_path)) && - (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { - error = -1; - goto cleanup; - } - - if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { - error = -1; - goto cleanup; - } - - if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) { - giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); - error = -1; - } - -cleanup: - git_buf_free(&temp_path); - git_buf_free(&old_path); - git_buf_free(&new_path); - git_buf_free(&normalized); - - return error; + return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); } -int git_reflog_delete(git_reference *ref) +int git_reflog_delete(git_repository *repo, const char *name) { + git_refdb *refdb; int error; - git_buf path = GIT_BUF_INIT; - - error = retrieve_reflog_path(&path, ref); - if (!error && git_path_exists(path.ptr)) - error = p_unlink(path.ptr); - - git_buf_free(&path); + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - return error; + return refdb->backend->reflog_delete(refdb->backend, name); } size_t git_reflog_entrycount(git_reflog *reflog) @@ -429,11 +148,6 @@ size_t git_reflog_entrycount(git_reflog *reflog) return reflog->entries.length; } -GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) -{ - return (total - 1) - idx; -} - const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) { assert(reflog); @@ -469,26 +183,21 @@ const char * git_reflog_entry_message(const git_reflog_entry *entry) return entry->msg; } -int git_reflog_drop( - git_reflog *reflog, - size_t idx, - int rewrite_previous_entry) +int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) { size_t entrycount; git_reflog_entry *entry, *previous; - assert(reflog); - entrycount = git_reflog_entrycount(reflog); entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); if (entry == NULL) { - giterr_set(GITERR_REFERENCE, "No reflog entry at index %"PRIuZ, idx); + giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx); return GIT_ENOTFOUND; } - reflog_entry_free(entry); + git_reflog_entry__free(entry); if (git_vector_remove( &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) @@ -521,3 +230,22 @@ int git_reflog_drop( return 0; } + +int git_reflog_append_to(git_repository *repo, const char *name, const git_oid *id, + const git_signature *committer, const char *msg) +{ + int error; + git_reflog *reflog; + + if ((error = git_reflog_read(&reflog, repo, name)) < 0) + return error; + + if ((error = git_reflog_append(reflog, id, committer, msg)) < 0) + goto cleanup; + + error = git_reflog_write(reflog); + +cleanup: + git_reflog_free(reflog); + return error; +} diff --git a/src/reflog.h b/src/reflog.h index 9444ebd10..2d31ae47d 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -27,9 +27,14 @@ struct git_reflog_entry { }; struct git_reflog { + git_refdb *db; char *ref_name; - git_repository *owner; git_vector entries; }; +GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) +{ + return (total - 1) - idx; +} + #endif /* INCLUDE_reflog_h__ */ diff --git a/src/refs.c b/src/refs.c index c045ab9dc..269c5f54e 100644 --- a/src/refs.c +++ b/src/refs.c @@ -138,6 +138,22 @@ int git_reference_name_to_id( return 0; } +static int reference_normalize_for_repo( + char *out, + size_t out_size, + git_repository *repo, + const char *name) +{ + int precompose; + unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL; + + if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) && + precompose) + flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE; + + return git_reference_normalize_name(out, out_size, name, flags); +} + int git_reference_lookup_resolved( git_reference **ref_out, git_repository *repo, @@ -159,13 +175,13 @@ int git_reference_lookup_resolved( else if (max_nesting < 0) max_nesting = DEFAULT_NESTING_LEVEL; - strncpy(scan_name, name, GIT_REFNAME_MAX); scan_type = GIT_REF_SYMBOLIC; - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; + if ((error = reference_normalize_for_repo( + scan_name, sizeof(scan_name), repo, name)) < 0) + return error; - if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; for (nesting = max_nesting; @@ -173,7 +189,7 @@ int git_reference_lookup_resolved( nesting--) { if (nesting != max_nesting) { - strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); + strncpy(scan_name, ref->target.symbolic, sizeof(scan_name)); git_reference_free(ref); } @@ -467,7 +483,7 @@ int git_reference_rename( if (reference_has_log < 0) return reference_has_log; - if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) + if (reference_has_log && (error = git_reflog_rename(git_reference_owner(ref), git_reference_name(ref), new_name)) < 0) return error; return 0; @@ -711,17 +727,18 @@ static bool is_all_caps_and_underscore(const char *name, size_t len) return true; } +/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ int git_reference__normalize_name( git_buf *buf, const char *name, unsigned int flags) { - // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 - char *current; int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; unsigned int process_flags; bool normalize = (buf != NULL); + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; + assert(name); process_flags = flags; @@ -733,6 +750,14 @@ int git_reference__normalize_name( if (normalize) git_buf_clear(buf); + if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) { + size_t namelen = strlen(current); + if ((error = git_path_iconv_init_precompose(&ic)) < 0 || + (error = git_path_iconv(&ic, ¤t, &namelen)) < 0) + goto cleanup; + error = GIT_EINVALIDSPEC; + } + while (true) { segment_len = ensure_segment_validity(current); if (segment_len < 0) { @@ -809,6 +834,8 @@ cleanup: if (error && normalize) git_buf_free(buf); + git_path_iconv_clear(&ic); + return error; } @@ -983,9 +1010,9 @@ static int peel_error(int error, git_reference *ref, const char* msg) } int git_reference_peel( - git_object **peeled, - git_reference *ref, - git_otype target_type) + git_object **peeled, + git_reference *ref, + git_otype target_type) { git_reference *resolved = NULL; git_object *target = NULL; @@ -1027,24 +1054,19 @@ cleanup: return error; } -int git_reference__is_valid_name( - const char *refname, - unsigned int flags) +int git_reference__is_valid_name(const char *refname, unsigned int flags) { - int error; - - error = git_reference__normalize_name(NULL, refname, flags) == 0; - giterr_clear(); + if (git_reference__normalize_name(NULL, refname, flags) < 0) { + giterr_clear(); + return false; + } - return error; + return true; } -int git_reference_is_valid_name( - const char *refname) +int git_reference_is_valid_name(const char *refname) { - return git_reference__is_valid_name( - refname, - GIT_REF_FORMAT_ALLOW_ONELEVEL); + return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL); } const char *git_reference_shorthand(git_reference *ref) diff --git a/src/refs.h b/src/refs.h index 4b91c25e8..80c7703fc 100644 --- a/src/refs.h +++ b/src/refs.h @@ -46,6 +46,8 @@ #define GIT_STASH_FILE "stash" #define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE +#define GIT_REF_FORMAT__PRECOMPOSE_UNICODE (1u << 16) + #define GIT_REFNAME_MAX 1024 struct git_reference { diff --git a/src/remote.c b/src/remote.c index ccedf2386..bdfa08642 100644 --- a/src/remote.c +++ b/src/remote.c @@ -10,6 +10,7 @@ #include "git2/oid.h" #include "git2/net.h" +#include "common.h" #include "config.h" #include "repository.h" #include "remote.h" diff --git a/src/repository.c b/src/repository.c index 0d7c09484..c5ce8425f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -843,10 +843,6 @@ fail: static bool is_chmod_supported(const char *file_path) { struct stat st1, st2; - static int _is_supported = -1; - - if (_is_supported > -1) - return _is_supported; if (p_stat(file_path, &st1) < 0) return false; @@ -857,27 +853,19 @@ static bool is_chmod_supported(const char *file_path) if (p_stat(file_path, &st2) < 0) return false; - _is_supported = (st1.st_mode != st2.st_mode); - - return _is_supported; + return (st1.st_mode != st2.st_mode); } static bool is_filesystem_case_insensitive(const char *gitdir_path) { git_buf path = GIT_BUF_INIT; - static int _is_insensitive = -1; + int is_insensitive = -1; - if (_is_insensitive > -1) - return _is_insensitive; - - if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0) - goto cleanup; + if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg")) + is_insensitive = git_path_exists(git_buf_cstr(&path)); - _is_insensitive = git_path_exists(git_buf_cstr(&path)); - -cleanup: git_buf_free(&path); - return _is_insensitive; + return is_insensitive; } static bool are_symlinks_supported(const char *wd_path) @@ -885,26 +873,77 @@ static bool are_symlinks_supported(const char *wd_path) git_buf path = GIT_BUF_INIT; int fd; struct stat st; - static int _symlinks_supported = -1; - - if (_symlinks_supported > -1) - return _symlinks_supported; + int symlinks_supported = -1; if ((fd = git_futils_mktmp(&path, wd_path)) < 0 || p_close(fd) < 0 || p_unlink(path.ptr) < 0 || p_symlink("testing", path.ptr) < 0 || p_lstat(path.ptr, &st) < 0) - _symlinks_supported = false; + symlinks_supported = false; else - _symlinks_supported = (S_ISLNK(st.st_mode) != 0); + symlinks_supported = (S_ISLNK(st.st_mode) != 0); (void)p_unlink(path.ptr); git_buf_free(&path); - return _symlinks_supported; + return symlinks_supported; +} + +#ifdef GIT_USE_ICONV + +static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX"; +static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX"; + +/* Check if the platform is decomposing unicode data for us. We will + * emulate core Git and prefer to use precomposed unicode data internally + * on these platforms, composing the decomposed unicode on the fly. + * + * This mainly happens on the Mac where HDFS stores filenames as + * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will + * return decomposed unicode from readdir() even when the actual + * filesystem is storing precomposed unicode. + */ +static bool does_fs_decompose_unicode_paths(const char *wd_path) +{ + git_buf path = GIT_BUF_INIT; + int fd; + bool found_decomposed = false; + char tmp[6]; + + /* Create a file using a precomposed path and then try to find it + * using the decomposed name. If the lookup fails, then we will mark + * that we should precompose unicode for this repository. + */ + if (git_buf_joinpath(&path, wd_path, nfc_file) < 0 || + (fd = p_mkstemp(path.ptr)) < 0) + goto done; + p_close(fd); + + /* record trailing digits generated by mkstemp */ + memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp)); + + /* try to look up as NFD path */ + if (git_buf_joinpath(&path, wd_path, nfd_file) < 0) + goto done; + memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp)); + + found_decomposed = git_path_exists(path.ptr); + + /* remove temporary file (using original precomposed path) */ + if (git_buf_joinpath(&path, wd_path, nfc_file) < 0) + goto done; + memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp)); + + (void)p_unlink(path.ptr); + +done: + git_buf_free(&path); + return found_decomposed; } +#endif + static int create_empty_file(const char *path, mode_t mode) { int fd; @@ -922,71 +961,131 @@ static int create_empty_file(const char *path, mode_t mode) return 0; } +static int repo_local_config( + git_config **out, + git_buf *config_dir, + git_repository *repo, + const char *repo_dir) +{ + int error = 0; + git_config *parent; + const char *cfg_path; + + if (git_buf_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; + cfg_path = git_buf_cstr(config_dir); + + /* make LOCAL config if missing */ + if (!git_path_isfile(cfg_path) && + (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) + return error; + + /* if no repo, just open that file directly */ + if (!repo) + return git_config_open_ondisk(out, cfg_path); + + /* otherwise, open parent config and get that level */ + if ((error = git_repository_config__weakptr(&parent, repo)) < 0) + return error; + + if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { + giterr_clear(); + + if (!(error = git_config_add_file_ondisk( + parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false))) + error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); + } + + git_config_free(parent); + + return error; +} + +static int repo_init_fs_configs( + git_config *cfg, + const char *cfg_path, + const char *repo_dir, + const char *work_dir, + bool update_ignorecase) +{ + int error = 0; + + if (!work_dir) + work_dir = repo_dir; + + if ((error = git_config_set_bool( + cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) + return error; + + if (!are_symlinks_supported(work_dir)) { + if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) + giterr_clear(); + + if (update_ignorecase) { + if (is_filesystem_case_insensitive(repo_dir)) { + if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) + giterr_clear(); + } + +#ifdef GIT_USE_ICONV + if ((error = git_config_set_bool( + cfg, "core.precomposeunicode", + does_fs_decompose_unicode_paths(work_dir))) < 0) + return error; +#endif + + return 0; +} + static int repo_init_config( const char *repo_dir, const char *work_dir, - git_repository_init_options *opts) + uint32_t flags, + uint32_t mode) { int error = 0; git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; + bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); + bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); -#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\ - if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ - goto cleanup; } while (0) + if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) + goto cleanup; - if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) - return -1; + if (is_reinit && (error = check_repositoryformatversion(config)) < 0) + goto cleanup; - if (!git_path_isfile(git_buf_cstr(&cfg_path)) && - create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) { - git_buf_free(&cfg_path); - return -1; - } +#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ + if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ + goto cleanup; } while (0) - if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { - git_buf_free(&cfg_path); - return -1; - } + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); - if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 && - (error = check_repositoryformatversion(config)) < 0) + if ((error = repo_init_fs_configs( + config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) goto cleanup; - SET_REPO_CONFIG( - bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); - SET_REPO_CONFIG( - int32, "core.repositoryformatversion", GIT_REPO_VERSION); - SET_REPO_CONFIG( - bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); - - if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) { + if (!is_bare) { SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - if (!are_symlinks_supported(work_dir)) - SET_REPO_CONFIG(bool, "core.symlinks", false); - - if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) SET_REPO_CONFIG(string, "core.worktree", work_dir); - } - else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) { + else if (is_reinit) { if (git_config_delete_entry(config, "core.worktree") < 0) giterr_clear(); } - } else { - if (!are_symlinks_supported(repo_dir)) - SET_REPO_CONFIG(bool, "core.symlinks", false); } - if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) && - is_filesystem_case_insensitive(repo_dir)) - SET_REPO_CONFIG(bool, "core.ignorecase", true); - - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { + if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { SET_REPO_CONFIG(int32, "core.sharedrepository", 1); SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); } - else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) { + else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { SET_REPO_CONFIG(int32, "core.sharedrepository", 2); SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); } @@ -998,6 +1097,41 @@ cleanup: return error; } +static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) +{ + git_repository *smrepo = NULL; + GIT_UNUSED(n); GIT_UNUSED(p); + + if (git_submodule_open(&smrepo, sm) < 0 || + git_repository_reinit_filesystem(smrepo, true) < 0) + giterr_clear(); + git_repository_free(smrepo); + + return 0; +} + +int git_repository_reinit_filesystem(git_repository *repo, int recurse) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + git_config *config = NULL; + const char *repo_dir = git_repository_path(repo); + + if (!(error = repo_local_config(&config, &path, repo, repo_dir))) + error = repo_init_fs_configs( + config, path.ptr, repo_dir, git_repository_workdir(repo), true); + + git_config_free(config); + git_buf_free(&path); + + git_repository__cvar_cache_clear(repo); + + if (!repo->is_bare && recurse) + (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); + + return error; +} + static int repo_write_template( const char *git_dir, bool allow_overwrite, @@ -1386,22 +1520,22 @@ int git_repository_init_ext( opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; error = repo_init_config( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts); + repo_path.ptr, wd_path.ptr, opts->flags, opts->mode); /* TODO: reinitialize the templates */ } else { if (!(error = repo_init_structure( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) && + repo_path.ptr, wd_path.ptr, opts)) && !(error = repo_init_config( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts))) + repo_path.ptr, wd_path.ptr, opts->flags, opts->mode))) error = repo_init_create_head( - git_buf_cstr(&repo_path), opts->initial_head); + repo_path.ptr, opts->initial_head); } if (error < 0) goto cleanup; - error = git_repository_open(out, git_buf_cstr(&repo_path)); + error = git_repository_open(out, repo_path.ptr); if (!error && opts->origin_url) error = repo_init_create_origin(*out, opts->origin_url); diff --git a/src/repository.h b/src/repository.h index 12dc50d51..832df3bd2 100644 --- a/src/repository.h +++ b/src/repository.h @@ -37,6 +37,7 @@ typedef enum { GIT_CVAR_IGNORESTAT, /* core.ignorestat */ GIT_CVAR_TRUSTCTIME, /* core.trustctime */ GIT_CVAR_ABBREV, /* core.abbrev */ + GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -86,6 +87,8 @@ typedef enum { GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE, /* core.abbrev */ GIT_ABBREV_DEFAULT = 7, + /* core.precomposeunicode */ + GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, } git_cvar_value; diff --git a/src/reset.c b/src/reset.c index cea212a93..a9780bfbc 100644 --- a/src/reset.c +++ b/src/reset.c @@ -24,10 +24,9 @@ int git_reset_default( { git_object *commit = NULL; git_tree *tree = NULL; - git_diff_list *diff = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i; - git_diff_delta *delta; + size_t i, max_i; git_index_entry entry; int error; git_index *index = NULL; @@ -58,7 +57,9 @@ int git_reset_default( &diff, repo, tree, index, &opts)) < 0) goto cleanup; - git_vector_foreach(&diff->deltas, i, delta) { + 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; @@ -85,7 +86,7 @@ cleanup: git_object_free(commit); git_tree_free(tree); git_index_free(index); - git_diff_list_free(diff); + git_diff_free(diff); return error; } @@ -135,7 +136,7 @@ int git_reset( if (reset_type == GIT_RESET_HARD) { /* overwrite working directory with HEAD */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_SKIP_UNMERGED; if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) goto cleanup; diff --git a/src/revparse.c b/src/revparse.c index e470a954d..c120b466f 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -160,7 +160,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) goto cleanup; - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) goto cleanup; numentries = git_reflog_entrycount(reflog); @@ -208,7 +208,7 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide const git_reflog_entry *entry; bool search_by_pos = (identifier <= 100000000); - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) return -1; numentries = git_reflog_entrycount(reflog); diff --git a/src/stash.c b/src/stash.c index 7742eee19..083c2a4cd 100644 --- a/src/stash.c +++ b/src/stash.c @@ -153,65 +153,61 @@ cleanup: return error; } -struct cb_data { - git_index *index; - - int error; - +struct stash_update_rules { bool include_changed; bool include_untracked; bool include_ignored; }; -static int update_index_cb( - const git_diff_delta *delta, - float progress, - void *payload) +static int stash_update_index_from_diff( + git_index *index, + const git_diff *diff, + struct stash_update_rules *data) { - struct cb_data *data = (struct cb_data *)payload; - const char *add_path = NULL; - - GIT_UNUSED(progress); - - switch (delta->status) { - case GIT_DELTA_IGNORED: - if (data->include_ignored) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_UNTRACKED: - if (data->include_untracked) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - if (data->include_changed) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_DELETED: - if (!data->include_changed) + int error = 0; + size_t d, max_d = git_diff_num_deltas(diff); + + for (d = 0; !error && d < max_d; ++d) { + const char *add_path = NULL; + const git_diff_delta *delta = git_diff_get_delta(diff, d); + + switch (delta->status) { + case GIT_DELTA_IGNORED: + if (data->include_ignored) + add_path = delta->new_file.path; break; - if (git_index_find(NULL, data->index, delta->old_file.path) == 0) - data->error = git_index_remove( - data->index, delta->old_file.path, 0); - break; - - default: - /* Unimplemented */ - giterr_set( - GITERR_INVALID, - "Cannot update index. Unimplemented status (%d)", - delta->status); - data->error = -1; - break; - } - if (add_path != NULL) - data->error = git_index_add_bypath(data->index, add_path); + case GIT_DELTA_UNTRACKED: + if (data->include_untracked) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + if (data->include_changed) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_DELETED: + if (data->include_changed && + !git_index_find(NULL, index, delta->old_file.path)) + error = git_index_remove(index, delta->old_file.path, 0); + break; + + default: + /* Unimplemented */ + giterr_set( + GITERR_INVALID, + "Cannot update index. Unimplemented status (%d)", + delta->status); + return -1; + } - return data->error; + if (add_path != NULL) + error = git_index_add_bypath(index, add_path); + } + + return error; } static int build_untracked_tree( @@ -221,15 +217,13 @@ static int build_untracked_tree( uint32_t flags) { git_tree *i_tree = NULL; - git_diff_list *diff = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct cb_data data = {0}; + struct stash_update_rules data = {0}; int error; git_index_clear(index); - data.index = index; - if (flags & GIT_STASH_INCLUDE_UNTRACKED) { opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; @@ -248,18 +242,13 @@ static int build_untracked_tree( &diff, git_index_owner(index), i_tree, &opts)) < 0) goto cleanup; - if ((error = git_diff_foreach( - diff, update_index_cb, NULL, NULL, &data)) < 0) - { - if (error == GIT_EUSER) - error = data.error; + if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) goto cleanup; - } error = build_tree_from_index(tree_out, index); cleanup: - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(i_tree); return error; } @@ -311,9 +300,9 @@ static int build_workdir_tree( { git_repository *repo = git_index_owner(index); git_tree *b_tree = NULL; - git_diff_list *diff = NULL, *diff2 = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct cb_data data = {0}; + struct stash_update_rules data = {0}; int error; opts.flags = GIT_DIFF_IGNORE_SUBMODULES; @@ -321,33 +310,19 @@ static int build_workdir_tree( if ((error = git_commit_tree(&b_tree, b_commit)) < 0) goto cleanup; - if ((error = git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts)) < 0) - goto cleanup; - - if ((error = git_diff_index_to_workdir(&diff2, repo, NULL, &opts)) < 0) - goto cleanup; - - if ((error = git_diff_merge(diff, diff2)) < 0) + if ((error = git_diff_tree_to_workdir_with_index( + &diff, repo, b_tree, &opts)) < 0) goto cleanup; - data.index = index; data.include_changed = true; - if ((error = git_diff_foreach( - diff, update_index_cb, NULL, NULL, &data)) < 0) - { - if (error == GIT_EUSER) - error = data.error; + if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) goto cleanup; - } - - if ((error = build_tree_from_index(tree_out, index)) < 0) - goto cleanup; + error = build_tree_from_index(tree_out, index); cleanup: - git_diff_list_free(diff); - git_diff_list_free(diff2); + git_diff_free(diff); git_tree_free(b_tree); return error; @@ -436,14 +411,16 @@ static int update_reflog( const git_signature *stasher, const char *message) { - git_reference *stash = NULL; + git_reference *stash; git_reflog *reflog = NULL; int error; if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + git_reference_free(stash); + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE) < 0)) goto cleanup; if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0) @@ -453,7 +430,6 @@ static int update_reflog( goto cleanup; cleanup: - git_reference_free(stash); git_reflog_free(reflog); return error; } @@ -599,7 +575,7 @@ int git_stash_foreach( if (error < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); @@ -633,7 +609,7 @@ int git_stash_drop( if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) return error; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); diff --git a/src/status.c b/src/status.c index be40b9f83..2b84794b5 100644 --- a/src/status.c +++ b/src/status.c @@ -52,7 +52,7 @@ static unsigned int index_delta2status(const git_diff_delta *head2idx) } static unsigned int workdir_delta2status( - git_diff_list *diff, git_diff_delta *idx2wd) + git_diff *diff, git_diff_delta *idx2wd) { git_status_t st = GIT_STATUS_CURRENT; @@ -361,8 +361,8 @@ void git_status_list_free(git_status_list *status) if (status == NULL) return; - git_diff_list_free(status->head2idx); - git_diff_list_free(status->idx2wd); + git_diff_free(status->head2idx); + git_diff_free(status->idx2wd); git_vector_foreach(&status->paired, i, status_entry) git__free(status_entry); diff --git a/src/status.h b/src/status.h index b58e0ebd6..33008b89c 100644 --- a/src/status.h +++ b/src/status.h @@ -14,8 +14,8 @@ struct git_status_list { git_status_options opts; - git_diff_list *head2idx; - git_diff_list *idx2wd; + git_diff *head2idx; + git_diff *idx2wd; git_vector paired; }; diff --git a/src/submodule.c b/src/submodule.c index 121383b9c..18d80f0a9 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -372,7 +372,8 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) memset(&entry, 0, sizeof(entry)); entry.path = sm->path; - git_index_entry__init_from_stat(&entry, &st); + git_index_entry__init_from_stat( + &entry, &st, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE)); /* calling git_submodule_open will have set sm->wd_oid if possible */ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { @@ -1527,7 +1528,7 @@ static void submodule_get_wd_status( (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; git_tree *sm_head = NULL; git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; @@ -1567,7 +1568,7 @@ static void submodule_get_wd_status( else { if (git_diff_num_deltas(diff) > 0) *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; } @@ -1587,7 +1588,7 @@ static void submodule_get_wd_status( if (git_diff_num_deltas(diff) != untracked) *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; } } diff --git a/src/transports/cred.c b/src/transports/cred.c index 35aaf4f91..cc7fdab4b 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -19,13 +19,13 @@ int git_cred_has_username(git_cred *cred) ret = !!c->username; break; } - case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + case GIT_CREDTYPE_SSH_KEY: { + git_cred_ssh_key *c = (git_cred_ssh_key *)cred; ret = !!c->username; break; } - case GIT_CREDTYPE_SSH_PUBLICKEY: { - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + case GIT_CREDTYPE_SSH_CUSTOM: { + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; ret = !!c->username; break; } @@ -58,7 +58,7 @@ int git_cred_userpass_plaintext_new( { git_cred_userpass_plaintext *c; - assert(cred); + assert(cred && username && password); c = git__malloc(sizeof(git_cred_userpass_plaintext)); GITERR_CHECK_ALLOC(c); @@ -84,10 +84,10 @@ int git_cred_userpass_plaintext_new( return 0; } -static void ssh_keyfile_passphrase_free(struct git_cred *cred) +static void ssh_key_free(struct git_cred *cred) { - git_cred_ssh_keyfile_passphrase *c = - (git_cred_ssh_keyfile_passphrase *)cred; + git_cred_ssh_key *c = + (git_cred_ssh_key *)cred; git__free(c->username); git__free(c->publickey); @@ -104,9 +104,9 @@ static void ssh_keyfile_passphrase_free(struct git_cred *cred) git__free(c); } -static void ssh_publickey_free(struct git_cred *cred) +static void ssh_custom_free(struct git_cred *cred) { - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; git__free(c->username); git__free(c->publickey); @@ -115,22 +115,22 @@ static void ssh_publickey_free(struct git_cred *cred) git__free(c); } -int git_cred_ssh_keyfile_passphrase_new( +int git_cred_ssh_key_new( git_cred **cred, const char *username, const char *publickey, const char *privatekey, const char *passphrase) { - git_cred_ssh_keyfile_passphrase *c; + git_cred_ssh_key *c; assert(cred && privatekey); - c = git__calloc(1, sizeof(git_cred_ssh_keyfile_passphrase)); + c = git__calloc(1, sizeof(git_cred_ssh_key)); GITERR_CHECK_ALLOC(c); - c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE; - c->parent.free = ssh_keyfile_passphrase_free; + c->parent.credtype = GIT_CREDTYPE_SSH_KEY; + c->parent.free = ssh_key_free; if (username) { c->username = git__strdup(username); @@ -154,7 +154,7 @@ int git_cred_ssh_keyfile_passphrase_new( return 0; } -int git_cred_ssh_publickey_new( +int git_cred_ssh_custom_new( git_cred **cred, const char *username, const char *publickey, @@ -162,15 +162,15 @@ int git_cred_ssh_publickey_new( git_cred_sign_callback sign_callback, void *sign_data) { - git_cred_ssh_publickey *c; + git_cred_ssh_custom *c; assert(cred); - c = git__calloc(1, sizeof(git_cred_ssh_publickey)); + c = git__calloc(1, sizeof(git_cred_ssh_custom)); GITERR_CHECK_ALLOC(c); - c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY; - c->parent.free = ssh_publickey_free; + c->parent.credtype = GIT_CREDTYPE_SSH_CUSTOM; + c->parent.free = ssh_custom_free; if (username) { c->username = git__strdup(username); diff --git a/src/transports/smart.c b/src/transports/smart.c index 416eb221f..a681d5f40 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -23,8 +23,13 @@ static int git_smart__recv_cb(gitno_buffer *buf) buf->offset += bytes_read; - if (t->packetsize_cb) - t->packetsize_cb(bytes_read, t->packetsize_payload); + if (t->packetsize_cb && !t->cancelled.val) + if (t->packetsize_cb(bytes_read, t->packetsize_payload)) { + git_atomic_set(&t->cancelled, 1); + + giterr_clear(); + return GIT_EUSER; + } return (int)(buf->offset - old_len); } diff --git a/src/transports/smart.h b/src/transports/smart.h index c52401a3c..7fda27d4e 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -21,6 +21,7 @@ #define GIT_CAP_INCLUDE_TAG "include-tag" #define GIT_CAP_DELETE_REFS "delete-refs" #define GIT_CAP_REPORT_STATUS "report-status" +#define GIT_CAP_THIN_PACK "thin-pack" enum git_pkt_type { GIT_PKT_CMD, @@ -116,10 +117,11 @@ typedef struct transport_smart_caps { side_band_64k:1, include_tag:1, delete_refs:1, - report_status:1; + report_status:1, + thin_pack:1; } transport_smart_caps; -typedef void (*packetsize_cb)(size_t received, void *payload); +typedef int (*packetsize_cb)(size_t received, void *payload); typedef struct { git_transport parent; diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 99da37567..a1f623c78 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -472,6 +472,9 @@ static int buffer_want_with_caps(const git_remote_head *head, transport_smart_ca if (caps->include_tag) git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " "); + if (caps->thin_pack) + git_buf_puts(&str, GIT_CAP_THIN_PACK " "); + if (git_buf_oom(&str)) return -1; diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 156b69e1f..af6e35fa1 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -128,6 +128,12 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { + caps->common = caps->thin_pack = 1; + ptr += strlen(GIT_CAP_THIN_PACK); + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -425,7 +431,7 @@ struct network_packetsize_payload size_t last_fired_bytes; }; -static void network_packetsize(size_t received, void *payload) +static int network_packetsize(size_t received, void *payload) { struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; @@ -435,8 +441,12 @@ static void network_packetsize(size_t received, void *payload) /* Fire notification if the threshold is reached */ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { npp->last_fired_bytes = npp->stats->received_bytes; - npp->callback(npp->stats, npp->payload); + + if (npp->callback(npp->stats, npp->payload)) + return GIT_EUSER; } + + return 0; } int git_smart__download_pack( @@ -450,7 +460,7 @@ int git_smart__download_pack( gitno_buffer *buf = &t->buffer; git_odb *odb; struct git_odb_writepack *writepack = NULL; - int error = -1; + int error = 0; struct network_packetsize_payload npp = {0}; memset(stats, 0, sizeof(git_transfer_progress)); @@ -463,13 +473,14 @@ int git_smart__download_pack( t->packetsize_payload = &npp; /* We might have something in the buffer already from negotiate_fetch */ - if (t->buffer.offset > 0) - t->packetsize_cb(t->buffer.offset, t->packetsize_payload); + if (t->buffer.offset > 0 && !t->cancelled.val) + if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) + git_atomic_set(&t->cancelled, 1); } if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)) - goto on_error; + goto done; /* * If the remote doesn't support the side-band, we can feed @@ -477,28 +488,37 @@ int git_smart__download_pack( * check which one belongs there. */ if (!t->caps.side_band && !t->caps.side_band_64k) { - if (no_sideband(t, writepack, buf, stats) < 0) - goto on_error; - - goto on_success; + error = no_sideband(t, writepack, buf, stats); + goto done; } do { git_pkt *pkt; + /* Check cancellation before network call */ if (t->cancelled.val) { giterr_set(GITERR_NET, "The fetch was cancelled by the user"); error = GIT_EUSER; - goto on_error; + goto done; } - if (recv_pkt(&pkt, buf) < 0) - goto on_error; + if ((error = recv_pkt(&pkt, buf)) < 0) + goto done; + + /* Check cancellation after network call */ + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto done; + } if (pkt->type == GIT_PKT_PROGRESS) { if (t->progress_cb) { git_pkt_progress *p = (git_pkt_progress *) pkt; - t->progress_cb(p->data, p->len, t->message_cb_payload); + if (t->progress_cb(p->data, p->len, t->message_cb_payload)) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + return GIT_EUSER; + } } git__free(pkt); } else if (pkt->type == GIT_PKT_DATA) { @@ -507,7 +527,7 @@ int git_smart__download_pack( git__free(pkt); if (error < 0) - goto on_error; + goto done; } else if (pkt->type == GIT_PKT_FLUSH) { /* A flush indicates the end of the packfile */ git__free(pkt); @@ -515,13 +535,9 @@ int git_smart__download_pack( } } while (1); - if (writepack->commit(writepack, stats) < 0) - goto on_error; - -on_success: - error = 0; + error = writepack->commit(writepack, stats); -on_error: +done: if (writepack) writepack->free(writepack); @@ -828,7 +844,10 @@ static int stream_thunk(void *buf, size_t size, void *data) if ((current_time - payload->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) { payload->last_progress_report_time = current_time; - payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload); + if (payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload)) { + giterr_clear(); + error = GIT_EUSER; + } } } @@ -840,7 +859,7 @@ int git_smart__push(git_transport *transport, git_push *push) transport_smart *t = (transport_smart *)transport; struct push_packbuilder_payload packbuilder_payload = {0}; git_buf pktline = GIT_BUF_INIT; - int error = -1, need_pack = 0; + int error = 0, need_pack = 0; push_spec *spec; unsigned int i; @@ -882,34 +901,31 @@ int git_smart__push(git_transport *transport, git_push *push) } } - if (git_smart__get_push_stream(t, &packbuilder_payload.stream) < 0 || - gen_pktline(&pktline, push) < 0 || - packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0) - goto on_error; + if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || + (error = gen_pktline(&pktline, push)) < 0 || + (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline))) < 0) + goto done; - if (need_pack && git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload) < 0) - goto on_error; + if (need_pack && + (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) + goto done; /* If we sent nothing or the server doesn't support report-status, then * we consider the pack to have been unpacked successfully */ if (!push->specs.length || !push->report_status) push->unpack_ok = 1; - else if (parse_report(&t->buffer, push) < 0) - goto on_error; + else if ((error = parse_report(&t->buffer, push)) < 0) + goto done; /* If progress is being reported write the final report */ if (push->transfer_progress_cb) { push->transfer_progress_cb(push->pb->nr_written, push->pb->nr_objects, packbuilder_payload.last_bytes, push->transfer_progress_cb_payload); } - if (push->status.length && - update_refs_from_report(&t->refs, &push->specs, &push->status) < 0) - goto on_error; - - error = 0; + if (push->status.length) + error = update_refs_from_report(&t->refs, &push->specs, &push->status); -on_error: +done: git_buf_free(&pktline); - return error; } diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 62f3f0bbf..6ce673d5e 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -249,15 +249,15 @@ static int _git_ssh_authenticate_session( rc = libssh2_userauth_password(session, user, c->password); break; } - case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; + case GIT_CREDTYPE_SSH_KEY: { + git_cred_ssh_key *c = (git_cred_ssh_key *)cred; user = c->username ? c->username : user; rc = libssh2_userauth_publickey_fromfile( session, c->username, c->publickey, c->privatekey, c->passphrase); break; } - case GIT_CREDTYPE_SSH_PUBLICKEY: { - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; + case GIT_CREDTYPE_SSH_CUSTOM: { + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; user = c->username ? c->username : user; rc = libssh2_userauth_publickey( @@ -349,7 +349,8 @@ static int _git_ssh_setup_conn( if (t->owner->cred_acquire_cb( &t->cred, t->owner->url, user, GIT_CREDTYPE_USERPASS_PLAINTEXT | - GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, + GIT_CREDTYPE_SSH_KEY | + GIT_CREDTYPE_SSH_CUSTOM, t->owner->cred_acquire_payload) < 0) goto on_error; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 067d6fcc3..a25fa301e 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -265,15 +265,32 @@ static int winhttp_stream_connect(winhttp_stream *s) goto on_error; } - /* Send Content-Type header -- only necessary on a POST */ if (post_verb == s->verb) { + /* Send Content-Type and Accept headers -- only necessary on a POST */ git_buf_clear(&buf); - if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) + if (git_buf_printf(&buf, + "Content-Type: application/x-git-%s-request", + s->service) < 0) goto on_error; git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, + "Accept: application/x-git-%s-result", + s->service) < 0) + goto on_error; + + git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { giterr_set(GITERR_OS, "Failed to add a header to the request"); goto on_error; } diff --git a/src/tree.c b/src/tree.c index 0bdf9a93e..bb59ff82b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -237,7 +237,12 @@ void git_tree__free(void *_tree) git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) { - return (git_filemode_t)entry->attr; + return normalize_filemode(entry->attr); +} + +git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) +{ + return entry->attr; } const char *git_tree_entry_name(const git_tree_entry *entry) @@ -386,8 +391,6 @@ int git_tree__parse(void *_tree, git_odb_object *odb_obj) if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer) return tree_error("Failed to parse tree. Can't parse filemode", NULL); - attr = normalize_filemode(attr); /* make sure to normalize the filemode */ - if (*buffer++ != ' ') return tree_error("Failed to parse tree. Object is corrupted", NULL); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 2f490529c..18f717b0f 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -125,8 +125,8 @@ static int do_lstat( errno = ENOENT; - /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the - * file path is a regular file,otherwise ENOENT must be set. + /* To match POSIX behavior, set ENOTDIR when any of the folders in the + * file path is a regular file, otherwise set ENOENT. */ if (posix_enotdir) { /* scan up path until we find an existing item */ |
