1#include "cache.h"2#include "bundle.h"3#include "object.h"4#include "commit.h"5#include "diff.h"6#include "revision.h"7#include "list-objects.h"8#include "run-command.h"9#include "refs.h"1011static const char bundle_signature[] = "# v2 git bundle\n";1213static void add_to_ref_list(const unsigned char *sha1, const char *name,14struct ref_list *list)15{16if (list->nr + 1 >= list->alloc) {17list->alloc = alloc_nr(list->nr + 1);18list->list = xrealloc(list->list,19list->alloc * sizeof(list->list[0]));20}21memcpy(list->list[list->nr].sha1, sha1, 20);22list->list[list->nr].name = xstrdup(name);23list->nr++;24}2526static int parse_bundle_header(int fd, struct bundle_header *header,27const char *report_path)28{29struct strbuf buf = STRBUF_INIT;30int status = 0;3132/* The bundle header begins with the signature */33if (strbuf_getwholeline_fd(&buf, fd, '\n') ||34strcmp(buf.buf, bundle_signature)) {35if (report_path)36error(_("'%s' does not look like a v2 bundle file"),37report_path);38status = -1;39goto abort;40}4142/* The bundle header ends with an empty line */43while (!strbuf_getwholeline_fd(&buf, fd, '\n') &&44buf.len && buf.buf[0] != '\n') {45unsigned char sha1[20];46int is_prereq = 0;4748if (*buf.buf == '-') {49is_prereq = 1;50strbuf_remove(&buf, 0, 1);51}52strbuf_rtrim(&buf);5354/*55* Tip lines have object name, SP, and refname.56* Prerequisites have object name that is optionally57* followed by SP and subject line.58*/59if (get_sha1_hex(buf.buf, sha1) ||60(40 <= buf.len && !isspace(buf.buf[40])) ||61(!is_prereq && buf.len <= 40)) {62if (report_path)63error(_("unrecognized header: %s%s (%d)"),64(is_prereq ? "-" : ""), buf.buf, (int)buf.len);65status = -1;66break;67} else {68if (is_prereq)69add_to_ref_list(sha1, "", &header->prerequisites);70else71add_to_ref_list(sha1, buf.buf + 41, &header->references);72}73}7475abort:76if (status) {77close(fd);78fd = -1;79}80strbuf_release(&buf);81return fd;82}8384int read_bundle_header(const char *path, struct bundle_header *header)85{86int fd = open(path, O_RDONLY);8788if (fd < 0)89return error(_("could not open '%s'"), path);90return parse_bundle_header(fd, header, path);91}9293int is_bundle(const char *path, int quiet)94{95struct bundle_header header;96int fd = open(path, O_RDONLY);9798if (fd < 0)99return 0;100memset(&header, 0, sizeof(header));101fd = parse_bundle_header(fd, &header, quiet ? NULL : path);102if (fd >= 0)103close(fd);104return (fd >= 0);105}106107static int list_refs(struct ref_list *r, int argc, const char **argv)108{109int i;110111for (i = 0; i < r->nr; i++) {112if (argc > 1) {113int j;114for (j = 1; j < argc; j++)115if (!strcmp(r->list[i].name, argv[j]))116break;117if (j == argc)118continue;119}120printf("%s %s\n", sha1_to_hex(r->list[i].sha1),121r->list[i].name);122}123return 0;124}125126#define PREREQ_MARK (1u<<16)127128int verify_bundle(struct bundle_header *header, int verbose)129{130/*131* Do fast check, then if any prereqs are missing then go line by line132* to be verbose about the errors133*/134struct ref_list *p = &header->prerequisites;135struct rev_info revs;136const char *argv[] = {NULL, "--all", NULL};137struct object_array refs;138struct commit *commit;139int i, ret = 0, req_nr;140const char *message = _("Repository lacks these prerequisite commits:");141142init_revisions(&revs, NULL);143for (i = 0; i < p->nr; i++) {144struct ref_list_entry *e = p->list + i;145struct object *o = parse_object(e->sha1);146if (o) {147o->flags |= PREREQ_MARK;148add_pending_object(&revs, o, e->name);149continue;150}151if (++ret == 1)152error("%s", message);153error("%s %s", sha1_to_hex(e->sha1), e->name);154}155if (revs.pending.nr != p->nr)156return ret;157req_nr = revs.pending.nr;158setup_revisions(2, argv, &revs, NULL);159160refs = revs.pending;161revs.leak_pending = 1;162163if (prepare_revision_walk(&revs))164die(_("revision walk setup failed"));165166i = req_nr;167while (i && (commit = get_revision(&revs)))168if (commit->object.flags & PREREQ_MARK)169i--;170171for (i = 0; i < req_nr; i++)172if (!(refs.objects[i].item->flags & SHOWN)) {173if (++ret == 1)174error("%s", message);175error("%s %s", sha1_to_hex(refs.objects[i].item->sha1),176refs.objects[i].name);177}178179clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);180free(refs.objects);181182if (verbose) {183struct ref_list *r;184185r = &header->references;186printf_ln(Q_("The bundle contains %d ref",187"The bundle contains %d refs",188r->nr),189r->nr);190list_refs(r, 0, NULL);191r = &header->prerequisites;192printf_ln(Q_("The bundle requires this ref",193"The bundle requires these %d refs",194r->nr),195r->nr);196list_refs(r, 0, NULL);197}198return ret;199}200201int list_bundle_refs(struct bundle_header *header, int argc, const char **argv)202{203return list_refs(&header->references, argc, argv);204}205206static int is_tag_in_date_range(struct object *tag, struct rev_info *revs)207{208unsigned long size;209enum object_type type;210char *buf, *line, *lineend;211unsigned long date;212213if (revs->max_age == -1 && revs->min_age == -1)214return 1;215216buf = read_sha1_file(tag->sha1, &type, &size);217if (!buf)218return 1;219line = memmem(buf, size, "\ntagger ", 8);220if (!line++)221return 1;222lineend = memchr(line, buf + size - line, '\n');223line = memchr(line, lineend ? lineend - line : buf + size - line, '>');224if (!line++)225return 1;226date = strtoul(line, NULL, 10);227free(buf);228return (revs->max_age == -1 || revs->max_age < date) &&229(revs->min_age == -1 || revs->min_age > date);230}231232int create_bundle(struct bundle_header *header, const char *path,233int argc, const char **argv)234{235static struct lock_file lock;236int bundle_fd = -1;237int bundle_to_stdout;238const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));239const char **argv_pack = xmalloc(6 * sizeof(const char *));240int i, ref_count = 0;241struct strbuf buf = STRBUF_INIT;242struct rev_info revs;243struct child_process rls;244FILE *rls_fout;245246bundle_to_stdout = !strcmp(path, "-");247if (bundle_to_stdout)248bundle_fd = 1;249else250bundle_fd = hold_lock_file_for_update(&lock, path,251LOCK_DIE_ON_ERROR);252253/* write signature */254write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));255256/* init revs to list objects for pack-objects later */257save_commit_buffer = 0;258init_revisions(&revs, NULL);259260/* write prerequisites */261memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *));262argv_boundary[0] = "rev-list";263argv_boundary[1] = "--boundary";264argv_boundary[2] = "--pretty=oneline";265argv_boundary[argc + 2] = NULL;266memset(&rls, 0, sizeof(rls));267rls.argv = argv_boundary;268rls.out = -1;269rls.git_cmd = 1;270if (start_command(&rls))271return -1;272rls_fout = xfdopen(rls.out, "r");273while (strbuf_getwholeline(&buf, rls_fout, '\n') != EOF) {274unsigned char sha1[20];275if (buf.len > 0 && buf.buf[0] == '-') {276write_or_die(bundle_fd, buf.buf, buf.len);277if (!get_sha1_hex(buf.buf + 1, sha1)) {278struct object *object = parse_object(sha1);279object->flags |= UNINTERESTING;280add_pending_object(&revs, object, xstrdup(buf.buf));281}282} else if (!get_sha1_hex(buf.buf, sha1)) {283struct object *object = parse_object(sha1);284object->flags |= SHOWN;285}286}287strbuf_release(&buf);288fclose(rls_fout);289if (finish_command(&rls))290return error(_("rev-list died"));291292/* write references */293argc = setup_revisions(argc, argv, &revs, NULL);294295if (argc > 1)296return error(_("unrecognized argument: %s"), argv[1]);297298object_array_remove_duplicates(&revs.pending);299300for (i = 0; i < revs.pending.nr; i++) {301struct object_array_entry *e = revs.pending.objects + i;302unsigned char sha1[20];303char *ref;304const char *display_ref;305int flag;306307if (e->item->flags & UNINTERESTING)308continue;309if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)310continue;311if (read_ref_full(e->name, sha1, 1, &flag))312flag = 0;313display_ref = (flag & REF_ISSYMREF) ? e->name : ref;314315if (e->item->type == OBJ_TAG &&316!is_tag_in_date_range(e->item, &revs)) {317e->item->flags |= UNINTERESTING;318continue;319}320321/*322* Make sure the refs we wrote out is correct; --max-count and323* other limiting options could have prevented all the tips324* from getting output.325*326* Non commit objects such as tags and blobs do not have327* this issue as they are not affected by those extra328* constraints.329*/330if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {331warning(_("ref '%s' is excluded by the rev-list options"),332e->name);333free(ref);334continue;335}336/*337* If you run "git bundle create bndl v1.0..v2.0", the338* name of the positive ref is "v2.0" but that is the339* commit that is referenced by the tag, and not the tag340* itself.341*/342if (hashcmp(sha1, e->item->sha1)) {343/*344* Is this the positive end of a range expressed345* in terms of a tag (e.g. v2.0 from the range346* "v1.0..v2.0")?347*/348struct commit *one = lookup_commit_reference(sha1);349struct object *obj;350351if (e->item == &(one->object)) {352/*353* Need to include e->name as an354* independent ref to the pack-objects355* input, so that the tag is included356* in the output; otherwise we would357* end up triggering "empty bundle"358* error.359*/360obj = parse_object(sha1);361obj->flags |= SHOWN;362add_pending_object(&revs, obj, e->name);363}364free(ref);365continue;366}367368ref_count++;369write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);370write_or_die(bundle_fd, " ", 1);371write_or_die(bundle_fd, display_ref, strlen(display_ref));372write_or_die(bundle_fd, "\n", 1);373free(ref);374}375if (!ref_count)376die(_("Refusing to create empty bundle."));377378/* end header */379write_or_die(bundle_fd, "\n", 1);380381/* write pack */382argv_pack[0] = "pack-objects";383argv_pack[1] = "--all-progress-implied";384argv_pack[2] = "--stdout";385argv_pack[3] = "--thin";386argv_pack[4] = "--delta-base-offset";387argv_pack[5] = NULL;388memset(&rls, 0, sizeof(rls));389rls.argv = argv_pack;390rls.in = -1;391rls.out = bundle_fd;392rls.git_cmd = 1;393if (start_command(&rls))394return error(_("Could not spawn pack-objects"));395396/*397* start_command closed bundle_fd if it was > 1398* so set the lock fd to -1 so commit_lock_file()399* won't fail trying to close it.400*/401lock.fd = -1;402403for (i = 0; i < revs.pending.nr; i++) {404struct object *object = revs.pending.objects[i].item;405if (object->flags & UNINTERESTING)406write_or_die(rls.in, "^", 1);407write_or_die(rls.in, sha1_to_hex(object->sha1), 40);408write_or_die(rls.in, "\n", 1);409}410close(rls.in);411if (finish_command(&rls))412return error(_("pack-objects died"));413if (!bundle_to_stdout) {414if (commit_lock_file(&lock))415die_errno(_("cannot create '%s'"), path);416}417return 0;418}419420int unbundle(struct bundle_header *header, int bundle_fd, int flags)421{422const char *argv_index_pack[] = {"index-pack",423"--fix-thin", "--stdin", NULL, NULL};424struct child_process ip;425426if (flags & BUNDLE_VERBOSE)427argv_index_pack[3] = "-v";428429if (verify_bundle(header, 0))430return -1;431memset(&ip, 0, sizeof(ip));432ip.argv = argv_index_pack;433ip.in = bundle_fd;434ip.no_stdout = 1;435ip.git_cmd = 1;436if (run_command(&ip))437return error(_("index-pack died"));438return 0;439}