commit 3ce1b84566a5dc6cbbfcfc87507aa84de4f0c9b9 from: Stefan Sperling date: Mon Jul 15 01:24:13 2019 UTC initial 'got import' implementation commit - 43012d5817071b74fc38aef617a3eb8f2da4a945 commit + 3ce1b84566a5dc6cbbfcfc87507aa84de4f0c9b9 blob - ac8037722555366872b56f211bcccace894db4d8 blob + 1103aa277a4635a8fe84c1c7112afb0056d03ded --- got/got.1 +++ got/got.1 @@ -63,6 +63,58 @@ are as follows: .It Cm init Ar path Create a new empty repository at the specified .Ar path . +.Pp +After +.Cm got init , +the +.Cm got import +command must be used to populate the empty repository before +.Cm got checkout +can be used. +.Pp +.It Cm import [ Fl b Ar branch ] [ Fl m Ar message ] [ Fl r Ar repository-path ] [ Fl I Ar pattern ] directory +Create an initial commit in a repository from the file hierarchy +within the specified +.Ar directory . +The created commit will not have any parent commits, i.e. it will be a +root commit. +Also create a new reference which provides a branch name for the newly +created commit. +.Pp +Show the path of each added file to indicate progress. +.Pp +The options for +.Cm got import +are as follows: +.Bl -tag -width Ds +.It Fl b Ar branch +Create the specified +.Ar branch +instead of creating the default branch +.Dq master . +Use of this option is required if the +.Dq master +branch already exists. +.It Fl m Ar message +Use the specified log message when creating the new commit. +Without the +.Fl m +option, +.Cm got import +opens a temporary file in an editor where a log message can be written. +.It Fl r Ar repository-path +Use the repository at the specified path. +If not specified, assume the repository is located at or above the current +working directory. +.It Fl I Ar pattern +Ignore files or directories with a name which matches the specified +.Ar pattern . +This option may be specified multiple times to build a list of ignore patterns. +The +.Ar pattern +follows the globbing rules documented in +.Xr glob 7 . +.El .It Cm checkout [ Fl b Ar branch ] [ Fl c Ar commit ] [ Fl p Ar path-prefix ] repository-path [ work-tree-path ] Copy files from a repository into a new work tree. If the @@ -639,7 +691,6 @@ The editor spawned by .Sh EXIT STATUS .Ex -std got .Sh EXAMPLES -.Pp Clone an existing Git repository for use with .Nm . This step currently requires @@ -648,8 +699,17 @@ This step currently requires .Dl $ cd /var/git/ .Dl $ git clone --bare https://github.com/openbsd/src.git .Pp -Check out a work tree from this Git repository to /usr/src: +Alternatively, for quick and dirty local testing of +.Nm +a new Git repository could be created and populated with files, +e.g. from a temporary CVS checkout located at +.Pa /tmp/src : .Pp +.Dl $ got init /var/git/src.git +.Dl $ got import -r /var/src.git -I CVS -I obj /tmp/src +.Pp +Check out a work tree from the Git repository to /usr/src: +.Pp .Dl $ got checkout /var/git/src.git /usr/src .Pp View local changes in a work tree directory: blob - 0bfd597accf4b09dfa3552fc715ec1c20c3108b7 blob + 2f7d8b5ef12121c16e9340c8363bb580459f61da --- got/got.c +++ got/got.c @@ -76,6 +76,7 @@ struct got_cmd { __dead static void usage(int); __dead static void usage_init(void); +__dead static void usage_import(void); __dead static void usage_checkout(void); __dead static void usage_update(void); __dead static void usage_log(void); @@ -94,6 +95,7 @@ __dead static void usage_backout(void); __dead static void usage_rebase(void); static const struct got_error* cmd_init(int, char *[]); +static const struct got_error* cmd_import(int, char *[]); 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 *[]); @@ -113,6 +115,7 @@ static const struct got_error* cmd_rebase(int, char * static struct got_cmd got_commands[] = { { "init", cmd_init, usage_init, "" }, + { "import", cmd_import, usage_import, "" }, { "checkout", cmd_checkout, usage_checkout, "co" }, { "update", cmd_update, usage_update, "up" }, { "log", cmd_log, usage_log, "" }, @@ -327,10 +330,317 @@ cmd_init(int argc, char *argv[]) error = got_repo_init(repo_path); if (error != NULL) + goto done; + +done: + free(repo_path); + return error; +} + +__dead static void +usage_import(void) +{ + fprintf(stderr, "usage: %s import [-b branch] [-m message] " + "[-r repository-path] [-I pattern] path\n", getprogname()); + exit(1); +} + +int +spawn_editor(const char *editor, const char *file) +{ + pid_t pid; + sig_t sighup, sigint, sigquit; + int st = -1; + + sighup = signal(SIGHUP, SIG_IGN); + sigint = signal(SIGINT, SIG_IGN); + sigquit = signal(SIGQUIT, SIG_IGN); + + switch (pid = fork()) { + case -1: + goto doneediting; + case 0: + execl(editor, editor, file, (char *)NULL); + _exit(127); + } + + while (waitpid(pid, &st, 0) == -1) + if (errno != EINTR) + break; + +doneediting: + (void)signal(SIGHUP, sighup); + (void)signal(SIGINT, sigint); + (void)signal(SIGQUIT, sigquit); + + if (!WIFEXITED(st)) { + errno = EINTR; + return -1; + } + + return WEXITSTATUS(st); +} + +static const struct got_error * +edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path, + const char *initial_content) +{ + const struct got_error *err = NULL; + char buf[1024]; + struct stat st, st2; + FILE *fp; + int content_changed = 0; + size_t len; + + *logmsg = NULL; + + if (stat(logmsg_path, &st) == -1) + return got_error_from_errno2("stat", logmsg_path); + + if (spawn_editor(editor, logmsg_path) == -1) + return got_error_from_errno("failed spawning editor"); + + if (stat(logmsg_path, &st2) == -1) + return got_error_from_errno("stat"); + + if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) + return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "no changes made to commit message, aborting"); + + *logmsg = malloc(st2.st_size + 1); + if (*logmsg == NULL) + return got_error_from_errno("malloc"); + (*logmsg)[0] = '\0'; + len = 0; + + fp = fopen(logmsg_path, "r"); + if (fp == NULL) { + err = got_error_from_errno("fopen"); + goto done; + } + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (!content_changed && strcmp(buf, initial_content) != 0) + content_changed = 1; + if (buf[0] == '#' || (len == 0 && buf[0] == '\n')) + continue; /* remove comments and leading empty lines */ + len = strlcat(*logmsg, buf, st2.st_size); + } + fclose(fp); + + while (len > 0 && (*logmsg)[len - 1] == '\n') { + (*logmsg)[len - 1] = '\0'; + len--; + } + + if (len == 0 || !content_changed) + err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, + "commit message cannot be empty, aborting"); +done: + if (err) { + free(*logmsg); + *logmsg = NULL; + } + return err; +} + +static const struct got_error * +collect_import_msg(char **logmsg, const char *editor, const char *path_dir, + const char *branch_name) +{ + char *initial_content = NULL, *logmsg_path = NULL; + const struct got_error *err = NULL; + int fd; + + if (asprintf(&initial_content, + "\n# %s to be imported to branch %s\n", path_dir, + branch_name) == -1) + return got_error_from_errno("asprintf"); + + err = got_opentemp_named_fd(&logmsg_path, &fd, "/tmp/got-importmsg"); + if (err) + goto done; + + dprintf(fd, initial_content); + close(fd); + + err = edit_logmsg(logmsg, editor, logmsg_path, initial_content); +done: + free(initial_content); + free(logmsg_path); + return err; +} + +static const struct got_error * +import_progress(void *arg, const char *path) +{ + printf("A %s\n", path); + return NULL; +} + +static const struct got_error * +cmd_import(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; + char *editor = NULL; + const char *got_author = getenv("GOT_AUTHOR"); + const char *branch_name = "master"; + char *refname = NULL, *id_str = NULL; + struct got_repository *repo = NULL; + struct got_reference *branch_ref = NULL, *head_ref = NULL; + struct got_object_id *new_commit_id = NULL; + int ch; + struct got_pathlist_head ignores; + struct got_pathlist_entry *pe; + + TAILQ_INIT(&ignores); + + while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { + switch (ch) { + case 'b': + branch_name = optarg; + break; + case 'm': + logmsg = strdup(optarg); + if (logmsg == NULL) { + error = got_error_from_errno("strdup"); + goto done; + } + break; + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) { + error = got_error_from_errno("realpath"); + goto done; + } + break; + case 'I': + if (optarg[0] == '\0') + break; + error = got_pathlist_insert(&pe, &ignores, optarg, + NULL); + if (error) + goto done; + break; + default: + usage_init(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath fattr flock proc exec unveil", + NULL) == -1) + err(1, "pledge"); +#endif + if (argc != 1) + usage_import(); + + if (got_author == NULL) { + /* TODO: Look current user up in password database */ + error = got_error(GOT_ERR_COMMIT_NO_AUTHOR); goto done; + } + if (repo_path == NULL) { + repo_path = getcwd(NULL, 0); + if (repo_path == NULL) + return got_error_from_errno("getcwd"); + } + got_path_strip_trailing_slashes(repo_path); + error = got_repo_open(&repo, repo_path); + if (error) + goto done; + + if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_ref_open(&branch_ref, repo, refname, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + } else { + error = got_error_msg(GOT_ERR_BRANCH_EXISTS, + "import target branch already exists"); + goto done; + } + + path_dir = realpath(argv[0], NULL); + if (path_dir == NULL) { + error = got_error_from_errno("realpath"); + goto done; + } + got_path_strip_trailing_slashes(path_dir); + + /* + * unveil(2) traverses exec(2); if an editor is used we have + * to apply unveil after the log message has been written. + */ + if (logmsg == NULL || strlen(logmsg) == 0) { + error = get_editor(&editor); + if (error) + goto done; + error = collect_import_msg(&logmsg, editor, path_dir, refname); + if (error) + goto done; + } + + if (unveil(path_dir, "r") != 0) + return got_error_from_errno2("unveil", path_dir); + + error = apply_unveil(got_repo_get_path(repo), 0, NULL, 0); + if (error) + goto done; + + error = got_repo_import(&new_commit_id, path_dir, logmsg, + got_author, &ignores, repo, import_progress, NULL); + if (error) + goto done; + + error = got_ref_alloc(&branch_ref, refname, new_commit_id); + if (error) + goto done; + + error = got_ref_write(branch_ref, repo); + if (error) + goto done; + + error = got_object_id_str(&id_str, new_commit_id); + if (error) + goto done; + + error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); + if (error) { + if (error->code != GOT_ERR_NOT_REF) + goto done; + + error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, + branch_ref); + if (error) + goto done; + + error = got_ref_write(head_ref, repo); + if (error) + goto done; + } + + printf("Created branch %s with commit %s\n", + got_ref_get_name(branch_ref), id_str); done: free(repo_path); + free(editor); + free(refname); + free(new_commit_id); + free(id_str); + if (branch_ref) + got_ref_close(branch_ref); + if (head_ref) + got_ref_close(head_ref); return error; } @@ -2759,43 +3069,7 @@ usage_commit(void) fprintf(stderr, "usage: %s commit [-m msg] file-path\n", getprogname()); exit(1); } - -int -spawn_editor(const char *editor, const char *file) -{ - pid_t pid; - sig_t sighup, sigint, sigquit; - int st = -1; - sighup = signal(SIGHUP, SIG_IGN); - sigint = signal(SIGINT, SIG_IGN); - sigquit = signal(SIGQUIT, SIG_IGN); - - switch (pid = fork()) { - case -1: - goto doneediting; - case 0: - execl(editor, editor, file, (char *)NULL); - _exit(127); - } - - while (waitpid(pid, &st, 0) == -1) - if (errno != EINTR) - break; - -doneediting: - (void)signal(SIGHUP, sighup); - (void)signal(SIGINT, sigint); - (void)signal(SIGQUIT, sigquit); - - if (!WIFEXITED(st)) { - errno = EINTR; - return -1; - } - - return WEXITSTATUS(st); -} - struct collect_commit_logmsg_arg { const char *cmdline_log; const char *editor; @@ -2815,11 +3089,8 @@ collect_commit_logmsg(struct got_pathlist_head *commit const struct got_error *err = NULL; char *template = NULL; struct collect_commit_logmsg_arg *a = arg; - char buf[1024]; - struct stat st, st2; - FILE *fp; + int fd; size_t len; - int fd, content_changed = 0; /* if a message was specified on the command line, just use it */ if (a->cmdline_log != NULL && strlen(a->cmdline_log) != 0) { @@ -2853,72 +3124,21 @@ collect_commit_logmsg(struct got_pathlist_head *commit } close(fd); - if (stat(a->logmsg_path, &st) == -1) { - err = got_error_from_errno2("stat", a->logmsg_path); - goto done; - } - - if (spawn_editor(a->editor, a->logmsg_path) == -1) { - err = got_error_from_errno("failed spawning editor"); - goto done; - } - - if (stat(a->logmsg_path, &st2) == -1) { - err = got_error_from_errno("stat"); - goto done; - } - - if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) { - unlink(a->logmsg_path); - free(a->logmsg_path); - a->logmsg_path = NULL; - err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, - "no changes made to commit message, aborting"); - goto done; - } - - *logmsg = malloc(st2.st_size + 1); - if (*logmsg == NULL) { - err = got_error_from_errno("malloc"); - goto done; - } - (*logmsg)[0] = '\0'; - len = 0; - - fp = fopen(a->logmsg_path, "r"); - if (fp == NULL) { - err = got_error_from_errno("fopen"); - goto done; - } - while (fgets(buf, sizeof(buf), fp) != NULL) { - if (!content_changed && strcmp(buf, initial_content) != 0) - content_changed = 1; - if (buf[0] == '#' || (len == 0 && buf[0] == '\n')) - continue; /* remove comments and leading empty lines */ - len = strlcat(*logmsg, buf, st2.st_size); - } - fclose(fp); - - while (len > 0 && (*logmsg)[len - 1] == '\n') { - (*logmsg)[len - 1] = '\0'; - len--; - } - - if (len == 0 || !content_changed) { - unlink(a->logmsg_path); - free(a->logmsg_path); - a->logmsg_path = NULL; - err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, - "commit message cannot be empty, aborting"); - goto done; - } + err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content); done: + unlink(a->logmsg_path); + free(a->logmsg_path); free(initial_content); free(template); /* Editor is done; we can now apply unveil(2) */ - if (err == NULL) + if (err == NULL) { err = apply_unveil(a->repo_path, 0, a->worktree_path, 0); + if (err) { + free(*logmsg); + *logmsg = NULL; + } + } return err; } blob - d4ac915a022f60064bf883eaa2bc5b4bb7355611 blob + 6b8435a8f69b24786fa17e77464465e2339529e4 --- include/got_repository.h +++ include/got_repository.h @@ -15,6 +15,7 @@ */ struct got_repository; +struct got_pathlist_head; /* Open and close repositories. */ const struct got_error *got_repo_open(struct got_repository**, const char *); @@ -61,3 +62,14 @@ const struct got_error *got_repo_init(const char *); /* Attempt to find a unique object ID for a given ID string prefix. */ const struct got_error *got_repo_match_object_id_prefix(struct got_object_id **, const char *, int, struct got_repository *); + +/* A callback function which is invoked when a path is imported. */ +typedef const struct got_error *(*got_repo_import_cb)(void *, const char *); + +/* + * Import an unversioned directory tree into the repository. + * Creates a root commit, i.e. a commit with zero parents. + */ +const struct got_error *got_repo_import(struct got_object_id **, const char *, + const char *, const char *, struct got_pathlist_head *, + struct got_repository *, got_repo_import_cb, void *); blob - feb2dcacbe2d9ef61576ea6b746e5588d5a1d92a blob + 867b2d236255af662c58302c5e1d984d299dbc85 --- lib/object_create.c +++ lib/object_create.c @@ -392,28 +392,30 @@ got_object_commit_create(struct got_object_id **id, goto done; } - SIMPLEQ_FOREACH(qid, parent_ids, entry) { - char *parent_str = NULL; + if (parent_ids) { + SIMPLEQ_FOREACH(qid, parent_ids, entry) { + char *parent_str = NULL; - free(id_str); + free(id_str); - err = got_object_id_str(&id_str, qid->id); - if (err) - goto done; - if (asprintf(&parent_str, "%s%s\n", GOT_COMMIT_LABEL_PARENT, - id_str) == -1) { - err = got_error_from_errno("asprintf"); - goto done; - } - len = strlen(parent_str); - SHA1Update(&sha1_ctx, parent_str, len); - n = fwrite(parent_str, 1, len, commitfile); - if (n != len) { - err = got_ferror(commitfile, GOT_ERR_IO); + err = got_object_id_str(&id_str, qid->id); + if (err) + goto done; + if (asprintf(&parent_str, "%s%s\n", + GOT_COMMIT_LABEL_PARENT, id_str) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + len = strlen(parent_str); + SHA1Update(&sha1_ctx, parent_str, len); + n = fwrite(parent_str, 1, len, commitfile); + if (n != len) { + err = got_ferror(commitfile, GOT_ERR_IO); + free(parent_str); + goto done; + } free(parent_str); - goto done; } - free(parent_str); } len = strlen(author_str); blob - c8076c6355d6683f3fadaa770be0345c15825f48 blob + 39c436e7f663ea304f8d339f4eebc4f646156e15 --- lib/repository.c +++ lib/repository.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,8 @@ #include "got_lib_delta.h" #include "got_lib_inflate.h" #include "got_lib_object.h" +#include "got_lib_object_parse.h" +#include "got_lib_object_create.h" #include "got_lib_pack.h" #include "got_lib_privsep.h" #include "got_lib_worktree.h" @@ -1109,3 +1112,223 @@ done: return err; } + +static const struct got_error * +alloc_added_blob_tree_entry(struct got_tree_entry **new_te, + const char *name, mode_t mode, struct got_object_id *blob_id) +{ + const struct got_error *err = NULL; + + *new_te = NULL; + + *new_te = calloc(1, sizeof(**new_te)); + if (*new_te == NULL) + return got_error_from_errno("calloc"); + + (*new_te)->name = strdup(name); + if ((*new_te)->name == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + (*new_te)->mode = S_IFREG | (mode & ((S_IRWXU | S_IRWXG | S_IRWXO))); + (*new_te)->id = blob_id; +done: + if (err && *new_te) { + got_object_tree_entry_close(*new_te); + *new_te = NULL; + } + return err; +} + +static const struct got_error * +import_file(struct got_tree_entry **new_te, struct dirent *de, + const char *path, struct got_repository *repo) +{ + const struct got_error *err; + struct got_object_id *blob_id = NULL; + char *filepath; + struct stat sb; + + if (asprintf(&filepath, "%s%s%s", path, + path[0] == '\0' ? "" : "/", de->d_name) == -1) + return got_error_from_errno("asprintf"); + + if (lstat(filepath, &sb) != 0) { + err = got_error_from_errno2("lstat", path); + goto done; + } + + err = got_object_blob_create(&blob_id, filepath, repo); + if (err) + goto done; + + err = alloc_added_blob_tree_entry(new_te, de->d_name, sb.st_mode, + blob_id); +done: + free(filepath); + if (err) + free(blob_id); + return err; +} + +static const struct got_error * +insert_tree_entry(struct got_tree_entry *new_te, + struct got_pathlist_head *paths) +{ + const struct got_error *err = NULL; + struct got_pathlist_entry *new_pe; + + err = got_pathlist_insert(&new_pe, paths, new_te->name, new_te); + if (err) + return err; + if (new_pe == NULL) + return got_error(GOT_ERR_TREE_DUP_ENTRY); + return NULL; +} + +static const struct got_error *write_tree(struct got_object_id **, + const char *, struct got_pathlist_head *, struct got_repository *, + got_repo_import_cb progress_cb, void *progress_arg); + +static const struct got_error * +import_subdir(struct got_tree_entry **new_te, struct dirent *de, + const char *path, struct got_pathlist_head *ignores, + struct got_repository *repo, + got_repo_import_cb progress_cb, void *progress_arg) +{ + const struct got_error *err; + char *subdirpath; + + if (asprintf(&subdirpath, "%s%s%s", path, + path[0] == '\0' ? "" : "/", de->d_name) == -1) + return got_error_from_errno("asprintf"); + + (*new_te) = calloc(1, sizeof(**new_te)); + (*new_te)->mode = S_IFDIR; + (*new_te)->name = strdup(de->d_name); + if ((*new_te)->name == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + err = write_tree(&(*new_te)->id, subdirpath, ignores, repo, + progress_cb, progress_arg); +done: + free(subdirpath); + if (err) { + got_object_tree_entry_close(*new_te); + *new_te = NULL; + } + return err; +} + +static const struct got_error * +write_tree(struct got_object_id **new_tree_id, const char *path_dir, + struct got_pathlist_head *ignores, struct got_repository *repo, + got_repo_import_cb progress_cb, void *progress_arg) +{ + const struct got_error *err = NULL; + DIR *dir; + struct dirent *de; + struct got_tree_entries new_tree_entries; + struct got_tree_entry *new_te = NULL; + struct got_pathlist_head paths; + struct got_pathlist_entry *pe; + char *name; + + *new_tree_id = NULL; + + TAILQ_INIT(&paths); + new_tree_entries.nentries = 0; + SIMPLEQ_INIT(&new_tree_entries.head); + + dir = opendir(path_dir); + if (dir == NULL) { + err = got_error_from_errno2("opendir", path_dir); + goto done; + } + + while ((de = readdir(dir)) != NULL) { + int ignore = 0; + + if (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0) + continue; + + TAILQ_FOREACH(pe, ignores, entry) { + if (fnmatch(pe->path, de->d_name, 0) == 0) { + ignore = 1; + break; + } + } + if (ignore) + continue; + if (de->d_type == DT_DIR) { + err = import_subdir(&new_te, de, path_dir, + ignores, repo, progress_cb, progress_arg); + if (err) + goto done; + } else if (de->d_type == DT_REG) { + err = import_file(&new_te, de, path_dir, repo); + if (err) + goto done; + } else + continue; + + err = insert_tree_entry(new_te, &paths); + if (err) + goto done; + } + + TAILQ_FOREACH(pe, &paths, entry) { + struct got_tree_entry *te = pe->data; + char *path; + new_tree_entries.nentries++; + SIMPLEQ_INSERT_TAIL(&new_tree_entries.head, te, entry); + if (!S_ISREG(te->mode)) + continue; + if (asprintf(&path, "%s/%s", path_dir, pe->path) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + err = (*progress_cb)(progress_arg, path); + free(path); + if (err) + goto done; + } + + name = basename(path_dir); + if (name == NULL) { + err = got_error_from_errno("basename"); + goto done; + } + + err = got_object_tree_create(new_tree_id, &new_tree_entries, repo); +done: + if (dir) + closedir(dir); + got_object_tree_entries_close(&new_tree_entries); + got_pathlist_free(&paths); + return err; +} + +const struct got_error * +got_repo_import(struct got_object_id **new_commit_id, const char *path_dir, + const char *logmsg, const char *author, struct got_pathlist_head *ignores, + struct got_repository *repo, got_repo_import_cb progress_cb, + void *progress_arg) +{ + const struct got_error *err; + struct got_object_id *new_tree_id; + + err = write_tree(&new_tree_id, path_dir, ignores, repo, + progress_cb, progress_arg); + if (err) + return err; + + err = got_object_commit_create(new_commit_id, new_tree_id, NULL, 0, + author, time(NULL), author, time(NULL), logmsg, repo); + free(new_tree_id); + return err; +} blob - cc53c4052b7de555d29b4d516d502908cf15873f blob + 682c9e1542649798fd47bc71d7532329397d40f9 --- regress/cmdline/Makefile +++ regress/cmdline/Makefile @@ -1,5 +1,5 @@ REGRESS_TARGETS=checkout update status log add rm diff commit \ - cherrypick backout rebase + cherrypick backout rebase import NOOBJ=Yes checkout: @@ -35,4 +35,7 @@ backout: rebase: ./rebase.sh +import: + ./import.sh + .include blob - 793da6f9f59ddd31762b67fadea5b2769de756cf blob + 4b6b06494055b3a907d857c1b28fb33f12c57ada --- regress/cmdline/common.sh +++ regress/cmdline/common.sh @@ -88,9 +88,17 @@ function make_test_tree echo delta > $repo/gamma/delta mkdir $repo/epsilon echo zeta > $repo/epsilon/zeta - (cd $repo && git add .) } +function get_blob_id +{ + repo="$1" + tree_path="$2" + filename="$3" + + got tree -r $repo -i $tree_path | grep ${filename}$ | cut -d' ' -f 1 +} + function test_init { local testname="$1" @@ -104,6 +112,7 @@ function test_init git_init $testroot/repo if [ -z "$no_tree" ]; then make_test_tree $testroot/repo + (cd $repo && git add .) git_commit $testroot/repo -m "adding the test tree" fi echo "$testroot" blob - /dev/null blob + bf8a0681d0998fa6be6c82f3b8fb3dde90f1cbba (mode 755) --- /dev/null +++ regress/cmdline/import.sh @@ -0,0 +1,201 @@ +#!/bin/sh +# +# Copyright (c) 2019 Stefan Sperling +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +. ./common.sh + +function test_import_basic { + local testroot=`mktemp -p /tmp -d got-test-$testname-XXXXXXXX` + + got init $testroot/repo + + mkdir $testroot/tree + make_test_tree $testroot/tree + + got import -m 'init' -r $testroot/repo $testroot/tree \ + > $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + local head_commit=`git_show_head $testroot/repo` + echo "A $testroot/tree/gamma/delta" > $testroot/stdout.expected + echo "A $testroot/tree/epsilon/zeta" >> $testroot/stdout.expected + echo "A $testroot/tree/alpha" >> $testroot/stdout.expected + echo "A $testroot/tree/beta" >> $testroot/stdout.expected + echo "Created branch refs/heads/master with commit $head_commit" \ + >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && got log -p | grep -v ^date: > $testroot/stdout) + + id_alpha=`get_blob_id $testroot/repo "" alpha` + id_beta=`get_blob_id $testroot/repo "" beta` + id_zeta=`get_blob_id $testroot/repo epsilon zeta` + id_delta=`get_blob_id $testroot/repo gamma delta` + + echo "-----------------------------------------------" \ + > $testroot/stdout.expected + echo "commit $head_commit (master)" >> $testroot/stdout.expected + echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected + echo " " >> $testroot/stdout.expected + echo " init" >> $testroot/stdout.expected + echo " " >> $testroot/stdout.expected + echo "diff /dev/null $head_commit" >> $testroot/stdout.expected + echo "blob - /dev/null" >> $testroot/stdout.expected + echo "blob + $id_alpha" >> $testroot/stdout.expected + echo "--- /dev/null" >> $testroot/stdout.expected + echo "+++ alpha" >> $testroot/stdout.expected + echo "@@ -0,0 +1 @@" >> $testroot/stdout.expected + echo "+alpha" >> $testroot/stdout.expected + echo "blob - /dev/null" >> $testroot/stdout.expected + echo "blob + $id_beta" >> $testroot/stdout.expected + echo "--- /dev/null" >> $testroot/stdout.expected + echo "+++ beta" >> $testroot/stdout.expected + echo "@@ -0,0 +1 @@" >> $testroot/stdout.expected + echo "+beta" >> $testroot/stdout.expected + echo "blob - /dev/null" >> $testroot/stdout.expected + echo "blob + $id_zeta" >> $testroot/stdout.expected + echo "--- /dev/null" >> $testroot/stdout.expected + echo "+++ epsilon/zeta" >> $testroot/stdout.expected + echo "@@ -0,0 +1 @@" >> $testroot/stdout.expected + echo "+zeta" >> $testroot/stdout.expected + echo "blob - /dev/null" >> $testroot/stdout.expected + echo "blob + $id_delta" >> $testroot/stdout.expected + echo "--- /dev/null" >> $testroot/stdout.expected + echo "+++ gamma/delta" >> $testroot/stdout.expected + echo "@@ -0,0 +1 @@" >> $testroot/stdout.expected + echo "+delta" >> $testroot/stdout.expected + echo "" >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "A $testroot/wt/alpha" > $testroot/stdout.expected + echo "A $testroot/wt/beta" >> $testroot/stdout.expected + echo "A $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected + echo "A $testroot/wt/gamma/delta" >> $testroot/stdout.expected + echo "Now shut up and hack" >> $testroot/stdout.expected + + got checkout $testroot/repo $testroot/wt > $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "alpha" > $testroot/content.expected + echo "beta" >> $testroot/content.expected + echo "zeta" >> $testroot/content.expected + echo "delta" >> $testroot/content.expected + cat $testroot/wt/alpha $testroot/wt/beta $testroot/wt/epsilon/zeta \ + $testroot/wt/gamma/delta > $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + fi + test_done "$testroot" "$ret" +} + +function test_import_requires_new_branch { + local testroot=`test_init import_requires_new_branch` + + mkdir $testroot/tree + make_test_tree $testroot/tree + + got import -m 'init' -r $testroot/repo $testroot/tree \ + > $testroot/stdout 2> $testroot/stderr + ret="$?" + if [ "$ret" == "0" ]; then + echo "import command should have failed but did not" + test_done "$testroot" "1" + return 1 + fi + + echo "got: import target branch already exists" \ + > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + got import -b newbranch -m 'init' -r $testroot/repo $testroot/tree \ + > $testroot/stdout + ret="$?" + test_done "$testroot" "$ret" + +} + +function test_import_ignores { + local testroot=`mktemp -p /tmp -d got-test-$testname-XXXXXXXX` + + got init $testroot/repo + + mkdir $testroot/tree + make_test_tree $testroot/tree + + got import -I alpha -I '*lta*' -I '*silon' \ + -m 'init' -r $testroot/repo $testroot/tree > $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + local head_commit=`git_show_head $testroot/repo` + echo "A $testroot/tree/beta" >> $testroot/stdout.expected + echo "Created branch refs/heads/master with commit $head_commit" \ + >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" + + +} + +run_test test_import_basic +run_test test_import_requires_new_branch +run_test test_import_ignores