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.
26 rev_list_add_head (rev_list
*rl
, rev_commit
*commit
, char *name
, int degree
)
29 rev_ref
**list
= &rl
->heads
;
32 list
= &(*list
)->next
;
33 r
= calloc (1, sizeof (rev_ref
));
43 rev_find_head (rev_list
*rl
, char *name
)
47 for (h
= rl
->heads
; h
; h
= h
->next
)
54 * We keep all file lists in a canonical sorted order,
55 * first by latest date and then by the address of the rev_file object
56 * (which are always unique)
60 rev_file_later (rev_file
*af
, rev_file
*bf
)
65 * When merging file lists, we should never see the same
66 * object in both trees
70 t
= time_compare (af
->date
, bf
->date
);
76 if ((uintptr_t) af
> (uintptr_t) bf
)
82 rev_commit_later (rev_commit
*a
, rev_commit
*b
)
87 t
= time_compare (a
->date
, b
->date
);
92 if ((uintptr_t) a
> (uintptr_t) b
)
98 * Commits further than 60 minutes apart are assume to be different
101 commit_time_close (time_t a
, time_t b
)
104 if (diff
< 0) diff
= -diff
;
105 if (diff
< commit_time_window
* 60)
111 * The heart of the merge operation; detect when two
112 * commits are "the same"
115 rev_commit_match (rev_commit
*a
, rev_commit
*b
)
118 * Very recent versions of CVS place a commitid in
119 * each commit to track patch sets. Use it if present
121 if (a
->commitid
&& b
->commitid
)
122 return a
->commitid
== b
->commitid
;
123 if (a
->commitid
|| b
->commitid
)
125 if (!commit_time_close (a
->date
, b
->date
))
127 if (a
->log
!= b
->log
)
129 if (a
->author
!= b
->author
)
136 rev_commit_dump (FILE *f
, char *title
, rev_commit
*c
, rev_commit
*m
)
138 fprintf (f
, "\n%s\n", title
);
142 fprintf (f
, "%c0x%x %s\n", c
== m
? '>' : ' ',
143 (int) c
, ctime_nonl (&c
->date
));
144 for (i
= 0; i
< c
->nfiles
; i
++) {
145 fprintf (f
, "\t%s", ctime_nonl (&c
->files
[i
]->date
));
146 dump_number_file (f
, c
->files
[i
]->name
, &c
->files
[i
]->number
);
156 rev_list_set_tail (rev_list
*rl
)
162 for (head
= rl
->heads
; head
; head
= head
->next
) {
164 if (head
->commit
&& head
->commit
->seen
) {
168 for (c
= head
->commit
; c
; c
= c
->parent
) {
169 if (tail
&& c
->parent
&& c
->seen
< c
->parent
->seen
) {
175 head
->commit
->tagged
= 1;
181 rev_ref_len (rev_ref
*r
)
192 rev_ref_sel (rev_ref
*r
, int len
)
194 rev_ref
*head
, **tail
;
198 int blen
= len
- alen
;
207 for (i
= 0; i
< alen
- 1; i
++)
214 a
= rev_ref_sel (a
, alen
);
215 b
= rev_ref_sel (b
, blen
);
221 if (a
->degree
< b
->degree
) {
228 tail
= &(*tail
)->next
;
244 rev_ref_sel_sort (rev_ref
*r
)
248 r
= rev_ref_sel (r
, rev_ref_len (r
));
249 for (s
= r
; s
&& s
->next
; s
= s
->next
) {
250 assert (s
->degree
<= s
->next
->degree
);
257 rev_ref_find_name (rev_ref
*h
, char *name
)
259 for (; h
; h
= h
->next
)
266 rev_ref_is_ready (char *name
, rev_list
*source
, rev_ref
*ready
)
268 for (; source
; source
= source
->next
) {
269 rev_ref
*head
= rev_find_head(source
, name
);
271 if (head
->parent
&& !rev_ref_find_name(ready
, head
->parent
->name
))
279 rev_ref_tsort (rev_ref
*refs
, rev_list
*head
)
281 rev_ref
*done
= NULL
;
282 rev_ref
**done_tail
= &done
;
285 // fprintf (stderr, "Tsort refs:\n");
287 for (prev
= &refs
; (r
= *prev
); prev
= &(*prev
)->next
) {
288 if (rev_ref_is_ready (r
->name
, head
, done
)) {
293 fprintf (stderr
, "Error: branch cycle\n");
298 // fprintf (stderr, "\t%s\n", r->name);
300 done_tail
= &r
->next
;
306 rev_list_count (rev_list
*head
)
317 rev_commit_date_compare (const void *av
, const void *bv
)
319 const rev_commit
*a
= *(const rev_commit
**) av
;
320 const rev_commit
*b
= *(const rev_commit
**) bv
;
324 * NULL entries sort last
334 * Entries with no files sort next
336 if (a
->nfiles
!= b
->nfiles
)
337 return b
->nfiles
- a
->nfiles
;
340 * tailed entries sort next
342 if (a
->tailed
!= b
->tailed
)
343 return a
->tailed
- b
->tailed
;
345 * Newest entries sort first
347 t
= -time_compare (a
->date
, b
->date
);
351 * Ensure total order by ordering based on file address
353 if ((uintptr_t) a
->file
> (uintptr_t) b
->file
)
355 if ((uintptr_t) a
->file
< (uintptr_t) b
->file
)
361 rev_commit_date_sort (rev_commit
**commits
, int ncommit
)
363 qsort (commits
, ncommit
, sizeof (rev_commit
*),
364 rev_commit_date_compare
);
366 * Trim off NULL entries
368 while (ncommit
&& !commits
[ncommit
-1])
374 rev_commit_has_file (rev_commit
*c
, rev_file
*f
)
380 for (i
= 0; i
< c
->ndirs
; i
++) {
381 rev_dir
*dir
= c
->dirs
[i
];
382 for (j
= 0; j
< dir
->nfiles
; j
++)
383 if (dir
->files
[j
] == f
)
391 rev_commit_find_file (rev_commit
*c
, char *name
)
395 for (n
= 0; n
< c
->nfiles
; n
++)
396 if (c
->files
[n
]->name
== name
)
402 static rev_file
**files
= NULL
;
403 static int sfiles
= 0;
406 rev_commit_cleanup (void)
416 rev_commit_build (rev_commit
**commits
, rev_commit
*leader
, int ncommit
)
424 if (rev_mode
== ExecuteGit
) {
425 reset_commits(commits
, ncommit
);
426 commit
= create_tree(leader
);
427 for (n
= 0; n
< ncommit
; n
++) {
428 if (commits
[n
] && commits
[n
]->file
) {
429 commit
->file
= commits
[n
]->file
;
435 if (ncommit
> sfiles
) {
440 files
= malloc ((sfiles
= ncommit
) * sizeof (rev_file
*));
443 for (n
= 0; n
< ncommit
; n
++)
444 if (commits
[n
] && commits
[n
]->file
)
445 files
[nfile
++] = commits
[n
]->file
;
452 rds
= rev_pack_files (files
, nfile
, &nds
);
454 commit
= calloc (1, sizeof (rev_commit
) +
455 nds
* sizeof (rev_dir
*));
457 commit
->date
= leader
->date
;
458 commit
->commitid
= leader
->commitid
;
459 commit
->log
= leader
->log
;
460 commit
->author
= leader
->author
;
462 commit
->file
= first
;
463 commit
->nfiles
= nfile
;
465 memcpy (commit
->dirs
, rds
, (commit
->ndirs
= nds
) * sizeof (rev_dir
*));
472 rev_ref_find_commit_file (rev_ref
*branch
, rev_file
*file
)
476 for (c
= branch
->commit
; c
; c
= c
->parent
)
477 if (rev_commit_has_file (c
, file
))
483 rev_commit_is_ancestor (rev_commit
*old
, rev_commit
*young
)
488 young
= young
->parent
;
495 rev_commit_locate_date (rev_ref
*branch
, time_t date
)
499 for (commit
= branch
->commit
; commit
; commit
= commit
->parent
)
501 if (time_compare (commit
->date
, date
) <= 0)
508 rev_commit_locate_one (rev_ref
*branch
, rev_commit
*file
)
515 for (commit
= branch
->commit
;
517 commit
= commit
->parent
)
519 if (rev_commit_match (commit
, file
))
526 rev_commit_locate_any (rev_ref
*branch
, rev_commit
*file
)
532 commit
= rev_commit_locate_any (branch
->next
, file
);
535 return rev_commit_locate_one (branch
, file
);
539 rev_commit_locate (rev_ref
*branch
, rev_commit
*file
)
544 * Check the presumed trunk first
546 commit
= rev_commit_locate_one (branch
, file
);
550 * Now look through all branches
552 while (branch
->parent
)
553 branch
= branch
->parent
;
554 return rev_commit_locate_any (branch
, file
);
558 rev_branch_of_commit (rev_list
*rl
, rev_commit
*commit
)
563 for (h
= rl
->heads
; h
; h
= h
->next
)
567 for (c
= h
->commit
; c
; c
= c
->parent
) {
568 if (rev_commit_match (c
, commit
))
578 * Time of first commit along entire history
581 rev_commit_first_date (rev_commit
*commit
)
583 while (commit
->parent
)
584 commit
= commit
->parent
;
589 * Merge a set of per-file branches into a global branch
592 rev_branch_merge (rev_ref
**branches
, int nbranch
,
593 rev_ref
*branch
, rev_list
*rl
)
597 rev_commit
*prev
= NULL
;
598 rev_commit
*head
= NULL
, **tail
= &head
;
599 rev_commit
**commits
= calloc (nbranch
, sizeof (rev_commit
*));
607 for (n
= 0; n
< nbranch
; n
++) {
610 * Initialize commits to head of each branch
612 c
= commits
[n
] = branches
[n
]->commit
;
614 * Compute number of branches with remaining entries
618 if (branches
[n
]->tail
) {
623 while (c
&& !c
->tail
) {
624 if (!start
|| time_compare(c
->date
, start
) < 0)
628 if (c
&& (c
->file
|| c
->date
!= c
->parent
->date
)) {
629 if (!start
|| time_compare(c
->date
, start
) < 0)
634 for (n
= 0; n
< nbranch
; n
++) {
635 rev_commit
*c
= commits
[n
];
638 if (!start
|| time_compare(start
, c
->date
) >= 0)
642 "Warning: %s too late date through branch %s\n",
643 c
->file
->name
, branch
->name
);
647 * Walk down branches until each one has merged with the
650 while (nlive
> 0 && nbranch
> 0) {
651 for (n
= 0, p
= commits
, latest
= NULL
; n
< nbranch
; n
++) {
652 rev_commit
*c
= commits
[n
];
658 if (!latest
|| time_compare(latest
->date
, c
->date
) < 0)
661 nbranch
= p
- commits
;
664 * Construct current commit
667 commit
= rev_commit_build (commits
, latest
, nbranch
);
668 if (rev_mode
== ExecuteGit
)
671 commit
= create_tree(latest
);
678 for (n
= 0; n
< nbranch
; n
++) {
679 rev_commit
*c
= commits
[n
];
681 /* already got to parent branch? */
685 if (c
!= latest
&& !rev_commit_match(c
, latest
)) {
686 if (c
->parent
|| c
->file
)
697 * Adding file independently added on another
700 if (!to
->parent
&& !to
->file
)
703 * If the parent is at the beginning of trunk
704 * and it is younger than some events on our
705 * branch, we have old CVS adding file
707 * added on another branch.
709 if (start
&& time_compare(start
, to
->date
) < 0)
712 * XXX: we still can't be sure that it's
713 * not a file added on trunk after parent
714 * branch had forked off it but before
715 * our branch's creation.
718 } else if (to
->file
) {
722 * See if it's recent CVS adding a file
723 * independently added on another branch.
727 if (to
->tail
&& to
->date
== to
->parent
->date
)
743 tail
= &commit
->parent
;
747 * Connect to parent branch
749 nbranch
= rev_commit_date_sort (commits
, nbranch
);
750 if (nbranch
&& branch
->parent
)
756 for (present
= 0; present
< nbranch
; present
++)
757 if (commits
[present
]->file
) {
759 * Skip files which appear in the repository after
760 * the first commit along the branch
762 if (prev
&& commits
[present
]->date
> prev
->date
&&
763 commits
[present
]->date
== rev_commit_first_date (commits
[present
]))
765 fprintf (stderr
, "Warning: file %s appears after branch %s date\n",
766 commits
[present
]->file
->name
, branch
->name
);
771 if (present
== nbranch
)
773 else if ((*tail
= rev_commit_locate_one (branch
->parent
,
776 if (prev
&& time_compare ((*tail
)->date
, prev
->date
) > 0) {
777 fprintf (stderr
, "Warning: branch point %s -> %s later than branch\n",
778 branch
->name
, branch
->parent
->name
);
779 fprintf (stderr
, "\ttrunk(%3d): %s %s", n
,
780 ctime_nonl (&commits
[present
]->date
),
781 commits
[present
]->file
? " " : "D" );
782 if (commits
[present
]->file
)
783 dump_number_file (stderr
,
784 commits
[present
]->file
->name
,
785 &commits
[present
]->file
->number
);
786 fprintf (stderr
, "\n");
787 fprintf (stderr
, "\tbranch(%3d): %s ", n
,
788 ctime_nonl (&prev
->file
->date
));
789 dump_number_file (stderr
,
791 &prev
->file
->number
);
792 fprintf (stderr
, "\n");
794 } else if ((*tail
= rev_commit_locate_date (branch
->parent
,
795 commits
[present
]->date
)))
796 fprintf (stderr
, "Warning: branch point %s -> %s matched by date\n",
797 branch
->name
, branch
->parent
->name
);
799 fprintf (stderr
, "Error: branch point %s -> %s not found.",
800 branch
->name
, branch
->parent
->name
);
802 if ((lost
= rev_branch_of_commit (rl
, commits
[present
])))
803 fprintf (stderr
, " Possible match on %s.", lost
->name
);
804 fprintf (stderr
, "\n");
810 *tail
= rev_commit_build (commits
, commits
[0], nbranch
);
812 for (n
= 0; n
< nbranch
; n
++)
814 commits
[n
]->tailed
= 0;
816 branch
->commit
= head
;
820 * Locate position in tree cooresponding to specific tag
823 rev_tag_search(Tag
*tag
, rev_commit
**commits
, rev_list
*rl
)
825 rev_commit_date_sort(commits
, tag
->count
);
826 tag
->parent
= rev_branch_of_commit(rl
, commits
[0]);
828 tag
->commit
= rev_commit_locate (tag
->parent
, commits
[0]);
830 fprintf (stderr
, "unmatched tag %s\n", tag
->name
);
831 /* AV: shouldn't we put it on some branch? */
832 tag
->commit
= rev_commit_build(commits
, commits
[0], tag
->count
);
834 tag
->commit
->tagged
= 1;
838 rev_ref_set_parent (rev_list
*rl
, rev_ref
*dest
, rev_list
*source
)
849 for (s
= source
; s
; s
= s
->next
) {
850 sh
= rev_find_head (s
, dest
->name
);
855 p
= rev_find_head (rl
, sh
->parent
->name
);
857 rev_ref_set_parent (rl
, p
, source
);
858 if (!max
|| p
->depth
> max
->depth
)
863 dest
->depth
= max
->depth
+ 1;
871 rev_head_find_parent (rev_list
*rl
, rev_ref
*h
, rev_list
*lhead
)
876 for (l
= lhead
; l
; l
= l
->next
) {
877 lh
= rev_find_head (l
, h
->name
);
887 rev_branch_name_is_ancestor (rev_ref
*old
, rev_ref
*young
)
890 if (young
->name
== old
->name
)
892 young
= young
->parent
;
900 rev_ref_parent (rev_ref
**refs
, int nref
, rev_list
*rl
)
902 rev_ref
*parent
= NULL
;
903 rev_ref
*branch
= NULL
;
907 for (n
= 0; n
< nref
; n
++)
909 if (refs
[n
]->parent
) {
911 parent
= refs
[n
]->parent
;
913 } else if (parent
->name
!= refs
[n
]->parent
->name
) {
914 if (rev_branch_name_is_ancestor (refs
[n
]->parent
, parent
))
916 else if (rev_branch_name_is_ancestor (parent
, refs
[n
]->parent
)) {
917 parent
= refs
[n
]->parent
;
920 fprintf (stderr
, "Branch name collision:\n");
921 fprintf (stderr
, "\tfirst branch: ");
922 dump_ref_name (stderr
, branch
);
923 fprintf (stderr
, "\n");
924 fprintf (stderr
, "\tsecond branch: ");
925 dump_ref_name (stderr
, refs
[n
]);
926 fprintf (stderr
, "\n");
933 for (h
= rl
->heads
; h
; h
= h
->next
)
934 if (parent
->name
== h
->name
)
936 fprintf (stderr
, "Reference missing in merge: %s\n", parent
->name
);
942 rev_list_merge (rev_list
*head
)
944 int count
= rev_list_count (head
);
945 rev_list
*rl
= calloc (1, sizeof (rev_list
));
949 rev_ref
**refs
= calloc (count
, sizeof (rev_ref
*));
953 * Find all of the heads across all of the incoming trees
954 * Yes, this is currently very inefficient
956 for (l
= head
; l
; l
= l
->next
) {
957 for (lh
= l
->heads
; lh
; lh
= lh
->next
) {
958 h
= rev_find_head (rl
, lh
->name
);
960 rev_list_add_head (rl
, NULL
, lh
->name
, lh
->degree
);
961 else if (lh
->degree
> h
->degree
)
962 h
->degree
= lh
->degree
;
966 * Sort by degree so that finding branch points always works
968 // rl->heads = rev_ref_sel_sort (rl->heads);
969 rl
->heads
= rev_ref_tsort (rl
->heads
, head
);
972 // for (h = rl->heads; h; h = h->next)
973 // fprintf (stderr, "head %s (%d)\n",
974 // h->name, h->degree);
976 * Find branch parent relationships
978 for (h
= rl
->heads
; h
; h
= h
->next
) {
979 rev_ref_set_parent (rl
, h
, head
);
980 // dump_ref_name (stderr, h);
981 // fprintf (stderr, "\n");
984 * Merge common branches
986 for (h
= rl
->heads
; h
; h
= h
->next
) {
988 * Locate branch in every tree
991 for (l
= head
; l
; l
= l
->next
) {
992 lh
= rev_find_head (l
, h
->name
);
997 rev_branch_merge (refs
, nref
, h
, rl
);
1000 * Compute 'tail' values
1002 rev_list_set_tail (rl
);
1006 * Find tag locations
1008 for (t
= all_tags
; t
; t
= t
->next
) {
1009 rev_commit
**commits
= tagged(t
);
1011 rev_tag_search(t
, commits
, rl
);
1013 fprintf (stderr
, "lost tag %s\n", t
->name
);
1016 rev_list_validate (rl
);
1021 * Icky. each file revision may be referenced many times in a single
1022 * tree. When freeing the tree, queue the file objects to be deleted
1023 * and clean them up afterwards
1026 static rev_file
*rev_files
;
1029 rev_file_mark_for_free (rev_file
*f
)
1033 f
->link
= rev_files
;
1039 rev_file_free_marked (void)
1043 for (f
= rev_files
; f
; f
= n
)
1052 rev_file_rev (char *name
, cvs_number
*n
, time_t date
)
1054 rev_file
*f
= calloc (1, sizeof (rev_file
));
1063 rev_file_free (rev_file
*f
)
1069 rev_commit_free (rev_commit
*commit
, int free_files
)
1073 while ((c
= commit
)) {
1077 if (free_files
&& c
->file
)
1078 rev_file_mark_for_free (c
->file
);
1085 rev_head_free (rev_ref
*head
, int free_files
)
1089 while ((h
= head
)) {
1091 rev_commit_free (h
->commit
, free_files
);
1097 rev_list_free (rev_list
*rl
, int free_files
)
1099 rev_head_free (rl
->heads
, free_files
);
1101 rev_file_free_marked ();
1106 rev_list_validate (rev_list
*rl
)
1110 for (h
= rl
->heads
; h
; h
= h
->next
) {
1113 for (c
= h
->commit
; c
&& c
->parent
; c
= c
->parent
) {
1116 // assert (time_compare (c->date, c->parent->date) >= 0);
1122 * Generate a list of files in uniq that aren't in common
1125 static rev_file_list
*
1126 rev_uniq_file (rev_commit
*uniq
, rev_commit
*common
, int *nuniqp
)
1130 rev_file_list
*head
= NULL
, **tail
= &head
, *fl
;
1134 for (i
= 0; i
< uniq
->ndirs
; i
++) {
1135 rev_dir
*dir
= uniq
->dirs
[i
];
1136 for (j
= 0; j
< dir
->nfiles
; j
++)
1137 if (!rev_commit_has_file (common
, dir
->files
[j
])) {
1138 fl
= calloc (1, sizeof (rev_file_list
));
1139 fl
->file
= dir
->files
[j
];
1150 rev_file_list_has_filename (rev_file_list
*fl
, char *name
)
1152 for (; fl
; fl
= fl
->next
)
1153 if (fl
->file
->name
== name
)
1159 * Generate a diff between two commits. Either may be NULL
1163 rev_commit_diff (rev_commit
*old
, rev_commit
*new)
1165 rev_diff
*diff
= calloc (1, sizeof (rev_diff
));
1167 diff
->del
= rev_uniq_file (old
, new, &diff
->ndel
);
1168 diff
->add
= rev_uniq_file (new, old
, &diff
->nadd
);
1173 rev_file_list_free (rev_file_list
*fl
)
1175 rev_file_list
*next
;
1185 rev_diff_free (rev_diff
*d
)
1187 rev_file_list_free (d
->del
);
1188 rev_file_list_free (d
->add
);