commit - 57ee5d5084c5c07c321352b7412a51d2f89ef298
commit + 6353ad76e488c8d0df631a779571fdeb41fc9c70
blob - c4412341928e247e4ba1e74b8a4014eb2107252d
blob + ee6fe6460a2a0de9ae5395eb0de8571d8aa8a135
--- got/Makefile
+++ got/Makefile
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
#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
#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))
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) {
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;
}
{
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;
}
};
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
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
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
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
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
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
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}