Gracefuly handle spaces around the equal sign in the Authors file.
[parsecvs.git] / git.c
blob6885162c78a434eb8be3b59a5f7f7f92c6b74b49
1 /*
2 * Copyright © 2006 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or (at
7 * your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 #include "cvs.h"
20 #include "cache.h"
21 #include "commit.h"
22 #include "utf8.h"
24 static int
25 git_filename (rev_file *file, char *name, int strip)
27 char *attic;
28 int l;
29 int len;
31 if (strlen (file->name) - strip >= MAXPATHLEN)
32 return 0;
33 strcpy (name, file->name + strip);
34 while ((attic = strstr (name, "Attic/")) &&
35 (attic == name || attic[-1] == '/'))
37 l = strlen (attic);
38 memmove (attic, attic + 6, l - 5);
40 len = strlen (name);
41 if (len > 2 && !strcmp (name + len - 2, ",v"))
42 name[len-2] = '\0';
43 return 1;
46 #define GIT_CVS_DIR ".git-cvs"
48 static char *
49 git_cvs_file (char *base)
51 char *filename_buf;
52 char *filename;
53 static int id;
55 if (id == 0)
57 if (mkdir (GIT_CVS_DIR, 0777) < 0 && errno != EEXIST) {
58 fprintf (stderr, "%s: %s\n", GIT_CVS_DIR, strerror (errno));
59 return NULL;
62 filename_buf = git_format_command ("%s/%s-%d",
63 GIT_CVS_DIR, base, id++);
64 if (!filename_buf)
65 return NULL;
66 filename = atom (filename_buf);
67 free (filename_buf);
68 return filename;
71 extern const char *log_command;
72 static char *log_buf;
73 static size_t log_size;
75 static char *
76 git_log(rev_commit *commit)
78 if (!log_command)
79 return commit->log;
81 char *filename;
82 char *command;
83 FILE *f;
84 int n;
85 size_t size;
87 filename = git_cvs_file ("log");
88 if (!filename)
89 return NULL;
90 f = fopen (filename, "w+");
91 if (!f) {
92 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
93 return NULL;
95 if (fputs (commit->log, f) == EOF) {
96 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
97 fclose (f);
98 return NULL;
100 fflush (f);
102 command = git_format_command ("%s '%s'", log_command, filename);
103 if (!command)
104 return NULL;
105 n = git_system (command);
106 free (command);
107 if (n != 0)
108 return NULL;
109 fflush (f);
110 rewind(f);
111 size = 0;
112 while (1) {
113 if (size + 1 >= log_size) {
114 if (!log_size)
115 log_size = 1024;
116 else
117 log_size *= 2;
118 log_buf = xrealloc(log_buf, log_size);
120 n = fread(log_buf + size, 1, log_size - size - 1, f);
121 if (!n)
122 break;
123 size += n;
125 fclose(f);
126 log_buf[size] = '\0';
127 return log_buf;
130 typedef struct _cvs_author {
131 struct _cvs_author *next;
132 char *name;
133 char *full;
134 char *email;
135 } cvs_author;
137 #define AUTHOR_HASH 1021
139 static cvs_author *author_buckets[AUTHOR_HASH];
141 static cvs_author *
142 git_fullname (char *name)
144 cvs_author **bucket = &author_buckets[((unsigned long) name) % AUTHOR_HASH];
145 cvs_author *a;
147 for (a = *bucket; a; a = a->next)
148 if (a->name == name)
149 return a;
150 return NULL;
153 void
154 git_free_author_map (void)
156 int h;
158 for (h = 0; h < AUTHOR_HASH; h++) {
159 cvs_author **bucket = &author_buckets[h];
160 cvs_author *a;
162 while ((a = *bucket)) {
163 *bucket = a->next;
164 free (a);
169 static int
170 git_load_author_map (char *filename)
172 char line[10240];
173 char *equal;
174 char *angle;
175 char *email;
176 char *name;
177 char *full;
178 FILE *f;
179 int lineno = 0;
180 cvs_author *a, **bucket;
182 f = fopen (filename, "r");
183 if (!f) {
184 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
185 return 0;
187 while (fgets (line, sizeof (line) - 1, f)) {
188 lineno++;
189 if (line[0] == '#')
190 continue;
191 equal = strchr (line, '=');
192 if (!equal) {
193 fprintf (stderr, "%s: (%d) missing '='\n", filename, lineno);
194 fclose (f);
195 return 0;
197 full = equal + 1;
198 while (equal > line && equal[-1] == ' ')
199 equal--;
200 *equal = '\0';
201 name = atom (line);
202 if (git_fullname (name)) {
203 fprintf (stderr, "%s: (%d) duplicate name '%s' ignored\n",
204 filename, lineno, name);
205 fclose (f);
206 return 0;
208 a = calloc (1, sizeof (cvs_author));
209 a->name = name;
210 angle = strchr (full, '<');
211 if (!angle) {
212 fprintf (stderr, "%s: (%d) missing email address '%s'\n",
213 filename, lineno, name);
214 fclose (f);
215 return 0;
217 email = angle + 1;
218 while (full < angle && full[0] == ' ')
219 full++;
220 while (angle > full && angle[-1] == ' ')
221 angle--;
222 *angle = '\0';
223 a->full = atom(full);
224 angle = strchr (email, '>');
225 if (!angle) {
226 fprintf (stderr, "%s: (%d) malformed email address '%s\n",
227 filename, lineno, name);
228 fclose (f);
229 return 0;
231 *angle = '\0';
232 a->email = atom (email);
233 bucket = &author_buckets[((unsigned long) name) % AUTHOR_HASH];
234 a->next = *bucket;
235 *bucket = a;
237 fclose (f);
238 return 1;
241 static int git_total_commits;
242 static int git_current_commit;
243 static char *git_current_head;
245 #define STATUS stdout
246 #define PROGRESS_LEN 20
248 static void
249 git_status (void)
251 int spot = git_current_commit * PROGRESS_LEN / git_total_commits;
252 int s;
254 fprintf (STATUS, "Save: %35.35s ", git_current_head);
255 for (s = 0; s < PROGRESS_LEN + 1; s++)
256 putc (s == spot ? '*' : '.', STATUS);
257 fprintf (STATUS, " %5d of %5d\n", git_current_commit, git_total_commits);
258 fflush (STATUS);
261 static char *commit_text;
262 static size_t commit_size;
263 static void add_buffer(size_t *offset, const char *fmt, ...)
265 va_list args;
266 size_t n;
267 while (1) {
268 va_start(args, fmt);
269 n = vsnprintf(commit_text + *offset, commit_size - *offset,
270 fmt, args);
271 va_end(args);
272 if (n < commit_size - *offset)
273 break;
274 if (!commit_size)
275 commit_size = 1024;
276 else
277 commit_size *= 2;
278 commit_text = xrealloc(commit_text, commit_size);
280 *offset += n;
283 * Create a commit object in the repository using the current
284 * index and the information from the provided rev_commit
286 static int
287 git_commit(rev_commit *commit)
289 cvs_author *author;
290 char *full;
291 char *email;
292 char *log;
293 unsigned char commit_sha1[20];
294 size_t size = 0;
295 int encoding_is_utf8;
297 if (!commit->sha1)
298 return 0;
300 log = git_log(commit);
301 if (!log)
302 return 0;
304 author = git_fullname(commit->author);
305 if (!author) {
306 // fprintf (stderr, "%s: not in author map\n", commit->author);
307 full = commit->author;
308 email = commit->author;
309 } else {
310 full = author->full;
311 email = author->email;
314 /* Not having i18n.commitencoding is the same as having utf-8 */
315 encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
317 add_buffer(&size, "tree %s\n", commit->sha1);
318 if (commit->parent)
319 add_buffer(&size, "parent %s\n", commit->parent->sha1);
320 add_buffer(&size, "author %s <%s> %lu +0000\n",
321 full, email, commit->date);
322 add_buffer(&size, "committer %s <%s> %lu +0000\n",
323 full, email, commit->date);
324 if (!encoding_is_utf8)
325 add_buffer(&size, "encoding %s\n", git_commit_encoding);
326 add_buffer(&size, "\n%s", log);
328 if (write_sha1_file(commit_text, size, commit_type, commit_sha1))
329 return 0;
331 commit->sha1 = atom(sha1_to_hex(commit_sha1));
332 if (!commit->sha1)
333 return 0;
334 return 1;
337 static int
338 git_update_ref (char *sha1, char *type, char *name)
340 char *command;
341 int n;
343 command = git_format_command ("git update-ref 'refs/%s/%s' '%s'",
344 type, name, sha1);
345 if (!command)
346 return 0;
347 n = git_system (command);
348 free (command);
349 if (n != 0)
350 return 0;
351 return 1;
354 static char *
355 git_mktag (rev_commit *commit, char *name)
357 char *filename;
358 FILE *f;
359 int rv;
360 char *command;
361 char *tag_sha1;
362 cvs_author *author;
364 filename = git_cvs_file ("tag");
365 if (!filename)
366 return NULL;
367 f = fopen (filename, "w");
368 if (!f) {
369 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
370 return NULL;
373 author = git_fullname (commit->author);
374 rv = fprintf (f,
375 "object %s\n"
376 "type commit\n"
377 "tag %s\n"
378 "tagger %s <%s> %ld +0000\n"
379 "\n",
380 commit->sha1,
381 name,
382 author ? author->full : commit->author,
383 author ? author->email : "",
384 commit->date);
385 if (rv < 1) {
386 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
387 fclose (f);
388 unlink (filename);
389 return NULL;
391 rv = fclose (f);
392 if (rv) {
393 fprintf (stderr, "%s: %s\n", filename, strerror (errno));
394 unlink (filename);
395 return NULL;
398 command = git_format_command ("git mktag < '%s'", filename);
399 if (!command) {
400 unlink (filename);
401 return NULL;
403 tag_sha1 = git_system_to_string (command);
404 unlink (filename);
405 free (command);
406 return tag_sha1;
409 static int
410 git_tag (rev_commit *commit, char *name)
412 char *tag_sha1;
414 tag_sha1 = git_mktag (commit, name);
415 if (!tag_sha1)
416 return 0;
417 return git_update_ref (tag_sha1, "tags", name);
420 static int
421 git_head (rev_commit *commit, char *name)
423 return git_update_ref (commit->sha1, "heads", name);
426 static int
427 git_commit_recurse (rev_ref *head, rev_commit *commit, int strip)
429 Tag *t;
431 if (commit->parent && !commit->tail)
432 if (!git_commit_recurse (head, commit->parent, strip))
433 return 0;
434 ++git_current_commit;
435 git_status ();
436 if (!git_commit (commit))
437 return 0;
438 for (t = all_tags; t; t = t->next)
439 if (t->commit == commit)
440 if (!git_tag (commit, t->name))
441 return 0;
442 return 1;
445 static int
446 git_head_commit (rev_ref *head, int strip)
448 git_current_head = head->name;
449 if (!head->tail)
450 if (!git_commit_recurse (head, head->commit, strip))
451 return 0;
452 if (!git_head (head->commit, head->name))
453 return 0;
454 return 1;
457 static int
458 git_ncommit (rev_list *rl)
460 rev_ref *h;
461 rev_commit *c;
462 int n = 0;
464 for (h = rl->heads; h; h = h->next) {
465 if (h->tail)
466 continue;
467 for (c = h->commit; c; c = c->parent) {
468 n++;
469 if (c->tail)
470 break;
473 return n;
477 git_rev_list_commit (rev_list *rl, int strip)
479 rev_ref *h;
481 git_load_author_map ("Authors");
482 git_total_commits = git_ncommit (rl);
483 git_current_commit = 0;
484 for (h = rl->heads; h; h = h->next)
485 if (!git_head_commit (h, strip))
486 return 0;
487 fprintf (STATUS, "\n");
488 // if (!git_checkout ("master"))
489 // return 0;
490 return 1;
493 static FILE *packf;
495 static char *
496 git_start_pack (void)
498 char *pack_file = git_cvs_file ("pack");
500 packf = fopen (pack_file, "w");
501 if (!packf)
502 return NULL;
503 return pack_file;
506 static void
507 git_file_pack (rev_file *file, int strip)
509 char filename[MAXPATHLEN + 1];
511 if (!git_filename (file, filename, strip))
512 return;
513 fprintf (packf, "%s %s\n", file->sha1, filename);
516 extern void reprepare_packed_git (void);
518 static void
519 git_end_pack (char *pack_file, char *pack_dir)
521 char *command;
522 char *pack_name;
523 char *src_pack_pack, *src_pack_idx;
524 char *dst_pack_pack, *dst_pack_idx;
526 if (fclose (packf) == EOF)
527 return;
528 command = git_format_command ("git pack-objects -q --non-empty .tmp-pack < '%s'",
529 pack_file);
530 if (!command) {
531 unlink (pack_file);
532 return;
534 pack_name = git_system_to_string (command);
535 unlink (pack_file);
536 free (command);
537 if (!pack_name)
538 return;
539 fprintf (STATUS, "Pack pack-%s created\n", pack_name);
540 fflush (STATUS);
541 src_pack_pack = git_format_command (".tmp-pack-%s.pack", pack_name);
542 src_pack_idx = git_format_command (".tmp-pack-%s.idx", pack_name);
543 dst_pack_pack = git_format_command ("%s/pack-%s.pack", pack_dir, pack_name);
544 dst_pack_idx = git_format_command ("%s/pack-%s.idx", pack_dir, pack_name);
545 if (!src_pack_pack || !src_pack_idx ||
546 !dst_pack_pack || !dst_pack_idx)
548 if (src_pack_pack) free (src_pack_pack);
549 if (src_pack_idx) free (src_pack_idx);
550 if (dst_pack_pack) free (dst_pack_pack);
551 if (dst_pack_idx) free (dst_pack_idx);
552 return;
554 if (rename (src_pack_pack, dst_pack_pack) == -1 ||
555 rename (src_pack_idx, dst_pack_idx) == -1)
556 return;
557 free (src_pack_pack);
558 free (src_pack_idx);
559 free (dst_pack_pack);
560 free (dst_pack_idx);
562 (void) git_system ("git prune-packed");
563 reprepare_packed_git ();
566 static char *
567 git_pack_directory (void)
569 static char *pack_dir;
571 if (!pack_dir)
573 char *git_dir;
574 char *objects_dir;
576 git_dir = git_system_to_string ("git rev-parse --git-dir");
577 if (!git_dir)
578 return NULL;
579 objects_dir = git_format_command ("%s/objects", git_dir);
580 if (!objects_dir)
581 return NULL;
582 if (access (objects_dir, F_OK) == -1 &&
583 mkdir (objects_dir, 0777) == -1)
585 free (objects_dir);
586 return NULL;
588 free (objects_dir);
589 pack_dir = git_format_command ("%s/objects/pack", git_dir);
590 if (!pack_dir)
591 return NULL;
592 if (access (pack_dir, F_OK) == -1 &&
593 mkdir (pack_dir, 0777) == -1)
595 free (pack_dir);
596 pack_dir = NULL;
599 return pack_dir;
602 void
603 git_rev_list_pack (rev_list *rl, int strip)
605 char *pack_file;
606 char *pack_dir;
608 pack_dir = git_pack_directory ();
609 if (!pack_dir)
610 return;
611 pack_file = git_start_pack ();
612 if (!pack_file)
613 return;
615 while (rl) {
616 rev_ref *h;
617 rev_commit *c;
619 for (h = rl->heads; h; h = h->next) {
620 if (h->tail)
621 continue;
622 for (c = h->commit; c; c = c->parent) {
623 if (c->file)
624 git_file_pack (c->file, strip);
625 if (c->tail)
626 break;
629 rl = rl->next;
631 git_end_pack (pack_file, pack_dir);