| 1 | #define USE_THE_REPOSITORY_VARIABLE |
| 2 | |
| 3 | #include "git-compat-util.h" |
| 4 | #include "commit.h" |
| 5 | #include "refs.h" |
| 6 | #include "diff.h" |
| 7 | #include "repository.h" |
| 8 | #include "revision.h" |
| 9 | #include "string-list.h" |
| 10 | #include "reflog-walk.h" |
| 11 | |
| 12 | struct complete_reflogs { |
| 13 | char *ref; |
| 14 | char *short_ref; |
| 15 | struct reflog_info { |
| 16 | struct object_id ooid, noid; |
| 17 | char *email; |
| 18 | timestamp_t timestamp; |
| 19 | int tz; |
| 20 | char *message; |
| 21 | } *items; |
| 22 | int nr, alloc; |
| 23 | }; |
| 24 | |
| 25 | static int read_one_reflog(const char *refname UNUSED, |
| 26 | struct object_id *ooid, struct object_id *noid, |
| 27 | const char *email, timestamp_t timestamp, int tz, |
| 28 | const char *message, void *cb_data) |
| 29 | { |
| 30 | struct complete_reflogs *array = cb_data; |
| 31 | struct reflog_info *item; |
| 32 | |
| 33 | ALLOC_GROW(array->items, array->nr + 1, array->alloc); |
| 34 | item = array->items + array->nr; |
| 35 | oidcpy(&item->ooid, ooid); |
| 36 | oidcpy(&item->noid, noid); |
| 37 | item->email = xstrdup(email); |
| 38 | item->timestamp = timestamp; |
| 39 | item->tz = tz; |
| 40 | item->message = xstrdup(message); |
| 41 | array->nr++; |
| 42 | return 0; |
| 43 | } |
| 44 | |
| 45 | static void free_complete_reflog(struct complete_reflogs *array) |
| 46 | { |
| 47 | int i; |
| 48 | |
| 49 | if (!array) |
| 50 | return; |
| 51 | |
| 52 | for (i = 0; i < array->nr; i++) { |
| 53 | free(array->items[i].email); |
| 54 | free(array->items[i].message); |
| 55 | } |
| 56 | free(array->items); |
| 57 | free(array->ref); |
| 58 | free(array->short_ref); |
| 59 | free(array); |
| 60 | } |
| 61 | |
| 62 | static void complete_reflogs_clear(void *util, const char *str UNUSED) |
| 63 | { |
| 64 | struct complete_reflogs *array = util; |
| 65 | free_complete_reflog(array); |
| 66 | } |
| 67 | |
| 68 | static struct complete_reflogs *read_complete_reflog(const char *ref) |
| 69 | { |
| 70 | struct complete_reflogs *reflogs = |
| 71 | xcalloc(1, sizeof(struct complete_reflogs)); |
| 72 | reflogs->ref = xstrdup(ref); |
| 73 | refs_for_each_reflog_ent(get_main_ref_store(the_repository), ref, |
| 74 | read_one_reflog, reflogs); |
| 75 | if (reflogs->nr == 0) { |
| 76 | const char *name; |
| 77 | void *name_to_free; |
| 78 | name = name_to_free = refs_resolve_refdup(get_main_ref_store(the_repository), |
| 79 | ref, |
| 80 | RESOLVE_REF_READING, |
| 81 | NULL, NULL); |
| 82 | if (name) { |
| 83 | refs_for_each_reflog_ent(get_main_ref_store(the_repository), |
| 84 | name, read_one_reflog, |
| 85 | reflogs); |
| 86 | free(name_to_free); |
| 87 | } |
| 88 | } |
| 89 | if (reflogs->nr == 0) { |
| 90 | char *refname = xstrfmt("refs/%s", ref); |
| 91 | refs_for_each_reflog_ent(get_main_ref_store(the_repository), |
| 92 | refname, read_one_reflog, reflogs); |
| 93 | if (reflogs->nr == 0) { |
| 94 | free(refname); |
| 95 | refname = xstrfmt("refs/heads/%s", ref); |
| 96 | refs_for_each_reflog_ent(get_main_ref_store(the_repository), |
| 97 | refname, read_one_reflog, |
| 98 | reflogs); |
| 99 | } |
| 100 | free(refname); |
| 101 | } |
| 102 | return reflogs; |
| 103 | } |
| 104 | |
| 105 | static int get_reflog_recno_by_time(struct complete_reflogs *array, |
| 106 | timestamp_t timestamp) |
| 107 | { |
| 108 | int i; |
| 109 | for (i = array->nr - 1; i >= 0; i--) |
| 110 | if (timestamp >= array->items[i].timestamp) |
| 111 | return i; |
| 112 | return -1; |
| 113 | } |
| 114 | |
| 115 | struct commit_reflog { |
| 116 | int recno; |
| 117 | enum selector_type { |
| 118 | SELECTOR_NONE, |
| 119 | SELECTOR_INDEX, |
| 120 | SELECTOR_DATE |
| 121 | } selector; |
| 122 | struct complete_reflogs *reflogs; |
| 123 | }; |
| 124 | |
| 125 | struct reflog_walk_info { |
| 126 | struct commit_reflog **logs; |
| 127 | size_t nr, alloc; |
| 128 | struct string_list complete_reflogs; |
| 129 | struct commit_reflog *last_commit_reflog; |
| 130 | }; |
| 131 | |
| 132 | void init_reflog_walk(struct reflog_walk_info **info) |
| 133 | { |
| 134 | CALLOC_ARRAY(*info, 1); |
| 135 | (*info)->complete_reflogs.strdup_strings = 1; |
| 136 | } |
| 137 | |
| 138 | void reflog_walk_info_release(struct reflog_walk_info *info) |
| 139 | { |
| 140 | size_t i; |
| 141 | |
| 142 | if (!info) |
| 143 | return; |
| 144 | |
| 145 | for (i = 0; i < info->nr; i++) |
| 146 | free(info->logs[i]); |
| 147 | string_list_clear_func(&info->complete_reflogs, |
| 148 | complete_reflogs_clear); |
| 149 | free(info->logs); |
| 150 | free(info); |
| 151 | } |
| 152 | |
| 153 | int add_reflog_for_walk(struct reflog_walk_info *info, |
| 154 | struct commit *commit, const char *name) |
| 155 | { |
| 156 | timestamp_t timestamp = 0; |
| 157 | int recno = -1; |
| 158 | struct string_list_item *item; |
| 159 | struct complete_reflogs *reflogs; |
| 160 | char *branch; |
| 161 | const char *at = strchr(name, '@'); |
| 162 | struct commit_reflog *commit_reflog; |
| 163 | enum selector_type selector = SELECTOR_NONE; |
| 164 | |
| 165 | if (commit->object.flags & UNINTERESTING) |
| 166 | die("cannot walk reflogs for %s", name); |
| 167 | |
| 168 | branch = xstrdup(name); |
| 169 | if (at && at[1] == '{') { |
| 170 | char *ep; |
| 171 | branch[at - name] = '\0'; |
| 172 | recno = strtoul(at + 2, &ep, 10); |
| 173 | if (*ep != '}') { |
| 174 | recno = -1; |
| 175 | timestamp = approxidate(at + 2); |
| 176 | selector = SELECTOR_DATE; |
| 177 | } |
| 178 | else |
| 179 | selector = SELECTOR_INDEX; |
| 180 | } else |
| 181 | recno = 0; |
| 182 | |
| 183 | item = string_list_lookup(&info->complete_reflogs, branch); |
| 184 | if (item) |
| 185 | reflogs = item->util; |
| 186 | else { |
| 187 | if (*branch == '\0') { |
| 188 | free(branch); |
| 189 | branch = refs_resolve_refdup(get_main_ref_store(the_repository), |
| 190 | "HEAD", 0, NULL, NULL); |
| 191 | if (!branch) |
| 192 | die("no current branch"); |
| 193 | |
| 194 | } |
| 195 | reflogs = read_complete_reflog(branch); |
| 196 | if (!reflogs || reflogs->nr == 0) { |
| 197 | char *b; |
| 198 | int ret = repo_dwim_log(the_repository, branch, strlen(branch), |
| 199 | NULL, &b); |
| 200 | if (ret > 1) |
| 201 | free(b); |
| 202 | else if (ret == 1) { |
| 203 | free_complete_reflog(reflogs); |
| 204 | free(branch); |
| 205 | branch = b; |
| 206 | reflogs = read_complete_reflog(branch); |
| 207 | } |
| 208 | } |
| 209 | if (!reflogs || reflogs->nr == 0) { |
| 210 | free_complete_reflog(reflogs); |
| 211 | free(branch); |
| 212 | return -1; |
| 213 | } |
| 214 | string_list_insert(&info->complete_reflogs, branch)->util |
| 215 | = reflogs; |
| 216 | } |
| 217 | free(branch); |
| 218 | |
| 219 | CALLOC_ARRAY(commit_reflog, 1); |
| 220 | if (recno < 0) { |
| 221 | commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp); |
| 222 | if (commit_reflog->recno < 0) { |
| 223 | free(commit_reflog); |
| 224 | return -1; |
| 225 | } |
| 226 | } else |
| 227 | commit_reflog->recno = reflogs->nr - recno - 1; |
| 228 | commit_reflog->selector = selector; |
| 229 | commit_reflog->reflogs = reflogs; |
| 230 | |
| 231 | ALLOC_GROW(info->logs, info->nr + 1, info->alloc); |
| 232 | info->logs[info->nr++] = commit_reflog; |
| 233 | |
| 234 | return 0; |
| 235 | } |
| 236 | |
| 237 | void get_reflog_selector(struct strbuf *sb, |
| 238 | struct reflog_walk_info *reflog_info, |
| 239 | struct date_mode dmode, int force_date, |
| 240 | int shorten) |
| 241 | { |
| 242 | struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; |
| 243 | struct reflog_info *info; |
| 244 | const char *printed_ref; |
| 245 | |
| 246 | if (!commit_reflog) |
| 247 | return; |
| 248 | |
| 249 | if (shorten) { |
| 250 | if (!commit_reflog->reflogs->short_ref) |
| 251 | commit_reflog->reflogs->short_ref |
| 252 | = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), |
| 253 | commit_reflog->reflogs->ref, |
| 254 | 0); |
| 255 | printed_ref = commit_reflog->reflogs->short_ref; |
| 256 | } else { |
| 257 | printed_ref = commit_reflog->reflogs->ref; |
| 258 | } |
| 259 | |
| 260 | strbuf_addf(sb, "%s@{", printed_ref); |
| 261 | if (commit_reflog->selector == SELECTOR_DATE || |
| 262 | (commit_reflog->selector == SELECTOR_NONE && force_date)) { |
| 263 | info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; |
| 264 | strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); |
| 265 | } else { |
| 266 | strbuf_addf(sb, "%d", commit_reflog->reflogs->nr |
| 267 | - 2 - commit_reflog->recno); |
| 268 | } |
| 269 | |
| 270 | strbuf_addch(sb, '}'); |
| 271 | } |
| 272 | |
| 273 | void get_reflog_message(struct strbuf *sb, |
| 274 | struct reflog_walk_info *reflog_info) |
| 275 | { |
| 276 | struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; |
| 277 | struct reflog_info *info; |
| 278 | size_t len; |
| 279 | |
| 280 | if (!commit_reflog) |
| 281 | return; |
| 282 | |
| 283 | info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; |
| 284 | len = strlen(info->message); |
| 285 | if (len > 0) |
| 286 | len--; /* strip away trailing newline */ |
| 287 | strbuf_add(sb, info->message, len); |
| 288 | } |
| 289 | |
| 290 | const char *get_reflog_ident(struct reflog_walk_info *reflog_info) |
| 291 | { |
| 292 | struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; |
| 293 | struct reflog_info *info; |
| 294 | |
| 295 | if (!commit_reflog) |
| 296 | return NULL; |
| 297 | |
| 298 | info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; |
| 299 | return info->email; |
| 300 | } |
| 301 | |
| 302 | timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info) |
| 303 | { |
| 304 | struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; |
| 305 | struct reflog_info *info; |
| 306 | |
| 307 | if (!commit_reflog) |
| 308 | return 0; |
| 309 | |
| 310 | info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; |
| 311 | return info->timestamp; |
| 312 | } |
| 313 | |
| 314 | void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, |
| 315 | struct date_mode dmode, int force_date) |
| 316 | { |
| 317 | if (reflog_info && reflog_info->last_commit_reflog) { |
| 318 | struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; |
| 319 | struct reflog_info *info; |
| 320 | struct strbuf selector = STRBUF_INIT; |
| 321 | |
| 322 | info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; |
| 323 | get_reflog_selector(&selector, reflog_info, dmode, force_date, 0); |
| 324 | if (oneline) { |
| 325 | printf("%s: %s", selector.buf, info->message); |
| 326 | } |
| 327 | else { |
| 328 | printf("Reflog: %s (%s)\nReflog message: %s", |
| 329 | selector.buf, info->email, info->message); |
| 330 | } |
| 331 | |
| 332 | strbuf_release(&selector); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | int reflog_walk_empty(struct reflog_walk_info *info) |
| 337 | { |
| 338 | return !info || !info->nr; |
| 339 | } |
| 340 | |
| 341 | static struct commit *next_reflog_commit(struct commit_reflog *log) |
| 342 | { |
| 343 | for (; log->recno >= 0; log->recno--) { |
| 344 | struct reflog_info *entry = &log->reflogs->items[log->recno]; |
| 345 | struct object *obj = parse_object(the_repository, |
| 346 | &entry->noid); |
| 347 | |
| 348 | if (obj && obj->type == OBJ_COMMIT) |
| 349 | return (struct commit *)obj; |
| 350 | } |
| 351 | return NULL; |
| 352 | } |
| 353 | |
| 354 | static timestamp_t log_timestamp(struct commit_reflog *log) |
| 355 | { |
| 356 | return log->reflogs->items[log->recno].timestamp; |
| 357 | } |
| 358 | |
| 359 | struct commit *next_reflog_entry(struct reflog_walk_info *walk) |
| 360 | { |
| 361 | struct commit_reflog *best = NULL; |
| 362 | struct commit *best_commit = NULL; |
| 363 | size_t i; |
| 364 | |
| 365 | for (i = 0; i < walk->nr; i++) { |
| 366 | struct commit_reflog *log = walk->logs[i]; |
| 367 | struct commit *commit = next_reflog_commit(log); |
| 368 | |
| 369 | if (!commit) |
| 370 | continue; |
| 371 | |
| 372 | if (!best || log_timestamp(log) > log_timestamp(best)) { |
| 373 | best = log; |
| 374 | best_commit = commit; |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | if (best) { |
| 379 | best->recno--; |
| 380 | walk->last_commit_reflog = best; |
| 381 | return best_commit; |
| 382 | } |
| 383 | |
| 384 | return NULL; |
| 385 | } |