commit 6353ad76e488c8d0df631a779571fdeb41fc9c70 from: Stefan Sperling date: Fri Feb 08 14:12:00 2019 UTC make 'got update' merge file edits commit - 57ee5d5084c5c07c321352b7412a51d2f89ef298 commit + 6353ad76e488c8d0df631a779571fdeb41fc9c70 blob - c4412341928e247e4ba1e74b8a4014eb2107252d blob + ee6fe6460a2a0de9ae5395eb0de8571d8aa8a135 --- got/Makefile +++ got/Makefile @@ -5,7 +5,7 @@ SRCS= got.c blame.c commit_graph.c delta.c diff.c dif diffreg.c error.c fileindex.c object.c object_cache.c \ object_idset.c object_parse.c opentemp.c path.c pack.c \ privsep.c reference.c repository.c sha1.c worktree.c \ - inflate.c + inflate.c buf.c worklist.c rcsutil.c diff3.c CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \ -DGOT_LIBEXECDIR=${GOT_LIBEXECDIR} blob - 7f9026a9b8d5c96a24479753a750d7e3d7ab7eec blob + b8c74fd38cb835f3c4f177441861efc0fba475fb --- include/got_worktree.h +++ include/got_worktree.h @@ -23,6 +23,8 @@ struct got_worktree; #define GOT_STATUS_UPDATE 'U' #define GOT_STATUS_DELETE 'D' #define GOT_STATUS_MODIFIY 'M' +#define GOT_STATUS_CONFLICT 'C' +#define GOT_STATUS_MERGE 'G' #define GOT_STATUS_MISSING '!' #define GOT_STATUS_UNVERSIONED '?' #define GOT_STATUS_OBSTRUCTED '~' blob - c0ccc0d1604c7cfb8c922ce2a76a686a2443fc8d blob + 9a944a8f829e0a7732e518c02a5246ce3e4f0c2c --- lib/worktree.c +++ lib/worktree.c @@ -46,6 +46,7 @@ #include "got_lib_inflate.h" #include "got_lib_delta.h" #include "got_lib_object.h" +#include "got_lib_diff.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) @@ -593,26 +594,115 @@ add_dir_on_disk(struct got_worktree *worktree, const c done: free(abspath); + return err; +} + +/* + * Perform a 3-way merge where the file's version in the file index (blob2) + * acts as the common ancestor, the incoming blob (blob1) acts as the first + * derived version, and the file on disk acts as the second derived version. + */ +static const struct got_error * +merge_blob(struct got_worktree *worktree, struct got_fileindex *fileindex, + struct got_fileindex_entry *ie, const char *ondisk_path, const char *path, + struct got_blob_object *blob1, struct got_repository *repo, + got_worktree_checkout_cb progress_cb, void *progress_arg) +{ + const struct got_error *err = NULL; + int merged_fd = -1; + struct got_blob_object *blob2 = NULL; + FILE *f1 = NULL, *f2 = NULL; + char *blob1_path = NULL, *blob2_path = NULL; + char *merged_path = NULL; + struct got_object_id id2; + char *id_str = NULL; + char *label1 = NULL; + int overlapcnt = 0; + + err = got_opentemp_named_fd(&merged_path, &merged_fd, "/tmp/got-merged"); + if (err) + return err; + err = got_opentemp_named(&blob1_path, &f1, "/tmp/got-merge-blob1"); + if (err) + goto done; + err = got_object_blob_dump_to_file(NULL, NULL, f1, blob1); + if (err) + goto done; + + err = got_opentemp_named(&blob2_path, &f2, "/tmp/got-merge-blob2"); + if (err) + goto done; + + memcpy(id2.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH); + err = got_object_open_as_blob(&blob2, repo, &id2, 8192); + if (err) + goto done; + err = got_object_blob_dump_to_file(NULL, NULL, f2, blob2); + if (err) + goto done; + + err = got_object_id_str(&id_str, worktree->base_commit_id); + if (err) + goto done; + if (asprintf(&label1, "commit %s", id_str) == -1) { + err = got_error_from_errno(); + goto done; + } + + err = got_merge_diff3(&overlapcnt, merged_fd, blob1_path, + blob2_path, ondisk_path, label1, path); + if (err) + goto done; + + (*progress_cb)(progress_arg, + overlapcnt > 0 ? GOT_STATUS_CONFLICT : GOT_STATUS_MERGE, path); + + + fsync(merged_fd); + + if (rename(merged_path, ondisk_path) != 0) { + err = got_error_from_errno(); + goto done; + } + + err = got_fileindex_entry_update(ie, ondisk_path, + blob1->id.sha1, worktree->base_commit_id->sha1); +done: + if (merged_fd != -1) + close(merged_fd); + if (f1) + fclose(f1); + if (f2) + fclose(f2); + if (blob2) + got_object_blob_close(blob2); + free(merged_path); + if (blob1_path) { + unlink(blob1_path); + free(blob1_path); + } + if (blob2_path) { + unlink(blob2_path); + free(blob2_path); + } + free(id_str); + free(label1); return err; } static const struct got_error * install_blob(struct got_worktree *worktree, struct got_fileindex *fileindex, - struct got_fileindex_entry *entry, const char *path, + struct got_fileindex_entry *entry, const char *ondisk_path, const char *path, struct got_blob_object *blob, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; - char *ondisk_path; int fd = -1; size_t len, hdrlen; int update = 0; char *tmppath = NULL; - if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1) - return got_error_from_errno(); - fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, GOT_DEFAULT_FILE_MODE); if (fd == -1) { @@ -695,8 +785,83 @@ install_blob(struct got_worktree *worktree, struct got done: if (fd != -1) close(fd); - free(ondisk_path); free(tmppath); + return err; +} + +static const struct got_error * +get_file_status(unsigned char *status, struct got_fileindex_entry *ie, + const char *abspath, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id id; + size_t hdrlen; + FILE *f = NULL; + uint8_t fbuf[8192]; + struct got_blob_object *blob = NULL; + size_t flen, blen; + struct stat sb; + + *status = GOT_STATUS_NO_CHANGE; + + if (lstat(abspath, &sb) == -1) + return got_error_from_errno(); + + if (!S_ISREG(sb.st_mode)) { + *status = GOT_STATUS_OBSTRUCTED; + return NULL; + } + + if (ie->ctime_sec == sb.st_ctime && + ie->ctime_nsec == sb.st_ctimensec && + ie->mtime_sec == sb.st_mtime && + ie->mtime_sec == sb.st_mtime && + ie->mtime_nsec == sb.st_mtimensec && + ie->size == (sb.st_size & 0xffffffff)) + return NULL; + + memcpy(id.sha1, ie->blob_sha1, sizeof(id.sha1)); + err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf)); + if (err) + return err; + + f = fopen(abspath, "r"); + if (f == NULL) { + err = got_error_from_errno(); + goto done; + } + hdrlen = got_object_blob_get_hdrlen(blob); + while (1) { + const uint8_t *bbuf = got_object_blob_get_read_buf(blob); + err = got_object_blob_read_block(&blen, blob); + if (err) + break; + flen = fread(fbuf, 1, sizeof(fbuf), f); + if (blen == 0) { + if (flen != 0) + *status = GOT_STATUS_MODIFIY; + break; + } else if (flen == 0) { + if (blen != 0) + *status = GOT_STATUS_MODIFIY; + break; + } else if (blen - hdrlen == flen) { + /* Skip blob object header first time around. */ + if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) { + *status = GOT_STATUS_MODIFIY; + break; + } + } else { + *status = GOT_STATUS_MODIFIY; + break; + } + hdrlen = 0; + } +done: + if (blob) + got_object_blob_close(blob); + if (f) + fclose(f); return err; } @@ -709,26 +874,47 @@ update_blob(struct got_worktree *worktree, { const struct got_error *err = NULL; struct got_blob_object *blob = NULL; + char *ondisk_path; + unsigned char status = GOT_STATUS_NO_CHANGE; + if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1) + return got_error_from_errno(); + if (ie) { if (memcmp(ie->commit_sha1, worktree->base_commit_id->sha1, SHA1_DIGEST_LENGTH) == 0) { (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, path); - return NULL; + goto done; } if (memcmp(ie->blob_sha1, te->id->sha1, SHA1_DIGEST_LENGTH) == 0) - return NULL; + goto done; + + err = get_file_status(&status, ie, ondisk_path, repo); + if (err) + goto done; + + if (status == GOT_STATUS_OBSTRUCTED) { + (*progress_cb)(progress_arg, status, path); + goto done; + } } err = got_object_open_as_blob(&blob, repo, te->id, 8192); if (err) - return err; + goto done; - err = install_blob(worktree, fileindex, ie, path, blob, repo, - progress_cb, progress_arg); + if (status == GOT_STATUS_MODIFIY) + err = merge_blob(worktree, fileindex, ie, ondisk_path, path, + blob, repo, progress_cb, progress_arg); + else + err = install_blob(worktree, fileindex, ie, ondisk_path, path, + blob, repo, progress_cb, progress_arg); + got_object_blob_close(blob); +done: + free(ondisk_path); return err; } @@ -944,82 +1130,6 @@ struct diff_dir_cb_arg { }; static const struct got_error * -get_file_status(unsigned char *status, struct got_fileindex_entry *ie, - const char *abspath, struct got_repository *repo) -{ - const struct got_error *err = NULL; - struct got_object_id id; - size_t hdrlen; - FILE *f = NULL; - uint8_t fbuf[8192]; - struct got_blob_object *blob = NULL; - size_t flen, blen; - struct stat sb; - - *status = GOT_STATUS_NO_CHANGE; - - if (lstat(abspath, &sb) == -1) - return got_error_from_errno(); - - if (!S_ISREG(sb.st_mode)) { - *status = GOT_STATUS_OBSTRUCTED; - return NULL; - } - - if (ie->ctime_sec == sb.st_ctime && - ie->ctime_nsec == sb.st_ctimensec && - ie->mtime_sec == sb.st_mtime && - ie->mtime_sec == sb.st_mtime && - ie->mtime_nsec == sb.st_mtimensec && - ie->size == (sb.st_size & 0xffffffff)) - return NULL; - - memcpy(id.sha1, ie->blob_sha1, sizeof(id.sha1)); - err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf)); - if (err) - return err; - - f = fopen(abspath, "r"); - if (f == NULL) { - err = got_error_from_errno(); - goto done; - } - hdrlen = got_object_blob_get_hdrlen(blob); - while (1) { - const uint8_t *bbuf = got_object_blob_get_read_buf(blob); - err = got_object_blob_read_block(&blen, blob); - if (err) - break; - flen = fread(fbuf, 1, sizeof(fbuf), f); - if (blen == 0) { - if (flen != 0) - *status = GOT_STATUS_MODIFIY; - break; - } else if (flen == 0) { - if (blen != 0) - *status = GOT_STATUS_MODIFIY; - break; - } else if (blen - hdrlen == flen) { - /* Skip blob object header first time around. */ - if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) { - *status = GOT_STATUS_MODIFIY; - break; - } - } else { - *status = GOT_STATUS_MODIFIY; - break; - } - hdrlen = 0; - } -done: - if (blob) - got_object_blob_close(blob); - if (f) - fclose(f); - return err; -} - -static const struct got_error * status_old_new(void *arg, struct got_fileindex_entry *ie, struct dirent *de, const char *parent_path) { blob - 32c4c6ac4ab75a05b1469c969541296100c928ae blob + de6d7654c62255aae15280ee7a09d127ca06b949 --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -607,7 +607,81 @@ function test_update_file_in_subsubdir { test_done "$testroot" "0" } + +function test_update_merges_file_edits { + local testroot=`test_init update_merges_file_edits` + + echo "1" > $testroot/repo/numbers + echo "2" >> $testroot/repo/numbers + echo "3" >> $testroot/repo/numbers + echo "4" >> $testroot/repo/numbers + echo "5" >> $testroot/repo/numbers + echo "6" >> $testroot/repo/numbers + echo "7" >> $testroot/repo/numbers + echo "8" >> $testroot/repo/numbers + (cd $testroot/repo && git add numbers) + git_commit $testroot/repo -m "added numbers file" + got checkout $testroot/repo $testroot/wt > /dev/null + if [ "$?" != "0" ]; then + test_done "$testroot" "$?" + return 1 + fi + + echo "modified alpha" > $testroot/repo/alpha + echo "modified beta" > $testroot/repo/beta + sed -i 's/2/22/' $testroot/repo/numbers + git_commit $testroot/repo -m "modified 3 files" + + echo "modified alpha, too" > $testroot/wt/alpha + touch $testroot/wt/beta + sed -i 's/7/77/' $testroot/wt/numbers + + echo "C alpha" > $testroot/stdout.expected + echo "U beta" >> $testroot/stdout.expected + echo "G numbers" >> $testroot/stdout.expected + echo -n "Updated to commit " >> $testroot/stdout.expected + git_show_head $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + + (cd $testroot/wt && got update > $testroot/stdout) + + cmp $testroot/stdout.expected $testroot/stdout + if [ "$?" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$?" + return 1 + fi + + echo -n "<<<<<<< commit " > $testroot/content.expected + git_show_head $testroot/repo >> $testroot/content.expected + echo >> $testroot/content.expected + echo "modified alpha" >> $testroot/content.expected + echo "=======" >> $testroot/content.expected + echo "modified alpha, too" >> $testroot/content.expected + echo '>>>>>>> alpha' >> $testroot/content.expected + echo "modified beta" >> $testroot/content.expected + echo "1" >> $testroot/content.expected + echo "22" >> $testroot/content.expected + echo "3" >> $testroot/content.expected + echo "4" >> $testroot/content.expected + echo "5" >> $testroot/content.expected + echo "6" >> $testroot/content.expected + echo "77" >> $testroot/content.expected + echo "8" >> $testroot/content.expected + + cat $testroot/wt/alpha > $testroot/content + cat $testroot/wt/beta >> $testroot/content + cat $testroot/wt/numbers >> $testroot/content + + cmp $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + fi + test_done "$testroot" "$ret" +} + run_test test_update_basic run_test test_update_adds_file run_test test_update_deletes_file @@ -621,3 +695,4 @@ run_test test_update_moves_files_to_new_dir run_test test_update_creates_missing_parent run_test test_update_creates_missing_parent_with_subdir run_test test_update_file_in_subsubdir +run_test test_update_merges_file_edits blob - 622918d65a3235175fbd16aaffcc60725d7d93cc blob + 8fb53dfe4ebce9973fd7098c98914117b3baf3bf --- regress/idset/Makefile +++ regress/idset/Makefile @@ -3,7 +3,8 @@ PROG = idset_test SRCS = error.c object.c privsep.c sha1.c pack.c inflate.c path.c opentemp.c \ delta.c repository.c reference.c worktree.c fileindex.c object_cache.c \ - object_idset.c object_parse.c idset_test.c + object_idset.c object_parse.c idset_test.c \ + buf.c worklist.c rcsutil.c diff.c diffreg.c diff3.c CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib LDADD = -lutil -lz blob - 5da0a61342d57368a2ad4a97cf0cfcacf13e6028 blob + d75f821ba3e7ed1050713ad1cc994f972eda1ee1 --- regress/repository/Makefile +++ regress/repository/Makefile @@ -4,6 +4,7 @@ PROG = repository_test SRCS = path.c repository.c error.c reference.c object.c object_cache.c \ object_idset.c object_parse.c opentemp.c sha1.c diff.c diffreg.c \ pack.c privsep.c delta.c fileindex.c worktree.c inflate.c \ + buf.c worklist.c rcsutil.c diff3.c \ repository_test.c CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \ blob - f31fa65e08a2297db4491f0a7c4c42af2674c65e blob + 21476afed2b0930f65f44d151ae98a2ba3c55081 --- regress/worktree/Makefile +++ regress/worktree/Makefile @@ -3,7 +3,9 @@ PROG = worktree_test SRCS = worktree.c repository.c object.c object_cache.c object_idset.c \ object_parse.c opentemp.c path.c error.c reference.c sha1.c pack.c \ - privsep.c delta.c inflate.c fileindex.c worktree_test.c + privsep.c delta.c inflate.c fileindex.c \ + buf.c worklist.c rcsutil.c diff.c diffreg.c diff3.c \ + worktree_test.c CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \ -DGOT_LIBEXECDIR=${GOT_LIBEXECDIR} blob - 72f8d6d7e4c381ebb68b1e052998d0843ff100aa blob + 87516af1da37593f11b27b5440b5d09a61937e7e --- tog/Makefile +++ tog/Makefile @@ -5,7 +5,7 @@ SRCS= tog.c blame.c commit_graph.c delta.c diff.c dif diffreg.c error.c fileindex.c object.c object_cache.c \ object_idset.c object_parse.c opentemp.c path.c pack.c \ privsep.c reference.c repository.c sha1.c worktree.c \ - utf8.c inflate.c + utf8.c inflate.c buf.c worklist.c rcsutil.c diff3.c CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \ -DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}