From 2d5c298f91b4b76a8b51b9b66283ef5a872736a0 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 17 Apr 2008 19:32:22 -0400 Subject: [PATCH 01/14] Mark the list of refs to fetch as const Fetching the objects doesn't actually modify the list in any of the code paths, so this will allow code that fetches the entire (const) list of available refs to just pass the list in directly. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- transport.c | 16 ++++++++-------- transport.h | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/transport.c b/transport.c index 393e0e8fe2..a3b805258f 100644 --- a/transport.c +++ b/transport.c @@ -203,7 +203,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport) } static int fetch_objs_via_rsync(struct transport *transport, - int nr_objs, struct ref **to_fetch) + int nr_objs, const struct ref **to_fetch) { struct strbuf buf = STRBUF_INIT; struct child_process rsync; @@ -350,7 +350,7 @@ static int rsync_transport_push(struct transport *transport, #ifndef NO_CURL /* http fetch is the only user */ static int fetch_objs_via_walker(struct transport *transport, - int nr_objs, struct ref **to_fetch) + int nr_objs, const struct ref **to_fetch) { char *dest = xstrdup(transport->url); struct walker *walker = transport->data; @@ -504,7 +504,7 @@ static struct ref *get_refs_via_curl(struct transport *transport) } static int fetch_objs_via_curl(struct transport *transport, - int nr_objs, struct ref **to_fetch) + int nr_objs, const struct ref **to_fetch) { if (!transport->data) transport->data = get_http_walker(transport->url, @@ -542,7 +542,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport) } static int fetch_refs_from_bundle(struct transport *transport, - int nr_heads, struct ref **to_fetch) + int nr_heads, const struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; return unbundle(&data->header, data->fd); @@ -616,7 +616,7 @@ static struct ref *get_refs_via_connect(struct transport *transport) } static int fetch_refs_via_pack(struct transport *transport, - int nr_heads, struct ref **to_fetch) + int nr_heads, const struct ref **to_fetch) { struct git_transport_data *data = transport->data; char **heads = xmalloc(nr_heads * sizeof(*heads)); @@ -784,12 +784,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport) return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, struct ref *refs) +int transport_fetch_refs(struct transport *transport, const struct ref *refs) { int rc; int nr_heads = 0, nr_alloc = 0; - struct ref **heads = NULL; - struct ref *rm; + const struct ref **heads = NULL; + const struct ref *rm; for (rm = refs; rm; rm = rm->next) { if (rm->peer_ref && diff --git a/transport.h b/transport.h index 8abfc0ae60..d0b52053ff 100644 --- a/transport.h +++ b/transport.h @@ -19,7 +19,7 @@ struct transport { const char *value); struct ref *(*get_refs_list)(struct transport *transport); - int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs); int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); int (*disconnect)(struct transport *connection); @@ -68,7 +68,7 @@ int transport_push(struct transport *connection, const struct ref *transport_get_remote_refs(struct transport *transport); -int transport_fetch_refs(struct transport *transport, struct ref *refs); +int transport_fetch_refs(struct transport *transport, const struct ref *refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); From ea3cd5c7c63fadacd66c364ae4b8c6d01e5809b1 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 17 Apr 2008 19:32:26 -0400 Subject: [PATCH 02/14] Add a lockfile function to append to a file This takes care of copying the original contents into the replacement file after the lock is held, so that concurrent additions can't miss each other's changes. [jc: munged to drop mmap in favor of copy_file.] Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- cache.h | 1 + lockfile.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/cache.h b/cache.h index 5a28dddec9..396eabf6ed 100644 --- a/cache.h +++ b/cache.h @@ -391,6 +391,7 @@ struct lock_file { char filename[PATH_MAX]; }; extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); +extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern int hold_locked_index(struct lock_file *, int); diff --git a/lockfile.c b/lockfile.c index 663f18f9c4..e9e0095e9f 100644 --- a/lockfile.c +++ b/lockfile.c @@ -160,6 +160,34 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on return fd; } +int hold_lock_file_for_append(struct lock_file *lk, const char *path, int die_on_error) +{ + int fd, orig_fd; + + fd = lock_file(lk, path); + if (fd < 0) { + if (die_on_error) + die("unable to create '%s.lock': %s", path, strerror(errno)); + return fd; + } + + orig_fd = open(path, O_RDONLY); + if (orig_fd < 0) { + if (errno != ENOENT) { + if (die_on_error) + die("cannot open '%s' for copying", path); + close(fd); + return error("cannot open '%s' for copying", path); + } + } else if (copy_fd(orig_fd, fd)) { + if (die_on_error) + exit(128); + close(fd); + return -1; + } + return fd; +} + int close_lock_file(struct lock_file *lk) { int fd = lk->fd; From bef70b22ba63d71c1ae2e070e64ff9863ea1ad14 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 17 Apr 2008 19:32:30 -0400 Subject: [PATCH 03/14] Add a library function to add an alternate to the alternates file This is in the core so that, if the alternates file has already been read, the addition can be parsed and put into effect for the current process. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- cache.h | 1 + sha1_file.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/cache.h b/cache.h index 396eabf6ed..9da9179afd 100644 --- a/cache.h +++ b/cache.h @@ -599,6 +599,7 @@ extern struct alternate_object_database { char base[FLEX_ARRAY]; /* more */ } *alt_odb_list; extern void prepare_alt_odb(void); +extern void add_to_alternates_file(const char *reference); struct pack_window { struct pack_window *next; diff --git a/sha1_file.c b/sha1_file.c index 3516777bc7..d21e23b464 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -380,6 +380,18 @@ static void read_info_alternates(const char * relative_base, int depth) munmap(map, mapsz); } +void add_to_alternates_file(const char *reference) +{ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), 1); + char *alt = mkpath("%s/objects\n", reference); + write_or_die(fd, alt, strlen(alt)); + if (commit_lock_file(lock)) + die("could not close alternates file"); + if (alt_odb_tail) + link_alt_odb_entries(alt, alt + strlen(alt), '\n', NULL, 0); +} + void prepare_alt_odb(void) { const char *alt; From e0aaa29ff324c40a6428d5cc26867392eedf94ad Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 17 Apr 2008 19:32:35 -0400 Subject: [PATCH 04/14] Have a constant extern refspec for "--tags" The refspec refs/tags/*:refs/tags/* is sufficiently common and generic to merit having a constant instead of generating it as needed. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-fetch.c | 10 ++-------- remote.c | 9 +++++++++ remote.h | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index 167f948036..30aa42df6e 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -127,14 +127,8 @@ static struct ref *get_ref_map(struct transport *transport, /* Merge everything on the command line, but not --tags */ for (rm = ref_map; rm; rm = rm->next) rm->merge = 1; - if (tags == TAGS_SET) { - struct refspec refspec; - refspec.src = "refs/tags/"; - refspec.dst = "refs/tags/"; - refspec.pattern = 1; - refspec.force = 0; - get_fetch_map(remote_refs, &refspec, &tail, 0); - } + if (tags == TAGS_SET) + get_fetch_map(remote_refs, tag_refspec, &tail, 0); } else { /* Use the defaults */ struct remote *remote = transport->remote; diff --git a/remote.c b/remote.c index 2d9af4023e..9cb40afd0e 100644 --- a/remote.c +++ b/remote.c @@ -2,6 +2,15 @@ #include "remote.h" #include "refs.h" +static struct refspec s_tag_refspec = { + 0, + 1, + "refs/tags/", + "refs/tags/" +}; + +const struct refspec *tag_refspec = &s_tag_refspec; + struct counted_string { size_t len; const char *s; diff --git a/remote.h b/remote.h index a38774bbdc..f0a79de210 100644 --- a/remote.h +++ b/remote.h @@ -51,6 +51,8 @@ struct refspec { char *dst; }; +extern const struct refspec *tag_refspec; + struct ref *alloc_ref(unsigned namelen); struct ref *copy_ref_list(const struct ref *ref); From e142a3c61d22c3a3c081cb2c693e4d3725c21522 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sun, 27 Apr 2008 13:39:24 -0400 Subject: [PATCH 05/14] Allow for having for_each_ref() list extra refs These refs can be anything, but they are most likely useful as pointing to objects that you know are in the object database but don't have any regular refs for. For example, when cloning with --reference, the refs in this repository should be listed as objects that we have, even though we don't have refs in our newly-created repository for them yet. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- refs.c | 18 ++++++++++++++++++ refs.h | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/refs.c b/refs.c index 4db73ed8f0..8300cc1b54 100644 --- a/refs.c +++ b/refs.c @@ -159,6 +159,8 @@ static struct cached_refs { } cached_refs; static struct ref_list *current_ref; +static struct ref_list *extra_refs; + static void free_ref_list(struct ref_list *list) { struct ref_list *next; @@ -215,6 +217,17 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) cached_refs->packed = sort_ref_list(list); } +void add_extra_ref(const char *name, const unsigned char *sha1, int flag) +{ + extra_refs = add_ref(name, sha1, flag, extra_refs, NULL); +} + +void clear_extra_refs(void) +{ + free_ref_list(extra_refs); + extra_refs = NULL; +} + static struct ref_list *get_packed_refs(void) { if (!cached_refs.did_packed) { @@ -536,6 +549,11 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, struct ref_list *packed = get_packed_refs(); struct ref_list *loose = get_loose_refs(); + struct ref_list *extra; + + for (extra = extra_refs; extra; extra = extra->next) + retval = do_one_ref(base, fn, trim, cb_data, extra); + while (packed && loose) { struct ref_list *entry; int cmp = strcmp(packed->name, loose->name); diff --git a/refs.h b/refs.h index 06abee1526..06ad260556 100644 --- a/refs.h +++ b/refs.h @@ -24,6 +24,15 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +/* + * Extra refs will be listed by for_each_ref() before any actual refs + * for the duration of this process or until clear_extra_refs() is + * called. Only extra refs added before for_each_ref() is called will + * be listed on a given call of for_each_ref(). + */ +extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags); +extern void clear_extra_refs(void); + extern int peel_ref(const char *, unsigned char *); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ From 19757d80e522e9f04c125ad5977c3a79db99b192 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sun, 27 Apr 2008 13:39:21 -0400 Subject: [PATCH 06/14] Add a function to set a non-default work tree This function may only be used before the work tree is used. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- cache.h | 1 + environment.c | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 9da9179afd..aba029315c 100644 --- a/cache.h +++ b/cache.h @@ -311,6 +311,7 @@ extern char *get_index_file(void); extern char *get_graft_file(void); extern int set_git_dir(const char *path); extern const char *get_git_work_tree(void); +extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" diff --git a/environment.c b/environment.c index 6739a3f417..0bff432049 100644 --- a/environment.c +++ b/environment.c @@ -41,7 +41,7 @@ enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; -static const char *work_tree; +static char *work_tree; static const char *git_dir; static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; @@ -81,10 +81,26 @@ const char *get_git_dir(void) return git_dir; } +static int git_work_tree_initialized; + +/* + * Note. This works only before you used a work tree. This was added + * primarily to support git-clone to work in a new repository it just + * created, and is not meant to flip between different work trees. + */ +void set_git_work_tree(const char *new_work_tree) +{ + if (is_bare_repository_cfg >= 0) + die("cannot set work tree after initialization"); + git_work_tree_initialized = 1; + free(work_tree); + work_tree = xstrdup(make_absolute_path(new_work_tree)); + is_bare_repository_cfg = 0; +} + const char *get_git_work_tree(void) { - static int initialized = 0; - if (!initialized) { + if (!git_work_tree_initialized) { work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); /* core.bare = true overrides implicit and config work tree */ if (!work_tree && is_bare_repository_cfg < 1) { @@ -94,7 +110,7 @@ const char *get_git_work_tree(void) work_tree = xstrdup(make_absolute_path(git_path(work_tree))); } else if (work_tree) work_tree = xstrdup(make_absolute_path(work_tree)); - initialized = 1; + git_work_tree_initialized = 1; if (work_tree) is_bare_repository_cfg = 0; } From f225aeb278eb1c8c028cd2ff1fdfe7a45067b8e1 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sun, 27 Apr 2008 13:39:27 -0400 Subject: [PATCH 07/14] Provide API access to init_db() The caller first calls set_git_dir() to specify the GIT_DIR, and then calls init_db() to initialize it. This also cleans up various parts of the code to account for the fact that everything is done with GIT_DIR set, so it's unnecessary to pass the specified directory around. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-init-db.c | 251 ++++++++++++++++++++++++---------------------- cache.h | 4 + 2 files changed, 134 insertions(+), 121 deletions(-) diff --git a/builtin-init-db.c b/builtin-init-db.c index a76f5d3474..5650685e4e 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -104,12 +104,14 @@ static void copy_templates_1(char *path, int baselen, } } -static void copy_templates(const char *git_dir, int len, const char *template_dir) +static void copy_templates(const char *template_dir) { char path[PATH_MAX]; char template_path[PATH_MAX]; int template_len; DIR *dir; + const char *git_dir = get_git_dir(); + int len = strlen(git_dir); if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); @@ -156,6 +158,8 @@ static void copy_templates(const char *git_dir, int len, const char *template_di } memcpy(path, git_dir, len); + if (len && path[len - 1] != '/') + path[len++] = '/'; path[len] = 0; copy_templates_1(path, len, template_path, template_len, @@ -163,8 +167,9 @@ static void copy_templates(const char *git_dir, int len, const char *template_di closedir(dir); } -static int create_default_files(const char *git_dir, const char *template_path) +static int create_default_files(const char *template_path) { + const char *git_dir = get_git_dir(); unsigned len = strlen(git_dir); static char path[PATH_MAX]; struct stat st1; @@ -183,19 +188,15 @@ static int create_default_files(const char *git_dir, const char *template_path) /* * Create .git/refs/{heads,tags} */ - strcpy(path + len, "refs"); - safe_create_dir(path, 1); - strcpy(path + len, "refs/heads"); - safe_create_dir(path, 1); - strcpy(path + len, "refs/tags"); - safe_create_dir(path, 1); + safe_create_dir(git_path("refs"), 1); + safe_create_dir(git_path("refs/heads"), 1); + safe_create_dir(git_path("refs/tags"), 1); /* First copy the templates -- we might have the default * config file there, in which case we would want to read * from it after installing. */ - path[len] = 0; - copy_templates(path, len, template_path); + copy_templates(template_path); git_config(git_default_config); @@ -204,14 +205,10 @@ static int create_default_files(const char *git_dir, const char *template_path) * shared-repository settings, we would need to fix them up. */ if (shared_repository) { - path[len] = 0; - adjust_shared_perm(path); - strcpy(path + len, "refs"); - adjust_shared_perm(path); - strcpy(path + len, "refs/heads"); - adjust_shared_perm(path); - strcpy(path + len, "refs/tags"); - adjust_shared_perm(path); + adjust_shared_perm(get_git_dir()); + adjust_shared_perm(git_path("refs")); + adjust_shared_perm(git_path("refs/heads")); + adjust_shared_perm(git_path("refs/tags")); } /* @@ -251,8 +248,10 @@ static int create_default_files(const char *git_dir, const char *template_path) /* allow template config file to override the default */ if (log_all_ref_updates == -1) git_config_set("core.logallrefupdates", "true"); - if (work_tree != git_work_tree_cfg) + if (prefixcmp(git_dir, work_tree) || + strcmp(git_dir + strlen(work_tree), "/.git")) { git_config_set("core.worktree", work_tree); + } } /* Check if symlink is supported in the work tree */ @@ -272,106 +271,13 @@ static int create_default_files(const char *git_dir, const char *template_path) return reinit; } -static void guess_repository_type(const char *git_dir) +int init_db(const char *template_dir, unsigned int flags) { - char cwd[PATH_MAX]; - const char *slash; - - if (0 <= is_bare_repository_cfg) - return; - if (!git_dir) - return; - - /* - * "GIT_DIR=. git init" is always bare. - * "GIT_DIR=`pwd` git init" too. - */ - if (!strcmp(".", git_dir)) - goto force_bare; - if (!getcwd(cwd, sizeof(cwd))) - die("cannot tell cwd"); - if (!strcmp(git_dir, cwd)) - goto force_bare; - /* - * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. - */ - if (!strcmp(git_dir, ".git")) - return; - slash = strrchr(git_dir, '/'); - if (slash && !strcmp(slash, "/.git")) - return; - - /* - * Otherwise it is often bare. At this point - * we are just guessing. - */ - force_bare: - is_bare_repository_cfg = 1; - return; -} - -static const char init_db_usage[] = -"git-init [-q | --quiet] [--template=] [--shared]"; - -/* - * If you want to, you can share the DB area with any number of branches. - * That has advantages: you can save space by sharing all the SHA1 objects. - * On the other hand, it might just make lookup slower and messier. You - * be the judge. The default case is to have one DB per managed directory. - */ -int cmd_init_db(int argc, const char **argv, const char *prefix) -{ - const char *git_dir; const char *sha1_dir; - const char *template_dir = NULL; char *path; - int len, i, reinit; - int quiet = 0; + int len, reinit; - for (i = 1; i < argc; i++, argv++) { - const char *arg = argv[1]; - if (!prefixcmp(arg, "--template=")) - template_dir = arg+11; - else if (!strcmp(arg, "--shared")) - shared_repository = PERM_GROUP; - else if (!prefixcmp(arg, "--shared=")) - shared_repository = git_config_perm("arg", arg+9); - else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) - quiet = 1; - else - usage(init_db_usage); - } - - /* - * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR - * without --bare. Catch the error early. - */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - if ((!git_dir || is_bare_repository_cfg == 1) - && getenv(GIT_WORK_TREE_ENVIRONMENT)) - die("%s (or --work-tree=) not allowed without " - "specifying %s (or --git-dir=)", - GIT_WORK_TREE_ENVIRONMENT, - GIT_DIR_ENVIRONMENT); - - guess_repository_type(git_dir); - - if (is_bare_repository_cfg <= 0) { - git_work_tree_cfg = xcalloc(PATH_MAX, 1); - if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die ("Cannot access current working directory."); - if (access(get_git_work_tree(), X_OK)) - die ("Cannot access work tree '%s'", - get_git_work_tree()); - } - - /* - * Set up the default .git directory contents - */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - if (!git_dir) - git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - safe_create_dir(git_dir, 0); + safe_create_dir(get_git_dir(), 0); /* Check to see if the repository version is right. * Note that a newly created repository does not have @@ -380,11 +286,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) */ check_repository_format(); - reinit = create_default_files(git_dir, template_dir); + reinit = create_default_files(template_dir); - /* - * And set up the object store. - */ sha1_dir = get_object_directory(); len = strlen(sha1_dir); path = xmalloc(len + 40); @@ -414,11 +317,117 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) git_config_set("receive.denyNonFastforwards", "true"); } - if (!quiet) + if (!(flags & INIT_DB_QUIET)) printf("%s%s Git repository in %s/\n", reinit ? "Reinitialized existing" : "Initialized empty", shared_repository ? " shared" : "", - git_dir); + get_git_dir()); return 0; } + +static int guess_repository_type(const char *git_dir) +{ + char cwd[PATH_MAX]; + const char *slash; + + /* + * "GIT_DIR=. git init" is always bare. + * "GIT_DIR=`pwd` git init" too. + */ + if (!strcmp(".", git_dir)) + return 1; + if (!getcwd(cwd, sizeof(cwd))) + die("cannot tell cwd"); + if (!strcmp(git_dir, cwd)) + return 1; + /* + * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. + */ + if (!strcmp(git_dir, ".git")) + return 0; + slash = strrchr(git_dir, '/'); + if (slash && !strcmp(slash, "/.git")) + return 0; + + /* + * Otherwise it is often bare. At this point + * we are just guessing. + */ + return 1; +} + +static const char init_db_usage[] = +"git-init [-q | --quiet] [--template=] [--shared]"; + +/* + * If you want to, you can share the DB area with any number of branches. + * That has advantages: you can save space by sharing all the SHA1 objects. + * On the other hand, it might just make lookup slower and messier. You + * be the judge. The default case is to have one DB per managed directory. + */ +int cmd_init_db(int argc, const char **argv, const char *prefix) +{ + const char *git_dir; + const char *template_dir = NULL; + unsigned int flags = 0; + int i; + + for (i = 1; i < argc; i++, argv++) { + const char *arg = argv[1]; + if (!prefixcmp(arg, "--template=")) + template_dir = arg+11; + else if (!strcmp(arg, "--shared")) + shared_repository = PERM_GROUP; + else if (!prefixcmp(arg, "--shared=")) + shared_repository = git_config_perm("arg", arg+9); + else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) + flags |= INIT_DB_QUIET; + else + usage(init_db_usage); + } + + /* + * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR + * without --bare. Catch the error early. + */ + git_dir = getenv(GIT_DIR_ENVIRONMENT); + if ((!git_dir || is_bare_repository_cfg == 1) + && getenv(GIT_WORK_TREE_ENVIRONMENT)) + die("%s (or --work-tree=) not allowed without " + "specifying %s (or --git-dir=)", + GIT_WORK_TREE_ENVIRONMENT, + GIT_DIR_ENVIRONMENT); + + /* + * Set up the default .git directory contents + */ + if (!git_dir) + git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; + + if (is_bare_repository_cfg < 0) + is_bare_repository_cfg = guess_repository_type(git_dir); + + if (!is_bare_repository_cfg) { + if (git_dir) { + const char *git_dir_parent = strrchr(git_dir, '/'); + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + git_work_tree_cfg = xstrdup(make_absolute_path(rel)); + free(rel); + } + } + if (!git_work_tree_cfg) { + git_work_tree_cfg = xcalloc(PATH_MAX, 1); + if (!getcwd(git_work_tree_cfg, PATH_MAX)) + die ("Cannot access current working directory."); + } + if (access(get_git_work_tree(), X_OK)) + die ("Cannot access work tree '%s'", + get_git_work_tree()); + } + + set_git_dir(make_absolute_path(git_dir)); + + return init_db(template_dir, flags); +} diff --git a/cache.h b/cache.h index aba029315c..f3ad9741cc 100644 --- a/cache.h +++ b/cache.h @@ -324,6 +324,10 @@ extern const char *prefix_filename(const char *prefix, int len, const char *path extern void verify_filename(const char *prefix, const char *name); extern void verify_non_filename(const char *prefix, const char *name); +#define INIT_DB_QUIET 0x0001 + +extern int init_db(const char *template_dir, unsigned int flags); + #define alloc_nr(x) (((x)+16)*3/2) /* From 8434c2f1afedb936e0ea8c07ce25733013c2f743 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sun, 27 Apr 2008 13:39:30 -0400 Subject: [PATCH 08/14] Build in clone Thanks to Johannes Schindelin for various comments and improvements, including supporting cloning full bundles. Signed-off-by: Junio C Hamano --- Makefile | 2 +- builtin-clone.c | 544 ++++++++++++++++++ builtin.h | 1 + git-clone.sh => contrib/examples/git-clone.sh | 0 git.c | 1 + 5 files changed, 547 insertions(+), 1 deletion(-) create mode 100644 builtin-clone.c rename git-clone.sh => contrib/examples/git-clone.sh (100%) diff --git a/Makefile b/Makefile index 9d84c8d799..d5f763b515 100644 --- a/Makefile +++ b/Makefile @@ -235,7 +235,6 @@ BASIC_LDFLAGS = SCRIPT_SH += git-am.sh SCRIPT_SH += git-bisect.sh -SCRIPT_SH += git-clone.sh SCRIPT_SH += git-filter-branch.sh SCRIPT_SH += git-lost-found.sh SCRIPT_SH += git-merge-octopus.sh @@ -481,6 +480,7 @@ BUILTIN_OBJS += builtin-check-ref-format.o BUILTIN_OBJS += builtin-checkout-index.o BUILTIN_OBJS += builtin-checkout.o BUILTIN_OBJS += builtin-clean.o +BUILTIN_OBJS += builtin-clone.o BUILTIN_OBJS += builtin-commit-tree.o BUILTIN_OBJS += builtin-commit.o BUILTIN_OBJS += builtin-config.o diff --git a/builtin-clone.c b/builtin-clone.c new file mode 100644 index 0000000000..a7c075d0e2 --- /dev/null +++ b/builtin-clone.c @@ -0,0 +1,544 @@ +/* + * Builtin "git clone" + * + * Copyright (c) 2007 Kristian Høgsberg , + * 2008 Daniel Barkalow + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds + * + * Clone a repository into a different directory that does not yet exist. + */ + +#include "cache.h" +#include "parse-options.h" +#include "fetch-pack.h" +#include "refs.h" +#include "tree.h" +#include "tree-walk.h" +#include "unpack-trees.h" +#include "transport.h" +#include "strbuf.h" +#include "dir.h" + +/* + * Overall FIXMEs: + * - respect DB_ENVIRONMENT for .git/objects. + * + * Implementation notes: + * - dropping use-separate-remote and no-separate-remote compatibility + * + */ +static const char * const builtin_clone_usage[] = { + "git-clone [options] [--] []", + NULL +}; + +static int option_quiet, option_no_checkout, option_bare; +static int option_local, option_no_hardlinks, option_shared; +static char *option_template, *option_reference, *option_depth; +static char *option_origin = NULL; +static char *option_upload_pack = "git-upload-pack"; + +static struct option builtin_clone_options[] = { + OPT__QUIET(&option_quiet), + OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, + "don't create a checkout"), + OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), + OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"), + OPT_BOOLEAN('l', "local", &option_local, + "to clone from a local repository"), + OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks, + "don't use local hardlinks, always copy"), + OPT_BOOLEAN('s', "shared", &option_shared, + "setup as shared repository"), + OPT_STRING(0, "template", &option_template, "path", + "path the template repository"), + OPT_STRING(0, "reference", &option_reference, "repo", + "reference repository"), + OPT_STRING('o', "origin", &option_origin, "branch", + "use instead or 'origin' to track upstream"), + OPT_STRING('u', "upload-pack", &option_upload_pack, "path", + "path to git-upload-pack on the remote"), + OPT_STRING(0, "depth", &option_depth, "depth", + "create a shallow clone of that depth"), + + OPT_END() +}; + +static char *get_repo_path(const char *repo, int *is_bundle) +{ + static char *suffix[] = { "/.git", ".git", "" }; + static char *bundle_suffix[] = { ".bundle", "" }; + struct stat st; + int i; + + for (i = 0; i < ARRAY_SIZE(suffix); i++) { + const char *path; + path = mkpath("%s%s", repo, suffix[i]); + if (!stat(path, &st) && S_ISDIR(st.st_mode)) { + *is_bundle = 0; + return xstrdup(make_absolute_path(path)); + } + } + + for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) { + const char *path; + path = mkpath("%s%s", repo, bundle_suffix[i]); + if (!stat(path, &st) && S_ISREG(st.st_mode)) { + *is_bundle = 1; + return xstrdup(make_absolute_path(path)); + } + } + + return NULL; +} + +static char *guess_dir_name(const char *repo, int is_bundle) +{ + const char *p, *start, *end, *limit; + int after_slash_or_colon; + + /* Guess dir name from repository: strip trailing '/', + * strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */ + + after_slash_or_colon = 1; + limit = repo + strlen(repo); + start = repo; + end = limit; + for (p = repo; p < limit; p++) { + const char *prefix = is_bundle ? ".bundle" : ".git"; + if (!prefixcmp(p, prefix)) { + if (!after_slash_or_colon) + end = p; + p += strlen(prefix) - 1; + } else if (!prefixcmp(p, ".bundle")) { + if (!after_slash_or_colon) + end = p; + p += 7; + } else if (*p == '/' || *p == ':') { + if (end == limit) + end = p; + after_slash_or_colon = 1; + } else if (after_slash_or_colon) { + start = p; + end = limit; + after_slash_or_colon = 0; + } + } + + return xstrndup(start, end - start); +} + +static int is_directory(const char *path) +{ + struct stat buf; + + return !stat(path, &buf) && S_ISDIR(buf.st_mode); +} + +static void setup_reference(const char *repo) +{ + const char *ref_git; + char *ref_git_copy; + + struct remote *remote; + struct transport *transport; + const struct ref *extra; + + ref_git = make_absolute_path(option_reference); + + if (is_directory(mkpath("%s/.git/objects", ref_git))) + ref_git = mkpath("%s/.git", ref_git); + else if (!is_directory(mkpath("%s/objects", ref_git))) + die("reference repository '%s' is not a local directory.", + option_reference); + + ref_git_copy = xstrdup(ref_git); + + add_to_alternates_file(ref_git_copy); + + remote = remote_get(ref_git_copy); + transport = transport_get(remote, ref_git_copy); + for (extra = transport_get_remote_refs(transport); extra; + extra = extra->next) + add_extra_ref(extra->name, extra->old_sha1, 0); + + transport_disconnect(transport); + + free(ref_git_copy); +} + +static void copy_or_link_directory(char *src, char *dest) +{ + struct dirent *de; + struct stat buf; + int src_len, dest_len; + DIR *dir; + + dir = opendir(src); + if (!dir) + die("failed to open %s\n", src); + + if (mkdir(dest, 0777)) { + if (errno != EEXIST) + die("failed to create directory %s\n", dest); + else if (stat(dest, &buf)) + die("failed to stat %s\n", dest); + else if (!S_ISDIR(buf.st_mode)) + die("%s exists and is not a directory\n", dest); + } + + src_len = strlen(src); + src[src_len] = '/'; + dest_len = strlen(dest); + dest[dest_len] = '/'; + + while ((de = readdir(dir)) != NULL) { + strcpy(src + src_len + 1, de->d_name); + strcpy(dest + dest_len + 1, de->d_name); + if (stat(src, &buf)) { + warning ("failed to stat %s\n", src); + continue; + } + if (S_ISDIR(buf.st_mode)) { + if (de->d_name[0] != '.') + copy_or_link_directory(src, dest); + continue; + } + + if (unlink(dest) && errno != ENOENT) + die("failed to unlink %s\n", dest); + if (option_no_hardlinks) { + if (copy_file(dest, src, 0666)) + die("failed to copy file to %s\n", dest); + } else { + if (link(src, dest)) + die("failed to create link %s\n", dest); + } + } +} + +static const struct ref *clone_local(const char *src_repo, + const char *dest_repo) +{ + const struct ref *ret; + char src[PATH_MAX]; + char dest[PATH_MAX]; + struct remote *remote; + struct transport *transport; + + if (option_shared) + add_to_alternates_file(src_repo); + else { + snprintf(src, PATH_MAX, "%s/objects", src_repo); + snprintf(dest, PATH_MAX, "%s/objects", dest_repo); + copy_or_link_directory(src, dest); + } + + remote = remote_get(src_repo); + transport = transport_get(remote, src_repo); + ret = transport_get_remote_refs(transport); + transport_disconnect(transport); + return ret; +} + +static const char *junk_work_tree; +static const char *junk_git_dir; +pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb; + if (getpid() != junk_pid) + return; + strbuf_init(&sb, 0); + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static const struct ref *locate_head(const struct ref *refs, + const struct ref *mapped_refs, + const struct ref **remote_head_p) +{ + const struct ref *remote_head = NULL; + const struct ref *remote_master = NULL; + const struct ref *r; + for (r = refs; r; r = r->next) + if (!strcmp(r->name, "HEAD")) + remote_head = r; + + for (r = mapped_refs; r; r = r->next) + if (!strcmp(r->name, "refs/heads/master")) + remote_master = r; + + if (remote_head_p) + *remote_head_p = remote_head; + + /* If there's no HEAD value at all, never mind. */ + if (!remote_head) + return NULL; + + /* If refs/heads/master could be right, it is. */ + if (remote_master && !hashcmp(remote_master->old_sha1, + remote_head->old_sha1)) + return remote_master; + + /* Look for another ref that points there */ + for (r = mapped_refs; r; r = r->next) + if (r != remote_head && + !hashcmp(r->old_sha1, remote_head->old_sha1)) + return r; + + /* Nothing is the same */ + return NULL; +} + +static struct ref *write_remote_refs(const struct ref *refs, + struct refspec *refspec, const char *reflog) +{ + struct ref *local_refs = NULL; + struct ref **tail = &local_refs; + struct ref *r; + + get_fetch_map(refs, refspec, &tail, 0); + get_fetch_map(refs, tag_refspec, &tail, 0); + + for (r = local_refs; r; r = r->next) + update_ref(reflog, + r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR); + return local_refs; +} + +int cmd_clone(int argc, const char **argv, const char *prefix) +{ + int use_local_hardlinks = 1; + int use_separate_remote = 1; + int is_bundle = 0; + struct stat buf; + const char *repo_name, *repo, *work_tree, *git_dir; + char *path, *dir; + const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; + char branch_top[256], key[256], value[256]; + struct strbuf reflog_msg; + + struct refspec refspec; + + junk_pid = getpid(); + + argc = parse_options(argc, argv, builtin_clone_options, + builtin_clone_usage, 0); + + if (argc == 0) + die("You must specify a repository to clone."); + + if (option_no_hardlinks) + use_local_hardlinks = 0; + + if (option_bare) { + if (option_origin) + die("--bare and --origin %s options are incompatible.", + option_origin); + option_no_checkout = 1; + use_separate_remote = 0; + } + + if (!option_origin) + option_origin = "origin"; + + repo_name = argv[0]; + + path = get_repo_path(repo_name, &is_bundle); + if (path) + repo = path; + else if (!strchr(repo_name, ':')) + repo = xstrdup(make_absolute_path(repo_name)); + else + repo = repo_name; + + if (argc == 2) + dir = xstrdup(argv[1]); + else + dir = guess_dir_name(repo_name, is_bundle); + + if (!stat(dir, &buf)) + die("destination directory '%s' already exists.", dir); + + strbuf_init(&reflog_msg, 0); + strbuf_addf(&reflog_msg, "clone: from %s", repo); + + if (option_bare) + work_tree = NULL; + else { + work_tree = getenv("GIT_WORK_TREE"); + if (work_tree && !stat(work_tree, &buf)) + die("working tree '%s' already exists.", work_tree); + } + + if (option_bare || work_tree) + git_dir = xstrdup(dir); + else { + work_tree = dir; + git_dir = xstrdup(mkpath("%s/.git", dir)); + } + + if (!option_bare) { + junk_work_tree = work_tree; + if (mkdir(work_tree, 0755)) + die("could not create work tree dir '%s'.", work_tree); + set_git_work_tree(work_tree); + } + junk_git_dir = git_dir; + atexit(remove_junk); + signal(SIGINT, remove_junk_on_signal); + + setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1); + + set_git_dir(make_absolute_path(git_dir)); + + fprintf(stderr, "Initialize %s\n", git_dir); + init_db(option_template, option_quiet ? INIT_DB_QUIET : 0); + + if (option_reference) + setup_reference(git_dir); + + git_config(git_default_config); + + if (option_bare) { + strcpy(branch_top, "refs/heads/"); + + git_config_set("core.bare", "true"); + } else { + snprintf(branch_top, sizeof(branch_top), + "refs/remotes/%s/", option_origin); + + /* Configure the remote */ + snprintf(key, sizeof(key), "remote.%s.url", option_origin); + git_config_set(key, repo); + + snprintf(key, sizeof(key), "remote.%s.fetch", option_origin); + snprintf(value, sizeof(value), + "+refs/heads/*:%s*", branch_top); + git_config_set_multivar(key, value, "^$", 0); + } + + refspec.force = 0; + refspec.pattern = 1; + refspec.src = "refs/heads/"; + refspec.dst = branch_top; + + if (path && !is_bundle) + refs = clone_local(path, git_dir); + else { + struct remote *remote = remote_get(argv[0]); + struct transport *transport = transport_get(remote, argv[0]); + + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + + if (option_depth) + transport_set_option(transport, TRANS_OPT_DEPTH, + option_depth); + + if (option_quiet) + transport->verbose = -1; + + refs = transport_get_remote_refs(transport); + transport_fetch_refs(transport, refs); + } + + clear_extra_refs(); + + mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf); + + head_points_at = locate_head(refs, mapped_refs, &remote_head); + + if (head_points_at) { + /* Local default branch link */ + create_symref("HEAD", head_points_at->name, NULL); + + if (!option_bare) { + struct strbuf head_ref; + const char *head = head_points_at->name; + + if (!prefixcmp(head, "refs/heads/")) + head += 11; + + /* Set up the initial local branch */ + + /* Local branch initial value */ + update_ref(reflog_msg.buf, "HEAD", + head_points_at->old_sha1, + NULL, 0, DIE_ON_ERR); + + strbuf_init(&head_ref, 0); + strbuf_addstr(&head_ref, branch_top); + strbuf_addstr(&head_ref, "HEAD"); + + /* Remote branch link */ + create_symref(head_ref.buf, + head_points_at->peer_ref->name, + reflog_msg.buf); + + snprintf(key, sizeof(key), "branch.%s.remote", head); + git_config_set(key, option_origin); + snprintf(key, sizeof(key), "branch.%s.merge", head); + git_config_set(key, head_points_at->name); + } + } else if (remote_head) { + /* Source had detached HEAD pointing somewhere. */ + if (!option_bare) + update_ref(reflog_msg.buf, "HEAD", + remote_head->old_sha1, + NULL, REF_NODEREF, DIE_ON_ERR); + } else { + /* Nothing to checkout out */ + if (!option_no_checkout) + warning("remote HEAD refers to nonexistent ref, " + "unable to checkout.\n"); + option_no_checkout = 1; + } + + if (!option_no_checkout) { + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + struct unpack_trees_options opts; + struct tree *tree; + struct tree_desc t; + int fd; + + /* We need to be in the new work tree for the checkout */ + setup_work_tree(); + + fd = hold_locked_index(lock_file, 1); + + memset(&opts, 0, sizeof opts); + opts.update = 1; + opts.verbose_update = !option_quiet; + opts.dst_index = &the_index; + + tree = parse_tree_indirect(remote_head->old_sha1); + parse_tree(tree); + init_tree_desc(&t, tree->buffer, tree->size); + unpack_trees(1, &t, &opts); + + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die("unable to write new index file"); + } + + strbuf_release(&reflog_msg); + junk_pid = 0; + return 0; +} diff --git a/builtin.h b/builtin.h index 95126fd0c1..23a90ded7d 100644 --- a/builtin.h +++ b/builtin.h @@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); +extern int cmd_clone(int argc, const char **argv, const char *prefix); extern int cmd_clean(int argc, const char **argv, const char *prefix); extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); diff --git a/git-clone.sh b/contrib/examples/git-clone.sh similarity index 100% rename from git-clone.sh rename to contrib/examples/git-clone.sh diff --git a/git.c b/git.c index 89b431fa28..2c9004f02e 100644 --- a/git.c +++ b/git.c @@ -286,6 +286,7 @@ static void handle_internal_command(int argc, const char **argv) { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, + { "clone", cmd_clone }, { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, From a73bc1275bb0939c51c496b1d50c516e6314eab2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 15 May 2008 10:48:25 +0100 Subject: [PATCH 09/14] builtin-clone: fix initial checkout Somewhere in the process of finishing up builtin-clone, the update of the working tree was lost. This was due to not using the option "merge" for unpack_trees(). Breakage noticed by Kevin Ballard. Signed-off-by: Johannes Schindelin Tested-by: Jeff King Acked-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-clone.c | 3 +++ t/t5601-clone.sh | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/builtin-clone.c b/builtin-clone.c index a7c075d0e2..8713128e72 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -525,7 +525,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof opts); opts.update = 1; + opts.merge = 1; + opts.fn = oneway_merge; opts.verbose_update = !option_quiet; + opts.src_index = &the_index; opts.dst_index = &the_index; tree = parse_tree_indirect(remote_head->old_sha1); diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index dc9d63dbf9..593d1a3877 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -23,4 +23,11 @@ test_expect_success 'clone with excess parameters' ' ' +test_expect_success 'clone checks out files' ' + + git clone src dst && + test -f dst/file + +' + test_done From 689ef4d42e1adf409b632ed5c36beda4852a4720 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Sat, 17 May 2008 23:00:01 -0500 Subject: [PATCH 10/14] builtin-clone.c: Need to closedir() in copy_or_link_directory() So not to leak file descriptors, close the directory after opening it. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- builtin-clone.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-clone.c b/builtin-clone.c index 8713128e72..8936a51810 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -215,6 +215,7 @@ static void copy_or_link_directory(char *src, char *dest) die("failed to create link %s\n", dest); } } + closedir(dir); } static const struct ref *clone_local(const char *src_repo, From fdabc242f465771116e08d1ea3737fdb7453b2f1 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 20 May 2008 14:15:14 -0400 Subject: [PATCH 11/14] clone: fall back to copying if hardlinking fails Note that it stops trying hardlinks if any fail. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-clone.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/builtin-clone.c b/builtin-clone.c index 8936a51810..2a3f6732f2 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -207,13 +207,15 @@ static void copy_or_link_directory(char *src, char *dest) if (unlink(dest) && errno != ENOENT) die("failed to unlink %s\n", dest); - if (option_no_hardlinks) { - if (copy_file(dest, src, 0666)) - die("failed to copy file to %s\n", dest); - } else { - if (link(src, dest)) + if (!option_no_hardlinks) { + if (!link(src, dest)) + continue; + if (option_local) die("failed to create link %s\n", dest); + option_no_hardlinks = 1; } + if (copy_file(dest, src, 0666)) + die("failed to copy file to %s\n", dest); } closedir(dir); } From 4ba776c231f27e69435d76bac98d033db859cd6f Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Thu, 22 May 2008 18:03:08 -0400 Subject: [PATCH 12/14] Test that --reference actually suppresses fetching referenced objects Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- t/t5700-clone-reference.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index b6a54867b4..58a97f1ed1 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -8,6 +8,8 @@ test_description='test clone --reference' base_dir=`pwd` +U=$base_dir/UPLOAD_LOG + test_expect_success 'preparing first repository' \ 'test_create_repo A && cd A && echo first > file1 && @@ -50,8 +52,13 @@ diff expected current' cd "$base_dir" +rm -f $U + test_expect_success 'cloning with reference (no -l -s)' \ -'git clone --reference B file://`pwd`/A D' +'GIT_DEBUG_SEND_PACK=3 git clone --reference B file://`pwd`/A D 3>$U' + +test_expect_success 'fetched no objects' \ +'! grep "^want" $U' cd "$base_dir" From fabb01996be9f7c8862ab1a6fdfd83c90be5324a Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Thu, 22 May 2008 18:03:05 -0400 Subject: [PATCH 13/14] Add a test for another combination of --reference In this case, the reference repository has some useful loose objects, but not all useful objects, and we make sure that we can find the objects we fetch from the repository we're cloning in the new repository, instead of potentially being distracted by the reference repository. Doing the wrong thing in a builtin-clone implementation would lead to this looking for an object in the wrong place, not finding it (because it's only in the right place), and crashing. Signed-off-by: Johan Herland Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- t/t5700-clone-reference.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index 58a97f1ed1..b53c3ff956 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -120,4 +120,25 @@ diff expected current' cd "$base_dir" +test_expect_success 'preparing alternate repository #1' \ +'test_create_repo F && cd F && +echo first > file1 && +git add file1 && +git commit -m initial' + +cd "$base_dir" + +test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \ +'git clone F G && cd F && +echo second > file2 && +git add file2 && +git commit -m addition' + +cd "$base_dir" + +test_expect_success 'cloning alternate repo #1, using #2 as reference' \ +'git clone --reference G F H' + +cd "$base_dir" + test_done From b50c8469cc9a336b22ef37b23711d4547a48bc2b Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Thu, 22 May 2008 18:03:00 -0400 Subject: [PATCH 14/14] Add test for cloning with "--reference" repo being a subset of source repo The first test in this series tests "git clone -l -s --reference B A C", where repo B is a superset of repo A (A has one commit, B has the same commit plus another). In this case, all objects to be cloned are already present in B. However, we should also test the case where the "--reference" repo is a _subset_ of the source repo (e.g. "git clone -l -s --reference A B C"), i.e. some objects are not available in the "--reference" repo, and will have to be found in the source repo. Signed-off-by: Johan Herland Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- t/t5700-clone-reference.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index b53c3ff956..0112c218e0 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -141,4 +141,9 @@ test_expect_success 'cloning alternate repo #1, using #2 as reference' \ cd "$base_dir" +test_expect_success 'cloning with reference being subset of source (-l -s)' \ +'git clone -l -s --reference A B E' + +cd "$base_dir" + test_done