]>
Commit | Line | Data |
---|---|---|
3315782c | 1 | /* |
5e0de328 | 2 | * Copyright (C) 2009-2012 the libgit2 contributors |
3315782c | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
3315782c | 6 | */ |
e802d8cc | 7 | #include <stdarg.h> |
7784bcbb | 8 | #include <ctype.h> |
3315782c | 9 | |
44908fe7 | 10 | #include "git2/object.h" |
d12299fe | 11 | |
3315782c VM |
12 | #include "common.h" |
13 | #include "repository.h" | |
14 | #include "commit.h" | |
15 | #include "tag.h" | |
237da401 | 16 | #include "blob.h" |
6fd195d7 | 17 | #include "fileops.h" |
b22d1479 | 18 | #include "config.h" |
9282e921 | 19 | #include "refs.h" |
20 | ||
f2d6a23a | 21 | #define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" |
22 | #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" | |
40c44d2f | 23 | |
7784bcbb | 24 | #define GIT_FILE_CONTENT_PREFIX "gitdir:" |
6a01b6bd | 25 | |
28990938 | 26 | #define GIT_BRANCH_MASTER "master" |
27 | ||
29e948de | 28 | #define GIT_REPO_VERSION 0 |
691aa968 | 29 | |
9462c471 VM |
30 | static void drop_odb(git_repository *repo) |
31 | { | |
32 | if (repo->_odb != NULL) { | |
33 | GIT_REFCOUNT_OWN(repo->_odb, NULL); | |
34 | git_odb_free(repo->_odb); | |
35 | repo->_odb = NULL; | |
eb2f3b47 | 36 | } |
9462c471 | 37 | } |
691aa968 | 38 | |
9462c471 VM |
39 | static void drop_config(git_repository *repo) |
40 | { | |
41 | if (repo->_config != NULL) { | |
42 | GIT_REFCOUNT_OWN(repo->_config, NULL); | |
43 | git_config_free(repo->_config); | |
44 | repo->_config = NULL; | |
eb2f3b47 | 45 | } |
f2c25d18 VM |
46 | |
47 | git_repository__cvar_cache_clear(repo); | |
691aa968 VM |
48 | } |
49 | ||
9462c471 | 50 | static void drop_index(git_repository *repo) |
6fd195d7 | 51 | { |
9462c471 VM |
52 | if (repo->_index != NULL) { |
53 | GIT_REFCOUNT_OWN(repo->_index, NULL); | |
54 | git_index_free(repo->_index); | |
55 | repo->_index = NULL; | |
56 | } | |
e0011be3 VM |
57 | } |
58 | ||
9462c471 | 59 | void git_repository_free(git_repository *repo) |
e0011be3 | 60 | { |
9462c471 VM |
61 | if (repo == NULL) |
62 | return; | |
6fd195d7 | 63 | |
9462c471 VM |
64 | git_cache_free(&repo->objects); |
65 | git_repository__refcache_free(&repo->references); | |
73b51450 | 66 | git_attr_cache_flush(repo); |
bfc9ca59 | 67 | git_submodule_config_free(repo); |
6fd195d7 | 68 | |
9462c471 VM |
69 | git__free(repo->path_repository); |
70 | git__free(repo->workdir); | |
6fd195d7 | 71 | |
9462c471 VM |
72 | drop_config(repo); |
73 | drop_index(repo); | |
74 | drop_odb(repo); | |
75 | ||
76 | git__free(repo); | |
6fd195d7 VM |
77 | } |
78 | ||
9462c471 VM |
79 | /* |
80 | * Git repository open methods | |
81 | * | |
82 | * Open a repository object from its path | |
83 | */ | |
cb8a7961 | 84 | static bool valid_repository_path(git_buf *repository_path) |
5ad739e8 | 85 | { |
97769280 | 86 | /* Check OBJECTS_DIR first, since it will generate the longest path name */ |
1a481123 | 87 | if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) |
cb8a7961 | 88 | return false; |
5ad739e8 | 89 | |
97769280 | 90 | /* Ensure HEAD file exists */ |
1a481123 | 91 | if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) |
cb8a7961 | 92 | return false; |
5ad739e8 | 93 | |
1a481123 | 94 | if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) |
cb8a7961 | 95 | return false; |
5ad739e8 | 96 | |
cb8a7961 | 97 | return true; |
5ad739e8 VM |
98 | } |
99 | ||
51d00446 | 100 | static git_repository *repository_alloc(void) |
3315782c VM |
101 | { |
102 | git_repository *repo = git__malloc(sizeof(git_repository)); | |
103 | if (!repo) | |
104 | return NULL; | |
105 | ||
106 | memset(repo, 0x0, sizeof(git_repository)); | |
107 | ||
cb8a7961 | 108 | if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) { |
3286c408 | 109 | git__free(repo); |
81201a4c | 110 | return NULL; |
111 | } | |
3315782c | 112 | |
f2c25d18 VM |
113 | /* set all the entries in the cvar cache to `unset` */ |
114 | git_repository__cvar_cache_clear(repo); | |
115 | ||
6fd195d7 VM |
116 | return repo; |
117 | } | |
118 | ||
9462c471 | 119 | static int load_config_data(git_repository *repo) |
ec3c7a16 | 120 | { |
cb8a7961 | 121 | int is_bare; |
9462c471 | 122 | git_config *config; |
ec3c7a16 | 123 | |
cb8a7961 VM |
124 | if (git_repository_config__weakptr(&config, repo) < 0) |
125 | return -1; | |
ec3c7a16 | 126 | |
d3e9c4a5 | 127 | /* Try to figure out if it's bare, default to non-bare if it's not set */ |
29e948de | 128 | if (git_config_get_bool(&is_bare, config, "core.bare") < 0) |
d3e9c4a5 CMN |
129 | repo->is_bare = 0; |
130 | else | |
131 | repo->is_bare = is_bare; | |
c94785a9 | 132 | |
cb8a7961 | 133 | return 0; |
9462c471 | 134 | } |
ec3c7a16 | 135 | |
7784bcbb | 136 | static int load_workdir(git_repository *repo, git_buf *parent_path) |
9462c471 | 137 | { |
7784bcbb RB |
138 | int error; |
139 | git_config *config; | |
140 | const char *worktree; | |
141 | git_buf worktree_buf = GIT_BUF_INIT; | |
ec3c7a16 | 142 | |
97769280 | 143 | if (repo->is_bare) |
cb8a7961 | 144 | return 0; |
ec3c7a16 | 145 | |
7784bcbb | 146 | if (git_repository_config__weakptr(&config, repo) < 0) |
cb8a7961 | 147 | return -1; |
ec3c7a16 | 148 | |
29e948de | 149 | error = git_config_get_string(&worktree, config, "core.worktree"); |
7784bcbb RB |
150 | if (!error && worktree != NULL) |
151 | repo->workdir = git__strdup(worktree); | |
904b67e6 | 152 | else if (error != GIT_ENOTFOUND) |
7784bcbb RB |
153 | return error; |
154 | else { | |
155 | giterr_clear(); | |
156 | ||
157 | if (parent_path && git_path_isdir(parent_path->ptr)) | |
158 | repo->workdir = git_buf_detach(parent_path); | |
159 | else { | |
160 | git_path_dirname_r(&worktree_buf, repo->path_repository); | |
161 | git_path_to_dir(&worktree_buf); | |
162 | repo->workdir = git_buf_detach(&worktree_buf); | |
163 | } | |
164 | } | |
165 | ||
166 | GITERR_CHECK_ALLOC(repo->workdir); | |
97769280 | 167 | |
cb8a7961 | 168 | return 0; |
ec3c7a16 VM |
169 | } |
170 | ||
7784bcbb RB |
171 | /* |
172 | * This function returns furthest offset into path where a ceiling dir | |
173 | * is found, so we can stop processing the path at that point. | |
174 | * | |
175 | * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on | |
176 | * the stack could remove directories name limits, but at the cost of doing | |
177 | * repeated malloc/frees inside the loop below, so let's not do it now. | |
178 | */ | |
179 | static int find_ceiling_dir_offset( | |
180 | const char *path, | |
181 | const char *ceiling_directories) | |
182 | { | |
183 | char buf[GIT_PATH_MAX + 1]; | |
184 | char buf2[GIT_PATH_MAX + 1]; | |
185 | const char *ceil, *sep; | |
44ef8b1b | 186 | size_t len, max_len = 0, min_len; |
7784bcbb RB |
187 | |
188 | assert(path); | |
189 | ||
44ef8b1b | 190 | min_len = (size_t)(git_path_root(path) + 1); |
7784bcbb RB |
191 | |
192 | if (ceiling_directories == NULL || min_len == 0) | |
44ef8b1b | 193 | return (int)min_len; |
7784bcbb RB |
194 | |
195 | for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { | |
196 | for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); | |
197 | len = sep - ceil; | |
198 | ||
44ef8b1b | 199 | if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1) |
7784bcbb RB |
200 | continue; |
201 | ||
202 | strncpy(buf, ceil, len); | |
203 | buf[len] = '\0'; | |
204 | ||
205 | if (p_realpath(buf, buf2) == NULL) | |
206 | continue; | |
207 | ||
208 | len = strlen(buf2); | |
209 | if (len > 0 && buf2[len-1] == '/') | |
210 | buf[--len] = '\0'; | |
211 | ||
212 | if (!strncmp(path, buf2, len) && | |
213 | path[len] == '/' && | |
214 | len > max_len) | |
215 | { | |
216 | max_len = len; | |
217 | } | |
218 | } | |
219 | ||
44ef8b1b | 220 | return (int)(max_len <= min_len ? min_len : max_len); |
7784bcbb RB |
221 | } |
222 | ||
223 | /* | |
224 | * Read the contents of `file_path` and set `path_out` to the repo dir that | |
225 | * it points to. Before calling, set `path_out` to the base directory that | |
226 | * should be used if the contents of `file_path` are a relative path. | |
227 | */ | |
228 | static int read_gitfile(git_buf *path_out, const char *file_path) | |
229 | { | |
230 | int error = 0; | |
231 | git_buf file = GIT_BUF_INIT; | |
232 | size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); | |
233 | ||
234 | assert(path_out && file_path); | |
235 | ||
236 | if (git_futils_readbuffer(&file, file_path) < 0) | |
237 | return -1; | |
238 | ||
239 | git_buf_rtrim(&file); | |
240 | ||
241 | if (file.size <= prefix_len || | |
242 | memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) | |
243 | { | |
244 | giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path); | |
245 | error = -1; | |
246 | } | |
247 | else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { | |
248 | const char *gitlink = ((const char *)file.ptr) + prefix_len; | |
0f49200c | 249 | while (*gitlink && git__isspace(*gitlink)) gitlink++; |
7784bcbb RB |
250 | error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); |
251 | } | |
252 | ||
253 | git_buf_free(&file); | |
254 | return error; | |
255 | } | |
256 | ||
257 | static int find_repo( | |
258 | git_buf *repo_path, | |
259 | git_buf *parent_path, | |
260 | const char *start_path, | |
261 | uint32_t flags, | |
262 | const char *ceiling_dirs) | |
691aa968 | 263 | { |
7784bcbb RB |
264 | int error; |
265 | git_buf path = GIT_BUF_INIT; | |
266 | struct stat st; | |
267 | dev_t initial_device = 0; | |
268 | bool try_with_dot_git = false; | |
269 | int ceiling_offset; | |
270 | ||
271 | git_buf_free(repo_path); | |
272 | ||
273 | if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0) | |
274 | return error; | |
275 | ||
276 | ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); | |
277 | ||
278 | if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) | |
279 | return error; | |
280 | ||
fa6420f7 | 281 | while (!error && !git_buf_len(repo_path)) { |
7784bcbb RB |
282 | if (p_stat(path.ptr, &st) == 0) { |
283 | /* check that we have not crossed device boundaries */ | |
284 | if (initial_device == 0) | |
285 | initial_device = st.st_dev; | |
286 | else if (st.st_dev != initial_device && | |
287 | (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0) | |
288 | break; | |
289 | ||
290 | if (S_ISDIR(st.st_mode)) { | |
291 | if (valid_repository_path(&path)) { | |
292 | git_path_to_dir(&path); | |
293 | git_buf_set(repo_path, path.ptr, path.size); | |
294 | break; | |
295 | } | |
296 | } | |
297 | else if (S_ISREG(st.st_mode)) { | |
298 | git_buf repo_link = GIT_BUF_INIT; | |
299 | ||
300 | if (!(error = read_gitfile(&repo_link, path.ptr))) { | |
301 | if (valid_repository_path(&repo_link)) | |
302 | git_buf_swap(repo_path, &repo_link); | |
146f5c75 CMN |
303 | |
304 | git_buf_free(&repo_link); | |
7784bcbb RB |
305 | break; |
306 | } | |
307 | git_buf_free(&repo_link); | |
308 | } | |
309 | } | |
310 | ||
311 | /* move up one directory level */ | |
312 | if (git_path_dirname_r(&path, path.ptr) < 0) { | |
313 | error = -1; | |
314 | break; | |
315 | } | |
316 | ||
317 | if (try_with_dot_git) { | |
318 | /* if we tried original dir with and without .git AND either hit | |
319 | * directory ceiling or NO_SEARCH was requested, then be done. | |
320 | */ | |
321 | if (path.ptr[ceiling_offset] == '\0' || | |
322 | (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0) | |
323 | break; | |
324 | /* otherwise look first for .git item */ | |
325 | error = git_buf_joinpath(&path, path.ptr, DOT_GIT); | |
326 | } | |
327 | try_with_dot_git = !try_with_dot_git; | |
328 | } | |
329 | ||
330 | if (!error && parent_path != NULL) { | |
fa6420f7 | 331 | if (!git_buf_len(repo_path)) |
7784bcbb RB |
332 | git_buf_clear(parent_path); |
333 | else { | |
334 | git_path_dirname_r(parent_path, path.ptr); | |
335 | git_path_to_dir(parent_path); | |
336 | } | |
337 | if (git_buf_oom(parent_path)) | |
338 | return -1; | |
339 | } | |
340 | ||
341 | git_buf_free(&path); | |
342 | ||
fa6420f7 | 343 | if (!git_buf_len(repo_path) && !error) { |
cb8a7961 | 344 | giterr_set(GITERR_REPOSITORY, |
7784bcbb | 345 | "Could not find repository from '%s'", start_path); |
904b67e6 | 346 | error = GIT_ENOTFOUND; |
97769280 | 347 | } |
691aa968 | 348 | |
7784bcbb RB |
349 | return error; |
350 | } | |
351 | ||
352 | int git_repository_open_ext( | |
353 | git_repository **repo_ptr, | |
354 | const char *start_path, | |
355 | uint32_t flags, | |
356 | const char *ceiling_dirs) | |
357 | { | |
358 | int error; | |
359 | git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; | |
360 | git_repository *repo; | |
361 | ||
362 | *repo_ptr = NULL; | |
363 | ||
364 | if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) | |
365 | return error; | |
366 | ||
e52ed7a5 | 367 | repo = repository_alloc(); |
cb8a7961 | 368 | GITERR_CHECK_ALLOC(repo); |
691aa968 | 369 | |
7784bcbb | 370 | repo->path_repository = git_buf_detach(&path); |
cb8a7961 | 371 | GITERR_CHECK_ALLOC(repo->path_repository); |
691aa968 | 372 | |
7784bcbb RB |
373 | if ((error = load_config_data(repo)) < 0 || |
374 | (error = load_workdir(repo, &parent)) < 0) | |
375 | { | |
376 | git_repository_free(repo); | |
377 | return error; | |
378 | } | |
691aa968 | 379 | |
146f5c75 | 380 | git_buf_free(&parent); |
7784bcbb | 381 | *repo_ptr = repo; |
cb8a7961 | 382 | return 0; |
7784bcbb | 383 | } |
97769280 | 384 | |
7784bcbb RB |
385 | int git_repository_open(git_repository **repo_out, const char *path) |
386 | { | |
387 | return git_repository_open_ext( | |
388 | repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); | |
389 | } | |
390 | ||
391 | int git_repository_discover( | |
392 | char *repository_path, | |
393 | size_t size, | |
394 | const char *start_path, | |
395 | int across_fs, | |
396 | const char *ceiling_dirs) | |
397 | { | |
398 | git_buf path = GIT_BUF_INIT; | |
399 | uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; | |
464cf248 | 400 | int error; |
7784bcbb RB |
401 | |
402 | assert(start_path && repository_path && size > 0); | |
403 | ||
404 | *repository_path = '\0'; | |
405 | ||
464cf248 | 406 | if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0) |
904b67e6 | 407 | return error != GIT_ENOTFOUND ? -1 : error; |
7784bcbb RB |
408 | |
409 | if (size < (size_t)(path.size + 1)) { | |
410 | giterr_set(GITERR_REPOSITORY, | |
411 | "The given buffer is too long to store the discovered path"); | |
412 | git_buf_free(&path); | |
413 | return -1; | |
414 | } | |
415 | ||
416 | /* success: we discovered a repository */ | |
417 | git_buf_copy_cstr(repository_path, size, &path); | |
418 | git_buf_free(&path); | |
419 | return 0; | |
691aa968 VM |
420 | } |
421 | ||
9462c471 | 422 | static int load_config( |
7784bcbb RB |
423 | git_config **out, |
424 | git_repository *repo, | |
425 | const char *global_config_path, | |
426 | const char *system_config_path) | |
b22d1479 | 427 | { |
97769280 | 428 | git_buf config_path = GIT_BUF_INIT; |
9462c471 | 429 | git_config *cfg = NULL; |
b22d1479 | 430 | |
9462c471 | 431 | assert(repo && out); |
07ff8817 | 432 | |
cb8a7961 VM |
433 | if (git_config_new(&cfg) < 0) |
434 | return -1; | |
b22d1479 | 435 | |
cb8a7961 VM |
436 | if (git_buf_joinpath( |
437 | &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) | |
438 | goto on_error; | |
97769280 | 439 | |
cb8a7961 VM |
440 | if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) |
441 | goto on_error; | |
442 | ||
443 | git_buf_free(&config_path); | |
b22d1479 | 444 | |
40fe5fbe | 445 | if (global_config_path != NULL) { |
cb8a7961 VM |
446 | if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) |
447 | goto on_error; | |
b22d1479 CMN |
448 | } |
449 | ||
07ff8817 | 450 | if (system_config_path != NULL) { |
cb8a7961 VM |
451 | if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) |
452 | goto on_error; | |
9ba9e513 CMN |
453 | } |
454 | ||
9462c471 | 455 | *out = cfg; |
cb8a7961 | 456 | return 0; |
b22d1479 | 457 | |
cb8a7961 VM |
458 | on_error: |
459 | git_buf_free(&config_path); | |
9462c471 VM |
460 | git_config_free(cfg); |
461 | *out = NULL; | |
cb8a7961 | 462 | return -1; |
b22d1479 CMN |
463 | } |
464 | ||
9462c471 | 465 | int git_repository_config__weakptr(git_config **out, git_repository *repo) |
40fe5fbe | 466 | { |
9462c471 | 467 | if (repo->_config == NULL) { |
97769280 | 468 | git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; |
cb8a7961 | 469 | int res; |
9462c471 VM |
470 | |
471 | const char *global_config_path = NULL; | |
472 | const char *system_config_path = NULL; | |
40fe5fbe | 473 | |
cb8a7961 | 474 | if (git_config_find_global_r(&global_buf) == 0) |
97769280 | 475 | global_config_path = global_buf.ptr; |
40fe5fbe | 476 | |
cb8a7961 | 477 | if (git_config_find_system_r(&system_buf) == 0) |
97769280 | 478 | system_config_path = system_buf.ptr; |
40fe5fbe | 479 | |
cb8a7961 | 480 | res = load_config(&repo->_config, repo, global_config_path, system_config_path); |
97769280 RB |
481 | |
482 | git_buf_free(&global_buf); | |
483 | git_buf_free(&system_buf); | |
484 | ||
cb8a7961 VM |
485 | if (res < 0) |
486 | return -1; | |
9462c471 VM |
487 | |
488 | GIT_REFCOUNT_OWN(repo->_config, repo); | |
489 | } | |
40fe5fbe | 490 | |
9462c471 | 491 | *out = repo->_config; |
cb8a7961 | 492 | return 0; |
40fe5fbe CMN |
493 | } |
494 | ||
9462c471 | 495 | int git_repository_config(git_config **out, git_repository *repo) |
fd0574e5 | 496 | { |
cb8a7961 VM |
497 | if (git_repository_config__weakptr(out, repo) < 0) |
498 | return -1; | |
fd0574e5 | 499 | |
cb8a7961 VM |
500 | GIT_REFCOUNT_INC(*out); |
501 | return 0; | |
9462c471 VM |
502 | } |
503 | ||
504 | void git_repository_set_config(git_repository *repo, git_config *config) | |
505 | { | |
506 | assert(repo && config); | |
507 | ||
508 | drop_config(repo); | |
509 | ||
510 | repo->_config = config; | |
511 | GIT_REFCOUNT_OWN(repo->_config, repo); | |
512 | } | |
513 | ||
514 | int git_repository_odb__weakptr(git_odb **out, git_repository *repo) | |
515 | { | |
516 | assert(repo && out); | |
517 | ||
518 | if (repo->_odb == NULL) { | |
97769280 | 519 | git_buf odb_path = GIT_BUF_INIT; |
cb8a7961 | 520 | int res; |
9462c471 | 521 | |
cb8a7961 VM |
522 | if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0) |
523 | return -1; | |
9462c471 | 524 | |
cb8a7961 | 525 | res = git_odb_open(&repo->_odb, odb_path.ptr); |
97769280 | 526 | git_buf_free(&odb_path); /* done with path */ |
cb8a7961 VM |
527 | |
528 | if (res < 0) | |
529 | return -1; | |
9462c471 VM |
530 | |
531 | GIT_REFCOUNT_OWN(repo->_odb, repo); | |
532 | } | |
fd0574e5 | 533 | |
9462c471 | 534 | *out = repo->_odb; |
cb8a7961 | 535 | return 0; |
fd0574e5 RG |
536 | } |
537 | ||
9462c471 | 538 | int git_repository_odb(git_odb **out, git_repository *repo) |
6fd195d7 | 539 | { |
cb8a7961 VM |
540 | if (git_repository_odb__weakptr(out, repo) < 0) |
541 | return -1; | |
1795f879 | 542 | |
cb8a7961 VM |
543 | GIT_REFCOUNT_INC(*out); |
544 | return 0; | |
9462c471 | 545 | } |
6fd195d7 | 546 | |
9462c471 VM |
547 | void git_repository_set_odb(git_repository *repo, git_odb *odb) |
548 | { | |
549 | assert(repo && odb); | |
3315782c | 550 | |
9462c471 | 551 | drop_odb(repo); |
1795f879 | 552 | |
9462c471 VM |
553 | repo->_odb = odb; |
554 | GIT_REFCOUNT_OWN(repo->_odb, repo); | |
baf861a5 | 555 | GIT_REFCOUNT_INC(odb); |
9462c471 VM |
556 | } |
557 | ||
558 | int git_repository_index__weakptr(git_index **out, git_repository *repo) | |
559 | { | |
560 | assert(out && repo); | |
561 | ||
9462c471 | 562 | if (repo->_index == NULL) { |
cb8a7961 | 563 | int res; |
97769280 | 564 | git_buf index_path = GIT_BUF_INIT; |
9462c471 | 565 | |
cb8a7961 VM |
566 | if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0) |
567 | return -1; | |
9462c471 | 568 | |
cb8a7961 | 569 | res = git_index_open(&repo->_index, index_path.ptr); |
97769280 | 570 | git_buf_free(&index_path); /* done with path */ |
cb8a7961 VM |
571 | |
572 | if (res < 0) | |
573 | return -1; | |
9462c471 VM |
574 | |
575 | GIT_REFCOUNT_OWN(repo->_index, repo); | |
da825c92 RB |
576 | |
577 | if (git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER) < 0) | |
578 | return -1; | |
9462c471 VM |
579 | } |
580 | ||
9462c471 | 581 | *out = repo->_index; |
cb8a7961 | 582 | return 0; |
9462c471 | 583 | } |
1795f879 | 584 | |
9462c471 VM |
585 | int git_repository_index(git_index **out, git_repository *repo) |
586 | { | |
cb8a7961 VM |
587 | if (git_repository_index__weakptr(out, repo) < 0) |
588 | return -1; | |
9462c471 | 589 | |
cb8a7961 VM |
590 | GIT_REFCOUNT_INC(*out); |
591 | return 0; | |
3315782c VM |
592 | } |
593 | ||
9462c471 VM |
594 | void git_repository_set_index(git_repository *repo, git_index *index) |
595 | { | |
596 | assert(repo && index); | |
597 | ||
598 | drop_index(repo); | |
599 | ||
600 | repo->_index = index; | |
601 | GIT_REFCOUNT_OWN(repo->_index, repo); | |
c1aefb35 | 602 | GIT_REFCOUNT_INC(index); |
9462c471 VM |
603 | } |
604 | ||
5663e61a | 605 | static int check_repositoryformatversion(git_repository *repo) |
40c44d2f | 606 | { |
5663e61a | 607 | git_config *config; |
cb8a7961 | 608 | int version; |
5663e61a | 609 | |
cb8a7961 VM |
610 | if (git_repository_config__weakptr(&config, repo) < 0) |
611 | return -1; | |
5663e61a | 612 | |
29e948de | 613 | if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0) |
cb8a7961 | 614 | return -1; |
5663e61a | 615 | |
29e948de | 616 | if (GIT_REPO_VERSION < version) { |
cb8a7961 VM |
617 | giterr_set(GITERR_REPOSITORY, |
618 | "Unsupported repository version %d. Only versions up to %d are supported.", | |
29e948de | 619 | version, GIT_REPO_VERSION); |
cb8a7961 VM |
620 | return -1; |
621 | } | |
5663e61a | 622 | |
cb8a7961 | 623 | return 0; |
5663e61a | 624 | } |
625 | ||
626 | static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare) | |
627 | { | |
5663e61a | 628 | git_repository *repo = NULL; |
629 | ||
cb8a7961 | 630 | GIT_UNUSED(is_bare); |
5663e61a | 631 | |
cb8a7961 VM |
632 | if (git_repository_open(&repo, repository_path) < 0) |
633 | return -1; | |
634 | ||
635 | if (check_repositoryformatversion(repo) < 0) { | |
636 | git_repository_free(repo); | |
637 | return -1; | |
638 | } | |
5663e61a | 639 | |
640 | /* TODO: reinitialize the templates */ | |
641 | ||
642 | *repo_out = repo; | |
cb8a7961 | 643 | return 0; |
4b8e27c8 | 644 | } |
645 | ||
9462c471 | 646 | static int repo_init_createhead(const char *git_dir) |
e1f8cad0 | 647 | { |
97769280 | 648 | git_buf ref_path = GIT_BUF_INIT; |
9462c471 VM |
649 | git_filebuf ref = GIT_FILEBUF_INIT; |
650 | ||
cb8a7961 VM |
651 | if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || |
652 | git_filebuf_open(&ref, ref_path.ptr, 0) < 0 || | |
653 | git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 || | |
654 | git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) | |
655 | return -1; | |
9462c471 | 656 | |
97769280 | 657 | git_buf_free(&ref_path); |
cb8a7961 | 658 | return 0; |
9462c471 VM |
659 | } |
660 | ||
fac66990 | 661 | static bool is_chmod_supported(const char *file_path) |
662 | { | |
663 | struct stat st1, st2; | |
664 | static int _is_supported = -1; | |
665 | ||
666 | if (_is_supported > -1) | |
667 | return _is_supported; | |
668 | ||
669 | if (p_stat(file_path, &st1) < 0) | |
670 | return false; | |
671 | ||
672 | if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) | |
673 | return false; | |
674 | ||
675 | if (p_stat(file_path, &st2) < 0) | |
676 | return false; | |
677 | ||
678 | _is_supported = (st1.st_mode != st2.st_mode); | |
679 | return _is_supported; | |
680 | } | |
681 | ||
693b23c0 | 682 | static bool is_filesystem_case_insensitive(const char *gitdir_path) |
683 | { | |
684 | git_buf path = GIT_BUF_INIT; | |
685 | static int _is_insensitive = -1; | |
686 | ||
687 | if (_is_insensitive > -1) | |
688 | return _is_insensitive; | |
689 | ||
690 | if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0) | |
691 | goto cleanup; | |
692 | ||
693 | _is_insensitive = git_path_exists(git_buf_cstr(&path)); | |
694 | ||
695 | cleanup: | |
696 | git_buf_free(&path); | |
697 | return _is_insensitive; | |
698 | } | |
699 | ||
700 | static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) | |
9462c471 | 701 | { |
97769280 RB |
702 | git_buf cfg_path = GIT_BUF_INIT; |
703 | git_config *config = NULL; | |
9462c471 VM |
704 | |
705 | #define SET_REPO_CONFIG(type, name, val) {\ | |
cb8a7961 VM |
706 | if (git_config_set_##type(config, name, val) < 0) { \ |
707 | git_buf_free(&cfg_path); \ | |
708 | git_config_free(config); \ | |
709 | return -1; } \ | |
9462c471 VM |
710 | } |
711 | ||
cb8a7961 VM |
712 | if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) |
713 | return -1; | |
9462c471 | 714 | |
fac66990 | 715 | if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { |
cb8a7961 VM |
716 | git_buf_free(&cfg_path); |
717 | return -1; | |
718 | } | |
9462c471 VM |
719 | |
720 | SET_REPO_CONFIG(bool, "core.bare", is_bare); | |
29e948de | 721 | SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); |
fac66990 | 722 | SET_REPO_CONFIG(bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); |
693b23c0 | 723 | |
7623b1b6 | 724 | if (!is_bare) |
725 | SET_REPO_CONFIG(bool, "core.logallrefupdates", true); | |
726 | ||
693b23c0 | 727 | if (!is_reinit && is_filesystem_case_insensitive(git_dir)) |
728 | SET_REPO_CONFIG(bool, "core.ignorecase", true); | |
9462c471 VM |
729 | /* TODO: what other defaults? */ |
730 | ||
97769280 | 731 | git_buf_free(&cfg_path); |
9462c471 | 732 | git_config_free(config); |
cb8a7961 | 733 | return 0; |
d2d6912e | 734 | } |
e1f8cad0 | 735 | |
dc34da6e RB |
736 | #define GIT_HOOKS_DIR "hooks/" |
737 | #define GIT_HOOKS_DIR_MODE 0755 | |
738 | ||
739 | #define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" | |
740 | #define GIT_HOOKS_README_MODE 0755 | |
741 | #define GIT_HOOKS_README_CONTENT \ | |
742 | "#!/bin/sh\n"\ | |
743 | "#\n"\ | |
744 | "# Place appropriately named executable hook scripts into this directory\n"\ | |
745 | "# to intercept various actions that git takes. See `git help hooks` for\n"\ | |
746 | "# more information.\n" | |
747 | ||
748 | #define GIT_INFO_DIR "info/" | |
749 | #define GIT_INFO_DIR_MODE 0755 | |
750 | ||
751 | #define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" | |
752 | #define GIT_INFO_EXCLUDE_MODE 0644 | |
753 | #define GIT_INFO_EXCLUDE_CONTENT \ | |
754 | "# File patterns to ignore; see `git help ignore` for more information.\n"\ | |
755 | "# Lines that start with '#' are comments.\n" | |
756 | ||
757 | #define GIT_DESC_FILE "description" | |
758 | #define GIT_DESC_MODE 0644 | |
759 | #define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" | |
760 | ||
761 | static int repo_write_template( | |
762 | const char *git_dir, const char *file, mode_t mode, const char *content) | |
763 | { | |
764 | git_buf path = GIT_BUF_INIT; | |
db628072 | 765 | int fd, error = 0; |
dc34da6e RB |
766 | |
767 | if (git_buf_joinpath(&path, git_dir, file) < 0) | |
768 | return -1; | |
769 | ||
770 | fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); | |
dc34da6e | 771 | |
db628072 RB |
772 | if (fd >= 0) { |
773 | error = p_write(fd, content, strlen(content)); | |
dc34da6e | 774 | |
db628072 RB |
775 | p_close(fd); |
776 | } | |
777 | else if (errno != EEXIST) | |
778 | error = fd; | |
dc34da6e | 779 | |
dc34da6e | 780 | git_buf_free(&path); |
db628072 RB |
781 | |
782 | if (error) | |
783 | giterr_set(GITERR_OS, | |
784 | "Failed to initialize repository with template '%s'", file); | |
785 | ||
786 | return error; | |
dc34da6e RB |
787 | } |
788 | ||
ed72182b | 789 | static int repo_init_structure(const char *git_dir, int is_bare) |
4b8e27c8 | 790 | { |
cb8a7961 | 791 | int i; |
97769280 RB |
792 | struct { const char *dir; mode_t mode; } dirs[] = { |
793 | { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */ | |
794 | { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */ | |
795 | { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */ | |
796 | { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */ | |
dc34da6e RB |
797 | { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */ |
798 | { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */ | |
97769280 RB |
799 | { NULL, 0 } |
800 | }; | |
dc34da6e RB |
801 | struct { const char *file; mode_t mode; const char *content; } tmpl[] = { |
802 | { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, | |
803 | { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, | |
804 | { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, | |
805 | { NULL, 0, NULL } | |
806 | }; | |
97769280 RB |
807 | |
808 | /* Make the base directory */ | |
cb8a7961 VM |
809 | if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0) |
810 | return -1; | |
a67a096a | 811 | |
6ac91dfe | 812 | /* Hides the ".git" directory */ |
ed72182b | 813 | if (!is_bare) { |
17837602 | 814 | #ifdef GIT_WIN32 |
cb8a7961 VM |
815 | if (p_hide_directory__w32(git_dir) < 0) { |
816 | giterr_set(GITERR_REPOSITORY, | |
817 | "Failed to mark Git repository folder as hidden"); | |
818 | return -1; | |
819 | } | |
6ac91dfe | 820 | #endif |
17837602 | 821 | } |
6ac91dfe | 822 | |
97769280 RB |
823 | /* Make subdirectories as needed */ |
824 | for (i = 0; dirs[i].dir != NULL; ++i) { | |
cb8a7961 VM |
825 | if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) |
826 | return -1; | |
97769280 | 827 | } |
1c2c7c0d | 828 | |
dc34da6e RB |
829 | /* Make template files as needed */ |
830 | for (i = 0; tmpl[i].file != NULL; ++i) { | |
831 | if (repo_write_template( | |
832 | git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) | |
833 | return -1; | |
834 | } | |
835 | ||
cb8a7961 | 836 | return 0; |
4b8e27c8 | 837 | } |
838 | ||
40c44d2f | 839 | int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) |
4b8e27c8 | 840 | { |
97769280 | 841 | git_buf repository_path = GIT_BUF_INIT; |
693b23c0 | 842 | bool is_reinit; |
843 | int result = -1; | |
932d1baf | 844 | |
4b8e27c8 | 845 | assert(repo_out && path); |
846 | ||
cb8a7961 | 847 | if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) |
693b23c0 | 848 | goto cleanup; |
4b8e27c8 | 849 | |
693b23c0 | 850 | is_reinit = git_path_isdir(repository_path.ptr) && valid_repository_path(&repository_path); |
851 | ||
852 | if (is_reinit) { | |
853 | if (repo_init_reinit(repo_out, repository_path.ptr, is_bare) < 0) | |
854 | goto cleanup; | |
855 | ||
856 | result = repo_init_config(repository_path.ptr, is_bare, is_reinit); | |
b3aa4406 | 857 | goto cleanup; |
1bc83ff1 | 858 | } |
d2d6912e | 859 | |
cb8a7961 | 860 | if (repo_init_structure(repository_path.ptr, is_bare) < 0 || |
693b23c0 | 861 | repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0 || |
cb8a7961 VM |
862 | repo_init_createhead(repository_path.ptr) < 0 || |
863 | git_repository_open(repo_out, repository_path.ptr) < 0) { | |
693b23c0 | 864 | goto cleanup; |
cb8a7961 | 865 | } |
d2d6912e | 866 | |
693b23c0 | 867 | result = 0; |
868 | ||
869 | cleanup: | |
97769280 | 870 | git_buf_free(&repository_path); |
693b23c0 | 871 | return result; |
40c44d2f | 872 | } |
35502d2e | 873 | |
c682886e | 874 | int git_repository_head_detached(git_repository *repo) |
35502d2e CMN |
875 | { |
876 | git_reference *ref; | |
9462c471 | 877 | git_odb *odb = NULL; |
cb8a7961 | 878 | int exists; |
9462c471 | 879 | |
cb8a7961 VM |
880 | if (git_repository_odb__weakptr(&odb, repo) < 0) |
881 | return -1; | |
35502d2e | 882 | |
cb8a7961 VM |
883 | if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) |
884 | return -1; | |
35502d2e | 885 | |
75abd2b9 MS |
886 | if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { |
887 | git_reference_free(ref); | |
35502d2e | 888 | return 0; |
75abd2b9 | 889 | } |
35502d2e | 890 | |
cb8a7961 | 891 | exists = git_odb_exists(odb, git_reference_oid(ref)); |
75abd2b9 MS |
892 | |
893 | git_reference_free(ref); | |
cb8a7961 | 894 | return exists; |
35502d2e CMN |
895 | } |
896 | ||
3601c4bf | 897 | int git_repository_head(git_reference **head_out, git_repository *repo) |
35502d2e | 898 | { |
f201d613 | 899 | return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); |
3601c4bf | 900 | } |
901 | ||
902 | int git_repository_head_orphan(git_repository *repo) | |
903 | { | |
cb8a7961 | 904 | git_reference *ref = NULL; |
3601c4bf | 905 | int error; |
906 | ||
907 | error = git_repository_head(&ref, repo); | |
cb8a7961 | 908 | git_reference_free(ref); |
35502d2e | 909 | |
904b67e6 | 910 | if (error == GIT_ENOTFOUND) |
cb8a7961 | 911 | return 1; |
75abd2b9 | 912 | |
cb8a7961 VM |
913 | if (error < 0) |
914 | return -1; | |
915 | ||
916 | return 0; | |
35502d2e | 917 | } |
e0011be3 | 918 | |
41233c40 VM |
919 | int git_repository_is_empty(git_repository *repo) |
920 | { | |
d4a0b124 | 921 | git_reference *head = NULL, *branch = NULL; |
41233c40 VM |
922 | int error; |
923 | ||
cb8a7961 VM |
924 | if (git_reference_lookup(&head, repo, "HEAD") < 0) |
925 | return -1; | |
41233c40 | 926 | |
75abd2b9 MS |
927 | if (git_reference_type(head) != GIT_REF_SYMBOLIC) { |
928 | git_reference_free(head); | |
0f489fb2 | 929 | return 0; |
75abd2b9 | 930 | } |
0f489fb2 | 931 | |
75abd2b9 MS |
932 | if (strcmp(git_reference_target(head), "refs/heads/master") != 0) { |
933 | git_reference_free(head); | |
0f489fb2 | 934 | return 0; |
75abd2b9 | 935 | } |
41233c40 | 936 | |
0f489fb2 | 937 | error = git_reference_resolve(&branch, head); |
75abd2b9 MS |
938 | |
939 | git_reference_free(head); | |
940 | git_reference_free(branch); | |
941 | ||
904b67e6 | 942 | if (error == GIT_ENOTFOUND) |
cb8a7961 VM |
943 | return 1; |
944 | ||
945 | if (error < 0) | |
946 | return -1; | |
947 | ||
948 | return 0; | |
41233c40 VM |
949 | } |
950 | ||
9462c471 | 951 | const char *git_repository_path(git_repository *repo) |
4a34b3a9 | 952 | { |
953 | assert(repo); | |
9462c471 VM |
954 | return repo->path_repository; |
955 | } | |
4a34b3a9 | 956 | |
9462c471 VM |
957 | const char *git_repository_workdir(git_repository *repo) |
958 | { | |
959 | assert(repo); | |
602ee38b | 960 | |
9462c471 VM |
961 | if (repo->is_bare) |
962 | return NULL; | |
602ee38b | 963 | |
9462c471 VM |
964 | return repo->workdir; |
965 | } | |
602ee38b | 966 | |
9462c471 VM |
967 | int git_repository_set_workdir(git_repository *repo, const char *workdir) |
968 | { | |
b78fb64d | 969 | git_buf path = GIT_BUF_INIT; |
970 | ||
9462c471 | 971 | assert(repo && workdir); |
602ee38b | 972 | |
b78fb64d | 973 | if (git_path_prettify_dir(&path, workdir, NULL) < 0) |
974 | return -1; | |
9462c471 | 975 | |
2bc8fa02 | 976 | git__free(repo->workdir); |
9462c471 | 977 | |
b78fb64d | 978 | repo->workdir = git_buf_detach(&path); |
9462c471 | 979 | repo->is_bare = 0; |
0d0fa7c3 | 980 | return 0; |
4a34b3a9 | 981 | } |
fa9bcd81 | 982 | |
983 | int git_repository_is_bare(git_repository *repo) | |
984 | { | |
985 | assert(repo); | |
986 | return repo->is_bare; | |
987 | } | |
f917481e RB |
988 | |
989 | int git_repository_head_tree(git_tree **tree, git_repository *repo) | |
990 | { | |
991 | git_oid head_oid; | |
992 | git_object *obj = NULL; | |
993 | ||
994 | if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) { | |
995 | /* cannot resolve HEAD - probably brand new repo */ | |
996 | giterr_clear(); | |
997 | *tree = NULL; | |
998 | return 0; | |
999 | } | |
1000 | ||
1001 | if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0 || | |
1002 | git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0) | |
1003 | return -1; | |
1004 | ||
1005 | *tree = (git_tree *)obj; | |
1006 | return 0; | |
1007 | } |