mirror of
https://github.com/git/git.git
synced 2024-11-15 14:14:08 +01:00
e4022ed2c8
If we are using a native transport and the transport chose to save the packfile it may have created a .keep file to protect the packfile from a concurrently running git-repack process. In such a case the git-fetch process should make sure it will unlink the .keep file even if it fails to update any refs as otherwise the newly downloaded packfile's diskspace will never be reclaimed if the objects are not actually referenced. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
540 lines
13 KiB
C
540 lines
13 KiB
C
/*
|
|
* "git fetch"
|
|
*/
|
|
#include "cache.h"
|
|
#include "refs.h"
|
|
#include "commit.h"
|
|
#include "builtin.h"
|
|
#include "path-list.h"
|
|
#include "remote.h"
|
|
#include "transport.h"
|
|
|
|
static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upload-pack>] [-f | --force] [--no-tags] [-t | --tags] [-k | --keep] [-u | --update-head-ok] [--depth <depth>] [-v | --verbose] [<repository> <refspec>...]";
|
|
|
|
static int append, force, tags, no_tags, update_head_ok, verbose, quiet;
|
|
static char *default_rla = NULL;
|
|
static struct transport *transport;
|
|
|
|
static void unlock_pack(void)
|
|
{
|
|
if (transport)
|
|
transport_unlock_pack(transport);
|
|
}
|
|
|
|
static void unlock_pack_on_signal(int signo)
|
|
{
|
|
unlock_pack();
|
|
signal(SIGINT, SIG_DFL);
|
|
raise(signo);
|
|
}
|
|
|
|
static void find_merge_config(struct ref *ref_map, struct remote *remote)
|
|
{
|
|
struct ref *rm = ref_map;
|
|
struct branch *branch = branch_get(NULL);
|
|
|
|
for (rm = ref_map; rm; rm = rm->next) {
|
|
if (!branch_has_merge_config(branch)) {
|
|
if (remote && remote->fetch &&
|
|
!strcmp(remote->fetch[0].src, rm->name))
|
|
rm->merge = 1;
|
|
} else {
|
|
if (branch_merges(branch, rm->name))
|
|
rm->merge = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct ref *get_ref_map(struct transport *transport,
|
|
struct refspec *refs, int ref_count, int tags,
|
|
int *autotags)
|
|
{
|
|
int i;
|
|
struct ref *rm;
|
|
struct ref *ref_map = NULL;
|
|
struct ref **tail = &ref_map;
|
|
|
|
struct ref *remote_refs = transport_get_remote_refs(transport);
|
|
|
|
if (ref_count || tags) {
|
|
for (i = 0; i < ref_count; i++) {
|
|
get_fetch_map(remote_refs, &refs[i], &tail);
|
|
if (refs[i].dst && refs[i].dst[0])
|
|
*autotags = 1;
|
|
}
|
|
/* Merge everything on the command line, but not --tags */
|
|
for (rm = ref_map; rm; rm = rm->next)
|
|
rm->merge = 1;
|
|
if (tags) {
|
|
struct refspec refspec;
|
|
refspec.src = "refs/tags/";
|
|
refspec.dst = "refs/tags/";
|
|
refspec.pattern = 1;
|
|
refspec.force = 0;
|
|
get_fetch_map(remote_refs, &refspec, &tail);
|
|
}
|
|
} else {
|
|
/* Use the defaults */
|
|
struct remote *remote = transport->remote;
|
|
if (remote->fetch_refspec_nr) {
|
|
for (i = 0; i < remote->fetch_refspec_nr; i++) {
|
|
get_fetch_map(remote_refs, &remote->fetch[i], &tail);
|
|
if (remote->fetch[i].dst &&
|
|
remote->fetch[i].dst[0])
|
|
*autotags = 1;
|
|
}
|
|
find_merge_config(ref_map, remote);
|
|
} else {
|
|
ref_map = get_remote_ref(remote_refs, "HEAD");
|
|
|
|
ref_map->merge = 1;
|
|
}
|
|
}
|
|
|
|
return ref_map;
|
|
}
|
|
|
|
static void show_new(enum object_type type, unsigned char *sha1_new)
|
|
{
|
|
fprintf(stderr, " %s: %s\n", typename(type),
|
|
find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
|
|
}
|
|
|
|
static int s_update_ref(const char *action,
|
|
struct ref *ref,
|
|
int check_old)
|
|
{
|
|
char msg[1024];
|
|
char *rla = getenv("GIT_REFLOG_ACTION");
|
|
static struct ref_lock *lock;
|
|
|
|
if (!rla)
|
|
rla = default_rla;
|
|
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
|
|
lock = lock_any_ref_for_update(ref->name,
|
|
check_old ? ref->old_sha1 : NULL, 0);
|
|
if (!lock)
|
|
return 1;
|
|
if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int update_local_ref(struct ref *ref,
|
|
const char *note,
|
|
int verbose)
|
|
{
|
|
char oldh[41], newh[41];
|
|
struct commit *current = NULL, *updated;
|
|
enum object_type type;
|
|
struct branch *current_branch = branch_get(NULL);
|
|
|
|
type = sha1_object_info(ref->new_sha1, NULL);
|
|
if (type < 0)
|
|
die("object %s not found", sha1_to_hex(ref->new_sha1));
|
|
|
|
if (!*ref->name) {
|
|
/* Not storing */
|
|
if (verbose) {
|
|
fprintf(stderr, "* fetched %s\n", note);
|
|
show_new(type, ref->new_sha1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
|
|
if (verbose) {
|
|
fprintf(stderr, "* %s: same as %s\n",
|
|
ref->name, note);
|
|
show_new(type, ref->new_sha1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!strcmp(ref->name, current_branch->name) &&
|
|
!(update_head_ok || is_bare_repository()) &&
|
|
!is_null_sha1(ref->old_sha1)) {
|
|
/*
|
|
* If this is the head, and it's not okay to update
|
|
* the head, and the old value of the head isn't empty...
|
|
*/
|
|
fprintf(stderr,
|
|
" * %s: Cannot fetch into the current branch.\n",
|
|
ref->name);
|
|
return 1;
|
|
}
|
|
|
|
if (!is_null_sha1(ref->old_sha1) &&
|
|
!prefixcmp(ref->name, "refs/tags/")) {
|
|
fprintf(stderr, "* %s: updating with %s\n",
|
|
ref->name, note);
|
|
show_new(type, ref->new_sha1);
|
|
return s_update_ref("updating tag", ref, 0);
|
|
}
|
|
|
|
current = lookup_commit_reference(ref->old_sha1);
|
|
updated = lookup_commit_reference(ref->new_sha1);
|
|
if (!current || !updated) {
|
|
char *msg;
|
|
if (!strncmp(ref->name, "refs/tags/", 10))
|
|
msg = "storing tag";
|
|
else
|
|
msg = "storing head";
|
|
fprintf(stderr, "* %s: storing %s\n",
|
|
ref->name, note);
|
|
show_new(type, ref->new_sha1);
|
|
return s_update_ref(msg, ref, 0);
|
|
}
|
|
|
|
strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
|
|
strcpy(newh, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
|
|
|
|
if (in_merge_bases(current, &updated, 1)) {
|
|
fprintf(stderr, "* %s: fast forward to %s\n",
|
|
ref->name, note);
|
|
fprintf(stderr, " old..new: %s..%s\n", oldh, newh);
|
|
return s_update_ref("fast forward", ref, 1);
|
|
}
|
|
if (!force && !ref->force) {
|
|
fprintf(stderr,
|
|
"* %s: not updating to non-fast forward %s\n",
|
|
ref->name, note);
|
|
fprintf(stderr,
|
|
" old...new: %s...%s\n", oldh, newh);
|
|
return 1;
|
|
}
|
|
fprintf(stderr,
|
|
"* %s: forcing update to non-fast forward %s\n",
|
|
ref->name, note);
|
|
fprintf(stderr, " old...new: %s...%s\n", oldh, newh);
|
|
return s_update_ref("forced-update", ref, 1);
|
|
}
|
|
|
|
static void store_updated_refs(const char *url, struct ref *ref_map)
|
|
{
|
|
FILE *fp;
|
|
struct commit *commit;
|
|
int url_len, i, note_len;
|
|
char note[1024];
|
|
const char *what, *kind;
|
|
struct ref *rm;
|
|
|
|
fp = fopen(git_path("FETCH_HEAD"), "a");
|
|
for (rm = ref_map; rm; rm = rm->next) {
|
|
struct ref *ref = NULL;
|
|
|
|
if (rm->peer_ref) {
|
|
ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
|
|
strcpy(ref->name, rm->peer_ref->name);
|
|
hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
|
|
hashcpy(ref->new_sha1, rm->old_sha1);
|
|
ref->force = rm->peer_ref->force;
|
|
}
|
|
|
|
commit = lookup_commit_reference(rm->old_sha1);
|
|
if (!commit)
|
|
rm->merge = 0;
|
|
|
|
if (!strcmp(rm->name, "HEAD")) {
|
|
kind = "";
|
|
what = "";
|
|
}
|
|
else if (!prefixcmp(rm->name, "refs/heads/")) {
|
|
kind = "branch";
|
|
what = rm->name + 11;
|
|
}
|
|
else if (!prefixcmp(rm->name, "refs/tags/")) {
|
|
kind = "tag";
|
|
what = rm->name + 10;
|
|
}
|
|
else if (!prefixcmp(rm->name, "refs/remotes/")) {
|
|
kind = "remote branch";
|
|
what = rm->name + 13;
|
|
}
|
|
else {
|
|
kind = "";
|
|
what = rm->name;
|
|
}
|
|
|
|
url_len = strlen(url);
|
|
for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
|
|
;
|
|
url_len = i + 1;
|
|
if (4 < i && !strncmp(".git", url + i - 3, 4))
|
|
url_len = i - 3;
|
|
|
|
note_len = 0;
|
|
if (*what) {
|
|
if (*kind)
|
|
note_len += sprintf(note + note_len, "%s ",
|
|
kind);
|
|
note_len += sprintf(note + note_len, "'%s' of ", what);
|
|
}
|
|
note_len += sprintf(note + note_len, "%.*s", url_len, url);
|
|
fprintf(fp, "%s\t%s\t%s\n",
|
|
sha1_to_hex(commit ? commit->object.sha1 :
|
|
rm->old_sha1),
|
|
rm->merge ? "" : "not-for-merge",
|
|
note);
|
|
|
|
if (ref)
|
|
update_local_ref(ref, note, verbose);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
static int fetch_refs(struct transport *transport, struct ref *ref_map)
|
|
{
|
|
int ret = transport_fetch_refs(transport, ref_map);
|
|
if (!ret)
|
|
store_updated_refs(transport->url, ref_map);
|
|
transport_unlock_pack(transport);
|
|
return ret;
|
|
}
|
|
|
|
static int add_existing(const char *refname, const unsigned char *sha1,
|
|
int flag, void *cbdata)
|
|
{
|
|
struct path_list *list = (struct path_list *)cbdata;
|
|
path_list_insert(refname, list);
|
|
return 0;
|
|
}
|
|
|
|
static struct ref *find_non_local_tags(struct transport *transport,
|
|
struct ref *fetch_map)
|
|
{
|
|
static struct path_list existing_refs = { NULL, 0, 0, 0 };
|
|
struct path_list new_refs = { NULL, 0, 0, 1 };
|
|
char *ref_name;
|
|
int ref_name_len;
|
|
unsigned char *ref_sha1;
|
|
struct ref *tag_ref;
|
|
struct ref *rm = NULL;
|
|
struct ref *ref_map = NULL;
|
|
struct ref **tail = &ref_map;
|
|
struct ref *ref;
|
|
|
|
for_each_ref(add_existing, &existing_refs);
|
|
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
|
|
if (prefixcmp(ref->name, "refs/tags"))
|
|
continue;
|
|
|
|
ref_name = xstrdup(ref->name);
|
|
ref_name_len = strlen(ref_name);
|
|
ref_sha1 = ref->old_sha1;
|
|
|
|
if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
|
|
ref_name[ref_name_len - 3] = 0;
|
|
tag_ref = transport_get_remote_refs(transport);
|
|
while (tag_ref) {
|
|
if (!strcmp(tag_ref->name, ref_name)) {
|
|
ref_sha1 = tag_ref->old_sha1;
|
|
break;
|
|
}
|
|
tag_ref = tag_ref->next;
|
|
}
|
|
}
|
|
|
|
if (!path_list_has_path(&existing_refs, ref_name) &&
|
|
!path_list_has_path(&new_refs, ref_name) &&
|
|
lookup_object(ref->old_sha1)) {
|
|
fprintf(stderr, "Auto-following %s\n",
|
|
ref_name);
|
|
|
|
path_list_insert(ref_name, &new_refs);
|
|
|
|
rm = alloc_ref(strlen(ref_name) + 1);
|
|
strcpy(rm->name, ref_name);
|
|
rm->peer_ref = alloc_ref(strlen(ref_name) + 1);
|
|
strcpy(rm->peer_ref->name, ref_name);
|
|
hashcpy(rm->old_sha1, ref_sha1);
|
|
|
|
*tail = rm;
|
|
tail = &rm->next;
|
|
}
|
|
free(ref_name);
|
|
}
|
|
|
|
return ref_map;
|
|
}
|
|
|
|
static int do_fetch(struct transport *transport,
|
|
struct refspec *refs, int ref_count)
|
|
{
|
|
struct ref *ref_map, *fetch_map;
|
|
struct ref *rm;
|
|
int autotags = (transport->remote->fetch_tags == 1);
|
|
if (transport->remote->fetch_tags == 2 && !no_tags)
|
|
tags = 1;
|
|
if (transport->remote->fetch_tags == -1)
|
|
no_tags = 1;
|
|
|
|
if (!transport->ops || !transport->ops->get_refs_list ||
|
|
!transport->ops->fetch)
|
|
die("Don't know how to fetch from %s", transport->url);
|
|
|
|
/* if not appending, truncate FETCH_HEAD */
|
|
if (!append)
|
|
fclose(fopen(git_path("FETCH_HEAD"), "w"));
|
|
|
|
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
|
|
|
|
for (rm = ref_map; rm; rm = rm->next) {
|
|
if (rm->peer_ref)
|
|
read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
|
|
}
|
|
|
|
if (fetch_refs(transport, ref_map)) {
|
|
free_refs(ref_map);
|
|
return 1;
|
|
}
|
|
|
|
fetch_map = ref_map;
|
|
|
|
/* if neither --no-tags nor --tags was specified, do automated tag
|
|
* following ... */
|
|
if (!(tags || no_tags) && autotags) {
|
|
ref_map = find_non_local_tags(transport, fetch_map);
|
|
if (ref_map) {
|
|
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
|
|
fetch_refs(transport, ref_map);
|
|
}
|
|
free_refs(ref_map);
|
|
}
|
|
|
|
free_refs(fetch_map);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cmd_fetch(int argc, const char **argv, const char *prefix)
|
|
{
|
|
struct remote *remote;
|
|
int i, j, rla_offset;
|
|
static const char **refs = NULL;
|
|
int ref_nr = 0;
|
|
int cmd_len = 0;
|
|
const char *depth = NULL, *upload_pack = NULL;
|
|
int keep = 0;
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
cmd_len += strlen(arg);
|
|
|
|
if (arg[0] != '-')
|
|
break;
|
|
if (!strcmp(arg, "--append") || !strcmp(arg, "-a")) {
|
|
append = 1;
|
|
continue;
|
|
}
|
|
if (!prefixcmp(arg, "--upload-pack=")) {
|
|
upload_pack = arg + 14;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--upload-pack")) {
|
|
i++;
|
|
if (i == argc)
|
|
usage(fetch_usage);
|
|
upload_pack = argv[i];
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
|
|
force = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--no-tags")) {
|
|
no_tags = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--tags") || !strcmp(arg, "-t")) {
|
|
tags = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--keep") || !strcmp(arg, "-k")) {
|
|
keep = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--update-head-ok") || !strcmp(arg, "-u")) {
|
|
update_head_ok = 1;
|
|
continue;
|
|
}
|
|
if (!prefixcmp(arg, "--depth=")) {
|
|
depth = arg + 8;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--depth")) {
|
|
i++;
|
|
if (i == argc)
|
|
usage(fetch_usage);
|
|
depth = argv[i];
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--quiet")) {
|
|
quiet = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) {
|
|
verbose++;
|
|
continue;
|
|
}
|
|
usage(fetch_usage);
|
|
}
|
|
|
|
for (j = i; j < argc; j++)
|
|
cmd_len += strlen(argv[j]);
|
|
|
|
default_rla = xmalloc(cmd_len + 5 + argc + 1);
|
|
sprintf(default_rla, "fetch");
|
|
rla_offset = strlen(default_rla);
|
|
for (j = 1; j < argc; j++) {
|
|
sprintf(default_rla + rla_offset, " %s", argv[j]);
|
|
rla_offset += strlen(argv[j]) + 1;
|
|
}
|
|
|
|
if (i == argc)
|
|
remote = remote_get(NULL);
|
|
else
|
|
remote = remote_get(argv[i++]);
|
|
|
|
transport = transport_get(remote, remote->uri[0], 1);
|
|
if (verbose >= 2)
|
|
transport->verbose = 1;
|
|
if (quiet)
|
|
transport->verbose = 0;
|
|
if (upload_pack)
|
|
transport_set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
|
|
if (keep)
|
|
transport_set_option(transport, TRANS_OPT_KEEP, "yes");
|
|
transport_set_option(transport, TRANS_OPT_DEPTH, depth);
|
|
|
|
if (!transport->url)
|
|
die("Where do you want to fetch from today?");
|
|
|
|
if (i < argc) {
|
|
int j = 0;
|
|
refs = xcalloc(argc - i + 1, sizeof(const char *));
|
|
while (i < argc) {
|
|
if (!strcmp(argv[i], "tag")) {
|
|
char *ref;
|
|
i++;
|
|
ref = xmalloc(strlen(argv[i]) * 2 + 22);
|
|
strcpy(ref, "refs/tags/");
|
|
strcat(ref, argv[i]);
|
|
strcat(ref, ":refs/tags/");
|
|
strcat(ref, argv[i]);
|
|
refs[j++] = ref;
|
|
} else
|
|
refs[j++] = argv[i];
|
|
i++;
|
|
}
|
|
refs[j] = NULL;
|
|
ref_nr = j;
|
|
for (j = 0; refs[j]; j++)
|
|
printf("ref: %s\n", refs[j]);
|
|
}
|
|
|
|
signal(SIGINT, unlock_pack_on_signal);
|
|
atexit(unlock_pack);
|
|
return do_fetch(transport, parse_ref_spec(ref_nr, refs), ref_nr);
|
|
}
|