]> Pileus Git - ~andy/git/blob - reflog-walk.c
Teach the revision walker to walk by reflogs with --walk-reflogs
[~andy/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 "path-list.h"
7
8 struct complete_reflogs {
9         char *ref;
10         struct reflog_info {
11                 unsigned char osha1[20], nsha1[20];
12                 char *email;
13                 unsigned long timestamp;
14                 int tz;
15                 char *message;
16         } *items;
17         int nr, alloc;
18 };
19
20 static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1,
21                 const char *email, unsigned long timestamp, int tz,
22                 const char *message, void *cb_data)
23 {
24         struct complete_reflogs *array = cb_data;
25         struct reflog_info *item;
26
27         if (array->nr >= array->alloc) {
28                 array->alloc = alloc_nr(array->nr + 1);
29                 array->items = xrealloc(array->items, array->alloc *
30                         sizeof(struct reflog_info));
31         }
32         item = array->items + array->nr;
33         memcpy(item->osha1, osha1, 20);
34         memcpy(item->nsha1, nsha1, 20);
35         item->email = xstrdup(email);
36         item->timestamp = timestamp;
37         item->tz = tz;
38         item->message = xstrdup(message);
39         array->nr++;
40         return 0;
41 }
42
43 static struct complete_reflogs *read_complete_reflog(const char *ref)
44 {
45         struct complete_reflogs *reflogs =
46                 xcalloc(sizeof(struct complete_reflogs), 1);
47         reflogs->ref = xstrdup(ref);
48         for_each_reflog_ent(ref, read_one_reflog, reflogs);
49         if (reflogs->nr == 0) {
50                 unsigned char sha1[20];
51                 const char *name = resolve_ref(ref, sha1, 1, NULL);
52                 if (name)
53                         for_each_reflog_ent(name, read_one_reflog, reflogs);
54         }
55         if (reflogs->nr == 0) {
56                 int len = strlen(ref);
57                 char *refname = xmalloc(len + 12);
58                 sprintf(refname, "refs/%s", ref);
59                 for_each_reflog_ent(refname, read_one_reflog, reflogs);
60                 if (reflogs->nr == 0) {
61                         sprintf(refname, "refs/heads/%s", ref);
62                         for_each_reflog_ent(refname, read_one_reflog, reflogs);
63                 }
64                 free(refname);
65         }
66         return reflogs;
67 }
68
69 static int get_reflog_recno_by_time(struct complete_reflogs *array,
70         unsigned long timestamp)
71 {
72         int i;
73         for (i = array->nr - 1; i >= 0; i++)
74                 if (timestamp >= array->items[i].timestamp)
75                         return i;
76         return -1;
77 }
78
79 struct commit_info_lifo {
80         struct commit_info {
81                 struct commit *commit;
82                 void *util;
83         } *items;
84         int nr, alloc;
85 };
86
87 static struct commit_info *get_commit_info(struct commit *commit,
88                 struct commit_info_lifo *lifo, int pop)
89 {
90         int i;
91         for (i = 0; i < lifo->nr; i++)
92                 if (lifo->items[i].commit == commit) {
93                         struct commit_info *result = &lifo->items[i];
94                         if (pop) {
95                                 if (i + 1 < lifo->nr)
96                                         memmove(lifo->items + i,
97                                                 lifo->items + i + 1,
98                                                 (lifo->nr - i) *
99                                                 sizeof(struct commit_info));
100                                 lifo->nr--;
101                         }
102                         return result;
103                 }
104         return NULL;
105 }
106
107 static void add_commit_info(struct commit *commit, void *util,
108                 struct commit_info_lifo *lifo)
109 {
110         struct commit_info *info;
111         if (lifo->nr >= lifo->alloc) {
112                 lifo->alloc = alloc_nr(lifo->nr + 1);
113                 lifo->items = xrealloc(lifo->items,
114                         lifo->alloc * sizeof(struct commit_info));
115         }
116         info = lifo->items + lifo->nr;
117         info->commit = commit;
118         info->util = util;
119         lifo->nr++;
120 }
121
122 struct commit_reflog {
123         int flag, recno;
124         struct complete_reflogs *reflogs;
125 };
126
127 struct reflog_walk_info {
128         struct commit_info_lifo reflogs;
129         struct path_list complete_reflogs;
130         struct commit_reflog *last_commit_reflog;
131 };
132
133 void init_reflog_walk(struct reflog_walk_info** info)
134 {
135         *info = xcalloc(sizeof(struct reflog_walk_info), 1);
136 }
137
138 void add_reflog_for_walk(struct reflog_walk_info *info,
139                 struct commit *commit, const char *name)
140 {
141         unsigned long timestamp = 0;
142         int recno = -1;
143         struct path_list_item *item;
144         struct complete_reflogs *reflogs;
145         char *branch, *at = strchr(name, '@');
146         struct commit_reflog *commit_reflog;
147
148         branch = xstrdup(name);
149         if (at && at[1] == '{') {
150                 char *ep;
151                 branch[at - name] = '\0';
152                 recno = strtoul(at + 2, &ep, 10);
153                 if (*ep != '}') {
154                         recno = -1;
155                         timestamp = approxidate(at + 2);
156                 }
157         } else
158                 recno = 0;
159
160         item = path_list_lookup(branch, &info->complete_reflogs);
161         if (item)
162                 reflogs = item->util;
163         else {
164                 reflogs = read_complete_reflog(branch);
165                 if (!reflogs || reflogs->nr == 0)
166                         die("No reflogs found for '%s'", branch);
167                 path_list_insert(branch, &info->complete_reflogs)->util
168                         = reflogs;
169         }
170
171         commit_reflog = xcalloc(sizeof(struct commit_reflog), 1);
172         if (recno < 0) {
173                 commit_reflog->flag = 1;
174                 commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
175                 if (commit_reflog->recno < 0) {
176                         free(branch);
177                         free(commit_reflog);
178                         return;
179                 }
180         } else
181                 commit_reflog->recno = reflogs->nr - recno - 1;
182         commit_reflog->reflogs = reflogs;
183
184         add_commit_info(commit, commit_reflog, &info->reflogs);
185 }
186
187 void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
188 {
189         struct commit_info *commit_info =
190                 get_commit_info(commit, &info->reflogs, 0);
191         struct commit_reflog *commit_reflog;
192         struct reflog_info *reflog;
193
194         info->last_commit_reflog = NULL;
195         if (!commit_info)
196                 return;
197
198         commit_reflog = commit_info->util;
199         if (commit_reflog->recno < 0) {
200                 commit->parents = NULL;
201                 return;
202         }
203
204         reflog = &commit_reflog->reflogs->items[commit_reflog->recno];
205         info->last_commit_reflog = commit_reflog;
206         commit_reflog->recno--;
207         commit_info->commit = (struct commit *)parse_object(reflog->osha1);
208         if (!commit_info->commit) {
209                 commit->parents = NULL;
210                 return;
211         }
212
213         commit->parents = xcalloc(sizeof(struct commit_list), 1);
214         commit->parents->item = commit_info->commit;
215         commit->object.flags &= ~(ADDED | SEEN | SHOWN);
216 }
217
218 void show_reflog_message(struct reflog_walk_info* info)
219 {
220         if (info && info->last_commit_reflog) {
221                 struct commit_reflog *commit_reflog = info->last_commit_reflog;
222                 struct reflog_info *info;
223
224                 printf("Reflog: %s@{", commit_reflog->reflogs->ref);
225                 info = &commit_reflog->reflogs->items[commit_reflog->recno + 1];
226                 if (commit_reflog->flag)
227                         printf("%s", show_rfc2822_date(info->timestamp,
228                                                 info->tz));
229                 else
230                         printf("%d", commit_reflog->reflogs->nr
231                                         - 2 - commit_reflog->recno);
232                 printf("} (%s)\nReflog message: %s",
233                         info->email, info->message);
234         }
235 }