index-pack: fix race condition with duplicate bases
[git] / shallow.c
1 #include "cache.h"
2 #include "commit.h"
3 #include "tag.h"
4 #include "pkt-line.h"
5
6 static int is_shallow = -1;
7 static struct stat shallow_stat;
8 static char *alternate_shallow_file;
9
10 void set_alternate_shallow_file(const char *path)
11 {
12         if (is_shallow != -1)
13                 die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
14         free(alternate_shallow_file);
15         alternate_shallow_file = path ? xstrdup(path) : NULL;
16 }
17
18 int register_shallow(const unsigned char *sha1)
19 {
20         struct commit_graft *graft =
21                 xmalloc(sizeof(struct commit_graft));
22         struct commit *commit = lookup_commit(sha1);
23
24         hashcpy(graft->sha1, sha1);
25         graft->nr_parent = -1;
26         if (commit && commit->object.parsed)
27                 commit->parents = NULL;
28         return register_commit_graft(graft, 0);
29 }
30
31 int is_repository_shallow(void)
32 {
33         FILE *fp;
34         char buf[1024];
35         const char *path = alternate_shallow_file;
36
37         if (is_shallow >= 0)
38                 return is_shallow;
39
40         if (!path)
41                 path = git_path("shallow");
42         /*
43          * fetch-pack sets '--shallow-file ""' as an indicator that no
44          * shallow file should be used. We could just open it and it
45          * will likely fail. But let's do an explicit check instead.
46          */
47         if (!*path ||
48             stat(path, &shallow_stat) ||
49             (fp = fopen(path, "r")) == NULL) {
50                 is_shallow = 0;
51                 return is_shallow;
52         }
53         is_shallow = 1;
54
55         while (fgets(buf, sizeof(buf), fp)) {
56                 unsigned char sha1[20];
57                 if (get_sha1_hex(buf, sha1))
58                         die("bad shallow line: %s", buf);
59                 register_shallow(sha1);
60         }
61         fclose(fp);
62         return is_shallow;
63 }
64
65 struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
66                 int shallow_flag, int not_shallow_flag)
67 {
68         int i = 0, cur_depth = 0;
69         struct commit_list *result = NULL;
70         struct object_array stack = OBJECT_ARRAY_INIT;
71         struct commit *commit = NULL;
72
73         while (commit || i < heads->nr || stack.nr) {
74                 struct commit_list *p;
75                 if (!commit) {
76                         if (i < heads->nr) {
77                                 commit = (struct commit *)
78                                         deref_tag(heads->objects[i++].item, NULL, 0);
79                                 if (!commit || commit->object.type != OBJ_COMMIT) {
80                                         commit = NULL;
81                                         continue;
82                                 }
83                                 if (!commit->util)
84                                         commit->util = xmalloc(sizeof(int));
85                                 *(int *)commit->util = 0;
86                                 cur_depth = 0;
87                         } else {
88                                 commit = (struct commit *)
89                                         stack.objects[--stack.nr].item;
90                                 cur_depth = *(int *)commit->util;
91                         }
92                 }
93                 if (parse_commit(commit))
94                         die("invalid commit");
95                 cur_depth++;
96                 if (cur_depth >= depth) {
97                         commit_list_insert(commit, &result);
98                         commit->object.flags |= shallow_flag;
99                         commit = NULL;
100                         continue;
101                 }
102                 commit->object.flags |= not_shallow_flag;
103                 for (p = commit->parents, commit = NULL; p; p = p->next) {
104                         if (!p->item->util) {
105                                 int *pointer = xmalloc(sizeof(int));
106                                 p->item->util = pointer;
107                                 *pointer =  cur_depth;
108                         } else {
109                                 int *pointer = p->item->util;
110                                 if (cur_depth >= *pointer)
111                                         continue;
112                                 *pointer = cur_depth;
113                         }
114                         if (p->next)
115                                 add_object_array(&p->item->object,
116                                                 NULL, &stack);
117                         else {
118                                 commit = p->item;
119                                 cur_depth = *(int *)commit->util;
120                         }
121                 }
122         }
123
124         return result;
125 }
126
127 void check_shallow_file_for_update(void)
128 {
129         struct stat st;
130
131         if (!is_shallow)
132                 return;
133         else if (is_shallow == -1)
134                 die("BUG: shallow must be initialized by now");
135
136         if (stat(git_path("shallow"), &st))
137                 die("shallow file was removed during fetch");
138         else if (st.st_mtime != shallow_stat.st_mtime
139 #ifdef USE_NSEC
140                  || ST_MTIME_NSEC(st) != ST_MTIME_NSEC(shallow_stat)
141 #endif
142                    )
143                 die("shallow file was changed during fetch");
144 }
145
146 struct write_shallow_data {
147         struct strbuf *out;
148         int use_pack_protocol;
149         int count;
150 };
151
152 static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
153 {
154         struct write_shallow_data *data = cb_data;
155         const char *hex = sha1_to_hex(graft->sha1);
156         if (graft->nr_parent != -1)
157                 return 0;
158         data->count++;
159         if (data->use_pack_protocol)
160                 packet_buf_write(data->out, "shallow %s", hex);
161         else {
162                 strbuf_addstr(data->out, hex);
163                 strbuf_addch(data->out, '\n');
164         }
165         return 0;
166 }
167
168 int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
169 {
170         struct write_shallow_data data;
171         data.out = out;
172         data.use_pack_protocol = use_pack_protocol;
173         data.count = 0;
174         for_each_commit_graft(write_one_shallow, &data);
175         return data.count;
176 }
177
178 char *setup_temporary_shallow(void)
179 {
180         struct strbuf sb = STRBUF_INIT;
181         int fd;
182
183         if (write_shallow_commits(&sb, 0)) {
184                 struct strbuf path = STRBUF_INIT;
185                 strbuf_addstr(&path, git_path("shallow_XXXXXX"));
186                 fd = xmkstemp(path.buf);
187                 if (write_in_full(fd, sb.buf, sb.len) != sb.len)
188                         die_errno("failed to write to %s",
189                                   path.buf);
190                 close(fd);
191                 strbuf_release(&sb);
192                 return strbuf_detach(&path, NULL);
193         }
194         /*
195          * is_repository_shallow() sees empty string as "no shallow
196          * file".
197          */
198         return xstrdup("");
199 }
200
201 void setup_alternate_shallow(struct lock_file *shallow_lock,
202                              const char **alternate_shallow_file)
203 {
204         struct strbuf sb = STRBUF_INIT;
205         int fd;
206
207         check_shallow_file_for_update();
208         fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
209                                        LOCK_DIE_ON_ERROR);
210         if (write_shallow_commits(&sb, 0)) {
211                 if (write_in_full(fd, sb.buf, sb.len) != sb.len)
212                         die_errno("failed to write to %s",
213                                   shallow_lock->filename);
214                 *alternate_shallow_file = shallow_lock->filename;
215         } else
216                 /*
217                  * is_repository_shallow() sees empty string as "no
218                  * shallow file".
219                  */
220                 *alternate_shallow_file = "";
221         strbuf_release(&sb);
222 }