reflog-walk.c 8.23 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
		struct object_id oid;
65 66
		const char *name;
		void *name_to_free;
67
		name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
68
						     oid.hash, NULL);
69
		if (name) {
70
			for_each_reflog_ent(name, read_one_reflog, reflogs);
71
			free(name_to_free);
72
		}
73 74
	}
	if (reflogs->nr == 0) {
75
		char *refname = xstrfmt("refs/%s", ref);
76 77
		for_each_reflog_ent(refname, read_one_reflog, reflogs);
		if (reflogs->nr == 0) {
78 79
			free(refname);
			refname = xstrfmt("refs/heads/%s", ref);
80 81 82 83 84 85 86 87
			for_each_reflog_ent(refname, read_one_reflog, reflogs);
		}
		free(refname);
	}
	return reflogs;
}

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

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

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

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

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

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

134 135 136 137 138 139 140 141
	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);
142
			selector = SELECTOR_DATE;
143
		}
144 145
		else
			selector = SELECTOR_INDEX;
146 147 148
	} else
		recno = 0;

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

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

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

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

201
	return 0;
202 203
}

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

	if (!commit_reflog)
		return;

216 217 218 219 220 221 222 223 224 225
	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);
226
	if (commit_reflog->selector == SELECTOR_DATE ||
227
	    (commit_reflog->selector == SELECTOR_NONE && force_date)) {
228 229 230 231 232 233 234 235 236 237
		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, '}');
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
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);
}

255 256 257 258 259 260 261 262 263 264 265 266
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;
}

267 268 269 270 271 272 273 274 275 276 277 278
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;
}

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

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

		strbuf_release(&selector);
298 299
	}
}
300 301 302

int reflog_walk_empty(struct reflog_walk_info *info)
{
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 347 348
	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;
349
}