builtin-am: implement patch queue mechanism
[alt-git.git] / builtin / am.c
blobac172c484101c7834bcf6ec75ef26aad60bf6310
1 /*
2 * Builtin "git am"
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"
12 struct am_state {
13 /* state directory path */
14 char *dir;
16 /* current and last patch numbers, 1-indexed */
17 int cur;
18 int last;
21 /**
22 * Initializes am_state with the default values. The state directory is set to
23 * dir.
25 static void am_state_init(struct am_state *state, const char *dir)
27 memset(state, 0, sizeof(*state));
29 assert(dir);
30 state->dir = xstrdup(dir);
33 /**
34 * Releases memory allocated by an am_state.
36 static void am_state_release(struct am_state *state)
38 free(state->dir);
41 /**
42 * Returns path relative to the am_state directory.
44 static inline const char *am_path(const struct am_state *state, const char *path)
46 return mkpath("%s/%s", state->dir, path);
49 /**
50 * Returns 1 if there is an am session in progress, 0 otherwise.
52 static int am_in_progress(const struct am_state *state)
54 struct stat st;
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;
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.
70 static int read_state_file(struct strbuf *sb, const struct am_state *state,
71 const char *file, int trim)
73 strbuf_reset(sb);
75 if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
76 if (trim)
77 strbuf_trim(sb);
79 return sb->len;
82 if (errno == ENOENT)
83 return -1;
85 die_errno(_("could not read '%s'"), am_path(state, file));
88 /**
89 * Loads state from disk.
91 static void am_load(struct am_state *state)
93 struct strbuf sb = STRBUF_INIT;
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);
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);
103 strbuf_release(&sb);
107 * Removes the am_state directory, forcefully terminating the current am
108 * session.
110 static void am_destroy(const struct am_state *state)
112 struct strbuf sb = STRBUF_INIT;
114 strbuf_addstr(&sb, state->dir);
115 remove_dir_recursively(&sb, 0);
116 strbuf_release(&sb);
120 * Setup a new am session for applying patches
122 static void am_setup(struct am_state *state)
124 if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
125 die_errno(_("failed to create directory '%s'"), state->dir);
128 * NOTE: Since the "next" and "last" files determine if an am_state
129 * session is in progress, they should be written last.
132 write_file(am_path(state, "next"), 1, "%d", state->cur);
134 write_file(am_path(state, "last"), 1, "%d", state->last);
138 * Increments the patch pointer, and cleans am_state for the application of the
139 * next patch.
141 static void am_next(struct am_state *state)
143 state->cur++;
144 write_file(am_path(state, "next"), 1, "%d", state->cur);
148 * Applies all queued mail.
150 static void am_run(struct am_state *state)
152 while (state->cur <= state->last) {
154 /* NEEDSWORK: Patch application not implemented yet */
156 am_next(state);
159 am_destroy(state);
162 int cmd_am(int argc, const char **argv, const char *prefix)
164 struct am_state state;
166 const char * const usage[] = {
167 N_("git am [options] [(<mbox>|<Maildir>)...]"),
168 NULL
171 struct option options[] = {
172 OPT_END()
176 * NEEDSWORK: Once all the features of git-am.sh have been
177 * re-implemented in builtin/am.c, this preamble can be removed.
179 if (!getenv("_GIT_USE_BUILTIN_AM")) {
180 const char *path = mkpath("%s/git-am", git_exec_path());
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();
190 git_config(git_default_config, NULL);
192 am_state_init(&state, git_path("rebase-apply"));
194 argc = parse_options(argc, argv, prefix, options, usage, 0);
196 if (am_in_progress(&state))
197 am_load(&state);
198 else
199 am_setup(&state);
201 am_run(&state);
203 am_state_release(&state);
205 return 0;