1/*
2 * Builtin "git am"
3 *
4 * Based on git-am.sh by Junio C Hamano.
5 */
6#include "cache.h"
7#include "builtin.h"
8#include "exec_cmd.h"
9#include "parse-options.h"
10#include "dir.h"
11
12struct am_state {
13 /* state directory path */
14 char *dir;
15
16 /* current and last patch numbers, 1-indexed */
17 int cur;
18 int last;
19};
20
21/**
22 * Initializes am_state with the default values. The state directory is set to
23 * dir.
24 */
25static void am_state_init(struct am_state *state, const char *dir)
26{
27 memset(state, 0, sizeof(*state));
28
29 assert(dir);
30 state->dir = xstrdup(dir);
31}
32
33/**
34 * Releases memory allocated by an am_state.
35 */
36static void am_state_release(struct am_state *state)
37{
38 free(state->dir);
39}
40
41/**
42 * Returns path relative to the am_state directory.
43 */
44static inline const char *am_path(const struct am_state *state, const char *path)
45{
46 return mkpath("%s/%s", state->dir, path);
47}
48
49/**
50 * Returns 1 if there is an am session in progress, 0 otherwise.
51 */
52static int am_in_progress(const struct am_state *state)
53{
54 struct stat st;
55
56 if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
57 return 0;
58 if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
59 return 0;
60 if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
61 return 0;
62 return 1;
63}
64
65/**
66 * Reads the contents of `file` in the `state` directory into `sb`. Returns the
67 * number of bytes read on success, -1 if the file does not exist. If `trim` is
68 * set, trailing whitespace will be removed.
69 */
70static int read_state_file(struct strbuf *sb, const struct am_state *state,
71 const char *file, int trim)
72{
73 strbuf_reset(sb);
74
75 if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
76 if (trim)
77 strbuf_trim(sb);
78
79 return sb->len;
80 }
81
82 if (errno == ENOENT)
83 return -1;
84
85 die_errno(_("could not read '%s'"), am_path(state, file));
86}
87
88/**
89 * Loads state from disk.
90 */
91static void am_load(struct am_state *state)
92{
93 struct strbuf sb = STRBUF_INIT;
94
95 if (read_state_file(&sb, state, "next", 1) < 0)
96 die("BUG: state file 'next' does not exist");
97 state->cur = strtol(sb.buf, NULL, 10);
98
99 if (read_state_file(&sb, state, "last", 1) < 0)
100 die("BUG: state file 'last' does not exist");
101 state->last = strtol(sb.buf, NULL, 10);
102
103 strbuf_release(&sb);
104}
105
106/**
107 * Removes the am_state directory, forcefully terminating the current am
108 * session.
109 */
110static void am_destroy(const struct am_state *state)
111{
112 struct strbuf sb = STRBUF_INIT;
113
114 strbuf_addstr(&sb, state->dir);
115 remove_dir_recursively(&sb, 0);
116 strbuf_release(&sb);
117}
118
119/**
120 * Setup a new am session for applying patches
121 */
122static void am_setup(struct am_state *state)
123{
124 if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
125 die_errno(_("failed to create directory '%s'"), state->dir);
126
127 /*
128 * NOTE: Since the "next" and "last" files determine if an am_state
129 * session is in progress, they should be written last.
130 */
131
132 write_file(am_path(state, "next"), 1, "%d", state->cur);
133
134 write_file(am_path(state, "last"), 1, "%d", state->last);
135}
136
137/**
138 * Increments the patch pointer, and cleans am_state for the application of the
139 * next patch.
140 */
141static void am_next(struct am_state *state)
142{
143 state->cur++;
144 write_file(am_path(state, "next"), 1, "%d", state->cur);
145}
146
147/**
148 * Applies all queued mail.
149 */
150static void am_run(struct am_state *state)
151{
152 while (state->cur <= state->last) {
153
154 /* NEEDSWORK: Patch application not implemented yet */
155
156 am_next(state);
157 }
158
159 am_destroy(state);
160}
161
162int cmd_am(int argc, const char **argv, const char *prefix)
163{
164 struct am_state state;
165
166 const char * const usage[] = {
167 N_("git am [options] [(<mbox>|<Maildir>)...]"),
168 NULL
169 };
170
171 struct option options[] = {
172 OPT_END()
173 };
174
175 /*
176 * NEEDSWORK: Once all the features of git-am.sh have been
177 * re-implemented in builtin/am.c, this preamble can be removed.
178 */
179 if (!getenv("_GIT_USE_BUILTIN_AM")) {
180 const char *path = mkpath("%s/git-am", git_exec_path());
181
182 if (sane_execvp(path, (char **)argv) < 0)
183 die_errno("could not exec %s", path);
184 } else {
185 prefix = setup_git_directory();
186 trace_repo_setup(prefix);
187 setup_work_tree();
188 }
189
190 git_config(git_default_config, NULL);
191
192 am_state_init(&state, git_path("rebase-apply"));
193
194 argc = parse_options(argc, argv, prefix, options, usage, 0);
195
196 if (am_in_progress(&state))
197 am_load(&state);
198 else
199 am_setup(&state);
200
201 am_run(&state);
202
203 am_state_release(&state);
204
205 return 0;
206}