commit 3b9f0f87ff620ce7d6ad75195b0c67425bc96ab7 from: Stefan Sperling date: Thu Jul 23 14:22:35 2020 UTC handle additional symlink conflicts and report skipping of unversioned files commit - c90c8ce30c933a907f591ebe599ed6b7506f8217 commit + 3b9f0f87ff620ce7d6ad75195b0c67425bc96ab7 blob - fa03c453a41b27d2fc77686b381c798275eb9f9c blob + ca0ab90afd597806d087a341444532719ec5a156 --- got/got.1 +++ got/got.1 @@ -543,6 +543,7 @@ Show the status of each affected file, using the follo .It \(a~ Ta versioned file is obstructed by a non-regular file .It ! Ta a missing versioned file was restored .It # Ta file was not updated because it contains merge conflicts +.It ? Ta changes destined for an unversioned file were not merged .El .Pp If no blob - 437116fcd6048a862de574f7a76e6a3ae5c11309 blob + 40f832a405d94d9b628752ef3ed637eeba54faf5 --- lib/worktree.c +++ lib/worktree.c @@ -742,6 +742,8 @@ merge_file(int *local_changes_subsumed, struct got_wor char *merged_path = NULL, *base_path = NULL; int overlapcnt = 0; char *parent; + char *symlink_path = NULL; + FILE *symlinkf = NULL; *local_changes_subsumed = 0; @@ -779,8 +781,45 @@ merge_file(int *local_changes_subsumed, struct got_wor */ } + /* + * In order the run a 3-way merge with a symlink we copy the symlink's + * target path into a temporary file and use that file with diff3. + */ + if (S_ISLNK(st_mode)) { + char target_path[PATH_MAX]; + ssize_t target_len; + size_t n; + + free(base_path); + if (asprintf(&base_path, "%s/got-symlink-merge", + parent) == -1) { + err = got_error_from_errno("asprintf"); + base_path = NULL; + goto done; + } + err = got_opentemp_named(&symlink_path, &symlinkf, base_path); + if (err) + goto done; + target_len = readlink(ondisk_path, target_path, + sizeof(target_path)); + if (target_len == -1) { + err = got_error_from_errno2("readlink", ondisk_path); + goto done; + } + n = fwrite(target_path, 1, target_len, symlinkf); + if (n != target_len) { + err = got_ferror(symlinkf, GOT_ERR_IO); + goto done; + } + if (fflush(symlinkf) == EOF) { + err = got_error_from_errno2("fflush", symlink_path); + goto done; + } + } + err = got_merge_diff3(&overlapcnt, merged_fd, deriv_path, - blob_orig_path, ondisk_path, label_deriv, label_orig, NULL); + blob_orig_path, symlink_path ? symlink_path : ondisk_path, + label_deriv, label_orig, NULL); if (err) goto done; @@ -816,7 +855,14 @@ done: if (err) { if (merged_path) unlink(merged_path); + } + if (symlink_path) { + if (unlink(symlink_path) == -1 && err == NULL) + err = got_error_from_errno2("unlink", symlink_path); } + if (symlinkf && fclose(symlinkf) == EOF && err == NULL) + err = got_error_from_errno2("fclose", symlink_path); + free(symlink_path); if (merged_fd != -1 && close(merged_fd) != 0 && err == NULL) err = got_error_from_errno("close"); if (f_orig && fclose(f_orig) != 0 && err == NULL) @@ -1155,7 +1201,7 @@ install_blob(struct got_worktree *worktree, const char const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, int installing_bad_symlink, - struct got_repository *repo, + int path_is_unversioned, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg); /* @@ -1232,7 +1278,8 @@ install_symlink(int *is_bad_symlink, struct got_worktr return install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, - 1, repo, progress_cb, progress_arg); + 1, path_is_unversioned, repo, progress_cb, + progress_arg); } if (len > 0) { /* Skip blob object header first time around. */ @@ -1283,7 +1330,7 @@ install_symlink(int *is_bad_symlink, struct got_worktr err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, - repo, progress_cb, progress_arg); + path_is_unversioned, repo, progress_cb, progress_arg); goto done; } @@ -1301,7 +1348,7 @@ install_symlink(int *is_bad_symlink, struct got_worktr err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, - repo, progress_cb, progress_arg); + path_is_unversioned, repo, progress_cb, progress_arg); goto done; } @@ -1351,7 +1398,8 @@ install_symlink(int *is_bad_symlink, struct got_worktr err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob, restoring_missing_file, reverting_versioned_file, 1, - repo, progress_cb, progress_arg); + path_is_unversioned, repo, + progress_cb, progress_arg); } else if (errno == ENOTDIR) { err = got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED); @@ -1373,8 +1421,8 @@ install_blob(struct got_worktree *worktree, const char const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, int installing_bad_symlink, - struct got_repository *repo, got_worktree_checkout_cb progress_cb, - void *progress_arg) + int path_is_unversioned, struct got_repository *repo, + got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; int fd = -1; @@ -1399,6 +1447,11 @@ install_blob(struct got_worktree *worktree, const char return got_error_from_errno2("open", ondisk_path); } else if (errno == EEXIST) { + if (path_is_unversioned) { + err = (*progress_cb)(progress_arg, + GOT_STATUS_UNVERSIONED, path); + goto done; + } if (!S_ISREG(st_mode) && !installing_bad_symlink) { /* TODO file is obstructed; do something */ err = got_error_path(ondisk_path, @@ -1905,7 +1958,8 @@ update_blob(struct got_worktree *worktree, } else { err = install_blob(worktree, ondisk_path, path, te->mode, sb.st_mode, blob, - status == GOT_STATUS_MISSING, 0, 0, repo, + status == GOT_STATUS_MISSING, 0, 0, + status == GOT_STATUS_UNVERSIONED, repo, progress_cb, progress_arg); } if (err) @@ -2780,7 +2834,7 @@ merge_file_cb(void *arg, struct got_blob_object *blob1 0, 1, repo, a->progress_cb, a->progress_arg); } else { err = install_blob(a->worktree, ondisk_path, path2, - mode2, sb.st_mode, blob2, 0, 0, 0, repo, + mode2, sb.st_mode, blob2, 0, 0, 0, 1, repo, a->progress_cb, a->progress_arg); } if (err) @@ -4309,8 +4363,9 @@ revert_file(void *arg, unsigned char status, unsigned err = install_blob(a->worktree, ondisk_path, ie->path, te ? te->mode : GOT_DEFAULT_FILE_MODE, - got_fileindex_perms_to_st(ie), blob, 0, 1, 0, - a->repo, a->progress_cb, a->progress_arg); + got_fileindex_perms_to_st(ie), blob, + 0, 1, 0, 0, a->repo, + a->progress_cb, a->progress_arg); } if (err) goto done; blob - 3cf2adea08569d237c3ae2792cad85f134715215 blob + 4ed5d4635a5d84136d563642565378542947f5fc --- regress/cmdline/checkout.sh +++ regress/cmdline/checkout.sh @@ -397,10 +397,10 @@ function test_checkout_into_nonempty_dir { return 1 fi - echo "U $testroot/wt/alpha" > $testroot/stdout.expected - echo "U $testroot/wt/beta" >> $testroot/stdout.expected - echo "U $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected - echo "U $testroot/wt/gamma/delta" >> $testroot/stdout.expected + echo "? $testroot/wt/alpha" > $testroot/stdout.expected + echo "? $testroot/wt/beta" >> $testroot/stdout.expected + echo "? $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected + echo "? $testroot/wt/gamma/delta" >> $testroot/stdout.expected echo "Now shut up and hack" >> $testroot/stdout.expected got checkout -E $testroot/repo $testroot/wt > $testroot/stdout blob - f9b695d8cdef6863a73038a1bdbbe643d583bb80 blob + 55663aa76dce7c64b71dc3d173d4daefb0d66870 --- regress/cmdline/cherrypick.sh +++ regress/cmdline/cherrypick.sh @@ -495,6 +495,7 @@ function test_cherrypick_symlink_conflicts { (cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link) # added regular file A vs added bad symlink to file A (cd $testroot/wt && ln -sf .got/bar dotgotfoo.link) + (cd $testroot/wt && got add dotgotfoo.link > /dev/null) # added bad symlink to file A vs added regular file A echo 'this is regular file bar' > $testroot/wt/dotgotbar.link (cd $testroot/wt && got add dotgotbar.link > /dev/null) @@ -520,12 +521,12 @@ function test_cherrypick_symlink_conflicts { echo "? boo.link" >> $testroot/stdout.expected echo "C epsilon.link" >> $testroot/stdout.expected echo "C dotgotbar.link" >> $testroot/stdout.expected - echo "U dotgotfoo.link" >> $testroot/stdout.expected + echo "C dotgotfoo.link" >> $testroot/stdout.expected echo "D nonexistent.link" >> $testroot/stdout.expected echo "! zeta.link" >> $testroot/stdout.expected echo "C new.link" >> $testroot/stdout.expected echo "Merged commit $commit_id2" >> $testroot/stdout.expected - echo "Files with new merge conflicts: 5" >> $testroot/stdout.expected + echo "Files with new merge conflicts: 6" >> $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" if [ "$ret" != "0" ]; then @@ -668,7 +669,12 @@ EOF return 1 fi - echo "this is regular file foo" > $testroot/content.expected + echo "<<<<<<< merged change: commit $commit_id2" \ + > $testroot/content.expected + echo "this is regular file foo" >> $testroot/content.expected + echo "=======" >> $testroot/content.expected + echo -n ".got/bar" >> $testroot/content.expected + echo '>>>>>>>' >> $testroot/content.expected cp $testroot/wt/dotgotfoo.link $testroot/content cmp -s $testroot/content.expected $testroot/content ret="$?" blob - 2ee2f8f53e19a50823b280fff77f85ce353b0389 blob + 64b8fab61a492d7a1035d3082da449c5bb41d027 --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -2016,8 +2016,10 @@ function test_update_symlink_conflicts { (cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link) # added regular file A vs added bad symlink to file A (cd $testroot/wt && ln -sf .got/bar dotgotfoo.link) + (cd $testroot/wt && got add dotgotfoo.link > /dev/null) # added bad symlink to file A vs added regular file A echo 'this is regular file bar' > $testroot/wt/dotgotbar.link + (cd $testroot/wt && got add dotgotbar.link > /dev/null) # removed symlink to non-existent file A vs modified symlink # to nonexistent file B (cd $testroot/wt && ln -sf nonexistent2 nonexistent.link) @@ -2030,8 +2032,8 @@ function test_update_symlink_conflicts { (cd $testroot/wt && got update > $testroot/stdout) echo "C alpha.link" >> $testroot/stdout.expected - echo "U dotgotbar.link" >> $testroot/stdout.expected - echo "U dotgotfoo.link" >> $testroot/stdout.expected + echo "C dotgotbar.link" >> $testroot/stdout.expected + echo "C dotgotfoo.link" >> $testroot/stdout.expected echo "C epsilon/beta.link" >> $testroot/stdout.expected echo "C epsilon.link" >> $testroot/stdout.expected echo "C new.link" >> $testroot/stdout.expected @@ -2040,7 +2042,7 @@ function test_update_symlink_conflicts { echo -n "Updated to commit " >> $testroot/stdout.expected git_show_head $testroot/repo >> $testroot/stdout.expected echo >> $testroot/stdout.expected - echo "Files with new merge conflicts: 5" >> $testroot/stdout.expected + echo "Files with new merge conflicts: 7" >> $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" @@ -2186,7 +2188,14 @@ EOF return 1 fi - echo "this is regular file foo" > $testroot/content.expected + echo "<<<<<<< merged change: commit $commit_id2" \ + > $testroot/content.expected + echo "this is regular file foo" >> $testroot/content.expected + echo "=======" >> $testroot/content.expected + echo -n ".got/bar" >> $testroot/content.expected + echo '>>>>>>>' >> $testroot/content.expected + echo -n "" >> $testroot/content.expected + cp $testroot/wt/dotgotfoo.link $testroot/content cmp -s $testroot/content.expected $testroot/content ret="$?" @@ -2201,7 +2210,14 @@ EOF test_done "$testroot" "1" return 1 fi - echo -n ".got/bar" > $testroot/content.expected + echo "<<<<<<< merged change: commit $commit_id2" \ + > $testroot/content.expected + echo -n ".got/bar" >> $testroot/content.expected + echo "=======" >> $testroot/content.expected + echo "this is regular file bar" >> $testroot/content.expected + echo '>>>>>>>' >> $testroot/content.expected + echo -n "" >> $testroot/content.expected + cp $testroot/wt/dotgotbar.link $testroot/content cmp -s $testroot/content.expected $testroot/content ret="$?"