builtin-am: implement patch queue mechanism
[git/git.git] / builtin / am.c
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
12 struct 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 */
25 static 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 */
36 static 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 */
44 static 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 */
52 static 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 */
70 static 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 */
91 static 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 */
110 static 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 */
122 static 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 */
141 static 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 */
150 static 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
162 int 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 }