| 1 | /* |
| 2 | * GIT - The information manager from hell |
| 3 | * |
| 4 | * Copyright (C) Linus Torvalds, 2005 |
| 5 | */ |
| 6 | #define USE_THE_REPOSITORY_VARIABLE |
| 7 | #include "builtin.h" |
| 8 | #include "config.h" |
| 9 | #include "environment.h" |
| 10 | #include "gettext.h" |
| 11 | #include "hex.h" |
| 12 | #include "object-name.h" |
| 13 | #include "odb.h" |
| 14 | |
| 15 | #include "commit.h" |
| 16 | #include "parse-options.h" |
| 17 | |
| 18 | static const char * const commit_tree_usage[] = { |
| 19 | N_("git commit-tree <tree> [(-p <parent>)...]"), |
| 20 | N_("git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...]\n" |
| 21 | " [(-F <file>)...] <tree>"), |
| 22 | NULL |
| 23 | }; |
| 24 | |
| 25 | static const char *sign_commit; |
| 26 | |
| 27 | static void new_parent(struct commit *parent, struct commit_list **parents_p) |
| 28 | { |
| 29 | struct object_id *oid = &parent->object.oid; |
| 30 | struct commit_list *parents; |
| 31 | for (parents = *parents_p; parents; parents = parents->next) { |
| 32 | if (parents->item == parent) { |
| 33 | error(_("duplicate parent %s ignored"), oid_to_hex(oid)); |
| 34 | return; |
| 35 | } |
| 36 | parents_p = &parents->next; |
| 37 | } |
| 38 | commit_list_insert(parent, parents_p); |
| 39 | } |
| 40 | |
| 41 | static int parse_parent_arg_callback(const struct option *opt, |
| 42 | const char *arg, int unset) |
| 43 | { |
| 44 | struct object_id oid; |
| 45 | struct commit_list **parents = opt->value; |
| 46 | |
| 47 | BUG_ON_OPT_NEG_NOARG(unset, arg); |
| 48 | |
| 49 | if (repo_get_oid_commit(the_repository, arg, &oid)) |
| 50 | die(_("not a valid object name %s"), arg); |
| 51 | |
| 52 | odb_assert_oid_type(the_repository->objects, &oid, OBJ_COMMIT); |
| 53 | new_parent(lookup_commit(the_repository, &oid), parents); |
| 54 | return 0; |
| 55 | } |
| 56 | |
| 57 | static int parse_message_arg_callback(const struct option *opt, |
| 58 | const char *arg, int unset) |
| 59 | { |
| 60 | struct strbuf *buf = opt->value; |
| 61 | |
| 62 | BUG_ON_OPT_NEG_NOARG(unset, arg); |
| 63 | |
| 64 | if (buf->len) |
| 65 | strbuf_addch(buf, '\n'); |
| 66 | strbuf_addstr(buf, arg); |
| 67 | strbuf_complete_line(buf); |
| 68 | |
| 69 | return 0; |
| 70 | } |
| 71 | |
| 72 | static int parse_file_arg_callback(const struct option *opt, |
| 73 | const char *arg, int unset) |
| 74 | { |
| 75 | int fd; |
| 76 | struct strbuf *buf = opt->value; |
| 77 | |
| 78 | BUG_ON_OPT_NEG_NOARG(unset, arg); |
| 79 | |
| 80 | if (buf->len) |
| 81 | strbuf_addch(buf, '\n'); |
| 82 | if (!strcmp(arg, "-")) |
| 83 | fd = 0; |
| 84 | else { |
| 85 | fd = xopen(arg, O_RDONLY); |
| 86 | } |
| 87 | if (strbuf_read(buf, fd, 0) < 0) |
| 88 | die_errno(_("git commit-tree: failed to read '%s'"), arg); |
| 89 | if (fd && close(fd)) |
| 90 | die_errno(_("git commit-tree: failed to close '%s'"), arg); |
| 91 | |
| 92 | return 0; |
| 93 | } |
| 94 | |
| 95 | int cmd_commit_tree(int argc, |
| 96 | const char **argv, |
| 97 | const char *prefix, |
| 98 | struct repository *repo UNUSED) |
| 99 | { |
| 100 | static struct strbuf buffer = STRBUF_INIT; |
| 101 | struct commit_list *parents = NULL; |
| 102 | struct object_id tree_oid; |
| 103 | struct object_id commit_oid; |
| 104 | |
| 105 | struct option options[] = { |
| 106 | OPT_CALLBACK_F('p', NULL, &parents, N_("parent"), |
| 107 | N_("id of a parent commit object"), PARSE_OPT_NONEG, |
| 108 | parse_parent_arg_callback), |
| 109 | OPT_CALLBACK_F('m', NULL, &buffer, N_("message"), |
| 110 | N_("commit message"), PARSE_OPT_NONEG, |
| 111 | parse_message_arg_callback), |
| 112 | OPT_CALLBACK_F('F', NULL, &buffer, N_("file"), |
| 113 | N_("read commit log message from file"), PARSE_OPT_NONEG, |
| 114 | parse_file_arg_callback), |
| 115 | { |
| 116 | .type = OPTION_STRING, |
| 117 | .short_name = 'S', |
| 118 | .long_name = "gpg-sign", |
| 119 | .value = &sign_commit, |
| 120 | .argh = N_("key-id"), |
| 121 | .help = N_("GPG sign commit"), |
| 122 | .flags = PARSE_OPT_OPTARG, |
| 123 | .defval = (intptr_t) "", |
| 124 | }, |
| 125 | OPT_END() |
| 126 | }; |
| 127 | int ret; |
| 128 | |
| 129 | repo_config(the_repository, git_default_config, NULL); |
| 130 | |
| 131 | show_usage_with_options_if_asked(argc, argv, |
| 132 | commit_tree_usage, options); |
| 133 | |
| 134 | argc = parse_options(argc, argv, prefix, options, commit_tree_usage, 0); |
| 135 | |
| 136 | if (argc != 1) |
| 137 | die(_("must give exactly one tree")); |
| 138 | |
| 139 | if (repo_get_oid_tree(the_repository, argv[0], &tree_oid)) |
| 140 | die(_("not a valid object name %s"), argv[0]); |
| 141 | |
| 142 | if (!buffer.len) { |
| 143 | if (strbuf_read(&buffer, 0, 0) < 0) |
| 144 | die_errno(_("git commit-tree: failed to read")); |
| 145 | } |
| 146 | |
| 147 | if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid, |
| 148 | NULL, sign_commit)) { |
| 149 | ret = 1; |
| 150 | goto out; |
| 151 | } |
| 152 | |
| 153 | printf("%s\n", oid_to_hex(&commit_oid)); |
| 154 | ret = 0; |
| 155 | |
| 156 | out: |
| 157 | commit_list_free(parents); |
| 158 | strbuf_release(&buffer); |
| 159 | return ret; |
| 160 | } |