commit - e5dc71984941ad561bd7f953c0cbb88bc5aad0b0
commit + 507dc3bb1293e483406626bf03199db0bc17dc5c
blob - c206a5114b58027528943401df8d7604ec503700
blob + b85c7d4acf1dfb346061b17df8a3b3ba58d863cc
--- got/got.1
+++ got/got.1
.Ar path-prefix
will be checked out.
.El
+.It Cm update [ Fl c Ar commit ] [ work-tree-path ]
+Update an existing work tree to another commit.
+By default, the latest commit on the current branch is assumed.
+If the
+.Ar work tree path
+is omitted, use the current working directory.
+.Pp
+The options for
+.Cm got update
+are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar commit
+Update the work tree to the specified
+.Ar commit .
+The expected argument is the name of a branch or a SHA1 hash which corresponds
+to a commit object.
+.El
.\".It Cm status
.\"Show current status of files.
.It Cm log [ Fl c Ar commit ] [ Fl C Ar number ] [ Fl f ] [ Fl l Ar N ] [ Fl p ] [ Fl r Ar repository-path ] [ path ]
blob - 6c95c1658ef7d66ad92828e36f72187ce1e36de9
blob + ed8b115d1fdbf57ea1026dc92aa61f6af82a0e33
--- got/got.c
+++ got/got.c
__dead static void usage(void);
__dead static void usage_checkout(void);
+__dead static void usage_update(void);
__dead static void usage_log(void);
__dead static void usage_diff(void);
__dead static void usage_blame(void);
__dead static void usage_tree(void);
static const struct got_error* cmd_checkout(int, char *[]);
+static const struct got_error* cmd_update(int, char *[]);
static const struct got_error* cmd_log(int, char *[]);
static const struct got_error* cmd_diff(int, char *[]);
static const struct got_error* cmd_blame(int, char *[]);
static struct cmd got_commands[] = {
{ "checkout", cmd_checkout, usage_checkout,
"check out a new work tree from a repository" },
+ { "update", cmd_update, usage_update,
+ "update a work tree to a different commit" },
{ "log", cmd_log, usage_log,
"show repository history" },
{ "diff", cmd_diff, usage_diff,
done:
free(repo_path);
+ free(worktree_path);
+ return error;
+}
+
+__dead static void
+usage_update(void)
+{
+ fprintf(stderr, "usage: %s update [-c commit] [worktree-path]\n",
+ getprogname());
+ exit(1);
+}
+
+static void
+update_progress(void *arg, unsigned char status, const char *path)
+{
+ if (status == GOT_STATUS_EXISTS)
+ return;
+
+ while (path[0] == '/')
+ path++;
+ printf("%c %s\n", status, path);
+}
+
+static const struct got_error *
+cmd_update(int argc, char *argv[])
+{
+ const struct got_error *error = NULL;
+ struct got_repository *repo = NULL;
+ struct got_worktree *worktree = NULL;
+ char *worktree_path = NULL;
+ struct got_object_id *commit_id = NULL;
+ const char *commit_id_str = NULL;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "c:")) != -1) {
+ switch (ch) {
+ case 'c':
+ commit_id_str = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+ == -1)
+ err(1, "pledge");
+#endif
+ if (argc == 0) {
+ worktree_path = getcwd(NULL, 0);
+ if (worktree_path == NULL) {
+ error = got_error_from_errno();
+ goto done;
+ }
+ } else if (argc == 1) {
+ worktree_path = realpath(argv[0], NULL);
+ if (worktree_path == NULL) {
+ error = got_error_from_errno();
+ goto done;
+ }
+ } else
+ usage_update();
+
+ error = got_worktree_open(&worktree, worktree_path);
+ if (error != NULL)
+ goto done;
+
+ error = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
+ if (error != NULL)
+ goto done;
+
+ if (commit_id_str == NULL) {
+ struct got_reference *head_ref;
+ error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
+ if (error != NULL)
+ goto done;
+ error = got_ref_resolve(&commit_id, repo, head_ref);
+ if (error != NULL)
+ goto done;
+ } else {
+ error = got_object_resolve_id_str(&commit_id, repo,
+ commit_id_str);
+ if (error != NULL)
+ goto done;
+ }
+
+ if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree),
+ commit_id) != 0) {
+ error = got_worktree_set_base_commit_id(worktree, repo,
+ commit_id);
+ if (error)
+ goto done;
+ }
+
+ error = got_worktree_checkout_files(worktree, repo,
+ update_progress, NULL, checkout_cancel, NULL);
+ if (error != NULL)
+ goto done;
+
+ printf("Now shut up and hack\n");
+
+done:
free(worktree_path);
+ free(commit_id);
return error;
}
blob - 1820266f93639ec63112a3c7606a8f613e897296
blob + 86beac84b3fe35b60f450a10b4b148f68230b12c
--- include/got_opentemp.h
+++ include/got_opentemp.h
/* Open a new temporary file for writing.
* The file is visible in the filesystem. */
const struct got_error *got_opentemp_named(char **, FILE **, const char *);
+
+/* Like got_opentemp_named() but returns a file descriptor instead of a FILE. */
+const struct got_error *got_opentemp_named_fd(char **, int *, const char *);
blob - 49f1b19f634b166145be20e9b7d2feaca633cfed
blob + 43411d61291f20f6d25f20ec303368bac36730c1
--- include/got_worktree.h
+++ include/got_worktree.h
/* status codes */
#define GOT_STATUS_ADD 'A'
#define GOT_STATUS_EXISTS 'E'
+#define GOT_STATUS_UPDATE 'U'
/*
* Attempt to initialize a new work tree on disk.
*/
char *got_worktree_get_head_ref_name(struct got_worktree *);
+/*
+ * Get the current base commit ID of a worktree.
+ */
+const struct got_object_id *got_worktree_get_base_commit_id(struct got_worktree *);
+
+/*
+ * Set the base commit Id of a worktree.
+ */
+const struct got_error *got_worktree_set_base_commit_id(struct got_worktree *,
+ struct got_repository *, struct got_object_id *);
+
/* A callback function which is invoked when a path is checked out. */
typedef void (*got_worktree_checkout_cb)(void *, unsigned char, const char *);
const struct got_error *got_worktree_checkout_files(struct got_worktree *,
struct got_repository *, got_worktree_checkout_cb progress, void *,
got_worktree_cancel_cb, void *);
+
blob - 082513f08946d9059c5cf1788e363acbe82371fa
blob + b9667ec410d6319b83dcc489049007dd715f5889
--- lib/opentemp.c
+++ lib/opentemp.c
return err;
}
+
+const struct got_error *
+got_opentemp_named_fd(char **path, int *outfd, const char *basepath)
+{
+ const struct got_error *err = NULL;
+ int fd;
+
+ *outfd = -1;
+
+ if (asprintf(path, "%s-XXXXXX", basepath) == -1) {
+ *path = NULL;
+ return got_error_from_errno();
+ }
+
+ fd = mkstemp(*path);
+ if (fd == -1) {
+ err = got_error_from_errno();
+ free(*path);
+ *path = NULL;
+ return err;
+ }
+
+ *outfd = fd;
+ return err;
+}
blob - 174d53dbfa4f1c9b5afae594ea6901007891a69e
blob + 67ce5a4496764e165e3247162f316282aa5b2736
--- lib/worktree.c
+++ lib/worktree.c
if (fd != -1 && close(fd) == -1 && err == NULL)
err = got_error_from_errno();
free(path);
+ return err;
+}
+
+static const struct got_error *
+update_meta_file(const char *path_got, const char *name, const char *content)
+{
+ const struct got_error *err = NULL;
+ FILE *tmpfile = NULL;
+ char *tmppath = NULL;
+ char *path = NULL;
+
+ if (asprintf(&path, "%s/%s", path_got, name) == -1) {
+ err = got_error_from_errno();
+ path = NULL;
+ goto done;
+ }
+
+ err = got_opentemp_named(&tmppath, &tmpfile, path);
+ if (err)
+ goto done;
+
+ if (content) {
+ int len = fprintf(tmpfile, "%s\n", content);
+ if (len != strlen(content) + 1) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ }
+
+ if (rename(tmppath, path) != 0) {
+ err = got_error_from_errno();
+ goto done;
+ }
+
+done:
+ free(tmppath);
+ fclose(tmpfile);
return err;
}
got_worktree_get_head_ref_name(struct got_worktree *worktree)
{
return got_ref_to_str(worktree->head_ref);
+}
+
+const struct got_object_id *
+got_worktree_get_base_commit_id(struct got_worktree *worktree)
+{
+ return worktree->base_commit_id;
}
+const struct got_error *
+got_worktree_set_base_commit_id(struct got_worktree *worktree,
+ struct got_repository *repo, struct got_object_id *commit_id)
+{
+ const struct got_error *err;
+ struct got_object *obj = NULL;
+ char *id_str = NULL;
+ char *path_got = NULL;
+
+ if (asprintf(&path_got, "%s/%s", worktree->root_path,
+ GOT_WORKTREE_GOT_DIR) == -1) {
+ err = got_error_from_errno();
+ path_got = NULL;
+ goto done;
+ }
+
+ err = got_object_open(&obj, repo, commit_id);
+ if (err)
+ return err;
+
+ if (obj->type != GOT_OBJ_TYPE_COMMIT) {
+ err = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ /* Record our base commit. */
+ err = got_object_id_str(&id_str, commit_id);
+ if (err)
+ goto done;
+ err = update_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, id_str);
+ if (err)
+ goto done;
+
+ free(worktree->base_commit_id);
+ worktree->base_commit_id = got_object_id_dup(commit_id);
+ if (worktree->base_commit_id == NULL) {
+ err = got_error_from_errno();
+ goto done;
+ }
+done:
+ if (obj)
+ got_object_close(obj);
+ free(id_str);
+ free(path_got);
+ return err;
+}
+
static const struct got_error *
lock_worktree(struct got_worktree *worktree, int operation)
{
{
const struct got_error *err = NULL;
char *ondisk_path;
- int fd;
+ int fd = -1;
size_t len, hdrlen;
+ int update = 0;
+ char *tmppath = NULL;
if (asprintf(&ondisk_path, "%s/%s", worktree->root_path,
apply_path_prefix(worktree, path)) == -1)
struct stat sb;
if (lstat(ondisk_path, &sb) == -1) {
err = got_error_from_errno();
+ goto done;
} else if (!S_ISREG(sb.st_mode)) {
/* TODO file is obstructed; do something */
err = got_error(GOT_ERR_FILE_OBSTRUCTED);
+ goto done;
} else {
- /* TODO: Merge the file! */
- (*progress_cb)(progress_arg, GOT_STATUS_EXISTS,
- progress_path);
- return NULL;
+ err = got_opentemp_named_fd(&tmppath, &fd,
+ ondisk_path);
+ if (err)
+ goto done;
+ update = 1;
}
- }
- return err;
+ } else
+ return err;
}
- (*progress_cb)(progress_arg, GOT_STATUS_ADD, progress_path);
+ (*progress_cb)(progress_arg,
+ update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, progress_path);
hdrlen = got_object_blob_get_hdrlen(blob);
do {
fsync(fd);
+ if (update) {
+ if (rename(tmppath, ondisk_path) != 0) {
+ err = got_error_from_errno();
+ goto done;
+ }
+ }
+
if (entry)
err = got_fileindex_entry_update(entry, ondisk_path,
blob->id.sha1, worktree->base_commit_id->sha1);
if (err)
goto done;
done:
- close(fd);
+ if (fd != -1)
+ close(fd);
free(ondisk_path);
+ free(tmppath);
return err;
}
apply_path_prefix(worktree, path));
if (entry &&
memcmp(entry->commit_sha1, worktree->base_commit_id->sha1,
+ SHA1_DIGEST_LENGTH) == 0) {
+ (*progress_cb)(progress_arg, GOT_STATUS_EXISTS,
+ progress_path);
+ break;
+ }
+ if (entry && memcmp(entry->blob_sha1, obj->id.sha1,
SHA1_DIGEST_LENGTH) == 0)
- break; /* file already checked out */
+ break;
err = got_object_blob_open(&blob, repo, obj, 8192);
if (err)
goto done;