author | Junio C Hamano <gitster@pobox.com> | |
Mon, 15 Mar 2010 07:58:42 +0000 (00:58 -0700) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Mon, 15 Mar 2010 07:58:42 +0000 (00:58 -0700) |
* tc/transport-verbosity:
transport: update flags to be in running order
fetch and pull: learn --progress
push: learn --progress
transport->progress: use flag authoritatively
clone: support multiple levels of verbosity
push: support multiple levels of verbosity
fetch: refactor verbosity option handling into transport.[ch]
Documentation/git-push: put --quiet before --verbose
Documentation/git-pull: put verbosity options before merge/fetch ones
Documentation/git-clone: mention progress in -v
Conflicts:
transport.h
transport: update flags to be in running order
fetch and pull: learn --progress
push: learn --progress
transport->progress: use flag authoritatively
clone: support multiple levels of verbosity
push: support multiple levels of verbosity
fetch: refactor verbosity option handling into transport.[ch]
Documentation/git-push: put --quiet before --verbose
Documentation/git-pull: put verbosity options before merge/fetch ones
Documentation/git-clone: mention progress in -v
Conflicts:
transport.h
1 | 2 | |||
---|---|---|---|---|
Documentation/git-clone.txt | patch | | diff1 | | diff2 | | blob | history |
Documentation/git-push.txt | patch | | diff1 | | diff2 | | blob | history |
builtin/clone.c | patch | | diff1 | | | | blob | history |
builtin/fetch.c | patch | | diff1 | | | | blob | history |
builtin/push.c | patch | | diff1 | | | | blob | history |
transport-helper.c | patch | | diff1 | | diff2 | | blob | history |
transport.c | patch | | diff1 | | diff2 | | blob | history |
transport.h | patch | | diff1 | | diff2 | | blob | history |
diff --cc Documentation/git-clone.txt
Simple merge
diff --cc Documentation/git-push.txt
index 49b6bd9d925f9150a4aaf2f2c4d7439503863d05,8d957244bd6cba71e0387da1dbcffe2ae1da30d6..59dc8b197ecb4b6912c9ec29f83f21c405ef8634
--thin::
--no-thin::
- These options are passed to 'git send-pack'. Thin
- transfer spends extra cycles to minimize the number of
- objects to be sent and meant to be used on slower connection.
+ These options are passed to linkgit:git-send-pack[1]. A thin transfer
+ significantly reduces the amount of sent data when the sender and
+ receiver share many of the same objects in common. The default is
+ \--thin.
+ -q::
+ --quiet::
+ Suppress all output, including the listing of updated refs,
+ unless an error occurs. Progress is not reported to the standard
+ error stream.
+
-v::
--verbose::
Run verbosely.
diff --cc builtin/clone.c
index 58bacbd552c1e2496034346265a3e5ab219e2672,0000000000000000000000000000000000000000..05f8fb4771b1ef07030338a6fd38dc7cb3bc1d1d
mode 100644,000000..100644
mode 100644,000000..100644
--- 1/builtin/clone.c
--- /dev/null
+++ b/builtin/clone.c
- static int option_quiet, option_no_checkout, option_bare, option_mirror;
+/*
+ * Builtin "git clone"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ * 2008 Daniel Barkalow <barkalow@iabervon.org>
+ * 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"
+#include "pack-refs.h"
+#include "sigchain.h"
+#include "branch.h"
+#include "remote.h"
+#include "run-command.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] [--] <repo> [<dir>]",
+ NULL
+};
+
- static int option_verbose;
++static int option_no_checkout, option_bare, option_mirror;
+static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static char *option_template, *option_reference, *option_depth;
+static char *option_origin = NULL;
+static char *option_branch = NULL;
+static char *option_upload_pack = "git-upload-pack";
- OPT__QUIET(&option_quiet),
- OPT__VERBOSE(&option_verbose),
++static int option_verbosity;
+static int option_progress;
+
+static struct option builtin_clone_options[] = {
- init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
++ OPT__VERBOSITY(&option_verbosity),
+ OPT_BOOLEAN(0, "progress", &option_progress,
+ "force progress reporting"),
+ OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
+ "don't create a checkout"),
+ OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
+ { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
+ "create a bare repository",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_BOOLEAN(0, "mirror", &option_mirror,
+ "create a mirror repository (implies bare)"),
+ 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_BOOLEAN(0, "recursive", &option_recursive,
+ "initialize submodules in the clone"),
+ 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 <branch> instead of 'origin' to track upstream"),
+ OPT_STRING('b', "branch", &option_branch, "branch",
+ "checkout <branch> instead of the remote's HEAD"),
+ 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 const char *argv_submodule[] = {
+ "submodule", "update", "--init", "--recursive", NULL
+};
+
+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 (is_directory(path)) {
+ *is_bundle = 0;
+ return xstrdup(make_nonrelative_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_nonrelative_path(path));
+ }
+ }
+
+ return NULL;
+}
+
+static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
+{
+ const char *end = repo + strlen(repo), *start;
+ char *dir;
+
+ /*
+ * Strip trailing spaces, slashes and /.git
+ */
+ while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+ end--;
+ if (end - repo > 5 && is_dir_sep(end[-5]) &&
+ !strncmp(end - 4, ".git", 4)) {
+ end -= 5;
+ while (repo < end && is_dir_sep(end[-1]))
+ end--;
+ }
+
+ /*
+ * Find last component, but be prepared that repo could have
+ * the form "remote.example.com:foo.git", i.e. no slash
+ * in the directory part.
+ */
+ start = end;
+ while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
+ start--;
+
+ /*
+ * Strip .{bundle,git}.
+ */
+ if (is_bundle) {
+ if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
+ end -= 7;
+ } else {
+ if (end - start > 4 && !strncmp(end - 4, ".git", 4))
+ end -= 4;
+ }
+
+ if (is_bare) {
+ struct strbuf result = STRBUF_INIT;
+ strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
+ dir = strbuf_detach(&result, NULL);
+ } else
+ dir = xstrndup(start, end - start);
+ /*
+ * Replace sequences of 'control' characters and whitespace
+ * with one ascii space, remove leading and trailing spaces.
+ */
+ if (*dir) {
+ char *out = dir;
+ int prev_space = 1 /* strip leading whitespace */;
+ for (end = dir; *end; ++end) {
+ char ch = *end;
+ if ((unsigned char)ch < '\x20')
+ ch = '\x20';
+ if (isspace(ch)) {
+ if (prev_space)
+ continue;
+ prev_space = 1;
+ } else
+ prev_space = 0;
+ *out++ = ch;
+ }
+ *out = '\0';
+ if (out > dir && prev_space)
+ out[-1] = '\0';
+ }
+ return dir;
+}
+
+static void strip_trailing_slashes(char *dir)
+{
+ char *end = dir + strlen(dir);
+
+ while (dir < end - 1 && is_dir_sep(end[-1]))
+ end--;
+ *end = '\0';
+}
+
+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(struct strbuf *src, struct strbuf *dest)
+{
+ struct dirent *de;
+ struct stat buf;
+ int src_len, dest_len;
+ DIR *dir;
+
+ dir = opendir(src->buf);
+ if (!dir)
+ die_errno("failed to open '%s'", src->buf);
+
+ if (mkdir(dest->buf, 0777)) {
+ if (errno != EEXIST)
+ die_errno("failed to create directory '%s'", dest->buf);
+ else if (stat(dest->buf, &buf))
+ die_errno("failed to stat '%s'", dest->buf);
+ else if (!S_ISDIR(buf.st_mode))
+ die("%s exists and is not a directory", dest->buf);
+ }
+
+ strbuf_addch(src, '/');
+ src_len = src->len;
+ strbuf_addch(dest, '/');
+ dest_len = dest->len;
+
+ while ((de = readdir(dir)) != NULL) {
+ strbuf_setlen(src, src_len);
+ strbuf_addstr(src, de->d_name);
+ strbuf_setlen(dest, dest_len);
+ strbuf_addstr(dest, de->d_name);
+ if (stat(src->buf, &buf)) {
+ warning ("failed to stat %s\n", src->buf);
+ continue;
+ }
+ if (S_ISDIR(buf.st_mode)) {
+ if (de->d_name[0] != '.')
+ copy_or_link_directory(src, dest);
+ continue;
+ }
+
+ if (unlink(dest->buf) && errno != ENOENT)
+ die_errno("failed to unlink '%s'", dest->buf);
+ if (!option_no_hardlinks) {
+ if (!link(src->buf, dest->buf))
+ continue;
+ if (option_local)
+ die_errno("failed to create link '%s'", dest->buf);
+ option_no_hardlinks = 1;
+ }
+ if (copy_file_with_time(dest->buf, src->buf, 0666))
+ die_errno("failed to copy file to '%s'", dest->buf);
+ }
+ closedir(dir);
+}
+
+static const struct ref *clone_local(const char *src_repo,
+ const char *dest_repo)
+{
+ const struct ref *ret;
+ struct strbuf src = STRBUF_INIT;
+ struct strbuf dest = STRBUF_INIT;
+ struct remote *remote;
+ struct transport *transport;
+
+ if (option_shared)
+ add_to_alternates_file(src_repo);
+ else {
+ strbuf_addf(&src, "%s/objects", src_repo);
+ strbuf_addf(&dest, "%s/objects", dest_repo);
+ copy_or_link_directory(&src, &dest);
+ strbuf_release(&src);
+ strbuf_release(&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;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (getpid() != junk_pid)
+ return;
+ 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();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static struct ref *wanted_peer_refs(const struct ref *refs,
+ struct refspec *refspec)
+{
+ struct ref *local_refs = NULL;
+ struct ref **tail = &local_refs;
+
+ get_fetch_map(refs, refspec, &tail, 0);
+ if (!option_mirror)
+ get_fetch_map(refs, tag_refspec, &tail, 0);
+
+ return local_refs;
+}
+
+static void write_remote_refs(const struct ref *local_refs)
+{
+ const struct ref *r;
+
+ for (r = local_refs; r; r = r->next)
+ add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+
+ pack_refs(PACK_REFS_ALL);
+ clear_extra_refs();
+}
+
+int cmd_clone(int argc, const char **argv, const char *prefix)
+{
+ int is_bundle = 0;
+ struct stat buf;
+ const char *repo_name, *repo, *work_tree, *git_dir;
+ char *path, *dir;
+ int dest_exists;
+ const struct ref *refs, *remote_head;
+ const struct ref *remote_head_points_at;
+ const struct ref *our_head_points_at;
+ struct ref *mapped_refs;
+ struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
+ struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
+ struct transport *transport = NULL;
+ char *src_ref_prefix = "refs/heads/";
+ int err = 0;
+
+ struct refspec *refspec;
+ const char *fetch_pattern;
+
+ junk_pid = getpid();
+
+ argc = parse_options(argc, argv, prefix, builtin_clone_options,
+ builtin_clone_usage, 0);
+
+ if (argc > 2)
+ usage_msg_opt("Too many arguments.",
+ builtin_clone_usage, builtin_clone_options);
+
+ if (argc == 0)
+ usage_msg_opt("You must specify a repository to clone.",
+ builtin_clone_usage, builtin_clone_options);
+
+ if (option_mirror)
+ option_bare = 1;
+
+ if (option_bare) {
+ if (option_origin)
+ die("--bare and --origin %s options are incompatible.",
+ option_origin);
+ option_no_checkout = 1;
+ }
+
+ if (!option_origin)
+ option_origin = "origin";
+
+ repo_name = argv[0];
+
+ path = get_repo_path(repo_name, &is_bundle);
+ if (path)
+ repo = xstrdup(make_nonrelative_path(repo_name));
+ 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, option_bare);
+ strip_trailing_slashes(dir);
+
+ dest_exists = !stat(dir, &buf);
+ if (dest_exists && !is_empty_dir(dir))
+ die("destination path '%s' already exists and is not "
+ "an empty directory.", dir);
+
+ 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 (safe_create_leading_directories_const(work_tree) < 0)
+ die_errno("could not create leading directories of '%s'",
+ work_tree);
+ if (!dest_exists && mkdir(work_tree, 0755))
+ die_errno("could not create work tree dir '%s'.",
+ work_tree);
+ set_git_work_tree(work_tree);
+ }
+ junk_git_dir = git_dir;
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
+
+ if (safe_create_leading_directories_const(git_dir) < 0)
+ die("could not create leading directories of '%s'", git_dir);
+ set_git_dir(make_absolute_path(git_dir));
+
- if (option_quiet)
- transport->verbose = -1;
- else if (option_verbose)
- transport->verbose = 1;
-
- if (option_progress)
- transport->progress = 1;
++ init_db(option_template, (option_verbosity < 0) ? INIT_DB_QUIET : 0);
+
+ /*
+ * At this point, the config exists, so we do not need the
+ * environment variable. We actually need to unset it, too, to
+ * re-enable parsing of the global configs.
+ */
+ unsetenv(CONFIG_ENVIRONMENT);
+
+ if (option_reference)
+ setup_reference(git_dir);
+
+ git_config(git_default_config, NULL);
+
+ if (option_bare) {
+ if (option_mirror)
+ src_ref_prefix = "refs/";
+ strbuf_addstr(&branch_top, src_ref_prefix);
+
+ git_config_set("core.bare", "true");
+ } else {
+ strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
+ }
+
+ strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+
+ if (option_mirror || !option_bare) {
+ /* Configure the remote */
+ strbuf_addf(&key, "remote.%s.fetch", option_origin);
+ git_config_set_multivar(key.buf, value.buf, "^$", 0);
+ strbuf_reset(&key);
+
+ if (option_mirror) {
+ strbuf_addf(&key, "remote.%s.mirror", option_origin);
+ git_config_set(key.buf, "true");
+ strbuf_reset(&key);
+ }
+
+ strbuf_addf(&key, "remote.%s.url", option_origin);
+ git_config_set(key.buf, repo);
+ strbuf_reset(&key);
+ }
+
+ fetch_pattern = value.buf;
+ refspec = parse_fetch_refspec(1, &fetch_pattern);
+
+ strbuf_reset(&value);
+
+ if (path && !is_bundle) {
+ refs = clone_local(path, git_dir);
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ } else {
+ struct remote *remote = remote_get(argv[0]);
+ transport = transport_get(remote, remote->url[0]);
+
+ if (!transport->get_refs_list || !transport->fetch)
+ die("Don't know how to clone %s", transport->url);
+
+ transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+
+ if (option_depth)
+ transport_set_option(transport, TRANS_OPT_DEPTH,
+ option_depth);
+
- opts.verbose_update = !option_quiet;
++ transport_set_verbosity(transport, option_verbosity, option_progress);
+
+ if (option_upload_pack)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+ option_upload_pack);
+
+ refs = transport_get_remote_refs(transport);
+ if (refs) {
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ transport_fetch_refs(transport, mapped_refs);
+ }
+ }
+
+ if (refs) {
+ clear_extra_refs();
+
+ write_remote_refs(mapped_refs);
+
+ remote_head = find_ref_by_name(refs, "HEAD");
+ remote_head_points_at =
+ guess_remote_head(remote_head, mapped_refs, 0);
+
+ if (option_branch) {
+ struct strbuf head = STRBUF_INIT;
+ strbuf_addstr(&head, src_ref_prefix);
+ strbuf_addstr(&head, option_branch);
+ our_head_points_at =
+ find_ref_by_name(mapped_refs, head.buf);
+ strbuf_release(&head);
+
+ if (!our_head_points_at) {
+ warning("Remote branch %s not found in "
+ "upstream %s, using HEAD instead",
+ option_branch, option_origin);
+ our_head_points_at = remote_head_points_at;
+ }
+ }
+ else
+ our_head_points_at = remote_head_points_at;
+ }
+ else {
+ warning("You appear to have cloned an empty repository.");
+ our_head_points_at = NULL;
+ remote_head_points_at = NULL;
+ remote_head = NULL;
+ option_no_checkout = 1;
+ if (!option_bare)
+ install_branch_config(0, "master", option_origin,
+ "refs/heads/master");
+ }
+
+ if (remote_head_points_at && !option_bare) {
+ struct strbuf head_ref = STRBUF_INIT;
+ strbuf_addstr(&head_ref, branch_top.buf);
+ strbuf_addstr(&head_ref, "HEAD");
+ create_symref(head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ reflog_msg.buf);
+ }
+
+ if (our_head_points_at) {
+ /* Local default branch link */
+ create_symref("HEAD", our_head_points_at->name, NULL);
+ if (!option_bare) {
+ const char *head = skip_prefix(our_head_points_at->name,
+ "refs/heads/");
+ update_ref(reflog_msg.buf, "HEAD",
+ our_head_points_at->old_sha1,
+ NULL, 0, DIE_ON_ERR);
+ install_branch_config(0, head, option_origin,
+ our_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);
+ our_head_points_at = remote_head;
+ }
+ } 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 (transport) {
+ transport_unlock_pack(transport);
+ transport_disconnect(transport);
+ }
+
+ 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.merge = 1;
+ opts.fn = oneway_merge;
++ opts.verbose_update = (option_verbosity > 0);
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ tree = parse_tree_indirect(our_head_points_at->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");
+
+ err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ sha1_to_hex(our_head_points_at->old_sha1), "1",
+ NULL);
+
+ if (!err && option_recursive)
+ err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+ }
+
+ strbuf_release(&reflog_msg);
+ strbuf_release(&branch_top);
+ strbuf_release(&key);
+ strbuf_release(&value);
+ junk_pid = 0;
+ return err;
+}
diff --cc builtin/fetch.c
index b6c5b344be8e8abb90cf3f03d3cea2600511787f,0000000000000000000000000000000000000000..2bb75c130526d506b066c890cf165302dbb3c14d
mode 100644,000000..100644
mode 100644,000000..100644
--- 1/builtin/fetch.c
--- /dev/null
+++ b/builtin/fetch.c
- if (verbosity >= 2)
- transport->verbose = verbosity <= 3 ? verbosity : 3;
- if (verbosity < 0)
- transport->verbose = -1;
+/*
+ * "git fetch"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "remote.h"
+#include "transport.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "sigchain.h"
+#include "transport.h"
+
+static const char * const builtin_fetch_usage[] = {
+ "git fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] <group>",
+ "git fetch --multiple [options] [<repository> | <group>]...",
+ "git fetch --all [options]",
+ NULL
+};
+
+enum {
+ TAGS_UNSET = 0,
+ TAGS_DEFAULT = 1,
+ TAGS_SET = 2
+};
+
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
++static int progress;
+static int tags = TAGS_DEFAULT;
+static const char *depth;
+static const char *upload_pack;
+static struct strbuf default_rla = STRBUF_INIT;
+static struct transport *transport;
+
+static struct option builtin_fetch_options[] = {
+ OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "all", &all,
+ "fetch from all remotes"),
+ OPT_BOOLEAN('a', "append", &append,
+ "append to .git/FETCH_HEAD instead of overwriting"),
+ OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ "path to upload pack on remote end"),
+ OPT_BOOLEAN('f', "force", &force,
+ "force overwrite of local branch"),
+ OPT_BOOLEAN('m', "multiple", &multiple,
+ "fetch from multiple remotes"),
+ OPT_SET_INT('t', "tags", &tags,
+ "fetch all tags and associated objects", TAGS_SET),
+ OPT_SET_INT('n', NULL, &tags,
+ "do not fetch all tags (--no-tags)", TAGS_UNSET),
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune tracking branches no longer on remote"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run,
+ "dry run"),
+ OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+ OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ "allow updating of HEAD ref"),
++ OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_STRING(0, "depth", &depth, "DEPTH",
+ "deepen history of shallow clone"),
+ OPT_END()
+};
+
+static void unlock_pack(void)
+{
+ if (transport)
+ transport_unlock_pack(transport);
+}
+
+static void unlock_pack_on_signal(int signo)
+{
+ unlock_pack();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static void add_merge_config(struct ref **head,
+ const struct ref *remote_refs,
+ struct branch *branch,
+ struct ref ***tail)
+{
+ int i;
+
+ for (i = 0; i < branch->merge_nr; i++) {
+ struct ref *rm, **old_tail = *tail;
+ struct refspec refspec;
+
+ for (rm = *head; rm; rm = rm->next) {
+ if (branch_merge_matches(branch, i, rm->name)) {
+ rm->merge = 1;
+ break;
+ }
+ }
+ if (rm)
+ continue;
+
+ /*
+ * Not fetched to a tracking branch? We need to fetch
+ * it anyway to allow this branch's "branch.$name.merge"
+ * to be honored by 'git pull', but we do not have to
+ * fail if branch.$name.merge is misconfigured to point
+ * at a nonexisting branch. If we were indeed called by
+ * 'git pull', it will notice the misconfiguration because
+ * there is no entry in the resulting FETCH_HEAD marked
+ * for merging.
+ */
+ refspec.src = branch->merge[i]->src;
+ refspec.dst = NULL;
+ refspec.pattern = 0;
+ refspec.force = 0;
+ get_fetch_map(remote_refs, &refspec, tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->merge = 1;
+ }
+}
+
+static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail);
+
+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;
+
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
+
+ if (ref_count || tags == TAGS_SET) {
+ for (i = 0; i < ref_count; i++) {
+ get_fetch_map(remote_refs, &refs[i], &tail, 0);
+ 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 == TAGS_SET)
+ get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ } else {
+ /* Use the defaults */
+ struct remote *remote = transport->remote;
+ struct branch *branch = branch_get(NULL);
+ int has_merge = branch_has_merge_config(branch);
+ if (remote && (remote->fetch_refspec_nr || has_merge)) {
+ for (i = 0; i < remote->fetch_refspec_nr; i++) {
+ get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
+ if (remote->fetch[i].dst &&
+ remote->fetch[i].dst[0])
+ *autotags = 1;
+ if (!i && !has_merge && ref_map &&
+ !remote->fetch[0].pattern)
+ ref_map->merge = 1;
+ }
+ /*
+ * if the remote we're fetching from is the same
+ * as given in branch.<name>.remote, we add the
+ * ref given in branch.<name>.merge, too.
+ */
+ if (has_merge &&
+ !strcmp(branch->remote_name, remote->name))
+ add_merge_config(&ref_map, remote_refs, branch, &tail);
+ } else {
+ ref_map = get_remote_ref(remote_refs, "HEAD");
+ if (!ref_map)
+ die("Couldn't find remote ref HEAD");
+ ref_map->merge = 1;
+ tail = &ref_map->next;
+ }
+ }
+ if (tags == TAGS_DEFAULT && *autotags)
+ find_non_local_tags(transport, &ref_map, &tail);
+ ref_remove_duplicates(ref_map);
+
+ return ref_map;
+}
+
+#define STORE_REF_ERROR_OTHER 1
+#define STORE_REF_ERROR_DF_CONFLICT 2
+
+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 (dry_run)
+ return 0;
+ if (!rla)
+ rla = default_rla.buf;
+ 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 errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
+ return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ return 0;
+}
+
+#define REFCOL_WIDTH 10
+
+static int update_local_ref(struct ref *ref,
+ const char *remote,
+ char *display)
+{
+ struct commit *current = NULL, *updated;
+ enum object_type type;
+ struct branch *current_branch = branch_get(NULL);
+ const char *pretty_ref = prettify_refname(ref->name);
+
+ *display = 0;
+ type = sha1_object_info(ref->new_sha1, NULL);
+ if (type < 0)
+ die("object %s not found", sha1_to_hex(ref->new_sha1));
+
+ if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+ if (verbosity > 0)
+ sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
+ "[up to date]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 0;
+ }
+
+ if (current_branch &&
+ !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...
+ */
+ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
+ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+
+ if (!is_null_sha1(ref->old_sha1) &&
+ !prefixcmp(ref->name, "refs/tags/")) {
+ int r;
+ r = s_update_ref("updating tag", ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
+ TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ current = lookup_commit_reference_gently(ref->old_sha1, 1);
+ updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+ if (!current || !updated) {
+ const char *msg;
+ const char *what;
+ int r;
+ if (!strncmp(ref->name, "refs/tags/", 10)) {
+ msg = "storing tag";
+ what = "[new tag]";
+ }
+ else {
+ msg = "storing head";
+ what = "[new branch]";
+ }
+
+ r = s_update_ref(msg, ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
+ TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
+ r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ if (in_merge_bases(current, &updated, 1)) {
+ char quickref[83];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "..");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("fast-forward", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
+ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ } else if (force || ref->force) {
+ char quickref[84];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "...");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("forced-update", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
+ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref,
+ r ? "unable to update local ref" : "forced update");
+ return r;
+ } else {
+ sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
+ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+}
+
+static int store_updated_refs(const char *raw_url, const char *remote_name,
+ struct ref *ref_map)
+{
+ FILE *fp;
+ struct commit *commit;
+ int url_len, i, note_len, shown_url = 0, rc = 0;
+ char note[1024];
+ const char *what, *kind;
+ struct ref *rm;
+ char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+
+ if (raw_url)
+ url = transport_anonymize_url(raw_url);
+ else
+ url = xstrdup("foreign");
+ 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_gently(rm->old_sha1, 1);
+ 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[note_len] = '\0';
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(commit ? commit->object.sha1 :
+ rm->old_sha1),
+ rm->merge ? "" : "not-for-merge",
+ note);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+
+ if (ref)
+ rc |= update_local_ref(ref, what, note);
+ else
+ sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
+ TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
+ REFCOL_WIDTH, *what ? what : "HEAD");
+ if (*note) {
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, "From %.*s\n",
+ url_len, url);
+ shown_url = 1;
+ }
+ if (verbosity >= 0)
+ fprintf(stderr, " %s\n", note);
+ }
+ }
+ free(url);
+ fclose(fp);
+ if (rc & STORE_REF_ERROR_DF_CONFLICT)
+ error("some local refs could not be updated; try running\n"
+ " 'git remote prune %s' to remove any old, conflicting "
+ "branches", remote_name);
+ return rc;
+}
+
+/*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and is connected
+ * locally.
+ *
+ * The refs we are going to fetch are in ref_map. If running
+ *
+ * $ git rev-list --objects --stdin --not --all
+ *
+ * (feeding all the refs in ref_map on its standard input)
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+static int quickfetch(struct ref *ref_map)
+{
+ struct child_process revlist;
+ struct ref *ref;
+ int err;
+ const char *argv[] = {"rev-list",
+ "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+
+ /*
+ * If we are deepening a shallow clone we already have these
+ * objects reachable. Running rev-list here will return with
+ * a good (0) exit status and we'll bypass the fetch that we
+ * really need to perform. Claiming failure now will ensure
+ * we perform the network exchange to deepen our history.
+ */
+ if (depth)
+ return -1;
+
+ if (!ref_map)
+ return 0;
+
+ memset(&revlist, 0, sizeof(revlist));
+ revlist.argv = argv;
+ revlist.git_cmd = 1;
+ revlist.no_stdout = 1;
+ revlist.no_stderr = 1;
+ revlist.in = -1;
+
+ err = start_command(&revlist);
+ if (err) {
+ error("could not run rev-list");
+ return err;
+ }
+
+ /*
+ * If rev-list --stdin encounters an unknown commit, it terminates,
+ * which will cause SIGPIPE in the write loop below.
+ */
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ for (ref = ref_map; ref; ref = ref->next) {
+ if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
+ write_str_in_full(revlist.in, "\n") < 0) {
+ if (errno != EPIPE && errno != EINVAL)
+ error("failed write to rev-list: %s", strerror(errno));
+ err = -1;
+ break;
+ }
+ }
+
+ if (close(revlist.in)) {
+ error("failed to close rev-list's stdin: %s", strerror(errno));
+ err = -1;
+ }
+
+ sigchain_pop(SIGPIPE);
+
+ return finish_command(&revlist) || err;
+}
+
+static int fetch_refs(struct transport *transport, struct ref *ref_map)
+{
+ int ret = quickfetch(ref_map);
+ if (ret)
+ ret = transport_fetch_refs(transport, ref_map);
+ if (!ret)
+ ret |= store_updated_refs(transport->url,
+ transport->remote->name,
+ ref_map);
+ transport_unlock_pack(transport);
+ return ret;
+}
+
+static int prune_refs(struct transport *transport, struct ref *ref_map)
+{
+ int result = 0;
+ struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ const char *dangling_msg = dry_run
+ ? " (%s will become dangling)\n"
+ : " (%s has become dangling)\n";
+
+ for (ref = stale_refs; ref; ref = ref->next) {
+ if (!dry_run)
+ result |= delete_ref(ref->name, NULL, 0);
+ if (verbosity >= 0) {
+ fprintf(stderr, " x %-*s %-*s -> %s\n",
+ TRANSPORT_SUMMARY_WIDTH, "[deleted]",
+ REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+ warn_dangling_symref(stderr, dangling_msg, ref->name);
+ }
+ }
+ free_refs(stale_refs);
+ return result;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1,
+ int flag, void *cbdata)
+{
+ struct string_list *list = (struct string_list *)cbdata;
+ struct string_list_item *item = string_list_insert(refname, list);
+ item->util = (void *)sha1;
+ return 0;
+}
+
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+ struct ref *rm = *head;
+ while (rm) {
+ if (!hashcmp(rm->old_sha1, sha1))
+ return 1;
+ rm = rm->next;
+ }
+ return 0;
+}
+
+struct tag_data {
+ struct ref **head;
+ struct ref ***tail;
+};
+
+static int add_to_tail(struct string_list_item *item, void *cb_data)
+{
+ struct tag_data *data = (struct tag_data *)cb_data;
+ struct ref *rm = NULL;
+
+ /* We have already decided to ignore this item */
+ if (!item->util)
+ return 0;
+
+ rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+
+ **data->tail = rm;
+ *data->tail = &rm->next;
+
+ return 0;
+}
+
+static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail)
+{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list remote_refs = { NULL, 0, 0, 0 };
+ struct tag_data data = {head, tail};
+ const struct ref *ref;
+ struct string_list_item *item = NULL;
+
+ 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;
+
+ /*
+ * The peeled ref always follows the matching base
+ * ref, so if we see a peeled ref that we don't want
+ * to fetch then we can mark the ref entry in the list
+ * as one to ignore by setting util to NULL.
+ */
+ if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
+ if (item && !has_sha1_file(ref->old_sha1) &&
+ !will_fetch(head, ref->old_sha1) &&
+ !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+ item = NULL;
+ continue;
+ }
+
+ /*
+ * If item is non-NULL here, then we previously saw a
+ * ref not followed by a peeled reference, so we need
+ * to check if it is a lightweight tag that we want to
+ * fetch.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ item = NULL;
+
+ /* skip duplicates and refs that we already have */
+ if (string_list_has_string(&remote_refs, ref->name) ||
+ string_list_has_string(&existing_refs, ref->name))
+ continue;
+
+ item = string_list_insert(ref->name, &remote_refs);
+ item->util = (void *)ref->old_sha1;
+ }
+ string_list_clear(&existing_refs, 0);
+
+ /*
+ * We may have a final lightweight tag that needs to be
+ * checked to see if it needs fetching.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ /*
+ * For all the tags in the remote_refs string list, call
+ * add_to_tail to add them to the list of refs to be fetched
+ */
+ for_each_string_list(add_to_tail, &remote_refs, &data);
+
+ string_list_clear(&remote_refs, 0);
+}
+
+static void check_not_current_branch(struct ref *ref_map)
+{
+ struct branch *current_branch = branch_get(NULL);
+
+ if (is_bare_repository() || !current_branch)
+ return;
+
+ for (; ref_map; ref_map = ref_map->next)
+ if (ref_map->peer_ref && !strcmp(current_branch->refname,
+ ref_map->peer_ref->name))
+ die("Refusing to fetch into current branch %s "
+ "of non-bare repository", current_branch->refname);
+}
+
+static int truncate_fetch_head(void)
+{
+ char *filename = git_path("FETCH_HEAD");
+ FILE *fp = fopen(filename, "w");
+
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+ fclose(fp);
+ return 0;
+}
+
+static int do_fetch(struct transport *transport,
+ struct refspec *refs, int ref_count)
+{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list_item *peer_item = NULL;
+ struct ref *ref_map;
+ struct ref *rm;
+ int autotags = (transport->remote->fetch_tags == 1);
+
+ for_each_ref(add_existing, &existing_refs);
+
+ if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
+ tags = TAGS_SET;
+ if (transport->remote->fetch_tags == -1)
+ tags = TAGS_UNSET;
+
+ if (!transport->get_refs_list || !transport->fetch)
+ die("Don't know how to fetch from %s", transport->url);
+
+ /* if not appending, truncate FETCH_HEAD */
+ if (!append && !dry_run) {
+ int errcode = truncate_fetch_head();
+ if (errcode)
+ return errcode;
+ }
+
+ ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+ if (!update_head_ok)
+ check_not_current_branch(ref_map);
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->peer_ref) {
+ peer_item = string_list_lookup(rm->peer_ref->name,
+ &existing_refs);
+ if (peer_item)
+ hashcpy(rm->peer_ref->old_sha1,
+ peer_item->util);
+ }
+ }
+
+ if (tags == TAGS_DEFAULT && autotags)
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (fetch_refs(transport, ref_map)) {
+ free_refs(ref_map);
+ return 1;
+ }
+ if (prune)
+ prune_refs(transport, ref_map);
+ free_refs(ref_map);
+
+ /* if neither --no-tags nor --tags was specified, do automated tag
+ * following ... */
+ if (tags == TAGS_DEFAULT && autotags) {
+ struct ref **tail = &ref_map;
+ ref_map = NULL;
+ find_non_local_tags(transport, &ref_map, &tail);
+ if (ref_map) {
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+ }
+ free_refs(ref_map);
+ }
+
+ return 0;
+}
+
+static void set_option(const char *name, const char *value)
+{
+ int r = transport_set_option(transport, name, value);
+ if (r < 0)
+ die("Option \"%s\" value \"%s\" is not valid for %s",
+ name, value, transport->url);
+ if (r > 0)
+ warning("Option \"%s\" is ignored for %s\n",
+ name, transport->url);
+}
+
+static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ if (!remote->skip_default_update)
+ string_list_append(remote->name, list);
+ return 0;
+}
+
+struct remote_group_data {
+ const char *name;
+ struct string_list *list;
+};
+
+static int get_remote_group(const char *key, const char *value, void *priv)
+{
+ struct remote_group_data *g = priv;
+
+ if (!prefixcmp(key, "remotes.") &&
+ !strcmp(key + 8, g->name)) {
+ /* split list by white space */
+ int space = strcspn(value, " \t\n");
+ while (*value) {
+ if (space > 1) {
+ string_list_append(xstrndup(value, space),
+ g->list);
+ }
+ value += space + (value[space] != '\0');
+ space = strcspn(value, " \t\n");
+ }
+ }
+
+ return 0;
+}
+
+static int add_remote_or_group(const char *name, struct string_list *list)
+{
+ int prev_nr = list->nr;
+ struct remote_group_data g = { name, list };
+
+ git_config(get_remote_group, &g);
+ if (list->nr == prev_nr) {
+ struct remote *remote;
+ if (!remote_is_configured(name))
+ return 0;
+ remote = remote_get(name);
+ string_list_append(remote->name, list);
+ }
+ return 1;
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+ int i, result = 0;
+ const char *argv[11] = { "fetch", "--append" };
+ int argc = 2;
+
+ if (dry_run)
+ argv[argc++] = "--dry-run";
+ if (prune)
+ argv[argc++] = "--prune";
+ if (update_head_ok)
+ argv[argc++] = "--update-head-ok";
+ if (force)
+ argv[argc++] = "--force";
+ if (keep)
+ argv[argc++] = "--keep";
+ if (verbosity >= 2)
+ argv[argc++] = "-v";
+ if (verbosity >= 1)
+ argv[argc++] = "-v";
+ else if (verbosity < 0)
+ argv[argc++] = "-q";
+
+ if (!append && !dry_run) {
+ int errcode = truncate_fetch_head();
+ if (errcode)
+ return errcode;
+ }
+
+ for (i = 0; i < list->nr; i++) {
+ const char *name = list->items[i].string;
+ argv[argc] = name;
+ argv[argc + 1] = NULL;
+ if (verbosity >= 0)
+ printf("Fetching %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+ error("Could not fetch %s", name);
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+static int fetch_one(struct remote *remote, int argc, const char **argv)
+{
+ int i;
+ static const char **refs = NULL;
+ int ref_nr = 0;
+ int exit_code;
+
+ if (!remote)
+ die("Where do you want to fetch from today?");
+
+ transport = transport_get(remote, NULL);
++ transport_set_verbosity(transport, verbosity, progress);
+ if (upload_pack)
+ set_option(TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(TRANS_OPT_DEPTH, depth);
+
+ if (argc > 0) {
+ int j = 0;
+ refs = xcalloc(argc + 1, sizeof(const char *));
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "tag")) {
+ char *ref;
+ i++;
+ if (i >= argc)
+ die("You need to specify a tag name.");
+ 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];
+ }
+ refs[j] = NULL;
+ ref_nr = j;
+ }
+
+ sigchain_push_common(unlock_pack_on_signal);
+ atexit(unlock_pack);
+ exit_code = do_fetch(transport,
+ parse_fetch_refspec(ref_nr, refs), ref_nr);
+ transport_disconnect(transport);
+ transport = NULL;
+ return exit_code;
+}
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct string_list list = { NULL, 0, 0, 0 };
+ struct remote *remote;
+ int result = 0;
+
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (all) {
+ if (argc == 1)
+ die("fetch --all does not take a repository argument");
+ else if (argc > 1)
+ die("fetch --all does not make sense with refspecs");
+ (void) for_each_remote(get_one_remote_for_fetch, &list);
+ result = fetch_multiple(&list);
+ } else if (argc == 0) {
+ /* No arguments -- use default remote */
+ remote = remote_get(NULL);
+ result = fetch_one(remote, argc, argv);
+ } else if (multiple) {
+ /* All arguments are assumed to be remotes or groups */
+ for (i = 0; i < argc; i++)
+ if (!add_remote_or_group(argv[i], &list))
+ die("No such remote or remote group: %s", argv[i]);
+ result = fetch_multiple(&list);
+ } else {
+ /* Single remote or group */
+ (void) add_remote_or_group(argv[0], &list);
+ if (list.nr > 1) {
+ /* More than one remote */
+ if (argc > 1)
+ die("Fetching a group and specifying refspecs does not make sense");
+ result = fetch_multiple(&list);
+ } else {
+ /* Zero or one remotes */
+ remote = remote_get(argv[0]);
+ result = fetch_one(remote, argc-1, argv+1);
+ }
+ }
+
+ /* All names were strdup()ed or strndup()ed */
+ list.strdup_strings = 1;
+ string_list_clear(&list, 0);
+
+ return result;
+}
diff --cc builtin/push.c
index 235ca12455e7447118f689040b019cc5aac7c8f3,0000000000000000000000000000000000000000..62957ededdf72b93f088586f5d577ef8d38a7f6c
mode 100644,000000..100644
mode 100644,000000..100644
--- 1/builtin/push.c
--- /dev/null
+++ b/builtin/push.c
- if (flags & TRANSPORT_PUSH_VERBOSE)
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+#include "remote.h"
+#include "transport.h"
+#include "parse-options.h"
+
+static const char * const push_usage[] = {
+ "git push [<options>] [<repository> <refspec>...]",
+ NULL,
+};
+
+static int thin;
+static int deleterefs;
+static const char *receivepack;
++static int verbosity;
++static int progress;
+
+static const char **refspec;
+static int refspec_nr;
+
+static void add_refspec(const char *ref)
+{
+ int nr = refspec_nr + 1;
+ refspec = xrealloc(refspec, nr * sizeof(char *));
+ refspec[nr-1] = ref;
+ refspec_nr = nr;
+}
+
+static void set_refspecs(const char **refs, int nr)
+{
+ int i;
+ for (i = 0; i < nr; i++) {
+ const char *ref = refs[i];
+ if (!strcmp("tag", ref)) {
+ char *tag;
+ int len;
+ if (nr <= ++i)
+ die("tag shorthand without <tag>");
+ len = strlen(refs[i]) + 11;
+ if (deleterefs) {
+ tag = xmalloc(len+1);
+ strcpy(tag, ":refs/tags/");
+ } else {
+ tag = xmalloc(len);
+ strcpy(tag, "refs/tags/");
+ }
+ strcat(tag, refs[i]);
+ ref = tag;
+ } else if (deleterefs && !strchr(ref, ':')) {
+ char *delref;
+ int len = strlen(ref)+1;
+ delref = xmalloc(len+1);
+ strcpy(delref, ":");
+ strcat(delref, ref);
+ ref = delref;
+ } else if (deleterefs)
+ die("--delete only accepts plain target ref names");
+ add_refspec(ref);
+ }
+}
+
+static void setup_push_tracking(void)
+{
+ struct strbuf refspec = STRBUF_INIT;
+ struct branch *branch = branch_get(NULL);
+ if (!branch)
+ die("You are not currently on a branch.");
+ if (!branch->merge_nr || !branch->merge)
+ die("The current branch %s is not tracking anything.",
+ branch->name);
+ if (branch->merge_nr != 1)
+ die("The current branch %s is tracking multiple branches, "
+ "refusing to push.", branch->name);
+ strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
+ add_refspec(refspec.buf);
+}
+
+static void setup_default_push_refspecs(void)
+{
+ switch (push_default) {
+ default:
+ case PUSH_DEFAULT_MATCHING:
+ add_refspec(":");
+ break;
+
+ case PUSH_DEFAULT_TRACKING:
+ setup_push_tracking();
+ break;
+
+ case PUSH_DEFAULT_CURRENT:
+ add_refspec("HEAD");
+ break;
+
+ case PUSH_DEFAULT_NOTHING:
+ die("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\".");
+ break;
+ }
+}
+
+static int push_with_options(struct transport *transport, int flags)
+{
+ int err;
+ int nonfastforward;
++
++ transport_set_verbosity(transport, verbosity, progress);
++
+ if (receivepack)
+ transport_set_option(transport,
+ TRANS_OPT_RECEIVEPACK, receivepack);
+ if (thin)
+ transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
- OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
- OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
++ if (verbosity > 0)
+ fprintf(stderr, "Pushing to %s\n", transport->url);
+ err = transport_push(transport, refspec_nr, refspec, flags,
+ &nonfastforward);
+ if (err != 0)
+ error("failed to push some refs to '%s'", transport->url);
+
+ err |= transport_disconnect(transport);
+
+ if (!err)
+ return 0;
+
+ if (nonfastforward && advice_push_nonfastforward) {
+ fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n"
+ "Merge the remote changes before pushing again. See the 'Note about\n"
+ "fast-forwards' section of 'git push --help' for details.\n");
+ }
+
+ return 1;
+}
+
+static int do_push(const char *repo, int flags)
+{
+ int i, errs;
+ struct remote *remote = remote_get(repo);
+ const char **url;
+ int url_nr;
+
+ if (!remote) {
+ if (repo)
+ die("bad repository '%s'", repo);
+ die("No destination configured to push to.");
+ }
+
+ if (remote->mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--all and --tags are incompatible");
+ return error("--all can't be combined with refspecs");
+ }
+
+ if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--mirror and --tags are incompatible");
+ return error("--mirror can't be combined with refspecs");
+ }
+
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+ (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+ return error("--all and --mirror are incompatible");
+ }
+
+ if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
+ if (remote->push_refspec_nr) {
+ refspec = remote->push_refspec;
+ refspec_nr = remote->push_refspec_nr;
+ } else if (!(flags & TRANSPORT_PUSH_MIRROR))
+ setup_default_push_refspecs();
+ }
+ errs = 0;
+ if (remote->pushurl_nr) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ } else {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+ if (url_nr) {
+ for (i = 0; i < url_nr; i++) {
+ struct transport *transport =
+ transport_get(remote, url[i]);
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ } else {
+ struct transport *transport =
+ transport_get(remote, NULL);
+
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ return !!errs;
+}
+
+int cmd_push(int argc, const char **argv, const char *prefix)
+{
+ int flags = 0;
+ int tags = 0;
+ int rc;
+ const char *repo = NULL; /* default repository */
+ struct option options[] = {
++ OPT__VERBOSITY(&verbosity),
+ OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
+ OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
+ OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
+ (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+ OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
+ OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
+ OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
+ OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
+ OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+ OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
+ OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
+ OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+ OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+ TRANSPORT_PUSH_SET_UPSTREAM),
++ OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+
+ if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+ die("--delete is incompatible with --all, --mirror and --tags");
+ if (deleterefs && argc < 2)
+ die("--delete doesn't make sense without any refs");
+
+ if (tags)
+ add_refspec("refs/tags/*");
+
+ if (argc > 0) {
+ repo = argv[0];
+ set_refspecs(argv + 1, argc - 1);
+ }
+
+ rc = do_push(repo, flags);
+ if (rc == -1)
+ usage_with_options(push_usage, options);
+ else
+ return rc;
+}
diff --cc transport-helper.c
Simple merge
diff --cc transport.c
index 825be9456c79a47726a6bbf35a6efb4d42d21033,e2cfabd7eeda2a6953b2785385f383f998525ca3..8ce39364a1e58b338bc45a4eb524b637ce3a8881
--- 1/transport.c
--- 2/transport.c
+++ b/transport.c
args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
args.use_thin_pack = data->options.thin;
- args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
- args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
+ args.verbose = (transport->verbose > 0);
+ args.quiet = (transport->verbose < 0);
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
+ args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
ret = send_pack(&args, data->fd, data->conn, remote_refs,
&data->extra_have);
transport->get_refs_list(transport, 1);
struct ref *local_refs = get_local_heads();
int match_flags = MATCH_REFS_NONE;
- int verbose = flags & TRANSPORT_PUSH_VERBOSE;
- int quiet = flags & TRANSPORT_PUSH_QUIET;
+ int verbose = (transport->verbose > 0);
+ int quiet = (transport->verbose < 0);
int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
- int ret, err;
+ int push_ret, ret, err;
if (flags & TRANSPORT_PUSH_ALL)
match_flags |= MATCH_REFS_ALL;
diff --cc transport.h
index 096f6e9478301395ca05d81eaac6e53e0acd69b0,d9bbabba0c092ef95069e5d2895d6451065cf256..c59d97388e6fdb1dccd4dca0d41dccc3d129f8fb
--- 1/transport.h
--- 2/transport.h
+++ b/transport.h
#define TRANSPORT_PUSH_FORCE 2
#define TRANSPORT_PUSH_DRY_RUN 4
#define TRANSPORT_PUSH_MIRROR 8
- #define TRANSPORT_PUSH_VERBOSE 16
- #define TRANSPORT_PUSH_PORCELAIN 32
- #define TRANSPORT_PUSH_QUIET 64
- #define TRANSPORT_PUSH_SET_UPSTREAM 128
+ #define TRANSPORT_PUSH_PORCELAIN 16
+ #define TRANSPORT_PUSH_SET_UPSTREAM 32
+
+#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
/* Returns a transport suitable for the url */
struct transport *transport_get(struct remote *, const char *);