reflog-walk.c 8.17 KB
Newer Older
1 2 3 4 5
#include "cache.h"
#include "commit.h"
#include "refs.h"
#include "diff.h"
#include "revision.h"
6
#include "string-list.h"
Junio C Hamano's avatar
Junio C Hamano committed
7
#include "reflog-walk.h"
8 9 10

struct complete_reflogs {
	char *ref;
11
	const char *short_ref;
12
	struct reflog_info {
13
		struct object_id ooid, noid;
14
		char *email;
15
		timestamp_t timestamp;
16 17 18 19 20 21
		int tz;
		char *message;
	} *items;
	int nr, alloc;
};

22
static int read_one_reflog(struct object_id *ooid, struct object_id *noid,
23
		const char *email, timestamp_t timestamp, int tz,
24 25 26 27 28
		const char *message, void *cb_data)
{
	struct complete_reflogs *array = cb_data;
	struct reflog_info *item;

29
	ALLOC_GROW(array->items, array->nr + 1, array->alloc);
30
	item = array->items + array->nr;
31 32
	oidcpy(&item->ooid, ooid);
	oidcpy(&item->noid, noid);
33 34 35 36 37 38 39 40
	item->email = xstrdup(email);
	item->timestamp = timestamp;
	item->tz = tz;
	item->message = xstrdup(message);
	array->nr++;
	return 0;
}

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
static void free_complete_reflog(struct complete_reflogs *array)
{
	int i;

	if (!array)
		return;

	for (i = 0; i < array->nr; i++) {
		free(array->items[i].email);
		free(array->items[i].message);
	}
	free(array->items);
	free(array->ref);
	free(array);
}

57 58 59
static struct complete_reflogs *read_complete_reflog(const char *ref)
{
	struct complete_reflogs *reflogs =
60
		xcalloc(1, sizeof(struct complete_reflogs));
61 62 63
	reflogs->ref = xstrdup(ref);
	for_each_reflog_ent(ref, read_one_reflog, reflogs);
	if (reflogs->nr == 0) {
64 65
		const char *name;
		void *name_to_free;
66
		name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
67
						     NULL, NULL);
68
		if (name) {
69
			for_each_reflog_ent(name, read_one_reflog, reflogs);
70
			free(name_to_free);
71
		}
72 73
	}
	if (reflogs->nr == 0) {
74
		char *refname = xstrfmt("refs/%s", ref);
75 76
		for_each_reflog_ent(refname, read_one_reflog, reflogs);
		if (reflogs->nr == 0) {
77 78
			free(refname);
			refname = xstrfmt("refs/heads/%s", ref);
79 80 81 82 83 84 85 86
			for_each_reflog_ent(refname, read_one_reflog, reflogs);
		}
		free(refname);
	}
	return reflogs;
}

static int get_reflog_recno_by_time(struct complete_reflogs *array,
87
	timestamp_t timestamp)
88 89
{
	int i;
90
	for (i = array->nr - 1; i >= 0; i--)
91 92 93 94 95 96
		if (timestamp >= array->items[i].timestamp)
			return i;
	return -1;
}

struct commit_reflog {
97 98 99 100 101 102
	int recno;
	enum selector_type {
		SELECTOR_NONE,
		SELECTOR_INDEX,
		SELECTOR_DATE
	} selector;
103 104 105 106
	struct complete_reflogs *reflogs;
};

struct reflog_walk_info {
107 108
	struct commit_reflog **logs;
	size_t nr, alloc;
109
	struct string_list complete_reflogs;
110 111 112
	struct commit_reflog *last_commit_reflog;
};

113
void init_reflog_walk(struct reflog_walk_info **info)
114
{
115
	*info = xcalloc(1, sizeof(struct reflog_walk_info));
116
	(*info)->complete_reflogs.strdup_strings = 1;
117 118
}

119
int add_reflog_for_walk(struct reflog_walk_info *info,
120 121
		struct commit *commit, const char *name)
{
122
	timestamp_t timestamp = 0;
123
	int recno = -1;
124
	struct string_list_item *item;
125 126 127
	struct complete_reflogs *reflogs;
	char *branch, *at = strchr(name, '@');
	struct commit_reflog *commit_reflog;
128
	enum selector_type selector = SELECTOR_NONE;
129

130 131 132
	if (commit->object.flags & UNINTERESTING)
		die ("Cannot walk reflogs for %s", name);

133 134 135 136 137 138 139 140
	branch = xstrdup(name);
	if (at && at[1] == '{') {
		char *ep;
		branch[at - name] = '\0';
		recno = strtoul(at + 2, &ep, 10);
		if (*ep != '}') {
			recno = -1;
			timestamp = approxidate(at + 2);
141
			selector = SELECTOR_DATE;
142
		}
143 144
		else
			selector = SELECTOR_INDEX;
145 146 147
	} else
		recno = 0;

148
	item = string_list_lookup(&info->complete_reflogs, branch);
149 150 151
	if (item)
		reflogs = item->util;
	else {
152 153
		if (*branch == '\0') {
			free(branch);
154
			branch = resolve_refdup("HEAD", 0, NULL, NULL);
155 156 157
			if (!branch)
				die ("No current branch");

158
		}
159
		reflogs = read_complete_reflog(branch);
160
		if (!reflogs || reflogs->nr == 0) {
161
			struct object_id oid;
162
			char *b;
163
			int ret = dwim_log(branch, strlen(branch),
164
					   &oid, &b);
165 166 167
			if (ret > 1)
				free(b);
			else if (ret == 1) {
168
				free_complete_reflog(reflogs);
169 170 171 172 173
				free(branch);
				branch = b;
				reflogs = read_complete_reflog(branch);
			}
		}
174
		if (!reflogs || reflogs->nr == 0) {
175
			free_complete_reflog(reflogs);
176
			free(branch);
177
			return -1;
178
		}
179
		string_list_insert(&info->complete_reflogs, branch)->util
180 181
			= reflogs;
	}
182
	free(branch);
183

184
	commit_reflog = xcalloc(1, sizeof(struct commit_reflog));
185 186 187 188
	if (recno < 0) {
		commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
		if (commit_reflog->recno < 0) {
			free(commit_reflog);
189
			return -1;
190 191 192
		}
	} else
		commit_reflog->recno = reflogs->nr - recno - 1;
193
	commit_reflog->selector = selector;
194 195
	commit_reflog->reflogs = reflogs;

196 197
	ALLOC_GROW(info->logs, info->nr + 1, info->alloc);
	info->logs[info->nr++] = commit_reflog;
198

199
	return 0;
200 201
}

202 203
void get_reflog_selector(struct strbuf *sb,
			 struct reflog_walk_info *reflog_info,
204
			 const struct date_mode *dmode, int force_date,
205
			 int shorten)
206 207 208
{
	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
	struct reflog_info *info;
209
	const char *printed_ref;
210 211 212 213

	if (!commit_reflog)
		return;

214 215 216 217 218 219 220 221 222 223
	if (shorten) {
		if (!commit_reflog->reflogs->short_ref)
			commit_reflog->reflogs->short_ref
				= shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0);
		printed_ref = commit_reflog->reflogs->short_ref;
	} else {
		printed_ref = commit_reflog->reflogs->ref;
	}

	strbuf_addf(sb, "%s@{", printed_ref);
224
	if (commit_reflog->selector == SELECTOR_DATE ||
225
	    (commit_reflog->selector == SELECTOR_NONE && force_date)) {
226 227 228 229 230 231 232 233 234 235
		info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
		strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode));
	} else {
		strbuf_addf(sb, "%d", commit_reflog->reflogs->nr
			    - 2 - commit_reflog->recno);
	}

	strbuf_addch(sb, '}');
}

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
void get_reflog_message(struct strbuf *sb,
			struct reflog_walk_info *reflog_info)
{
	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
	struct reflog_info *info;
	size_t len;

	if (!commit_reflog)
		return;

	info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
	len = strlen(info->message);
	if (len > 0)
		len--; /* strip away trailing newline */
	strbuf_add(sb, info->message, len);
}

253 254 255 256 257 258 259 260 261 262 263 264
const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
{
	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
	struct reflog_info *info;

	if (!commit_reflog)
		return NULL;

	info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
	return info->email;
}

265 266 267 268 269 270 271 272 273 274 275 276
timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info)
{
	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
	struct reflog_info *info;

	if (!commit_reflog)
		return 0;

	info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
	return info->timestamp;
}

277
void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
278
			 const struct date_mode *dmode, int force_date)
279
{
280 281
	if (reflog_info && reflog_info->last_commit_reflog) {
		struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
282
		struct reflog_info *info;
283
		struct strbuf selector = STRBUF_INIT;
284

285
		info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
286
		get_reflog_selector(&selector, reflog_info, dmode, force_date, 0);
287
		if (oneline) {
288
			printf("%s: %s", selector.buf, info->message);
289 290
		}
		else {
291 292
			printf("Reflog: %s (%s)\nReflog message: %s",
			       selector.buf, info->email, info->message);
293
		}
294 295

		strbuf_release(&selector);
296 297
	}
}
298 299 300

int reflog_walk_empty(struct reflog_walk_info *info)
{
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	return !info || !info->nr;
}

static struct commit *next_reflog_commit(struct commit_reflog *log)
{
	for (; log->recno >= 0; log->recno--) {
		struct reflog_info *entry = &log->reflogs->items[log->recno];
		struct object *obj = parse_object(&entry->noid);

		if (obj && obj->type == OBJ_COMMIT)
			return (struct commit *)obj;
	}
	return NULL;
}

static timestamp_t log_timestamp(struct commit_reflog *log)
{
	return log->reflogs->items[log->recno].timestamp;
}

struct commit *next_reflog_entry(struct reflog_walk_info *walk)
{
	struct commit_reflog *best = NULL;
	struct commit *best_commit = NULL;
	size_t i;

	for (i = 0; i < walk->nr; i++) {
		struct commit_reflog *log = walk->logs[i];
		struct commit *commit = next_reflog_commit(log);

		if (!commit)
			continue;

		if (!best || log_timestamp(log) > log_timestamp(best)) {
			best = log;
			best_commit = commit;
		}
	}

	if (best) {
		best->recno--;
		walk->last_commit_reflog = best;
		return best_commit;
	}

	return NULL;
347
}