reflog-walk.c 8.52 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 15 16 17 18 19 20 21 22 23 24 25 26 27 28
		char *email;
		unsigned long timestamp;
		int tz;
		char *message;
	} *items;
	int nr, alloc;
};

static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1,
		const char *email, unsigned long timestamp, int tz,
		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
	hashcpy(item->ooid.hash, osha1);
	hashcpy(item->noid.hash, nsha1);
33 34 35 36 37 38 39 40 41 42 43
	item->email = xstrdup(email);
	item->timestamp = timestamp;
	item->tz = tz;
	item->message = xstrdup(message);
	array->nr++;
	return 0;
}

static struct complete_reflogs *read_complete_reflog(const char *ref)
{
	struct complete_reflogs *reflogs =
44
		xcalloc(1, sizeof(struct complete_reflogs));
45 46 47
	reflogs->ref = xstrdup(ref);
	for_each_reflog_ent(ref, read_one_reflog, reflogs);
	if (reflogs->nr == 0) {
48
		struct object_id oid;
49 50
		const char *name;
		void *name_to_free;
51
		name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
52
						     oid.hash, NULL);
53
		if (name) {
54
			for_each_reflog_ent(name, read_one_reflog, reflogs);
55
			free(name_to_free);
56
		}
57 58
	}
	if (reflogs->nr == 0) {
59
		char *refname = xstrfmt("refs/%s", ref);
60 61
		for_each_reflog_ent(refname, read_one_reflog, reflogs);
		if (reflogs->nr == 0) {
62 63
			free(refname);
			refname = xstrfmt("refs/heads/%s", ref);
64 65 66 67 68 69 70 71 72 73 74
			for_each_reflog_ent(refname, read_one_reflog, reflogs);
		}
		free(refname);
	}
	return reflogs;
}

static int get_reflog_recno_by_time(struct complete_reflogs *array,
	unsigned long timestamp)
{
	int i;
75
	for (i = array->nr - 1; i >= 0; i--)
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
		if (timestamp >= array->items[i].timestamp)
			return i;
	return -1;
}

struct commit_info_lifo {
	struct commit_info {
		struct commit *commit;
		void *util;
	} *items;
	int nr, alloc;
};

static struct commit_info *get_commit_info(struct commit *commit,
		struct commit_info_lifo *lifo, int pop)
{
	int i;
	for (i = 0; i < lifo->nr; i++)
		if (lifo->items[i].commit == commit) {
			struct commit_info *result = &lifo->items[i];
			if (pop) {
				if (i + 1 < lifo->nr)
					memmove(lifo->items + i,
						lifo->items + i + 1,
						(lifo->nr - i) *
						sizeof(struct commit_info));
				lifo->nr--;
			}
			return result;
		}
	return NULL;
}

static void add_commit_info(struct commit *commit, void *util,
		struct commit_info_lifo *lifo)
{
	struct commit_info *info;
113
	ALLOC_GROW(lifo->items, lifo->nr + 1, lifo->alloc);
114 115 116 117 118 119 120
	info = lifo->items + lifo->nr;
	info->commit = commit;
	info->util = util;
	lifo->nr++;
}

struct commit_reflog {
121 122 123 124 125 126
	int recno;
	enum selector_type {
		SELECTOR_NONE,
		SELECTOR_INDEX,
		SELECTOR_DATE
	} selector;
127 128 129 130 131
	struct complete_reflogs *reflogs;
};

struct reflog_walk_info {
	struct commit_info_lifo reflogs;
132
	struct string_list complete_reflogs;
133 134 135
	struct commit_reflog *last_commit_reflog;
};

136
void init_reflog_walk(struct reflog_walk_info **info)
137
{
138
	*info = xcalloc(1, sizeof(struct reflog_walk_info));
139 140
}

141
int add_reflog_for_walk(struct reflog_walk_info *info,
142 143 144 145
		struct commit *commit, const char *name)
{
	unsigned long timestamp = 0;
	int recno = -1;
146
	struct string_list_item *item;
147 148 149
	struct complete_reflogs *reflogs;
	char *branch, *at = strchr(name, '@');
	struct commit_reflog *commit_reflog;
150
	enum selector_type selector = SELECTOR_NONE;
151

152 153 154
	if (commit->object.flags & UNINTERESTING)
		die ("Cannot walk reflogs for %s", name);

155 156 157 158 159 160 161 162
	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);
163
			selector = SELECTOR_DATE;
164
		}
165 166
		else
			selector = SELECTOR_INDEX;
167 168 169
	} else
		recno = 0;

170
	item = string_list_lookup(&info->complete_reflogs, branch);
171 172 173
	if (item)
		reflogs = item->util;
	else {
174
		if (*branch == '\0') {
175
			struct object_id oid;
176
			free(branch);
177
			branch = resolve_refdup("HEAD", 0, oid.hash, NULL);
178 179 180
			if (!branch)
				die ("No current branch");

181
		}
182
		reflogs = read_complete_reflog(branch);
183
		if (!reflogs || reflogs->nr == 0) {
184
			struct object_id oid;
185
			char *b;
186
			if (dwim_log(branch, strlen(branch), oid.hash, &b) == 1) {
187 188 189 190 191 192 193 194 195
				if (reflogs) {
					free(reflogs->ref);
					free(reflogs);
				}
				free(branch);
				branch = b;
				reflogs = read_complete_reflog(branch);
			}
		}
196
		if (!reflogs || reflogs->nr == 0)
197
			return -1;
198
		string_list_insert(&info->complete_reflogs, branch)->util
199 200 201
			= reflogs;
	}

202
	commit_reflog = xcalloc(1, sizeof(struct commit_reflog));
203 204 205 206 207
	if (recno < 0) {
		commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
		if (commit_reflog->recno < 0) {
			free(branch);
			free(commit_reflog);
208
			return -1;
209 210 211
		}
	} else
		commit_reflog->recno = reflogs->nr - recno - 1;
212
	commit_reflog->selector = selector;
213 214 215
	commit_reflog->reflogs = reflogs;

	add_commit_info(commit, commit_reflog, &info->reflogs);
216
	return 0;
217 218 219 220 221 222 223
}

void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
{
	struct commit_info *commit_info =
		get_commit_info(commit, &info->reflogs, 0);
	struct commit_reflog *commit_reflog;
224
	struct object *logobj;
225 226 227 228 229 230 231 232 233 234 235 236
	struct reflog_info *reflog;

	info->last_commit_reflog = NULL;
	if (!commit_info)
		return;

	commit_reflog = commit_info->util;
	if (commit_reflog->recno < 0) {
		commit->parents = NULL;
		return;
	}
	info->last_commit_reflog = commit_reflog;
237 238 239 240

	do {
		reflog = &commit_reflog->reflogs->items[commit_reflog->recno];
		commit_reflog->recno--;
241
		logobj = parse_object(reflog->ooid.hash);
242 243
	} while (commit_reflog->recno && (logobj && logobj->type != OBJ_COMMIT));

244
	if (!logobj && commit_reflog->recno >= 0 && is_null_sha1(reflog->ooid.hash)) {
245 246
		/* a root commit, but there are still more entries to show */
		reflog = &commit_reflog->reflogs->items[commit_reflog->recno];
247
		logobj = parse_object(reflog->noid.hash);
248 249
	}

250 251
	if (!logobj || logobj->type != OBJ_COMMIT) {
		commit_info->commit = NULL;
252 253 254
		commit->parents = NULL;
		return;
	}
255
	commit_info->commit = (struct commit *)logobj;
256

257
	commit->parents = xcalloc(1, sizeof(struct commit_list));
258 259 260
	commit->parents->item = commit_info->commit;
}

261 262
void get_reflog_selector(struct strbuf *sb,
			 struct reflog_walk_info *reflog_info,
263
			 const struct date_mode *dmode, int force_date,
264
			 int shorten)
265 266 267
{
	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
	struct reflog_info *info;
268
	const char *printed_ref;
269 270 271 272

	if (!commit_reflog)
		return;

273 274 275 276 277 278 279 280 281 282
	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, "%[email protected]{", printed_ref);
283
	if (commit_reflog->selector == SELECTOR_DATE ||
284
	    (commit_reflog->selector == SELECTOR_NONE && force_date)) {
285 286 287 288 289 290 291 292 293 294
		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, '}');
}

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
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);
}

312 313 314 315 316 317 318 319 320 321 322 323
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;
}

324
void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
325
			 const struct date_mode *dmode, int force_date)
326
{
327 328
	if (reflog_info && reflog_info->last_commit_reflog) {
		struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
329
		struct reflog_info *info;
330
		struct strbuf selector = STRBUF_INIT;
331

332
		info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
333
		get_reflog_selector(&selector, reflog_info, dmode, force_date, 0);
334
		if (oneline) {
335
			printf("%s: %s", selector.buf, info->message);
336 337
		}
		else {
338 339
			printf("Reflog: %s (%s)\nReflog message: %s",
			       selector.buf, info->email, info->message);
340
		}
341 342

		strbuf_release(&selector);
343 344
	}
}