Commit e090a568 by Edward Thomson

Merge pull request #1010 from ethomson/fetch_head

create FETCH_HEAD specially instead of as a ref file
parents d18713fb b0f6e45d
......@@ -401,6 +401,23 @@ GIT_EXTERN(int) git_remote_rename(
int (*callback)(const char *problematic_refspec, void *payload),
void *payload);
/**
* Retrieve the update FETCH_HEAD setting.
*
* @param remote the remote to query
* @return the update FETCH_HEAD setting
*/
GIT_EXTERN(int) git_remote_update_fetchhead(git_remote *remote);
/**
* Sets the update FETCH_HEAD setting. By default, FETCH_HEAD will be
* updated on every fetch. Set to 0 to disable.
*
* @param remote the remote to configure
* @param value 0 to disable updating FETCH_HEAD
*/
GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value);
/** @} */
GIT_END_DECL
#endif
......@@ -268,6 +268,11 @@ int git_branch_tracking(
if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0)
goto cleanup;
if (remote_name == NULL || merge_name == NULL) {
error = GIT_ENOTFOUND;
goto cleanup;
}
if (strcmp(".", remote_name) != 0) {
if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0)
......
......@@ -271,6 +271,12 @@ static int setup_remotes_and_fetch(
/* Create the "origin" remote */
if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) {
/*
* Don't write FETCH_HEAD, we'll check out the remote tracking
* branch ourselves based on the server's default.
*/
git_remote_set_update_fetchhead(origin, 0);
/* Connect and download everything */
if (!git_remote_connect(origin, GIT_DIR_FETCH)) {
if (!git_remote_download(origin, progress_cb, progress_payload)) {
......
/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/types.h"
#include "git2/oid.h"
#include "fetchhead.h"
#include "common.h"
#include "fileops.h"
#include "filebuf.h"
#include "refs.h"
#include "repository.h"
int git_fetchhead_ref_cmp(const void *a, const void *b)
{
const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
if (one->is_merge && !two->is_merge)
return -1;
if (two->is_merge && !one->is_merge)
return 1;
return strcmp(one->ref_name, two->ref_name);
}
int git_fetchhead_ref_create(
git_fetchhead_ref **fetchhead_ref_out,
git_oid *oid,
int is_merge,
const char *ref_name,
const char *remote_url)
{
git_fetchhead_ref *fetchhead_ref = NULL;
assert(fetchhead_ref_out && oid && ref_name && remote_url);
fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
GITERR_CHECK_ALLOC(fetchhead_ref);
memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
git_oid_cpy(&fetchhead_ref->oid, oid);
fetchhead_ref->is_merge = is_merge;
fetchhead_ref->ref_name = git__strdup(ref_name);
fetchhead_ref->remote_url = git__strdup(remote_url);
*fetchhead_ref_out = fetchhead_ref;
return 0;
}
static int fetchhead_ref_write(
git_filebuf *file,
git_fetchhead_ref *fetchhead_ref)
{
char oid[GIT_OID_HEXSZ + 1];
const char *type, *name;
assert(file && fetchhead_ref);
git_oid_fmt(oid, &fetchhead_ref->oid);
oid[GIT_OID_HEXSZ] = '\0';
if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
type = "branch ";
name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
} else if(git__prefixcmp(fetchhead_ref->ref_name,
GIT_REFS_TAGS_DIR) == 0) {
type = "tag ";
name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
} else {
type = "";
name = fetchhead_ref->ref_name;
}
return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
oid,
(fetchhead_ref->is_merge) ? "" : "not-for-merge",
type,
name,
fetchhead_ref->remote_url);
}
int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf path = GIT_BUF_INIT;
unsigned int i;
git_fetchhead_ref *fetchhead_ref;
assert(repo && fetchhead_refs);
if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
return -1;
if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) {
git_buf_free(&path);
return -1;
}
git_buf_free(&path);
git_vector_sort(fetchhead_refs);
git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
fetchhead_ref_write(&file, fetchhead_ref);
return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
}
void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
{
if (fetchhead_ref == NULL)
return;
git__free(fetchhead_ref->remote_url);
git__free(fetchhead_ref->ref_name);
git__free(fetchhead_ref);
}
/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_fetchhead_h__
#define INCLUDE_fetchhead_h__
#include "vector.h"
typedef struct git_fetchhead_ref {
git_oid oid;
unsigned int is_merge;
char *ref_name;
char *remote_url;
} git_fetchhead_ref;
int git_fetchhead_ref_create(git_fetchhead_ref **fetchhead_ref_out, git_oid *oid, int is_merge, const char *ref_name, const char *remote_url);
int git_fetchhead_ref_cmp(const void *a, const void *b);
int git_fetchhead_write(git_repository *repository, git_vector *fetchhead_refs);
void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
#endif
......@@ -236,3 +236,10 @@ int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
return git_buf_oom(out) == false;
}
int git_refspec_is_wildcard(const git_refspec *spec)
{
assert(spec && spec->src);
return (spec->src[strlen(spec->src) - 1] == '*');
}
......@@ -53,4 +53,12 @@ int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *n
int git_refspec__serialize(git_buf *out, const git_refspec *refspec);
/**
* Determines if a refspec is a wildcard refspec.
*
* @param spec the refspec
* @return 1 if the refspec is a wildcard, 0 otherwise
*/
int git_refspec_is_wildcard(const git_refspec *spec);
#endif
......@@ -14,6 +14,8 @@
#include "remote.h"
#include "fetch.h"
#include "refs.h"
#include "refspec.h"
#include "fetchhead.h"
#include <regex.h>
......@@ -68,6 +70,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
memset(remote, 0x0, sizeof(git_remote));
remote->repo = repo;
remote->check_cert = 1;
remote->update_fetchhead = 1;
if (git_vector_init(&remote->refs, 32, NULL) < 0)
return -1;
......@@ -116,6 +119,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
memset(remote, 0x0, sizeof(git_remote));
remote->check_cert = 1;
remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
......@@ -539,6 +543,120 @@ static int update_tips_callback(git_remote_head *head, void *payload)
return 0;
}
static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
{
unsigned int i;
git_remote_head *remote_ref;
assert(update_heads && fetchspec_src);
*out = NULL;
git_vector_foreach(update_heads, i, remote_ref) {
if (strcmp(remote_ref->name, fetchspec_src) == 0) {
*out = remote_ref;
break;
}
}
return 0;
}
static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
{
git_reference *resolved_ref = NULL;
git_reference *tracking_ref = NULL;
git_buf remote_name = GIT_BUF_INIT;
int error = 0;
assert(out && remote && ref);
*out = NULL;
if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
(!git_reference_is_branch(resolved_ref)) ||
(error = git_branch_tracking(&tracking_ref, resolved_ref)) < 0 ||
(error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {
/* Not an error if HEAD is orphaned or no tracking branch */
if (error == GIT_ENOTFOUND)
error = 0;
goto cleanup;
}
error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
cleanup:
git_reference_free(tracking_ref);
git_reference_free(resolved_ref);
git_buf_free(&remote_name);
return error;
}
static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads)
{
struct git_refspec *spec;
git_reference *head_ref = NULL;
git_fetchhead_ref *fetchhead_ref;
git_remote_head *remote_ref, *merge_remote_ref;
git_vector fetchhead_refs;
bool include_all_fetchheads;
unsigned int i = 0;
int error = 0;
assert(remote);
spec = &remote->fetch;
if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
return -1;
/* Iff refspec is * (but not subdir slash star), include tags */
include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);
/* Determine what to merge: if refspec was a wildcard, just use HEAD */
if (git_refspec_is_wildcard(spec)) {
if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||
(error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0)
goto cleanup;
} else {
/* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
goto cleanup;
}
/* Create the FETCH_HEAD file */
git_vector_foreach(update_heads, i, remote_ref) {
int merge_this_fetchhead = (merge_remote_ref == remote_ref);
if (!include_all_fetchheads &&
!git_refspec_src_matches(spec, remote_ref->name) &&
!merge_this_fetchhead)
continue;
if (git_fetchhead_ref_create(&fetchhead_ref,
&remote_ref->oid,
merge_this_fetchhead,
remote_ref->name,
git_remote_url(remote)) < 0)
goto cleanup;
if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
goto cleanup;
}
git_fetchhead_write(remote->repo, &fetchhead_refs);
cleanup:
for (i = 0; i < fetchhead_refs.length; ++i)
git_fetchhead_ref_free(fetchhead_refs.contents[i]);
git_vector_free(&fetchhead_refs);
git_reference_free(head_ref);
return error;
}
int git_remote_update_tips(git_remote *remote)
{
int error = 0, autotag;
......@@ -550,7 +668,7 @@ int git_remote_update_tips(git_remote *remote)
git_reference *ref;
struct git_refspec *spec;
git_refspec tagspec;
git_vector refs;
git_vector refs, update_heads;
assert(remote);
......@@ -563,7 +681,8 @@ int git_remote_update_tips(git_remote *remote)
return -1;
/* Make a copy of the transport's refs */
if (git_vector_init(&refs, 16, NULL) < 0)
if (git_vector_init(&refs, 16, NULL) < 0 ||
git_vector_init(&update_heads, 16, NULL) < 0)
return -1;
if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0)
......@@ -611,6 +730,9 @@ int git_remote_update_tips(git_remote *remote)
if (autotag && !git_odb_exists(odb, &head->oid))
continue;
if (git_vector_insert(&update_heads, head) < 0)
goto on_error;
error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
......@@ -634,13 +756,19 @@ int git_remote_update_tips(git_remote *remote)
}
}
if (git_remote_update_fetchhead(remote) &&
(error = git_remote_write_fetchhead(remote, &update_heads)) < 0)
goto on_error;
git_vector_free(&refs);
git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return 0;
on_error:
git_vector_free(&refs);
git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return -1;
......@@ -1131,3 +1259,13 @@ int git_remote_rename(
return 0;
}
int git_remote_update_fetchhead(git_remote *remote)
{
return remote->update_fetchhead;
}
void git_remote_set_update_fetchhead(git_remote *remote, int value)
{
remote->update_fetchhead = value;
}
......@@ -29,7 +29,8 @@ struct git_remote {
git_transfer_progress stats;
unsigned int need_pack:1,
download_tags:2, /* There are four possible values */
check_cert:1;
check_cert:1,
update_fetchhead:1;
};
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
......
#define FETCH_HEAD_WILDCARD_DATA \
"49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n"
#define FETCH_HEAD_NO_MERGE_DATA \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
"49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
"d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
"55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
"8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n"
#define FETCH_HEAD_EXPLICIT_DATA \
"0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n"
#include "clar_libgit2.h"
#include "repository.h"
#include "fetchhead.h"
#include "fetchhead_data.h"
#include "git2/clone.h"
CL_IN_CATEGORY("network")
#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
static git_repository *g_repo;
void test_fetchhead_network__initialize(void)
{
g_repo = NULL;
}
static void cleanup_repository(void *path)
{
if (g_repo)
git_repository_free(g_repo);
cl_fixture_cleanup((const char *)path);
}
static void fetchhead_test_clone(void)
{
cl_set_cleanup(&cleanup_repository, "./test1");
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test1", NULL, NULL, NULL));
}
static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
{
git_remote *remote;
git_buf fetchhead_buf = GIT_BUF_INIT;
int equals = 0;
cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO);
if(fetchspec != NULL)
git_remote_set_fetchspec(remote, fetchspec);
cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH));
cl_git_pass(git_remote_download(remote, NULL, NULL));
git_remote_disconnect(remote);
cl_git_pass(git_remote_update_tips(remote));
git_remote_free(remote);
cl_git_pass(git_futils_readbuffer(&fetchhead_buf,
"./test1/.git/FETCH_HEAD"));
equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0);
git_buf_free(&fetchhead_buf);
cl_assert(equals);
}
void test_fetchhead_network__wildcard_spec(void)
{
fetchhead_test_clone();
fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA);
}
void test_fetchhead_network__explicit_spec(void)
{
fetchhead_test_clone();
fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA);
}
void test_fetchhead_network__no_merges(void)
{
git_config *config;
fetchhead_test_clone();
cl_git_pass(git_repository_config(&config, g_repo));
cl_git_pass(git_config_set_string(config, "branch.master.remote", NULL));
cl_git_pass(git_config_set_string(config, "branch.master.merge", NULL));
git_config_free(config);
fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA);
}
#include "clar_libgit2.h"
#include "repository.h"
#include "fetchhead.h"
#include "fetchhead_data.h"
#define DO_LOCAL_TEST 0
static git_repository *g_repo;
void test_fetchhead_nonetwork__initialize(void)
{
g_repo = NULL;
}
static void cleanup_repository(void *path)
{
if (g_repo)
git_repository_free(g_repo);
cl_fixture_cleanup((const char *)path);
}
void test_fetchhead_nonetwork__write(void)
{
git_vector fetchhead_vector;
git_fetchhead_ref *fetchhead[6];
git_oid oid[6];
git_buf fetchhead_buf = GIT_BUF_INIT;
size_t i;
int equals = 0;
git_vector_init(&fetchhead_vector, 6, NULL);
cl_set_cleanup(&cleanup_repository, "./test1");
cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
cl_git_pass(git_oid_fromstr(&oid[0],
"49322bb17d3acc9146f98c97d078513228bbf3c0"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[0], &oid[0], 1,
"refs/heads/master",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[0]));
cl_git_pass(git_oid_fromstr(&oid[1],
"0966a434eb1a025db6b71485ab63a3bfbea520b6"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[1], &oid[1], 0,
"refs/heads/first-merge",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[1]));
cl_git_pass(git_oid_fromstr(&oid[2],
"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[2], &oid[2], 0,
"refs/heads/no-parent",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[2]));
cl_git_pass(git_oid_fromstr(&oid[3],
"d96c4e80345534eccee5ac7b07fc7603b56124cb"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[3], &oid[3], 0,
"refs/tags/annotated_tag",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[3]));
cl_git_pass(git_oid_fromstr(&oid[4],
"55a1a760df4b86a02094a904dfa511deb5655905"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[4], &oid[4], 0,
"refs/tags/blob",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[4]));
cl_git_pass(git_oid_fromstr(&oid[5],
"8f50ba15d49353813cc6e20298002c0d17b0a9ee"));
cl_git_pass(git_fetchhead_ref_create(&fetchhead[5], &oid[5], 0,
"refs/tags/commit_tree",
"git://github.com/libgit2/TestGitRepository"));
cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[5]));
git_fetchhead_write(g_repo, &fetchhead_vector);
cl_git_pass(git_futils_readbuffer(&fetchhead_buf,
"./test1/.git/FETCH_HEAD"));
equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA) == 0);
for (i=0; i < 6; i++)
git_fetchhead_ref_free(fetchhead[i]);
git_buf_free(&fetchhead_buf);
git_vector_free(&fetchhead_vector);
cl_assert(equals);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment