Merge branch 'jk/reflog-date' into next
[git] / reflog-walk.c
1 #include "cache.h"
2 #include "commit.h"
3 #include "refs.h"
4 #include "diff.h"
5 #include "revision.h"
6 #include "string-list.h"
7 #include "reflog-walk.h"
8
9 struct complete_reflogs {
10         char *ref;
11         struct reflog_info {
12                 unsigned char osha1[20], nsha1[20];
13                 char *email;
14                 unsigned long timestamp;
15                 int tz;
16                 char *message;
17         } *items;
18         int nr, alloc;
19 };
20
21 static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1,
22                 const char *email, unsigned long timestamp, int tz,
23                 const char *message, void *cb_data)
24 {
25         struct complete_reflogs *array = cb_data;
26         struct reflog_info *item;
27
28         if (array->nr >= array->alloc) {
29                 array->alloc = alloc_nr(array->nr + 1);
30                 array->items = xrealloc(array->items, array->alloc *
31                         sizeof(struct reflog_info));
32         }
33         item = array->items + array->nr;
34         memcpy(item->osha1, osha1, 20);
35         memcpy(item->nsha1, nsha1, 20);
36         item->email = xstrdup(email);
37         item->timestamp = timestamp;
38         item->tz = tz;
39         item->message = xstrdup(message);
40         array->nr++;
41         return 0;
42 }
43
44 static struct complete_reflogs *read_complete_reflog(const char *ref)
45 {
46         struct complete_reflogs *reflogs =
47                 xcalloc(sizeof(struct complete_reflogs), 1);
48         reflogs->ref = xstrdup(ref);
49         for_each_reflog_ent(ref, read_one_reflog, reflogs);
50         if (reflogs->nr == 0) {
51                 unsigned char sha1[20];
52                 const char *name = resolve_ref(ref, sha1, 1, NULL);
53                 if (name)
54                         for_each_reflog_ent(name, read_one_reflog, reflogs);
55         }
56         if (reflogs->nr == 0) {
57                 int len = strlen(ref);
58                 char *refname = xmalloc(len + 12);
59                 sprintf(refname, "refs/%s", ref);
60                 for_each_reflog_ent(refname, read_one_reflog, reflogs);
61                 if (reflogs->nr == 0) {
62                         sprintf(refname, "refs/heads/%s", ref);
63                         for_each_reflog_ent(refname, read_one_reflog, reflogs);
64                 }
65                 free(refname);
66         }
67         return reflogs;
68 }
69
70 static int get_reflog_recno_by_time(struct complete_reflogs *array,
71         unsigned long timestamp)
72 {
73         int i;
74         for (i = array->nr - 1; i >= 0; i--)
75                 if (timestamp >= array->items[i].timestamp)
76                         return i;
77         return -1;
78 }
79
80 struct commit_info_lifo {
81         struct commit_info {
82                 struct commit *commit;
83                 void *util;
84         } *items;
85         int nr, alloc;
86 };
87
88 static struct commit_info *get_commit_info(struct commit *commit,
89                 struct commit_info_lifo *lifo, int pop)
90 {
91         int i;
92         for (i = 0; i < lifo->nr; i++)
93                 if (lifo->items[i].commit == commit) {
94                         struct commit_info *result = &lifo->items[i];
95                         if (pop) {
96                                 if (i + 1 < lifo->nr)
97                                         memmove(lifo->items + i,
98                                                 lifo->items + i + 1,
99                                                 (lifo->nr - i) *
100                                                 sizeof(struct commit_info));
101                                 lifo->nr--;
102                         }
103                         return result;
104                 }
105         return NULL;
106 }
107
108 static void add_commit_info(struct commit *commit, void *util,
109                 struct commit_info_lifo *lifo)
110 {
111         struct commit_info *info;
112         if (lifo->nr >= lifo->alloc) {
113                 lifo->alloc = alloc_nr(lifo->nr + 1);
114                 lifo->items = xrealloc(lifo->items,
115                         lifo->alloc * sizeof(struct commit_info));
116         }
117         info = lifo->items + lifo->nr;
118         info->commit = commit;
119         info->util = util;
120         lifo->nr++;
121 }
122
123 struct commit_reflog {
124         int flag, recno;
125         struct complete_reflogs *reflogs;
126 };
127
128 struct reflog_walk_info {
129         struct commit_info_lifo reflogs;
130         struct string_list complete_reflogs;
131         struct commit_reflog *last_commit_reflog;
132 };
133
134 void init_reflog_walk(struct reflog_walk_info** info)
135 {
136         *info = xcalloc(sizeof(struct reflog_walk_info), 1);
137 }
138
139 int add_reflog_for_walk(struct reflog_walk_info *info,
140                 struct commit *commit, const char *name)
141 {
142         unsigned long timestamp = 0;
143         int recno = -1;
144         struct string_list_item *item;
145         struct complete_reflogs *reflogs;
146         char *branch, *at = strchr(name, '@');
147         struct commit_reflog *commit_reflog;
148
149         if (commit->object.flags & UNINTERESTING)
150                 die ("Cannot walk reflogs for %s", name);
151
152         branch = xstrdup(name);
153         if (at && at[1] == '{') {
154                 char *ep;
155                 branch[at - name] = '\0';
156                 recno = strtoul(at + 2, &ep, 10);
157                 if (*ep != '}') {
158                         recno = -1;
159                         timestamp = approxidate(at + 2);
160                 }
161         } else
162                 recno = 0;
163
164         item = string_list_lookup(branch, &info->complete_reflogs);
165         if (item)
166                 reflogs = item->util;
167         else {
168                 if (*branch == '\0') {
169                         unsigned char sha1[20];
170                         const char *head = resolve_ref("HEAD", sha1, 0, NULL);
171                         if (!head)
172                                 die ("No current branch");
173                         free(branch);
174                         branch = xstrdup(head);
175                 }
176                 reflogs = read_complete_reflog(branch);
177                 if (!reflogs || reflogs->nr == 0) {
178                         unsigned char sha1[20];
179                         char *b;
180                         if (dwim_log(branch, strlen(branch), sha1, &b) == 1) {
181                                 if (reflogs) {
182                                         free(reflogs->ref);
183                                         free(reflogs);
184                                 }
185                                 free(branch);
186                                 branch = b;
187                                 reflogs = read_complete_reflog(branch);
188                         }
189                 }
190                 if (!reflogs || reflogs->nr == 0)
191                         return -1;
192                 string_list_insert(branch, &info->complete_reflogs)->util
193                         = reflogs;
194         }
195
196         commit_reflog = xcalloc(sizeof(struct commit_reflog), 1);
197         if (recno < 0) {
198                 commit_reflog->flag = 1;
199                 commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
200                 if (commit_reflog->recno < 0) {
201                         free(branch);
202                         free(commit_reflog);
203                         return -1;
204                 }
205         } else
206                 commit_reflog->recno = reflogs->nr - recno - 1;
207         commit_reflog->reflogs = reflogs;
208
209         add_commit_info(commit, commit_reflog, &info->reflogs);
210         return 0;
211 }
212
213 void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
214 {
215         struct commit_info *commit_info =
216                 get_commit_info(commit, &info->reflogs, 0);
217         struct commit_reflog *commit_reflog;
218         struct reflog_info *reflog;
219
220         info->last_commit_reflog = NULL;
221         if (!commit_info)
222                 return;
223
224         commit_reflog = commit_info->util;
225         if (commit_reflog->recno < 0) {
226                 commit->parents = NULL;
227                 return;
228         }
229
230         reflog = &commit_reflog->reflogs->items[commit_reflog->recno];
231         info->last_commit_reflog = commit_reflog;
232         commit_reflog->recno--;
233         commit_info->commit = (struct commit *)parse_object(reflog->osha1);
234         if (!commit_info->commit) {
235                 commit->parents = NULL;
236                 return;
237         }
238
239         commit->parents = xcalloc(sizeof(struct commit_list), 1);
240         commit->parents->item = commit_info->commit;
241         commit->object.flags &= ~(ADDED | SEEN | SHOWN);
242 }
243
244 void show_reflog_message(struct reflog_walk_info *info, int oneline,
245         enum date_mode dmode)
246 {
247         if (info && info->last_commit_reflog) {
248                 struct commit_reflog *commit_reflog = info->last_commit_reflog;
249                 struct reflog_info *info;
250
251                 info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
252                 if (oneline) {
253                         printf("%s@{", commit_reflog->reflogs->ref);
254                         if (commit_reflog->flag || dmode)
255                                 printf("%s", show_date(info->timestamp,
256                                                        info->tz,
257                                                        dmode));
258                         else
259                                 printf("%d", commit_reflog->reflogs->nr
260                                        - 2 - commit_reflog->recno);
261                         printf("}: %s", info->message);
262                 }
263                 else {
264                         printf("Reflog: %s@{", commit_reflog->reflogs->ref);
265                         if (commit_reflog->flag || dmode)
266                                 printf("%s", show_date(info->timestamp,
267                                                         info->tz,
268                                                         dmode));
269                         else
270                                 printf("%d", commit_reflog->reflogs->nr
271                                        - 2 - commit_reflog->recno);
272                         printf("} (%s)\nReflog message: %s",
273                                info->email, info->message);
274                 }
275         }
276 }