diff options
author | Carlos Martín Nieto <cmn@dwim.me> | 2015-06-17 08:15:49 +0200 |
---|---|---|
committer | Carlos Martín Nieto <cmn@dwim.me> | 2015-06-17 08:15:49 +0200 |
commit | a56db99234017eb7814258dfe8bcdec365417a3b (patch) | |
tree | ba49074368a7d9b6dff87e8a6540e8137b29d4b9 | |
parent | 5f83758fa35a7942457d676e41240dbfbda598b5 (diff) | |
parent | 892abf93157ea576fc3f2ccac118045a6a47247c (diff) | |
download | libgit2-a56db99234017eb7814258dfe8bcdec365417a3b.tar.gz |
Merge pull request #3219 from libgit2/cmn/racy-diff
Zero out racily-clean entries' file_size
-rw-r--r-- | src/checkout.c | 17 | ||||
-rw-r--r-- | src/index.c | 18 | ||||
-rw-r--r-- | src/unix/posix.h | 3 | ||||
-rw-r--r-- | src/win32/posix.h | 3 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 38 | ||||
-rw-r--r-- | src/win32/w32_util.h | 10 | ||||
-rw-r--r-- | tests/checkout/checkout_helpers.c | 22 | ||||
-rw-r--r-- | tests/checkout/checkout_helpers.h | 2 | ||||
-rw-r--r-- | tests/checkout/crlf.c | 11 | ||||
-rw-r--r-- | tests/core/posix.c | 49 | ||||
-rw-r--r-- | tests/diff/racy.c | 39 | ||||
-rw-r--r-- | tests/diff/workdir.c | 6 | ||||
-rw-r--r-- | tests/merge/workdir/dirty.c | 12 | ||||
-rw-r--r-- | tests/status/worktree.c | 6 |
14 files changed, 229 insertions, 7 deletions
diff --git a/src/checkout.c b/src/checkout.c index cc73e483f..fd2b19a95 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -150,6 +150,15 @@ static int checkout_notify( } } +GIT_INLINE(bool) is_workdir_base_or_new( + const git_oid *workdir_id, + const git_diff_file *baseitem, + const git_diff_file *newitem) +{ + return (git_oid__cmp(&baseitem->id, workdir_id) == 0 || + git_oid__cmp(&newitem->id, workdir_id) == 0); +} + static bool checkout_is_workdir_modified( checkout_data *data, const git_diff_file *baseitem, @@ -193,8 +202,7 @@ static bool checkout_is_workdir_modified( if (wditem->mtime.seconds == ie->mtime.seconds && wditem->mtime.nanoseconds == ie->mtime.nanoseconds && wditem->file_size == ie->file_size) - return (git_oid__cmp(&baseitem->id, &ie->id) != 0 && - git_oid_cmp(&newitem->id, &ie->id) != 0); + return !is_workdir_base_or_new(&ie->id, baseitem, newitem); } /* depending on where base is coming from, we may or may not know @@ -206,7 +214,10 @@ static bool checkout_is_workdir_modified( if (git_diff__oid_for_entry(&oid, data->diff, wditem, NULL) < 0) return false; - return (git_oid__cmp(&baseitem->id, &oid) != 0); + /* Allow the checkout if the workdir is not modified *or* if the checkout + * target's contents are already in the working directory. + */ + return !is_workdir_base_or_new(&oid, baseitem, newitem); } #define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ diff --git a/src/index.c b/src/index.c index 14d8d3684..a931f048a 100644 --- a/src/index.c +++ b/src/index.c @@ -658,11 +658,29 @@ int git_index__changed_relative_to( index->stamp.ino != fs->ino); } +/* + * Force the next diff to take a look at those entries which have the + * same timestamp as the current index. + */ +static void truncate_racily_clean(git_index *index) +{ + size_t i; + git_index_entry *entry; + git_time_t ts = index->stamp.mtime; + + git_vector_foreach(&index->entries, i, entry) { + if (entry->mtime.seconds == ts || ts == 0) + entry->file_size = 0; + } +} + int git_index_write(git_index *index) { git_indexwriter writer = GIT_INDEXWRITER_INIT; int error; + truncate_racily_clean(index); + if ((error = git_indexwriter_init(&writer, index)) == 0) error = git_indexwriter_commit(&writer); diff --git a/src/unix/posix.h b/src/unix/posix.h index 8b4f427f7..425e6bb1b 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -19,6 +19,9 @@ typedef int GIT_SOCKET; #define p_lstat(p,b) lstat(p,b) #define p_stat(p,b) stat(p, b) +#define p_utimes(f, t) utimes(f, t) +#define p_futimes(f, t) futimes(f, t) + #define p_readlink(a, b, c) readlink(a, b, c) #define p_symlink(o,n) symlink(o, n) #define p_link(o,n) link(o, n) diff --git a/src/win32/posix.h b/src/win32/posix.h index bf35c8125..ac98fd864 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -20,6 +20,9 @@ typedef SOCKET GIT_SOCKET; extern int p_lstat(const char *file_name, struct stat *buf); extern int p_stat(const char* path, struct stat* buf); +extern int p_utimes(const char *filename, const struct timeval times[2]); +extern int p_futimes(int fd, const struct timeval times[2]); + extern int p_readlink(const char *path, char *buf, size_t bufsiz); extern int p_symlink(const char *old, const char *new); extern int p_link(const char *old, const char *new); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 332ea233c..504562b0e 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -201,6 +201,44 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, true); } +int p_utimes(const char *filename, const struct timeval times[2]) +{ + int fd, error; + + if ((fd = p_open(filename, O_RDWR)) < 0) + return fd; + + error = p_futimes(fd, times); + + close(fd); + return error; +} + +int p_futimes(int fd, const struct timeval times[2]) +{ + HANDLE handle; + FILETIME atime = {0}, mtime = {0}; + + if (times == NULL) { + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &atime); + SystemTimeToFileTime(&st, &mtime); + } else { + git_win32__timeval_to_filetime(&atime, times[0]); + git_win32__timeval_to_filetime(&mtime, times[1]); + } + + if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) + return -1; + + if (SetFileTime(handle, NULL, &atime, &mtime) == 0) + return -1; + + return 0; +} + int p_readlink(const char *path, char *buf, size_t bufsiz) { git_win32_path path_w, target_w; diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 8cb0f5b94..377d651a8 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -79,6 +79,16 @@ GIT_INLINE(time_t) git_win32__filetime_to_time_t(const FILETIME *ft) return (time_t)winTime; } +GIT_INLINE(void) git_win32__timeval_to_filetime( + FILETIME *ft, const struct timeval tv) +{ + long long ticks = (tv.tv_sec * 10000000LL) + + (tv.tv_usec * 10LL) + 116444736000000000LL; + + ft->dwHighDateTime = ((ticks >> 32) & 0xffffffffLL); + ft->dwLowDateTime = (ticks & 0xffffffffLL); +} + GIT_INLINE(int) git_win32__file_attribute_to_stat( struct stat *st, const WIN32_FILE_ATTRIBUTE_DATA *attrdata, diff --git a/tests/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c index 06b4e0682..f6e36d39b 100644 --- a/tests/checkout/checkout_helpers.c +++ b/tests/checkout/checkout_helpers.c @@ -2,6 +2,7 @@ #include "checkout_helpers.h" #include "refs.h" #include "fileops.h" +#include "index.h" void assert_on_branch(git_repository *repo, const char *branch) { @@ -128,3 +129,24 @@ int checkout_count_callback( return 0; } + +void tick_index(git_index *index) +{ + int index_fd; + git_time_t ts; + struct timeval times[2]; + + cl_assert(index->on_disk); + cl_assert(git_index_path(index)); + + cl_git_pass(git_index_read(index, true)); + ts = index->stamp.mtime; + + times[0].tv_sec = ts; + times[0].tv_usec = 0; + times[1].tv_sec = ts + 1; + times[1].tv_usec = 0; + + cl_git_pass(p_utimes(git_index_path(index), times)); + cl_git_pass(git_index_read(index, true)); +} diff --git a/tests/checkout/checkout_helpers.h b/tests/checkout/checkout_helpers.h index 705ee903d..6058a196c 100644 --- a/tests/checkout/checkout_helpers.h +++ b/tests/checkout/checkout_helpers.h @@ -27,3 +27,5 @@ extern int checkout_count_callback( const git_diff_file *target, const git_diff_file *workdir, void *payload); + +extern void tick_index(git_index *index); diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c index 381b04013..61459b3a4 100644 --- a/tests/checkout/crlf.c +++ b/tests/checkout/crlf.c @@ -4,6 +4,7 @@ #include "git2/checkout.h" #include "repository.h" +#include "index.h" #include "posix.h" static git_repository *g_repo; @@ -40,9 +41,10 @@ void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) cl_repo_set_bool(g_repo, "core.autocrlf", false); - git_checkout_head(g_repo, &opts); - git_repository_index(&index, g_repo); + tick_index(index); + + git_checkout_head(g_repo, &opts); cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW)); @@ -140,9 +142,10 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) cl_repo_set_bool(g_repo, "core.autocrlf", true); - git_checkout_head(g_repo, &opts); - git_repository_index(&index, g_repo); + tick_index(index); + + git_checkout_head(g_repo, &opts); cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); diff --git a/tests/core/posix.c b/tests/core/posix.c index 1cef937cd..5a9e24899 100644 --- a/tests/core/posix.c +++ b/tests/core/posix.c @@ -97,3 +97,52 @@ void test_core_posix__inet_pton(void) cl_git_fail(p_inet_pton(5, "315.124", NULL)); /* AF_CHAOS */ cl_assert_equal_i(EAFNOSUPPORT, errno); } + +void test_core_posix__utimes(void) +{ + struct timeval times[2]; + struct stat st; + time_t curtime; + int fd; + + /* test p_utimes */ + times[0].tv_sec = 1234567890; + times[0].tv_usec = 0; + times[1].tv_sec = 1234567890; + times[1].tv_usec = 0; + + cl_git_mkfile("foo", "Dummy file."); + cl_must_pass(p_utimes("foo", times)); + + p_stat("foo", &st); + cl_assert_equal_i(1234567890, st.st_atime); + cl_assert_equal_i(1234567890, st.st_mtime); + + + /* test p_futimes */ + times[0].tv_sec = 1414141414; + times[0].tv_usec = 0; + times[1].tv_sec = 1414141414; + times[1].tv_usec = 0; + + cl_must_pass(fd = p_open("foo", O_RDWR)); + cl_must_pass(p_futimes(fd, times)); + p_close(fd); + + p_stat("foo", &st); + cl_assert_equal_i(1414141414, st.st_atime); + cl_assert_equal_i(1414141414, st.st_mtime); + + + /* test p_utimes with current time, assume that + * it takes < 5 seconds to get the time...! + */ + cl_must_pass(p_utimes("foo", NULL)); + + curtime = time(NULL); + p_stat("foo", &st); + cl_assert((st.st_atime - curtime) < 5); + cl_assert((st.st_mtime - curtime) < 5); + + p_unlink("foo"); +} diff --git a/tests/diff/racy.c b/tests/diff/racy.c new file mode 100644 index 000000000..a109f8c3b --- /dev/null +++ b/tests/diff/racy.c @@ -0,0 +1,39 @@ +#include "clar_libgit2.h" + +#include "buffer.h" + +static git_repository *g_repo; + +void test_diff_racy__initialize(void) +{ + cl_git_pass(git_repository_init(&g_repo, "diff_racy", false)); +} + +void test_diff_racy__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_racy__diff(void) +{ + git_index *index; + git_diff *diff; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "A")); + cl_git_mkfile(path.ptr, "A"); + + /* Put 'A' into the index */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "A")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + + /* Change its contents quickly, so we get the same timestamp */ + cl_git_mkfile(path.ptr, "B"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); +} diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 5d6ebed95..6b72f3286 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -2,6 +2,7 @@ #include "diff_helpers.h" #include "repository.h" #include "git2/sys/diff.h" +#include "../checkout/checkout_helpers.h" static git_repository *g_repo = NULL; @@ -1583,6 +1584,7 @@ void test_diff_workdir__can_update_index(void) git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff *diff = NULL; git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; + git_index *index; g_repo = cl_git_sandbox_init("status"); @@ -1607,6 +1609,10 @@ void test_diff_workdir__can_update_index(void) /* now allow diff to update stat cache */ opts.flags |= GIT_DIFF_UPDATE_INDEX; + /* advance a tick for the index so we don't re-calculate racily-clean entries */ + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + tick_index(index); + basic_diff_status(&diff, &opts); cl_git_pass(git_diff_get_perfdata(&perf, diff)); diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c index b66225d1f..30c404b70 100644 --- a/tests/merge/workdir/dirty.c +++ b/tests/merge/workdir/dirty.c @@ -2,6 +2,7 @@ #include "git2/merge.h" #include "buffer.h" #include "merge.h" +#include "index.h" #include "../merge_helpers.h" #include "posix.h" @@ -231,9 +232,20 @@ static int merge_differently_filtered_files(char *files[]) cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + /* Emulate checkout with a broken or misconfigured filter: modify some + * files on-disk and then update the index with the updated file size + * and time, as if some filter applied them. These files should not be + * treated as dirty since we created them. + * + * (Make sure to update the index stamp to defeat racy-git protections + * trying to sanity check the files in the index; those would rehash the + * files, showing them as dirty, the exact mechanism we're trying to avoid.) + */ + write_files(files); hack_index(files); + repo_index->stamp.mtime = time(NULL) + 1; cl_git_pass(git_index_write(repo_index)); error = merge_branch(); diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 3b18ae6c0..f8d1f7f54 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -6,6 +6,7 @@ #include "util.h" #include "path.h" #include "../diff/diff_helpers.h" +#include "../checkout/checkout_helpers.h" #include "git2/sys/diff.h" /** @@ -956,6 +957,7 @@ void test_status_worktree__update_stat_cache_0(void) git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_status_list *status; git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; + git_index *index; opts.flags = GIT_STATUS_OPT_DEFAULTS; @@ -967,6 +969,10 @@ void test_status_worktree__update_stat_cache_0(void) git_status_list_free(status); + /* tick the index so we avoid recalculating racily-clean entries */ + cl_git_pass(git_repository_index__weakptr(&index, repo)); + tick_index(index); + opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX; cl_git_pass(git_status_list_new(&status, repo, &opts)); |