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