1#include <err.h>
2#include <libgen.h>
3#include <libgen.h>
4#include <limits.h>
5#include <stdbool.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include <git2.h>
14#include <sys/types.h>
15
16static bool amend_single = false;
17static bool verbose = false;
18static git_repository *repo;
19
20static void
21giterr(const char *fn)
22{
23 const git_error *err;
24
25 err = git_error_last();
26 errx(EXIT_FAILURE, "%s failed: %s", fn, err->message);
27}
28
29static void
30usage(char *prog)
31{
32 const char *usage = "[-a] [-v] revspec";
33
34 fprintf(stderr, "USAGE: %s %s\n", basename(prog), usage);
35 exit(EXIT_FAILURE);
36}
37
38static void
39randtime(git_time *dest, const git_time *orig)
40{
41 time_t t;
42 struct tm *tm;
43 uint8_t rbytes[3];
44
45 if (getentropy(&rbytes, sizeof(rbytes)) == -1)
46 err(EXIT_FAILURE, "getentropy failed");
47
48 /* https://github.com/libgit2/libgit2/blob/79d0e0c10ffec81152b5b1eaeb47b59adf1d4bcc/examples/log.c#L321 */
49 t = (time_t)orig->time + (orig->offset * 60);
50 if (!(tm = gmtime(&t)))
51 errx(EXIT_FAILURE, "gmtime failed");
52
53 /* randomize time but retain current date */
54 tm->tm_hour = rbytes[0] % 24;
55 tm->tm_min = rbytes[1] % 60;
56 tm->tm_sec = rbytes[2] % 60;
57
58 /* convert broken-down time to UTC epoch and use the
59 * offset given in orig to convert it to a local time */
60 memcpy(dest, orig, sizeof(git_time));
61 if ((dest->time = timegm(tm)) == (time_t)-1)
62 err(EXIT_FAILURE, "timegm failed");
63 dest->time -= orig->offset * 60; /* convert back to local time */
64}
65
66static git_signature *
67redate(git_commit *commit)
68{
69 char buf[GIT_OID_HEXSZ + 1];
70 git_signature *newauth;
71 const git_signature *origauth;
72
73 if (verbose) {
74 git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
75 printf("Updating time of commit %s\n", buf);
76 }
77
78 origauth = git_commit_author(commit);
79 if (git_signature_dup(&newauth, origauth))
80 err(EXIT_FAILURE, "git_signature_dup failed");
81 randtime(&newauth->when, &origauth->when);
82
83 return newauth;
84}
85
86static void
87rebase(const char *revspec)
88{
89 git_oid oid;
90 git_rebase *rebase;
91 git_annotated_commit *upstream;
92 git_rebase_operation *op;
93
94 if (git_annotated_commit_from_revspec(&upstream, repo, revspec))
95 giterr("git_annotated_commit_from_revspec");
96 if (git_rebase_init(&rebase, repo, NULL, upstream, NULL, NULL))
97 giterr("git_rebase_init");
98
99 while (!git_rebase_next(&op, rebase)) {
100 git_commit *commit;
101 git_signature *author;
102
103 if (git_commit_lookup(&commit, repo, &op->id))
104 goto err;
105 author = redate(commit);
106 if (git_rebase_commit(&oid, rebase, author, author, NULL, NULL))
107 goto err;
108
109 git_commit_free(commit);
110 git_signature_free(author);
111 }
112
113 if (git_rebase_finish(rebase, NULL))
114 giterr("git_rebase_finish");
115 return;
116
117err:
118 warnx("rebase failed: %s", git_error_last());
119 if (git_rebase_abort(rebase))
120 giterr("git_rebase_abort");
121}
122
123static void
124amend(void)
125{
126 git_oid oidcpy;
127 git_commit *commit;
128 const git_oid *oid;
129 git_reference *head;
130 const char *refname;
131 git_signature *author;
132 git_annotated_commit *annotated;
133
134 if (git_repository_head(&head, repo))
135 giterr("git_repository_head");
136 if (git_annotated_commit_from_ref(&annotated, repo, head))
137 giterr("git_annotated_commit_from_ref");
138
139 oid = git_annotated_commit_id(annotated);
140 if (git_oid_cpy(&oidcpy, oid))
141 giterr("git_oid_cpy");
142
143 if (git_commit_lookup(&commit, repo, oid))
144 giterr("git_commit_lookup");
145 refname = git_reference_name(head);
146
147 author = redate(commit);
148 if (git_commit_amend(&oidcpy, commit, refname, author, author, NULL, NULL, NULL))
149 giterr("git_commit_amend");
150}
151
152int
153main(int argc, char **argv)
154{
155 int opt;
156 static char cwd[PATH_MAX + 1];
157 static git_buf rfp;
158
159 if (!getcwd(cwd, sizeof(cwd)))
160 err(EXIT_FAILURE, "getcwd failed");
161
162 while ((opt = getopt(argc, argv, "av")) != -1) {
163 switch (opt) {
164 case 'a':
165 amend_single = true;
166 break;
167 case 'v':
168 verbose = true;
169 break;
170 default:
171 usage(argv[0]);
172 break;
173 }
174 }
175
176 git_libgit2_init();
177 if (git_repository_discover(&rfp, cwd, 0, NULL))
178 giterr("git_repository_discover");
179 if (git_repository_open(&repo, rfp.ptr))
180 giterr("git_repository_open");
181
182 if (amend_single) {
183 amend();
184 } else {
185 if (argc <= 1 || optind >= argc)
186 usage(argv[0]);
187 rebase(argv[optind]);
188 }
189}