struct refspec query;
memset(&query, 0, sizeof(struct refspec));
query.dst = tracking_branch;
- return !(remote_find_tracking(remote, &query) ||
- prefixcmp(query.src, "refs/heads/"));
+ return !remote_find_tracking(remote, &query);
}
static int validate_remote_tracking_branch(char *ref)
hashcpy(sha1, commit->object.sha1);
if (!dont_change_ref) {
- lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+ lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
if (!lock)
die_errno(_("Failed to lock ref for update"));
}
start_name);
if (real_ref && track)
- setup_tracking(ref.buf+11, real_ref, track, quiet);
+ setup_tracking(ref.buf + 11, real_ref, track, quiet);
if (!dont_change_ref)
if (write_ref_sha1(lock, sha1, msg) < 0)
#include "column.h"
#include "sequencer.h"
#include "notes-utils.h"
+#include "mailmap.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [options] [--] <pathspec>..."),
* and return the paths that match the given pattern in list.
*/
static int list_paths(struct string_list *list, const char *with_tree,
- const char *prefix, const char **pattern)
+ const char *prefix, const struct pathspec *pattern)
{
int i;
char *m;
- if (!pattern)
+ if (!pattern->nr)
return 0;
- for (i = 0; pattern[i]; i++)
- ;
- m = xcalloc(1, i);
+ m = xcalloc(1, pattern->nr);
if (with_tree) {
char *max_prefix = common_prefix(pattern);
if (ce->ce_flags & CE_UPDATE)
continue;
- if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+ if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
continue;
item = string_list_insert(list, ce->name);
if (ce_skip_worktree(ce))
{
int fd;
struct string_list partial;
- const char **pathspec = NULL;
+ struct pathspec pathspec;
char *old_index_env = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
-
- if (read_cache_preload(pathspec) < 0)
+ if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
if (interactive) {
* (A) if all goes well, commit the real index;
* (B) on failure, rollback the real index.
*/
- if (all || (also && pathspec && *pathspec)) {
+ if (all || (also && pathspec.nr)) {
fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+ add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
* and create commit from the_index.
* We still need to refresh the index here.
*/
- if (!only && (!pathspec || !*pathspec)) {
+ if (!only && !pathspec.nr) {
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
if (active_cache_changed) {
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
- if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
discard_cache();
const char *hook_arg2 = NULL;
int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
+ int old_display_comment_prefix;
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
if (s->fp == NULL)
die_errno(_("could not open '%s'"), git_path(commit_editmsg));
+ /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
+ old_display_comment_prefix = s->display_comment_prefix;
+ s->display_comment_prefix = 1;
+
if (clean_message_contents)
stripspace(&sb, 0);
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
+ s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
struct rev_info revs;
struct commit *commit;
struct strbuf buf = STRBUF_INIT;
+ struct string_list mailmap = STRING_LIST_INIT_NODUP;
const char *av[20];
int ac = 0;
av[++ac] = buf.buf;
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
+ revs.mailmap = &mailmap;
+ read_mailmap(revs.mailmap, NULL);
+
prepare_revision_walk(&revs);
commit = get_revision(&revs);
if (commit) {
struct pretty_print_context ctx = {0};
ctx.date_mode = DATE_NORMAL;
strbuf_release(&buf);
- format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+ format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
+ clear_mailmap(&mailmap);
return strbuf_detach(&buf, NULL);
}
die(_("No existing author found with '%s'"), name);
if (patch_interactive)
interactive = 1;
- if (!!also + !!only + !!all + !!interactive > 1)
+ if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
if (argc == 0 && (also || (only && !amend)))
die(_("No paths with --include/--only does not make sense."));
s->use_color = git_config_colorbool(k, v);
return 0;
}
+ if (!strcmp(k, "status.displaycommentprefix")) {
+ s->display_comment_prefix = git_config_bool(k, v);
+ return 0;
+ }
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
if (slot < 0)
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
- OPT_BOOLEAN('z', "null", &s.null_termination,
- N_("terminate entries with NUL")),
+ OPT_BOOL('z', "null", &s.null_termination,
+ N_("terminate entries with NUL")),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
N_("mode"),
N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
- OPT_BOOLEAN(0, "ignored", &show_ignored_in_status,
- N_("show ignored files")),
+ OPT_BOOL(0, "ignored", &show_ignored_in_status,
+ N_("show ignored files")),
{ OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
- if (*argv)
- s.pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&s.pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- read_cache_preload(s.pathspec);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+ read_cache_preload(&s.pathspec);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
- OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
- OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")),
+ OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+ OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
- OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")),
+ OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
/* end commit message options */
OPT_GROUP(N_("Commit contents options")),
- OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")),
- OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")),
- OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")),
- OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")),
- OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")),
- OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
- OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
+ OPT_BOOL('a', "all", &all, N_("commit all changed files")),
+ OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
+ OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
+ OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+ OPT_BOOL('o', "only", &only, N_("commit only specified files")),
+ OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
+ OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT),
OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
- OPT_BOOLEAN('z', "null", &s.null_termination,
- N_("terminate entries with NUL")),
- OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")),
- OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
+ OPT_BOOL('z', "null", &s.null_termination,
+ N_("terminate entries with NUL")),
+ OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
+ OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
/* end commit contents options */
- { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
- N_("ok to record an empty change"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
- N_("ok to record a change with an empty message"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
+ N_("ok to record an empty change")),
+ OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message,
+ N_("ok to record a change with an empty message")),
OPT_END()
};
!current_head
? NULL
: current_head->object.sha1,
- 0);
+ 0, NULL);
nl = strchr(sb.buf, '\n');
if (nl)
TAGS_SET = 2
};
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int fetch_prune_config = -1; /* unspecified */
+static int prune = -1; /* unspecified */
+#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+
+static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int tags = TAGS_DEFAULT, unshallow;
static const char *depth;
static const char *upload_pack;
static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
+static struct transport *gtransport;
+static struct transport *gsecondary;
static const char *submodule_prefix = "";
static const char *recurse_submodules_default;
return 0;
}
+static int git_fetch_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "fetch.prune")) {
+ fetch_prune_config = git_config_bool(k, v);
+ return 0;
+ }
+ return 0;
+}
+
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "all", &all,
- N_("fetch from all remotes")),
- OPT_BOOLEAN('a', "append", &append,
- N_("append to .git/FETCH_HEAD instead of overwriting")),
+ OPT_BOOL(0, "all", &all,
+ N_("fetch from all remotes")),
+ OPT_BOOL('a', "append", &append,
+ N_("append to .git/FETCH_HEAD instead of overwriting")),
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
N_("path to upload pack on remote end")),
OPT__FORCE(&force, N_("force overwrite of local branch")),
- OPT_BOOLEAN('m', "multiple", &multiple,
- N_("fetch from multiple remotes")),
+ OPT_BOOL('m', "multiple", &multiple,
+ N_("fetch from multiple remotes")),
OPT_SET_INT('t', "tags", &tags,
N_("fetch all tags and associated objects"), TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
- OPT_BOOLEAN('p', "prune", &prune,
- N_("prune remote-tracking branches no longer on remote")),
+ OPT_BOOL('p', "prune", &prune,
+ N_("prune remote-tracking branches no longer on remote")),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
N_("control recursive fetching of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
- OPT_BOOLEAN(0, "dry-run", &dry_run,
- N_("dry run")),
- OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")),
- OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ OPT_BOOL(0, "dry-run", &dry_run,
+ N_("dry run")),
+ OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
+ OPT_BOOL('u', "update-head-ok", &update_head_ok,
N_("allow updating of HEAD ref")),
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_STRING(0, "depth", &depth, N_("depth"),
static void unlock_pack(void)
{
- if (transport)
- transport_unlock_pack(transport);
+ if (gtransport)
+ transport_unlock_pack(gtransport);
+ if (gsecondary)
+ transport_unlock_pack(gsecondary);
}
static void unlock_pack_on_signal(int signo)
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);
+ check_old ? ref->old_sha1 : NULL,
+ 0, NULL);
if (!lock)
return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
STORE_REF_ERROR_OTHER;
return 0;
}
+static void set_option(struct transport *transport, 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 struct transport *prepare_transport(struct remote *remote)
+{
+ struct transport *transport;
+ transport = transport_get(remote, NULL);
+ transport_set_verbosity(transport, verbosity, progress);
+ if (upload_pack)
+ set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(transport, TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(transport, TRANS_OPT_DEPTH, depth);
+ return transport;
+}
+
+static void backfill_tags(struct transport *transport, struct ref *ref_map)
+{
+ if (transport->cannot_reuse) {
+ gsecondary = prepare_transport(transport->remote);
+ transport = gsecondary;
+ }
+
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+
+ if (gsecondary) {
+ transport_disconnect(gsecondary);
+ gsecondary = NULL;
+ }
+}
+
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
goto cleanup;
}
if (prune) {
- /* If --tags was specified, pretend the user gave us the canonical tags refspec */
+ /*
+ * If --tags was specified, pretend that the user gave us
+ * the canonical tags refspec
+ */
if (tags == TAGS_SET) {
const char *tags_str = "refs/tags/*:refs/tags/*";
struct refspec *tags_refspec, *refspec;
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);
- }
+ if (ref_map)
+ backfill_tags(transport, ref_map);
free_refs(ref_map);
}
return retcode;
}
-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 (dry_run)
argv_array_push(argv, "--dry-run");
- if (prune)
+ if (prune > 0)
argv_array_push(argv, "--prune");
if (update_head_ok)
argv_array_push(argv, "--update-head-ok");
die(_("No remote repository specified. Please, specify either a URL or a\n"
"remote name from which new revisions should be fetched."));
- 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);
+ gtransport = prepare_transport(remote);
+
+ if (prune < 0) {
+ /* no command line request */
+ if (0 <= gtransport->remote->prune)
+ prune = gtransport->remote->prune;
+ else if (0 <= fetch_prune_config)
+ prune = fetch_prune_config;
+ else
+ prune = PRUNE_BY_DEFAULT;
+ }
if (argc > 0) {
int j = 0;
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
refspec = parse_fetch_refspec(ref_nr, refs);
- exit_code = do_fetch(transport, refspec, ref_nr);
+ exit_code = do_fetch(gtransport, refspec, ref_nr);
free_refspec(ref_nr, refspec);
- transport_disconnect(transport);
- transport = NULL;
+ transport_disconnect(gtransport);
+ gtransport = NULL;
return exit_code;
}
for (i = 1; i < argc; i++)
strbuf_addf(&default_rla, " %s", argv[i]);
+ git_config(git_fetch_config, NULL);
+
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
#include "commit.h"
#include "object.h"
#include "remote.h"
+#include "connect.h"
#include "transport.h"
#include "string-list.h"
#include "sha1-array.h"
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
+static int fix_thin = 1;
static const char *head_name;
static void *head_name_to_free;
static int sent_capabilities;
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
+ lock = lock_any_ref_for_update(namespaced_name, old_sha1,
+ 0, NULL);
if (!lock) {
rp_error("failed to lock %s", name);
return "failed to lock";
keeper[i++] = "--stdin";
if (fsck_objects)
keeper[i++] = "--strict";
- keeper[i++] = "--fix-thin";
+ if (fix_thin)
+ keeper[i++] = "--fix-thin";
keeper[i++] = hdr_arg;
keeper[i++] = keep_arg;
keeper[i++] = NULL;
stateless_rpc = 1;
continue;
}
+ if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
+ fix_thin = 0;
+ continue;
+ }
usage(receive_pack_usage);
}
complete = 0;
}
}
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
if (complete)
tree->object.flags |= SEEN;
* we take the lock for the ref itself to prevent it from
* getting updated.
*/
- lock = lock_any_ref_for_update(ref, sha1, 0);
+ lock = lock_any_ref_for_update(ref, sha1, 0, NULL);
if (!lock)
return error("cannot lock ref '%s'", ref);
log_file = git_pathdup("logs/%s", ref);
else if (!force)
die("replace ref '%s' already exists", ref);
- lock = lock_any_ref_for_update(ref, prev, 0);
+ lock = lock_any_ref_for_update(ref, prev, 0, NULL);
if (!lock)
die("%s: cannot lock the ref", ref);
if (write_ref_sha1(lock, repl, NULL) < 0)
{
int list = 0, delete = 0, force = 0;
struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, N_("list replace refs")),
- OPT_BOOLEAN('d', NULL, &delete, N_("delete replace refs")),
- OPT_BOOLEAN('f', NULL, &force, N_("replace the ref if it exists")),
+ OPT_BOOL('l', NULL, &list, N_("list replace refs")),
+ OPT_BOOL('d', NULL, &delete, N_("delete replace refs")),
+ OPT_BOOL('f', NULL, &force, N_("replace the ref if it exists")),
OPT_END()
};
}
}
-static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
+static int read_from_tree(const struct pathspec *pathspec,
+ unsigned char *tree_sha1)
{
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
- diff_tree_setup_paths(pathspec, &opt);
+ copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- diff_tree_release_paths(&opt);
+ free_pathspec(&opt.pathspec);
return 0;
}
}
-static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
+static void parse_args(struct pathspec *pathspec,
+ const char **argv, const char *prefix,
+ int patch_mode,
+ const char **rev_ret)
{
const char *rev = "HEAD";
unsigned char unused[20];
}
}
*rev_ret = rev;
- return argv[0] ? get_pathspec(prefix, argv) : NULL;
+ parse_pathspec(pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
+ prefix, argv);
}
- static int update_refs(const char *rev, const unsigned char *sha1)
+ static int reset_refs(const char *rev, const unsigned char *sha1)
{
int update_ref_status;
struct strbuf msg = STRBUF_INIT;
int patch_mode = 0, unborn;
const char *rev;
unsigned char sha1[20];
- const char **pathspec = NULL;
+ struct pathspec pathspec;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_SET_INT(0, "mixed", &reset_type,
N_("reset HEAD, index and working tree"), MERGE),
OPT_SET_INT(0, "keep", &reset_type,
N_("reset HEAD but keep local changes"), KEEP),
- OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
+ OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_reset_usage,
PARSE_OPT_KEEP_DASHDASH);
- pathspec = parse_args(argv, prefix, &rev);
+ parse_args(&pathspec, argv, prefix, patch_mode, &rev);
unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
- } else if (!pathspec) {
+ } else if (!pathspec.nr) {
struct commit *commit;
if (get_sha1_committish(rev, sha1))
die(_("Failed to resolve '%s' as a valid revision."), rev);
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
- return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
+ return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", &pathspec);
}
/* git reset tree [--] paths... can be used to
* load chosen paths from the tree into the index without
* affecting the working tree nor HEAD. */
- if (pathspec) {
+ if (pathspec.nr) {
if (reset_type == MIXED)
warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
die_if_unmerged_cache(reset_type);
if (reset_type != SOFT) {
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ struct lock_file *lock = xcalloc(1, sizeof(*lock));
int newfd = hold_locked_index(lock, 1);
if (reset_type == MIXED) {
- if (read_from_tree(pathspec, sha1))
+ int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+ if (read_from_tree(&pathspec, sha1))
return 1;
+ refresh_index(&the_index, flags, NULL, NULL,
+ _("Unstaged changes after reset:"));
} else {
int err = reset_index(sha1, reset_type, quiet);
if (reset_type == KEEP && !err)
die(_("Could not reset index file to revision '%s'."), rev);
}
- if (reset_type == MIXED) { /* Report what has not been updated. */
- int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
- refresh_index(&the_index, flags, NULL, NULL,
- _("Unstaged changes after reset:"));
- }
-
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock))
die(_("Could not write new index file."));
}
- if (!pathspec && !unborn) {
+ if (!pathspec.nr && !unborn) {
/* Any resets without paths update HEAD to the head being
* switched to, saving the previous head in ORIG_HEAD before. */
- update_ref_status = update_refs(rev, sha1);
+ update_ref_status = reset_refs(rev, sha1);
if (reset_type == HARD && !update_ref_status && !quiet)
print_new_head_line(lookup_commit_reference(sha1));
}
- if (!pathspec)
+ if (!pathspec.nr)
remove_branch_state();
return update_ref_status;
struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
- int annotate = 0, force = 0, lines = -1, list = 0,
- delete = 0, verify = 0;
+ int annotate = 0, force = 0, lines = -1;
+ int cmdmode = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
struct option options[] = {
- OPT_BOOLEAN('l', "list", &list, N_("list tag names")),
+ OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
N_("print <n> lines of each tag message"),
PARSE_OPT_OPTARG, NULL, 1 },
- OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")),
- OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")),
+ OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
+ OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
OPT_GROUP(N_("Tag creation options")),
- OPT_BOOLEAN('a', "annotate", &annotate,
+ OPT_BOOL('a', "annotate", &annotate,
N_("annotated tag, needs a message")),
OPT_CALLBACK('m', "message", &msg, N_("message"),
N_("tag message"), parse_msg_arg),
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
- OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
+ OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
N_("how to strip spaces and #comments from message")),
OPT_STRING('u', "local-user", &keyid, N_("key id"),
}
if (opt.sign)
annotate = 1;
- if (argc == 0 && !(delete || verify))
- list = 1;
+ if (argc == 0 && !cmdmode)
+ cmdmode = 'l';
- if ((annotate || msg.given || msgfile || force) &&
- (list || delete || verify))
+ if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
- if (list + delete + verify > 1)
- usage_with_options(git_tag_usage, options);
finalize_colopts(&colopts, -1);
- if (list && lines != -1) {
+ if (cmdmode == 'l' && lines != -1) {
if (explicitly_enable_column(colopts))
die(_("--column and -n are incompatible"));
colopts = 0;
}
- if (list) {
+ if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
struct column_options copts;
die(_("--contains option is only allowed with -l."));
if (points_at.nr)
die(_("--points-at option is only allowed with -l."));
- if (delete)
+ if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag);
- if (verify)
+ if (cmdmode == 'v')
return for_each_tag_name(argv, verify_tag);
if (msg.given || msgfile) {
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);
- lock = lock_any_ref_for_update(ref.buf, prev, 0);
+ lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
if (!lock)
die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
#include "refs.h"
#include "builtin.h"
#include "parse-options.h"
+ #include "quote.h"
+ #include "argv-array.h"
static const char * const git_update_ref_usage[] = {
N_("git update-ref [options] -d <refname> [<oldval>]"),
N_("git update-ref [options] <refname> <newval> [<oldval>]"),
+ N_("git update-ref [options] --stdin [-z]"),
NULL
};
+ static int updates_alloc;
+ static int updates_count;
+ static const struct ref_update **updates;
+
+ static char line_termination = '\n';
+ static int update_flags;
+
+ static struct ref_update *update_alloc(void)
+ {
+ struct ref_update *update;
+
+ /* Allocate and zero-init a struct ref_update */
+ update = xcalloc(1, sizeof(*update));
+ ALLOC_GROW(updates, updates_count + 1, updates_alloc);
+ updates[updates_count++] = update;
+
+ /* Store and reset accumulated options */
+ update->flags = update_flags;
+ update_flags = 0;
+
+ return update;
+ }
+
+ static void update_store_ref_name(struct ref_update *update,
+ const char *ref_name)
+ {
+ if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", ref_name);
+ update->ref_name = xstrdup(ref_name);
+ }
+
+ static void update_store_new_sha1(struct ref_update *update,
+ const char *newvalue)
+ {
+ if (*newvalue && get_sha1(newvalue, update->new_sha1))
+ die("invalid new value for ref %s: %s",
+ update->ref_name, newvalue);
+ }
+
+ static void update_store_old_sha1(struct ref_update *update,
+ const char *oldvalue)
+ {
+ if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
+ die("invalid old value for ref %s: %s",
+ update->ref_name, oldvalue);
+
+ /* We have an old value if non-empty, or if empty without -z */
+ update->have_old = *oldvalue || line_termination;
+ }
+
+ static const char *parse_arg(const char *next, struct strbuf *arg)
+ {
+ /* Parse SP-terminated, possibly C-quoted argument */
+ if (*next != '"')
+ while (*next && !isspace(*next))
+ strbuf_addch(arg, *next++);
+ else if (unquote_c_style(arg, next, &next))
+ die("badly quoted argument: %s", next);
+
+ /* Return position after the argument */
+ return next;
+ }
+
+ static const char *parse_first_arg(const char *next, struct strbuf *arg)
+ {
+ /* Parse argument immediately after "command SP" */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, use the next argument */
+ next = parse_arg(next, arg);
+ } else {
+ /* With -z, use rest of first NUL-terminated line */
+ strbuf_addstr(arg, next);
+ next = next + arg->len;
+ }
+ return next;
+ }
+
+ static const char *parse_next_arg(const char *next, struct strbuf *arg)
+ {
+ /* Parse next SP-terminated or NUL-terminated argument, if any */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!*next)
+ return NULL;
+ if (*next != ' ')
+ die("expected SP but got: %s", next);
+ next = parse_arg(next + 1, arg);
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (*next)
+ die("expected NUL but got: %s", next);
+ if (strbuf_getline(arg, stdin, '\0') == EOF)
+ return NULL;
+ next = arg->buf + arg->len;
+ }
+ return next;
+ }
+
+ static void parse_cmd_update(const char *next)
+ {
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("update line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("update %s missing <newvalue>", ref.buf);
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("update %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("update %s has extra input: %s", ref.buf, next);
+ }
+
+ static void parse_cmd_create(const char *next)
+ {
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("create line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("create %s missing <newvalue>", ref.buf);
+ if (is_null_sha1(update->new_sha1))
+ die("create %s given zero new value", ref.buf);
+
+ if (next && *next)
+ die("create %s has extra input: %s", ref.buf, next);
+ }
+
+ static void parse_cmd_delete(const char *next)
+ {
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("delete line missing <ref>");
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("delete %s missing [<oldvalue>] NUL", ref.buf);
+ if (update->have_old && is_null_sha1(update->old_sha1))
+ die("delete %s given zero old value", ref.buf);
+
+ if (next && *next)
+ die("delete %s has extra input: %s", ref.buf, next);
+ }
+
+ static void parse_cmd_verify(const char *next)
+ {
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf value = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("verify line missing <ref>");
+
+ if ((next = parse_next_arg(next, &value)) != NULL) {
+ update_store_old_sha1(update, value.buf);
+ update_store_new_sha1(update, value.buf);
+ } else if(!line_termination)
+ die("verify %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("verify %s has extra input: %s", ref.buf, next);
+ }
+
+ static void parse_cmd_option(const char *next)
+ {
+ if (!strcmp(next, "no-deref"))
+ update_flags |= REF_NODEREF;
+ else
+ die("option unknown: %s", next);
+ }
+
+ static void update_refs_stdin(void)
+ {
+ struct strbuf cmd = STRBUF_INIT;
+
+ /* Read each line dispatch its command */
+ while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
+ if (!cmd.buf[0])
+ die("empty command in input");
+ else if (isspace(*cmd.buf))
+ die("whitespace before command: %s", cmd.buf);
+ else if (!prefixcmp(cmd.buf, "update "))
+ parse_cmd_update(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "create "))
+ parse_cmd_create(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "delete "))
+ parse_cmd_delete(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "verify "))
+ parse_cmd_verify(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "option "))
+ parse_cmd_option(cmd.buf + 7);
+ else
+ die("unknown command: %s", cmd.buf);
+
+ strbuf_release(&cmd);
+ }
+
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname, *oldval, *msg = NULL;
unsigned char sha1[20], oldsha1[20];
- int delete = 0, no_deref = 0, flags = 0;
+ int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
- OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")),
- OPT_BOOLEAN('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
- OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+ OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
+ OPT_BOOL( 0 , "no-deref", &no_deref,
N_("update <refname> not the one it points to")),
- OPT_BOOLEAN( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
++ OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
++ OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
OPT_END(),
};
if (msg && !*msg)
die("Refusing to perform update with empty message.");
+ if (read_stdin) {
+ if (delete || no_deref || argc > 0)
+ usage_with_options(git_update_ref_usage, options);
+ if (end_null)
+ line_termination = '\0';
+ update_refs_stdin();
+ return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+ }
+
+ if (end_null)
+ usage_with_options(git_update_ref_usage, options);
+
if (delete) {
if (argc < 1 || argc > 2)
usage_with_options(git_update_ref_usage, options);
('author' (sp name)? sp '<' email '>' sp when lf)?
'committer' (sp name)? sp '<' email '>' sp when lf
commit_msg
- ('from' sp committish lf)?
- ('merge' sp committish lf)*
+ ('from' sp commit-ish lf)?
+ ('merge' sp commit-ish lf)*
(file_change | ls)*
lf?;
commit_msg ::= data;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
- note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
- note_inm ::= 'N' sp 'inline' sp committish lf
+ note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
+ note_inm ::= 'N' sp 'inline' sp commit-ish lf
data;
new_tag ::= 'tag' sp tag_str lf
- 'from' sp committish lf
+ 'from' sp commit-ish lf
('tagger' (sp name)? sp '<' email '>' sp when lf)?
tag_msg;
tag_msg ::= data;
reset_branch ::= 'reset' sp ref_str lf
- ('from' sp committish lf)?
+ ('from' sp commit-ish lf)?
lf?;
checkpoint ::= 'checkpoint' lf
# stream formatting is: \, " and LF. Otherwise these values
# are UTF8.
#
- committish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
+ commit-ish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
ref_str ::= ref;
sha1exp_str ::= sha1exp;
tag_str ::= tag;
static int tree_content_remove(
struct tree_entry *root,
const char *p,
- struct tree_entry *backup_leaf)
+ struct tree_entry *backup_leaf,
+ int allow_root)
{
struct tree_content *t;
const char *slash1;
if (!root->tree)
load_tree(root);
+
+ if (!*p && allow_root) {
+ e = root;
+ goto del_entry;
+ }
+
t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
goto del_entry;
if (!e->tree)
load_tree(e);
- if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
+ if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) {
for (n = 0; n < e->tree->entry_count; n++) {
if (e->tree->entries[n]->versions[1].mode) {
hashclr(root->versions[1].sha1);
static int tree_content_get(
struct tree_entry *root,
const char *p,
- struct tree_entry *leaf)
+ struct tree_entry *leaf,
+ int allow_root)
{
struct tree_content *t;
const char *slash1;
n = slash1 - p;
else
n = strlen(p);
- if (!n)
+ if (!n && !allow_root)
die("Empty path component found in input");
if (!root->tree)
load_tree(root);
+
+ if (!n) {
+ e = root;
+ goto found_entry;
+ }
+
t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
- if (!slash1) {
- memcpy(leaf, e, sizeof(*leaf));
- if (e->tree && is_null_sha1(e->versions[1].sha1))
- leaf->tree = dup_tree_content(e->tree);
- else
- leaf->tree = NULL;
- return 1;
- }
+ if (!slash1)
+ goto found_entry;
if (!S_ISDIR(e->versions[1].mode))
return 0;
if (!e->tree)
load_tree(e);
- return tree_content_get(e, slash1 + 1, leaf);
+ return tree_content_get(e, slash1 + 1, leaf, 0);
}
}
return 0;
+
+found_entry:
+ memcpy(leaf, e, sizeof(*leaf));
+ if (e->tree && is_null_sha1(e->versions[1].sha1))
+ leaf->tree = dup_tree_content(e->tree);
+ else
+ leaf->tree = NULL;
+ return 1;
}
static int update_branch(struct branch *b)
return 0;
if (read_ref(b->name, old_sha1))
hashclr(old_sha1);
- lock = lock_any_ref_for_update(b->name, old_sha1, 0);
+ lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
if (!lock)
return error("Unable to lock %s", b->name);
if (!force_update && !is_null_sha1(old_sha1)) {
}
/* Rename fullpath to realpath */
- if (!tree_content_remove(orig_root, fullpath, &leaf))
+ if (!tree_content_remove(orig_root, fullpath, &leaf, 0))
die("Failed to remove path %s", fullpath);
tree_content_set(orig_root, realpath,
leaf.versions[1].sha1,
/* Git does not track empty, non-toplevel directories. */
if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
- tree_content_remove(&b->branch_tree, p, NULL);
+ tree_content_remove(&b->branch_tree, p, NULL, 0);
return;
}
die("Garbage after path in: %s", command_buf.buf);
p = uq.buf;
}
- tree_content_remove(&b->branch_tree, p, NULL);
+ tree_content_remove(&b->branch_tree, p, NULL, 1);
}
static void file_change_cr(struct branch *b, int rename)
memset(&leaf, 0, sizeof(leaf));
if (rename)
- tree_content_remove(&b->branch_tree, s, &leaf);
+ tree_content_remove(&b->branch_tree, s, &leaf, 1);
else
- tree_content_get(&b->branch_tree, s, &leaf);
+ tree_content_get(&b->branch_tree, s, &leaf, 1);
if (!leaf.versions[1].mode)
die("Path %s not in branch", s);
if (!*d) { /* C "path/to/subdir" "" */
assert(*p == ' ');
p++; /* skip space */
- /* <committish> */
+ /* <commit-ish> */
s = lookup_branch(p);
if (s) {
if (is_null_sha1(s->sha1))
}
construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
- if (tree_content_remove(&b->branch_tree, path, NULL))
+ if (tree_content_remove(&b->branch_tree, path, NULL, 0))
b->num_notes--;
if (is_null_sha1(sha1))
case OBJ_TAG:
break;
default:
- die("Not a treeish: %s", command_buf.buf);
+ die("Not a tree-ish: %s", command_buf.buf);
}
if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
struct tree_entry *root = NULL;
struct tree_entry leaf = {NULL};
- /* ls SP (<treeish> SP)? <path> */
+ /* ls SP (<tree-ish> SP)? <path> */
p = command_buf.buf + strlen("ls ");
if (*p == '"') {
if (!b)
struct object_entry *e = parse_treeish_dataref(&p);
root = new_tree_entry();
hashcpy(root->versions[1].sha1, e->idx.sha1);
+ if (!is_null_sha1(root->versions[1].sha1))
+ root->versions[1].mode = S_IFDIR;
load_tree(root);
if (*p++ != ' ')
die("Missing space after tree-ish: %s", command_buf.buf);
die("Garbage after path in: %s", command_buf.buf);
p = uq.buf;
}
- tree_content_get(root, p, &leaf);
+ tree_content_get(root, p, &leaf, 1);
/*
* A directory in preparation would have a sha1 of zero
* until it is saved. Save, for simplicity.
}
struct ref_lock *lock_any_ref_for_update(const char *refname,
- const unsigned char *old_sha1, int flags)
+ const unsigned char *old_sha1,
+ int flags, int *type_p)
{
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
return NULL;
- return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
+ return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
}
/*
return 0;
}
- static int repack_without_ref(const char *refname)
+ static int repack_without_refs(const char **refnames, int n)
{
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
+ int i, removed = 0;
+
+ /* Look for a packed ref */
+ for (i = 0; i < n; i++)
+ if (get_packed_ref(refnames[i]))
+ break;
- if (!get_packed_ref(refname))
- return 0; /* refname does not exist in packed refs */
+ /* Avoid locking if we have nothing to do */
+ if (i == n)
+ return 0; /* no refname exists in packed refs */
if (lock_packed_refs(0)) {
unable_to_lock_error(git_path("packed-refs"), errno);
- return error("cannot delete '%s' from packed refs", refname);
+ return error("cannot delete '%s' from packed refs", refnames[i]);
}
packed = get_packed_refs(&ref_cache);
- /* Remove refname from the cache: */
- if (remove_entry(packed, refname) == -1) {
+ /* Remove refnames from the cache */
+ for (i = 0; i < n; i++)
+ if (remove_entry(packed, refnames[i]) != -1)
+ removed = 1;
+ if (!removed) {
/*
- * The packed entry disappeared while we were
+ * All packed entries disappeared while we were
* acquiring the lock.
*/
rollback_packed_refs();
return 0;
}
- /* Remove any other accumulated cruft: */
+ /* Remove any other accumulated cruft */
do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
for_each_string_list_item(ref_to_delete, &refs_to_delete) {
if (remove_entry(packed, ref_to_delete->string) == -1)
die("internal error");
}
- /* Write what remains: */
+ /* Write what remains */
return commit_packed_refs();
}
- int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+ static int repack_without_ref(const char *refname)
{
- struct ref_lock *lock;
- int err, i = 0, ret = 0, flag = 0;
+ return repack_without_refs(&refname, 1);
+ }
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
- if (!lock)
- return 1;
+ static int delete_ref_loose(struct ref_lock *lock, int flag)
+ {
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
- i = strlen(lock->lk->filename) - 5; /* .lock */
+ int err, i = strlen(lock->lk->filename) - 5; /* .lock */
+
lock->lk->filename[i] = 0;
err = unlink_or_warn(lock->lk->filename);
- if (err && errno != ENOENT)
- ret = 1;
-
lock->lk->filename[i] = '.';
+ if (err && errno != ENOENT)
+ return 1;
}
+ return 0;
+ }
+
+ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+ {
+ struct ref_lock *lock;
+ int ret = 0, flag = 0;
+
+ lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
+ if (!lock)
+ return 1;
+ ret |= delete_ref_loose(lock, flag);
+
/* removing the loose one could have resurrected an earlier
* packed one. Also, if it was not loose we need to repack
* without it.
return retval;
}
- int update_ref(const char *action, const char *refname,
- const unsigned char *sha1, const unsigned char *oldval,
- int flags, enum action_on_err onerr)
+ static struct ref_lock *update_ref_lock(const char *refname,
+ const unsigned char *oldval,
+ int flags, int *type_p,
+ enum action_on_err onerr)
{
- static struct ref_lock *lock;
- lock = lock_any_ref_for_update(refname, oldval, flags);
+ struct ref_lock *lock;
+ lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
if (!lock) {
const char *str = "Cannot lock the ref '%s'.";
switch (onerr) {
case DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break;
}
- return 1;
}
+ return lock;
+ }
+
+ static int update_ref_write(const char *action, const char *refname,
+ const unsigned char *sha1, struct ref_lock *lock,
+ enum action_on_err onerr)
+ {
if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'.";
switch (onerr) {
return 0;
}
-struct ref *find_ref_by_name(const struct ref *list, const char *name)
-{
- for ( ; list; list = list->next)
- if (!strcmp(list->name, name))
- return (struct ref *)list;
- return NULL;
-}
-
+ int update_ref(const char *action, const char *refname,
+ const unsigned char *sha1, const unsigned char *oldval,
+ int flags, enum action_on_err onerr)
+ {
+ struct ref_lock *lock;
+ lock = update_ref_lock(refname, oldval, flags, 0, onerr);
+ if (!lock)
+ return 1;
+ return update_ref_write(action, refname, sha1, lock, onerr);
+ }
+
+ static int ref_update_compare(const void *r1, const void *r2)
+ {
+ const struct ref_update * const *u1 = r1;
+ const struct ref_update * const *u2 = r2;
+ return strcmp((*u1)->ref_name, (*u2)->ref_name);
+ }
+
+ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
+ enum action_on_err onerr)
+ {
+ int i;
+ for (i = 1; i < n; i++)
+ if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+ const char *str =
+ "Multiple updates for ref '%s' not allowed.";
+ switch (onerr) {
+ case MSG_ON_ERR:
+ error(str, updates[i]->ref_name); break;
+ case DIE_ON_ERR:
+ die(str, updates[i]->ref_name); break;
+ case QUIET_ON_ERR:
+ break;
+ }
+ return 1;
+ }
+ return 0;
+ }
+
+ int update_refs(const char *action, const struct ref_update **updates_orig,
+ int n, enum action_on_err onerr)
+ {
+ int ret = 0, delnum = 0, i;
+ struct ref_update **updates;
+ int *types;
+ struct ref_lock **locks;
+ const char **delnames;
+
+ if (!updates_orig || !n)
+ return 0;
+
+ /* Allocate work space */
+ updates = xmalloc(sizeof(*updates) * n);
+ types = xmalloc(sizeof(*types) * n);
+ locks = xcalloc(n, sizeof(*locks));
+ delnames = xmalloc(sizeof(*delnames) * n);
+
+ /* Copy, sort, and reject duplicate refs */
+ memcpy(updates, updates_orig, sizeof(*updates) * n);
+ qsort(updates, n, sizeof(*updates), ref_update_compare);
+ ret = ref_update_reject_duplicates(updates, n, onerr);
+ if (ret)
+ goto cleanup;
+
+ /* Acquire all locks while verifying old values */
+ for (i = 0; i < n; i++) {
+ locks[i] = update_ref_lock(updates[i]->ref_name,
+ (updates[i]->have_old ?
+ updates[i]->old_sha1 : NULL),
+ updates[i]->flags,
+ &types[i], onerr);
+ if (!locks[i]) {
+ ret = 1;
+ goto cleanup;
+ }
+ }
+
+ /* Perform updates first so live commits remain referenced */
+ for (i = 0; i < n; i++)
+ if (!is_null_sha1(updates[i]->new_sha1)) {
+ ret = update_ref_write(action,
+ updates[i]->ref_name,
+ updates[i]->new_sha1,
+ locks[i], onerr);
+ locks[i] = NULL; /* freed by update_ref_write */
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Perform deletes now that updates are safely completed */
+ for (i = 0; i < n; i++)
+ if (locks[i]) {
+ delnames[delnum++] = locks[i]->ref_name;
+ ret |= delete_ref_loose(locks[i], types[i]);
+ }
+ ret |= repack_without_refs(delnames, delnum);
+ for (i = 0; i < delnum; i++)
+ unlink_or_warn(git_path("logs/%s", delnames[i]));
+ clear_loose_ref_cache(&ref_cache);
+
+ cleanup:
+ for (i = 0; i < n; i++)
+ if (locks[i])
+ unlock_ref(locks[i]);
+ free(updates);
+ free(types);
+ free(locks);
+ free(delnames);
+ return ret;
+ }
+
/*
* generate a format suitable for scanf from a ref_rev_parse_rules
* rule, that is replace the "%.*s" spec with a "%s" spec