Merge branch 'jc/maint-apply-match-beginning'
[git] / builtin-pack-refs.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "refs.h"
4 #include "object.h"
5 #include "tag.h"
6 #include "parse-options.h"
7
8 struct ref_to_prune {
9         struct ref_to_prune *next;
10         unsigned char sha1[20];
11         char name[FLEX_ARRAY];
12 };
13
14 #define PACK_REFS_PRUNE 0x0001
15 #define PACK_REFS_ALL   0x0002
16
17 struct pack_refs_cb_data {
18         unsigned int flags;
19         struct ref_to_prune *ref_to_prune;
20         FILE *refs_file;
21 };
22
23 static int do_not_prune(int flags)
24 {
25         /* If it is already packed or if it is a symref,
26          * do not prune it.
27          */
28         return (flags & (REF_ISSYMREF|REF_ISPACKED));
29 }
30
31 static int handle_one_ref(const char *path, const unsigned char *sha1,
32                           int flags, void *cb_data)
33 {
34         struct pack_refs_cb_data *cb = cb_data;
35         int is_tag_ref;
36
37         /* Do not pack the symbolic refs */
38         if ((flags & REF_ISSYMREF))
39                 return 0;
40         is_tag_ref = !prefixcmp(path, "refs/tags/");
41
42         /* ALWAYS pack refs that were already packed or are tags */
43         if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
44                 return 0;
45
46         fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
47         if (is_tag_ref) {
48                 struct object *o = parse_object(sha1);
49                 if (o->type == OBJ_TAG) {
50                         o = deref_tag(o, path, 0);
51                         if (o)
52                                 fprintf(cb->refs_file, "^%s\n",
53                                         sha1_to_hex(o->sha1));
54                 }
55         }
56
57         if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
58                 int namelen = strlen(path) + 1;
59                 struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
60                 hashcpy(n->sha1, sha1);
61                 strcpy(n->name, path);
62                 n->next = cb->ref_to_prune;
63                 cb->ref_to_prune = n;
64         }
65         return 0;
66 }
67
68 /* make sure nobody touched the ref, and unlink */
69 static void prune_ref(struct ref_to_prune *r)
70 {
71         struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
72
73         if (lock) {
74                 unlink(git_path("%s", r->name));
75                 unlock_ref(lock);
76         }
77 }
78
79 static void prune_refs(struct ref_to_prune *r)
80 {
81         while (r) {
82                 prune_ref(r);
83                 r = r->next;
84         }
85 }
86
87 static struct lock_file packed;
88
89 static int pack_refs(unsigned int flags)
90 {
91         int fd;
92         struct pack_refs_cb_data cbdata;
93
94         memset(&cbdata, 0, sizeof(cbdata));
95         cbdata.flags = flags;
96
97         fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
98         cbdata.refs_file = fdopen(fd, "w");
99         if (!cbdata.refs_file)
100                 die("unable to create ref-pack file structure (%s)",
101                     strerror(errno));
102
103         /* perhaps other traits later as well */
104         fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
105
106         for_each_ref(handle_one_ref, &cbdata);
107         if (ferror(cbdata.refs_file))
108                 die("failed to write ref-pack file");
109         if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
110                 die("failed to write ref-pack file (%s)", strerror(errno));
111         /*
112          * Since the lock file was fdopen()'ed and then fclose()'ed above,
113          * assign -1 to the lock file descriptor so that commit_lock_file()
114          * won't try to close() it.
115          */
116         packed.fd = -1;
117         if (commit_lock_file(&packed) < 0)
118                 die("unable to overwrite old ref-pack file (%s)", strerror(errno));
119         if (cbdata.flags & PACK_REFS_PRUNE)
120                 prune_refs(cbdata.ref_to_prune);
121         return 0;
122 }
123
124 static char const * const pack_refs_usage[] = {
125         "git-pack-refs [options]",
126         NULL
127 };
128
129 int cmd_pack_refs(int argc, const char **argv, const char *prefix)
130 {
131         unsigned int flags = PACK_REFS_PRUNE;
132         struct option opts[] = {
133                 OPT_BIT(0, "all",   &flags, "pack everything", PACK_REFS_ALL),
134                 OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
135                 OPT_END(),
136         };
137         if (parse_options(argc, argv, opts, pack_refs_usage, 0))
138                 usage_with_options(pack_refs_usage, opts);
139         return pack_refs(flags);
140 }