merge(third_party/git): Merge squashed git subtree at v2.23.0

Merge commit '1b593e1ea4' as 'third_party/git'
This commit is contained in:
Vincent Ambo 2020-01-11 23:36:56 +00:00
commit 7ef0d62730
3629 changed files with 1139935 additions and 0 deletions

545
third_party/git/builtin/add.c vendored Normal file
View file

@ -0,0 +1,545 @@
/*
* "git add" builtin command
*
* Copyright (C) 2006 Linus Torvalds
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "lockfile.h"
#include "dir.h"
#include "pathspec.h"
#include "exec-cmd.h"
#include "cache-tree.h"
#include "run-command.h"
#include "parse-options.h"
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
#include "bulk-checkin.h"
#include "argv-array.h"
#include "submodule.h"
static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
NULL
};
static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes;
static int add_renormalize;
struct update_callback_data {
int flags;
int add_errors;
};
static void chmod_pathspec(struct pathspec *pathspec, char flip)
{
int i;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
continue;
if (chmod_cache_entry(ce, flip) < 0)
fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name);
}
}
static int fix_unmerged_status(struct diff_filepair *p,
struct update_callback_data *data)
{
if (p->status != DIFF_STATUS_UNMERGED)
return p->status;
if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode)
/*
* This is not an explicit add request, and the
* path is missing from the working tree (deleted)
*/
return DIFF_STATUS_DELETED;
else
/*
* Either an explicit add request, or path exists
* in the working tree. An attempt to explicitly
* add a path that does not exist in the working tree
* will be caught as an error by the caller immediately.
*/
return DIFF_STATUS_MODIFIED;
}
static void update_callback(struct diff_queue_struct *q,
struct diff_options *opt, void *cbdata)
{
int i;
struct update_callback_data *data = cbdata;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
switch (fix_unmerged_status(p, data)) {
default:
die(_("unexpected diff status %c"), p->status);
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
if (add_file_to_index(&the_index, path, data->flags)) {
if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
die(_("updating files failed"));
data->add_errors++;
}
break;
case DIFF_STATUS_DELETED:
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
break;
if (!(data->flags & ADD_CACHE_PRETEND))
remove_file_from_index(&the_index, path);
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
printf(_("remove '%s'\n"), path);
break;
}
}
}
int add_files_to_cache(const char *prefix,
const struct pathspec *pathspec, int flags)
{
struct update_callback_data data;
struct rev_info rev;
memset(&data, 0, sizeof(data));
data.flags = flags;
repo_init_revisions(the_repository, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
if (pathspec)
copy_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = &data;
rev.diffopt.flags.override_submodule_config = 1;
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
clear_pathspec(&rev.prune_data);
return !!data.add_errors;
}
static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
{
int i, retval = 0;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce))
continue; /* do not touch unmerged paths */
if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
continue; /* do not touch non blobs */
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
continue;
retval |= add_file_to_cache(ce->name, flags | ADD_CACHE_RENORMALIZE);
}
return retval;
}
static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
{
char *seen;
int i;
struct dir_entry **src, **dst;
seen = xcalloc(pathspec->nr, 1);
src = dst = dir->entries;
i = dir->nr;
while (--i >= 0) {
struct dir_entry *entry = *src++;
if (dir_path_match(&the_index, entry, pathspec, prefix, seen))
*dst++ = entry;
}
dir->nr = dst - dir->entries;
add_pathspec_matches_against_index(pathspec, &the_index, seen);
return seen;
}
static void refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
int i;
seen = xcalloc(pathspec->nr, 1);
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
pathspec, seen, _("Unstaged changes after refreshing the index:"));
for (i = 0; i < pathspec->nr; i++) {
if (!seen[i])
die(_("pathspec '%s' did not match any files"),
pathspec->items[i].match);
}
free(seen);
}
int run_add_interactive(const char *revision, const char *patch_mode,
const struct pathspec *pathspec)
{
int status, i;
struct argv_array argv = ARGV_ARRAY_INIT;
argv_array_push(&argv, "add--interactive");
if (patch_mode)
argv_array_push(&argv, patch_mode);
if (revision)
argv_array_push(&argv, revision);
argv_array_push(&argv, "--");
for (i = 0; i < pathspec->nr; i++)
/* pass original pathspec, to be re-parsed */
argv_array_push(&argv, pathspec->items[i].original);
status = run_command_v_opt(argv.argv, RUN_GIT_CMD);
argv_array_clear(&argv);
return status;
}
int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
struct pathspec pathspec;
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_FULL |
PATHSPEC_SYMLINK_LEADING_PATH |
PATHSPEC_PREFIX_ORIGIN,
prefix, argv);
return run_add_interactive(NULL,
patch ? "--patch" : NULL,
&pathspec);
}
static int edit_patch(int argc, const char **argv, const char *prefix)
{
char *file = git_pathdup("ADD_EDIT.patch");
const char *apply_argv[] = { "apply", "--recount", "--cached",
NULL, NULL };
struct child_process child = CHILD_PROCESS_INIT;
struct rev_info rev;
int out;
struct stat st;
apply_argv[3] = file;
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
if (read_cache() < 0)
die(_("Could not read the index"));
repo_init_revisions(the_repository, &rev, prefix);
rev.diffopt.context = 7;
argc = setup_revisions(argc, argv, &rev, NULL);
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
rev.diffopt.use_color = 0;
rev.diffopt.flags.ignore_dirty_submodules = 1;
out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (out < 0)
die(_("Could not open '%s' for writing."), file);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
die(_("Could not write patch"));
if (launch_editor(file, NULL, NULL))
die(_("editing patch failed"));
if (stat(file, &st))
die_errno(_("Could not stat '%s'"), file);
if (!st.st_size)
die(_("Empty patch. Aborted."));
child.git_cmd = 1;
child.argv = apply_argv;
if (run_command(&child))
die(_("Could not apply '%s'"), file);
unlink(file);
free(file);
return 0;
}
static const char ignore_error[] =
N_("The following paths are ignored by one of your .gitignore files:\n");
static int verbose, show_only, ignored_too, refresh_only;
static int ignore_add_errors, intent_to_add, ignore_missing;
static int warn_on_embedded_repo = 1;
#define ADDREMOVE_DEFAULT 1
static int addremove = ADDREMOVE_DEFAULT;
static int addremove_explicit = -1; /* unspecified */
static char *chmod_arg;
static int ignore_removal_cb(const struct option *opt, const char *arg, int unset)
{
/* if we are told to ignore, we are not adding removals */
*(int *)opt->value = !unset ? 0 : 1;
return 0;
}
static struct option builtin_add_options[] = {
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_GROUP(""),
OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
{ OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
NULL /* takes no arguments */,
N_("ignore paths removed in the working tree (same as --no-all)"),
PARSE_OPT_NOARG, ignore_removal_cb },
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")),
OPT_END(),
};
static int add_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "add.ignoreerrors") ||
!strcmp(var, "add.ignore-errors")) {
ignore_add_errors = git_config_bool(var, value);
return 0;
}
return git_default_config(var, value, cb);
}
static const char embedded_advice[] = N_(
"You've added another git repository inside your current repository.\n"
"Clones of the outer repository will not contain the contents of\n"
"the embedded repository and will not know how to obtain it.\n"
"If you meant to add a submodule, use:\n"
"\n"
" git submodule add <url> %s\n"
"\n"
"If you added this path by mistake, you can remove it from the\n"
"index with:\n"
"\n"
" git rm --cached %s\n"
"\n"
"See \"git help submodule\" for more information."
);
static void check_embedded_repo(const char *path)
{
struct strbuf name = STRBUF_INIT;
if (!warn_on_embedded_repo)
return;
if (!ends_with(path, "/"))
return;
/* Drop trailing slash for aesthetics */
strbuf_addstr(&name, path);
strbuf_strip_suffix(&name, "/");
warning(_("adding embedded git repository: %s"), name.buf);
if (advice_add_embedded_repo) {
advise(embedded_advice, name.buf, name.buf);
/* there may be multiple entries; advise only once */
advice_add_embedded_repo = 0;
}
strbuf_release(&name);
}
static int add_files(struct dir_struct *dir, int flags)
{
int i, exit_status = 0;
if (dir->ignored_nr) {
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
fprintf(stderr, _("Use -f if you really want to add them.\n"));
exit_status = 1;
}
for (i = 0; i < dir->nr; i++) {
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
if (!ignore_add_errors)
die(_("adding files failed"));
exit_status = 1;
} else {
check_embedded_repo(dir->entries[i]->name);
}
}
return exit_status;
}
int cmd_add(int argc, const char **argv, const char *prefix)
{
int exit_status = 0;
struct pathspec pathspec;
struct dir_struct dir;
int flags;
int add_new_files;
int require_pathspec;
char *seen = NULL;
struct lock_file lock_file = LOCK_INIT;
git_config(add_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_add_options,
builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
if (patch_interactive)
add_interactive = 1;
if (add_interactive)
exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
if (edit_interactive)
return(edit_patch(argc, argv, prefix));
argc--;
argv++;
if (0 <= addremove_explicit)
addremove = addremove_explicit;
else if (take_worktree_changes && ADDREMOVE_DEFAULT)
addremove = 0; /* "-u" was given but not "-A" */
if (addremove && take_worktree_changes)
die(_("-A and -u are mutually incompatible"));
if (!take_worktree_changes && addremove_explicit < 0 && argc)
/* Turn "git add pathspec..." to "git add -A pathspec..." */
addremove = 1;
if (!show_only && ignore_missing)
die(_("Option --ignore-missing can only be used together with --dry-run"));
if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') ||
chmod_arg[1] != 'x' || chmod_arg[2]))
die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
(show_only ? ADD_CACHE_PRETEND : 0) |
(intent_to_add ? ADD_CACHE_INTENT : 0) |
(ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
(!(addremove || take_worktree_changes)
? ADD_CACHE_IGNORE_REMOVAL : 0));
if (require_pathspec && argc == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0;
}
/*
* Check the "pathspec '%s' did not match any files" block
* below before enabling new magic.
*/
parse_pathspec(&pathspec, PATHSPEC_ATTR,
PATHSPEC_PREFER_FULL |
PATHSPEC_SYMLINK_LEADING_PATH,
prefix, argv);
if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
die_in_unpopulated_submodule(&the_index, prefix);
die_path_inside_submodule(&the_index, &pathspec);
if (add_new_files) {
int baselen;
/* Set up the default git porcelain excludes */
memset(&dir, 0, sizeof(dir));
if (!ignored_too) {
dir.flags |= DIR_COLLECT_IGNORED;
setup_standard_excludes(&dir);
}
/* This picks up the paths that are not tracked */
baselen = fill_directory(&dir, &the_index, &pathspec);
if (pathspec.nr)
seen = prune_directory(&dir, &pathspec, baselen);
}
if (refresh_only) {
refresh(verbose, &pathspec);
goto finish;
}
if (pathspec.nr) {
int i;
if (!seen)
seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
/*
* file_exists() assumes exact match
*/
GUARD_PATHSPEC(&pathspec,
PATHSPEC_FROMTOP |
PATHSPEC_LITERAL |
PATHSPEC_GLOB |
PATHSPEC_ICASE |
PATHSPEC_EXCLUDE);
for (i = 0; i < pathspec.nr; i++) {
const char *path = pathspec.items[i].match;
if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
continue;
if (!seen[i] && path[0] &&
((pathspec.items[i].magic &
(PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
!file_exists(path))) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
if (is_excluded(&dir, &the_index, path, &dtype))
dir_add_ignored(&dir, &the_index,
path, pathspec.items[i].len);
} else
die(_("pathspec '%s' did not match any files"),
pathspec.items[i].original);
}
}
free(seen);
}
plug_bulk_checkin();
if (add_renormalize)
exit_status |= renormalize_tracked_files(&pathspec, flags);
else
exit_status |= add_files_to_cache(prefix, &pathspec, flags);
if (add_new_files)
exit_status |= add_files(&dir, flags);
if (chmod_arg && pathspec.nr)
chmod_pathspec(&pathspec, chmod_arg[0]);
unplug_bulk_checkin();
finish:
if (write_locked_index(&the_index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
UNLEAK(pathspec);
UNLEAK(dir);
return exit_status;
}

2373
third_party/git/builtin/am.c vendored Normal file

File diff suppressed because it is too large Load diff

22
third_party/git/builtin/annotate.c vendored Normal file
View file

@ -0,0 +1,22 @@
/*
* "git annotate" builtin alias
*
* Copyright (C) 2006 Ryan Anderson
*/
#include "git-compat-util.h"
#include "builtin.h"
#include "argv-array.h"
int cmd_annotate(int argc, const char **argv, const char *prefix)
{
struct argv_array args = ARGV_ARRAY_INIT;
int i;
argv_array_pushl(&args, "annotate", "-c", NULL);
for (i = 1; i < argc; i++) {
argv_array_push(&args, argv[i]);
}
return cmd_blame(args.argc, args.argv, prefix);
}

34
third_party/git/builtin/apply.c vendored Normal file
View file

@ -0,0 +1,34 @@
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
#include "lockfile.h"
#include "apply.h"
static const char * const apply_usage[] = {
N_("git apply [<options>] [<patch>...]"),
NULL
};
int cmd_apply(int argc, const char **argv, const char *prefix)
{
int force_apply = 0;
int options = 0;
int ret;
struct apply_state state;
if (init_apply_state(&state, the_repository, prefix))
exit(128);
argc = apply_parse_options(argc, argv,
&state, &force_apply, &options,
apply_usage);
if (check_apply_state(&state, force_apply))
exit(128);
ret = apply_all_patches(&state, argc, argv, options);
clear_apply_state(&state);
return ret;
}

112
third_party/git/builtin/archive.c vendored Normal file
View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2006 Franck Bui-Huu
* Copyright (c) 2006 Rene Scharfe
*/
#include "cache.h"
#include "builtin.h"
#include "archive.h"
#include "transport.h"
#include "parse-options.h"
#include "pkt-line.h"
#include "sideband.h"
static void create_output_file(const char *output_file)
{
int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (output_fd < 0)
die_errno(_("could not create archive file '%s'"), output_file);
if (output_fd != 1) {
if (dup2(output_fd, 1) < 0)
die_errno(_("could not redirect output"));
else
close(output_fd);
}
}
static int run_remote_archiver(int argc, const char **argv,
const char *remote, const char *exec,
const char *name_hint)
{
int fd[2], i, rv;
struct transport *transport;
struct remote *_remote;
struct packet_reader reader;
_remote = remote_get(remote);
if (!_remote->url[0])
die(_("git archive: Remote with no URL"));
transport = transport_get(_remote, _remote->url[0]);
transport_connect(transport, "git-upload-archive", exec, fd);
/*
* Inject a fake --format field at the beginning of the
* arguments, with the format inferred from our output
* filename. This way explicit --format options can override
* it.
*/
if (name_hint) {
const char *format = archive_format_from_filename(name_hint);
if (format)
packet_write_fmt(fd[1], "argument --format=%s\n", format);
}
for (i = 1; i < argc; i++)
packet_write_fmt(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
packet_reader_init(&reader, fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_DIE_ON_ERR_PACKET);
if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
die(_("git archive: expected ACK/NAK, got a flush packet"));
if (strcmp(reader.line, "ACK")) {
if (starts_with(reader.line, "NACK "))
die(_("git archive: NACK %s"), reader.line + 5);
die(_("git archive: protocol error"));
}
if (packet_reader_read(&reader) != PACKET_READ_FLUSH)
die(_("git archive: expected a flush"));
/* Now, start reading from fd[0] and spit it out to stdout */
rv = recv_sideband("archive", fd[0], 1);
rv |= transport_disconnect(transport);
return !!rv;
}
#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
PARSE_OPT_KEEP_ARGV0 | \
PARSE_OPT_KEEP_UNKNOWN | \
PARSE_OPT_NO_INTERNAL_HELP )
int cmd_archive(int argc, const char **argv, const char *prefix)
{
const char *exec = "git-upload-archive";
const char *output = NULL;
const char *remote = NULL;
struct option local_opts[] = {
OPT_FILENAME('o', "output", &output,
N_("write the archive to this file")),
OPT_STRING(0, "remote", &remote, N_("repo"),
N_("retrieve the archive from remote repository <repo>")),
OPT_STRING(0, "exec", &exec, N_("command"),
N_("path to the remote git-upload-archive command")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, local_opts, NULL,
PARSE_OPT_KEEP_ALL);
init_archivers();
if (output)
create_output_file(output);
if (remote)
return run_remote_archiver(argc, argv, remote, exec, output);
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
return write_archive(argc, argv, prefix, the_repository, output, 0);
}

714
third_party/git/builtin/bisect--helper.c vendored Normal file
View file

@ -0,0 +1,714 @@
#include "builtin.h"
#include "cache.h"
#include "parse-options.h"
#include "bisect.h"
#include "refs.h"
#include "dir.h"
#include "argv-array.h"
#include "run-command.h"
#include "prompt.h"
#include "quote.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_head_name, "head-name")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --next-all [--no-checkout]"),
N_("git bisect--helper --write-terms <bad_term> <good_term>"),
N_("git bisect--helper --bisect-clean-state"),
N_("git bisect--helper --bisect-reset [<commit>]"),
N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"),
N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
"[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
NULL
};
struct bisect_terms {
char *term_good;
char *term_bad;
};
static void free_terms(struct bisect_terms *terms)
{
FREE_AND_NULL(terms->term_good);
FREE_AND_NULL(terms->term_bad);
}
static void set_terms(struct bisect_terms *terms, const char *bad,
const char *good)
{
free((void *)terms->term_good);
terms->term_good = xstrdup(good);
free((void *)terms->term_bad);
terms->term_bad = xstrdup(bad);
}
static const char *vocab_bad = "bad|new";
static const char *vocab_good = "good|old";
/*
* Check whether the string `term` belongs to the set of strings
* included in the variable arguments.
*/
LAST_ARG_MUST_BE_NULL
static int one_of(const char *term, ...)
{
int res = 0;
va_list matches;
const char *match;
va_start(matches, term);
while (!res && (match = va_arg(matches, const char *)))
res = !strcmp(term, match);
va_end(matches);
return res;
}
static int check_term_format(const char *term, const char *orig_term)
{
int res;
char *new_term = xstrfmt("refs/bisect/%s", term);
res = check_refname_format(new_term, 0);
free(new_term);
if (res)
return error(_("'%s' is not a valid term"), term);
if (one_of(term, "help", "start", "skip", "next", "reset",
"visualize", "view", "replay", "log", "run", "terms", NULL))
return error(_("can't use the builtin command '%s' as a term"), term);
/*
* In theory, nothing prevents swapping completely good and bad,
* but this situation could be confusing and hasn't been tested
* enough. Forbid it for now.
*/
if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) ||
(strcmp(orig_term, "good") && one_of(term, "good", "old", NULL)))
return error(_("can't change the meaning of the term '%s'"), term);
return 0;
}
static int write_terms(const char *bad, const char *good)
{
FILE *fp = NULL;
int res;
if (!strcmp(bad, good))
return error(_("please use two different terms"));
if (check_term_format(bad, "bad") || check_term_format(good, "good"))
return -1;
fp = fopen(git_path_bisect_terms(), "w");
if (!fp)
return error_errno(_("could not open the file BISECT_TERMS"));
res = fprintf(fp, "%s\n%s\n", bad, good);
res |= fclose(fp);
return (res < 0) ? -1 : 0;
}
static int is_expected_rev(const char *expected_hex)
{
struct strbuf actual_hex = STRBUF_INIT;
int res = 0;
if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
strbuf_trim(&actual_hex);
res = !strcmp(actual_hex.buf, expected_hex);
}
strbuf_release(&actual_hex);
return res;
}
static void check_expected_revs(const char **revs, int rev_nr)
{
int i;
for (i = 0; i < rev_nr; i++) {
if (!is_expected_rev(revs[i])) {
unlink_or_warn(git_path_bisect_ancestors_ok());
unlink_or_warn(git_path_bisect_expected_rev());
}
}
}
static int bisect_reset(const char *commit)
{
struct strbuf branch = STRBUF_INIT;
if (!commit) {
if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) {
printf(_("We are not bisecting.\n"));
return 0;
}
strbuf_rtrim(&branch);
} else {
struct object_id oid;
if (get_oid_commit(commit, &oid))
return error(_("'%s' is not a valid commit"), commit);
strbuf_addstr(&branch, commit);
}
if (!file_exists(git_path_bisect_head())) {
struct argv_array argv = ARGV_ARRAY_INIT;
argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
strbuf_release(&branch);
argv_array_clear(&argv);
return error(_("could not check out original"
" HEAD '%s'. Try 'git bisect"
" reset <commit>'."), branch.buf);
}
argv_array_clear(&argv);
}
strbuf_release(&branch);
return bisect_clean_state();
}
static void log_commit(FILE *fp, char *fmt, const char *state,
struct commit *commit)
{
struct pretty_print_context pp = {0};
struct strbuf commit_msg = STRBUF_INIT;
char *label = xstrfmt(fmt, state);
format_commit_message(commit, "%s", &commit_msg, &pp);
fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid),
commit_msg.buf);
strbuf_release(&commit_msg);
free(label);
}
static int bisect_write(const char *state, const char *rev,
const struct bisect_terms *terms, int nolog)
{
struct strbuf tag = STRBUF_INIT;
struct object_id oid;
struct commit *commit;
FILE *fp = NULL;
int retval = 0;
if (!strcmp(state, terms->term_bad)) {
strbuf_addf(&tag, "refs/bisect/%s", state);
} else if (one_of(state, terms->term_good, "skip", NULL)) {
strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
} else {
retval = error(_("Bad bisect_write argument: %s"), state);
goto finish;
}
if (get_oid(rev, &oid)) {
retval = error(_("couldn't get the oid of the rev '%s'"), rev);
goto finish;
}
if (update_ref(NULL, tag.buf, &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
retval = -1;
goto finish;
}
fp = fopen(git_path_bisect_log(), "a");
if (!fp) {
retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
goto finish;
}
commit = lookup_commit_reference(the_repository, &oid);
log_commit(fp, "%s", state, commit);
if (!nolog)
fprintf(fp, "git bisect %s %s\n", state, rev);
finish:
if (fp)
fclose(fp);
strbuf_release(&tag);
return retval;
}
static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
{
int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms());
if (one_of(cmd, "skip", "start", "terms", NULL))
return 0;
if (has_term_file && strcmp(cmd, terms->term_bad) &&
strcmp(cmd, terms->term_good))
return error(_("Invalid command: you're currently in a "
"%s/%s bisect"), terms->term_bad,
terms->term_good);
if (!has_term_file) {
if (one_of(cmd, "bad", "good", NULL)) {
set_terms(terms, "bad", "good");
return write_terms(terms->term_bad, terms->term_good);
}
if (one_of(cmd, "new", "old", NULL)) {
set_terms(terms, "new", "old");
return write_terms(terms->term_bad, terms->term_good);
}
}
return 0;
}
static int mark_good(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
int *m_good = (int *)cb_data;
*m_good = 0;
return 1;
}
static const char *need_bad_and_good_revision_warning =
N_("You need to give me at least one %s and %s revision.\n"
"You can use \"git bisect %s\" and \"git bisect %s\" for that.");
static const char *need_bisect_start_warning =
N_("You need to start by \"git bisect start\".\n"
"You then need to give me at least one %s and %s revision.\n"
"You can use \"git bisect %s\" and \"git bisect %s\" for that.");
static int bisect_next_check(const struct bisect_terms *terms,
const char *current_term)
{
int missing_good = 1, missing_bad = 1, retval = 0;
const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
const char *good_glob = xstrfmt("%s-*", terms->term_good);
if (ref_exists(bad_ref))
missing_bad = 0;
for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
(void *) &missing_good);
if (!missing_good && !missing_bad)
goto finish;
if (!current_term) {
retval = -1;
goto finish;
}
if (missing_good && !missing_bad &&
!strcmp(current_term, terms->term_good)) {
char *yesno;
/*
* have bad (or new) but not good (or old). We could bisect
* although this is less optimum.
*/
warning(_("bisecting only with a %s commit"), terms->term_bad);
if (!isatty(0))
goto finish;
/*
* TRANSLATORS: Make sure to include [Y] and [n] in your
* translation. The program will only accept English input
* at this point.
*/
yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO);
if (starts_with(yesno, "N") || starts_with(yesno, "n"))
retval = -1;
goto finish;
}
if (!is_empty_or_missing_file(git_path_bisect_start())) {
retval = error(_(need_bad_and_good_revision_warning),
vocab_bad, vocab_good, vocab_bad, vocab_good);
} else {
retval = error(_(need_bisect_start_warning),
vocab_good, vocab_bad, vocab_good, vocab_bad);
}
finish:
free((void *) good_glob);
free((void *) bad_ref);
return retval;
}
static int get_terms(struct bisect_terms *terms)
{
struct strbuf str = STRBUF_INIT;
FILE *fp = NULL;
int res = 0;
fp = fopen(git_path_bisect_terms(), "r");
if (!fp) {
res = -1;
goto finish;
}
free_terms(terms);
strbuf_getline_lf(&str, fp);
terms->term_bad = strbuf_detach(&str, NULL);
strbuf_getline_lf(&str, fp);
terms->term_good = strbuf_detach(&str, NULL);
finish:
if (fp)
fclose(fp);
strbuf_release(&str);
return res;
}
static int bisect_terms(struct bisect_terms *terms, const char *option)
{
if (get_terms(terms))
return error(_("no terms defined"));
if (option == NULL) {
printf(_("Your current terms are %s for the old state\n"
"and %s for the new state.\n"),
terms->term_good, terms->term_bad);
return 0;
}
if (one_of(option, "--term-good", "--term-old", NULL))
printf("%s\n", terms->term_good);
else if (one_of(option, "--term-bad", "--term-new", NULL))
printf("%s\n", terms->term_bad);
else
return error(_("invalid argument %s for 'git bisect terms'.\n"
"Supported options are: "
"--term-good|--term-old and "
"--term-bad|--term-new."), option);
return 0;
}
static int bisect_append_log_quoted(const char **argv)
{
int retval = 0;
FILE *fp = fopen(git_path_bisect_log(), "a");
struct strbuf orig_args = STRBUF_INIT;
if (!fp)
return -1;
if (fprintf(fp, "git bisect start") < 1) {
retval = -1;
goto finish;
}
sq_quote_argv(&orig_args, argv);
if (fprintf(fp, "%s\n", orig_args.buf) < 1)
retval = -1;
finish:
fclose(fp);
strbuf_release(&orig_args);
return retval;
}
static int bisect_start(struct bisect_terms *terms, int no_checkout,
const char **argv, int argc)
{
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos, retval = 0;
struct string_list revs = STRING_LIST_INIT_DUP;
struct string_list states = STRING_LIST_INIT_DUP;
struct strbuf start_head = STRBUF_INIT;
struct strbuf bisect_names = STRBUF_INIT;
struct object_id head_oid;
struct object_id oid;
const char *head;
if (is_bare_repository())
no_checkout = 1;
/*
* Check for one bad and then some good revisions
*/
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "--")) {
has_double_dash = 1;
break;
}
}
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(argv[i], "--")) {
break;
} else if (!strcmp(arg, "--no-checkout")) {
no_checkout = 1;
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
must_write_terms = 1;
free((void *) terms->term_good);
terms->term_good = xstrdup(argv[++i]);
} else if (skip_prefix(arg, "--term-good=", &arg) ||
skip_prefix(arg, "--term-old=", &arg)) {
must_write_terms = 1;
free((void *) terms->term_good);
terms->term_good = xstrdup(arg);
} else if (!strcmp(arg, "--term-bad") ||
!strcmp(arg, "--term-new")) {
must_write_terms = 1;
free((void *) terms->term_bad);
terms->term_bad = xstrdup(argv[++i]);
} else if (skip_prefix(arg, "--term-bad=", &arg) ||
skip_prefix(arg, "--term-new=", &arg)) {
must_write_terms = 1;
free((void *) terms->term_bad);
terms->term_bad = xstrdup(arg);
} else if (starts_with(arg, "--") &&
!one_of(arg, "--term-good", "--term-bad", NULL)) {
return error(_("unrecognized option: '%s'"), arg);
} else {
char *commit_id = xstrfmt("%s^{commit}", arg);
if (get_oid(commit_id, &oid) && has_double_dash)
die(_("'%s' does not appear to be a valid "
"revision"), arg);
string_list_append(&revs, oid_to_hex(&oid));
free(commit_id);
}
}
pathspec_pos = i;
/*
* The user ran "git bisect start <sha1> <sha1>", hence did not
* explicitly specify the terms, but we are already starting to
* set references named with the default terms, and won't be able
* to change afterwards.
*/
if (revs.nr)
must_write_terms = 1;
for (i = 0; i < revs.nr; i++) {
if (bad_seen) {
string_list_append(&states, terms->term_good);
} else {
bad_seen = 1;
string_list_append(&states, terms->term_bad);
}
}
/*
* Verify HEAD
*/
head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags);
if (!head)
if (get_oid("HEAD", &head_oid))
return error(_("bad HEAD - I need a HEAD"));
/*
* Check if we are bisecting
*/
if (!is_empty_or_missing_file(git_path_bisect_start())) {
/* Reset to the rev from where we started */
strbuf_read_file(&start_head, git_path_bisect_start(), 0);
strbuf_trim(&start_head);
if (!no_checkout) {
struct argv_array argv = ARGV_ARRAY_INIT;
argv_array_pushl(&argv, "checkout", start_head.buf,
"--", NULL);
if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
retval = error(_("checking out '%s' failed."
" Try 'git bisect start "
"<valid-branch>'."),
start_head.buf);
goto finish;
}
}
} else {
/* Get the rev from where we start. */
if (!get_oid(head, &head_oid) &&
!starts_with(head, "refs/heads/")) {
strbuf_reset(&start_head);
strbuf_addstr(&start_head, oid_to_hex(&head_oid));
} else if (!get_oid(head, &head_oid) &&
skip_prefix(head, "refs/heads/", &head)) {
/*
* This error message should only be triggered by
* cogito usage, and cogito users should understand
* it relates to cg-seek.
*/
if (!is_empty_or_missing_file(git_path_head_name()))
return error(_("won't bisect on cg-seek'ed tree"));
strbuf_addstr(&start_head, head);
} else {
return error(_("bad HEAD - strange symbolic ref"));
}
}
/*
* Get rid of any old bisect state.
*/
if (bisect_clean_state())
return -1;
/*
* In case of mistaken revs or checkout error, or signals received,
* "bisect_auto_next" below may exit or misbehave.
* We have to trap this to be able to clean up using
* "bisect_clean_state".
*/
/*
* Write new start state
*/
write_file(git_path_bisect_start(), "%s\n", start_head.buf);
if (no_checkout) {
if (get_oid(start_head.buf, &oid) < 0) {
retval = error(_("invalid ref: '%s'"), start_head.buf);
goto finish;
}
if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
retval = -1;
goto finish;
}
}
if (pathspec_pos < argc - 1)
sq_quote_argv(&bisect_names, argv + pathspec_pos);
write_file(git_path_bisect_names(), "%s\n", bisect_names.buf);
for (i = 0; i < states.nr; i++)
if (bisect_write(states.items[i].string,
revs.items[i].string, terms, 1)) {
retval = -1;
goto finish;
}
if (must_write_terms && write_terms(terms->term_bad,
terms->term_good)) {
retval = -1;
goto finish;
}
retval = bisect_append_log_quoted(argv);
if (retval)
retval = -1;
finish:
string_list_clear(&revs, 0);
string_list_clear(&states, 0);
strbuf_release(&start_head);
strbuf_release(&bisect_names);
return retval;
}
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
NEXT_ALL = 1,
WRITE_TERMS,
BISECT_CLEAN_STATE,
CHECK_EXPECTED_REVS,
BISECT_RESET,
BISECT_WRITE,
CHECK_AND_SET_TERMS,
BISECT_NEXT_CHECK,
BISECT_TERMS,
BISECT_START
} cmdmode = 0;
int no_checkout = 0, res = 0, nolog = 0;
struct option options[] = {
OPT_CMDMODE(0, "next-all", &cmdmode,
N_("perform 'git bisect next'"), NEXT_ALL),
OPT_CMDMODE(0, "write-terms", &cmdmode,
N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
N_("check for expected revs"), CHECK_EXPECTED_REVS),
OPT_CMDMODE(0, "bisect-reset", &cmdmode,
N_("reset the bisection state"), BISECT_RESET),
OPT_CMDMODE(0, "bisect-write", &cmdmode,
N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE),
OPT_CMDMODE(0, "check-and-set-terms", &cmdmode,
N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS),
OPT_CMDMODE(0, "bisect-next-check", &cmdmode,
N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
OPT_CMDMODE(0, "bisect-terms", &cmdmode,
N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START),
OPT_BOOL(0, "no-checkout", &no_checkout,
N_("update BISECT_HEAD instead of checking out the current commit")),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
};
struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
argc = parse_options(argc, argv, prefix, options,
git_bisect_helper_usage,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
if (!cmdmode)
usage_with_options(git_bisect_helper_usage, options);
switch (cmdmode) {
case NEXT_ALL:
return bisect_next_all(the_repository, prefix, no_checkout);
case WRITE_TERMS:
if (argc != 2)
return error(_("--write-terms requires two arguments"));
return write_terms(argv[0], argv[1]);
case BISECT_CLEAN_STATE:
if (argc != 0)
return error(_("--bisect-clean-state requires no arguments"));
return bisect_clean_state();
case CHECK_EXPECTED_REVS:
check_expected_revs(argv, argc);
return 0;
case BISECT_RESET:
if (argc > 1)
return error(_("--bisect-reset requires either no argument or a commit"));
return !!bisect_reset(argc ? argv[0] : NULL);
case BISECT_WRITE:
if (argc != 4 && argc != 5)
return error(_("--bisect-write requires either 4 or 5 arguments"));
set_terms(&terms, argv[3], argv[2]);
res = bisect_write(argv[0], argv[1], &terms, nolog);
break;
case CHECK_AND_SET_TERMS:
if (argc != 3)
return error(_("--check-and-set-terms requires 3 arguments"));
set_terms(&terms, argv[2], argv[1]);
res = check_and_set_terms(&terms, argv[0]);
break;
case BISECT_NEXT_CHECK:
if (argc != 2 && argc != 3)
return error(_("--bisect-next-check requires 2 or 3 arguments"));
set_terms(&terms, argv[1], argv[0]);
res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL);
break;
case BISECT_TERMS:
if (argc > 1)
return error(_("--bisect-terms requires 0 or 1 argument"));
res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
break;
case BISECT_START:
set_terms(&terms, "bad", "good");
res = bisect_start(&terms, no_checkout, argv, argc);
break;
default:
return error("BUG: unknown subcommand '%d'", cmdmode);
}
free_terms(&terms);
return !!res;
}

1176
third_party/git/builtin/blame.c vendored Normal file

File diff suppressed because it is too large Load diff

855
third_party/git/builtin/branch.c vendored Normal file
View file

@ -0,0 +1,855 @@
/*
* Builtin "git branch"
*
* Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
* Based on git-branch.sh by Junio C Hamano.
*/
#include "cache.h"
#include "config.h"
#include "color.h"
#include "refs.h"
#include "commit.h"
#include "builtin.h"
#include "remote.h"
#include "parse-options.h"
#include "branch.h"
#include "diff.h"
#include "revision.h"
#include "string-list.h"
#include "column.h"
#include "utf8.h"
#include "wt-status.h"
#include "ref-filter.h"
#include "worktree.h"
#include "help.h"
#include "commit-reach.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
N_("git branch [<options>] [-r | -a] [--points-at]"),
N_("git branch [<options>] [-r | -a] [--format]"),
NULL
};
static const char *head;
static struct object_id head_oid;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
GIT_COLOR_NORMAL, /* PLAIN */
GIT_COLOR_RED, /* REMOTE */
GIT_COLOR_NORMAL, /* LOCAL */
GIT_COLOR_GREEN, /* CURRENT */
GIT_COLOR_BLUE, /* UPSTREAM */
GIT_COLOR_CYAN, /* WORKTREE */
};
enum color_branch {
BRANCH_COLOR_RESET = 0,
BRANCH_COLOR_PLAIN = 1,
BRANCH_COLOR_REMOTE = 2,
BRANCH_COLOR_LOCAL = 3,
BRANCH_COLOR_CURRENT = 4,
BRANCH_COLOR_UPSTREAM = 5,
BRANCH_COLOR_WORKTREE = 6
};
static const char *color_branch_slots[] = {
[BRANCH_COLOR_RESET] = "reset",
[BRANCH_COLOR_PLAIN] = "plain",
[BRANCH_COLOR_REMOTE] = "remote",
[BRANCH_COLOR_LOCAL] = "local",
[BRANCH_COLOR_CURRENT] = "current",
[BRANCH_COLOR_UPSTREAM] = "upstream",
[BRANCH_COLOR_WORKTREE] = "worktree",
};
static struct string_list output = STRING_LIST_INIT_DUP;
static unsigned int colopts;
define_list_config_array(color_branch_slots);
static int git_branch_config(const char *var, const char *value, void *cb)
{
const char *slot_name;
struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
if (!strcmp(var, "branch.sort")) {
if (!value)
return config_error_nonbool(var);
parse_ref_sorting(sorting_tail, value);
return 0;
}
if (starts_with(var, "column."))
return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
}
if (skip_prefix(var, "color.branch.", &slot_name)) {
int slot = LOOKUP_CONFIG(color_branch_slots, slot_name);
if (slot < 0)
return 0;
if (!value)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
return git_color_default_config(var, value, cb);
}
static const char *branch_get_color(enum color_branch ix)
{
if (want_color(branch_use_color))
return branch_colors[ix];
return "";
}
static int branch_merged(int kind, const char *name,
struct commit *rev, struct commit *head_rev)
{
/*
* This checks whether the merge bases of branch and HEAD (or
* the other branch this branch builds upon) contains the
* branch, which means that the branch has already been merged
* safely to HEAD (or the other branch).
*/
struct commit *reference_rev = NULL;
const char *reference_name = NULL;
void *reference_name_to_free = NULL;
int merged;
if (kind == FILTER_REFS_BRANCHES) {
struct branch *branch = branch_get(name);
const char *upstream = branch_get_upstream(branch, NULL);
struct object_id oid;
if (upstream &&
(reference_name = reference_name_to_free =
resolve_refdup(upstream, RESOLVE_REF_READING,
&oid, NULL)) != NULL)
reference_rev = lookup_commit_reference(the_repository,
&oid);
}
if (!reference_rev)
reference_rev = head_rev;
merged = in_merge_bases(rev, reference_rev);
/*
* After the safety valve is fully redefined to "check with
* upstream, if any, otherwise with HEAD", we should just
* return the result of the in_merge_bases() above without
* any of the following code, but during the transition period,
* a gentle reminder is in order.
*/
if ((head_rev != reference_rev) &&
in_merge_bases(rev, head_rev) != merged) {
if (merged)
warning(_("deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."),
name, reference_name);
else
warning(_("not deleting branch '%s' that is not yet merged to\n"
" '%s', even though it is merged to HEAD."),
name, reference_name);
}
free(reference_name_to_free);
return merged;
}
static int check_branch_commit(const char *branchname, const char *refname,
const struct object_id *oid, struct commit *head_rev,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(the_repository, oid);
if (!rev) {
error(_("Couldn't look up commit object for '%s'"), refname);
return -1;
}
if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
error(_("The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, "
"run 'git branch -D %s'."), branchname, branchname);
return -1;
}
return 0;
}
static void delete_branch_config(const char *branchname)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "branch.%s", branchname);
if (git_config_rename_section(buf.buf, NULL) < 0)
warning(_("Update of config-file failed"));
strbuf_release(&buf);
}
static int delete_branches(int argc, const char **argv, int force, int kinds,
int quiet)
{
struct commit *head_rev = NULL;
struct object_id oid;
char *name = NULL;
const char *fmt;
int i;
int ret = 0;
int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
unsigned allowed_interpret;
switch (kinds) {
case FILTER_REFS_REMOTES:
fmt = "refs/remotes/%s";
/* For subsequent UI messages */
remote_branch = 1;
allowed_interpret = INTERPRET_BRANCH_REMOTE;
force = 1;
break;
case FILTER_REFS_BRANCHES:
fmt = "refs/heads/%s";
allowed_interpret = INTERPRET_BRANCH_LOCAL;
break;
default:
die(_("cannot use -a with -d"));
}
if (!force) {
head_rev = lookup_commit_reference(the_repository, &head_oid);
if (!head_rev)
die(_("Couldn't look up commit object for HEAD"));
}
for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
char *target = NULL;
int flags = 0;
strbuf_branchname(&bname, argv[i], allowed_interpret);
free(name);
name = mkpathdup(fmt, bname.buf);
if (kinds == FILTER_REFS_BRANCHES) {
const struct worktree *wt =
find_shared_symref("HEAD", name);
if (wt) {
error(_("Cannot delete branch '%s' "
"checked out at '%s'"),
bname.buf, wt->path);
ret = 1;
continue;
}
}
target = resolve_refdup(name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
| RESOLVE_REF_ALLOW_BAD_NAME,
&oid, &flags);
if (!target) {
error(remote_branch
? _("remote-tracking branch '%s' not found.")
: _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
}
if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
force)) {
ret = 1;
goto next;
}
if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
REF_NO_DEREF)) {
error(remote_branch
? _("Error deleting remote-tracking branch '%s'")
: _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
goto next;
}
if (!quiet) {
printf(remote_branch
? _("Deleted remote-tracking branch %s (was %s).\n")
: _("Deleted branch %s (was %s).\n"),
bname.buf,
(flags & REF_ISBROKEN) ? "broken"
: (flags & REF_ISSYMREF) ? target
: find_unique_abbrev(&oid, DEFAULT_ABBREV));
}
delete_branch_config(bname.buf);
next:
free(target);
}
free(name);
strbuf_release(&bname);
return ret;
}
static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
{
int i, max = 0;
for (i = 0; i < refs->nr; i++) {
struct ref_array_item *it = refs->items[i];
const char *desc = it->refname;
int w;
skip_prefix(it->refname, "refs/heads/", &desc);
skip_prefix(it->refname, "refs/remotes/", &desc);
if (it->kind == FILTER_REFS_DETACHED_HEAD) {
char *head_desc = get_head_description();
w = utf8_strwidth(head_desc);
free(head_desc);
} else
w = utf8_strwidth(desc);
if (it->kind == FILTER_REFS_REMOTES)
w += remote_bonus;
if (w > max)
max = w;
}
return max;
}
static const char *quote_literal_for_format(const char *s)
{
static struct strbuf buf = STRBUF_INIT;
strbuf_reset(&buf);
while (*s) {
const char *ep = strchrnul(s, '%');
if (s < ep)
strbuf_add(&buf, s, ep - s);
if (*ep == '%') {
strbuf_addstr(&buf, "%%");
s = ep + 1;
} else {
s = ep;
}
}
return buf.buf;
}
static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix)
{
struct strbuf fmt = STRBUF_INIT;
struct strbuf local = STRBUF_INIT;
struct strbuf remote = STRBUF_INIT;
strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)",
branch_get_color(BRANCH_COLOR_CURRENT),
branch_get_color(BRANCH_COLOR_WORKTREE),
branch_get_color(BRANCH_COLOR_LOCAL));
strbuf_addf(&remote, " %s",
branch_get_color(BRANCH_COLOR_REMOTE));
if (filter->verbose) {
struct strbuf obname = STRBUF_INIT;
if (filter->abbrev < 0)
strbuf_addf(&obname, "%%(objectname:short)");
else if (!filter->abbrev)
strbuf_addf(&obname, "%%(objectname)");
else
strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev);
strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
strbuf_addstr(&local, branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&local, " %s ", obname.buf);
if (filter->verbose > 1)
{
strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)",
branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
"%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
}
else
strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
strbuf_addf(&remote, "%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
"%%(if)%%(symref)%%(then) -> %%(symref:short)"
"%%(else) %s %%(contents:subject)%%(end)",
maxwidth, quote_literal_for_format(remote_prefix),
branch_get_color(BRANCH_COLOR_RESET), obname.buf);
strbuf_release(&obname);
} else {
strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&remote, "%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
quote_literal_for_format(remote_prefix),
branch_get_color(BRANCH_COLOR_RESET));
}
strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
strbuf_release(&local);
strbuf_release(&remote);
return strbuf_detach(&fmt, NULL);
}
static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format)
{
int i;
struct ref_array array;
int maxwidth = 0;
const char *remote_prefix = "";
char *to_free = NULL;
/*
* If we are listing more than just remote branches,
* then remote branches will have a "remotes/" prefix.
* We need to account for this in the width.
*/
if (filter->kind != FILTER_REFS_REMOTES)
remote_prefix = "remotes/";
memset(&array, 0, sizeof(array));
filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
if (!format->format)
format->format = to_free = build_format(filter, maxwidth, remote_prefix);
format->use_color = branch_use_color;
if (verify_ref_format(format))
die(_("unable to parse format string"));
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++) {
struct strbuf out = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
if (format_ref_array_item(array.items[i], format, &out, &err))
die("%s", err.buf);
if (column_active(colopts)) {
assert(!filter->verbose && "--column and --verbose are incompatible");
/* format to a string_list to let print_columns() do its job */
string_list_append(&output, out.buf);
} else {
fwrite(out.buf, 1, out.len, stdout);
putchar('\n');
}
strbuf_release(&err);
strbuf_release(&out);
}
ref_array_clear(&array);
free(to_free);
}
static void print_current_branch_name(void)
{
int flags;
const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
const char *shortname;
if (!refname)
die(_("could not resolve HEAD"));
else if (!(flags & REF_ISSYMREF))
return;
else if (skip_prefix(refname, "refs/heads/", &shortname))
puts(shortname);
else
die(_("HEAD (%s) points outside of refs/heads/"), refname);
}
static void reject_rebase_or_bisect_branch(const char *target)
{
struct worktree **worktrees = get_worktrees(0);
int i;
for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];
if (!wt->is_detached)
continue;
if (is_worktree_being_rebased(wt, target))
die(_("Branch %s is being rebased at %s"),
target, wt->path);
if (is_worktree_being_bisected(wt, target))
die(_("Branch %s is being bisected at %s"),
target, wt->path);
}
free_worktrees(worktrees);
}
static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
const char *interpreted_oldname = NULL;
const char *interpreted_newname = NULL;
int recovery = 0;
if (!oldname) {
if (copy)
die(_("cannot copy the current branch while not on any."));
else
die(_("cannot rename the current branch while not on any."));
}
if (strbuf_check_branch_ref(&oldref, oldname)) {
/*
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
*/
if (ref_exists(oldref.buf))
recovery = 1;
else
die(_("Invalid branch name: '%s'"), oldname);
}
/*
* A command like "git branch -M currentbranch currentbranch" cannot
* cause the worktree to become inconsistent with HEAD, so allow it.
*/
if (!strcmp(oldname, newname))
validate_branchname(newname, &newref);
else
validate_new_branchname(newname, &newref, force);
reject_rebase_or_bisect_branch(oldref.buf);
if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
!skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
BUG("expected prefix missing for refs");
}
if (copy)
strbuf_addf(&logmsg, "Branch: copied %s to %s",
oldref.buf, newref.buf);
else
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch copy failed"));
if (recovery) {
if (copy)
warning(_("Created a copy of a misnamed branch '%s'"),
interpreted_oldname);
else
warning(_("Renamed a misnamed branch '%s' away"),
interpreted_oldname);
}
if (!copy &&
replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
strbuf_release(&logmsg);
strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
strbuf_release(&oldref);
strbuf_addf(&newsection, "branch.%s", interpreted_newname);
strbuf_release(&newref);
if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
die(_("Branch is renamed, but update of config-file failed"));
if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
die(_("Branch is copied, but update of config-file failed"));
strbuf_release(&oldsection);
strbuf_release(&newsection);
}
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
static int edit_branch_description(const char *branch_name)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf name = STRBUF_INIT;
read_branch_desc(&buf, branch_name);
if (!buf.len || buf.buf[buf.len-1] != '\n')
strbuf_addch(&buf, '\n');
strbuf_commented_addf(&buf,
_("Please edit the description for the branch\n"
" %s\n"
"Lines starting with '%c' will be stripped.\n"),
branch_name, comment_line_char);
write_file_buf(edit_description(), buf.buf, buf.len);
strbuf_reset(&buf);
if (launch_editor(edit_description(), &buf, NULL)) {
strbuf_release(&buf);
return -1;
}
strbuf_stripspace(&buf, 1);
strbuf_addf(&name, "branch.%s.description", branch_name);
git_config_set(name.buf, buf.len ? buf.buf : NULL);
strbuf_release(&name);
strbuf_release(&buf);
return 0;
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
int show_current = 0;
int reflog = 0, edit_description = 0;
int quiet = 0, unset_upstream = 0;
const char *new_upstream = NULL;
enum branch_track track;
struct ref_filter filter;
int icase = 0;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
struct ref_format format = REF_FORMAT_INIT;
struct option options[] = {
OPT_GROUP(N_("Generic options")),
OPT__VERBOSE(&filter.verbose,
N_("show hash and subject, give twice for upstream branch")),
OPT__QUIET(&quiet, N_("suppress informational messages")),
OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"),
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
FILTER_REFS_REMOTES),
OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
OPT_WITHOUT(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT__ABBREV(&filter.abbrev),
OPT_GROUP(N_("Specific git-branch actions:")),
OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES),
OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
OPT_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
OPT_BOOL('l', "list", &list, N_("list branch names")),
OPT_BOOL(0, "show-current", &show_current, N_("show current branch name")),
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
OPT_BOOL(0, "edit-description", &edit_description,
N_("edit the description for the branch")),
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
OPT_MERGED(&filter, N_("print only branches that are merged")),
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
OPT_REF_SORT(sorting_tail),
{
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), 0, parse_opt_object_name
},
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
setup_ref_filter_porcelain_msg();
memset(&filter, 0, sizeof(filter));
filter.kind = FILTER_REFS_BRANCHES;
filter.abbrev = -1;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_branch_usage, options);
git_config(git_branch_config, sorting_tail);
track = git_branch_track;
head = resolve_refdup("HEAD", 0, &head_oid, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
if (!strcmp(head, "HEAD"))
filter.detached = 1;
else if (!skip_prefix(head, "refs/heads/", &head))
die(_("HEAD not found below refs/heads!"));
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
!show_current && !unset_upstream && argc == 0)
list = 1;
if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
filter.no_commit)
list = 1;
if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
finalize_colopts(&colopts, -1);
if (filter.verbose) {
if (explicitly_enable_column(colopts))
die(_("--column and --verbose are incompatible"));
colopts = 0;
}
if (force) {
delete *= 2;
rename *= 2;
copy *= 2;
}
if (list)
setup_auto_pager("branch", 1);
if (delete) {
if (!argc)
die(_("branch name required"));
return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
} else if (show_current) {
print_current_branch_name();
return 0;
} else if (list) {
/* git branch --local also shows HEAD when it is detached */
if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
filter.kind |= FILTER_REFS_DETACHED_HEAD;
filter.name_patterns = argv;
/*
* If no sorting parameter is given then we default to sorting
* by 'refname'. This would give us an alphabetically sorted
* array with the 'HEAD' ref at the beginning followed by
* local branches 'refs/heads/...' and finally remote-tracking
* branches 'refs/remotes/...'.
*/
if (!sorting)
sorting = ref_default_sorting();
sorting->ignore_case = icase;
print_ref_list(&filter, sorting, &format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
return 0;
} else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
if (!argc) {
if (filter.detached)
die(_("Cannot give description to detached HEAD"));
branch_name = head;
} else if (argc == 1)
branch_name = argv[0];
else
die(_("cannot edit description of more than one branch"));
strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
if (!ref_exists(branch_ref.buf)) {
strbuf_release(&branch_ref);
if (!argc)
return error(_("No commit on branch '%s' yet."),
branch_name);
else
return error(_("No branch named '%s'."),
branch_name);
}
strbuf_release(&branch_ref);
if (edit_branch_description(branch_name))
return 1;
} else if (copy) {
if (!argc)
die(_("branch name required"));
else if (argc == 1)
copy_or_rename_branch(head, argv[0], 1, copy > 1);
else if (argc == 2)
copy_or_rename_branch(argv[0], argv[1], 1, copy > 1);
else
die(_("too many branches for a copy operation"));
} else if (rename) {
if (!argc)
die(_("branch name required"));
else if (argc == 1)
copy_or_rename_branch(head, argv[0], 0, rename > 1);
else if (argc == 2)
copy_or_rename_branch(argv[0], argv[1], 0, rename > 1);
else
die(_("too many arguments for a rename operation"));
} else if (new_upstream) {
struct branch *branch = branch_get(argv[0]);
if (argc > 1)
die(_("too many arguments to set new upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
die(_("could not set upstream of HEAD to %s when "
"it does not point to any branch."),
new_upstream);
die(_("no such branch '%s'"), argv[0]);
}
if (!ref_exists(branch->refname))
die(_("branch '%s' does not exist"), branch->name);
/*
* create_branch takes care of setting up the tracking
* info and making sure new_upstream is correct
*/
create_branch(the_repository, branch->name, new_upstream,
0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
if (argc > 1)
die(_("too many arguments to unset upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
die(_("could not unset upstream of HEAD when "
"it does not point to any branch."));
die(_("no such branch '%s'"), argv[0]);
}
if (!branch_has_merge_config(branch))
die(_("Branch '%s' has no upstream information"), branch->name);
strbuf_addf(&buf, "branch.%s.remote", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, 1);
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, 1);
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
create_branch(the_repository,
argv[0], (argc == 2) ? argv[1] : head,
force, 0, reflog, quiet, track);
} else
usage_with_options(builtin_branch_usage, options);
return 0;
}

67
third_party/git/builtin/bundle.c vendored Normal file
View file

@ -0,0 +1,67 @@
#include "builtin.h"
#include "cache.h"
#include "bundle.h"
/*
* Basic handler for bundle files to connect repositories via sneakernet.
* Invocation must include action.
* This function can create a bundle or provide information on an existing
* bundle supporting "fetch", "pull", and "ls-remote".
*/
static const char builtin_bundle_usage[] =
"git bundle create <file> <git-rev-list args>\n"
" or: git bundle verify <file>\n"
" or: git bundle list-heads <file> [<refname>...]\n"
" or: git bundle unbundle <file> [<refname>...]";
int cmd_bundle(int argc, const char **argv, const char *prefix)
{
struct bundle_header header;
const char *cmd, *bundle_file;
int bundle_fd = -1;
if (argc < 3)
usage(builtin_bundle_usage);
cmd = argv[1];
bundle_file = prefix_filename(prefix, argv[2]);
argc -= 2;
argv += 2;
memset(&header, 0, sizeof(header));
if (strcmp(cmd, "create") && (bundle_fd =
read_bundle_header(bundle_file, &header)) < 0)
return 1;
if (!strcmp(cmd, "verify")) {
close(bundle_fd);
if (argc != 1) {
usage(builtin_bundle_usage);
return 1;
}
if (verify_bundle(the_repository, &header, 1))
return 1;
fprintf(stderr, _("%s is okay\n"), bundle_file);
return 0;
}
if (!strcmp(cmd, "list-heads")) {
close(bundle_fd);
return !!list_bundle_refs(&header, argc, argv);
}
if (!strcmp(cmd, "create")) {
if (argc < 2) {
usage(builtin_bundle_usage);
return 1;
}
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
return !!create_bundle(the_repository, bundle_file, argc, argv);
} else if (!strcmp(cmd, "unbundle")) {
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
return !!unbundle(the_repository, &header, bundle_fd, 0) ||
list_bundle_refs(&header, argc, argv);
} else
usage(builtin_bundle_usage);
}

717
third_party/git/builtin/cat-file.c vendored Normal file
View file

@ -0,0 +1,717 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "diff.h"
#include "parse-options.h"
#include "userdiff.h"
#include "streaming.h"
#include "tree-walk.h"
#include "sha1-array.h"
#include "packfile.h"
#include "object-store.h"
struct batch_options {
int enabled;
int follow_symlinks;
int print_contents;
int buffer_output;
int all_objects;
int unordered;
int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */
const char *format;
};
static const char *force_path;
static int filter_object(const char *path, unsigned mode,
const struct object_id *oid,
char **buf, unsigned long *size)
{
enum object_type type;
*buf = read_object_file(oid, &type, size);
if (!*buf)
return error(_("cannot read object %s '%s'"),
oid_to_hex(oid), path);
if ((type == OBJ_BLOB) && S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
free(*buf);
*size = strbuf.len;
*buf = strbuf_detach(&strbuf, NULL);
}
}
return 0;
}
static int stream_blob(const struct object_id *oid)
{
if (stream_blob_to_fd(1, oid, NULL, 0))
die("unable to stream %s to stdout", oid_to_hex(oid));
return 0;
}
static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
int unknown_type)
{
struct object_id oid;
enum object_type type;
char *buf;
unsigned long size;
struct object_context obj_context;
struct object_info oi = OBJECT_INFO_INIT;
struct strbuf sb = STRBUF_INIT;
unsigned flags = OBJECT_INFO_LOOKUP_REPLACE;
const char *path = force_path;
if (unknown_type)
flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
if (get_oid_with_context(the_repository, obj_name,
GET_OID_RECORD_PATH,
&oid, &obj_context))
die("Not a valid object name %s", obj_name);
if (!path)
path = obj_context.path;
if (obj_context.mode == S_IFINVALID)
obj_context.mode = 0100644;
buf = NULL;
switch (opt) {
case 't':
oi.type_name = &sb;
if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0)
die("git cat-file: could not get object info");
if (sb.len) {
printf("%s\n", sb.buf);
strbuf_release(&sb);
return 0;
}
break;
case 's':
oi.sizep = &size;
if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0)
die("git cat-file: could not get object info");
printf("%"PRIuMAX"\n", (uintmax_t)size);
return 0;
case 'e':
return !has_object_file(&oid);
case 'w':
if (!path)
die("git cat-file --filters %s: <object> must be "
"<sha1:path>", obj_name);
if (filter_object(path, obj_context.mode,
&oid, &buf, &size))
return -1;
break;
case 'c':
if (!path)
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);
if (textconv_object(the_repository, path, obj_context.mode,
&oid, 1, &buf, &size))
break;
/* else fallthrough */
case 'p':
type = oid_object_info(the_repository, &oid, NULL);
if (type < 0)
die("Not a valid object name %s", obj_name);
/* custom pretty-print here */
if (type == OBJ_TREE) {
const char *ls_args[3] = { NULL };
ls_args[0] = "ls-tree";
ls_args[1] = obj_name;
return cmd_ls_tree(2, ls_args, NULL);
}
if (type == OBJ_BLOB)
return stream_blob(&oid);
buf = read_object_file(&oid, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
/* otherwise just spit out the data */
break;
case 0:
if (type_from_string(exp_type) == OBJ_BLOB) {
struct object_id blob_oid;
if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) {
char *buffer = read_object_file(&oid, &type,
&size);
const char *target;
if (!skip_prefix(buffer, "object ", &target) ||
get_oid_hex(target, &blob_oid))
die("%s not a valid tag", oid_to_hex(&oid));
free(buffer);
} else
oidcpy(&blob_oid, &oid);
if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB)
return stream_blob(&blob_oid);
/*
* we attempted to dereference a tag to a blob
* and failed; there may be new dereference
* mechanisms this code is not aware of.
* fall-back to the usual case.
*/
}
buf = read_object_with_reference(the_repository,
&oid, exp_type, &size, NULL);
break;
default:
die("git cat-file: unknown option: %s", exp_type);
}
if (!buf)
die("git cat-file %s: bad file", obj_name);
write_or_die(1, buf, size);
free(buf);
free(obj_context.path);
return 0;
}
struct expand_data {
struct object_id oid;
enum object_type type;
unsigned long size;
off_t disk_size;
const char *rest;
struct object_id delta_base_oid;
/*
* If mark_query is true, we do not expand anything, but rather
* just mark the object_info with items we wish to query.
*/
int mark_query;
/*
* Whether to split the input on whitespace before feeding it to
* get_sha1; this is decided during the mark_query phase based on
* whether we have a %(rest) token in our format.
*/
int split_on_whitespace;
/*
* After a mark_query run, this object_info is set up to be
* passed to oid_object_info_extended. It will point to the data
* elements above, so you can retrieve the response from there.
*/
struct object_info info;
/*
* This flag will be true if the requested batch format and options
* don't require us to call oid_object_info, which can then be
* optimized out.
*/
unsigned skip_object_info : 1;
};
static int is_atom(const char *atom, const char *s, int slen)
{
int alen = strlen(atom);
return alen == slen && !memcmp(atom, s, alen);
}
static void expand_atom(struct strbuf *sb, const char *atom, int len,
void *vdata)
{
struct expand_data *data = vdata;
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
strbuf_addstr(sb, oid_to_hex(&data->oid));
} else if (is_atom("objecttype", atom, len)) {
if (data->mark_query)
data->info.typep = &data->type;
else
strbuf_addstr(sb, type_name(data->type));
} else if (is_atom("objectsize", atom, len)) {
if (data->mark_query)
data->info.sizep = &data->size;
else
strbuf_addf(sb, "%"PRIuMAX , (uintmax_t)data->size);
} else if (is_atom("objectsize:disk", atom, len)) {
if (data->mark_query)
data->info.disk_sizep = &data->disk_size;
else
strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size);
} else if (is_atom("rest", atom, len)) {
if (data->mark_query)
data->split_on_whitespace = 1;
else if (data->rest)
strbuf_addstr(sb, data->rest);
} else if (is_atom("deltabase", atom, len)) {
if (data->mark_query)
data->info.delta_base_sha1 = data->delta_base_oid.hash;
else
strbuf_addstr(sb,
oid_to_hex(&data->delta_base_oid));
} else
die("unknown format element: %.*s", len, atom);
}
static size_t expand_format(struct strbuf *sb, const char *start, void *data)
{
const char *end;
if (*start != '(')
return 0;
end = strchr(start + 1, ')');
if (!end)
die("format element '%s' does not end in ')'", start);
expand_atom(sb, start + 1, end - start - 1, data);
return end - start + 1;
}
static void batch_write(struct batch_options *opt, const void *data, int len)
{
if (opt->buffer_output) {
if (fwrite(data, 1, len, stdout) != len)
die_errno("unable to write to stdout");
} else
write_or_die(1, data, len);
}
static void print_object_or_die(struct batch_options *opt, struct expand_data *data)
{
const struct object_id *oid = &data->oid;
assert(data->info.typep);
if (data->type == OBJ_BLOB) {
if (opt->buffer_output)
fflush(stdout);
if (opt->cmdmode) {
char *contents;
unsigned long size;
if (!data->rest)
die("missing path for '%s'", oid_to_hex(oid));
if (opt->cmdmode == 'w') {
if (filter_object(data->rest, 0100644, oid,
&contents, &size))
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
} else if (opt->cmdmode == 'c') {
enum object_type type;
if (!textconv_object(the_repository,
data->rest, 0100644, oid,
1, &contents, &size))
contents = read_object_file(oid,
&type,
&size);
if (!contents)
die("could not convert '%s' %s",
oid_to_hex(oid), data->rest);
} else
BUG("invalid cmdmode: %c", opt->cmdmode);
batch_write(opt, contents, size);
free(contents);
} else {
stream_blob(oid);
}
}
else {
enum object_type type;
unsigned long size;
void *contents;
contents = read_object_file(oid, &type, &size);
if (!contents)
die("object %s disappeared", oid_to_hex(oid));
if (type != data->type)
die("object %s changed type!?", oid_to_hex(oid));
if (data->info.sizep && size != data->size)
die("object %s changed size!?", oid_to_hex(oid));
batch_write(opt, contents, size);
free(contents);
}
}
static void batch_object_write(const char *obj_name,
struct strbuf *scratch,
struct batch_options *opt,
struct expand_data *data)
{
if (!data->skip_object_info &&
oid_object_info_extended(the_repository, &data->oid, &data->info,
OBJECT_INFO_LOOKUP_REPLACE) < 0) {
printf("%s missing\n",
obj_name ? obj_name : oid_to_hex(&data->oid));
fflush(stdout);
return;
}
strbuf_reset(scratch);
strbuf_expand(scratch, opt->format, expand_format, data);
strbuf_addch(scratch, '\n');
batch_write(opt, scratch->buf, scratch->len);
if (opt->print_contents) {
print_object_or_die(opt, data);
batch_write(opt, "\n", 1);
}
}
static void batch_one_object(const char *obj_name,
struct strbuf *scratch,
struct batch_options *opt,
struct expand_data *data)
{
struct object_context ctx;
int flags = opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0;
enum get_oid_result result;
result = get_oid_with_context(the_repository, obj_name,
flags, &data->oid, &ctx);
if (result != FOUND) {
switch (result) {
case MISSING_OBJECT:
printf("%s missing\n", obj_name);
break;
case SHORT_NAME_AMBIGUOUS:
printf("%s ambiguous\n", obj_name);
break;
case DANGLING_SYMLINK:
printf("dangling %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
case SYMLINK_LOOP:
printf("loop %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
case NOT_DIR:
printf("notdir %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
default:
BUG("unknown get_sha1_with_context result %d\n",
result);
break;
}
fflush(stdout);
return;
}
if (ctx.mode == 0) {
printf("symlink %"PRIuMAX"\n%s\n",
(uintmax_t)ctx.symlink_path.len,
ctx.symlink_path.buf);
fflush(stdout);
return;
}
batch_object_write(obj_name, scratch, opt, data);
}
struct object_cb_data {
struct batch_options *opt;
struct expand_data *expand;
struct oidset *seen;
struct strbuf *scratch;
};
static int batch_object_cb(const struct object_id *oid, void *vdata)
{
struct object_cb_data *data = vdata;
oidcpy(&data->expand->oid, oid);
batch_object_write(NULL, data->scratch, data->opt, data->expand);
return 0;
}
static int collect_loose_object(const struct object_id *oid,
const char *path,
void *data)
{
oid_array_append(data, oid);
return 0;
}
static int collect_packed_object(const struct object_id *oid,
struct packed_git *pack,
uint32_t pos,
void *data)
{
oid_array_append(data, oid);
return 0;
}
static int batch_unordered_object(const struct object_id *oid, void *vdata)
{
struct object_cb_data *data = vdata;
if (oidset_insert(data->seen, oid))
return 0;
return batch_object_cb(oid, data);
}
static int batch_unordered_loose(const struct object_id *oid,
const char *path,
void *data)
{
return batch_unordered_object(oid, data);
}
static int batch_unordered_packed(const struct object_id *oid,
struct packed_git *pack,
uint32_t pos,
void *data)
{
return batch_unordered_object(oid, data);
}
static int batch_objects(struct batch_options *opt)
{
struct strbuf input = STRBUF_INIT;
struct strbuf output = STRBUF_INIT;
struct expand_data data;
int save_warning;
int retval = 0;
if (!opt->format)
opt->format = "%(objectname) %(objecttype) %(objectsize)";
/*
* Expand once with our special mark_query flag, which will prime the
* object_info to be handed to oid_object_info_extended for each
* object.
*/
memset(&data, 0, sizeof(data));
data.mark_query = 1;
strbuf_expand(&output, opt->format, expand_format, &data);
data.mark_query = 0;
strbuf_release(&output);
if (opt->cmdmode)
data.split_on_whitespace = 1;
if (opt->all_objects) {
struct object_info empty = OBJECT_INFO_INIT;
if (!memcmp(&data.info, &empty, sizeof(empty)))
data.skip_object_info = 1;
}
/*
* If we are printing out the object, then always fill in the type,
* since we will want to decide whether or not to stream.
*/
if (opt->print_contents)
data.info.typep = &data.type;
if (opt->all_objects) {
struct object_cb_data cb;
if (repository_format_partial_clone)
warning("This repository has extensions.partialClone set. Some objects may not be loaded.");
cb.opt = opt;
cb.expand = &data;
cb.scratch = &output;
if (opt->unordered) {
struct oidset seen = OIDSET_INIT;
cb.seen = &seen;
for_each_loose_object(batch_unordered_loose, &cb, 0);
for_each_packed_object(batch_unordered_packed, &cb,
FOR_EACH_OBJECT_PACK_ORDER);
oidset_clear(&seen);
} else {
struct oid_array sa = OID_ARRAY_INIT;
for_each_loose_object(collect_loose_object, &sa, 0);
for_each_packed_object(collect_packed_object, &sa, 0);
oid_array_for_each_unique(&sa, batch_object_cb, &cb);
oid_array_clear(&sa);
}
strbuf_release(&output);
return 0;
}
/*
* We are going to call get_sha1 on a potentially very large number of
* objects. In most large cases, these will be actual object sha1s. The
* cost to double-check that each one is not also a ref (just so we can
* warn) ends up dwarfing the actual cost of the object lookups
* themselves. We can work around it by just turning off the warning.
*/
save_warning = warn_on_object_refname_ambiguity;
warn_on_object_refname_ambiguity = 0;
while (strbuf_getline(&input, stdin) != EOF) {
if (data.split_on_whitespace) {
/*
* Split at first whitespace, tying off the beginning
* of the string and saving the remainder (or NULL) in
* data.rest.
*/
char *p = strpbrk(input.buf, " \t");
if (p) {
while (*p && strchr(" \t", *p))
*p++ = '\0';
}
data.rest = p;
}
batch_one_object(input.buf, &output, opt, &data);
}
strbuf_release(&input);
strbuf_release(&output);
warn_on_object_refname_ambiguity = save_warning;
return retval;
}
static const char * const cat_file_usage[] = {
N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"),
NULL
};
static int git_cat_file_config(const char *var, const char *value, void *cb)
{
if (userdiff_config(var, value) < 0)
return -1;
return git_default_config(var, value, cb);
}
static int batch_option_callback(const struct option *opt,
const char *arg,
int unset)
{
struct batch_options *bo = opt->value;
BUG_ON_OPT_NEG(unset);
if (bo->enabled) {
return error(_("only one batch option may be specified"));
}
bo->enabled = 1;
bo->print_contents = !strcmp(opt->long_name, "batch");
bo->format = arg;
return 0;
}
int cmd_cat_file(int argc, const char **argv, const char *prefix)
{
int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
int unknown_type = 0;
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
OPT_CMDMODE('e', NULL, &opt,
N_("exit with zero when there's no error"), 'e'),
OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_CMDMODE(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
OPT_CMDMODE(0, "filters", &opt,
N_("for blob objects, run filters on object's content"), 'w'),
OPT_STRING(0, "path", &force_path, N_("blob"),
N_("use a specific path for --textconv/--filters")),
OPT_BOOL(0, "allow-unknown-type", &unknown_type,
N_("allow -s and -t to work with broken/corrupt objects")),
OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
{ OPTION_CALLBACK, 0, "batch", &batch, "format",
N_("show info and content of objects fed from the standard input"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
batch_option_callback },
{ OPTION_CALLBACK, 0, "batch-check", &batch, "format",
N_("show info about objects fed from the standard input"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
batch_option_callback },
OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
N_("follow in-tree symlinks (used with --batch or --batch-check)")),
OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
N_("show all objects with --batch or --batch-check")),
OPT_BOOL(0, "unordered", &batch.unordered,
N_("do not order --batch-all-objects output")),
OPT_END()
};
git_config(git_cat_file_config, NULL);
batch.buffer_output = -1;
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
if (opt) {
if (batch.enabled && (opt == 'c' || opt == 'w'))
batch.cmdmode = opt;
else if (argc == 1)
obj_name = argv[0];
else
usage_with_options(cat_file_usage, options);
}
if (!opt && !batch.enabled) {
if (argc == 2) {
exp_type = argv[0];
obj_name = argv[1];
} else
usage_with_options(cat_file_usage, options);
}
if (batch.enabled) {
if (batch.cmdmode != opt || argc)
usage_with_options(cat_file_usage, options);
if (batch.cmdmode && batch.all_objects)
die("--batch-all-objects cannot be combined with "
"--textconv nor with --filters");
}
if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
usage_with_options(cat_file_usage, options);
}
if (force_path && opt != 'c' && opt != 'w') {
error("--path=<path> needs --textconv or --filters");
usage_with_options(cat_file_usage, options);
}
if (force_path && batch.enabled) {
error("--path=<path> incompatible with --batch");
usage_with_options(cat_file_usage, options);
}
if (batch.buffer_output < 0)
batch.buffer_output = batch.all_objects;
if (batch.enabled)
return batch_objects(&batch);
if (unknown_type && opt != 't' && opt != 's')
die("git cat-file --allow-unknown-type: use with -s or -t");
return cat_one_file(opt, exp_type, obj_name, unknown_type);
}

189
third_party/git/builtin/check-attr.c vendored Normal file
View file

@ -0,0 +1,189 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "attr.h"
#include "quote.h"
#include "parse-options.h"
static int all_attrs;
static int cached_attrs;
static int stdin_paths;
static const char * const check_attr_usage[] = {
N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."),
N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"),
NULL
};
static int nul_term_line;
static const struct option check_attr_options[] = {
OPT_BOOL('a', "all", &all_attrs, N_("report all attributes set on file")),
OPT_BOOL(0, "cached", &cached_attrs, N_("use .gitattributes only from the index")),
OPT_BOOL(0 , "stdin", &stdin_paths, N_("read file names from stdin")),
OPT_BOOL('z', NULL, &nul_term_line,
N_("terminate input and output records by a NUL character")),
OPT_END()
};
static void output_attr(struct attr_check *check, const char *file)
{
int j;
int cnt = check->nr;
for (j = 0; j < cnt; j++) {
const char *value = check->items[j].value;
if (ATTR_TRUE(value))
value = "set";
else if (ATTR_FALSE(value))
value = "unset";
else if (ATTR_UNSET(value))
value = "unspecified";
if (nul_term_line) {
printf("%s%c" /* path */
"%s%c" /* attrname */
"%s%c" /* attrvalue */,
file, 0,
git_attr_name(check->items[j].attr), 0, value, 0);
} else {
quote_c_style(file, NULL, stdout, 0);
printf(": %s: %s\n",
git_attr_name(check->items[j].attr), value);
}
}
}
static void check_attr(const char *prefix,
struct attr_check *check,
int collect_all,
const char *file)
{
char *full_path =
prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
if (collect_all) {
git_all_attrs(&the_index, full_path, check);
} else {
git_check_attr(&the_index, full_path, check);
}
output_attr(check, file);
free(full_path);
}
static void check_attr_stdin_paths(const char *prefix,
struct attr_check *check,
int collect_all)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
strbuf_getline_fn getline_fn;
getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
while (getline_fn(&buf, stdin) != EOF) {
if (!nul_term_line && buf.buf[0] == '"') {
strbuf_reset(&unquoted);
if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
check_attr(prefix, check, collect_all, buf.buf);
maybe_flush_or_die(stdout, "attribute to stdout");
}
strbuf_release(&buf);
strbuf_release(&unquoted);
}
static NORETURN void error_with_usage(const char *msg)
{
error("%s", msg);
usage_with_options(check_attr_usage, check_attr_options);
}
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
struct attr_check *check;
int cnt, i, doubledash, filei;
if (!is_bare_repository())
setup_work_tree();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, check_attr_options,
check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
if (read_cache() < 0) {
die("invalid cache");
}
if (cached_attrs)
git_attr_set_direction(GIT_ATTR_INDEX);
doubledash = -1;
for (i = 0; doubledash < 0 && i < argc; i++) {
if (!strcmp(argv[i], "--"))
doubledash = i;
}
/* Process --all and/or attribute arguments: */
if (all_attrs) {
if (doubledash >= 1)
error_with_usage("Attributes and --all both specified");
cnt = 0;
filei = doubledash + 1;
} else if (doubledash == 0) {
error_with_usage("No attribute specified");
} else if (doubledash < 0) {
if (!argc)
error_with_usage("No attribute specified");
if (stdin_paths) {
/* Treat all arguments as attribute names. */
cnt = argc;
filei = argc;
} else {
/* Treat exactly one argument as an attribute name. */
cnt = 1;
filei = 1;
}
} else {
cnt = doubledash;
filei = doubledash + 1;
}
/* Check file argument(s): */
if (stdin_paths) {
if (filei < argc)
error_with_usage("Can't specify files with --stdin");
} else {
if (filei >= argc)
error_with_usage("No file specified");
}
check = attr_check_alloc();
if (!all_attrs) {
for (i = 0; i < cnt; i++) {
const struct git_attr *a = git_attr(argv[i]);
if (!a)
return error("%s: not a valid attribute name",
argv[i]);
attr_check_append(check, a);
}
}
if (stdin_paths)
check_attr_stdin_paths(prefix, check, all_attrs);
else {
for (i = filei; i < argc; i++)
check_attr(prefix, check, all_attrs, argv[i]);
maybe_flush_or_die(stdout, "attribute to stdout");
}
attr_check_free(check);
return 0;
}

193
third_party/git/builtin/check-ignore.c vendored Normal file
View file

@ -0,0 +1,193 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "quote.h"
#include "pathspec.h"
#include "parse-options.h"
#include "submodule.h"
static int quiet, verbose, stdin_paths, show_non_matching, no_index;
static const char * const check_ignore_usage[] = {
"git check-ignore [<options>] <pathname>...",
"git check-ignore [<options>] --stdin",
NULL
};
static int nul_term_line;
static const struct option check_ignore_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_GROUP(""),
OPT_BOOL(0, "stdin", &stdin_paths,
N_("read file names from stdin")),
OPT_BOOL('z', NULL, &nul_term_line,
N_("terminate input and output records by a NUL character")),
OPT_BOOL('n', "non-matching", &show_non_matching,
N_("show non-matching input paths")),
OPT_BOOL(0, "no-index", &no_index,
N_("ignore index when checking")),
OPT_END()
};
static void output_exclude(const char *path, struct exclude *exclude)
{
char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : "";
char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
if (!nul_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
if (exclude) {
quote_c_style(exclude->el->src, NULL, stdout, 0);
printf(":%d:%s%s%s\t",
exclude->srcpos,
bang, exclude->pattern, slash);
}
else {
printf("::\t");
}
quote_c_style(path, NULL, stdout, 0);
fputc('\n', stdout);
}
} else {
if (!verbose) {
printf("%s%c", path, '\0');
} else {
if (exclude)
printf("%s%c%d%c%s%s%s%c%s%c",
exclude->el->src, '\0',
exclude->srcpos, '\0',
bang, exclude->pattern, slash, '\0',
path, '\0');
else
printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
}
}
}
static int check_ignore(struct dir_struct *dir,
const char *prefix, int argc, const char **argv)
{
const char *full_path;
char *seen;
int num_ignored = 0, i;
struct exclude *exclude;
struct pathspec pathspec;
if (!argc) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
return 0;
}
/*
* check-ignore just needs paths. Magic beyond :/ is really
* irrelevant.
*/
parse_pathspec(&pathspec,
PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
PATHSPEC_SYMLINK_LEADING_PATH |
PATHSPEC_KEEP_ORDER,
prefix, argv);
die_path_inside_submodule(&the_index, &pathspec);
/*
* look for pathspecs matching entries in the index, since these
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
for (i = 0; i < pathspec.nr; i++) {
full_path = pathspec.items[i].match;
exclude = NULL;
if (!seen[i]) {
int dtype = DT_UNKNOWN;
exclude = last_exclude_matching(dir, &the_index,
full_path, &dtype);
}
if (!quiet && (exclude || show_non_matching))
output_exclude(pathspec.items[i].original, exclude);
if (exclude)
num_ignored++;
}
free(seen);
return num_ignored;
}
static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
char *pathspec[2] = { NULL, NULL };
strbuf_getline_fn getline_fn;
int num_ignored = 0;
getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
while (getline_fn(&buf, stdin) != EOF) {
if (!nul_term_line && buf.buf[0] == '"') {
strbuf_reset(&unquoted);
if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
pathspec[0] = buf.buf;
num_ignored += check_ignore(dir, prefix,
1, (const char **)pathspec);
maybe_flush_or_die(stdout, "check-ignore to stdout");
}
strbuf_release(&buf);
strbuf_release(&unquoted);
return num_ignored;
}
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
struct dir_struct dir;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, check_ignore_options,
check_ignore_usage, 0);
if (stdin_paths) {
if (argc > 0)
die(_("cannot specify pathnames with --stdin"));
} else {
if (nul_term_line)
die(_("-z only makes sense with --stdin"));
if (argc == 0)
die(_("no path specified"));
}
if (quiet) {
if (argc > 1)
die(_("--quiet is only valid with a single pathname"));
if (verbose)
die(_("cannot have both --quiet and --verbose"));
}
if (show_non_matching && !verbose)
die(_("--non-matching is only valid with --verbose"));
/* read_cache() is only necessary so we can watch out for submodules. */
if (!no_index && read_cache() < 0)
die(_("index file corrupt"));
memset(&dir, 0, sizeof(dir));
setup_standard_excludes(&dir);
if (stdin_paths) {
num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
num_ignored = check_ignore(&dir, prefix, argc, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}
clear_directory(&dir);
return !num_ignored;
}

67
third_party/git/builtin/check-mailmap.c vendored Normal file
View file

@ -0,0 +1,67 @@
#include "builtin.h"
#include "config.h"
#include "mailmap.h"
#include "parse-options.h"
#include "string-list.h"
static int use_stdin;
static const char * const check_mailmap_usage[] = {
N_("git check-mailmap [<options>] <contact>..."),
NULL
};
static const struct option check_mailmap_options[] = {
OPT_BOOL(0, "stdin", &use_stdin, N_("also read contacts from stdin")),
OPT_END()
};
static void check_mailmap(struct string_list *mailmap, const char *contact)
{
const char *name, *mail;
size_t namelen, maillen;
struct ident_split ident;
if (split_ident_line(&ident, contact, strlen(contact)))
die(_("unable to parse contact: %s"), contact);
name = ident.name_begin;
namelen = ident.name_end - ident.name_begin;
mail = ident.mail_begin;
maillen = ident.mail_end - ident.mail_begin;
map_user(mailmap, &mail, &maillen, &name, &namelen);
if (namelen)
printf("%.*s ", (int)namelen, name);
printf("<%.*s>\n", (int)maillen, mail);
}
int cmd_check_mailmap(int argc, const char **argv, const char *prefix)
{
int i;
struct string_list mailmap = STRING_LIST_INIT_NODUP;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, check_mailmap_options,
check_mailmap_usage, 0);
if (argc == 0 && !use_stdin)
die(_("no contacts specified"));
read_mailmap(&mailmap, NULL);
for (i = 0; i < argc; ++i)
check_mailmap(&mailmap, argv[i]);
maybe_flush_or_die(stdout, "stdout");
if (use_stdin) {
struct strbuf buf = STRBUF_INIT;
while (strbuf_getline_lf(&buf, stdin) != EOF) {
check_mailmap(&mailmap, buf.buf);
maybe_flush_or_die(stdout, "stdout");
}
strbuf_release(&buf);
}
clear_mailmap(&mailmap);
return 0;
}

View file

@ -0,0 +1,91 @@
/*
* GIT - The information manager from hell
*/
#include "cache.h"
#include "refs.h"
#include "builtin.h"
#include "strbuf.h"
static const char builtin_check_ref_format_usage[] =
"git check-ref-format [--normalize] [<options>] <refname>\n"
" or: git check-ref-format --branch <branchname-shorthand>";
/*
* Return a copy of refname but with leading slashes removed and runs
* of adjacent slashes replaced with single slashes.
*
* This function is similar to normalize_path_copy(), but stripped down
* to meet check_ref_format's simpler needs.
*/
static char *collapse_slashes(const char *refname)
{
char *ret = xmallocz(strlen(refname));
char ch;
char prev = '/';
char *cp = ret;
while ((ch = *refname++) != '\0') {
if (prev == '/' && ch == prev)
continue;
*cp++ = ch;
prev = ch;
}
*cp = '\0';
return ret;
}
static int check_ref_format_branch(const char *arg)
{
struct strbuf sb = STRBUF_INIT;
const char *name;
int nongit;
setup_git_directory_gently(&nongit);
if (strbuf_check_branch_ref(&sb, arg) ||
!skip_prefix(sb.buf, "refs/heads/", &name))
die("'%s' is not a valid branch name", arg);
printf("%s\n", name);
strbuf_release(&sb);
return 0;
}
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
{
int i;
int normalize = 0;
int flags = 0;
const char *refname;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_check_ref_format_usage);
if (argc == 3 && !strcmp(argv[1], "--branch"))
return check_ref_format_branch(argv[2]);
for (i = 1; i < argc && argv[i][0] == '-'; i++) {
if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print"))
normalize = 1;
else if (!strcmp(argv[i], "--allow-onelevel"))
flags |= REFNAME_ALLOW_ONELEVEL;
else if (!strcmp(argv[i], "--no-allow-onelevel"))
flags &= ~REFNAME_ALLOW_ONELEVEL;
else if (!strcmp(argv[i], "--refspec-pattern"))
flags |= REFNAME_REFSPEC_PATTERN;
else
usage(builtin_check_ref_format_usage);
}
if (! (i == argc - 1))
usage(builtin_check_ref_format_usage);
refname = argv[i];
if (normalize)
refname = collapse_slashes(refname);
if (check_refname_format(refname, flags))
return 1;
if (normalize)
printf("%s\n", refname);
return 0;
}

262
third_party/git/builtin/checkout-index.c vendored Normal file
View file

@ -0,0 +1,262 @@
/*
* Check-out files from the "current cache directory"
*
* Copyright (C) 2005 Linus Torvalds
*
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
#define CHECKOUT_ALL 4
static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
static struct checkout state = CHECKOUT_INIT;
static void write_tempfile_record(const char *name, const char *prefix)
{
int i;
if (CHECKOUT_ALL == checkout_stage) {
for (i = 1; i < 4; i++) {
if (i > 1)
putchar(' ');
if (topath[i][0])
fputs(topath[i], stdout);
else
putchar('.');
}
} else
fputs(topath[checkout_stage], stdout);
putchar('\t');
write_name_quoted_relative(name, prefix, stdout,
nul_term_line ? '\0' : '\n');
for (i = 0; i < 4; i++) {
topath[i][0] = 0;
}
}
static int checkout_file(const char *name, const char *prefix)
{
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
int did_checkout = 0;
int errs = 0;
if (pos < 0)
pos = -pos - 1;
while (pos < active_nr) {
struct cache_entry *ce = active_cache[pos];
if (ce_namelen(ce) != namelen ||
memcmp(ce->name, name, namelen))
break;
has_same_name = 1;
pos++;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
did_checkout = 1;
if (checkout_entry(ce, &state,
to_tempfile ? topath[ce_stage(ce)] : NULL,
NULL) < 0)
errs++;
}
if (did_checkout) {
if (to_tempfile)
write_tempfile_record(name, prefix);
return errs > 0 ? -1 : 0;
}
if (!state.quiet) {
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
fprintf(stderr, "is not in the cache");
else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d",
checkout_stage);
else
fprintf(stderr, "is unmerged");
fputc('\n', stderr);
}
return -1;
}
static void checkout_all(const char *prefix, int prefix_length)
{
int i, errs = 0;
struct cache_entry *last_ce = NULL;
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
if (prefix && *prefix &&
(ce_namelen(ce) <= prefix_length ||
memcmp(prefix, ce->name, prefix_length)))
continue;
if (last_ce && to_tempfile) {
if (ce_namelen(last_ce) != ce_namelen(ce)
|| memcmp(last_ce->name, ce->name, ce_namelen(ce)))
write_tempfile_record(last_ce->name, prefix);
}
if (checkout_entry(ce, &state,
to_tempfile ? topath[ce_stage(ce)] : NULL,
NULL) < 0)
errs++;
last_ce = ce;
}
if (last_ce && to_tempfile)
write_tempfile_record(last_ce->name, prefix);
if (errs)
/* we have already done our error reporting.
* exit with the same code as die().
*/
exit(128);
}
static const char * const builtin_checkout_index_usage[] = {
N_("git checkout-index [<options>] [--] [<file>...]"),
NULL
};
static int option_parse_stage(const struct option *opt,
const char *arg, int unset)
{
BUG_ON_OPT_NEG(unset);
if (!strcmp(arg, "all")) {
to_tempfile = 1;
checkout_stage = CHECKOUT_ALL;
} else {
int ch = arg[0];
if ('1' <= ch && ch <= '3')
checkout_stage = arg[0] - '0';
else
die(_("stage should be between 1 and 3 or all"));
}
return 0;
}
int cmd_checkout_index(int argc, const char **argv, const char *prefix)
{
int i;
struct lock_file lock_file = LOCK_INIT;
int all = 0;
int read_from_stdin = 0;
int prefix_length;
int force = 0, quiet = 0, not_new = 0;
int index_opt = 0;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
OPT_BOOL('n', "no-create", &not_new,
N_("don't checkout new files")),
OPT_BOOL('u', "index", &index_opt,
N_("update stat information in the index file")),
OPT_BOOL('z', NULL, &nul_term_line,
N_("paths are separated with NUL character")),
OPT_BOOL(0, "stdin", &read_from_stdin,
N_("read list of paths from the standard input")),
OPT_BOOL(0, "temp", &to_tempfile,
N_("write the content to temporary files")),
OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
N_("when creating files, prepend <string>")),
{ OPTION_CALLBACK, 0, "stage", NULL, "(1|2|3|all)",
N_("copy out the files from named stage"),
PARSE_OPT_NONEG, option_parse_stage },
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_checkout_index_usage,
builtin_checkout_index_options);
git_config(git_default_config, NULL);
prefix_length = prefix ? strlen(prefix) : 0;
if (read_cache() < 0) {
die("invalid cache");
}
argc = parse_options(argc, argv, prefix, builtin_checkout_index_options,
builtin_checkout_index_usage, 0);
state.istate = &the_index;
state.force = force;
state.quiet = quiet;
state.not_new = not_new;
if (!state.base_dir)
state.base_dir = "";
state.base_dir_len = strlen(state.base_dir);
/*
* when --prefix is specified we do not want to update cache.
*/
if (index_opt && !state.base_dir_len && !to_tempfile) {
state.refresh_cache = 1;
state.istate = &the_index;
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
}
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
char *p;
if (all)
die("git checkout-index: don't mix '--all' and explicit filenames");
if (read_from_stdin)
die("git checkout-index: don't mix '--stdin' and explicit filenames");
p = prefix_path(prefix, prefix_length, arg);
checkout_file(p, prefix);
free(p);
}
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
strbuf_getline_fn getline_fn;
if (all)
die("git checkout-index: don't mix '--all' and '--stdin'");
getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
while (getline_fn(&buf, stdin) != EOF) {
char *p;
if (!nul_term_line && buf.buf[0] == '"') {
strbuf_reset(&unquoted);
if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
p = prefix_path(prefix, prefix_length, buf.buf);
checkout_file(p, prefix);
free(p);
}
strbuf_release(&unquoted);
strbuf_release(&buf);
}
if (all)
checkout_all(prefix, prefix_length);
if (is_lock_file_locked(&lock_file) &&
write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("Unable to write new index file");
return 0;
}

1801
third_party/git/builtin/checkout.c vendored Normal file

File diff suppressed because it is too large Load diff

1051
third_party/git/builtin/clean.c vendored Normal file

File diff suppressed because it is too large Load diff

1282
third_party/git/builtin/clone.c vendored Normal file

File diff suppressed because it is too large Load diff

59
third_party/git/builtin/column.c vendored Normal file
View file

@ -0,0 +1,59 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "strbuf.h"
#include "parse-options.h"
#include "string-list.h"
#include "column.h"
static const char * const builtin_column_usage[] = {
N_("git column [<options>]"),
NULL
};
static unsigned int colopts;
static int column_config(const char *var, const char *value, void *cb)
{
return git_column_config(var, value, cb, &colopts);
}
int cmd_column(int argc, const char **argv, const char *prefix)
{
struct string_list list = STRING_LIST_INIT_DUP;
struct strbuf sb = STRBUF_INIT;
struct column_options copts;
const char *command = NULL, *real_command = NULL;
struct option options[] = {
OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")),
OPT_COLUMN(0, "mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")),
OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")),
OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")),
OPT_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")),
OPT_END()
};
/* This one is special and must be the first one */
if (argc > 1 && starts_with(argv[1], "--command=")) {
command = argv[1] + 10;
git_config(column_config, (void *)command);
} else
git_config(column_config, NULL);
memset(&copts, 0, sizeof(copts));
copts.padding = 1;
argc = parse_options(argc, argv, prefix, options, builtin_column_usage, 0);
if (argc)
usage_with_options(builtin_column_usage, options);
if (real_command || command) {
if (!real_command || !command || strcmp(real_command, command))
die(_("--command must be the first argument"));
}
finalize_colopts(&colopts, -1);
while (!strbuf_getline(&sb, stdin))
string_list_append(&list, sb.buf);
print_columns(&list, colopts, &copts);
return 0;
}

263
third_party/git/builtin/commit-graph.c vendored Normal file
View file

@ -0,0 +1,263 @@
#include "builtin.h"
#include "config.h"
#include "dir.h"
#include "lockfile.h"
#include "parse-options.h"
#include "repository.h"
#include "commit-graph.h"
#include "object-store.h"
static char const * const builtin_commit_graph_usage[] = {
N_("git commit-graph [--object-dir <objdir>]"),
N_("git commit-graph read [--object-dir <objdir>]"),
N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"),
N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] <split options>"),
NULL
};
static const char * const builtin_commit_graph_verify_usage[] = {
N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"),
NULL
};
static const char * const builtin_commit_graph_read_usage[] = {
N_("git commit-graph read [--object-dir <objdir>]"),
NULL
};
static const char * const builtin_commit_graph_write_usage[] = {
N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] <split options>"),
NULL
};
static struct opts_commit_graph {
const char *obj_dir;
int reachable;
int stdin_packs;
int stdin_commits;
int append;
int split;
int shallow;
} opts;
static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
char *graph_name;
int open_ok;
int fd;
struct stat st;
int flags = 0;
static struct option builtin_commit_graph_verify_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
OPT_BOOL(0, "shallow", &opts.shallow,
N_("if the commit-graph is split, only verify the tip file")),
OPT_END(),
};
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_verify_options,
builtin_commit_graph_verify_usage, 0);
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
if (opts.shallow)
flags |= COMMIT_GRAPH_VERIFY_SHALLOW;
graph_name = get_commit_graph_filename(opts.obj_dir);
open_ok = open_commit_graph(graph_name, &fd, &st);
if (!open_ok && errno != ENOENT)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
FREE_AND_NULL(graph_name);
if (open_ok)
graph = load_commit_graph_one_fd_st(fd, &st);
else
graph = read_commit_graph_one(the_repository, opts.obj_dir);
/* Return failure if open_ok predicted success */
if (!graph)
return !!open_ok;
UNLEAK(graph);
return verify_commit_graph(the_repository, graph, flags);
}
static int graph_read(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
char *graph_name;
int open_ok;
int fd;
struct stat st;
static struct option builtin_commit_graph_read_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
OPT_END(),
};
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_read_options,
builtin_commit_graph_read_usage, 0);
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
graph_name = get_commit_graph_filename(opts.obj_dir);
open_ok = open_commit_graph(graph_name, &fd, &st);
if (!open_ok)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
graph = load_commit_graph_one_fd_st(fd, &st);
if (!graph)
return 1;
FREE_AND_NULL(graph_name);
printf("header: %08x %d %d %d %d\n",
ntohl(*(uint32_t*)graph->data),
*(unsigned char*)(graph->data + 4),
*(unsigned char*)(graph->data + 5),
*(unsigned char*)(graph->data + 6),
*(unsigned char*)(graph->data + 7));
printf("num_commits: %u\n", graph->num_commits);
printf("chunks:");
if (graph->chunk_oid_fanout)
printf(" oid_fanout");
if (graph->chunk_oid_lookup)
printf(" oid_lookup");
if (graph->chunk_commit_data)
printf(" commit_metadata");
if (graph->chunk_extra_edges)
printf(" extra_edges");
printf("\n");
UNLEAK(graph);
return 0;
}
extern int read_replace_refs;
static struct split_commit_graph_opts split_opts;
static int graph_write(int argc, const char **argv)
{
struct string_list *pack_indexes = NULL;
struct string_list *commit_hex = NULL;
struct string_list lines;
int result = 0;
unsigned int flags = COMMIT_GRAPH_PROGRESS;
static struct option builtin_commit_graph_write_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
OPT_BOOL(0, "reachable", &opts.reachable,
N_("start walk at all refs")),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
N_("scan pack-indexes listed by stdin for commits")),
OPT_BOOL(0, "stdin-commits", &opts.stdin_commits,
N_("start walk at commits listed by stdin")),
OPT_BOOL(0, "append", &opts.append,
N_("include all commits already in the commit-graph file")),
OPT_BOOL(0, "split", &opts.split,
N_("allow writing an incremental commit-graph file")),
OPT_INTEGER(0, "max-commits", &split_opts.max_commits,
N_("maximum number of commits in a non-base split commit-graph")),
OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
N_("maximum ratio between two levels of a split commit-graph")),
OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time,
N_("maximum number of commits in a non-base split commit-graph")),
OPT_END(),
};
split_opts.size_multiple = 2;
split_opts.max_commits = 0;
split_opts.expire_time = 0;
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_write_options,
builtin_commit_graph_write_usage, 0);
if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
if (opts.append)
flags |= COMMIT_GRAPH_APPEND;
if (opts.split)
flags |= COMMIT_GRAPH_SPLIT;
read_replace_refs = 0;
if (opts.reachable) {
if (write_commit_graph_reachable(opts.obj_dir, flags, &split_opts))
return 1;
return 0;
}
string_list_init(&lines, 0);
if (opts.stdin_packs || opts.stdin_commits) {
struct strbuf buf = STRBUF_INIT;
while (strbuf_getline(&buf, stdin) != EOF)
string_list_append(&lines, strbuf_detach(&buf, NULL));
if (opts.stdin_packs)
pack_indexes = &lines;
if (opts.stdin_commits)
commit_hex = &lines;
UNLEAK(buf);
}
if (write_commit_graph(opts.obj_dir,
pack_indexes,
commit_hex,
flags,
&split_opts))
result = 1;
UNLEAK(lines);
return result;
}
int cmd_commit_graph(int argc, const char **argv, const char *prefix)
{
static struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
OPT_END(),
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_graph_usage,
builtin_commit_graph_options);
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_commit_graph_options,
builtin_commit_graph_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (argc > 0) {
if (!strcmp(argv[0], "read"))
return graph_read(argc, argv);
if (!strcmp(argv[0], "verify"))
return graph_verify(argc, argv);
if (!strcmp(argv[0], "write"))
return graph_write(argc, argv);
}
usage_with_options(builtin_commit_graph_usage,
builtin_commit_graph_options);
}

152
third_party/git/builtin/commit-tree.c vendored Normal file
View file

@ -0,0 +1,152 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
#include "config.h"
#include "object-store.h"
#include "repository.h"
#include "commit.h"
#include "tree.h"
#include "builtin.h"
#include "utf8.h"
#include "gpg-interface.h"
#include "parse-options.h"
static const char * const commit_tree_usage[] = {
N_("git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...] "
"[(-F <file>)...] <tree>"),
NULL
};
static const char *sign_commit;
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
struct object_id *oid = &parent->object.oid;
struct commit_list *parents;
for (parents = *parents_p; parents; parents = parents->next) {
if (parents->item == parent) {
error(_("duplicate parent %s ignored"), oid_to_hex(oid));
return;
}
parents_p = &parents->next;
}
commit_list_insert(parent, parents_p);
}
static int commit_tree_config(const char *var, const char *value, void *cb)
{
int status = git_gpg_config(var, value, NULL);
if (status)
return status;
return git_default_config(var, value, cb);
}
static int parse_parent_arg_callback(const struct option *opt,
const char *arg, int unset)
{
struct object_id oid;
struct commit_list **parents = opt->value;
BUG_ON_OPT_NEG_NOARG(unset, arg);
if (get_oid_commit(arg, &oid))
die(_("not a valid object name %s"), arg);
assert_oid_type(&oid, OBJ_COMMIT);
new_parent(lookup_commit(the_repository, &oid), parents);
return 0;
}
static int parse_message_arg_callback(const struct option *opt,
const char *arg, int unset)
{
struct strbuf *buf = opt->value;
BUG_ON_OPT_NEG_NOARG(unset, arg);
if (buf->len)
strbuf_addch(buf, '\n');
strbuf_addstr(buf, arg);
strbuf_complete_line(buf);
return 0;
}
static int parse_file_arg_callback(const struct option *opt,
const char *arg, int unset)
{
int fd;
struct strbuf *buf = opt->value;
BUG_ON_OPT_NEG_NOARG(unset, arg);
if (buf->len)
strbuf_addch(buf, '\n');
if (!strcmp(arg, "-"))
fd = 0;
else {
fd = open(arg, O_RDONLY);
if (fd < 0)
die_errno(_("git commit-tree: failed to open '%s'"), arg);
}
if (strbuf_read(buf, fd, 0) < 0)
die_errno(_("git commit-tree: failed to read '%s'"), arg);
if (fd && close(fd))
die_errno(_("git commit-tree: failed to close '%s'"), arg);
return 0;
}
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
static struct strbuf buffer = STRBUF_INIT;
struct commit_list *parents = NULL;
struct object_id tree_oid;
struct object_id commit_oid;
struct option options[] = {
{ OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"),
N_("id of a parent commit object"), PARSE_OPT_NONEG,
parse_parent_arg_callback },
{ OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"),
N_("commit message"), PARSE_OPT_NONEG,
parse_message_arg_callback },
{ OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"),
N_("read commit log message from file"), PARSE_OPT_NONEG,
parse_file_arg_callback },
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
OPT_END()
};
git_config(commit_tree_config, NULL);
if (argc < 2 || !strcmp(argv[1], "-h"))
usage_with_options(commit_tree_usage, options);
argc = parse_options(argc, argv, prefix, options, commit_tree_usage, 0);
if (argc != 1)
die(_("must give exactly one tree"));
if (get_oid_tree(argv[0], &tree_oid))
die(_("not a valid object name %s"), argv[0]);
if (!buffer.len) {
if (strbuf_read(&buffer, 0, 0) < 0)
die_errno(_("git commit-tree: failed to read"));
}
if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid,
NULL, sign_commit)) {
strbuf_release(&buffer);
return 1;
}
printf("%s\n", oid_to_hex(&commit_oid));
strbuf_release(&buffer);
return 0;
}

1716
third_party/git/builtin/commit.c vendored Normal file

File diff suppressed because it is too large Load diff

872
third_party/git/builtin/config.c vendored Normal file
View file

@ -0,0 +1,872 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "color.h"
#include "parse-options.h"
#include "urlmatch.h"
#include "quote.h"
#include "worktree.h"
static const char *const builtin_config_usage[] = {
N_("git config [<options>]"),
NULL
};
static char *key;
static regex_t *key_regexp;
static regex_t *regexp;
static int show_keys;
static int omit_values;
static int use_key_regexp;
static int do_all;
static int do_not_match;
static char delim = '=';
static char key_delim = ' ';
static char term = '\n';
static int use_global_config, use_system_config, use_local_config;
static int use_worktree_config;
static struct git_config_source given_config_source;
static int actions, type;
static char *default_value;
static int end_null;
static int respect_includes_opt = -1;
static struct config_options config_options;
static int show_origin;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
#define ACTION_GET_REGEXP (1<<2)
#define ACTION_REPLACE_ALL (1<<3)
#define ACTION_ADD (1<<4)
#define ACTION_UNSET (1<<5)
#define ACTION_UNSET_ALL (1<<6)
#define ACTION_RENAME_SECTION (1<<7)
#define ACTION_REMOVE_SECTION (1<<8)
#define ACTION_LIST (1<<9)
#define ACTION_EDIT (1<<10)
#define ACTION_SET (1<<11)
#define ACTION_SET_ALL (1<<12)
#define ACTION_GET_COLOR (1<<13)
#define ACTION_GET_COLORBOOL (1<<14)
#define ACTION_GET_URLMATCH (1<<15)
/*
* The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
* one line of output and which should therefore be paged.
*/
#define PAGING_ACTIONS (ACTION_LIST | ACTION_GET_ALL | \
ACTION_GET_REGEXP | ACTION_GET_URLMATCH)
#define TYPE_BOOL 1
#define TYPE_INT 2
#define TYPE_BOOL_OR_INT 3
#define TYPE_PATH 4
#define TYPE_EXPIRY_DATE 5
#define TYPE_COLOR 6
#define OPT_CALLBACK_VALUE(s, l, v, h, i) \
{ OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \
PARSE_OPT_NONEG, option_parse_type, (i) }
static NORETURN void usage_builtin_config(void);
static int option_parse_type(const struct option *opt, const char *arg,
int unset)
{
int new_type, *to_type;
if (unset) {
*((int *) opt->value) = 0;
return 0;
}
/*
* To support '--<type>' style flags, begin with new_type equal to
* opt->defval.
*/
new_type = opt->defval;
if (!new_type) {
if (!strcmp(arg, "bool"))
new_type = TYPE_BOOL;
else if (!strcmp(arg, "int"))
new_type = TYPE_INT;
else if (!strcmp(arg, "bool-or-int"))
new_type = TYPE_BOOL_OR_INT;
else if (!strcmp(arg, "path"))
new_type = TYPE_PATH;
else if (!strcmp(arg, "expiry-date"))
new_type = TYPE_EXPIRY_DATE;
else if (!strcmp(arg, "color"))
new_type = TYPE_COLOR;
else
die(_("unrecognized --type argument, %s"), arg);
}
to_type = opt->value;
if (*to_type && *to_type != new_type) {
/*
* Complain when there is a new type not equal to the old type.
* This allows for combinations like '--int --type=int' and
* '--type=int --type=int', but disallows ones like '--type=bool
* --int' and '--type=bool
* --type=int'.
*/
error(_("only one type at a time"));
usage_builtin_config();
}
*to_type = new_type;
return 0;
}
static struct option builtin_config_options[] = {
OPT_GROUP(N_("Config file location")),
OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
OPT_GROUP(N_("Action")),
OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP),
OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL),
OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET),
OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL),
OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
OPT_GROUP(N_("Type")),
OPT_CALLBACK('t', "type", &type, "", N_("value is given this type"), option_parse_type),
OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT),
OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
OPT_GROUP(N_("Other")),
OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
OPT_END(),
};
static NORETURN void usage_builtin_config(void)
{
usage_with_options(builtin_config_usage, builtin_config_options);
}
static void check_argc(int argc, int min, int max)
{
if (argc >= min && argc <= max)
return;
if (min == max)
error(_("wrong number of arguments, should be %d"), min);
else
error(_("wrong number of arguments, should be from %d to %d"),
min, max);
usage_builtin_config();
}
static void show_config_origin(struct strbuf *buf)
{
const char term = end_null ? '\0' : '\t';
strbuf_addstr(buf, current_config_origin_type());
strbuf_addch(buf, ':');
if (end_null)
strbuf_addstr(buf, current_config_name());
else
quote_c_style(current_config_name(), buf, NULL, 0);
strbuf_addch(buf, term);
}
static int show_all_config(const char *key_, const char *value_, void *cb)
{
if (show_origin) {
struct strbuf buf = STRBUF_INIT;
show_config_origin(&buf);
/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
}
if (!omit_values && value_)
printf("%s%c%s%c", key_, delim, value_, term);
else
printf("%s%c", key_, term);
return 0;
}
struct strbuf_list {
struct strbuf *items;
int nr;
int alloc;
};
static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
if (show_origin)
show_config_origin(buf);
if (show_keys)
strbuf_addstr(buf, key_);
if (!omit_values) {
if (show_keys)
strbuf_addch(buf, key_delim);
if (type == TYPE_INT)
strbuf_addf(buf, "%"PRId64,
git_config_int64(key_, value_ ? value_ : ""));
else if (type == TYPE_BOOL)
strbuf_addstr(buf, git_config_bool(key_, value_) ?
"true" : "false");
else if (type == TYPE_BOOL_OR_INT) {
int is_bool, v;
v = git_config_bool_or_int(key_, value_, &is_bool);
if (is_bool)
strbuf_addstr(buf, v ? "true" : "false");
else
strbuf_addf(buf, "%d", v);
} else if (type == TYPE_PATH) {
const char *v;
if (git_config_pathname(&v, key_, value_) < 0)
return -1;
strbuf_addstr(buf, v);
free((char *)v);
} else if (type == TYPE_EXPIRY_DATE) {
timestamp_t t;
if (git_config_expiry_date(&t, key_, value_) < 0)
return -1;
strbuf_addf(buf, "%"PRItime, t);
} else if (type == TYPE_COLOR) {
char v[COLOR_MAXLEN];
if (git_config_color(v, key_, value_) < 0)
return -1;
strbuf_addstr(buf, v);
} else if (value_) {
strbuf_addstr(buf, value_);
} else {
/* Just show the key name; back out delimiter */
if (show_keys)
strbuf_setlen(buf, buf->len - 1);
}
}
strbuf_addch(buf, term);
return 0;
}
static int collect_config(const char *key_, const char *value_, void *cb)
{
struct strbuf_list *values = cb;
if (!use_key_regexp && strcmp(key_, key))
return 0;
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0;
if (regexp != NULL &&
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
strbuf_init(&values->items[values->nr], 0);
return format_config(&values->items[values->nr++], key_, value_);
}
static int get_value(const char *key_, const char *regex_)
{
int ret = CONFIG_GENERIC_ERROR;
struct strbuf_list values = {NULL};
int i;
if (use_key_regexp) {
char *tl;
/*
* NEEDSWORK: this naive pattern lowercasing obviously does not
* work for more complex patterns like "^[^.]*Foo.*bar".
* Perhaps we should deprecate this altogether someday.
*/
key = xstrdup(key_);
for (tl = key + strlen(key) - 1;
tl >= key && *tl != '.';
tl--)
*tl = tolower(*tl);
for (tl = key; *tl && *tl != '.'; tl++)
*tl = tolower(*tl);
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
error(_("invalid key pattern: %s"), key_);
FREE_AND_NULL(key_regexp);
ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
} else {
if (git_config_parse_key(key_, &key, NULL)) {
ret = CONFIG_INVALID_KEY;
goto free_strings;
}
}
if (regex_) {
if (regex_[0] == '!') {
do_not_match = 1;
regex_++;
}
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
error(_("invalid pattern: %s"), regex_);
FREE_AND_NULL(regexp);
ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
}
config_with_options(collect_config, &values,
&given_config_source, &config_options);
if (!values.nr && default_value) {
struct strbuf *item;
ALLOC_GROW(values.items, values.nr + 1, values.alloc);
item = &values.items[values.nr++];
strbuf_init(item, 0);
if (format_config(item, key_, default_value) < 0)
die(_("failed to format default config value: %s"),
default_value);
}
ret = !values.nr;
for (i = 0; i < values.nr; i++) {
struct strbuf *buf = values.items + i;
if (do_all || i == values.nr - 1)
fwrite(buf->buf, 1, buf->len, stdout);
strbuf_release(buf);
}
free(values.items);
free_strings:
free(key);
if (key_regexp) {
regfree(key_regexp);
free(key_regexp);
}
if (regexp) {
regfree(regexp);
free(regexp);
}
return ret;
}
static char *normalize_value(const char *key, const char *value)
{
if (!value)
return NULL;
if (type == 0 || type == TYPE_PATH || type == TYPE_EXPIRY_DATE)
/*
* We don't do normalization for TYPE_PATH here: If
* the path is like ~/foobar/, we prefer to store
* "~/foobar/" in the config file, and to expand the ~
* when retrieving the value.
* Also don't do normalization for expiry dates.
*/
return xstrdup(value);
if (type == TYPE_INT)
return xstrfmt("%"PRId64, git_config_int64(key, value));
if (type == TYPE_BOOL)
return xstrdup(git_config_bool(key, value) ? "true" : "false");
if (type == TYPE_BOOL_OR_INT) {
int is_bool, v;
v = git_config_bool_or_int(key, value, &is_bool);
if (!is_bool)
return xstrfmt("%d", v);
else
return xstrdup(v ? "true" : "false");
}
if (type == TYPE_COLOR) {
char v[COLOR_MAXLEN];
if (git_config_color(v, key, value))
die(_("cannot parse color '%s'"), value);
/*
* The contents of `v` now contain an ANSI escape
* sequence, not suitable for including within a
* configuration file. Treat the above as a
* "sanity-check", and return the given value, which we
* know is representable as valid color code.
*/
return xstrdup(value);
}
BUG("cannot normalize type %d", type);
}
static int get_color_found;
static const char *get_color_slot;
static const char *get_colorbool_slot;
static char parsed_color[COLOR_MAXLEN];
static int git_get_color_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, get_color_slot)) {
if (!value)
config_error_nonbool(var);
if (color_parse(value, parsed_color) < 0)
return -1;
get_color_found = 1;
}
return 0;
}
static void get_color(const char *var, const char *def_color)
{
get_color_slot = var;
get_color_found = 0;
parsed_color[0] = '\0';
config_with_options(git_get_color_config, NULL,
&given_config_source, &config_options);
if (!get_color_found && def_color) {
if (color_parse(def_color, parsed_color) < 0)
die(_("unable to parse default color value"));
}
fputs(parsed_color, stdout);
}
static int get_colorbool_found;
static int get_diff_color_found;
static int get_color_ui_found;
static int git_get_colorbool_config(const char *var, const char *value,
void *cb)
{
if (!strcmp(var, get_colorbool_slot))
get_colorbool_found = git_config_colorbool(var, value);
else if (!strcmp(var, "diff.color"))
get_diff_color_found = git_config_colorbool(var, value);
else if (!strcmp(var, "color.ui"))
get_color_ui_found = git_config_colorbool(var, value);
return 0;
}
static int get_colorbool(const char *var, int print)
{
get_colorbool_slot = var;
get_colorbool_found = -1;
get_diff_color_found = -1;
get_color_ui_found = -1;
config_with_options(git_get_colorbool_config, NULL,
&given_config_source, &config_options);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
get_colorbool_found = get_diff_color_found;
if (get_colorbool_found < 0)
get_colorbool_found = get_color_ui_found;
}
if (get_colorbool_found < 0)
/* default value if none found in config */
get_colorbool_found = GIT_COLOR_AUTO;
get_colorbool_found = want_color(get_colorbool_found);
if (print) {
printf("%s\n", get_colorbool_found ? "true" : "false");
return 0;
} else
return get_colorbool_found ? 0 : 1;
}
static void check_write(void)
{
if (!given_config_source.file && !startup_info->have_repository)
die(_("not in a git directory"));
if (given_config_source.use_stdin)
die(_("writing to stdin is not supported"));
if (given_config_source.blob)
die(_("writing config blobs is not supported"));
}
struct urlmatch_current_candidate_value {
char value_is_null;
struct strbuf value;
};
static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
{
struct string_list *values = cb;
struct string_list_item *item = string_list_insert(values, var);
struct urlmatch_current_candidate_value *matched = item->util;
if (!matched) {
matched = xmalloc(sizeof(*matched));
strbuf_init(&matched->value, 0);
item->util = matched;
} else {
strbuf_reset(&matched->value);
}
if (value) {
strbuf_addstr(&matched->value, value);
matched->value_is_null = 0;
} else {
matched->value_is_null = 1;
}
return 0;
}
static int get_urlmatch(const char *var, const char *url)
{
int ret;
char *section_tail;
struct string_list_item *item;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
struct string_list values = STRING_LIST_INIT_DUP;
config.collect_fn = urlmatch_collect_fn;
config.cascade_fn = NULL;
config.cb = &values;
if (!url_normalize(url, &config.url))
die("%s", config.url.err);
config.section = xstrdup_tolower(var);
section_tail = strchr(config.section, '.');
if (section_tail) {
*section_tail = '\0';
config.key = section_tail + 1;
show_keys = 0;
} else {
config.key = NULL;
show_keys = 1;
}
config_with_options(urlmatch_config_entry, &config,
&given_config_source, &config_options);
ret = !values.nr;
for_each_string_list_item(item, &values) {
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf buf = STRBUF_INIT;
format_config(&buf, item->string,
matched->value_is_null ? NULL : matched->value.buf);
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
strbuf_release(&matched->value);
}
string_list_clear(&config.vars, 1);
string_list_clear(&values, 1);
free(config.url.url);
free((void *)config.section);
return ret;
}
static char *default_user_config(void)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf,
_("# This is Git's per-user configuration file.\n"
"[user]\n"
"# Please adapt and uncomment the following lines:\n"
"# name = %s\n"
"# email = %s\n"),
ident_default_name(),
ident_default_email());
return strbuf_detach(&buf, NULL);
}
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
char *value;
given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (use_global_config + use_system_config + use_local_config +
use_worktree_config +
!!given_config_source.file + !!given_config_source.blob > 1) {
error(_("only one config file at a time"));
usage_builtin_config();
}
if (use_local_config && nongit)
die(_("--local can only be used inside a git repository"));
if (given_config_source.blob && nongit)
die(_("--blob can only be used inside a git repository"));
if (given_config_source.file &&
!strcmp(given_config_source.file, "-")) {
given_config_source.file = NULL;
given_config_source.use_stdin = 1;
}
if (use_global_config) {
char *user_config = expand_user_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
if (!user_config)
/*
* It is unknown if HOME/.gitconfig exists, so
* we do not know if we should write to XDG
* location; error out even if XDG_CONFIG_HOME
* is set and points at a sane location.
*/
die(_("$HOME not set"));
if (access_or_warn(user_config, R_OK, 0) &&
xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
given_config_source.file = xdg_config;
free(user_config);
} else {
given_config_source.file = user_config;
free(xdg_config);
}
}
else if (use_system_config)
given_config_source.file = git_etc_gitconfig();
else if (use_local_config)
given_config_source.file = git_pathdup("config");
else if (use_worktree_config) {
struct worktree **worktrees = get_worktrees(0);
if (repository_format_worktree_config)
given_config_source.file = git_pathdup("config.worktree");
else if (worktrees[0] && worktrees[1])
die(_("--worktree cannot be used with multiple "
"working trees unless the config\n"
"extension worktreeConfig is enabled. "
"Please read \"CONFIGURATION FILE\"\n"
"section in \"git help worktree\" for details"));
else
given_config_source.file = git_pathdup("config");
free_worktrees(worktrees);
} else if (given_config_source.file) {
if (!is_absolute_path(given_config_source.file) && prefix)
given_config_source.file =
prefix_filename(prefix, given_config_source.file);
}
if (respect_includes_opt == -1)
config_options.respect_includes = !given_config_source.file;
else
config_options.respect_includes = respect_includes_opt;
if (!nongit) {
config_options.commondir = get_git_common_dir();
config_options.git_dir = get_git_dir();
}
if (end_null) {
term = '\0';
delim = '\n';
key_delim = '\n';
}
if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) {
error(_("--get-color and variable type are incoherent"));
usage_builtin_config();
}
if (HAS_MULTI_BITS(actions)) {
error(_("only one action at a time"));
usage_builtin_config();
}
if (actions == 0)
switch (argc) {
case 1: actions = ACTION_GET; break;
case 2: actions = ACTION_SET; break;
case 3: actions = ACTION_SET_ALL; break;
default:
usage_builtin_config();
}
if (omit_values &&
!(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) {
error(_("--name-only is only applicable to --list or --get-regexp"));
usage_builtin_config();
}
if (show_origin && !(actions &
(ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) {
error(_("--show-origin is only applicable to --get, --get-all, "
"--get-regexp, and --list"));
usage_builtin_config();
}
if (default_value && !(actions & ACTION_GET)) {
error(_("--default is only applicable to --get"));
usage_builtin_config();
}
if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1);
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
if (config_with_options(show_all_config, NULL,
&given_config_source,
&config_options) < 0) {
if (given_config_source.file)
die_errno(_("unable to read config file '%s'"),
given_config_source.file);
else
die(_("error processing config file(s)"));
}
}
else if (actions == ACTION_EDIT) {
char *config_file;
check_argc(argc, 0, 0);
if (!given_config_source.file && nongit)
die(_("not in a git directory"));
if (given_config_source.use_stdin)
die(_("editing stdin is not supported"));
if (given_config_source.blob)
die(_("editing blobs is not supported"));
git_config(git_default_config, NULL);
config_file = given_config_source.file ?
xstrdup(given_config_source.file) :
git_pathdup("config");
if (use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
char *content = default_user_config();
write_str_in_full(fd, content);
free(content);
close(fd);
}
else if (errno != EEXIST)
die_errno(_("cannot create configuration file %s"), config_file);
}
launch_editor(config_file, NULL, NULL);
free(config_file);
}
else if (actions == ACTION_SET) {
int ret;
check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);
if (ret == CONFIG_NOTHING_SET)
error(_("cannot overwrite multiple values with a single value\n"
" Use a regexp, --add or --replace-all to change %s."), argv[0]);
return ret;
}
else if (actions == ACTION_SET_ALL) {
check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value,
CONFIG_REGEX_NONE, 0);
}
else if (actions == ACTION_REPLACE_ALL) {
check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2], 1);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
else if (actions == ACTION_GET_ALL) {
do_all = 1;
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
else if (actions == ACTION_GET_REGEXP) {
show_keys = 1;
use_key_regexp = 1;
do_all = 1;
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
else if (actions == ACTION_GET_URLMATCH) {
check_argc(argc, 2, 2);
return get_urlmatch(argv[0], argv[1]);
}
else if (actions == ACTION_UNSET) {
check_write();
check_argc(argc, 1, 2);
if (argc == 2)
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1], 0);
else
return git_config_set_in_file_gently(given_config_source.file,
argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
check_write();
check_argc(argc, 1, 2);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
check_write();
check_argc(argc, 2, 2);
ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], argv[1]);
if (ret < 0)
return ret;
if (ret == 0)
die(_("no such section: %s"), argv[0]);
}
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
check_write();
check_argc(argc, 1, 1);
ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], NULL);
if (ret < 0)
return ret;
if (ret == 0)
die(_("no such section: %s"), argv[0]);
}
else if (actions == ACTION_GET_COLOR) {
check_argc(argc, 1, 2);
get_color(argv[0], argv[1]);
}
else if (actions == ACTION_GET_COLORBOOL) {
check_argc(argc, 1, 2);
if (argc == 2)
color_stdout_is_tty = git_config_bool("command line", argv[1]);
return get_colorbool(argv[0], argc == 2);
}
return 0;
}

172
third_party/git/builtin/count-objects.c vendored Normal file
View file

@ -0,0 +1,172 @@
/*
* Builtin "git count-objects".
*
* Copyright (c) 2006 Junio C Hamano
*/
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "repository.h"
#include "builtin.h"
#include "parse-options.h"
#include "quote.h"
#include "packfile.h"
#include "object-store.h"
static unsigned long garbage;
static off_t size_garbage;
static int verbose;
static unsigned long loose, packed, packed_loose;
static off_t loose_size;
static const char *bits_to_msg(unsigned seen_bits)
{
switch (seen_bits) {
case 0:
return "no corresponding .idx or .pack";
case PACKDIR_FILE_GARBAGE:
return "garbage found";
case PACKDIR_FILE_PACK:
return "no corresponding .idx";
case PACKDIR_FILE_IDX:
return "no corresponding .pack";
case PACKDIR_FILE_PACK|PACKDIR_FILE_IDX:
default:
return NULL;
}
}
static void real_report_garbage(unsigned seen_bits, const char *path)
{
struct stat st;
const char *desc = bits_to_msg(seen_bits);
if (!desc)
return;
if (!stat(path, &st))
size_garbage += st.st_size;
warning("%s: %s", desc, path);
garbage++;
}
static void loose_garbage(const char *path)
{
if (verbose)
report_garbage(PACKDIR_FILE_GARBAGE, path);
}
static int count_loose(const struct object_id *oid, const char *path, void *data)
{
struct stat st;
if (lstat(path, &st) || !S_ISREG(st.st_mode))
loose_garbage(path);
else {
loose_size += on_disk_bytes(st);
loose++;
if (verbose && has_object_pack(oid))
packed_loose++;
}
return 0;
}
static int count_cruft(const char *basename, const char *path, void *data)
{
loose_garbage(path);
return 0;
}
static int print_alternate(struct object_directory *odb, void *data)
{
printf("alternate: ");
quote_c_style(odb->path, NULL, stdout, 0);
putchar('\n');
return 0;
}
static char const * const count_objects_usage[] = {
N_("git count-objects [-v] [-H | --human-readable]"),
NULL
};
int cmd_count_objects(int argc, const char **argv, const char *prefix)
{
int human_readable = 0;
struct option opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL('H', "human-readable", &human_readable,
N_("print sizes in human readable format")),
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
/* we do not take arguments other than flags for now */
if (argc)
usage_with_options(count_objects_usage, opts);
if (verbose) {
report_garbage = real_report_garbage;
report_linked_checkout_garbage();
}
for_each_loose_file_in_objdir(get_object_directory(),
count_loose, count_cruft, NULL, NULL);
if (verbose) {
struct packed_git *p;
unsigned long num_pack = 0;
off_t size_pack = 0;
struct strbuf loose_buf = STRBUF_INIT;
struct strbuf pack_buf = STRBUF_INIT;
struct strbuf garbage_buf = STRBUF_INIT;
for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (open_pack_index(p))
continue;
packed += p->num_objects;
size_pack += p->pack_size + p->index_size;
num_pack++;
}
if (human_readable) {
strbuf_humanise_bytes(&loose_buf, loose_size);
strbuf_humanise_bytes(&pack_buf, size_pack);
strbuf_humanise_bytes(&garbage_buf, size_garbage);
} else {
strbuf_addf(&loose_buf, "%lu",
(unsigned long)(loose_size / 1024));
strbuf_addf(&pack_buf, "%lu",
(unsigned long)(size_pack / 1024));
strbuf_addf(&garbage_buf, "%lu",
(unsigned long)(size_garbage / 1024));
}
printf("count: %lu\n", loose);
printf("size: %s\n", loose_buf.buf);
printf("in-pack: %lu\n", packed);
printf("packs: %lu\n", num_pack);
printf("size-pack: %s\n", pack_buf.buf);
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
printf("size-garbage: %s\n", garbage_buf.buf);
foreach_alt_odb(print_alternate, NULL);
strbuf_release(&loose_buf);
strbuf_release(&pack_buf);
strbuf_release(&garbage_buf);
} else {
struct strbuf buf = STRBUF_INIT;
if (human_readable)
strbuf_humanise_bytes(&buf, loose_size);
else
strbuf_addf(&buf, "%lu kilobytes",
(unsigned long)(loose_size / 1024));
printf("%lu objects, %s\n", loose, buf.buf);
strbuf_release(&buf);
}
return 0;
}

31
third_party/git/builtin/credential.c vendored Normal file
View file

@ -0,0 +1,31 @@
#include "git-compat-util.h"
#include "credential.h"
#include "builtin.h"
static const char usage_msg[] =
"git credential [fill|approve|reject]";
int cmd_credential(int argc, const char **argv, const char *prefix)
{
const char *op;
struct credential c = CREDENTIAL_INIT;
if (argc != 2 || !strcmp(argv[1], "-h"))
usage(usage_msg);
op = argv[1];
if (credential_read(&c, stdin) < 0)
die("unable to read credential from stdin");
if (!strcmp(op, "fill")) {
credential_fill(&c);
credential_write(&c, stdout);
} else if (!strcmp(op, "approve")) {
credential_approve(&c);
} else if (!strcmp(op, "reject")) {
credential_reject(&c);
} else {
usage(usage_msg);
}
return 0;
}

662
third_party/git/builtin/describe.c vendored Normal file
View file

@ -0,0 +1,662 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
#include "commit.h"
#include "tag.h"
#include "blob.h"
#include "refs.h"
#include "builtin.h"
#include "exec-cmd.h"
#include "parse-options.h"
#include "revision.h"
#include "diff.h"
#include "hashmap.h"
#include "argv-array.h"
#include "run-command.h"
#include "object-store.h"
#include "revision.h"
#include "list-objects.h"
#include "commit-slab.h"
#define MAX_TAGS (FLAG_BITS - 1)
define_commit_slab(commit_names, struct commit_name *);
static const char * const describe_usage[] = {
N_("git describe [<options>] [<commit-ish>...]"),
N_("git describe [<options>] --dirty"),
NULL
};
static int debug; /* Display lots of verbose info */
static int all; /* Any valid ref can be used */
static int tags; /* Allow lightweight tags */
static int longformat;
static int first_parent;
static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
static struct hashmap names;
static int have_util;
static struct string_list patterns = STRING_LIST_INIT_NODUP;
static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
static int always;
static const char *suffix, *dirty, *broken;
static struct commit_names commit_names;
/* diff-index command arguments to check if working tree is dirty. */
static const char *diff_index_args[] = {
"diff-index", "--quiet", "HEAD", "--", NULL
};
struct commit_name {
struct hashmap_entry entry;
struct object_id peeled;
struct tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
struct object_id oid;
char *path;
};
static const char *prio_names[] = {
N_("head"), N_("lightweight"), N_("annotated"),
};
static int commit_name_neq(const void *unused_cmp_data,
const void *entry,
const void *entry_or_key,
const void *peeled)
{
const struct commit_name *cn1 = entry;
const struct commit_name *cn2 = entry_or_key;
return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled);
}
static inline struct commit_name *find_commit_name(const struct object_id *peeled)
{
return hashmap_get_from_hash(&names, oidhash(peeled), peeled);
}
static int replace_name(struct commit_name *e,
int prio,
const struct object_id *oid,
struct tag **tag)
{
if (!e || e->prio < prio)
return 1;
if (e->prio == 2 && prio == 2) {
/* Multiple annotated tags point to the same commit.
* Select one to keep based upon their tagger date.
*/
struct tag *t;
if (!e->tag) {
t = lookup_tag(the_repository, &e->oid);
if (!t || parse_tag(t))
return 1;
e->tag = t;
}
t = lookup_tag(the_repository, oid);
if (!t || parse_tag(t))
return 0;
*tag = t;
if (e->tag->date < t->date)
return 1;
}
return 0;
}
static void add_to_known_names(const char *path,
const struct object_id *peeled,
int prio,
const struct object_id *oid)
{
struct commit_name *e = find_commit_name(peeled);
struct tag *tag = NULL;
if (replace_name(e, prio, oid, &tag)) {
if (!e) {
e = xmalloc(sizeof(struct commit_name));
oidcpy(&e->peeled, peeled);
hashmap_entry_init(e, oidhash(peeled));
hashmap_add(&names, e);
e->path = NULL;
}
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
oidcpy(&e->oid, oid);
free(e->path);
e->path = xstrdup(path);
}
}
static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
int is_tag = 0;
struct object_id peeled;
int is_annotated, prio;
const char *path_to_match = NULL;
if (skip_prefix(path, "refs/tags/", &path_to_match)) {
is_tag = 1;
} else if (all) {
if ((exclude_patterns.nr || patterns.nr) &&
!skip_prefix(path, "refs/heads/", &path_to_match) &&
!skip_prefix(path, "refs/remotes/", &path_to_match)) {
/* Only accept reference of known type if there are match/exclude patterns */
return 0;
}
} else {
/* Reject anything outside refs/tags/ unless --all */
return 0;
}
/*
* If we're given exclude patterns, first exclude any tag which match
* any of the exclude pattern.
*/
if (exclude_patterns.nr) {
struct string_list_item *item;
for_each_string_list_item(item, &exclude_patterns) {
if (!wildmatch(item->string, path_to_match, 0))
return 0;
}
}
/*
* If we're given patterns, accept only tags which match at least one
* pattern.
*/
if (patterns.nr) {
int found = 0;
struct string_list_item *item;
for_each_string_list_item(item, &patterns) {
if (!wildmatch(item->string, path_to_match, 0)) {
found = 1;
break;
}
}
if (!found)
return 0;
}
/* Is it annotated? */
if (!peel_ref(path, &peeled)) {
is_annotated = !oideq(oid, &peeled);
} else {
oidcpy(&peeled, oid);
is_annotated = 0;
}
/*
* By default, we only use annotated tags, but with --tags
* we fall back to lightweight ones (even without --tags,
* we still remember lightweight ones, only to give hints
* in an error message). --all allows any refs to be used.
*/
if (is_annotated)
prio = 2;
else if (is_tag)
prio = 1;
else
prio = 0;
add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);
return 0;
}
struct possible_tag {
struct commit_name *name;
int depth;
int found_order;
unsigned flag_within;
};
static int compare_pt(const void *a_, const void *b_)
{
struct possible_tag *a = (struct possible_tag *)a_;
struct possible_tag *b = (struct possible_tag *)b_;
if (a->depth != b->depth)
return a->depth - b->depth;
if (a->found_order != b->found_order)
return a->found_order - b->found_order;
return 0;
}
static unsigned long finish_depth_computation(
struct commit_list **list,
struct possible_tag *best)
{
unsigned long seen_commits = 0;
while (*list) {
struct commit *c = pop_commit(list);
struct commit_list *parents = c->parents;
seen_commits++;
if (c->object.flags & best->flag_within) {
struct commit_list *a = *list;
while (a) {
struct commit *i = a->item;
if (!(i->object.flags & best->flag_within))
break;
a = a->next;
}
if (!a)
break;
} else
best->depth++;
while (parents) {
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
commit_list_insert_by_date(p, list);
p->object.flags |= c->object.flags;
parents = parents->next;
}
}
return seen_commits;
}
static void append_name(struct commit_name *n, struct strbuf *dst)
{
if (n->prio == 2 && !n->tag) {
n->tag = lookup_tag(the_repository, &n->oid);
if (!n->tag || parse_tag(n->tag))
die(_("annotated tag %s not available"), n->path);
}
if (n->tag && !n->name_checked) {
if (!n->tag->tag)
die(_("annotated tag %s has no embedded name"), n->path);
if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
n->name_checked = 1;
}
if (n->tag) {
if (all)
strbuf_addstr(dst, "tags/");
strbuf_addstr(dst, n->tag->tag);
} else {
strbuf_addstr(dst, n->path);
}
}
static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst)
{
strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid, abbrev));
}
static void describe_commit(struct object_id *oid, struct strbuf *dst)
{
struct commit *cmit, *gave_up_on = NULL;
struct commit_list *list;
struct commit_name *n;
struct possible_tag all_matches[MAX_TAGS];
unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
unsigned long seen_commits = 0;
unsigned int unannotated_cnt = 0;
cmit = lookup_commit_reference(the_repository, oid);
n = find_commit_name(&cmit->object.oid);
if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
*/
append_name(n, dst);
if (longformat)
append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
return;
}
if (!max_candidates)
die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid));
if (debug)
fprintf(stderr, _("No exact match on refs or tags, searching to describe\n"));
if (!have_util) {
struct hashmap_iter iter;
struct commit *c;
struct commit_name *n;
init_commit_names(&commit_names);
n = hashmap_iter_first(&names, &iter);
for (; n; n = hashmap_iter_next(&iter)) {
c = lookup_commit_reference_gently(the_repository,
&n->peeled, 1);
if (c)
*commit_names_at(&commit_names, c) = n;
}
have_util = 1;
}
list = NULL;
cmit->object.flags = SEEN;
commit_list_insert(cmit, &list);
while (list) {
struct commit *c = pop_commit(&list);
struct commit_list *parents = c->parents;
struct commit_name **slot;
seen_commits++;
slot = commit_names_peek(&commit_names, c);
n = slot ? *slot : NULL;
if (n) {
if (!tags && !all && n->prio < 2) {
unannotated_cnt++;
} else if (match_cnt < max_candidates) {
struct possible_tag *t = &all_matches[match_cnt++];
t->name = n;
t->depth = seen_commits - 1;
t->flag_within = 1u << match_cnt;
t->found_order = match_cnt;
c->object.flags |= t->flag_within;
if (n->prio == 2)
annotated_cnt++;
}
else {
gave_up_on = c;
break;
}
}
for (cur_match = 0; cur_match < match_cnt; cur_match++) {
struct possible_tag *t = &all_matches[cur_match];
if (!(c->object.flags & t->flag_within))
t->depth++;
}
if (annotated_cnt && !list) {
if (debug)
fprintf(stderr, _("finished search at %s\n"),
oid_to_hex(&c->object.oid));
break;
}
while (parents) {
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
if (first_parent)
break;
}
}
if (!match_cnt) {
struct object_id *cmit_oid = &cmit->object.oid;
if (always) {
strbuf_add_unique_abbrev(dst, cmit_oid, abbrev);
if (suffix)
strbuf_addstr(dst, suffix);
return;
}
if (unannotated_cnt)
die(_("No annotated tags can describe '%s'.\n"
"However, there were unannotated tags: try --tags."),
oid_to_hex(cmit_oid));
else
die(_("No tags can describe '%s'.\n"
"Try --always, or create some tags."),
oid_to_hex(cmit_oid));
}
QSORT(all_matches, match_cnt, compare_pt);
if (gave_up_on) {
commit_list_insert_by_date(gave_up_on, &list);
seen_commits--;
}
seen_commits += finish_depth_computation(&list, &all_matches[0]);
free_commit_list(list);
if (debug) {
static int label_width = -1;
if (label_width < 0) {
int i, w;
for (i = 0; i < ARRAY_SIZE(prio_names); i++) {
w = strlen(_(prio_names[i]));
if (label_width < w)
label_width = w;
}
}
for (cur_match = 0; cur_match < match_cnt; cur_match++) {
struct possible_tag *t = &all_matches[cur_match];
fprintf(stderr, " %-*s %8d %s\n",
label_width, _(prio_names[t->name->prio]),
t->depth, t->name->path);
}
fprintf(stderr, _("traversed %lu commits\n"), seen_commits);
if (gave_up_on) {
fprintf(stderr,
_("more than %i tags found; listed %i most recent\n"
"gave up search at %s\n"),
max_candidates, max_candidates,
oid_to_hex(&gave_up_on->object.oid));
}
}
append_name(all_matches[0].name, dst);
if (abbrev)
append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
}
struct process_commit_data {
struct object_id current_commit;
struct object_id looking_for;
struct strbuf *dst;
struct rev_info *revs;
};
static void process_commit(struct commit *commit, void *data)
{
struct process_commit_data *pcd = data;
pcd->current_commit = commit->object.oid;
}
static void process_object(struct object *obj, const char *path, void *data)
{
struct process_commit_data *pcd = data;
if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
reset_revision_walk();
describe_commit(&pcd->current_commit, pcd->dst);
strbuf_addf(pcd->dst, ":%s", path);
free_commit_list(pcd->revs->commits);
pcd->revs->commits = NULL;
}
}
static void describe_blob(struct object_id oid, struct strbuf *dst)
{
struct rev_info revs;
struct argv_array args = ARGV_ARRAY_INIT;
struct process_commit_data pcd = { null_oid, oid, dst, &revs};
argv_array_pushl(&args, "internal: The first arg is not parsed",
"--objects", "--in-commit-order", "--reverse", "HEAD",
NULL);
repo_init_revisions(the_repository, &revs, NULL);
if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
BUG("setup_revisions could not handle all args?");
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
traverse_commit_list(&revs, process_commit, process_object, &pcd);
reset_revision_walk();
}
static void describe(const char *arg, int last_one)
{
struct object_id oid;
struct commit *cmit;
struct strbuf sb = STRBUF_INIT;
if (debug)
fprintf(stderr, _("describe %s\n"), arg);
if (get_oid(arg, &oid))
die(_("Not a valid object name %s"), arg);
cmit = lookup_commit_reference_gently(the_repository, &oid, 1);
if (cmit)
describe_commit(&oid, &sb);
else if (oid_object_info(the_repository, &oid, NULL) == OBJ_BLOB)
describe_blob(oid, &sb);
else
die(_("%s is neither a commit nor blob"), arg);
puts(sb.buf);
if (!last_one)
clear_commit_marks(cmit, -1);
strbuf_release(&sb);
}
int cmd_describe(int argc, const char **argv, const char *prefix)
{
int contains = 0;
struct option options[] = {
OPT_BOOL(0, "contains", &contains, N_("find the tag that comes after the commit")),
OPT_BOOL(0, "debug", &debug, N_("debug search strategy on stderr")),
OPT_BOOL(0, "all", &all, N_("use any ref")),
OPT_BOOL(0, "tags", &tags, N_("use any tag, even unannotated")),
OPT_BOOL(0, "long", &longformat, N_("always use long format")),
OPT_BOOL(0, "first-parent", &first_parent, N_("only follow first parent")),
OPT__ABBREV(&abbrev),
OPT_SET_INT(0, "exact-match", &max_candidates,
N_("only output exact matches"), 0),
OPT_INTEGER(0, "candidates", &max_candidates,
N_("consider <n> most recent tags (default: 10)")),
OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
N_("only consider tags matching <pattern>")),
OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
N_("do not consider tags matching <pattern>")),
OPT_BOOL(0, "always", &always,
N_("show abbreviated commit object as fallback")),
{OPTION_STRING, 0, "dirty", &dirty, N_("mark"),
N_("append <mark> on dirty working tree (default: \"-dirty\")"),
PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
{OPTION_STRING, 0, "broken", &broken, N_("mark"),
N_("append <mark> on broken working tree (default: \"-broken\")"),
PARSE_OPT_OPTARG, NULL, (intptr_t) "-broken"},
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, describe_usage, 0);
if (abbrev < 0)
abbrev = DEFAULT_ABBREV;
if (max_candidates < 0)
max_candidates = 0;
else if (max_candidates > MAX_TAGS)
max_candidates = MAX_TAGS;
save_commit_buffer = 0;
if (longformat && abbrev == 0)
die(_("--long is incompatible with --abbrev=0"));
if (contains) {
struct string_list_item *item;
struct argv_array args;
argv_array_init(&args);
argv_array_pushl(&args, "name-rev",
"--peel-tag", "--name-only", "--no-undefined",
NULL);
if (always)
argv_array_push(&args, "--always");
if (!all) {
argv_array_push(&args, "--tags");
for_each_string_list_item(item, &patterns)
argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
for_each_string_list_item(item, &exclude_patterns)
argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
}
if (argc)
argv_array_pushv(&args, argv);
else
argv_array_push(&args, "HEAD");
return cmd_name_rev(args.argc, args.argv, prefix);
}
hashmap_init(&names, commit_name_neq, NULL, 0);
for_each_rawref(get_name, NULL);
if (!hashmap_get_size(&names) && !always)
die(_("No names found, cannot describe anything."));
if (argc == 0) {
if (broken) {
struct child_process cp = CHILD_PROCESS_INIT;
argv_array_pushv(&cp.args, diff_index_args);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.no_stdout = 1;
if (!dirty)
dirty = "-dirty";
switch (run_command(&cp)) {
case 0:
suffix = NULL;
break;
case 1:
suffix = dirty;
break;
default:
/* diff-index aborted abnormally */
suffix = broken;
}
} else if (dirty) {
struct lock_file index_lock = LOCK_INIT;
struct rev_info revs;
struct argv_array args = ARGV_ARRAY_INIT;
int fd, result;
setup_work_tree();
read_cache();
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED,
NULL, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
repo_update_index_if_able(the_repository, &index_lock);
repo_init_revisions(the_repository, &revs, prefix);
argv_array_pushv(&args, diff_index_args);
if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1)
BUG("malformed internal diff-index command line");
result = run_diff_index(&revs, 0);
if (!diff_result_code(&revs.diffopt, result))
suffix = NULL;
else
suffix = dirty;
}
describe("HEAD", 1);
} else if (dirty) {
die(_("--dirty is incompatible with commit-ishes"));
} else if (broken) {
die(_("--broken is incompatible with commit-ishes"));
} else {
while (argc-- > 0)
describe(*argv++, argc == 0);
}
return 0;
}

75
third_party/git/builtin/diff-files.c vendored Normal file
View file

@ -0,0 +1,75 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
#include "submodule.h"
static const char diff_files_usage[] =
"git diff-files [-q] [-0 | -1 | -2 | -3 | -c | --cc] [<common-diff-options>] [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_files(int argc, const char **argv, const char *prefix)
{
struct rev_info rev;
int result;
unsigned options = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(diff_files_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, &rev, NULL);
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "--base"))
rev.max_count = 1;
else if (!strcmp(argv[1], "--ours"))
rev.max_count = 2;
else if (!strcmp(argv[1], "--theirs"))
rev.max_count = 3;
else if (!strcmp(argv[1], "-q"))
options |= DIFF_SILENT_ON_REMOVED;
else
usage(diff_files_usage);
argv++; argc--;
}
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
/*
* Make sure there are NO revision (i.e. pending object) parameter,
* rev.max_count is reasonable (0 <= n <= 3), and
* there is no other revision filtering parameters.
*/
if (rev.pending.nr ||
rev.min_age != -1 || rev.max_age != -1 ||
3 < rev.max_count)
usage(diff_files_usage);
/*
* "diff-files --base -p" should not combine merges because it
* was not asked to. "diff-files -c -p" should not densify
* (the user should ask with "diff-files --cc" explicitly).
*/
if (rev.max_count == -1 && !rev.combine_merges &&
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
result = run_diff_files(&rev, options);
return diff_result_code(&rev.diffopt, result);
}

62
third_party/git/builtin/diff-index.c vendored Normal file
View file

@ -0,0 +1,62 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
#include "submodule.h"
static const char diff_cache_usage[] =
"git diff-index [-m] [--cached] "
"[<common-diff-options>] <tree-ish> [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_index(int argc, const char **argv, const char *prefix)
{
struct rev_info rev;
int cached = 0;
int i;
int result;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(diff_cache_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--cached"))
cached = 1;
else
usage(diff_cache_usage);
}
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
/*
* Make sure there is one revision (i.e. pending object),
* and there is no revision filtering parameters.
*/
if (rev.pending.nr != 1 ||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
usage(diff_cache_usage);
if (!cached) {
setup_work_tree();
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
} else if (read_cache() < 0) {
perror("read_cache");
return -1;
}
result = run_diff_index(&rev, cached);
UNLEAK(rev);
return diff_result_code(&rev.diffopt, result);
}

196
third_party/git/builtin/diff-tree.c vendored Normal file
View file

@ -0,0 +1,196 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "diff.h"
#include "commit.h"
#include "log-tree.h"
#include "builtin.h"
#include "submodule.h"
#include "repository.h"
static struct rev_info log_tree_opt;
static int diff_tree_commit_oid(const struct object_id *oid)
{
struct commit *commit = lookup_commit_reference(the_repository, oid);
if (!commit)
return -1;
return log_tree_commit(&log_tree_opt, commit);
}
/* Diff one or more commits. */
static int stdin_diff_commit(struct commit *commit, const char *p)
{
struct object_id oid;
struct commit_list **pptr = NULL;
/* Graft the fake parents locally to the commit */
while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) {
struct commit *parent = lookup_commit(the_repository, &oid);
if (!pptr) {
/* Free the real parent list */
free_commit_list(commit->parents);
commit->parents = NULL;
pptr = &(commit->parents);
}
if (parent) {
pptr = &commit_list_insert(parent, pptr)->next;
}
}
return log_tree_commit(&log_tree_opt, commit);
}
/* Diff two trees. */
static int stdin_diff_trees(struct tree *tree1, const char *p)
{
struct object_id oid;
struct tree *tree2;
if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p)
return error("Need exactly two trees, separated by a space");
tree2 = lookup_tree(the_repository, &oid);
if (!tree2 || parse_tree(tree2))
return -1;
printf("%s %s\n", oid_to_hex(&tree1->object.oid),
oid_to_hex(&tree2->object.oid));
diff_tree_oid(&tree1->object.oid, &tree2->object.oid,
"", &log_tree_opt.diffopt);
log_tree_diff_flush(&log_tree_opt);
return 0;
}
static int diff_tree_stdin(char *line)
{
int len = strlen(line);
struct object_id oid;
struct object *obj;
const char *p;
if (!len || line[len-1] != '\n')
return -1;
line[len-1] = 0;
if (parse_oid_hex(line, &oid, &p))
return -1;
obj = parse_object(the_repository, &oid);
if (!obj)
return -1;
if (obj->type == OBJ_COMMIT)
return stdin_diff_commit((struct commit *)obj, p);
if (obj->type == OBJ_TREE)
return stdin_diff_trees((struct tree *)obj, p);
error("Object %s is a %s, not a commit or tree",
oid_to_hex(&oid), type_name(obj->type));
return -1;
}
static const char diff_tree_usage[] =
"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
"[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n"
" -r diff recursively\n"
" -c show combined diff for merge commits\n"
" --cc show combined diff for merge commits removing uninteresting hunks\n"
" --combined-all-paths\n"
" show name of file in all parents for combined diffs\n"
" --root include the initial commit as diff against /dev/null\n"
COMMON_DIFF_OPTIONS_HELP;
static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
{
if (!rev->diffopt.output_format) {
if (rev->dense_combined_merges)
rev->diffopt.output_format = DIFF_FORMAT_PATCH;
else
rev->diffopt.output_format = DIFF_FORMAT_RAW;
}
}
int cmd_diff_tree(int argc, const char **argv, const char *prefix)
{
char line[1000];
struct object *tree1, *tree2;
static struct rev_info *opt = &log_tree_opt;
struct setup_revision_opt s_r_opt;
int read_stdin = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(diff_tree_usage);
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
repo_init_revisions(the_repository, opt, prefix);
if (read_cache() < 0)
die(_("index file corrupt"));
opt->abbrev = 0;
opt->diff = 1;
opt->disable_stdin = 1;
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.tweak = diff_tree_tweak_rev;
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, opt, &s_r_opt);
while (--argc > 0) {
const char *arg = *++argv;
if (!strcmp(arg, "--stdin")) {
read_stdin = 1;
continue;
}
usage(diff_tree_usage);
}
/*
* NOTE! We expect "a..b" to expand to "^a b" but it is
* perfectly valid for revision range parser to yield "b ^a",
* which means the same thing. If we get the latter, i.e. the
* second one is marked UNINTERESTING, we recover the original
* order the user gave, i.e. "a..b", by swapping the trees.
*/
switch (opt->pending.nr) {
case 0:
if (!read_stdin)
usage(diff_tree_usage);
break;
case 1:
tree1 = opt->pending.objects[0].item;
diff_tree_commit_oid(&tree1->oid);
break;
case 2:
tree1 = opt->pending.objects[0].item;
tree2 = opt->pending.objects[1].item;
if (tree2->flags & UNINTERESTING) {
SWAP(tree2, tree1);
}
diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);
log_tree_diff_flush(opt);
break;
}
if (read_stdin) {
int saved_nrl = 0;
int saved_dcctc = 0;
if (opt->diffopt.detect_rename) {
if (!the_index.cache)
repo_read_index(the_repository);
opt->diffopt.setup |= DIFF_SETUP_USE_SIZE_CACHE;
}
while (fgets(line, sizeof(line), stdin)) {
struct object_id oid;
if (get_oid_hex(line, &oid)) {
fputs(line, stdout);
fflush(stdout);
}
else {
diff_tree_stdin(line);
if (saved_nrl < opt->diffopt.needed_rename_limit)
saved_nrl = opt->diffopt.needed_rename_limit;
if (opt->diffopt.degraded_cc_to_c)
saved_dcctc = 1;
}
}
opt->diffopt.degraded_cc_to_c = saved_dcctc;
opt->diffopt.needed_rename_limit = saved_nrl;
}
return diff_result_code(&opt->diffopt, 0);
}

465
third_party/git/builtin/diff.c vendored Normal file
View file

@ -0,0 +1,465 @@
/*
* Builtin "git diff"
*
* Copyright (c) 2006 Junio C Hamano
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
#include "color.h"
#include "commit.h"
#include "blob.h"
#include "tag.h"
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
#include "submodule.h"
#include "sha1-array.h"
#define DIFF_NO_INDEX_EXPLICIT 1
#define DIFF_NO_INDEX_IMPLICIT 2
static const char builtin_diff_usage[] =
"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
static const char *blob_path(struct object_array_entry *entry)
{
return entry->path ? entry->path : entry->name;
}
static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
const struct object_id *old_oid,
const struct object_id *new_oid,
int old_oid_valid,
int new_oid_valid,
const char *old_path,
const char *new_path)
{
struct diff_filespec *one, *two;
if (!is_null_oid(old_oid) && !is_null_oid(new_oid) &&
oideq(old_oid, new_oid) && (old_mode == new_mode))
return;
if (opt->flags.reverse_diff) {
SWAP(old_mode, new_mode);
SWAP(old_oid, new_oid);
SWAP(old_path, new_path);
}
if (opt->prefix &&
(strncmp(old_path, opt->prefix, opt->prefix_length) ||
strncmp(new_path, opt->prefix, opt->prefix_length)))
return;
one = alloc_filespec(old_path);
two = alloc_filespec(new_path);
fill_filespec(one, old_oid, old_oid_valid, old_mode);
fill_filespec(two, new_oid, new_oid_valid, new_mode);
diff_queue(&diff_queued_diff, one, two);
}
static int builtin_diff_b_f(struct rev_info *revs,
int argc, const char **argv,
struct object_array_entry **blob)
{
/* Blob vs file in the working tree*/
struct stat st;
const char *path;
if (argc > 1)
usage(builtin_diff_usage);
GUARD_PATHSPEC(&revs->prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
path = revs->prune_data.items[0].match;
if (lstat(path, &st))
die_errno(_("failed to stat '%s'"), path);
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
die(_("'%s': not a regular file or symlink"), path);
diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
if (blob[0]->mode == S_IFINVALID)
blob[0]->mode = canon_mode(st.st_mode);
stuff_change(&revs->diffopt,
blob[0]->mode, canon_mode(st.st_mode),
&blob[0]->item->oid, &null_oid,
1, 0,
blob[0]->path ? blob[0]->path : path,
path);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
return 0;
}
static int builtin_diff_blobs(struct rev_info *revs,
int argc, const char **argv,
struct object_array_entry **blob)
{
const unsigned mode = canon_mode(S_IFREG | 0644);
if (argc > 1)
usage(builtin_diff_usage);
if (blob[0]->mode == S_IFINVALID)
blob[0]->mode = mode;
if (blob[1]->mode == S_IFINVALID)
blob[1]->mode = mode;
stuff_change(&revs->diffopt,
blob[0]->mode, blob[1]->mode,
&blob[0]->item->oid, &blob[1]->item->oid,
1, 1,
blob_path(blob[0]), blob_path(blob[1]));
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
return 0;
}
static int builtin_diff_index(struct rev_info *revs,
int argc, const char **argv)
{
int cached = 0;
while (1 < argc) {
const char *arg = argv[1];
if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
cached = 1;
else
usage(builtin_diff_usage);
argv++; argc--;
}
/*
* Make sure there is one revision (i.e. pending object),
* and there is no revision filtering parameters.
*/
if (revs->pending.nr != 1 ||
revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1)
usage(builtin_diff_usage);
if (!cached) {
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
} else if (read_cache() < 0) {
perror("read_cache");
return -1;
}
return run_diff_index(revs, cached);
}
static int builtin_diff_tree(struct rev_info *revs,
int argc, const char **argv,
struct object_array_entry *ent0,
struct object_array_entry *ent1)
{
const struct object_id *(oid[2]);
int swap = 0;
if (argc > 1)
usage(builtin_diff_usage);
/*
* We saw two trees, ent0 and ent1. If ent1 is uninteresting,
* swap them.
*/
if (ent1->item->flags & UNINTERESTING)
swap = 1;
oid[swap] = &ent0->item->oid;
oid[1 - swap] = &ent1->item->oid;
diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
}
static int builtin_diff_combined(struct rev_info *revs,
int argc, const char **argv,
struct object_array_entry *ent,
int ents)
{
struct oid_array parents = OID_ARRAY_INIT;
int i;
if (argc > 1)
usage(builtin_diff_usage);
if (!revs->dense_combined_merges && !revs->combine_merges)
revs->dense_combined_merges = revs->combine_merges = 1;
for (i = 1; i < ents; i++)
oid_array_append(&parents, &ent[i].item->oid);
diff_tree_combined(&ent[0].item->oid, &parents,
revs->dense_combined_merges, revs);
oid_array_clear(&parents);
return 0;
}
static void refresh_index_quietly(void)
{
struct lock_file lock_file = LOCK_INIT;
int fd;
fd = hold_locked_index(&lock_file, 0);
if (fd < 0)
return;
discard_cache();
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
repo_update_index_if_able(the_repository, &lock_file);
}
static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
{
unsigned int options = 0;
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "--base"))
revs->max_count = 1;
else if (!strcmp(argv[1], "--ours"))
revs->max_count = 2;
else if (!strcmp(argv[1], "--theirs"))
revs->max_count = 3;
else if (!strcmp(argv[1], "-q"))
options |= DIFF_SILENT_ON_REMOVED;
else if (!strcmp(argv[1], "-h"))
usage(builtin_diff_usage);
else
return error(_("invalid option: %s"), argv[1]);
argv++; argc--;
}
/*
* "diff --base" should not combine merges because it was not
* asked to. "diff -c" should not densify (if the user wants
* dense one, --cc can be explicitly asked for, or just rely
* on the default).
*/
if (revs->max_count == -1 && !revs->combine_merges &&
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
return run_diff_files(revs, options);
}
int cmd_diff(int argc, const char **argv, const char *prefix)
{
int i;
struct rev_info rev;
struct object_array ent = OBJECT_ARRAY_INIT;
int blobs = 0, paths = 0;
struct object_array_entry *blob[2];
int nongit = 0, no_index = 0;
int result = 0;
/*
* We could get N tree-ish in the rev.pending_objects list.
* Also there could be M blobs there, and P pathspecs.
*
* N=0, M=0:
* cache vs files (diff-files)
* N=0, M=2:
* compare two random blobs. P must be zero.
* N=0, M=1, P=1:
* compare a blob with a working tree file.
*
* N=1, M=0:
* tree vs cache (diff-index --cached)
*
* N=2, M=0:
* tree vs tree (diff-tree)
*
* N=0, M=0, P=2:
* compare two filesystem entities (aka --no-index).
*
* Other cases are errors.
*/
/* Were we asked to do --no-index explicitly? */
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--")) {
i++;
break;
}
if (!strcmp(argv[i], "--no-index"))
no_index = DIFF_NO_INDEX_EXPLICIT;
if (argv[i][0] != '-')
break;
}
prefix = setup_git_directory_gently(&nongit);
if (!no_index) {
/*
* Treat git diff with at least one path outside of the
* repo the same as if the command would have been executed
* outside of a git repository. In this case it behaves
* the same way as "git diff --no-index <a> <b>", which acts
* as a colourful "diff" replacement.
*/
if (nongit || ((argc == i + 2) &&
(!path_inside_repo(prefix, argv[i]) ||
!path_inside_repo(prefix, argv[i + 1]))))
no_index = DIFF_NO_INDEX_IMPLICIT;
}
init_diff_ui_defaults();
git_config(git_diff_ui_config, NULL);
precompose_argv(argc, argv);
repo_init_revisions(the_repository, &rev, prefix);
/* Set up defaults that will apply to both no-index and regular diffs. */
rev.diffopt.stat_width = -1;
rev.diffopt.stat_graph_width = -1;
rev.diffopt.flags.allow_external = 1;
rev.diffopt.flags.allow_textconv = 1;
/* If this is a no-index diff, just run it and exit there. */
if (no_index)
exit(diff_no_index(&rev, no_index == DIFF_NO_INDEX_IMPLICIT,
argc, argv));
/*
* Otherwise, we are doing the usual "git" diff; set up any
* further defaults that apply to regular diffs.
*/
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
/*
* Default to intent-to-add entries invisible in the
* index. This makes them show up as new files in diff-files
* and not at all in diff-cached.
*/
rev.diffopt.ita_invisible_in_index = 1;
if (nongit)
die(_("Not a git repository"));
argc = setup_revisions(argc, argv, &rev, NULL);
if (!rev.diffopt.output_format) {
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
diff_setup_done(&rev.diffopt);
}
rev.diffopt.flags.recursive = 1;
setup_diff_pager(&rev.diffopt);
/*
* Do we have --cached and not have a pending object, then
* default to HEAD by hand. Eek.
*/
if (!rev.pending.nr) {
int i;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--"))
break;
else if (!strcmp(arg, "--cached") ||
!strcmp(arg, "--staged")) {
add_head_to_pending(&rev);
if (!rev.pending.nr) {
struct tree *tree;
tree = lookup_tree(the_repository,
the_repository->hash_algo->empty_tree);
add_pending_object(&rev, &tree->object, "HEAD");
}
break;
}
}
}
for (i = 0; i < rev.pending.nr; i++) {
struct object_array_entry *entry = &rev.pending.objects[i];
struct object *obj = entry->item;
const char *name = entry->name;
int flags = (obj->flags & UNINTERESTING);
if (!obj->parsed)
obj = parse_object(the_repository, &obj->oid);
obj = deref_tag(the_repository, obj, NULL, 0);
if (!obj)
die(_("invalid object '%s' given."), name);
if (obj->type == OBJ_COMMIT)
obj = &get_commit_tree(((struct commit *)obj))->object;
if (obj->type == OBJ_TREE) {
obj->flags |= flags;
add_object_array(obj, name, &ent);
} else if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die(_("more than two blobs given: '%s'"), name);
blob[blobs] = entry;
blobs++;
} else {
die(_("unhandled object '%s' given."), name);
}
}
if (rev.prune_data.nr)
paths += rev.prune_data.nr;
/*
* Now, do the arguments look reasonable?
*/
if (!ent.nr) {
switch (blobs) {
case 0:
result = builtin_diff_files(&rev, argc, argv);
break;
case 1:
if (paths != 1)
usage(builtin_diff_usage);
result = builtin_diff_b_f(&rev, argc, argv, blob);
break;
case 2:
if (paths)
usage(builtin_diff_usage);
result = builtin_diff_blobs(&rev, argc, argv, blob);
break;
default:
usage(builtin_diff_usage);
}
}
else if (blobs)
usage(builtin_diff_usage);
else if (ent.nr == 1)
result = builtin_diff_index(&rev, argc, argv);
else if (ent.nr == 2)
result = builtin_diff_tree(&rev, argc, argv,
&ent.objects[0], &ent.objects[1]);
else if (ent.objects[0].item->flags & UNINTERESTING) {
/*
* diff A...B where there is at least one merge base
* between A and B. We have ent.objects[0] ==
* merge-base, ent.objects[ents-2] == A, and
* ent.objects[ents-1] == B. Show diff between the
* base and B. Note that we pick one merge base at
* random if there are more than one.
*/
result = builtin_diff_tree(&rev, argc, argv,
&ent.objects[0],
&ent.objects[ent.nr-1]);
} else
result = builtin_diff_combined(&rev, argc, argv,
ent.objects, ent.nr);
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
UNLEAK(rev);
UNLEAK(ent);
UNLEAK(blob);
return result;
}

764
third_party/git/builtin/difftool.c vendored Normal file
View file

@ -0,0 +1,764 @@
/*
* "git difftool" builtin command
*
* This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
* git-difftool--helper script.
*
* This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
* The GIT_DIFF* variables are exported for use by git-difftool--helper.
*
* Any arguments that are unknown to this script are forwarded to 'git diff'.
*
* Copyright (C) 2016 Johannes Schindelin
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "run-command.h"
#include "exec-cmd.h"
#include "parse-options.h"
#include "argv-array.h"
#include "strbuf.h"
#include "lockfile.h"
#include "object-store.h"
#include "dir.h"
static int trust_exit_code;
static const char *const builtin_difftool_usage[] = {
N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
NULL
};
static int difftool_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "difftool.trustexitcode")) {
trust_exit_code = git_config_bool(var, value);
return 0;
}
return git_default_config(var, value, cb);
}
static int print_tool_help(void)
{
const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
return run_command_v_opt(argv, RUN_GIT_CMD);
}
static int parse_index_info(char *p, int *mode1, int *mode2,
struct object_id *oid1, struct object_id *oid2,
char *status)
{
if (*p != ':')
return error("expected ':', got '%c'", *p);
*mode1 = (int)strtol(p + 1, &p, 8);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
*mode2 = (int)strtol(p + 1, &p, 8);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
if (parse_oid_hex(++p, oid1, (const char **)&p))
return error("expected object ID, got '%s'", p);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
if (parse_oid_hex(++p, oid2, (const char **)&p))
return error("expected object ID, got '%s'", p);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
*status = *++p;
if (!*status)
return error("missing status");
if (p[1] && !isdigit(p[1]))
return error("unexpected trailer: '%s'", p + 1);
return 0;
}
/*
* Remove any trailing slash from $workdir
* before starting to avoid double slashes in symlink targets.
*/
static void add_path(struct strbuf *buf, size_t base_len, const char *path)
{
strbuf_setlen(buf, base_len);
if (buf->len && buf->buf[buf->len - 1] != '/')
strbuf_addch(buf, '/');
strbuf_addstr(buf, path);
}
/*
* Determine whether we can simply reuse the file in the worktree.
*/
static int use_wt_file(const char *workdir, const char *name,
struct object_id *oid)
{
struct strbuf buf = STRBUF_INIT;
struct stat st;
int use = 0;
strbuf_addstr(&buf, workdir);
add_path(&buf, buf.len, name);
if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
struct object_id wt_oid;
int fd = open(buf.buf, O_RDONLY);
if (fd >= 0 &&
!index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) {
if (is_null_oid(oid)) {
oidcpy(oid, &wt_oid);
use = 1;
} else if (oideq(oid, &wt_oid))
use = 1;
}
}
strbuf_release(&buf);
return use;
}
struct working_tree_entry {
struct hashmap_entry entry;
char path[FLEX_ARRAY];
};
static int working_tree_entry_cmp(const void *unused_cmp_data,
const void *entry,
const void *entry_or_key,
const void *unused_keydata)
{
const struct working_tree_entry *a = entry;
const struct working_tree_entry *b = entry_or_key;
return strcmp(a->path, b->path);
}
/*
* The `left` and `right` entries hold paths for the symlinks hashmap,
* and a SHA-1 surrounded by brief text for submodules.
*/
struct pair_entry {
struct hashmap_entry entry;
char left[PATH_MAX], right[PATH_MAX];
const char path[FLEX_ARRAY];
};
static int pair_cmp(const void *unused_cmp_data,
const void *entry,
const void *entry_or_key,
const void *unused_keydata)
{
const struct pair_entry *a = entry;
const struct pair_entry *b = entry_or_key;
return strcmp(a->path, b->path);
}
static void add_left_or_right(struct hashmap *map, const char *path,
const char *content, int is_right)
{
struct pair_entry *e, *existing;
FLEX_ALLOC_STR(e, path, path);
hashmap_entry_init(e, strhash(path));
existing = hashmap_get(map, e, NULL);
if (existing) {
free(e);
e = existing;
} else {
e->left[0] = e->right[0] = '\0';
hashmap_add(map, e);
}
strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
}
struct path_entry {
struct hashmap_entry entry;
char path[FLEX_ARRAY];
};
static int path_entry_cmp(const void *unused_cmp_data,
const void *entry,
const void *entry_or_key,
const void *key)
{
const struct path_entry *a = entry;
const struct path_entry *b = entry_or_key;
return strcmp(a->path, key ? key : b->path);
}
static void changed_files(struct hashmap *result, const char *index_path,
const char *workdir)
{
struct child_process update_index = CHILD_PROCESS_INIT;
struct child_process diff_files = CHILD_PROCESS_INIT;
struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
const char *git_dir = absolute_path(get_git_dir()), *env[] = {
NULL, NULL
};
FILE *fp;
strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
env[0] = index_env.buf;
argv_array_pushl(&update_index.args,
"--git-dir", git_dir, "--work-tree", workdir,
"update-index", "--really-refresh", "-q",
"--unmerged", NULL);
update_index.no_stdin = 1;
update_index.no_stdout = 1;
update_index.no_stderr = 1;
update_index.git_cmd = 1;
update_index.use_shell = 0;
update_index.clean_on_exit = 1;
update_index.dir = workdir;
update_index.env = env;
/* Ignore any errors of update-index */
run_command(&update_index);
argv_array_pushl(&diff_files.args,
"--git-dir", git_dir, "--work-tree", workdir,
"diff-files", "--name-only", "-z", NULL);
diff_files.no_stdin = 1;
diff_files.git_cmd = 1;
diff_files.use_shell = 0;
diff_files.clean_on_exit = 1;
diff_files.out = -1;
diff_files.dir = workdir;
diff_files.env = env;
if (start_command(&diff_files))
die("could not obtain raw diff");
fp = xfdopen(diff_files.out, "r");
while (!strbuf_getline_nul(&buf, fp)) {
struct path_entry *entry;
FLEX_ALLOC_STR(entry, path, buf.buf);
hashmap_entry_init(entry, strhash(buf.buf));
hashmap_add(result, entry);
}
fclose(fp);
if (finish_command(&diff_files))
die("diff-files did not exit properly");
strbuf_release(&index_env);
strbuf_release(&buf);
}
static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, tmpdir);
remove_dir_recursively(&buf, 0);
if (exit_code)
warning(_("failed: %d"), exit_code);
exit(exit_code);
}
static int ensure_leading_directories(char *path)
{
switch (safe_create_leading_directories(path)) {
case SCLD_OK:
case SCLD_EXISTS:
return 0;
default:
return error(_("could not create leading directories "
"of '%s'"), path);
}
}
/*
* Unconditional writing of a plain regular file is what
* "git difftool --dir-diff" wants to do for symlinks. We are preparing two
* temporary directories to be fed to a Git-unaware tool that knows how to
* show a diff of two directories (e.g. "diff -r A B").
*
* Because the tool is Git-unaware, if a symbolic link appears in either of
* these temporary directories, it will try to dereference and show the
* difference of the target of the symbolic link, which is not what we want,
* as the goal of the dir-diff mode is to produce an output that is logically
* equivalent to what "git diff" produces.
*
* Most importantly, we want to get textual comparison of the result of the
* readlink(2). get_symlink() provides that---it returns the contents of
* the symlink that gets written to a regular file to force the external tool
* to compare the readlink(2) result as text, even on a filesystem that is
* capable of doing a symbolic link.
*/
static char *get_symlink(const struct object_id *oid, const char *path)
{
char *data;
if (is_null_oid(oid)) {
/* The symlink is unknown to Git so read from the filesystem */
struct strbuf link = STRBUF_INIT;
if (has_symlinks) {
if (strbuf_readlink(&link, path, strlen(path)))
die(_("could not read symlink %s"), path);
} else if (strbuf_read_file(&link, path, 128))
die(_("could not read symlink file %s"), path);
data = strbuf_detach(&link, NULL);
} else {
enum object_type type;
unsigned long size;
data = read_object_file(oid, &type, &size);
if (!data)
die(_("could not read object %s for symlink %s"),
oid_to_hex(oid), path);
}
return data;
}
static int checkout_path(unsigned mode, struct object_id *oid,
const char *path, const struct checkout *state)
{
struct cache_entry *ce;
int ret;
ce = make_transient_cache_entry(mode, oid, path, 0);
ret = checkout_entry(ce, state, NULL, NULL);
discard_cache_entry(ce);
return ret;
}
static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
int argc, const char **argv)
{
char tmpdir[PATH_MAX];
struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
struct strbuf wtdir = STRBUF_INIT;
char *lbase_dir, *rbase_dir;
size_t ldir_len, rdir_len, wtdir_len;
const char *workdir, *tmp;
int ret = 0, i;
FILE *fp;
struct hashmap working_tree_dups, submodules, symlinks2;
struct hashmap_iter iter;
struct pair_entry *entry;
struct index_state wtindex;
struct checkout lstate, rstate;
int rc, flags = RUN_GIT_CMD, err = 0;
struct child_process child = CHILD_PROCESS_INIT;
const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
struct hashmap wt_modified, tmp_modified;
int indices_loaded = 0;
workdir = get_git_work_tree();
/* Setup temp directories */
tmp = getenv("TMPDIR");
xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
if (!mkdtemp(tmpdir))
return error("could not create '%s'", tmpdir);
strbuf_addf(&ldir, "%s/left/", tmpdir);
strbuf_addf(&rdir, "%s/right/", tmpdir);
strbuf_addstr(&wtdir, workdir);
if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
strbuf_addch(&wtdir, '/');
mkdir(ldir.buf, 0700);
mkdir(rdir.buf, 0700);
memset(&wtindex, 0, sizeof(wtindex));
memset(&lstate, 0, sizeof(lstate));
lstate.base_dir = lbase_dir = xstrdup(ldir.buf);
lstate.base_dir_len = ldir.len;
lstate.force = 1;
memset(&rstate, 0, sizeof(rstate));
rstate.base_dir = rbase_dir = xstrdup(rdir.buf);
rstate.base_dir_len = rdir.len;
rstate.force = 1;
ldir_len = ldir.len;
rdir_len = rdir.len;
wtdir_len = wtdir.len;
hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);
hashmap_init(&submodules, pair_cmp, NULL, 0);
hashmap_init(&symlinks2, pair_cmp, NULL, 0);
child.no_stdin = 1;
child.git_cmd = 1;
child.use_shell = 0;
child.clean_on_exit = 1;
child.dir = prefix;
child.out = -1;
argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
NULL);
for (i = 0; i < argc; i++)
argv_array_push(&child.args, argv[i]);
if (start_command(&child))
die("could not obtain raw diff");
fp = xfdopen(child.out, "r");
/* Build index info for left and right sides of the diff */
i = 0;
while (!strbuf_getline_nul(&info, fp)) {
int lmode, rmode;
struct object_id loid, roid;
char status;
const char *src_path, *dst_path;
if (starts_with(info.buf, "::"))
die(N_("combined diff formats('-c' and '--cc') are "
"not supported in\n"
"directory diff mode('-d' and '--dir-diff')."));
if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
&status))
break;
if (strbuf_getline_nul(&lpath, fp))
break;
src_path = lpath.buf;
i++;
if (status != 'C' && status != 'R') {
dst_path = src_path;
} else {
if (strbuf_getline_nul(&rpath, fp))
break;
dst_path = rpath.buf;
}
if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
strbuf_reset(&buf);
strbuf_addf(&buf, "Subproject commit %s",
oid_to_hex(&loid));
add_left_or_right(&submodules, src_path, buf.buf, 0);
strbuf_reset(&buf);
strbuf_addf(&buf, "Subproject commit %s",
oid_to_hex(&roid));
if (oideq(&loid, &roid))
strbuf_addstr(&buf, "-dirty");
add_left_or_right(&submodules, dst_path, buf.buf, 1);
continue;
}
if (S_ISLNK(lmode)) {
char *content = get_symlink(&loid, src_path);
add_left_or_right(&symlinks2, src_path, content, 0);
free(content);
}
if (S_ISLNK(rmode)) {
char *content = get_symlink(&roid, dst_path);
add_left_or_right(&symlinks2, dst_path, content, 1);
free(content);
}
if (lmode && status != 'C') {
if (checkout_path(lmode, &loid, src_path, &lstate)) {
ret = error("could not write '%s'", src_path);
goto finish;
}
}
if (rmode && !S_ISLNK(rmode)) {
struct working_tree_entry *entry;
/* Avoid duplicate working_tree entries */
FLEX_ALLOC_STR(entry, path, dst_path);
hashmap_entry_init(entry, strhash(dst_path));
if (hashmap_get(&working_tree_dups, entry, NULL)) {
free(entry);
continue;
}
hashmap_add(&working_tree_dups, entry);
if (!use_wt_file(workdir, dst_path, &roid)) {
if (checkout_path(rmode, &roid, dst_path,
&rstate)) {
ret = error("could not write '%s'",
dst_path);
goto finish;
}
} else if (!is_null_oid(&roid)) {
/*
* Changes in the working tree need special
* treatment since they are not part of the
* index.
*/
struct cache_entry *ce2 =
make_cache_entry(&wtindex, rmode, &roid,
dst_path, 0, 0);
add_index_entry(&wtindex, ce2,
ADD_CACHE_JUST_APPEND);
add_path(&rdir, rdir_len, dst_path);
if (ensure_leading_directories(rdir.buf)) {
ret = error("could not create "
"directory for '%s'",
dst_path);
goto finish;
}
add_path(&wtdir, wtdir_len, dst_path);
if (symlinks) {
if (symlink(wtdir.buf, rdir.buf)) {
ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
goto finish;
}
} else {
struct stat st;
if (stat(wtdir.buf, &st))
st.st_mode = 0644;
if (copy_file(rdir.buf, wtdir.buf,
st.st_mode)) {
ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
goto finish;
}
}
}
}
}
fclose(fp);
fp = NULL;
if (finish_command(&child)) {
ret = error("error occurred running diff --raw");
goto finish;
}
if (!i)
goto finish;
/*
* Changes to submodules require special treatment.This loop writes a
* temporary file to both the left and right directories to show the
* change in the recorded SHA1 for the submodule.
*/
hashmap_iter_init(&submodules, &iter);
while ((entry = hashmap_iter_next(&iter))) {
if (*entry->left) {
add_path(&ldir, ldir_len, entry->path);
ensure_leading_directories(ldir.buf);
write_file(ldir.buf, "%s", entry->left);
}
if (*entry->right) {
add_path(&rdir, rdir_len, entry->path);
ensure_leading_directories(rdir.buf);
write_file(rdir.buf, "%s", entry->right);
}
}
/*
* Symbolic links require special treatment.The standard "git diff"
* shows only the link itself, not the contents of the link target.
* This loop replicates that behavior.
*/
hashmap_iter_init(&symlinks2, &iter);
while ((entry = hashmap_iter_next(&iter))) {
if (*entry->left) {
add_path(&ldir, ldir_len, entry->path);
ensure_leading_directories(ldir.buf);
write_file(ldir.buf, "%s", entry->left);
}
if (*entry->right) {
add_path(&rdir, rdir_len, entry->path);
ensure_leading_directories(rdir.buf);
write_file(rdir.buf, "%s", entry->right);
}
}
strbuf_release(&buf);
strbuf_setlen(&ldir, ldir_len);
helper_argv[1] = ldir.buf;
strbuf_setlen(&rdir, rdir_len);
helper_argv[2] = rdir.buf;
if (extcmd) {
helper_argv[0] = extcmd;
flags = 0;
} else
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
rc = run_command_v_opt(helper_argv, flags);
/*
* If the diff includes working copy files and those
* files were modified during the diff, then the changes
* should be copied back to the working tree.
* Do not copy back files when symlinks are used and the
* external tool did not replace the original link with a file.
*
* These hashes are loaded lazily since they aren't needed
* in the common case of --symlinks and the difftool updating
* files through the symlink.
*/
hashmap_init(&wt_modified, path_entry_cmp, NULL, wtindex.cache_nr);
hashmap_init(&tmp_modified, path_entry_cmp, NULL, wtindex.cache_nr);
for (i = 0; i < wtindex.cache_nr; i++) {
struct hashmap_entry dummy;
const char *name = wtindex.cache[i]->name;
struct stat st;
add_path(&rdir, rdir_len, name);
if (lstat(rdir.buf, &st))
continue;
if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
continue;
if (!indices_loaded) {
struct lock_file lock = LOCK_INIT;
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/wtindex", tmpdir);
if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
ret = error("could not write %s", buf.buf);
goto finish;
}
changed_files(&wt_modified, buf.buf, workdir);
strbuf_setlen(&rdir, rdir_len);
changed_files(&tmp_modified, buf.buf, rdir.buf);
add_path(&rdir, rdir_len, name);
indices_loaded = 1;
}
hashmap_entry_init(&dummy, strhash(name));
if (hashmap_get(&tmp_modified, &dummy, name)) {
add_path(&wtdir, wtdir_len, name);
if (hashmap_get(&wt_modified, &dummy, name)) {
warning(_("both files modified: '%s' and '%s'."),
wtdir.buf, rdir.buf);
warning(_("working tree file has been left."));
warning("%s", "");
err = 1;
} else if (unlink(wtdir.buf) ||
copy_file(wtdir.buf, rdir.buf, st.st_mode))
warning_errno(_("could not copy '%s' to '%s'"),
rdir.buf, wtdir.buf);
}
}
if (err) {
warning(_("temporary files exist in '%s'."), tmpdir);
warning(_("you may want to cleanup or recover these."));
exit(1);
} else
exit_cleanup(tmpdir, rc);
finish:
if (fp)
fclose(fp);
free(lbase_dir);
free(rbase_dir);
strbuf_release(&ldir);
strbuf_release(&rdir);
strbuf_release(&wtdir);
strbuf_release(&buf);
return ret;
}
static int run_file_diff(int prompt, const char *prefix,
int argc, const char **argv)
{
struct argv_array args = ARGV_ARRAY_INIT;
const char *env[] = {
"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
NULL
};
int ret = 0, i;
if (prompt > 0)
env[2] = "GIT_DIFFTOOL_PROMPT=true";
else if (!prompt)
env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
argv_array_push(&args, "diff");
for (i = 0; i < argc; i++)
argv_array_push(&args, argv[i]);
ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
exit(ret);
}
int cmd_difftool(int argc, const char **argv, const char *prefix)
{
int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
tool_help = 0, no_index = 0;
static char *difftool_cmd = NULL, *extcmd = NULL;
struct option builtin_difftool_options[] = {
OPT_BOOL('g', "gui", &use_gui_tool,
N_("use `diff.guitool` instead of `diff.tool`")),
OPT_BOOL('d', "dir-diff", &dir_diff,
N_("perform a full-directory diff")),
OPT_SET_INT_F('y', "no-prompt", &prompt,
N_("do not prompt before launching a diff tool"),
0, PARSE_OPT_NONEG),
OPT_SET_INT_F(0, "prompt", &prompt, NULL,
1, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN),
OPT_BOOL(0, "symlinks", &symlinks,
N_("use symlinks in dir-diff mode")),
OPT_STRING('t', "tool", &difftool_cmd, N_("tool"),
N_("use the specified diff tool")),
OPT_BOOL(0, "tool-help", &tool_help,
N_("print a list of diff tools that may be used with "
"`--tool`")),
OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
N_("make 'git-difftool' exit when an invoked diff "
"tool returns a non - zero exit code")),
OPT_STRING('x', "extcmd", &extcmd, N_("command"),
N_("specify a custom command for viewing diffs")),
OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")),
OPT_END()
};
git_config(difftool_config, NULL);
symlinks = has_symlinks;
argc = parse_options(argc, argv, prefix, builtin_difftool_options,
builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
if (tool_help)
return print_tool_help();
if (!no_index && !startup_info->have_repository)
die(_("difftool requires worktree or --no-index"));
if (!no_index){
setup_work_tree();
setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
} else if (dir_diff)
die(_("--dir-diff is incompatible with --no-index"));
if (use_gui_tool + !!difftool_cmd + !!extcmd > 1)
die(_("--gui, --tool and --extcmd are mutually exclusive"));
if (use_gui_tool)
setenv("GIT_MERGETOOL_GUI", "true", 1);
else if (difftool_cmd) {
if (*difftool_cmd)
setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
else
die(_("no <tool> given for --tool=<tool>"));
}
if (extcmd) {
if (*extcmd)
setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
else
die(_("no <cmd> given for --extcmd=<cmd>"));
}
setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
trust_exit_code ? "true" : "false", 1);
/*
* In directory diff mode, 'git-difftool--helper' is called once
* to compare the a / b directories. In file diff mode, 'git diff'
* will invoke a separate instance of 'git-difftool--helper' for
* each file that changed.
*/
if (dir_diff)
return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
return run_file_diff(prompt, prefix, argc, argv);
}

95
third_party/git/builtin/env--helper.c vendored Normal file
View file

@ -0,0 +1,95 @@
#include "builtin.h"
#include "config.h"
#include "parse-options.h"
static char const * const env__helper_usage[] = {
N_("git env--helper --type=[bool|ulong] <options> <env-var>"),
NULL
};
static enum {
ENV_HELPER_TYPE_BOOL = 1,
ENV_HELPER_TYPE_ULONG
} cmdmode = 0;
static int option_parse_type(const struct option *opt, const char *arg,
int unset)
{
if (!strcmp(arg, "bool"))
cmdmode = ENV_HELPER_TYPE_BOOL;
else if (!strcmp(arg, "ulong"))
cmdmode = ENV_HELPER_TYPE_ULONG;
else
die(_("unrecognized --type argument, %s"), arg);
return 0;
}
int cmd_env__helper(int argc, const char **argv, const char *prefix)
{
int exit_code = 0;
const char *env_variable = NULL;
const char *env_default = NULL;
int ret;
int ret_int, default_int;
unsigned long ret_ulong, default_ulong;
struct option opts[] = {
OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"),
N_("value is given this type"), PARSE_OPT_NONEG,
option_parse_type),
OPT_STRING(0, "default", &env_default, N_("value"),
N_("default for git_env_*(...) to fall back on")),
OPT_BOOL(0, "exit-code", &exit_code,
N_("be quiet only use git_env_*() value as exit code")),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, opts, env__helper_usage,
PARSE_OPT_KEEP_UNKNOWN);
if (env_default && !*env_default)
usage_with_options(env__helper_usage, opts);
if (!cmdmode)
usage_with_options(env__helper_usage, opts);
if (argc != 1)
usage_with_options(env__helper_usage, opts);
env_variable = argv[0];
switch (cmdmode) {
case ENV_HELPER_TYPE_BOOL:
if (env_default) {
default_int = git_parse_maybe_bool(env_default);
if (default_int == -1) {
error(_("option `--default' expects a boolean value with `--type=bool`, not `%s`"),
env_default);
usage_with_options(env__helper_usage, opts);
}
} else {
default_int = 0;
}
ret_int = git_env_bool(env_variable, default_int);
if (!exit_code)
puts(ret_int ? "true" : "false");
ret = ret_int;
break;
case ENV_HELPER_TYPE_ULONG:
if (env_default) {
if (!git_parse_ulong(env_default, &default_ulong)) {
error(_("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`"),
env_default);
usage_with_options(env__helper_usage, opts);
}
} else {
default_ulong = 0;
}
ret_ulong = git_env_ulong(env_variable, default_ulong);
if (!exit_code)
printf("%lu\n", ret_ulong);
ret = ret_ulong;
break;
default:
BUG("unknown <type> value");
break;
}
return !ret;
}

1222
third_party/git/builtin/fast-export.c vendored Normal file

File diff suppressed because it is too large Load diff

270
third_party/git/builtin/fetch-pack.c vendored Normal file
View file

@ -0,0 +1,270 @@
#include "builtin.h"
#include "pkt-line.h"
#include "fetch-pack.h"
#include "remote.h"
#include "connect.h"
#include "sha1-array.h"
#include "protocol.h"
static const char fetch_pack_usage[] =
"git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] "
"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
"[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
const char *name)
{
struct ref *ref;
struct object_id oid;
const char *p;
if (!parse_oid_hex(name, &oid, &p)) {
if (*p == ' ') {
/* <oid> <ref>, find refname */
name = p + 1;
} else if (*p == '\0') {
; /* <oid>, leave oid as name */
} else {
/* <ref>, clear cruft from oid */
oidclr(&oid);
}
} else {
/* <ref>, clear cruft from get_oid_hex */
oidclr(&oid);
}
ref = alloc_ref(name);
oidcpy(&ref->old_oid, &oid);
(*nr)++;
ALLOC_GROW(*sought, *nr, *alloc);
(*sought)[*nr - 1] = ref;
}
int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
{
int i, ret;
struct ref *ref = NULL;
const char *dest = NULL;
struct ref **sought = NULL;
int nr_sought = 0, alloc_sought = 0;
int fd[2];
char *pack_lockfile = NULL;
char **pack_lockfile_ptr = NULL;
struct child_process *conn;
struct fetch_pack_args args;
struct oid_array shallow = OID_ARRAY_INIT;
struct string_list deepen_not = STRING_LIST_INIT_DUP;
struct packet_reader reader;
enum protocol_version version;
fetch_if_missing = 0;
packet_trace_identity("fetch-pack");
memset(&args, 0, sizeof(args));
args.uploadpack = "git-upload-pack";
for (i = 1; i < argc && *argv[i] == '-'; i++) {
const char *arg = argv[i];
if (skip_prefix(arg, "--upload-pack=", &arg)) {
args.uploadpack = arg;
continue;
}
if (skip_prefix(arg, "--exec=", &arg)) {
args.uploadpack = arg;
continue;
}
if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
args.quiet = 1;
continue;
}
if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
args.lock_pack = args.keep_pack;
args.keep_pack = 1;
continue;
}
if (!strcmp("--thin", arg)) {
args.use_thin_pack = 1;
continue;
}
if (!strcmp("--include-tag", arg)) {
args.include_tag = 1;
continue;
}
if (!strcmp("--all", arg)) {
args.fetch_all = 1;
continue;
}
if (!strcmp("--stdin", arg)) {
args.stdin_refs = 1;
continue;
}
if (!strcmp("--diag-url", arg)) {
args.diag_url = 1;
continue;
}
if (!strcmp("-v", arg)) {
args.verbose = 1;
continue;
}
if (skip_prefix(arg, "--depth=", &arg)) {
args.depth = strtol(arg, NULL, 0);
continue;
}
if (skip_prefix(arg, "--shallow-since=", &arg)) {
args.deepen_since = xstrdup(arg);
continue;
}
if (skip_prefix(arg, "--shallow-exclude=", &arg)) {
string_list_append(&deepen_not, arg);
continue;
}
if (!strcmp(arg, "--deepen-relative")) {
args.deepen_relative = 1;
continue;
}
if (!strcmp("--no-progress", arg)) {
args.no_progress = 1;
continue;
}
if (!strcmp("--stateless-rpc", arg)) {
args.stateless_rpc = 1;
continue;
}
if (!strcmp("--lock-pack", arg)) {
args.lock_pack = 1;
pack_lockfile_ptr = &pack_lockfile;
continue;
}
if (!strcmp("--check-self-contained-and-connected", arg)) {
args.check_self_contained_and_connected = 1;
continue;
}
if (!strcmp("--cloning", arg)) {
args.cloning = 1;
continue;
}
if (!strcmp("--update-shallow", arg)) {
args.update_shallow = 1;
continue;
}
if (!strcmp("--from-promisor", arg)) {
args.from_promisor = 1;
continue;
}
if (!strcmp("--no-dependents", arg)) {
args.no_dependents = 1;
continue;
}
if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
parse_list_objects_filter(&args.filter_options, arg);
continue;
}
if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
list_objects_filter_set_no_filter(&args.filter_options);
continue;
}
usage(fetch_pack_usage);
}
if (deepen_not.nr)
args.deepen_not = &deepen_not;
if (i < argc)
dest = argv[i++];
else
usage(fetch_pack_usage);
/*
* Copy refs from cmdline to growable list, then append any
* refs from the standard input:
*/
for (; i < argc; i++)
add_sought_entry(&sought, &nr_sought, &alloc_sought, argv[i]);
if (args.stdin_refs) {
if (args.stateless_rpc) {
/* in stateless RPC mode we use pkt-line to read
* from stdin, until we get a flush packet
*/
for (;;) {
char *line = packet_read_line(0, NULL);
if (!line)
break;
add_sought_entry(&sought, &nr_sought, &alloc_sought, line);
}
}
else {
/* read from stdin one ref per line, until EOF */
struct strbuf line = STRBUF_INIT;
while (strbuf_getline_lf(&line, stdin) != EOF)
add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf);
strbuf_release(&line);
}
}
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
fd[1] = 1;
} else {
int flags = args.verbose ? CONNECT_VERBOSE : 0;
if (args.diag_url)
flags |= CONNECT_DIAG_URL;
conn = git_connect(fd, dest, args.uploadpack,
flags);
if (!conn)
return args.diag_url ? 0 : 1;
}
packet_reader_init(&reader, fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_DIE_ON_ERR_PACKET);
version = discover_version(&reader);
switch (version) {
case protocol_v2:
get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
break;
case protocol_v1:
case protocol_v0:
get_remote_heads(&reader, &ref, 0, NULL, &shallow);
break;
case protocol_unknown_version:
BUG("unknown protocol version");
}
ref = fetch_pack(&args, fd, ref, sought, nr_sought,
&shallow, pack_lockfile_ptr, version);
if (pack_lockfile) {
printf("lock %s\n", pack_lockfile);
fflush(stdout);
}
if (args.check_self_contained_and_connected &&
args.self_contained_and_connected) {
printf("connectivity-ok\n");
fflush(stdout);
}
close(fd[0]);
close(fd[1]);
if (finish_connect(conn))
return 1;
ret = !ref;
/*
* If the heads to pull were given, we should have consumed
* all of them by matching the remote. Otherwise, 'git fetch
* remote no-such-ref' would silently succeed without issuing
* an error.
*/
ret |= report_unmatched_refs(sought, nr_sought);
while (ref) {
printf("%s %s\n",
oid_to_hex(&ref->old_oid), ref->name);
ref = ref->next;
}
return ret;
}

1736
third_party/git/builtin/fetch.c vendored Normal file

File diff suppressed because it is too large Load diff

719
third_party/git/builtin/fmt-merge-msg.c vendored Normal file
View file

@ -0,0 +1,719 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "refs.h"
#include "object-store.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "tag.h"
#include "string-list.h"
#include "branch.h"
#include "fmt-merge-msg.h"
#include "gpg-interface.h"
#include "repository.h"
#include "commit-reach.h"
static const char * const fmt_merge_msg_usage[] = {
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
NULL
};
static int use_branch_desc;
int fmt_merge_msg_config(const char *key, const char *value, void *cb)
{
if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
int is_bool;
merge_log_config = git_config_bool_or_int(key, value, &is_bool);
if (!is_bool && merge_log_config < 0)
return error("%s: negative length %s", key, value);
if (is_bool && merge_log_config)
merge_log_config = DEFAULT_MERGE_LOG_LEN;
} else if (!strcmp(key, "merge.branchdesc")) {
use_branch_desc = git_config_bool(key, value);
} else {
return git_default_config(key, value, cb);
}
return 0;
}
/* merge data per repository where the merged tips came from */
struct src_data {
struct string_list branch, tag, r_branch, generic;
int head_status;
};
struct origin_data {
struct object_id oid;
unsigned is_local_branch:1;
};
static void init_src_data(struct src_data *data)
{
data->branch.strdup_strings = 1;
data->tag.strdup_strings = 1;
data->r_branch.strdup_strings = 1;
data->generic.strdup_strings = 1;
}
static struct string_list srcs = STRING_LIST_INIT_DUP;
static struct string_list origins = STRING_LIST_INIT_DUP;
struct merge_parents {
int alloc, nr;
struct merge_parent {
struct object_id given;
struct object_id commit;
unsigned char used;
} *item;
};
/*
* I know, I know, this is inefficient, but you won't be pulling and merging
* hundreds of heads at a time anyway.
*/
static struct merge_parent *find_merge_parent(struct merge_parents *table,
struct object_id *given,
struct object_id *commit)
{
int i;
for (i = 0; i < table->nr; i++) {
if (given && !oideq(&table->item[i].given, given))
continue;
if (commit && !oideq(&table->item[i].commit, commit))
continue;
return &table->item[i];
}
return NULL;
}
static void add_merge_parent(struct merge_parents *table,
struct object_id *given,
struct object_id *commit)
{
if (table->nr && find_merge_parent(table, given, commit))
return;
ALLOC_GROW(table->item, table->nr + 1, table->alloc);
oidcpy(&table->item[table->nr].given, given);
oidcpy(&table->item[table->nr].commit, commit);
table->item[table->nr].used = 0;
table->nr++;
}
static int handle_line(char *line, struct merge_parents *merge_parents)
{
int i, len = strlen(line);
struct origin_data *origin_data;
char *src;
const char *origin;
struct src_data *src_data;
struct string_list_item *item;
int pulling_head = 0;
struct object_id oid;
const unsigned hexsz = the_hash_algo->hexsz;
if (len < hexsz + 3 || line[hexsz] != '\t')
return 1;
if (starts_with(line + hexsz + 1, "not-for-merge"))
return 0;
if (line[hexsz + 1] != '\t')
return 2;
i = get_oid_hex(line, &oid);
if (i)
return 3;
if (!find_merge_parent(merge_parents, &oid, NULL))
return 0; /* subsumed by other parents */
origin_data = xcalloc(1, sizeof(struct origin_data));
oidcpy(&origin_data->oid, &oid);
if (line[len - 1] == '\n')
line[len - 1] = 0;
line += hexsz + 2;
/*
* At this point, line points at the beginning of comment e.g.
* "branch 'frotz' of git://that/repository.git".
* Find the repository name and point it with src.
*/
src = strstr(line, " of ");
if (src) {
*src = 0;
src += 4;
pulling_head = 0;
} else {
src = line;
pulling_head = 1;
}
item = unsorted_string_list_lookup(&srcs, src);
if (!item) {
item = string_list_append(&srcs, src);
item->util = xcalloc(1, sizeof(struct src_data));
init_src_data(item->util);
}
src_data = item->util;
if (pulling_head) {
origin = src;
src_data->head_status |= 1;
} else if (starts_with(line, "branch ")) {
origin_data->is_local_branch = 1;
origin = line + 7;
string_list_append(&src_data->branch, origin);
src_data->head_status |= 2;
} else if (starts_with(line, "tag ")) {
origin = line;
string_list_append(&src_data->tag, origin + 4);
src_data->head_status |= 2;
} else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
string_list_append(&src_data->r_branch, origin);
src_data->head_status |= 2;
} else {
origin = src;
string_list_append(&src_data->generic, line);
src_data->head_status |= 2;
}
if (!strcmp(".", src) || !strcmp(src, origin)) {
int len = strlen(origin);
if (origin[0] == '\'' && origin[len - 1] == '\'')
origin = xmemdupz(origin + 1, len - 2);
} else
origin = xstrfmt("%s of %s", origin, src);
if (strcmp(".", src))
origin_data->is_local_branch = 0;
string_list_append(&origins, origin)->util = origin_data;
return 0;
}
static void print_joined(const char *singular, const char *plural,
struct string_list *list, struct strbuf *out)
{
if (list->nr == 0)
return;
if (list->nr == 1) {
strbuf_addf(out, "%s%s", singular, list->items[0].string);
} else {
int i;
strbuf_addstr(out, plural);
for (i = 0; i < list->nr - 1; i++)
strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
list->items[i].string);
strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
}
}
static void add_branch_desc(struct strbuf *out, const char *name)
{
struct strbuf desc = STRBUF_INIT;
if (!read_branch_desc(&desc, name)) {
const char *bp = desc.buf;
while (*bp) {
const char *ep = strchrnul(bp, '\n');
if (*ep)
ep++;
strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
bp = ep;
}
strbuf_complete_line(out);
}
strbuf_release(&desc);
}
#define util_as_integral(elem) ((intptr_t)((elem)->util))
static void record_person_from_buf(int which, struct string_list *people,
const char *buffer)
{
char *name_buf, *name, *name_end;
struct string_list_item *elem;
const char *field;
field = (which == 'a') ? "\nauthor " : "\ncommitter ";
name = strstr(buffer, field);
if (!name)
return;
name += strlen(field);
name_end = strchrnul(name, '<');
if (*name_end)
name_end--;
while (isspace(*name_end) && name <= name_end)
name_end--;
if (name_end < name)
return;
name_buf = xmemdupz(name, name_end - name + 1);
elem = string_list_lookup(people, name_buf);
if (!elem) {
elem = string_list_insert(people, name_buf);
elem->util = (void *)0;
}
elem->util = (void*)(util_as_integral(elem) + 1);
free(name_buf);
}
static void record_person(int which, struct string_list *people,
struct commit *commit)
{
const char *buffer = get_commit_buffer(commit, NULL);
record_person_from_buf(which, people, buffer);
unuse_commit_buffer(commit, buffer);
}
static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
{
const struct string_list_item *a = a_, *b = b_;
return util_as_integral(b) - util_as_integral(a);
}
static void add_people_count(struct strbuf *out, struct string_list *people)
{
if (people->nr == 1)
strbuf_addstr(out, people->items[0].string);
else if (people->nr == 2)
strbuf_addf(out, "%s (%d) and %s (%d)",
people->items[0].string,
(int)util_as_integral(&people->items[0]),
people->items[1].string,
(int)util_as_integral(&people->items[1]));
else if (people->nr)
strbuf_addf(out, "%s (%d) and others",
people->items[0].string,
(int)util_as_integral(&people->items[0]));
}
static void credit_people(struct strbuf *out,
struct string_list *them,
int kind)
{
const char *label;
const char *me;
if (kind == 'a') {
label = "By";
me = git_author_info(IDENT_NO_DATE);
} else {
label = "Via";
me = git_committer_info(IDENT_NO_DATE);
}
if (!them->nr ||
(them->nr == 1 &&
me &&
skip_prefix(me, them->items->string, &me) &&
starts_with(me, " <")))
return;
strbuf_addf(out, "\n%c %s ", comment_line_char, label);
add_people_count(out, them);
}
static void add_people_info(struct strbuf *out,
struct string_list *authors,
struct string_list *committers)
{
QSORT(authors->items, authors->nr,
cmp_string_list_util_as_integral);
QSORT(committers->items, committers->nr,
cmp_string_list_util_as_integral);
credit_people(out, authors, 'a');
credit_people(out, committers, 'c');
}
static void shortlog(const char *name,
struct origin_data *origin_data,
struct commit *head,
struct rev_info *rev,
struct fmt_merge_msg_opts *opts,
struct strbuf *out)
{
int i, count = 0;
struct commit *commit;
struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP;
struct string_list authors = STRING_LIST_INIT_DUP;
struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
const struct object_id *oid = &origin_data->oid;
int limit = opts->shortlog_len;
branch = deref_tag(the_repository, parse_object(the_repository, oid),
oid_to_hex(oid),
the_hash_algo->hexsz);
if (!branch || branch->type != OBJ_COMMIT)
return;
setup_revisions(0, NULL, rev, NULL);
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
if (prepare_revision_walk(rev))
die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0};
if (commit->parents && commit->parents->next) {
/* do not list a merge but count committer */
if (opts->credit_people)
record_person('c', &committers, commit);
continue;
}
if (!count && opts->credit_people)
/* the 'tip' committer */
record_person('c', &committers, commit);
if (opts->credit_people)
record_person('a', &authors, commit);
count++;
if (subjects.nr > limit)
continue;
format_commit_message(commit, "%s", &sb, &ctx);
strbuf_ltrim(&sb);
if (!sb.len)
string_list_append(&subjects,
oid_to_hex(&commit->object.oid));
else
string_list_append_nodup(&subjects,
strbuf_detach(&sb, NULL));
}
if (opts->credit_people)
add_people_info(out, &authors, &committers);
if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
strbuf_addf(out, "\n* %s:\n", name);
if (origin_data->is_local_branch && use_branch_desc)
add_branch_desc(out, name);
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
strbuf_addstr(out, " ...\n");
else
strbuf_addf(out, " %s\n", subjects.items[i].string);
clear_commit_marks((struct commit *)branch, flags);
clear_commit_marks(head, flags);
free_commit_list(rev->commits);
rev->commits = NULL;
rev->pending.nr = 0;
string_list_clear(&authors, 0);
string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}
static void fmt_merge_msg_title(struct strbuf *out,
const char *current_branch)
{
int i = 0;
char *sep = "";
strbuf_addstr(out, "Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.items[i].util;
const char *subsep = "";
strbuf_addstr(out, sep);
sep = "; ";
if (src_data->head_status == 1) {
strbuf_addstr(out, srcs.items[i].string);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
strbuf_addstr(out, "HEAD");
}
if (src_data->branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("branch ", "branches ", &src_data->branch,
out);
}
if (src_data->r_branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("remote-tracking branch ", "remote-tracking branches ",
&src_data->r_branch, out);
}
if (src_data->tag.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("tag ", "tags ", &src_data->tag, out);
}
if (src_data->generic.nr) {
strbuf_addstr(out, subsep);
print_joined("commit ", "commits ", &src_data->generic,
out);
}
if (strcmp(".", srcs.items[i].string))
strbuf_addf(out, " of %s", srcs.items[i].string);
}
if (!strcmp("master", current_branch))
strbuf_addch(out, '\n');
else
strbuf_addf(out, " into %s\n", current_branch);
}
static void fmt_tag_signature(struct strbuf *tagbuf,
struct strbuf *sig,
const char *buf,
unsigned long len)
{
const char *tag_body = strstr(buf, "\n\n");
if (tag_body) {
tag_body += 2;
strbuf_add(tagbuf, tag_body, buf + len - tag_body);
}
strbuf_complete_line(tagbuf);
if (sig->len) {
strbuf_addch(tagbuf, '\n');
strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
}
}
static void fmt_merge_msg_sigs(struct strbuf *out)
{
int i, tag_number = 0, first_tag = 0;
struct strbuf tagbuf = STRBUF_INIT;
for (i = 0; i < origins.nr; i++) {
struct object_id *oid = origins.items[i].util;
enum object_type type;
unsigned long size, len;
char *buf = read_object_file(oid, &type, &size);
struct strbuf sig = STRBUF_INIT;
if (!buf || type != OBJ_TAG)
goto next;
len = parse_signature(buf, size);
if (size == len)
; /* merely annotated */
else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
if (!sig.len)
strbuf_addstr(&sig, "gpg verification failed.\n");
}
if (!tag_number++) {
fmt_tag_signature(&tagbuf, &sig, buf, len);
first_tag = i;
} else {
if (tag_number == 2) {
struct strbuf tagline = STRBUF_INIT;
strbuf_addch(&tagline, '\n');
strbuf_add_commented_lines(&tagline,
origins.items[first_tag].string,
strlen(origins.items[first_tag].string));
strbuf_insert(&tagbuf, 0, tagline.buf,
tagline.len);
strbuf_release(&tagline);
}
strbuf_addch(&tagbuf, '\n');
strbuf_add_commented_lines(&tagbuf,
origins.items[i].string,
strlen(origins.items[i].string));
fmt_tag_signature(&tagbuf, &sig, buf, len);
}
strbuf_release(&sig);
next:
free(buf);
}
if (tagbuf.len) {
strbuf_addch(out, '\n');
strbuf_addbuf(out, &tagbuf);
}
strbuf_release(&tagbuf);
}
static void find_merge_parents(struct merge_parents *result,
struct strbuf *in, struct object_id *head)
{
struct commit_list *parents;
struct commit *head_commit;
int pos = 0, i, j;
parents = NULL;
while (pos < in->len) {
int len;
char *p = in->buf + pos;
char *newline = strchr(p, '\n');
const char *q;
struct object_id oid;
struct commit *parent;
struct object *obj;
len = newline ? newline - p : strlen(p);
pos += len + !!newline;
if (parse_oid_hex(p, &oid, &q) ||
q[0] != '\t' ||
q[1] != '\t')
continue; /* skip not-for-merge */
/*
* Do not use get_merge_parent() here; we do not have
* "name" here and we do not want to contaminate its
* util field yet.
*/
obj = parse_object(the_repository, &oid);
parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
if (!parent)
continue;
commit_list_insert(parent, &parents);
add_merge_parent(result, &obj->oid, &parent->object.oid);
}
head_commit = lookup_commit(the_repository, head);
if (head_commit)
commit_list_insert(head_commit, &parents);
reduce_heads_replace(&parents);
while (parents) {
struct commit *cmit = pop_commit(&parents);
for (i = 0; i < result->nr; i++)
if (oideq(&result->item[i].commit, &cmit->object.oid))
result->item[i].used = 1;
}
for (i = j = 0; i < result->nr; i++) {
if (result->item[i].used) {
if (i != j)
result->item[j] = result->item[i];
j++;
}
}
result->nr = j;
}
int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
struct fmt_merge_msg_opts *opts)
{
int i = 0, pos = 0;
struct object_id head_oid;
const char *current_branch;
void *current_branch_to_free;
struct merge_parents merge_parents;
memset(&merge_parents, 0, sizeof(merge_parents));
/* get current branch */
current_branch = current_branch_to_free =
resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
if (!current_branch)
die("No current branch");
if (starts_with(current_branch, "refs/heads/"))
current_branch += 11;
find_merge_parents(&merge_parents, in, &head_oid);
/* get a line */
while (pos < in->len) {
int len;
char *newline, *p = in->buf + pos;
newline = strchr(p, '\n');
len = newline ? newline - p : strlen(p);
pos += len + !!newline;
i++;
p[len] = 0;
if (handle_line(p, &merge_parents))
die("error in line %d: %.*s", i, len, p);
}
if (opts->add_title && srcs.nr)
fmt_merge_msg_title(out, current_branch);
if (origins.nr)
fmt_merge_msg_sigs(out);
if (opts->shortlog_len) {
struct commit *head;
struct rev_info rev;
head = lookup_commit_or_die(&head_oid, "HEAD");
repo_init_revisions(the_repository, &rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
strbuf_complete_line(out);
for (i = 0; i < origins.nr; i++)
shortlog(origins.items[i].string,
origins.items[i].util,
head, &rev, opts, out);
}
strbuf_complete_line(out);
free(current_branch_to_free);
free(merge_parents.item);
return 0;
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
const char *message = NULL;
int shortlog_len = -1;
struct option options[] = {
{ OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"),
N_("populate log with at most <n> entries from shortlog"),
PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
{ OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"),
N_("alias for --log (deprecated)"),
PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
DEFAULT_MERGE_LOG_LEN },
OPT_STRING('m', "message", &message, N_("text"),
N_("use <text> as start of message")),
OPT_FILENAME('F', "file", &inpath, N_("file to read from")),
OPT_END()
};
FILE *in = stdin;
struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
int ret;
struct fmt_merge_msg_opts opts;
git_config(fmt_merge_msg_config, NULL);
argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
0);
if (argc > 0)
usage_with_options(fmt_merge_msg_usage, options);
if (shortlog_len < 0)
shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
if (inpath && strcmp(inpath, "-")) {
in = fopen(inpath, "r");
if (!in)
die_errno("cannot open '%s'", inpath);
}
if (strbuf_read(&input, fileno(in), 0) < 0)
die_errno("could not read input file");
if (message)
strbuf_addstr(&output, message);
memset(&opts, 0, sizeof(opts));
opts.add_title = !message;
opts.credit_people = 1;
opts.shortlog_len = shortlog_len;
ret = fmt_merge_msg(&input, &output, &opts);
if (ret)
return ret;
write_in_full(STDOUT_FILENO, output.buf, output.len);
return 0;
}

87
third_party/git/builtin/for-each-ref.c vendored Normal file
View file

@ -0,0 +1,87 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "refs.h"
#include "object.h"
#include "parse-options.h"
#include "ref-filter.h"
static char const * const for_each_ref_usage[] = {
N_("git for-each-ref [<options>] [<pattern>]"),
N_("git for-each-ref [--points-at <object>]"),
N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"),
N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
NULL
};
int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
{
int i;
struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
int maxcount = 0, icase = 0;
struct ref_array array;
struct ref_filter filter;
struct ref_format format = REF_FORMAT_INIT;
struct option opts[] = {
OPT_BIT('s', "shell", &format.quote_style,
N_("quote placeholders suitably for shells"), QUOTE_SHELL),
OPT_BIT('p', "perl", &format.quote_style,
N_("quote placeholders suitably for perl"), QUOTE_PERL),
OPT_BIT(0 , "python", &format.quote_style,
N_("quote placeholders suitably for python"), QUOTE_PYTHON),
OPT_BIT(0 , "tcl", &format.quote_style,
N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
OPT_GROUP(""),
OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT__COLOR(&format.use_color, N_("respect format colors")),
OPT_REF_SORT(sorting_tail),
OPT_CALLBACK(0, "points-at", &filter.points_at,
N_("object"), N_("print only refs which points at the given object"),
parse_opt_object_name),
OPT_MERGED(&filter, N_("print only refs that are merged")),
OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END(),
};
memset(&array, 0, sizeof(array));
memset(&filter, 0, sizeof(filter));
format.format = "%(objectname) %(objecttype)\t%(refname)";
git_config(git_default_config, NULL);
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
if (maxcount < 0) {
error("invalid --count argument: `%d'", maxcount);
usage_with_options(for_each_ref_usage, opts);
}
if (HAS_MULTI_BITS(format.quote_style)) {
error("more than one quoting style?");
usage_with_options(for_each_ref_usage, opts);
}
if (verify_ref_format(&format))
usage_with_options(for_each_ref_usage, opts);
if (!sorting)
sorting = ref_default_sorting();
sorting->ignore_case = icase;
filter.ignore_case = icase;
filter.name_patterns = argv;
filter.match_as_path = 1;
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
ref_array_sort(sorting, &array);
if (!maxcount || array.nr < maxcount)
maxcount = array.nr;
for (i = 0; i < maxcount; i++)
show_ref_array_item(array.items[i], &format);
ref_array_clear(&array);
return 0;
}

976
third_party/git/builtin/fsck.c vendored Normal file
View file

@ -0,0 +1,976 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "cache.h"
#include "repository.h"
#include "config.h"
#include "commit.h"
#include "tree.h"
#include "blob.h"
#include "tag.h"
#include "refs.h"
#include "pack.h"
#include "cache-tree.h"
#include "tree-walk.h"
#include "fsck.h"
#include "parse-options.h"
#include "dir.h"
#include "progress.h"
#include "streaming.h"
#include "decorate.h"
#include "packfile.h"
#include "object-store.h"
#include "run-command.h"
#include "worktree.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
#define HAS_OBJ 0x0004
/* This flag is set if something points to this object. */
#define USED 0x0008
static int show_root;
static int show_tags;
static int show_unreachable;
static int include_reflogs = 1;
static int check_full = 1;
static int connectivity_only;
static int check_strict;
static int keep_cache_objects;
static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
static int errors_found;
static int write_lost_and_found;
static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
#define ERROR_REFS 010
#define ERROR_COMMIT_GRAPH 020
static const char *describe_object(struct object *obj)
{
static struct strbuf bufs[] = {
STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
};
static int b = 0;
struct strbuf *buf;
char *name = NULL;
if (name_objects)
name = lookup_decoration(fsck_walk_options.object_names, obj);
buf = bufs + b;
b = (b + 1) % ARRAY_SIZE(bufs);
strbuf_reset(buf);
strbuf_addstr(buf, oid_to_hex(&obj->oid));
if (name)
strbuf_addf(buf, " (%s)", name);
return buf->buf;
}
static const char *printable_type(struct object *obj)
{
const char *ret;
if (obj->type == OBJ_NONE) {
enum object_type type = oid_object_info(the_repository,
&obj->oid, NULL);
if (type > 0)
object_as_type(the_repository, obj, type, 0);
}
ret = type_name(obj->type);
if (!ret)
ret = _("unknown");
return ret;
}
static int fsck_config(const char *var, const char *value, void *cb)
{
if (strcmp(var, "fsck.skiplist") == 0) {
const char *path;
struct strbuf sb = STRBUF_INIT;
if (git_config_pathname(&path, var, value))
return 1;
strbuf_addf(&sb, "skiplist=%s", path);
free((char *)path);
fsck_set_msg_types(&fsck_obj_options, sb.buf);
strbuf_release(&sb);
return 0;
}
if (skip_prefix(var, "fsck.", &var)) {
fsck_set_msg_type(&fsck_obj_options, var, value);
return 0;
}
return git_default_config(var, value, cb);
}
static int objerror(struct object *obj, const char *err)
{
errors_found |= ERROR_OBJECT;
/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("error in %s %s: %s"),
printable_type(obj), describe_object(obj), err);
return -1;
}
static int fsck_error_func(struct fsck_options *o,
struct object *obj, int type, const char *message)
{
switch (type) {
case FSCK_WARN:
/* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("warning in %s %s: %s"),
printable_type(obj), describe_object(obj), message);
return 0;
case FSCK_ERROR:
/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("error in %s %s: %s"),
printable_type(obj), describe_object(obj), message);
return 1;
default:
BUG("%d (FSCK_IGNORE?) should never trigger this callback", type);
}
}
static struct object_array pending;
static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options)
{
struct object *parent = data;
/*
* The only case data is NULL or type is OBJ_ANY is when
* mark_object_reachable() calls us. All the callers of
* that function has non-NULL obj hence ...
*/
if (!obj) {
/* ... these references to parent->fld are safe here */
printf_ln(_("broken link from %7s %s"),
printable_type(parent), describe_object(parent));
printf_ln(_("broken link from %7s %s"),
(type == OBJ_ANY ? _("unknown") : type_name(type)),
_("unknown"));
errors_found |= ERROR_REACHABLE;
return 1;
}
if (type != OBJ_ANY && obj->type != type)
/* ... and the reference to parent is safe here */
objerror(parent, _("wrong object type in link"));
if (obj->flags & REACHABLE)
return 0;
obj->flags |= REACHABLE;
if (is_promisor_object(&obj->oid))
/*
* Further recursion does not need to be performed on this
* object since it is a promisor object (so it does not need to
* be added to "pending").
*/
return 0;
if (!(obj->flags & HAS_OBJ)) {
if (parent && !has_object_file(&obj->oid)) {
printf_ln(_("broken link from %7s %s\n"
" to %7s %s"),
printable_type(parent),
describe_object(parent),
printable_type(obj),
describe_object(obj));
errors_found |= ERROR_REACHABLE;
}
return 1;
}
add_object_array(obj, NULL, &pending);
return 0;
}
static void mark_object_reachable(struct object *obj)
{
mark_object(obj, OBJ_ANY, NULL, NULL);
}
static int traverse_one_object(struct object *obj)
{
int result = fsck_walk(obj, obj, &fsck_walk_options);
if (obj->type == OBJ_TREE) {
struct tree *tree = (struct tree *)obj;
free_tree_buffer(tree);
}
return result;
}
static int traverse_reachable(void)
{
struct progress *progress = NULL;
unsigned int nr = 0;
int result = 0;
if (show_progress)
progress = start_delayed_progress(_("Checking connectivity"), 0);
while (pending.nr) {
result |= traverse_one_object(object_array_pop(&pending));
display_progress(progress, ++nr);
}
stop_progress(&progress);
return !!result;
}
static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options)
{
if (!obj)
return 1;
obj->flags |= USED;
return 0;
}
static void mark_unreachable_referents(const struct object_id *oid)
{
struct fsck_options options = FSCK_OPTIONS_DEFAULT;
struct object *obj = lookup_object(the_repository, oid);
if (!obj || !(obj->flags & HAS_OBJ))
return; /* not part of our original set */
if (obj->flags & REACHABLE)
return; /* reachable objects already traversed */
/*
* Avoid passing OBJ_NONE to fsck_walk, which will parse the object
* (and we want to avoid parsing blobs).
*/
if (obj->type == OBJ_NONE) {
enum object_type type = oid_object_info(the_repository,
&obj->oid, NULL);
if (type > 0)
object_as_type(the_repository, obj, type, 0);
}
options.walk = mark_used;
fsck_walk(obj, NULL, &options);
}
static int mark_loose_unreachable_referents(const struct object_id *oid,
const char *path,
void *data)
{
mark_unreachable_referents(oid);
return 0;
}
static int mark_packed_unreachable_referents(const struct object_id *oid,
struct packed_git *pack,
uint32_t pos,
void *data)
{
mark_unreachable_referents(oid);
return 0;
}
/*
* Check a single reachable object
*/
static void check_reachable_object(struct object *obj)
{
/*
* We obviously want the object to be parsed,
* except if it was in a pack-file and we didn't
* do a full fsck
*/
if (!(obj->flags & HAS_OBJ)) {
if (is_promisor_object(&obj->oid))
return;
if (has_object_pack(&obj->oid))
return; /* it is in pack - forget about it */
printf_ln(_("missing %s %s"), printable_type(obj),
describe_object(obj));
errors_found |= ERROR_REACHABLE;
return;
}
}
/*
* Check a single unreachable object
*/
static void check_unreachable_object(struct object *obj)
{
/*
* Missing unreachable object? Ignore it. It's not like
* we miss it (since it can't be reached), nor do we want
* to complain about it being unreachable (since it does
* not exist).
*/
if (!(obj->flags & HAS_OBJ))
return;
/*
* Unreachable object that exists? Show it if asked to,
* since this is something that is prunable.
*/
if (show_unreachable) {
printf_ln(_("unreachable %s %s"), printable_type(obj),
describe_object(obj));
return;
}
/*
* "!USED" means that nothing at all points to it, including
* other unreachable objects. In other words, it's the "tip"
* of some set of unreachable objects, usually a commit that
* got dropped.
*
* Such starting points are more interesting than some random
* set of unreachable objects, so we show them even if the user
* hasn't asked for _all_ unreachable objects. If you have
* deleted a branch by mistake, this is a prime candidate to
* start looking at, for example.
*/
if (!(obj->flags & USED)) {
if (show_dangling)
printf_ln(_("dangling %s %s"), printable_type(obj),
describe_object(obj));
if (write_lost_and_found) {
char *filename = git_pathdup("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
describe_object(obj));
FILE *f;
if (safe_create_leading_directories_const(filename)) {
error(_("could not create lost-found"));
free(filename);
return;
}
f = xfopen(filename, "w");
if (obj->type == OBJ_BLOB) {
if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1))
die_errno(_("could not write '%s'"), filename);
} else
fprintf(f, "%s\n", describe_object(obj));
if (fclose(f))
die_errno(_("could not finish '%s'"),
filename);
free(filename);
}
return;
}
/*
* Otherwise? It's there, it's unreachable, and some other unreachable
* object points to it. Ignore it - it's not interesting, and we showed
* all the interesting cases above.
*/
}
static void check_object(struct object *obj)
{
if (verbose)
fprintf_ln(stderr, _("Checking %s"), describe_object(obj));
if (obj->flags & REACHABLE)
check_reachable_object(obj);
else
check_unreachable_object(obj);
}
static void check_connectivity(void)
{
int i, max;
/* Traverse the pending reachable objects */
traverse_reachable();
/*
* With --connectivity-only, we won't have actually opened and marked
* unreachable objects with USED. Do that now to make --dangling, etc
* accurate.
*/
if (connectivity_only && (show_dangling || write_lost_and_found)) {
/*
* Even though we already have a "struct object" for each of
* these in memory, we must not iterate over the internal
* object hash as we do below. Our loop would potentially
* resize the hash, making our iteration invalid.
*
* Instead, we'll just go back to the source list of objects,
* and ignore any that weren't present in our earlier
* traversal.
*/
for_each_loose_object(mark_loose_unreachable_referents, NULL, 0);
for_each_packed_object(mark_packed_unreachable_referents, NULL, 0);
}
/* Look up all the requirements, warn about missing objects.. */
max = get_max_object_index();
if (verbose)
fprintf_ln(stderr, _("Checking connectivity (%d objects)"), max);
for (i = 0; i < max; i++) {
struct object *obj = get_indexed_object(i);
if (obj)
check_object(obj);
}
}
static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
{
int err;
if (obj->flags & SEEN)
return 0;
obj->flags |= SEEN;
if (verbose)
fprintf_ln(stderr, _("Checking %s %s"),
printable_type(obj), describe_object(obj));
if (fsck_walk(obj, NULL, &fsck_obj_options))
objerror(obj, _("broken links"));
err = fsck_object(obj, buffer, size, &fsck_obj_options);
if (err)
goto out;
if (obj->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *) obj;
if (!commit->parents && show_root)
printf_ln(_("root %s"),
describe_object(&commit->object));
}
if (obj->type == OBJ_TAG) {
struct tag *tag = (struct tag *) obj;
if (show_tags && tag->tagged) {
printf_ln(_("tagged %s %s (%s) in %s"),
printable_type(tag->tagged),
describe_object(tag->tagged),
tag->tag,
describe_object(&tag->object));
}
}
out:
if (obj->type == OBJ_TREE)
free_tree_buffer((struct tree *)obj);
if (obj->type == OBJ_COMMIT)
free_commit_buffer(the_repository->parsed_objects,
(struct commit *)obj);
return err;
}
static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
unsigned long size, void *buffer, int *eaten)
{
/*
* Note, buffer may be NULL if type is OBJ_BLOB. See
* verify_packfile(), data_valid variable for details.
*/
struct object *obj;
obj = parse_object_buffer(the_repository, oid, type, size, buffer,
eaten);
if (!obj) {
errors_found |= ERROR_OBJECT;
return error(_("%s: object corrupt or missing"),
oid_to_hex(oid));
}
obj->flags &= ~(REACHABLE | SEEN);
obj->flags |= HAS_OBJ;
return fsck_obj(obj, buffer, size);
}
static int default_refs;
static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
timestamp_t timestamp)
{
struct object *obj;
if (!is_null_oid(oid)) {
obj = lookup_object(the_repository, oid);
if (obj && (obj->flags & HAS_OBJ)) {
if (timestamp && name_objects)
add_decoration(fsck_walk_options.object_names,
obj,
xstrfmt("%s@{%"PRItime"}", refname, timestamp));
obj->flags |= USED;
mark_object_reachable(obj);
} else if (!is_promisor_object(oid)) {
error(_("%s: invalid reflog entry %s"),
refname, oid_to_hex(oid));
errors_found |= ERROR_REACHABLE;
}
}
}
static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid,
const char *email, timestamp_t timestamp, int tz,
const char *message, void *cb_data)
{
const char *refname = cb_data;
if (verbose)
fprintf_ln(stderr, _("Checking reflog %s->%s"),
oid_to_hex(ooid), oid_to_hex(noid));
fsck_handle_reflog_oid(refname, ooid, 0);
fsck_handle_reflog_oid(refname, noid, timestamp);
return 0;
}
static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
int flag, void *cb_data)
{
struct strbuf refname = STRBUF_INIT;
strbuf_worktree_ref(cb_data, &refname, logname);
for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf);
strbuf_release(&refname);
return 0;
}
static int fsck_handle_ref(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct object *obj;
obj = parse_object(the_repository, oid);
if (!obj) {
if (is_promisor_object(oid)) {
/*
* Increment default_refs anyway, because this is a
* valid ref.
*/
default_refs++;
return 0;
}
error(_("%s: invalid sha1 pointer %s"),
refname, oid_to_hex(oid));
errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
}
if (obj->type != OBJ_COMMIT && is_branch(refname)) {
error(_("%s: not a commit"), refname);
errors_found |= ERROR_REFS;
}
default_refs++;
obj->flags |= USED;
if (name_objects)
add_decoration(fsck_walk_options.object_names,
obj, xstrdup(refname));
mark_object_reachable(obj);
return 0;
}
static int fsck_head_link(const char *head_ref_name,
const char **head_points_at,
struct object_id *head_oid);
static void get_default_heads(void)
{
struct worktree **worktrees, **p;
const char *head_points_at;
struct object_id head_oid;
for_each_rawref(fsck_handle_ref, NULL);
worktrees = get_worktrees(0);
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct strbuf ref = STRBUF_INIT;
strbuf_worktree_ref(wt, &ref, "HEAD");
fsck_head_link(ref.buf, &head_points_at, &head_oid);
if (head_points_at && !is_null_oid(&head_oid))
fsck_handle_ref(ref.buf, &head_oid, 0, NULL);
strbuf_release(&ref);
if (include_reflogs)
refs_for_each_reflog(get_worktree_ref_store(wt),
fsck_handle_reflog, wt);
}
free_worktrees(worktrees);
/*
* Not having any default heads isn't really fatal, but
* it does mean that "--unreachable" no longer makes any
* sense (since in this case everything will obviously
* be unreachable by definition.
*
* Showing dangling objects is valid, though (as those
* dangling objects are likely lost heads).
*
* So we just print a warning about it, and clear the
* "show_unreachable" flag.
*/
if (!default_refs) {
fprintf_ln(stderr, _("notice: No default references"));
show_unreachable = 0;
}
}
static int fsck_loose(const struct object_id *oid, const char *path, void *data)
{
struct object *obj;
enum object_type type;
unsigned long size;
void *contents;
int eaten;
if (read_loose_object(path, oid, &type, &size, &contents) < 0) {
errors_found |= ERROR_OBJECT;
error(_("%s: object corrupt or missing: %s"),
oid_to_hex(oid), path);
return 0; /* keep checking other objects */
}
if (!contents && type != OBJ_BLOB)
BUG("read_loose_object streamed a non-blob");
obj = parse_object_buffer(the_repository, oid, type, size,
contents, &eaten);
if (!obj) {
errors_found |= ERROR_OBJECT;
error(_("%s: object could not be parsed: %s"),
oid_to_hex(oid), path);
if (!eaten)
free(contents);
return 0; /* keep checking other objects */
}
obj->flags &= ~(REACHABLE | SEEN);
obj->flags |= HAS_OBJ;
if (fsck_obj(obj, contents, size))
errors_found |= ERROR_OBJECT;
if (!eaten)
free(contents);
return 0; /* keep checking other objects, even if we saw an error */
}
static int fsck_cruft(const char *basename, const char *path, void *data)
{
if (!starts_with(basename, "tmp_obj_"))
fprintf_ln(stderr, _("bad sha1 file: %s"), path);
return 0;
}
static int fsck_subdir(unsigned int nr, const char *path, void *progress)
{
display_progress(progress, nr + 1);
return 0;
}
static void fsck_object_dir(const char *path)
{
struct progress *progress = NULL;
if (verbose)
fprintf_ln(stderr, _("Checking object directory"));
if (show_progress)
progress = start_progress(_("Checking object directories"), 256);
for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
progress);
display_progress(progress, 256);
stop_progress(&progress);
}
static int fsck_head_link(const char *head_ref_name,
const char **head_points_at,
struct object_id *head_oid)
{
int null_is_error = 0;
if (verbose)
fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
*head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL);
if (!*head_points_at) {
errors_found |= ERROR_REFS;
return error(_("invalid %s"), head_ref_name);
}
if (!strcmp(*head_points_at, head_ref_name))
/* detached HEAD */
null_is_error = 1;
else if (!starts_with(*head_points_at, "refs/heads/")) {
errors_found |= ERROR_REFS;
return error(_("%s points to something strange (%s)"),
head_ref_name, *head_points_at);
}
if (is_null_oid(head_oid)) {
if (null_is_error) {
errors_found |= ERROR_REFS;
return error(_("%s: detached HEAD points at nothing"),
head_ref_name);
}
fprintf_ln(stderr,
_("notice: %s points to an unborn branch (%s)"),
head_ref_name, *head_points_at + 11);
}
return 0;
}
static int fsck_cache_tree(struct cache_tree *it)
{
int i;
int err = 0;
if (verbose)
fprintf_ln(stderr, _("Checking cache tree"));
if (0 <= it->entry_count) {
struct object *obj = parse_object(the_repository, &it->oid);
if (!obj) {
error(_("%s: invalid sha1 pointer in cache-tree"),
oid_to_hex(&it->oid));
errors_found |= ERROR_REFS;
return 1;
}
obj->flags |= USED;
if (name_objects)
add_decoration(fsck_walk_options.object_names,
obj, xstrdup(":"));
mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, _("non-tree in cache-tree"));
}
for (i = 0; i < it->subtree_nr; i++)
err |= fsck_cache_tree(it->down[i]->cache_tree);
return err;
}
static void mark_object_for_connectivity(const struct object_id *oid)
{
struct object *obj = lookup_unknown_object(oid);
obj->flags |= HAS_OBJ;
}
static int mark_loose_for_connectivity(const struct object_id *oid,
const char *path,
void *data)
{
mark_object_for_connectivity(oid);
return 0;
}
static int mark_packed_for_connectivity(const struct object_id *oid,
struct packed_git *pack,
uint32_t pos,
void *data)
{
mark_object_for_connectivity(oid);
return 0;
}
static char const * const fsck_usage[] = {
N_("git fsck [<options>] [<object>...]"),
NULL
};
static struct option fsck_opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
OPT_BOOL(0, "tags", &show_tags, N_("report tags")),
OPT_BOOL(0, "root", &show_root, N_("report root nodes")),
OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")),
OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
OPT_BOOL(0, "lost-found", &write_lost_and_found,
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
OPT_END(),
};
int cmd_fsck(int argc, const char **argv, const char *prefix)
{
int i;
struct object_directory *odb;
/* fsck knows how to handle missing promisor objects */
fetch_if_missing = 0;
errors_found = 0;
read_replace_refs = 0;
argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
fsck_walk_options.walk = mark_object;
fsck_obj_options.walk = mark_used;
fsck_obj_options.error_func = fsck_error_func;
if (check_strict)
fsck_obj_options.strict = 1;
if (show_progress == -1)
show_progress = isatty(2);
if (verbose)
show_progress = 0;
if (write_lost_and_found) {
check_full = 1;
include_reflogs = 0;
}
if (name_objects)
fsck_walk_options.object_names =
xcalloc(1, sizeof(struct decoration));
git_config(fsck_config, NULL);
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
} else {
prepare_alt_odb(the_repository);
for (odb = the_repository->objects->odb; odb; odb = odb->next)
fsck_object_dir(odb->path);
if (check_full) {
struct packed_git *p;
uint32_t total = 0, count = 0;
struct progress *progress = NULL;
if (show_progress) {
for (p = get_all_packs(the_repository); p;
p = p->next) {
if (open_pack_index(p))
continue;
total += p->num_objects;
}
progress = start_progress(_("Checking objects"), total);
}
for (p = get_all_packs(the_repository); p;
p = p->next) {
/* verify gives error messages itself */
if (verify_pack(the_repository,
p, fsck_obj_buffer,
progress, count))
errors_found |= ERROR_PACK;
count += p->num_objects;
}
stop_progress(&progress);
}
if (fsck_finish(&fsck_obj_options))
errors_found |= ERROR_OBJECT;
}
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
struct object_id oid;
if (!get_oid(arg, &oid)) {
struct object *obj = lookup_object(the_repository,
&oid);
if (!obj || !(obj->flags & HAS_OBJ)) {
if (is_promisor_object(&oid))
continue;
error(_("%s: object missing"), oid_to_hex(&oid));
errors_found |= ERROR_OBJECT;
continue;
}
obj->flags |= USED;
if (name_objects)
add_decoration(fsck_walk_options.object_names,
obj, xstrdup(arg));
mark_object_reachable(obj);
continue;
}
error(_("invalid parameter: expected sha1, got '%s'"), arg);
errors_found |= ERROR_OBJECT;
}
/*
* If we've not been given any explicit head information, do the
* default ones from .git/refs. We also consider the index file
* in this case (ie this implies --cache).
*/
if (!argc) {
get_default_heads();
keep_cache_objects = 1;
}
if (keep_cache_objects) {
verify_index_checksum = 1;
verify_ce_order = 1;
read_cache();
for (i = 0; i < active_nr; i++) {
unsigned int mode;
struct blob *blob;
struct object *obj;
mode = active_cache[i]->ce_mode;
if (S_ISGITLINK(mode))
continue;
blob = lookup_blob(the_repository,
&active_cache[i]->oid);
if (!blob)
continue;
obj = &blob->object;
obj->flags |= USED;
if (name_objects)
add_decoration(fsck_walk_options.object_names,
obj,
xstrfmt(":%s", active_cache[i]->name));
mark_object_reachable(obj);
}
if (active_cache_tree)
fsck_cache_tree(active_cache_tree);
}
check_connectivity();
if (!git_config_get_bool("core.commitgraph", &i) && i) {
struct child_process commit_graph_verify = CHILD_PROCESS_INIT;
const char *verify_argv[] = { "commit-graph", "verify", NULL, NULL, NULL };
prepare_alt_odb(the_repository);
for (odb = the_repository->objects->odb; odb; odb = odb->next) {
child_process_init(&commit_graph_verify);
commit_graph_verify.argv = verify_argv;
commit_graph_verify.git_cmd = 1;
verify_argv[2] = "--object-dir";
verify_argv[3] = odb->path;
if (run_command(&commit_graph_verify))
errors_found |= ERROR_COMMIT_GRAPH;
}
}
if (!git_config_get_bool("core.multipackindex", &i) && i) {
struct child_process midx_verify = CHILD_PROCESS_INIT;
const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL };
prepare_alt_odb(the_repository);
for (odb = the_repository->objects->odb; odb; odb = odb->next) {
child_process_init(&midx_verify);
midx_verify.argv = midx_argv;
midx_verify.git_cmd = 1;
midx_argv[2] = "--object-dir";
midx_argv[3] = odb->path;
if (run_command(&midx_verify))
errors_found |= ERROR_COMMIT_GRAPH;
}
}
return errors_found;
}

702
third_party/git/builtin/gc.c vendored Normal file
View file

@ -0,0 +1,702 @@
/*
* git gc builtin command
*
* Cleanup unreachable files and optimize the repository.
*
* Copyright (c) 2007 James Bowes
*
* Based on git-gc.sh, which is
*
* Copyright (c) 2006 Shawn O. Pearce
*/
#include "builtin.h"
#include "repository.h"
#include "config.h"
#include "tempfile.h"
#include "lockfile.h"
#include "parse-options.h"
#include "run-command.h"
#include "sigchain.h"
#include "argv-array.h"
#include "commit.h"
#include "commit-graph.h"
#include "packfile.h"
#include "object-store.h"
#include "pack.h"
#include "pack-objects.h"
#include "blob.h"
#include "tree.h"
#define FAILED_RUN "failed to run %s"
static const char * const builtin_gc_usage[] = {
N_("git gc [<options>]"),
NULL
};
static int pack_refs = 1;
static int prune_reflogs = 1;
static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
static int gc_write_commit_graph;
static int detach_auto = 1;
static timestamp_t gc_log_expire_time;
static const char *gc_log_expire = "1.day.ago";
static const char *prune_expire = "2.weeks.ago";
static const char *prune_worktrees_expire = "3.months.ago";
static unsigned long big_pack_threshold;
static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
static struct argv_array reflog = ARGV_ARRAY_INIT;
static struct argv_array repack = ARGV_ARRAY_INIT;
static struct argv_array prune = ARGV_ARRAY_INIT;
static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
static struct tempfile *pidfile;
static struct lock_file log_lock;
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
static void clean_pack_garbage(void)
{
int i;
for (i = 0; i < pack_garbage.nr; i++)
unlink_or_warn(pack_garbage.items[i].string);
string_list_clear(&pack_garbage, 0);
}
static void report_pack_garbage(unsigned seen_bits, const char *path)
{
if (seen_bits == PACKDIR_FILE_IDX)
string_list_append(&pack_garbage, path);
}
static void process_log_file(void)
{
struct stat st;
if (fstat(get_lock_file_fd(&log_lock), &st)) {
/*
* Perhaps there was an i/o error or another
* unlikely situation. Try to make a note of
* this in gc.log along with any existing
* messages.
*/
int saved_errno = errno;
fprintf(stderr, _("Failed to fstat %s: %s"),
get_tempfile_path(log_lock.tempfile),
strerror(saved_errno));
fflush(stderr);
commit_lock_file(&log_lock);
errno = saved_errno;
} else if (st.st_size) {
/* There was some error recorded in the lock file */
commit_lock_file(&log_lock);
} else {
/* No error, clean up any old gc.log */
unlink(git_path("gc.log"));
rollback_lock_file(&log_lock);
}
}
static void process_log_file_at_exit(void)
{
fflush(stderr);
process_log_file();
}
static void process_log_file_on_signal(int signo)
{
process_log_file();
sigchain_pop(signo);
raise(signo);
}
static int gc_config_is_timestamp_never(const char *var)
{
const char *value;
timestamp_t expire;
if (!git_config_get_value(var, &value) && value) {
if (parse_expiry_date(value, &expire))
die(_("failed to parse '%s' value '%s'"), var, value);
return expire == 0;
}
return 0;
}
static void gc_config(void)
{
const char *value;
if (!git_config_get_value("gc.packrefs", &value)) {
if (value && !strcmp(value, "notbare"))
pack_refs = -1;
else
pack_refs = git_config_bool("gc.packrefs", value);
}
if (gc_config_is_timestamp_never("gc.reflogexpire") &&
gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
prune_reflogs = 0;
git_config_get_int("gc.aggressivewindow", &aggressive_window);
git_config_get_int("gc.aggressivedepth", &aggressive_depth);
git_config_get_int("gc.auto", &gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph);
git_config_get_bool("gc.autodetach", &detach_auto);
git_config_get_expiry("gc.pruneexpire", &prune_expire);
git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
git_config_get_expiry("gc.logexpiry", &gc_log_expire);
git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold);
git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size);
git_config(git_default_config, NULL);
}
static int too_many_loose_objects(void)
{
/*
* Quickly check if a "gc" is needed, by estimating how
* many loose objects there are. Because SHA-1 is evenly
* distributed, we can check only one and get a reasonable
* estimate.
*/
DIR *dir;
struct dirent *ent;
int auto_threshold;
int num_loose = 0;
int needed = 0;
const unsigned hexsz_loose = the_hash_algo->hexsz - 2;
dir = opendir(git_path("objects/17"));
if (!dir)
return 0;
auto_threshold = DIV_ROUND_UP(gc_auto_threshold, 256);
while ((ent = readdir(dir)) != NULL) {
if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
ent->d_name[hexsz_loose] != '\0')
continue;
if (++num_loose > auto_threshold) {
needed = 1;
break;
}
}
closedir(dir);
return needed;
}
static struct packed_git *find_base_packs(struct string_list *packs,
unsigned long limit)
{
struct packed_git *p, *base = NULL;
for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (limit) {
if (p->pack_size >= limit)
string_list_append(packs, p->pack_name);
} else if (!base || base->pack_size < p->pack_size) {
base = p;
}
}
if (base)
string_list_append(packs, base->pack_name);
return base;
}
static int too_many_packs(void)
{
struct packed_git *p;
int cnt;
if (gc_auto_pack_limit <= 0)
return 0;
for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_local)
continue;
if (p->pack_keep)
continue;
/*
* Perhaps check the size of the pack and count only
* very small ones here?
*/
cnt++;
}
return gc_auto_pack_limit < cnt;
}
static uint64_t total_ram(void)
{
#if defined(HAVE_SYSINFO)
struct sysinfo si;
if (!sysinfo(&si))
return si.totalram;
#elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM))
int64_t physical_memory;
int mib[2];
size_t length;
mib[0] = CTL_HW;
# if defined(HW_MEMSIZE)
mib[1] = HW_MEMSIZE;
# else
mib[1] = HW_PHYSMEM;
# endif
length = sizeof(int64_t);
if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0))
return physical_memory;
#elif defined(GIT_WINDOWS_NATIVE)
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
if (GlobalMemoryStatusEx(&memInfo))
return memInfo.ullTotalPhys;
#endif
return 0;
}
static uint64_t estimate_repack_memory(struct packed_git *pack)
{
unsigned long nr_objects = approximate_object_count();
size_t os_cache, heap;
if (!pack || !nr_objects)
return 0;
/*
* First we have to scan through at least one pack.
* Assume enough room in OS file cache to keep the entire pack
* or we may accidentally evict data of other processes from
* the cache.
*/
os_cache = pack->pack_size + pack->index_size;
/* then pack-objects needs lots more for book keeping */
heap = sizeof(struct object_entry) * nr_objects;
/*
* internal rev-list --all --objects takes up some memory too,
* let's say half of it is for blobs
*/
heap += sizeof(struct blob) * nr_objects / 2;
/*
* and the other half is for trees (commits and tags are
* usually insignificant)
*/
heap += sizeof(struct tree) * nr_objects / 2;
/* and then obj_hash[], underestimated in fact */
heap += sizeof(struct object *) * nr_objects;
/* revindex is used also */
heap += sizeof(struct revindex_entry) * nr_objects;
/*
* read_sha1_file() (either at delta calculation phase, or
* writing phase) also fills up the delta base cache
*/
heap += delta_base_cache_limit;
/* and of course pack-objects has its own delta cache */
heap += max_delta_cache_size;
return os_cache + heap;
}
static int keep_one_pack(struct string_list_item *item, void *data)
{
argv_array_pushf(&repack, "--keep-pack=%s", basename(item->string));
return 0;
}
static void add_repack_all_option(struct string_list *keep_pack)
{
if (prune_expire && !strcmp(prune_expire, "now"))
argv_array_push(&repack, "-a");
else {
argv_array_push(&repack, "-A");
if (prune_expire)
argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
}
if (keep_pack)
for_each_string_list(keep_pack, keep_one_pack, NULL);
}
static void add_repack_incremental_option(void)
{
argv_array_push(&repack, "--no-write-bitmap-index");
}
static int need_to_gc(void)
{
/*
* Setting gc.auto to 0 or negative can disable the
* automatic gc.
*/
if (gc_auto_threshold <= 0)
return 0;
/*
* If there are too many loose objects, but not too many
* packs, we run "repack -d -l". If there are too many packs,
* we run "repack -A -d -l". Otherwise we tell the caller
* there is no need.
*/
if (too_many_packs()) {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
if (big_pack_threshold) {
find_base_packs(&keep_pack, big_pack_threshold);
if (keep_pack.nr >= gc_auto_pack_limit) {
big_pack_threshold = 0;
string_list_clear(&keep_pack, 0);
find_base_packs(&keep_pack, 0);
}
} else {
struct packed_git *p = find_base_packs(&keep_pack, 0);
uint64_t mem_have, mem_want;
mem_have = total_ram();
mem_want = estimate_repack_memory(p);
/*
* Only allow 1/2 of memory for pack-objects, leave
* the rest for the OS and other processes in the
* system.
*/
if (!mem_have || mem_want < mem_have / 2)
string_list_clear(&keep_pack, 0);
}
add_repack_all_option(&keep_pack);
string_list_clear(&keep_pack, 0);
} else if (too_many_loose_objects())
add_repack_incremental_option();
else
return 0;
if (run_hook_le(NULL, "pre-auto-gc", NULL))
return 0;
return 1;
}
/* return NULL on success, else hostname running the gc */
static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
{
struct lock_file lock = LOCK_INIT;
char my_host[HOST_NAME_MAX + 1];
struct strbuf sb = STRBUF_INIT;
struct stat st;
uintmax_t pid;
FILE *fp;
int fd;
char *pidfile_path;
if (is_tempfile_active(pidfile))
/* already locked */
return NULL;
if (xgethostname(my_host, sizeof(my_host)))
xsnprintf(my_host, sizeof(my_host), "unknown");
pidfile_path = git_pathdup("gc.pid");
fd = hold_lock_file_for_update(&lock, pidfile_path,
LOCK_DIE_ON_ERROR);
if (!force) {
static char locking_host[HOST_NAME_MAX + 1];
static char *scan_fmt;
int should_exit;
if (!scan_fmt)
scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX);
fp = fopen(pidfile_path, "r");
memset(locking_host, 0, sizeof(locking_host));
should_exit =
fp != NULL &&
!fstat(fileno(fp), &st) &&
/*
* 12 hour limit is very generous as gc should
* never take that long. On the other hand we
* don't really need a strict limit here,
* running gc --auto one day late is not a big
* problem. --force can be used in manual gc
* after the user verifies that no gc is
* running.
*/
time(NULL) - st.st_mtime <= 12 * 3600 &&
fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
/* be gentle to concurrent "gc" on remote hosts */
(strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
if (fp != NULL)
fclose(fp);
if (should_exit) {
if (fd >= 0)
rollback_lock_file(&lock);
*ret_pid = pid;
free(pidfile_path);
return locking_host;
}
}
strbuf_addf(&sb, "%"PRIuMAX" %s",
(uintmax_t) getpid(), my_host);
write_in_full(fd, sb.buf, sb.len);
strbuf_release(&sb);
commit_lock_file(&lock);
pidfile = register_tempfile(pidfile_path);
free(pidfile_path);
return NULL;
}
/*
* Returns 0 if there was no previous error and gc can proceed, 1 if
* gc should not proceed due to an error in the last run. Prints a
* message and returns -1 if an error occured while reading gc.log
*/
static int report_last_gc_error(void)
{
struct strbuf sb = STRBUF_INIT;
int ret = 0;
ssize_t len;
struct stat st;
char *gc_log_path = git_pathdup("gc.log");
if (stat(gc_log_path, &st)) {
if (errno == ENOENT)
goto done;
ret = error_errno(_("cannot stat '%s'"), gc_log_path);
goto done;
}
if (st.st_mtime < gc_log_expire_time)
goto done;
len = strbuf_read_file(&sb, gc_log_path, 0);
if (len < 0)
ret = error_errno(_("cannot read '%s'"), gc_log_path);
else if (len > 0) {
/*
* A previous gc failed. Report the error, and don't
* bother with an automatic gc run since it is likely
* to fail in the same way.
*/
warning(_("The last gc run reported the following. "
"Please correct the root cause\n"
"and remove %s.\n"
"Automatic cleanup will not be performed "
"until the file is removed.\n\n"
"%s"),
gc_log_path, sb.buf);
ret = 1;
}
strbuf_release(&sb);
done:
free(gc_log_path);
return ret;
}
static void gc_before_repack(void)
{
/*
* We may be called twice, as both the pre- and
* post-daemonized phases will call us, but running these
* commands more than once is pointless and wasteful.
*/
static int done = 0;
if (done++)
return;
if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
die(FAILED_RUN, pack_refs_cmd.argv[0]);
if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
die(FAILED_RUN, reflog.argv[0]);
}
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
int force = 0;
const char *name;
pid_t pid;
int daemonized = 0;
int keep_base_pack = -1;
timestamp_t dummy;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
{ OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
N_("prune unreferenced objects"),
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL_F(0, "force", &force,
N_("force running gc even if there may be another gc running"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL(0, "keep-largest-pack", &keep_base_pack,
N_("repack all other packs except the largest pack")),
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_gc_usage, builtin_gc_options);
argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
argv_array_pushl(&prune, "prune", "--expire", NULL);
argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
/* default expiry time, overwritten in gc_config */
gc_config();
if (parse_expiry_date(gc_log_expire, &gc_log_expire_time))
die(_("failed to parse gc.logexpiry value %s"), gc_log_expire);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
argc = parse_options(argc, argv, prefix, builtin_gc_options,
builtin_gc_usage, 0);
if (argc > 0)
usage_with_options(builtin_gc_usage, builtin_gc_options);
if (prune_expire && parse_expiry_date(prune_expire, &dummy))
die(_("failed to parse prune expiry value %s"), prune_expire);
if (aggressive) {
argv_array_push(&repack, "-f");
if (aggressive_depth > 0)
argv_array_pushf(&repack, "--depth=%d", aggressive_depth);
if (aggressive_window > 0)
argv_array_pushf(&repack, "--window=%d", aggressive_window);
}
if (quiet)
argv_array_push(&repack, "-q");
if (auto_gc) {
/*
* Auto-gc should be least intrusive as possible.
*/
if (!need_to_gc())
return 0;
if (!quiet) {
if (detach_auto)
fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
else
fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
}
if (detach_auto) {
int ret = report_last_gc_error();
if (ret < 0)
/* an I/O error occured, already reported */
exit(128);
if (ret == 1)
/* Last gc --auto failed. Skip this one. */
return 0;
if (lock_repo_for_gc(force, &pid))
return 0;
gc_before_repack(); /* dies on failure */
delete_tempfile(&pidfile);
/*
* failure to daemonize is ok, we'll continue
* in foreground
*/
daemonized = !daemonize();
}
} else {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
if (keep_base_pack != -1) {
if (keep_base_pack)
find_base_packs(&keep_pack, 0);
} else if (big_pack_threshold) {
find_base_packs(&keep_pack, big_pack_threshold);
}
add_repack_all_option(&keep_pack);
string_list_clear(&keep_pack, 0);
}
name = lock_repo_for_gc(force, &pid);
if (name) {
if (auto_gc)
return 0; /* be quiet on --auto */
die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
name, (uintmax_t)pid);
}
if (daemonized) {
hold_lock_file_for_update(&log_lock,
git_path("gc.log"),
LOCK_DIE_ON_ERROR);
dup2(get_lock_file_fd(&log_lock), 2);
sigchain_push_common(process_log_file_on_signal);
atexit(process_log_file_at_exit);
}
gc_before_repack();
if (!repository_format_precious_objects) {
close_object_store(the_repository->objects);
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
die(FAILED_RUN, repack.argv[0]);
if (prune_expire) {
argv_array_push(&prune, prune_expire);
if (quiet)
argv_array_push(&prune, "--no-progress");
if (repository_format_partial_clone)
argv_array_push(&prune,
"--exclude-promisor-objects");
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
die(FAILED_RUN, prune.argv[0]);
}
}
if (prune_worktrees_expire) {
argv_array_push(&prune_worktrees, prune_worktrees_expire);
if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
die(FAILED_RUN, prune_worktrees.argv[0]);
}
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
die(FAILED_RUN, rerere.argv[0]);
report_garbage = report_pack_garbage;
reprepare_packed_git(the_repository);
if (pack_garbage.nr > 0) {
close_object_store(the_repository->objects);
clean_pack_garbage();
}
if (gc_write_commit_graph &&
write_commit_graph_reachable(get_object_directory(),
!quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0,
NULL))
return 1;
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
"run 'git prune' to remove them."));
if (!daemonized)
unlink(git_path("gc.log"));
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2005, 2006 Rene Scharfe
*/
#include "cache.h"
#include "commit.h"
#include "tar.h"
#include "builtin.h"
#include "quote.h"
static const char builtin_get_tar_commit_id_usage[] =
"git get-tar-commit-id";
/* ustar header + extended global header content */
#define RECORDSIZE (512)
#define HEADERSIZE (2 * RECORDSIZE)
int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
{
char buffer[HEADERSIZE];
struct ustar_header *header = (struct ustar_header *)buffer;
char *content = buffer + RECORDSIZE;
const char *comment;
ssize_t n;
long len;
char *end;
if (argc != 1)
usage(builtin_get_tar_commit_id_usage);
n = read_in_full(0, buffer, HEADERSIZE);
if (n < 0)
die_errno("git get-tar-commit-id: read error");
if (n != HEADERSIZE)
die_errno("git get-tar-commit-id: EOF before reading tar header");
if (header->typeflag[0] != 'g')
return 1;
len = strtol(content, &end, 10);
if (errno == ERANGE || end == content || len < 0)
return 1;
if (!skip_prefix(end, " comment=", &comment))
return 1;
len -= comment - content;
if (len < 1 || !(len % 2) ||
hash_algo_by_length((len - 1) / 2) == GIT_HASH_UNKNOWN)
return 1;
if (write_in_full(1, comment, len) < 0)
die_errno("git get-tar-commit-id: write error");
return 0;
}

1149
third_party/git/builtin/grep.c vendored Normal file

File diff suppressed because it is too large Load diff

162
third_party/git/builtin/hash-object.c vendored Normal file
View file

@ -0,0 +1,162 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
* Copyright (C) Junio C Hamano, 2005
*/
#include "builtin.h"
#include "config.h"
#include "object-store.h"
#include "blob.h"
#include "quote.h"
#include "parse-options.h"
#include "exec-cmd.h"
/*
* This is to create corrupt objects for debugging and as such it
* needs to bypass the data conversion performed by, and the type
* limitation imposed by, index_fd() and its callees.
*/
static int hash_literally(struct object_id *oid, int fd, const char *type, unsigned flags)
{
struct strbuf buf = STRBUF_INIT;
int ret;
if (strbuf_read(&buf, fd, 4096) < 0)
ret = -1;
else
ret = hash_object_file_literally(buf.buf, buf.len, type, oid,
flags);
strbuf_release(&buf);
return ret;
}
static void hash_fd(int fd, const char *type, const char *path, unsigned flags,
int literally)
{
struct stat st;
struct object_id oid;
if (fstat(fd, &st) < 0 ||
(literally
? hash_literally(&oid, fd, type, flags)
: index_fd(the_repository->index, &oid, fd, &st,
type_from_string(type), path, flags)))
die((flags & HASH_WRITE_OBJECT)
? "Unable to add %s to database"
: "Unable to hash %s", path);
printf("%s\n", oid_to_hex(&oid));
maybe_flush_or_die(stdout, "hash to stdout");
}
static void hash_object(const char *path, const char *type, const char *vpath,
unsigned flags, int literally)
{
int fd;
fd = open(path, O_RDONLY);
if (fd < 0)
die_errno("Cannot open '%s'", path);
hash_fd(fd, type, vpath, flags, literally);
}
static void hash_stdin_paths(const char *type, int no_filters, unsigned flags,
int literally)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
while (strbuf_getline(&buf, stdin) != EOF) {
if (buf.buf[0] == '"') {
strbuf_reset(&unquoted);
if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
hash_object(buf.buf, type, no_filters ? NULL : buf.buf, flags,
literally);
}
strbuf_release(&buf);
strbuf_release(&unquoted);
}
int cmd_hash_object(int argc, const char **argv, const char *prefix)
{
static const char * const hash_object_usage[] = {
N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."),
N_("git hash-object --stdin-paths"),
NULL
};
const char *type = blob_type;
int hashstdin = 0;
int stdin_paths = 0;
int no_filters = 0;
int literally = 0;
int nongit = 0;
unsigned flags = HASH_FORMAT_CHECK;
const char *vpath = NULL;
const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
HASH_WRITE_OBJECT),
OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")),
OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")),
OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")),
OPT_BOOL( 0, "literally", &literally, N_("just hash any random garbage to create corrupt objects for debugging Git")),
OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")),
OPT_END()
};
int i;
const char *errstr = NULL;
argc = parse_options(argc, argv, prefix, hash_object_options,
hash_object_usage, 0);
if (flags & HASH_WRITE_OBJECT)
prefix = setup_git_directory();
else
prefix = setup_git_directory_gently(&nongit);
if (vpath && prefix)
vpath = xstrdup(prefix_filename(prefix, vpath));
git_config(git_default_config, NULL);
if (stdin_paths) {
if (hashstdin)
errstr = "Can't use --stdin-paths with --stdin";
else if (argc)
errstr = "Can't specify files with --stdin-paths";
else if (vpath)
errstr = "Can't use --stdin-paths with --path";
}
else {
if (hashstdin > 1)
errstr = "Multiple --stdin arguments are not supported";
if (vpath && no_filters)
errstr = "Can't use --path with --no-filters";
}
if (errstr) {
error("%s", errstr);
usage_with_options(hash_object_usage, hash_object_options);
}
if (hashstdin)
hash_fd(0, type, vpath, flags, literally);
for (i = 0 ; i < argc; i++) {
const char *arg = argv[i];
char *to_free = NULL;
if (prefix)
arg = to_free = prefix_filename(prefix, arg);
hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg,
flags, literally);
free(to_free);
}
if (stdin_paths)
hash_stdin_paths(type, no_filters, flags, literally);
return 0;
}

537
third_party/git/builtin/help.c vendored Normal file
View file

@ -0,0 +1,537 @@
/*
* Builtin help command
*/
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "exec-cmd.h"
#include "parse-options.h"
#include "run-command.h"
#include "column.h"
#include "help.h"
#include "alias.h"
#ifndef DEFAULT_HELP_FORMAT
#define DEFAULT_HELP_FORMAT "man"
#endif
static struct man_viewer_list {
struct man_viewer_list *next;
char name[FLEX_ARRAY];
} *man_viewer_list;
static struct man_viewer_info_list {
struct man_viewer_info_list *next;
const char *info;
char name[FLEX_ARRAY];
} *man_viewer_info_list;
enum help_format {
HELP_FORMAT_NONE,
HELP_FORMAT_MAN,
HELP_FORMAT_INFO,
HELP_FORMAT_WEB
};
static const char *html_path;
static int show_all = 0;
static int show_guides = 0;
static int show_config;
static int verbose = 1;
static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static int exclude_guides;
static struct option builtin_help_options[] = {
OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")),
OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN),
OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
HELP_FORMAT_WEB),
OPT_SET_INT('i', "info", &help_format, N_("show info page"),
HELP_FORMAT_INFO),
OPT__VERBOSE(&verbose, N_("print command description")),
OPT_END(),
};
static const char * const builtin_help_usage[] = {
N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
NULL
};
static enum help_format parse_help_format(const char *format)
{
if (!strcmp(format, "man"))
return HELP_FORMAT_MAN;
if (!strcmp(format, "info"))
return HELP_FORMAT_INFO;
if (!strcmp(format, "web") || !strcmp(format, "html"))
return HELP_FORMAT_WEB;
/*
* Please update _git_config() in git-completion.bash when you
* add new help formats.
*/
die(_("unrecognized help format '%s'"), format);
}
static const char *get_man_viewer_info(const char *name)
{
struct man_viewer_info_list *viewer;
for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
{
if (!strcasecmp(name, viewer->name))
return viewer->info;
}
return NULL;
}
static int check_emacsclient_version(void)
{
struct strbuf buffer = STRBUF_INIT;
struct child_process ec_process = CHILD_PROCESS_INIT;
const char *argv_ec[] = { "emacsclient", "--version", NULL };
int version;
/* emacsclient prints its version number on stderr */
ec_process.argv = argv_ec;
ec_process.err = -1;
ec_process.stdout_to_stderr = 1;
if (start_command(&ec_process))
return error(_("Failed to start emacsclient."));
strbuf_read(&buffer, ec_process.err, 20);
close(ec_process.err);
/*
* Don't bother checking return value, because "emacsclient --version"
* seems to always exits with code 1.
*/
finish_command(&ec_process);
if (!starts_with(buffer.buf, "emacsclient")) {
strbuf_release(&buffer);
return error(_("Failed to parse emacsclient version."));
}
strbuf_remove(&buffer, 0, strlen("emacsclient"));
version = atoi(buffer.buf);
if (version < 22) {
strbuf_release(&buffer);
return error(_("emacsclient version '%d' too old (< 22)."),
version);
}
strbuf_release(&buffer);
return 0;
}
static void exec_woman_emacs(const char *path, const char *page)
{
if (!check_emacsclient_version()) {
/* This works only with emacsclient version >= 22. */
struct strbuf man_page = STRBUF_INIT;
if (!path)
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
warning_errno(_("failed to exec '%s'"), path);
strbuf_release(&man_page);
}
}
static void exec_man_konqueror(const char *path, const char *page)
{
const char *display = getenv("DISPLAY");
if (display && *display) {
struct strbuf man_page = STRBUF_INIT;
const char *filename = "kfmclient";
/* It's simpler to launch konqueror using kfmclient. */
if (path) {
size_t len;
if (strip_suffix(path, "/konqueror", &len))
path = xstrfmt("%.*s/kfmclient", (int)len, path);
filename = basename((char *)path);
} else
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
warning_errno(_("failed to exec '%s'"), path);
strbuf_release(&man_page);
}
}
static void exec_man_man(const char *path, const char *page)
{
if (!path)
path = "man";
execlp(path, "man", page, (char *)NULL);
warning_errno(_("failed to exec '%s'"), path);
}
static void exec_man_cmd(const char *cmd, const char *page)
{
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL);
warning(_("failed to exec '%s'"), cmd);
strbuf_release(&shell_cmd);
}
static void add_man_viewer(const char *name)
{
struct man_viewer_list **p = &man_viewer_list;
while (*p)
p = &((*p)->next);
FLEX_ALLOC_STR(*p, name, name);
}
static int supported_man_viewer(const char *name, size_t len)
{
return (!strncasecmp("man", name, len) ||
!strncasecmp("woman", name, len) ||
!strncasecmp("konqueror", name, len));
}
static void do_add_man_viewer_info(const char *name,
size_t len,
const char *value)
{
struct man_viewer_info_list *new_man_viewer;
FLEX_ALLOC_MEM(new_man_viewer, name, name, len);
new_man_viewer->info = xstrdup(value);
new_man_viewer->next = man_viewer_info_list;
man_viewer_info_list = new_man_viewer;
}
static int add_man_viewer_path(const char *name,
size_t len,
const char *value)
{
if (supported_man_viewer(name, len))
do_add_man_viewer_info(name, len, value);
else
warning(_("'%s': path for unsupported man viewer.\n"
"Please consider using 'man.<tool>.cmd' instead."),
name);
return 0;
}
static int add_man_viewer_cmd(const char *name,
size_t len,
const char *value)
{
if (supported_man_viewer(name, len))
warning(_("'%s': cmd for supported man viewer.\n"
"Please consider using 'man.<tool>.path' instead."),
name);
else
do_add_man_viewer_info(name, len, value);
return 0;
}
static int add_man_viewer_info(const char *var, const char *value)
{
const char *name, *subkey;
int namelen;
if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
return 0;
if (!strcmp(subkey, "path")) {
if (!value)
return config_error_nonbool(var);
return add_man_viewer_path(name, namelen, value);
}
if (!strcmp(subkey, "cmd")) {
if (!value)
return config_error_nonbool(var);
return add_man_viewer_cmd(name, namelen, value);
}
return 0;
}
static int git_help_config(const char *var, const char *value, void *cb)
{
if (starts_with(var, "column."))
return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
help_format = parse_help_format(value);
return 0;
}
if (!strcmp(var, "help.htmlpath")) {
if (!value)
return config_error_nonbool(var);
html_path = xstrdup(value);
return 0;
}
if (!strcmp(var, "man.viewer")) {
if (!value)
return config_error_nonbool(var);
add_man_viewer(value);
return 0;
}
if (starts_with(var, "man."))
return add_man_viewer_info(var, value);
return git_default_config(var, value, cb);
}
static struct cmdnames main_cmds, other_cmds;
static int is_git_command(const char *s)
{
if (is_builtin(s))
return 1;
load_command_list("git-", &main_cmds, &other_cmds);
return is_in_cmdlist(&main_cmds, s) ||
is_in_cmdlist(&other_cmds, s);
}
static const char *cmd_to_page(const char *git_cmd)
{
if (!git_cmd)
return "git";
else if (starts_with(git_cmd, "git"))
return git_cmd;
else if (is_git_command(git_cmd))
return xstrfmt("git-%s", git_cmd);
else
return xstrfmt("git%s", git_cmd);
}
static void setup_man_path(void)
{
struct strbuf new_path = STRBUF_INIT;
const char *old_path = getenv("MANPATH");
char *git_man_path = system_path(GIT_MAN_PATH);
/* We should always put ':' after our path. If there is no
* old_path, the ':' at the end will let 'man' to try
* system-wide paths after ours to find the manual page. If
* there is old_path, we need ':' as delimiter. */
strbuf_addstr(&new_path, git_man_path);
strbuf_addch(&new_path, ':');
if (old_path)
strbuf_addstr(&new_path, old_path);
free(git_man_path);
setenv("MANPATH", new_path.buf, 1);
strbuf_release(&new_path);
}
static void exec_viewer(const char *name, const char *page)
{
const char *info = get_man_viewer_info(name);
if (!strcasecmp(name, "man"))
exec_man_man(info, page);
else if (!strcasecmp(name, "woman"))
exec_woman_emacs(info, page);
else if (!strcasecmp(name, "konqueror"))
exec_man_konqueror(info, page);
else if (info)
exec_man_cmd(info, page);
else
warning(_("'%s': unknown man viewer."), name);
}
static void show_man_page(const char *git_cmd)
{
struct man_viewer_list *viewer;
const char *page = cmd_to_page(git_cmd);
const char *fallback = getenv("GIT_MAN_VIEWER");
setup_man_path();
for (viewer = man_viewer_list; viewer; viewer = viewer->next)
{
exec_viewer(viewer->name, page); /* will return when unable */
}
if (fallback)
exec_viewer(fallback, page);
exec_viewer("man", page);
die(_("no man viewer handled the request"));
}
static void show_info_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
die(_("no info viewer handled the request"));
}
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
char *to_free = NULL;
if (!html_path)
html_path = to_free = system_path(GIT_HTML_PATH);
/* Check that we have a git documentation directory. */
if (!strstr(html_path, "://")) {
if (stat(mkpath("%s/git.html", html_path), &st)
|| !S_ISREG(st.st_mode))
die("'%s': not a documentation directory.", html_path);
}
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
free(to_free);
}
static void open_html(const char *path)
{
execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
}
static void show_html_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
struct strbuf page_path; /* it leaks but we exec bellow */
get_html_page_path(&page_path, page);
open_html(page_path.buf);
}
static const char *check_git_cmd(const char* cmd)
{
char *alias;
if (is_git_command(cmd))
return cmd;
alias = alias_lookup(cmd);
if (alias) {
const char **argv;
int count;
/*
* handle_builtin() in git.c rewrites "git cmd --help"
* to "git help --exclude-guides cmd", so we can use
* exclude_guides to distinguish "git cmd --help" from
* "git help cmd". In the latter case, or if cmd is an
* alias for a shell command, just print the alias
* definition.
*/
if (!exclude_guides || alias[0] == '!') {
printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
free(alias);
exit(0);
}
/*
* Otherwise, we pretend that the command was "git
* word0 --help". We use split_cmdline() to get the
* first word of the alias, to ensure that we use the
* same rules as when the alias is actually
* used. split_cmdline() modifies alias in-place.
*/
fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias);
count = split_cmdline(alias, &argv);
if (count < 0)
die(_("bad alias.%s string: %s"), cmd,
split_cmdline_strerror(count));
free(argv);
UNLEAK(alias);
return alias;
}
if (exclude_guides)
return help_unknown_cmd(cmd);
return cmd;
}
int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
enum help_format parsed_help_format;
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
parsed_help_format = help_format;
if (show_all) {
git_config(git_help_config, NULL);
if (verbose) {
setup_pager();
list_all_cmds_help();
return 0;
}
printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
load_command_list("git-", &main_cmds, &other_cmds);
list_commands(colopts, &main_cmds, &other_cmds);
}
if (show_config) {
int for_human = show_config == 1;
if (!for_human) {
list_config_help(for_human);
return 0;
}
setup_pager();
list_config_help(for_human);
printf("\n%s\n", _("'git help config' for more information"));
return 0;
}
if (show_guides)
list_common_guides_help();
if (show_all || show_guides) {
printf("%s\n", _(git_more_info_string));
/*
* We're done. Ignore any remaining args
*/
return 0;
}
if (!argv[0]) {
printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
list_common_cmds_help();
printf("\n%s\n", _(git_more_info_string));
return 0;
}
setup_git_directory_gently(&nongit);
git_config(git_help_config, NULL);
if (parsed_help_format != HELP_FORMAT_NONE)
help_format = parsed_help_format;
if (help_format == HELP_FORMAT_NONE)
help_format = parse_help_format(DEFAULT_HELP_FORMAT);
argv[0] = check_git_cmd(argv[0]);
switch (help_format) {
case HELP_FORMAT_NONE:
case HELP_FORMAT_MAN:
show_man_page(argv[0]);
break;
case HELP_FORMAT_INFO:
show_info_page(argv[0]);
break;
case HELP_FORMAT_WEB:
show_html_page(argv[0]);
break;
}
return 0;
}

1849
third_party/git/builtin/index-pack.c vendored Normal file

File diff suppressed because it is too large Load diff

601
third_party/git/builtin/init-db.c vendored Normal file
View file

@ -0,0 +1,601 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
#include "config.h"
#include "refs.h"
#include "builtin.h"
#include "exec-cmd.h"
#include "parse-options.h"
#ifndef DEFAULT_GIT_TEMPLATE_DIR
#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
#endif
#ifdef NO_TRUSTABLE_FILEMODE
#define TEST_FILEMODE 0
#else
#define TEST_FILEMODE 1
#endif
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
static const char *init_db_template_dir;
static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
DIR *dir)
{
size_t path_baselen = path->len;
size_t template_baselen = template_path->len;
struct dirent *de;
/* Note: if ".git/hooks" file exists in the repository being
* re-initialized, /etc/core-git/templates/hooks/update would
* cause "git init" to fail here. I think this is sane but
* it means that the set of templates we ship by default, along
* with the way the namespace under .git/ is organized, should
* be really carefully chosen.
*/
safe_create_dir(path->buf, 1);
while ((de = readdir(dir)) != NULL) {
struct stat st_git, st_template;
int exists = 0;
strbuf_setlen(path, path_baselen);
strbuf_setlen(template_path, template_baselen);
if (de->d_name[0] == '.')
continue;
strbuf_addstr(path, de->d_name);
strbuf_addstr(template_path, de->d_name);
if (lstat(path->buf, &st_git)) {
if (errno != ENOENT)
die_errno(_("cannot stat '%s'"), path->buf);
}
else
exists = 1;
if (lstat(template_path->buf, &st_template))
die_errno(_("cannot stat template '%s'"), template_path->buf);
if (S_ISDIR(st_template.st_mode)) {
DIR *subdir = opendir(template_path->buf);
if (!subdir)
die_errno(_("cannot opendir '%s'"), template_path->buf);
strbuf_addch(path, '/');
strbuf_addch(template_path, '/');
copy_templates_1(path, template_path, subdir);
closedir(subdir);
}
else if (exists)
continue;
else if (S_ISLNK(st_template.st_mode)) {
struct strbuf lnk = STRBUF_INIT;
if (strbuf_readlink(&lnk, template_path->buf,
st_template.st_size) < 0)
die_errno(_("cannot readlink '%s'"), template_path->buf);
if (symlink(lnk.buf, path->buf))
die_errno(_("cannot symlink '%s' '%s'"),
lnk.buf, path->buf);
strbuf_release(&lnk);
}
else if (S_ISREG(st_template.st_mode)) {
if (copy_file(path->buf, template_path->buf, st_template.st_mode))
die_errno(_("cannot copy '%s' to '%s'"),
template_path->buf, path->buf);
}
else
error(_("ignoring template %s"), template_path->buf);
}
}
static void copy_templates(const char *template_dir)
{
struct strbuf path = STRBUF_INIT;
struct strbuf template_path = STRBUF_INIT;
size_t template_len;
struct repository_format template_format = REPOSITORY_FORMAT_INIT;
struct strbuf err = STRBUF_INIT;
DIR *dir;
char *to_free = NULL;
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
if (!template_dir)
template_dir = init_db_template_dir;
if (!template_dir)
template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
if (!template_dir[0]) {
free(to_free);
return;
}
strbuf_addstr(&template_path, template_dir);
strbuf_complete(&template_path, '/');
template_len = template_path.len;
dir = opendir(template_path.buf);
if (!dir) {
warning(_("templates not found in %s"), template_dir);
goto free_return;
}
/* Make sure that template is from the correct vintage */
strbuf_addstr(&template_path, "config");
read_repository_format(&template_format, template_path.buf);
strbuf_setlen(&template_path, template_len);
/*
* No mention of version at all is OK, but anything else should be
* verified.
*/
if (template_format.version >= 0 &&
verify_repository_format(&template_format, &err) < 0) {
warning(_("not copying templates from '%s': %s"),
template_dir, err.buf);
strbuf_release(&err);
goto close_free_return;
}
strbuf_addstr(&path, get_git_common_dir());
strbuf_complete(&path, '/');
copy_templates_1(&path, &template_path, dir);
close_free_return:
closedir(dir);
free_return:
free(to_free);
strbuf_release(&path);
strbuf_release(&template_path);
clear_repository_format(&template_format);
}
static int git_init_db_config(const char *k, const char *v, void *cb)
{
if (!strcmp(k, "init.templatedir"))
return git_config_pathname(&init_db_template_dir, k, v);
if (starts_with(k, "core."))
return platform_core_config(k, v, cb);
return 0;
}
/*
* If the git_dir is not directly inside the working tree, then git will not
* find it by default, and we need to set the worktree explicitly.
*/
static int needs_work_tree_config(const char *git_dir, const char *work_tree)
{
if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git"))
return 0;
if (skip_prefix(git_dir, work_tree, &git_dir) &&
!strcmp(git_dir, "/.git"))
return 0;
return 1;
}
static int create_default_files(const char *template_path,
const char *original_git_dir)
{
struct stat st1;
struct strbuf buf = STRBUF_INIT;
char *path;
char repo_version_string[10];
char junk[2];
int reinit;
int filemode;
struct strbuf err = STRBUF_INIT;
/* Just look for `init.templatedir` */
init_db_template_dir = NULL; /* re-set in case it was set before */
git_config(git_init_db_config, NULL);
/*
* First copy the templates -- we might have the default
* config file there, in which case we would want to read
* from it after installing.
*
* Before reading that config, we also need to clear out any cached
* values (since we've just potentially changed what's available on
* disk).
*/
copy_templates(template_path);
git_config_clear();
reset_shared_repository();
git_config(git_default_config, NULL);
/*
* We must make sure command-line options continue to override any
* values we might have just re-read from the config.
*/
is_bare_repository_cfg = init_is_bare_repository;
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
/*
* We would have created the above under user's umask -- under
* shared-repository settings, we would need to fix them up.
*/
if (get_shared_repository()) {
adjust_shared_perm(get_git_dir());
}
/*
* We need to create a "refs" dir in any case so that older
* versions of git can tell that this is a repository.
*/
safe_create_dir(git_path("refs"), 1);
adjust_shared_perm(git_path("refs"));
if (refs_init_db(&err))
die("failed to set up refs db: %s", err.buf);
/*
* Create the default symlink from ".git/HEAD" to the "master"
* branch, if it does not exist yet.
*/
path = git_path_buf(&buf, "HEAD");
reinit = (!access(path, R_OK)
|| readlink(path, junk, sizeof(junk)-1) != -1);
if (!reinit) {
if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
exit(1);
}
/* This forces creation of new config file */
xsnprintf(repo_version_string, sizeof(repo_version_string),
"%d", GIT_REPO_VERSION);
git_config_set("core.repositoryformatversion", repo_version_string);
/* Check filemode trustability */
path = git_path_buf(&buf, "config");
filemode = TEST_FILEMODE;
if (TEST_FILEMODE && !lstat(path, &st1)) {
struct stat st2;
filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
!lstat(path, &st2) &&
st1.st_mode != st2.st_mode &&
!chmod(path, st1.st_mode));
if (filemode && !reinit && (st1.st_mode & S_IXUSR))
filemode = 0;
}
git_config_set("core.filemode", filemode ? "true" : "false");
if (is_bare_repository())
git_config_set("core.bare", "true");
else {
const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
if (log_all_ref_updates == LOG_REFS_UNSET)
git_config_set("core.logallrefupdates", "true");
if (needs_work_tree_config(original_git_dir, work_tree))
git_config_set("core.worktree", work_tree);
}
if (!reinit) {
/* Check if symlink is supported in the work tree */
path = git_path_buf(&buf, "tXXXXXX");
if (!close(xmkstemp(path)) &&
!unlink(path) &&
!symlink("testing", path) &&
!lstat(path, &st1) &&
S_ISLNK(st1.st_mode))
unlink(path); /* good */
else
git_config_set("core.symlinks", "false");
/* Check if the filesystem is case-insensitive */
path = git_path_buf(&buf, "CoNfIg");
if (!access(path, F_OK))
git_config_set("core.ignorecase", "true");
probe_utf8_pathname_composition();
}
strbuf_release(&buf);
return reinit;
}
static void create_object_directory(void)
{
struct strbuf path = STRBUF_INIT;
size_t baselen;
strbuf_addstr(&path, get_object_directory());
baselen = path.len;
safe_create_dir(path.buf, 1);
strbuf_setlen(&path, baselen);
strbuf_addstr(&path, "/pack");
safe_create_dir(path.buf, 1);
strbuf_setlen(&path, baselen);
strbuf_addstr(&path, "/info");
safe_create_dir(path.buf, 1);
strbuf_release(&path);
}
static void separate_git_dir(const char *git_dir, const char *git_link)
{
struct stat st;
if (!stat(git_link, &st)) {
const char *src;
if (S_ISREG(st.st_mode))
src = read_gitfile(git_link);
else if (S_ISDIR(st.st_mode))
src = git_link;
else
die(_("unable to handle file type %d"), (int)st.st_mode);
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
}
write_file(git_link, "gitdir: %s", git_dir);
}
int init_db(const char *git_dir, const char *real_git_dir,
const char *template_dir, unsigned int flags)
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
char *original_git_dir = real_pathdup(git_dir, 1);
if (real_git_dir) {
struct stat st;
if (!exist_ok && !stat(git_dir, &st))
die(_("%s already exists"), git_dir);
if (!exist_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
set_git_dir(real_path(real_git_dir));
git_dir = get_git_dir();
separate_git_dir(git_dir, original_git_dir);
}
else {
set_git_dir(real_path(git_dir));
git_dir = get_git_dir();
}
startup_info->have_repository = 1;
/* Just look for `core.hidedotfiles` */
git_config(git_init_db_config, NULL);
safe_create_dir(git_dir, 0);
init_is_bare_repository = is_bare_repository();
/* Check to see if the repository version is right.
* Note that a newly created repository does not have
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
check_repository_format();
reinit = create_default_files(template_dir, original_git_dir);
create_object_directory();
if (get_shared_repository()) {
char buf[10];
/* We do not spell "group" and such, so that
* the configuration can be read by older version
* of git. Note, we use octal numbers for new share modes,
* and compatibility values for PERM_GROUP and
* PERM_EVERYBODY.
*/
if (get_shared_repository() < 0)
/* force to the mode value */
xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository());
else if (get_shared_repository() == PERM_GROUP)
xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
else if (get_shared_repository() == PERM_EVERYBODY)
xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
else
BUG("invalid value for shared_repository");
git_config_set("core.sharedrepository", buf);
git_config_set("receive.denyNonFastforwards", "true");
}
if (!(flags & INIT_DB_QUIET)) {
int len = strlen(git_dir);
if (reinit)
printf(get_shared_repository()
? _("Reinitialized existing shared Git repository in %s%s\n")
: _("Reinitialized existing Git repository in %s%s\n"),
git_dir, len && git_dir[len-1] != '/' ? "/" : "");
else
printf(get_shared_repository()
? _("Initialized empty shared Git repository in %s%s\n")
: _("Initialized empty Git repository in %s%s\n"),
git_dir, len && git_dir[len-1] != '/' ? "/" : "");
}
free(original_git_dir);
return 0;
}
static int guess_repository_type(const char *git_dir)
{
const char *slash;
char *cwd;
int cwd_is_git_dir;
/*
* "GIT_DIR=. git init" is always bare.
* "GIT_DIR=`pwd` git init" too.
*/
if (!strcmp(".", git_dir))
return 1;
cwd = xgetcwd();
cwd_is_git_dir = !strcmp(git_dir, cwd);
free(cwd);
if (cwd_is_git_dir)
return 1;
/*
* "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
*/
if (!strcmp(git_dir, ".git"))
return 0;
slash = strrchr(git_dir, '/');
if (slash && !strcmp(slash, "/.git"))
return 0;
/*
* Otherwise it is often bare. At this point
* we are just guessing.
*/
return 1;
}
static int shared_callback(const struct option *opt, const char *arg, int unset)
{
BUG_ON_OPT_NEG(unset);
*((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
return 0;
}
static const char *const init_db_usage[] = {
N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [<directory>]"),
NULL
};
/*
* If you want to, you can share the DB area with any number of branches.
* That has advantages: you can save space by sharing all the SHA1 objects.
* On the other hand, it might just make lookup slower and messier. You
* be the judge. The default case is to have one DB per managed directory.
*/
int cmd_init_db(int argc, const char **argv, const char *prefix)
{
const char *git_dir;
const char *real_git_dir = NULL;
const char *work_tree;
const char *template_dir = NULL;
unsigned int flags = 0;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, N_("template-directory"),
N_("directory from which templates will be used")),
OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
N_("create a bare repository"), 1),
{ OPTION_CALLBACK, 0, "shared", &init_shared_repository,
N_("permissions"),
N_("specify that the git repository is to be shared amongst several users"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
if (real_git_dir && !is_absolute_path(real_git_dir))
real_git_dir = real_pathdup(real_git_dir, 1);
if (template_dir && *template_dir && !is_absolute_path(template_dir))
template_dir = absolute_pathdup(template_dir);
if (argc == 1) {
int mkdir_tried = 0;
retry:
if (chdir(argv[0]) < 0) {
if (!mkdir_tried) {
int saved;
/*
* At this point we haven't read any configuration,
* and we know shared_repository should always be 0;
* but just in case we play safe.
*/
saved = get_shared_repository();
set_shared_repository(0);
switch (safe_create_leading_directories_const(argv[0])) {
case SCLD_OK:
case SCLD_PERMS:
break;
case SCLD_EXISTS:
errno = EEXIST;
/* fallthru */
default:
die_errno(_("cannot mkdir %s"), argv[0]);
break;
}
set_shared_repository(saved);
if (mkdir(argv[0], 0777) < 0)
die_errno(_("cannot mkdir %s"), argv[0]);
mkdir_tried = 1;
goto retry;
}
die_errno(_("cannot chdir to %s"), argv[0]);
}
} else if (0 < argc) {
usage(init_db_usage[0]);
}
if (is_bare_repository_cfg == 1) {
char *cwd = xgetcwd();
setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0);
free(cwd);
}
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
/*
* GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
* without --bare. Catch the error early.
*/
git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT));
work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT));
if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
die(_("%s (or --work-tree=<directory>) not allowed without "
"specifying %s (or --git-dir=<directory>)"),
GIT_WORK_TREE_ENVIRONMENT,
GIT_DIR_ENVIRONMENT);
/*
* Set up the default .git directory contents
*/
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
if (is_bare_repository_cfg < 0)
is_bare_repository_cfg = guess_repository_type(git_dir);
if (!is_bare_repository_cfg) {
const char *git_dir_parent = strrchr(git_dir, '/');
if (git_dir_parent) {
char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
git_work_tree_cfg = real_pathdup(rel, 1);
free(rel);
}
if (!git_work_tree_cfg)
git_work_tree_cfg = xgetcwd();
if (work_tree)
set_git_work_tree(work_tree);
else
set_git_work_tree(git_work_tree_cfg);
if (access(get_git_work_tree(), X_OK))
die_errno (_("Cannot access work tree '%s'"),
get_git_work_tree());
}
else {
if (work_tree)
set_git_work_tree(work_tree);
}
UNLEAK(real_git_dir);
UNLEAK(git_dir);
UNLEAK(work_tree);
flags |= INIT_DB_EXIST_OK;
return init_db(git_dir, real_git_dir, template_dir, flags);
}

View file

@ -0,0 +1,140 @@
/*
* Builtin "git interpret-trailers"
*
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
*
*/
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
#include "string-list.h"
#include "trailer.h"
#include "config.h"
static const char * const git_interpret_trailers_usage[] = {
N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
NULL
};
static enum trailer_where where;
static enum trailer_if_exists if_exists;
static enum trailer_if_missing if_missing;
static int option_parse_where(const struct option *opt,
const char *arg, int unset)
{
return trailer_set_where(&where, arg);
}
static int option_parse_if_exists(const struct option *opt,
const char *arg, int unset)
{
return trailer_set_if_exists(&if_exists, arg);
}
static int option_parse_if_missing(const struct option *opt,
const char *arg, int unset)
{
return trailer_set_if_missing(&if_missing, arg);
}
static void new_trailers_clear(struct list_head *trailers)
{
struct list_head *pos, *tmp;
struct new_trailer_item *item;
list_for_each_safe(pos, tmp, trailers) {
item = list_entry(pos, struct new_trailer_item, list);
list_del(pos);
free(item);
}
}
static int option_parse_trailer(const struct option *opt,
const char *arg, int unset)
{
struct list_head *trailers = opt->value;
struct new_trailer_item *item;
if (unset) {
new_trailers_clear(trailers);
return 0;
}
if (!arg)
return -1;
item = xmalloc(sizeof(*item));
item->text = arg;
item->where = where;
item->if_exists = if_exists;
item->if_missing = if_missing;
list_add_tail(&item->list, trailers);
return 0;
}
static int parse_opt_parse(const struct option *opt, const char *arg,
int unset)
{
struct process_trailer_options *v = opt->value;
v->only_trailers = 1;
v->only_input = 1;
v->unfold = 1;
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
return 0;
}
int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
{
struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
LIST_HEAD(trailers);
struct option options[] = {
OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")),
OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")),
OPT_CALLBACK(0, "where", NULL, N_("action"),
N_("where to place the new trailer"), option_parse_where),
OPT_CALLBACK(0, "if-exists", NULL, N_("action"),
N_("action if trailer already exists"), option_parse_if_exists),
OPT_CALLBACK(0, "if-missing", NULL, N_("action"),
N_("action if trailer is missing"), option_parse_if_missing),
OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")),
OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")),
OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")),
{ OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse },
OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")),
OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"),
N_("trailer(s) to add"), option_parse_trailer),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
git_interpret_trailers_usage, 0);
if (opts.only_input && !list_empty(&trailers))
usage_msg_opt(
_("--trailer with --only-input does not make sense"),
git_interpret_trailers_usage,
options);
if (argc) {
int i;
for (i = 0; i < argc; i++)
process_trailers(argv[i], &opts, &trailers);
} else {
if (opts.in_place)
die(_("no input file given for in-place editing"));
process_trailers(NULL, &opts, &trailers);
}
new_trailers_clear(&trailers);
return 0;
}

2134
third_party/git/builtin/log.c vendored Normal file

File diff suppressed because it is too large Load diff

692
third_party/git/builtin/ls-files.c vendored Normal file
View file

@ -0,0 +1,692 @@
/*
* This merges the file listing in the directory cache index
* with the actual working directory list, and shows different
* combinations of the two.
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
#include "repository.h"
#include "config.h"
#include "quote.h"
#include "dir.h"
#include "builtin.h"
#include "tree.h"
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
#include "pathspec.h"
#include "run-command.h"
#include "submodule.h"
#include "submodule-config.h"
static int abbrev;
static int show_deleted;
static int show_cached;
static int show_others;
static int show_stage;
static int show_unmerged;
static int show_resolve_undo;
static int show_modified;
static int show_killed;
static int show_valid_bit;
static int show_fsmonitor_bit;
static int line_terminator = '\n';
static int debug_mode;
static int show_eol;
static int recurse_submodules;
static const char *prefix;
static int max_prefix_len;
static int prefix_len;
static struct pathspec pathspec;
static int error_unmatch;
static char *ps_matched;
static const char *with_tree;
static int exc_given;
static int exclude_args;
static const char *tag_cached = "";
static const char *tag_unmerged = "";
static const char *tag_removed = "";
static const char *tag_other = "";
static const char *tag_killed = "";
static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
static void write_eolinfo(const struct index_state *istate,
const struct cache_entry *ce, const char *path)
{
if (show_eol) {
struct stat st;
const char *i_txt = "";
const char *w_txt = "";
const char *a_txt = get_convert_attr_ascii(istate, path);
if (ce && S_ISREG(ce->ce_mode))
i_txt = get_cached_convert_stats_ascii(istate,
ce->name);
if (!lstat(path, &st) && S_ISREG(st.st_mode))
w_txt = get_wt_convert_stats_ascii(path);
printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt);
}
}
static void write_name(const char *name)
{
/*
* With "--full-name", prefix_len=0; this caller needs to pass
* an empty string in that case (a NULL is good for "").
*/
write_name_quoted_relative(name, prefix_len ? prefix : NULL,
stdout, line_terminator);
}
static const char *get_tag(const struct cache_entry *ce, const char *tag)
{
static char alttag[4];
if (tag && *tag && ((show_valid_bit && (ce->ce_flags & CE_VALID)) ||
(show_fsmonitor_bit && (ce->ce_flags & CE_FSMONITOR_VALID)))) {
memcpy(alttag, tag, 3);
if (isalpha(tag[0])) {
alttag[0] = tolower(tag[0]);
} else if (tag[0] == '?') {
alttag[0] = '!';
} else {
alttag[0] = 'v';
alttag[1] = tag[0];
alttag[2] = ' ';
alttag[3] = 0;
}
tag = alttag;
}
return tag;
}
static void print_debug(const struct cache_entry *ce)
{
if (debug_mode) {
const struct stat_data *sd = &ce->ce_stat_data;
printf(" ctime: %u:%u\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
printf(" mtime: %u:%u\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
printf(" dev: %u\tino: %u\n", sd->sd_dev, sd->sd_ino);
printf(" uid: %u\tgid: %u\n", sd->sd_uid, sd->sd_gid);
printf(" size: %u\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
static void show_dir_entry(const struct index_state *istate,
const char *tag, struct dir_entry *ent)
{
int len = max_prefix_len;
if (len > ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
if (!dir_path_match(istate, ent, &pathspec, len, ps_matched))
return;
fputs(tag, stdout);
write_eolinfo(istate, NULL, ent->name);
write_name(ent->name);
}
static void show_other_files(const struct index_state *istate,
const struct dir_struct *dir)
{
int i;
for (i = 0; i < dir->nr; i++) {
struct dir_entry *ent = dir->entries[i];
if (!index_name_is_other(istate, ent->name, ent->len))
continue;
show_dir_entry(istate, tag_other, ent);
}
}
static void show_killed_files(const struct index_state *istate,
const struct dir_struct *dir)
{
int i;
for (i = 0; i < dir->nr; i++) {
struct dir_entry *ent = dir->entries[i];
char *cp, *sp;
int pos, len, killed = 0;
for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
sp = strchr(cp, '/');
if (!sp) {
/* If ent->name is prefix of an entry in the
* cache, it will be killed.
*/
pos = index_name_pos(istate, ent->name, ent->len);
if (0 <= pos)
BUG("killed-file %.*s not found",
ent->len, ent->name);
pos = -pos - 1;
while (pos < istate->cache_nr &&
ce_stage(istate->cache[pos]))
pos++; /* skip unmerged */
if (istate->cache_nr <= pos)
break;
/* pos points at a name immediately after
* ent->name in the cache. Does it expect
* ent->name to be a directory?
*/
len = ce_namelen(istate->cache[pos]);
if ((ent->len < len) &&
!strncmp(istate->cache[pos]->name,
ent->name, ent->len) &&
istate->cache[pos]->name[ent->len] == '/')
killed = 1;
break;
}
if (0 <= index_name_pos(istate, ent->name, sp - ent->name)) {
/* If any of the leading directories in
* ent->name is registered in the cache,
* ent->name will be killed.
*/
killed = 1;
break;
}
}
if (killed)
show_dir_entry(istate, tag_killed, dir->entries[i]);
}
}
static void show_files(struct repository *repo, struct dir_struct *dir);
static void show_submodule(struct repository *superproject,
struct dir_struct *dir, const char *path)
{
struct repository subrepo;
const struct submodule *sub = submodule_from_path(superproject,
&null_oid, path);
if (repo_submodule_init(&subrepo, superproject, sub))
return;
if (repo_read_index(&subrepo) < 0)
die("index file corrupt");
show_files(&subrepo, dir);
repo_clear(&subrepo);
}
static void show_ce(struct repository *repo, struct dir_struct *dir,
const struct cache_entry *ce, const char *fullname,
const char *tag)
{
if (max_prefix_len > strlen(fullname))
die("git ls-files: internal error - cache entry not superset of prefix");
if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
is_submodule_active(repo, ce->name)) {
show_submodule(repo, dir, ce->name);
} else if (match_pathspec(repo->index, &pathspec, fullname, strlen(fullname),
max_prefix_len, ps_matched,
S_ISDIR(ce->ce_mode) ||
S_ISGITLINK(ce->ce_mode))) {
tag = get_tag(ce, tag);
if (!show_stage) {
fputs(tag, stdout);
} else {
printf("%s%06o %s %d\t",
tag,
ce->ce_mode,
find_unique_abbrev(&ce->oid, abbrev),
ce_stage(ce));
}
write_eolinfo(repo->index, ce, fullname);
write_name(fullname);
print_debug(ce);
}
}
static void show_ru_info(const struct index_state *istate)
{
struct string_list_item *item;
if (!istate->resolve_undo)
return;
for_each_string_list_item(item, istate->resolve_undo) {
const char *path = item->string;
struct resolve_undo_info *ui = item->util;
int i, len;
len = strlen(path);
if (len < max_prefix_len)
continue; /* outside of the prefix */
if (!match_pathspec(istate, &pathspec, path, len,
max_prefix_len, ps_matched, 0))
continue; /* uninterested */
for (i = 0; i < 3; i++) {
if (!ui->mode[i])
continue;
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
find_unique_abbrev(&ui->oid[i], abbrev),
i + 1);
write_name(path);
}
}
}
static int ce_excluded(struct dir_struct *dir, struct index_state *istate,
const char *fullname, const struct cache_entry *ce)
{
int dtype = ce_to_dtype(ce);
return is_excluded(dir, istate, fullname, &dtype);
}
static void construct_fullname(struct strbuf *out, const struct repository *repo,
const struct cache_entry *ce)
{
strbuf_reset(out);
if (repo->submodule_prefix)
strbuf_addstr(out, repo->submodule_prefix);
strbuf_addstr(out, ce->name);
}
static void show_files(struct repository *repo, struct dir_struct *dir)
{
int i;
struct strbuf fullname = STRBUF_INIT;
/* For cached/deleted files we don't need to even do the readdir */
if (show_others || show_killed) {
if (!show_others)
dir->flags |= DIR_COLLECT_KILLED_ONLY;
fill_directory(dir, repo->index, &pathspec);
if (show_others)
show_other_files(repo->index, dir);
if (show_killed)
show_killed_files(repo->index, dir);
}
if (show_cached || show_stage) {
for (i = 0; i < repo->index->cache_nr; i++) {
const struct cache_entry *ce = repo->index->cache[i];
construct_fullname(&fullname, repo, ce);
if ((dir->flags & DIR_SHOW_IGNORED) &&
!ce_excluded(dir, repo->index, fullname.buf, ce))
continue;
if (show_unmerged && !ce_stage(ce))
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
show_ce(repo, dir, ce, fullname.buf,
ce_stage(ce) ? tag_unmerged :
(ce_skip_worktree(ce) ? tag_skip_worktree :
tag_cached));
}
}
if (show_deleted || show_modified) {
for (i = 0; i < repo->index->cache_nr; i++) {
const struct cache_entry *ce = repo->index->cache[i];
struct stat st;
int err;
construct_fullname(&fullname, repo, ce);
if ((dir->flags & DIR_SHOW_IGNORED) &&
!ce_excluded(dir, repo->index, fullname.buf, ce))
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
if (ce_skip_worktree(ce))
continue;
err = lstat(fullname.buf, &st);
if (show_deleted && err)
show_ce(repo, dir, ce, fullname.buf, tag_removed);
if (show_modified && ie_modified(repo->index, ce, &st, 0))
show_ce(repo, dir, ce, fullname.buf, tag_modified);
}
}
strbuf_release(&fullname);
}
/*
* Prune the index to only contain stuff starting with "prefix"
*/
static void prune_index(struct index_state *istate,
const char *prefix, size_t prefixlen)
{
int pos;
unsigned int first, last;
if (!prefix || !istate->cache_nr)
return;
pos = index_name_pos(istate, prefix, prefixlen);
if (pos < 0)
pos = -pos-1;
first = pos;
last = istate->cache_nr;
while (last > first) {
int next = first + ((last - first) >> 1);
const struct cache_entry *ce = istate->cache[next];
if (!strncmp(ce->name, prefix, prefixlen)) {
first = next+1;
continue;
}
last = next;
}
MOVE_ARRAY(istate->cache, istate->cache + pos, last - pos);
istate->cache_nr = last - pos;
}
static int get_common_prefix_len(const char *common_prefix)
{
int common_prefix_len;
if (!common_prefix)
return 0;
common_prefix_len = strlen(common_prefix);
/*
* If the prefix has a trailing slash, strip it so that submodules wont
* be pruned from the index.
*/
if (common_prefix[common_prefix_len - 1] == '/')
common_prefix_len--;
return common_prefix_len;
}
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
* squash them down to stage #0. This is used for
* --error-unmatch to list and check the path patterns
* that were given from the command line. We are not
* going to write this index out.
*/
void overlay_tree_on_index(struct index_state *istate,
const char *tree_name, const char *prefix)
{
struct tree *tree;
struct object_id oid;
struct pathspec pathspec;
struct cache_entry *last_stage0 = NULL;
int i;
if (get_oid(tree_name, &oid))
die("tree-ish %s not found.", tree_name);
tree = parse_tree_indirect(&oid);
if (!tree)
die("bad tree-ish %s", tree_name);
/* Hoist the unmerged entries up to stage #3 to make room */
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
continue;
ce->ce_flags |= CE_STAGEMASK;
}
if (prefix) {
static const char *(matchbuf[1]);
matchbuf[0] = NULL;
parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
memset(&pathspec, 0, sizeof(pathspec));
if (read_tree(the_repository, tree, 1, &pathspec, istate))
die("unable to read tree entries %s", tree_name);
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
switch (ce_stage(ce)) {
case 0:
last_stage0 = ce;
/* fallthru */
default:
continue;
case 1:
/*
* If there is stage #0 entry for this, we do not
* need to show it. We use CE_UPDATE bit to mark
* such an entry.
*/
if (last_stage0 &&
!strcmp(last_stage0->name, ce->name))
ce->ce_flags |= CE_UPDATE;
}
}
}
static const char * const ls_files_usage[] = {
N_("git ls-files [<options>] [<file>...]"),
NULL
};
static int option_parse_exclude(const struct option *opt,
const char *arg, int unset)
{
struct string_list *exclude_list = opt->value;
BUG_ON_OPT_NEG(unset);
exc_given = 1;
string_list_append(exclude_list, arg);
return 0;
}
static int option_parse_exclude_from(const struct option *opt,
const char *arg, int unset)
{
struct dir_struct *dir = opt->value;
BUG_ON_OPT_NEG(unset);
exc_given = 1;
add_excludes_from_file(dir, arg);
return 0;
}
static int option_parse_exclude_standard(const struct option *opt,
const char *arg, int unset)
{
struct dir_struct *dir = opt->value;
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
exc_given = 1;
setup_standard_excludes(dir);
return 0;
}
int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{
int require_work_tree = 0, show_tag = 0, i;
const char *max_prefix;
struct dir_struct dir;
struct exclude_list *el;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct option builtin_ls_files_options[] = {
/* Think twice before adding "--nul" synonym to this */
OPT_SET_INT('z', NULL, &line_terminator,
N_("paths are separated with NUL character"), '\0'),
OPT_BOOL('t', NULL, &show_tag,
N_("identify the file status with tags")),
OPT_BOOL('v', NULL, &show_valid_bit,
N_("use lowercase letters for 'assume unchanged' files")),
OPT_BOOL('f', NULL, &show_fsmonitor_bit,
N_("use lowercase letters for 'fsmonitor clean' files")),
OPT_BOOL('c', "cached", &show_cached,
N_("show cached files in the output (default)")),
OPT_BOOL('d', "deleted", &show_deleted,
N_("show deleted files in the output")),
OPT_BOOL('m', "modified", &show_modified,
N_("show modified files in the output")),
OPT_BOOL('o', "others", &show_others,
N_("show other files in the output")),
OPT_BIT('i', "ignored", &dir.flags,
N_("show ignored files in the output"),
DIR_SHOW_IGNORED),
OPT_BOOL('s', "stage", &show_stage,
N_("show staged contents' object name in the output")),
OPT_BOOL('k', "killed", &show_killed,
N_("show files on the filesystem that need to be removed")),
OPT_BIT(0, "directory", &dir.flags,
N_("show 'other' directories' names only"),
DIR_SHOW_OTHER_DIRECTORIES),
OPT_BOOL(0, "eol", &show_eol, N_("show line endings of files")),
OPT_NEGBIT(0, "empty-directory", &dir.flags,
N_("don't show empty directories"),
DIR_HIDE_EMPTY_DIRECTORIES),
OPT_BOOL('u', "unmerged", &show_unmerged,
N_("show unmerged files in the output")),
OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
N_("show resolve-undo information")),
{ OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
N_("skip files matching pattern"),
PARSE_OPT_NONEG, option_parse_exclude },
{ OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
N_("exclude patterns are read from <file>"),
PARSE_OPT_NONEG, option_parse_exclude_from },
OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
N_("read additional per-directory exclude patterns in <file>")),
{ OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
N_("add the standard git exclusions"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
option_parse_exclude_standard },
OPT_SET_INT_F(0, "full-name", &prefix_len,
N_("make the output relative to the project top directory"),
0, PARSE_OPT_NONEG),
OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
N_("recurse through submodules")),
OPT_BOOL(0, "error-unmatch", &error_unmatch,
N_("if any <file> is not in the index, treat this as an error")),
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
N_("pretend that paths removed since <tree-ish> are still present")),
OPT__ABBREV(&abbrev),
OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(ls_files_usage, builtin_ls_files_options);
memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
git_config(git_default_config, NULL);
if (repo_read_index(the_repository) < 0)
die("index file corrupt");
argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
ls_files_usage, 0);
el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
for (i = 0; i < exclude_list.nr; i++) {
add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
}
if (show_tag || show_valid_bit || show_fsmonitor_bit) {
tag_cached = "H ";
tag_unmerged = "M ";
tag_removed = "R ";
tag_modified = "C ";
tag_other = "? ";
tag_killed = "K ";
tag_skip_worktree = "S ";
tag_resolve_undo = "U ";
}
if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
require_work_tree = 1;
if (show_unmerged)
/*
* There's no point in showing unmerged unless
* you also show the stage information.
*/
show_stage = 1;
if (dir.exclude_per_dir)
exc_given = 1;
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
if (recurse_submodules &&
(show_stage || show_deleted || show_others || show_unmerged ||
show_killed || show_modified || show_resolve_undo || with_tree))
die("ls-files --recurse-submodules unsupported mode");
if (recurse_submodules && error_unmatch)
die("ls-files --recurse-submodules does not support "
"--error-unmatch");
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,
prefix, argv);
/*
* Find common prefix for all pathspec's
* This is used as a performance optimization which unfortunately cannot
* be done when recursing into submodules because when a pathspec is
* given which spans repository boundaries you can't simply remove the
* submodule entry because the pathspec may match something inside the
* submodule.
*/
if (recurse_submodules)
max_prefix = NULL;
else
max_prefix = common_prefix(&pathspec);
max_prefix_len = get_common_prefix_len(max_prefix);
prune_index(the_repository->index, max_prefix, max_prefix_len);
/* Treat unmatching pathspec elements as errors */
if (pathspec.nr && error_unmatch)
ps_matched = xcalloc(pathspec.nr, 1);
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
/* With no flags, we default to showing the cached files */
if (!(show_stage || show_deleted || show_others || show_unmerged ||
show_killed || show_modified || show_resolve_undo))
show_cached = 1;
if (with_tree) {
/*
* Basic sanity check; show-stages and show-unmerged
* would not make any sense with this option.
*/
if (show_stage || show_unmerged)
die("ls-files --with-tree is incompatible with -s or -u");
overlay_tree_on_index(the_repository->index, with_tree, max_prefix);
}
show_files(the_repository, &dir);
if (show_resolve_undo)
show_ru_info(the_repository->index);
if (ps_matched) {
int bad;
bad = report_path_error(ps_matched, &pathspec);
if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");
return bad ? 1 : 0;
}
UNLEAK(dir);
return 0;
}

152
third_party/git/builtin/ls-remote.c vendored Normal file
View file

@ -0,0 +1,152 @@
#include "builtin.h"
#include "cache.h"
#include "transport.h"
#include "ref-filter.h"
#include "remote.h"
#include "refs.h"
static const char * const ls_remote_usage[] = {
N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
" [-q | --quiet] [--exit-code] [--get-url]\n"
" [--symref] [<repository> [<refs>...]]"),
NULL
};
/*
* Is there one among the list of patterns that match the tail part
* of the path?
*/
static int tail_match(const char **pattern, const char *path)
{
const char *p;
char *pathbuf;
if (!pattern)
return 1; /* no restriction */
pathbuf = xstrfmt("/%s", path);
while ((p = *(pattern++)) != NULL) {
if (!wildmatch(p, pathbuf, 0)) {
free(pathbuf);
return 1;
}
}
free(pathbuf);
return 0;
}
int cmd_ls_remote(int argc, const char **argv, const char *prefix)
{
const char *dest = NULL;
unsigned flags = 0;
int get_url = 0;
int quiet = 0;
int status = 0;
int show_symref_target = 0;
const char *uploadpack = NULL;
const char **pattern = NULL;
struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
int i;
struct string_list server_options = STRING_LIST_INIT_DUP;
struct remote *remote;
struct transport *transport;
const struct ref *ref;
struct ref_array ref_array;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
struct option options[] = {
OPT__QUIET(&quiet, N_("do not print remote URL")),
OPT_STRING(0, "upload-pack", &uploadpack, N_("exec"),
N_("path of git-upload-pack on the remote host")),
{ OPTION_STRING, 0, "exec", &uploadpack, N_("exec"),
N_("path of git-upload-pack on the remote host"),
PARSE_OPT_HIDDEN },
OPT_BIT('t', "tags", &flags, N_("limit to tags"), REF_TAGS),
OPT_BIT('h', "heads", &flags, N_("limit to heads"), REF_HEADS),
OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL),
OPT_BOOL(0, "get-url", &get_url,
N_("take url.<base>.insteadOf into account")),
OPT_REF_SORT(sorting_tail),
OPT_SET_INT_F(0, "exit-code", &status,
N_("exit with exit code 2 if no matching refs are found"),
2, PARSE_OPT_NOCOMPLETE),
OPT_BOOL(0, "symref", &show_symref_target,
N_("show underlying ref in addition to the object pointed by it")),
OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
OPT_END()
};
memset(&ref_array, 0, sizeof(ref_array));
argc = parse_options(argc, argv, prefix, options, ls_remote_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
dest = argv[0];
if (argc > 1) {
int i;
pattern = xcalloc(argc, sizeof(const char *));
for (i = 1; i < argc; i++) {
pattern[i - 1] = xstrfmt("*/%s", argv[i]);
}
}
if (flags & REF_TAGS)
argv_array_push(&ref_prefixes, "refs/tags/");
if (flags & REF_HEADS)
argv_array_push(&ref_prefixes, "refs/heads/");
remote = remote_get(dest);
if (!remote) {
if (dest)
die("bad repository '%s'", dest);
die("No remote configured to list refs from.");
}
if (!remote->url_nr)
die("remote %s has no configured URL", dest);
if (get_url) {
printf("%s\n", *remote->url);
UNLEAK(sorting);
return 0;
}
transport = transport_get(remote, NULL);
if (uploadpack != NULL)
transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
if (server_options.nr)
transport->server_options = &server_options;
ref = transport_get_remote_refs(transport, &ref_prefixes);
if (transport_disconnect(transport)) {
UNLEAK(sorting);
return 1;
}
if (!dest && !quiet)
fprintf(stderr, "From %s\n", *remote->url);
for ( ; ref; ref = ref->next) {
struct ref_array_item *item;
if (!check_ref_type(ref, flags))
continue;
if (!tail_match(pattern, ref->name))
continue;
item = ref_array_push(&ref_array, ref->name, &ref->old_oid);
item->symref = xstrdup_or_null(ref->symref);
}
if (sorting)
ref_array_sort(sorting, &ref_array);
for (i = 0; i < ref_array.nr; i++) {
const struct ref_array_item *ref = ref_array.items[i];
if (show_symref_target && ref->symref)
printf("ref: %s\t%s\n", ref->symref, ref->refname);
printf("%s\t%s\n", oid_to_hex(&ref->objectname), ref->refname);
status = 0; /* we found something */
}
UNLEAK(sorting);
ref_array_clear(&ref_array);
return status;
}

190
third_party/git/builtin/ls-tree.c vendored Normal file
View file

@ -0,0 +1,190 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
#include "config.h"
#include "object-store.h"
#include "blob.h"
#include "tree.h"
#include "commit.h"
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
#include "pathspec.h"
static int line_termination = '\n';
#define LS_RECURSIVE 1
#define LS_TREE_ONLY 2
#define LS_SHOW_TREES 4
#define LS_NAME_ONLY 8
#define LS_SHOW_SIZE 16
static int abbrev;
static int ls_options;
static struct pathspec pathspec;
static int chomp_prefix;
static const char *ls_tree_prefix;
static const char * const ls_tree_usage[] = {
N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
NULL
};
static int show_recursive(const char *base, int baselen, const char *pathname)
{
int i;
if (ls_options & LS_RECURSIVE)
return 1;
if (!pathspec.nr)
return 0;
for (i = 0; i < pathspec.nr; i++) {
const char *spec = pathspec.items[i].match;
int len, speclen;
if (strncmp(base, spec, baselen))
continue;
len = strlen(pathname);
spec += baselen;
speclen = strlen(spec);
if (speclen <= len)
continue;
if (spec[len] && spec[len] != '/')
continue;
if (memcmp(pathname, spec, len))
continue;
return 1;
}
return 0;
}
static int show_tree(const struct object_id *oid, struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
int retval = 0;
int baselen;
const char *type = blob_type;
if (S_ISGITLINK(mode)) {
/*
* Maybe we want to have some recursive version here?
*
* Something similar to this incomplete example:
*
if (show_subprojects(base, baselen, pathname))
retval = READ_TREE_RECURSIVE;
*
*/
type = commit_type;
} else if (S_ISDIR(mode)) {
if (show_recursive(base->buf, base->len, pathname)) {
retval = READ_TREE_RECURSIVE;
if (!(ls_options & LS_SHOW_TREES))
return retval;
}
type = tree_type;
}
else if (ls_options & LS_TREE_ONLY)
return 0;
if (!(ls_options & LS_NAME_ONLY)) {
if (ls_options & LS_SHOW_SIZE) {
char size_text[24];
if (!strcmp(type, blob_type)) {
unsigned long size;
if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
xsnprintf(size_text, sizeof(size_text),
"BAD");
else
xsnprintf(size_text, sizeof(size_text),
"%"PRIuMAX, (uintmax_t)size);
} else
xsnprintf(size_text, sizeof(size_text), "-");
printf("%06o %s %s %7s\t", mode, type,
find_unique_abbrev(oid, abbrev),
size_text);
} else
printf("%06o %s %s\t", mode, type,
find_unique_abbrev(oid, abbrev));
}
baselen = base->len;
strbuf_addstr(base, pathname);
write_name_quoted_relative(base->buf,
chomp_prefix ? ls_tree_prefix : NULL,
stdout, line_termination);
strbuf_setlen(base, baselen);
return retval;
}
int cmd_ls_tree(int argc, const char **argv, const char *prefix)
{
struct object_id oid;
struct tree *tree;
int i, full_tree = 0;
const struct option ls_tree_options[] = {
OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
LS_TREE_ONLY),
OPT_BIT('r', NULL, &ls_options, N_("recurse into subtrees"),
LS_RECURSIVE),
OPT_BIT('t', NULL, &ls_options, N_("show trees when recursing"),
LS_SHOW_TREES),
OPT_SET_INT('z', NULL, &line_termination,
N_("terminate entries with NUL byte"), 0),
OPT_BIT('l', "long", &ls_options, N_("include object size"),
LS_SHOW_SIZE),
OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
LS_NAME_ONLY),
OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
LS_NAME_ONLY),
OPT_SET_INT(0, "full-name", &chomp_prefix,
N_("use full path names"), 0),
OPT_BOOL(0, "full-tree", &full_tree,
N_("list entire tree; not just current directory "
"(implies --full-name)")),
OPT__ABBREV(&abbrev),
OPT_END()
};
git_config(git_default_config, NULL);
ls_tree_prefix = prefix;
if (prefix && *prefix)
chomp_prefix = strlen(prefix);
argc = parse_options(argc, argv, prefix, ls_tree_options,
ls_tree_usage, 0);
if (full_tree) {
ls_tree_prefix = prefix = NULL;
chomp_prefix = 0;
}
/* -d -r should imply -t, but -d by itself should not have to. */
if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
ls_options |= LS_SHOW_TREES;
if (argc < 1)
usage_with_options(ls_tree_usage, ls_tree_options);
if (get_oid(argv[0], &oid))
die("Not a valid object name %s", argv[0]);
/*
* show_recursive() rolls its own matching code and is
* generally ignorant of 'struct pathspec'. The magic mask
* cannot be lifted until it is converted to use
* match_pathspec() or tree_entry_interesting()
*/
parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC &
~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
PATHSPEC_PREFER_CWD,
prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
pathspec.items[i].nowildcard_len = pathspec.items[i].len;
pathspec.has_wildcard = 0;
tree = parse_tree_indirect(&oid);
if (!tree)
die("not a tree object");
return !!read_tree_recursive(the_repository, tree, "", 0, 0,
&pathspec, show_tree, NULL);
}

65
third_party/git/builtin/mailinfo.c vendored Normal file
View file

@ -0,0 +1,65 @@
/*
* Another stupid program, this one parsing the headers of an
* email to figure out authorship and subject
*/
#include "cache.h"
#include "builtin.h"
#include "utf8.h"
#include "strbuf.h"
#include "mailinfo.h"
static const char mailinfo_usage[] =
"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
const char *def_charset;
struct mailinfo mi;
int status;
char *msgfile, *patchfile;
setup_mailinfo(&mi);
def_charset = get_commit_output_encoding();
mi.metainfo_charset = def_charset;
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "-k"))
mi.keep_subject = 1;
else if (!strcmp(argv[1], "-b"))
mi.keep_non_patch_brackets_in_subject = 1;
else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
mi.add_message_id = 1;
else if (!strcmp(argv[1], "-u"))
mi.metainfo_charset = def_charset;
else if (!strcmp(argv[1], "-n"))
mi.metainfo_charset = NULL;
else if (starts_with(argv[1], "--encoding="))
mi.metainfo_charset = argv[1] + 11;
else if (!strcmp(argv[1], "--scissors"))
mi.use_scissors = 1;
else if (!strcmp(argv[1], "--no-scissors"))
mi.use_scissors = 0;
else if (!strcmp(argv[1], "--no-inbody-headers"))
mi.use_inbody_headers = 0;
else
usage(mailinfo_usage);
argc--; argv++;
}
if (argc != 3)
usage(mailinfo_usage);
mi.input = stdin;
mi.output = stdout;
msgfile = prefix_filename(prefix, argv[1]);
patchfile = prefix_filename(prefix, argv[2]);
status = !!mailinfo(&mi, msgfile, patchfile);
clear_mailinfo(&mi);
free(msgfile);
free(patchfile);
return status;
}

369
third_party/git/builtin/mailsplit.c vendored Normal file
View file

@ -0,0 +1,369 @@
/*
* Totally braindamaged mbox splitter program.
*
* It just splits a mbox into a list of files: "0001" "0002" ..
* so you can process them further from there.
*/
#include "cache.h"
#include "builtin.h"
#include "string-list.h"
#include "strbuf.h"
static const char git_mailsplit_usage[] =
"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]";
static int is_from_line(const char *line, int len)
{
const char *colon;
if (len < 20 || memcmp("From ", line, 5))
return 0;
colon = line + len - 2;
line += 5;
for (;;) {
if (colon < line)
return 0;
if (*--colon == ':')
break;
}
if (!isdigit(colon[-4]) ||
!isdigit(colon[-2]) ||
!isdigit(colon[-1]) ||
!isdigit(colon[ 1]) ||
!isdigit(colon[ 2]))
return 0;
/* year */
if (strtol(colon+3, NULL, 10) <= 90)
return 0;
/* Ok, close enough */
return 1;
}
static struct strbuf buf = STRBUF_INIT;
static int keep_cr;
static int mboxrd;
static int is_gtfrom(const struct strbuf *buf)
{
size_t min = strlen(">From ");
size_t ngt;
if (buf->len < min)
return 0;
ngt = strspn(buf->buf, ">");
return ngt && starts_with(buf->buf + ngt, "From ");
}
/* Called with the first line (potentially partial)
* already in buf[] -- normally that should begin with
* the Unix "From " line. Write it into the specified
* file.
*/
static int split_one(FILE *mbox, const char *name, int allow_bare)
{
FILE *output;
int fd;
int status = 0;
int is_bare = !is_from_line(buf.buf, buf.len);
if (is_bare && !allow_bare) {
fprintf(stderr, "corrupt mailbox\n");
exit(1);
}
fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0)
die_errno("cannot open output file '%s'", name);
output = xfdopen(fd, "w");
/* Copy it out, while searching for a line that begins with
* "From " and having something that looks like a date format.
*/
for (;;) {
if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' &&
buf.buf[buf.len-2] == '\r') {
strbuf_setlen(&buf, buf.len-2);
strbuf_addch(&buf, '\n');
}
if (mboxrd && is_gtfrom(&buf))
strbuf_remove(&buf, 0, 1);
if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
die_errno("cannot write output");
if (strbuf_getwholeline(&buf, mbox, '\n')) {
if (feof(mbox)) {
status = 1;
break;
}
die_errno("cannot read mbox");
}
if (!is_bare && is_from_line(buf.buf, buf.len))
break; /* done with one message */
}
fclose(output);
return status;
}
static int populate_maildir_list(struct string_list *list, const char *path)
{
DIR *dir;
struct dirent *dent;
char *name = NULL;
char *subs[] = { "cur", "new", NULL };
char **sub;
int ret = -1;
for (sub = subs; *sub; ++sub) {
free(name);
name = xstrfmt("%s/%s", path, *sub);
if ((dir = opendir(name)) == NULL) {
if (errno == ENOENT)
continue;
error_errno("cannot opendir %s", name);
goto out;
}
while ((dent = readdir(dir)) != NULL) {
if (dent->d_name[0] == '.')
continue;
free(name);
name = xstrfmt("%s/%s", *sub, dent->d_name);
string_list_insert(list, name);
}
closedir(dir);
}
ret = 0;
out:
free(name);
return ret;
}
static int maildir_filename_cmp(const char *a, const char *b)
{
while (*a && *b) {
if (isdigit(*a) && isdigit(*b)) {
long int na, nb;
na = strtol(a, (char **)&a, 10);
nb = strtol(b, (char **)&b, 10);
if (na != nb)
return na - nb;
/* strtol advanced our pointers */
}
else {
if (*a != *b)
return (unsigned char)*a - (unsigned char)*b;
a++;
b++;
}
}
return (unsigned char)*a - (unsigned char)*b;
}
static int split_maildir(const char *maildir, const char *dir,
int nr_prec, int skip)
{
char *file = NULL;
FILE *f = NULL;
int ret = -1;
int i;
struct string_list list = STRING_LIST_INIT_DUP;
list.cmp = maildir_filename_cmp;
if (populate_maildir_list(&list, maildir) < 0)
goto out;
for (i = 0; i < list.nr; i++) {
char *name;
free(file);
file = xstrfmt("%s/%s", maildir, list.items[i].string);
f = fopen(file, "r");
if (!f) {
error_errno("cannot open mail %s", file);
goto out;
}
if (strbuf_getwholeline(&buf, f, '\n')) {
error_errno("cannot read mail %s", file);
goto out;
}
name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
split_one(f, name, 1);
free(name);
fclose(f);
f = NULL;
}
ret = skip;
out:
if (f)
fclose(f);
free(file);
string_list_clear(&list, 1);
return ret;
}
static int split_mbox(const char *file, const char *dir, int allow_bare,
int nr_prec, int skip)
{
int ret = -1;
int peek;
FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
int file_done = 0;
if (!f) {
error_errno("cannot open mbox %s", file);
goto out;
}
do {
peek = fgetc(f);
if (peek == EOF) {
if (f == stdin)
/* empty stdin is OK */
ret = skip;
else {
fclose(f);
error(_("empty mbox: '%s'"), file);
}
goto out;
}
} while (isspace(peek));
ungetc(peek, f);
if (strbuf_getwholeline(&buf, f, '\n')) {
/* empty stdin is OK */
if (f != stdin) {
error("cannot read mbox %s", file);
goto out;
}
file_done = 1;
}
while (!file_done) {
char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
file_done = split_one(f, name, allow_bare);
free(name);
}
if (f != stdin)
fclose(f);
ret = skip;
out:
return ret;
}
int cmd_mailsplit(int argc, const char **argv, const char *prefix)
{
int nr = 0, nr_prec = 4, num = 0;
int allow_bare = 0;
const char *dir = NULL;
const char **argp;
static const char *stdin_only[] = { "-", NULL };
for (argp = argv+1; *argp; argp++) {
const char *arg = *argp;
if (arg[0] != '-')
break;
/* do flags here */
if ( arg[1] == 'd' ) {
nr_prec = strtol(arg+2, NULL, 10);
if (nr_prec < 3 || 10 <= nr_prec)
usage(git_mailsplit_usage);
continue;
} else if ( arg[1] == 'f' ) {
nr = strtol(arg+2, NULL, 10);
} else if ( arg[1] == 'h' ) {
usage(git_mailsplit_usage);
} else if ( arg[1] == 'b' && !arg[2] ) {
allow_bare = 1;
} else if (!strcmp(arg, "--keep-cr")) {
keep_cr = 1;
} else if ( arg[1] == 'o' && arg[2] ) {
dir = arg+2;
} else if (!strcmp(arg, "--mboxrd")) {
mboxrd = 1;
} else if ( arg[1] == '-' && !arg[2] ) {
argp++; /* -- marks end of options */
break;
} else {
die("unknown option: %s", arg);
}
}
if ( !dir ) {
/* Backwards compatibility: if no -o specified, accept
<mbox> <dir> or just <dir> */
switch (argc - (argp-argv)) {
case 1:
dir = argp[0];
argp = stdin_only;
break;
case 2:
stdin_only[0] = argp[0];
dir = argp[1];
argp = stdin_only;
break;
default:
usage(git_mailsplit_usage);
}
} else {
/* New usage: if no more argument, parse stdin */
if ( !*argp )
argp = stdin_only;
}
while (*argp) {
const char *arg = *argp++;
struct stat argstat;
int ret = 0;
if (arg[0] == '-' && arg[1] == 0) {
ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
if (ret < 0) {
error("cannot split patches from stdin");
return 1;
}
num += (ret - nr);
nr = ret;
continue;
}
if (stat(arg, &argstat) == -1) {
error_errno("cannot stat %s", arg);
return 1;
}
if (S_ISDIR(argstat.st_mode))
ret = split_maildir(arg, dir, nr_prec, nr);
else
ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
if (ret < 0) {
error("cannot split patches from %s", arg);
return 1;
}
num += (ret - nr);
nr = ret;
}
printf("%d\n", num);
return 0;
}

198
third_party/git/builtin/merge-base.c vendored Normal file
View file

@ -0,0 +1,198 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "refs.h"
#include "diff.h"
#include "revision.h"
#include "parse-options.h"
#include "repository.h"
#include "commit-reach.h"
static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
{
struct commit_list *result, *r;
result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1);
if (!result)
return 1;
for (r = result; r; r = r->next) {
printf("%s\n", oid_to_hex(&r->item->object.oid));
if (!show_all)
break;
}
free_commit_list(result);
return 0;
}
static const char * const merge_base_usage[] = {
N_("git merge-base [-a | --all] <commit> <commit>..."),
N_("git merge-base [-a | --all] --octopus <commit>..."),
N_("git merge-base --independent <commit>..."),
N_("git merge-base --is-ancestor <commit> <commit>"),
N_("git merge-base --fork-point <ref> [<commit>]"),
NULL
};
static struct commit *get_commit_reference(const char *arg)
{
struct object_id revkey;
struct commit *r;
if (get_oid(arg, &revkey))
die("Not a valid object name %s", arg);
r = lookup_commit_reference(the_repository, &revkey);
if (!r)
die("Not a valid commit name %s", arg);
return r;
}
static int handle_independent(int count, const char **args)
{
struct commit_list *revs = NULL, *rev;
int i;
for (i = count - 1; i >= 0; i--)
commit_list_insert(get_commit_reference(args[i]), &revs);
reduce_heads_replace(&revs);
if (!revs)
return 1;
for (rev = revs; rev; rev = rev->next)
printf("%s\n", oid_to_hex(&rev->item->object.oid));
free_commit_list(revs);
return 0;
}
static int handle_octopus(int count, const char **args, int show_all)
{
struct commit_list *revs = NULL;
struct commit_list *result, *rev;
int i;
for (i = count - 1; i >= 0; i--)
commit_list_insert(get_commit_reference(args[i]), &revs);
result = get_octopus_merge_bases(revs);
free_commit_list(revs);
reduce_heads_replace(&result);
if (!result)
return 1;
for (rev = result; rev; rev = rev->next) {
printf("%s\n", oid_to_hex(&rev->item->object.oid));
if (!show_all)
break;
}
free_commit_list(result);
return 0;
}
static int handle_is_ancestor(int argc, const char **argv)
{
struct commit *one, *two;
if (argc != 2)
die("--is-ancestor takes exactly two commits");
one = get_commit_reference(argv[0]);
two = get_commit_reference(argv[1]);
if (in_merge_bases(one, two))
return 0;
else
return 1;
}
static int handle_fork_point(int argc, const char **argv)
{
struct object_id oid;
char *refname;
struct commit *derived, *fork_point;
const char *commitname;
switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
case 0:
die("No such ref: '%s'", argv[0]);
case 1:
break; /* good */
default:
die("Ambiguous refname: '%s'", argv[0]);
}
commitname = (argc == 2) ? argv[1] : "HEAD";
if (get_oid(commitname, &oid))
die("Not a valid object name: '%s'", commitname);
derived = lookup_commit_reference(the_repository, &oid);
fork_point = get_fork_point(refname, derived);
if (!fork_point)
return 1;
printf("%s\n", oid_to_hex(&fork_point->object.oid));
return 0;
}
int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
struct commit **rev;
int rev_nr = 0;
int show_all = 0;
int cmdmode = 0;
struct option options[] = {
OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
OPT_CMDMODE(0, "octopus", &cmdmode,
N_("find ancestors for a single n-way merge"), 'o'),
OPT_CMDMODE(0, "independent", &cmdmode,
N_("list revs not reachable from others"), 'r'),
OPT_CMDMODE(0, "is-ancestor", &cmdmode,
N_("is the first one ancestor of the other?"), 'a'),
OPT_CMDMODE(0, "fork-point", &cmdmode,
N_("find where <commit> forked from reflog of <ref>"), 'f'),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
if (cmdmode == 'a') {
if (argc < 2)
usage_with_options(merge_base_usage, options);
if (show_all)
die("--is-ancestor cannot be used with --all");
return handle_is_ancestor(argc, argv);
}
if (cmdmode == 'r' && show_all)
die("--independent cannot be used with --all");
if (cmdmode == 'o')
return handle_octopus(argc, argv, show_all);
if (cmdmode == 'r')
return handle_independent(argc, argv);
if (cmdmode == 'f') {
if (argc < 1 || 2 < argc)
usage_with_options(merge_base_usage, options);
return handle_fork_point(argc, argv);
}
if (argc < 2)
usage_with_options(merge_base_usage, options);
ALLOC_ARRAY(rev, argc);
while (argc-- > 0)
rev[rev_nr++] = get_commit_reference(*argv++);
return show_merge_base(rev, rev_nr, show_all);
}

118
third_party/git/builtin/merge-file.c vendored Normal file
View file

@ -0,0 +1,118 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
#include "parse-options.h"
static const char *const merge_file_usage[] = {
N_("git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> <orig-file> <file2>"),
NULL
};
static int label_cb(const struct option *opt, const char *arg, int unset)
{
static int label_count = 0;
const char **names = (const char **)opt->value;
BUG_ON_OPT_NEG(unset);
if (label_count >= 3)
return error("too many labels on the command line");
names[label_count++] = arg;
return 0;
}
int cmd_merge_file(int argc, const char **argv, const char *prefix)
{
const char *names[3] = { NULL, NULL, NULL };
mmfile_t mmfs[3];
mmbuffer_t result = {NULL, 0};
xmparam_t xmp = {{0}};
int ret = 0, i = 0, to_stdout = 0;
int quiet = 0;
struct option options[] = {
OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")),
OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3),
OPT_SET_INT(0, "ours", &xmp.favor, N_("for conflicts, use our version"),
XDL_MERGE_FAVOR_OURS),
OPT_SET_INT(0, "theirs", &xmp.favor, N_("for conflicts, use their version"),
XDL_MERGE_FAVOR_THEIRS),
OPT_SET_INT(0, "union", &xmp.favor, N_("for conflicts, use a union version"),
XDL_MERGE_FAVOR_UNION),
OPT_INTEGER(0, "marker-size", &xmp.marker_size,
N_("for conflicts, use this marker size")),
OPT__QUIET(&quiet, N_("do not warn about conflicts")),
OPT_CALLBACK('L', NULL, names, N_("name"),
N_("set labels for file1/orig-file/file2"), &label_cb),
OPT_END(),
};
xmp.level = XDL_MERGE_ZEALOUS_ALNUM;
xmp.style = 0;
xmp.favor = 0;
if (startup_info->have_repository) {
/* Read the configuration file */
git_config(git_xmerge_config, NULL);
if (0 <= git_xmerge_style)
xmp.style = git_xmerge_style;
}
argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
if (argc != 3)
usage_with_options(merge_file_usage, options);
if (quiet) {
if (!freopen("/dev/null", "w", stderr))
return error_errno("failed to redirect stderr to /dev/null");
}
for (i = 0; i < 3; i++) {
char *fname;
int ret;
if (!names[i])
names[i] = argv[i];
fname = prefix_filename(prefix, argv[i]);
ret = read_mmfile(mmfs + i, fname);
free(fname);
if (ret)
return -1;
if (mmfs[i].size > MAX_XDIFF_SIZE ||
buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
return error("Cannot merge binary files: %s",
argv[i]);
}
xmp.ancestor = names[1];
xmp.file1 = names[0];
xmp.file2 = names[2];
ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);
for (i = 0; i < 3; i++)
free(mmfs[i].ptr);
if (ret >= 0) {
const char *filename = argv[0];
char *fpath = prefix_filename(prefix, argv[0]);
FILE *f = to_stdout ? stdout : fopen(fpath, "wb");
if (!f)
ret = error_errno("Could not open %s for writing",
filename);
else if (result.size &&
fwrite(result.ptr, result.size, 1, f) != 1)
ret = error_errno("Could not write to %s", filename);
else if (fclose(f))
ret = error_errno("Could not close %s", filename);
free(result.ptr);
free(fpath);
}
if (ret > 127)
ret = 127;
return ret;
}

111
third_party/git/builtin/merge-index.c vendored Normal file
View file

@ -0,0 +1,111 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "run-command.h"
static const char *pgm;
static int one_shot, quiet;
static int err;
static int merge_entry(int pos, const char *path)
{
int found;
const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
char hexbuf[4][GIT_MAX_HEXSZ + 1];
char ownbuf[4][60];
if (pos >= active_nr)
die("git merge-index: %s not in the cache", path);
found = 0;
do {
const struct cache_entry *ce = active_cache[pos];
int stage = ce_stage(ce);
if (strcmp(ce->name, path))
break;
found++;
oid_to_hex_r(hexbuf[stage], &ce->oid);
xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
arguments[stage] = hexbuf[stage];
arguments[stage + 4] = ownbuf[stage];
} while (++pos < active_nr);
if (!found)
die("git merge-index: %s not in the cache", path);
if (run_command_v_opt(arguments, 0)) {
if (one_shot)
err++;
else {
if (!quiet)
die("merge program failed");
exit(1);
}
}
return found;
}
static void merge_one_path(const char *path)
{
int pos = cache_name_pos(path, strlen(path));
/*
* If it already exists in the cache as stage0, it's
* already merged and there is nothing to do.
*/
if (pos < 0)
merge_entry(-pos-1, path);
}
static void merge_all(void)
{
int i;
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
i += merge_entry(i, ce->name)-1;
}
}
int cmd_merge_index(int argc, const char **argv, const char *prefix)
{
int i, force_file = 0;
/* Without this we cannot rely on waitpid() to tell
* what happened to our children.
*/
signal(SIGCHLD, SIG_DFL);
if (argc < 3)
usage("git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])");
read_cache();
i = 1;
if (!strcmp(argv[i], "-o")) {
one_shot = 1;
i++;
}
if (!strcmp(argv[i], "-q")) {
quiet = 1;
i++;
}
pgm = argv[i++];
for (; i < argc; i++) {
const char *arg = argv[i];
if (!force_file && *arg == '-') {
if (!strcmp(arg, "--")) {
force_file = 1;
continue;
}
if (!strcmp(arg, "-a")) {
merge_all();
continue;
}
die("git merge-index: unknown option %s", arg);
}
merge_one_path(arg);
}
if (err && !quiet)
die("merge program failed");
return err;
}

33
third_party/git/builtin/merge-ours.c vendored Normal file
View file

@ -0,0 +1,33 @@
/*
* Implementation of git-merge-ours.sh as builtin
*
* Copyright (c) 2007 Thomas Harning Jr
* Original:
* Original Copyright (c) 2005 Junio C Hamano
*
* Pretend we resolved the heads, but declare our tree trumps everybody else.
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "git-compat-util.h"
#include "builtin.h"
#include "diff.h"
static const char builtin_merge_ours_usage[] =
"git merge-ours <base>... -- HEAD <remote>...";
int cmd_merge_ours(int argc, const char **argv, const char *prefix)
{
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_merge_ours_usage);
/*
* The contents of the current index becomes the tree we
* commit. The index must match HEAD, or this merge cannot go
* through.
*/
if (read_cache() < 0)
die_errno("read_cache failed");
if (index_differs_from(the_repository, "HEAD", NULL, 0))
exit(2);
exit(0);
}

View file

@ -0,0 +1,88 @@
#include "builtin.h"
#include "commit.h"
#include "tag.h"
#include "merge-recursive.h"
#include "xdiff-interface.h"
static const char builtin_merge_recursive_usage[] =
"git %s <base>... -- <head> <remote> ...";
static char *better_branch_name(const char *branch)
{
static char githead_env[8 + GIT_MAX_HEXSZ + 1];
char *name;
if (strlen(branch) != the_hash_algo->hexsz)
return xstrdup(branch);
xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
name = getenv(githead_env);
return xstrdup(name ? name : branch);
}
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
const struct object_id *bases[21];
unsigned bases_count = 0;
int i, failed;
struct object_id h1, h2;
struct merge_options o;
char *better1, *better2;
struct commit *result;
init_merge_options(&o, the_repository);
if (argv[0] && ends_with(argv[0], "-subtree"))
o.subtree_shift = "";
if (argc < 4)
usagef(builtin_merge_recursive_usage, argv[0]);
for (i = 1; i < argc; ++i) {
const char *arg = argv[i];
if (starts_with(arg, "--")) {
if (!arg[2])
break;
if (parse_merge_opt(&o, arg + 2))
die(_("unknown option %s"), arg);
continue;
}
if (bases_count < ARRAY_SIZE(bases)-1) {
struct object_id *oid = xmalloc(sizeof(struct object_id));
if (get_oid(argv[i], oid))
die(_("could not parse object '%s'"), argv[i]);
bases[bases_count++] = oid;
}
else
warning(Q_("cannot handle more than %d base. "
"Ignoring %s.",
"cannot handle more than %d bases. "
"Ignoring %s.",
(int)ARRAY_SIZE(bases)-1),
(int)ARRAY_SIZE(bases)-1, argv[i]);
}
if (argc - i != 3) /* "--" "<head>" "<remote>" */
die(_("not handling anything other than two heads merge."));
o.branch1 = argv[++i];
o.branch2 = argv[++i];
if (get_oid(o.branch1, &h1))
die(_("could not resolve ref '%s'"), o.branch1);
if (get_oid(o.branch2, &h2))
die(_("could not resolve ref '%s'"), o.branch2);
o.branch1 = better1 = better_branch_name(o.branch1);
o.branch2 = better2 = better_branch_name(o.branch2);
if (o.verbosity >= 3)
printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result);
free(better1);
free(better2);
if (failed < 0)
return 128; /* die() error code */
return failed;
}

389
third_party/git/builtin/merge-tree.c vendored Normal file
View file

@ -0,0 +1,389 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "tree-walk.h"
#include "xdiff-interface.h"
#include "object-store.h"
#include "repository.h"
#include "blob.h"
#include "exec-cmd.h"
#include "merge-blobs.h"
static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
struct merge_list {
struct merge_list *next;
struct merge_list *link; /* other stages for this object */
unsigned int stage : 2;
unsigned int mode;
const char *path;
struct blob *blob;
};
static struct merge_list *merge_result, **merge_result_end = &merge_result;
static void add_merge_entry(struct merge_list *entry)
{
*merge_result_end = entry;
merge_result_end = &entry->next;
}
static void merge_trees(struct tree_desc t[3], const char *base);
static const char *explanation(struct merge_list *entry)
{
switch (entry->stage) {
case 0:
return "merged";
case 3:
return "added in remote";
case 2:
if (entry->link)
return "added in both";
return "added in local";
}
/* Existed in base */
entry = entry->link;
if (!entry)
return "removed in both";
if (entry->link)
return "changed in both";
if (entry->stage == 3)
return "removed in local";
return "removed in remote";
}
static void *result(struct merge_list *entry, unsigned long *size)
{
enum object_type type;
struct blob *base, *our, *their;
const char *path = entry->path;
if (!entry->stage)
return read_object_file(&entry->blob->object.oid, &type, size);
base = NULL;
if (entry->stage == 1) {
base = entry->blob;
entry = entry->link;
}
our = NULL;
if (entry && entry->stage == 2) {
our = entry->blob;
entry = entry->link;
}
their = NULL;
if (entry)
their = entry->blob;
return merge_blobs(the_repository->index, path,
base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
{
enum object_type type;
while (entry) {
if (entry->stage == 2)
return read_object_file(&entry->blob->object.oid,
&type, size);
entry = entry->link;
}
return NULL;
}
static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
{
int i;
for (i = 0; i < nbuf; i++)
printf("%.*s", (int) mb[i].size, mb[i].ptr);
return 0;
}
static void show_diff(struct merge_list *entry)
{
unsigned long size;
mmfile_t src, dst;
xpparam_t xpp;
xdemitconf_t xecfg;
xdemitcb_t ecb;
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
ecb.out_hunk = NULL;
ecb.out_line = show_outf;
ecb.priv = NULL;
src.ptr = origin(entry, &size);
if (!src.ptr)
size = 0;
src.size = size;
dst.ptr = result(entry, &size);
if (!dst.ptr)
size = 0;
dst.size = size;
if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb))
die("unable to generate diff");
free(src.ptr);
free(dst.ptr);
}
static void show_result_list(struct merge_list *entry)
{
printf("%s\n", explanation(entry));
do {
struct merge_list *link = entry->link;
static const char *desc[4] = { "result", "base", "our", "their" };
printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, oid_to_hex(&entry->blob->object.oid), entry->path);
entry = link;
} while (entry);
}
static void show_result(void)
{
struct merge_list *walk;
walk = merge_result;
while (walk) {
show_result_list(walk);
show_diff(walk);
walk = walk->next;
}
}
/* An empty entry never compares same, not even to another empty entry */
static int same_entry(struct name_entry *a, struct name_entry *b)
{
return !is_null_oid(&a->oid) &&
!is_null_oid(&b->oid) &&
oideq(&a->oid, &b->oid) &&
a->mode == b->mode;
}
static int both_empty(struct name_entry *a, struct name_entry *b)
{
return is_null_oid(&a->oid) && is_null_oid(&b->oid);
}
static struct merge_list *create_entry(unsigned stage, unsigned mode, const struct object_id *oid, const char *path)
{
struct merge_list *res = xcalloc(1, sizeof(*res));
res->stage = stage;
res->path = path;
res->mode = mode;
res->blob = lookup_blob(the_repository, oid);
return res;
}
static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
{
char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz);
return make_traverse_path(path, info, n);
}
static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result)
{
struct merge_list *orig, *final;
const char *path;
/* If it's already ours, don't bother showing it */
if (!ours)
return;
path = traverse_path(info, result);
orig = create_entry(2, ours->mode, &ours->oid, path);
final = create_entry(0, result->mode, &result->oid, path);
final->link = orig;
add_merge_entry(final);
}
static void unresolved_directory(const struct traverse_info *info,
struct name_entry n[3])
{
struct repository *r = the_repository;
char *newbase;
struct name_entry *p;
struct tree_desc t[3];
void *buf0, *buf1, *buf2;
for (p = n; p < n + 3; p++) {
if (p->mode && S_ISDIR(p->mode))
break;
}
if (n + 3 <= p)
return; /* there is no tree here */
newbase = traverse_path(info, p);
#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL)
buf0 = fill_tree_descriptor(r, t + 0, ENTRY_OID(n + 0));
buf1 = fill_tree_descriptor(r, t + 1, ENTRY_OID(n + 1));
buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));
#undef ENTRY_OID
merge_trees(t, newbase);
free(buf0);
free(buf1);
free(buf2);
free(newbase);
}
static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
{
const char *path;
struct merge_list *link;
if (!n->mode)
return entry;
if (entry)
path = entry->path;
else
path = traverse_path(info, n);
link = create_entry(stage, n->mode, &n->oid, path);
link->link = entry;
return link;
}
static void unresolved(const struct traverse_info *info, struct name_entry n[3])
{
struct merge_list *entry = NULL;
int i;
unsigned dirmask = 0, mask = 0;
for (i = 0; i < 3; i++) {
mask |= (1 << i);
/*
* Treat missing entries as directories so that we return
* after unresolved_directory has handled this.
*/
if (!n[i].mode || S_ISDIR(n[i].mode))
dirmask |= (1 << i);
}
unresolved_directory(info, n);
if (dirmask == mask)
return;
if (n[2].mode && !S_ISDIR(n[2].mode))
entry = link_entry(3, info, n + 2, entry);
if (n[1].mode && !S_ISDIR(n[1].mode))
entry = link_entry(2, info, n + 1, entry);
if (n[0].mode && !S_ISDIR(n[0].mode))
entry = link_entry(1, info, n + 0, entry);
add_merge_entry(entry);
}
/*
* Merge two trees together (t[1] and t[2]), using a common base (t[0])
* as the origin.
*
* This walks the (sorted) trees in lock-step, checking every possible
* name. Note that directories automatically sort differently from other
* files (see "base_name_compare"), so you'll never see file/directory
* conflicts, because they won't ever compare the same.
*
* IOW, if a directory changes to a filename, it will automatically be
* seen as the directory going away, and the filename being created.
*
* Think of this as a three-way diff.
*
* The output will be either:
* - successful merge
* "0 mode sha1 filename"
* NOTE NOTE NOTE! FIXME! We really really need to walk the index
* in parallel with this too!
*
* - conflict:
* "1 mode sha1 filename"
* "2 mode sha1 filename"
* "3 mode sha1 filename"
* where not all of the 1/2/3 lines may exist, of course.
*
* The successful merge rules are the same as for the three-way merge
* in git-read-tree.
*/
static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
{
/* Same in both? */
if (same_entry(entry+1, entry+2) || both_empty(entry+1, entry+2)) {
/* Modified, added or removed identically */
resolve(info, NULL, entry+1);
return mask;
}
if (same_entry(entry+0, entry+1)) {
if (!is_null_oid(&entry[2].oid) && !S_ISDIR(entry[2].mode)) {
/* We did not touch, they modified -- take theirs */
resolve(info, entry+1, entry+2);
return mask;
}
/*
* If we did not touch a directory but they made it
* into a file, we fall through and unresolved()
* recurses down. Likewise for the opposite case.
*/
}
if (same_entry(entry+0, entry+2) || both_empty(entry+0, entry+2)) {
/* We added, modified or removed, they did not touch -- take ours */
resolve(info, NULL, entry+1);
return mask;
}
unresolved(info, entry);
return mask;
}
static void merge_trees(struct tree_desc t[3], const char *base)
{
struct traverse_info info;
setup_traverse_info(&info, base);
info.fn = threeway_callback;
traverse_trees(&the_index, 3, t, &info);
}
static void *get_tree_descriptor(struct repository *r,
struct tree_desc *desc,
const char *rev)
{
struct object_id oid;
void *buf;
if (repo_get_oid(r, rev, &oid))
die("unknown rev %s", rev);
buf = fill_tree_descriptor(r, desc, &oid);
if (!buf)
die("%s is not a tree", rev);
return buf;
}
int cmd_merge_tree(int argc, const char **argv, const char *prefix)
{
struct repository *r = the_repository;
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
if (argc != 4)
usage(merge_tree_usage);
buf1 = get_tree_descriptor(r, t+0, argv[1]);
buf2 = get_tree_descriptor(r, t+1, argv[2]);
buf3 = get_tree_descriptor(r, t+2, argv[3]);
merge_trees(t, "");
free(buf1);
free(buf2);
free(buf3);
show_result();
return 0;
}

1680
third_party/git/builtin/merge.c vendored Normal file

File diff suppressed because it is too large Load diff

176
third_party/git/builtin/mktag.c vendored Normal file
View file

@ -0,0 +1,176 @@
#include "builtin.h"
#include "tag.h"
#include "replace-object.h"
#include "object-store.h"
/*
* A signature file has a very simple fixed format: four lines
* of "object <sha1>" + "type <typename>" + "tag <tagname>" +
* "tagger <committer>", followed by a blank line, a free-form tag
* message and a signature block that git itself doesn't care about,
* but that can be verified with gpg or similar.
*
* The first four lines are guaranteed to be at least 83 bytes:
* "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
* shortest possible type-line, "tag .\n" at 6 bytes is the shortest
* single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
* the shortest possible tagger-line.
*/
/*
* We refuse to tag something we can't verify. Just because.
*/
static int verify_object(const struct object_id *oid, const char *expected_type)
{
int ret = -1;
enum object_type type;
unsigned long size;
void *buffer = read_object_file(oid, &type, &size);
const struct object_id *repl = lookup_replace_object(the_repository, oid);
if (buffer) {
if (type == type_from_string(expected_type))
ret = check_object_signature(repl, buffer, size, expected_type);
free(buffer);
}
return ret;
}
static int verify_tag(char *buffer, unsigned long size)
{
int typelen;
char type[20];
struct object_id oid;
const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p;
size_t len;
if (size < 84)
return error("wanna fool me ? you obviously got the size wrong !");
buffer[size] = 0;
/* Verify object line */
object = buffer;
if (memcmp(object, "object ", 7))
return error("char%d: does not start with \"object \"", 0);
if (parse_oid_hex(object + 7, &oid, &p))
return error("char%d: could not get SHA1 hash", 7);
/* Verify type line */
type_line = p + 1;
if (memcmp(type_line - 1, "\ntype ", 6))
return error("char%d: could not find \"\\ntype \"", 47);
/* Verify tag-line */
tag_line = strchr(type_line, '\n');
if (!tag_line)
return error("char%"PRIuMAX": could not find next \"\\n\"",
(uintmax_t) (type_line - buffer));
tag_line++;
if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
return error("char%"PRIuMAX": no \"tag \" found",
(uintmax_t) (tag_line - buffer));
/* Get the actual type */
typelen = tag_line - type_line - strlen("type \n");
if (typelen >= sizeof(type))
return error("char%"PRIuMAX": type too long",
(uintmax_t) (type_line+5 - buffer));
memcpy(type, type_line+5, typelen);
type[typelen] = 0;
/* Verify that the object matches */
if (verify_object(&oid, type))
return error("char%d: could not verify object %s", 7, oid_to_hex(&oid));
/* Verify the tag-name: we don't allow control characters or spaces in it */
tag_line += 4;
for (;;) {
unsigned char c = *tag_line++;
if (c == '\n')
break;
if (c > ' ')
continue;
return error("char%"PRIuMAX": could not verify tag name",
(uintmax_t) (tag_line - buffer));
}
/* Verify the tagger line */
tagger_line = tag_line;
if (memcmp(tagger_line, "tagger ", 7))
return error("char%"PRIuMAX": could not find \"tagger \"",
(uintmax_t) (tagger_line - buffer));
/*
* Check for correct form for name and email
* i.e. " <" followed by "> " on _this_ line
* No angle brackets within the name or email address fields.
* No spaces within the email address field.
*/
tagger_line += 7;
if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
strpbrk(tagger_line, "<>\n") != lb+1 ||
strpbrk(lb+2, "><\n ") != rb)
return error("char%"PRIuMAX": malformed tagger field",
(uintmax_t) (tagger_line - buffer));
/* Check for author name, at least one character, space is acceptable */
if (lb == tagger_line)
return error("char%"PRIuMAX": missing tagger name",
(uintmax_t) (tagger_line - buffer));
/* timestamp, 1 or more digits followed by space */
tagger_line = rb + 2;
if (!(len = strspn(tagger_line, "0123456789")))
return error("char%"PRIuMAX": missing tag timestamp",
(uintmax_t) (tagger_line - buffer));
tagger_line += len;
if (*tagger_line != ' ')
return error("char%"PRIuMAX": malformed tag timestamp",
(uintmax_t) (tagger_line - buffer));
tagger_line++;
/* timezone, 5 digits [+-]hhmm, max. 1400 */
if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
strspn(tagger_line+1, "0123456789") == 4 &&
tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
return error("char%"PRIuMAX": malformed tag timezone",
(uintmax_t) (tagger_line - buffer));
tagger_line += 6;
/* Verify the blank line separating the header from the body */
if (*tagger_line != '\n')
return error("char%"PRIuMAX": trailing garbage in tag header",
(uintmax_t) (tagger_line - buffer));
/* The actual stuff afterwards we don't care about.. */
return 0;
}
int cmd_mktag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
struct object_id result;
if (argc != 1)
usage("git mktag");
if (strbuf_read(&buf, 0, 4096) < 0) {
die_errno("could not read from stdin");
}
/* Verify it for some basic sanity: it needs to start with
"object <sha1>\ntype\ntagger " */
if (verify_tag(buf.buf, buf.len) < 0)
die("invalid tag signature file");
if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0)
die("unable to write tag file");
strbuf_release(&buf);
printf("%s\n", oid_to_hex(&result));
return 0;
}

193
third_party/git/builtin/mktree.c vendored Normal file
View file

@ -0,0 +1,193 @@
/*
* GIT - the stupid content tracker
*
* Copyright (c) Junio C Hamano, 2006, 2009
*/
#include "builtin.h"
#include "quote.h"
#include "tree.h"
#include "parse-options.h"
#include "object-store.h"
static struct treeent {
unsigned mode;
struct object_id oid;
int len;
char name[FLEX_ARRAY];
} **entries;
static int alloc, used;
static void append_to_tree(unsigned mode, struct object_id *oid, char *path)
{
struct treeent *ent;
size_t len = strlen(path);
if (strchr(path, '/'))
die("path %s contains slash", path);
FLEX_ALLOC_MEM(ent, name, path, len);
ent->mode = mode;
ent->len = len;
oidcpy(&ent->oid, oid);
ALLOC_GROW(entries, used + 1, alloc);
entries[used++] = ent;
}
static int ent_compare(const void *a_, const void *b_)
{
struct treeent *a = *(struct treeent **)a_;
struct treeent *b = *(struct treeent **)b_;
return base_name_compare(a->name, a->len, a->mode,
b->name, b->len, b->mode);
}
static void write_tree(struct object_id *oid)
{
struct strbuf buf;
size_t size;
int i;
QSORT(entries, used, ent_compare);
for (size = i = 0; i < used; i++)
size += 32 + entries[i]->len;
strbuf_init(&buf, size);
for (i = 0; i < used; i++) {
struct treeent *ent = entries[i];
strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0');
strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz);
}
write_object_file(buf.buf, buf.len, tree_type, oid);
strbuf_release(&buf);
}
static const char *mktree_usage[] = {
N_("git mktree [-z] [--missing] [--batch]"),
NULL
};
static void mktree_line(char *buf, int nul_term_line, int allow_missing)
{
char *ptr, *ntr;
const char *p;
unsigned mode;
enum object_type mode_type; /* object type derived from mode */
enum object_type obj_type; /* object type derived from sha */
char *path, *to_free = NULL;
struct object_id oid;
ptr = buf;
/*
* Read non-recursive ls-tree output format:
* mode SP type SP sha1 TAB name
*/
mode = strtoul(ptr, &ntr, 8);
if (ptr == ntr || !ntr || *ntr != ' ')
die("input format error: %s", buf);
ptr = ntr + 1; /* type */
ntr = strchr(ptr, ' ');
if (!ntr || parse_oid_hex(ntr + 1, &oid, &p) ||
*p != '\t')
die("input format error: %s", buf);
/* It is perfectly normal if we do not have a commit from a submodule */
if (S_ISGITLINK(mode))
allow_missing = 1;
*ntr++ = 0; /* now at the beginning of SHA1 */
path = (char *)p + 1; /* at the beginning of name */
if (!nul_term_line && path[0] == '"') {
struct strbuf p_uq = STRBUF_INIT;
if (unquote_c_style(&p_uq, path, NULL))
die("invalid quoting");
path = to_free = strbuf_detach(&p_uq, NULL);
}
/*
* Object type is redundantly derivable three ways.
* These should all agree.
*/
mode_type = object_type(mode);
if (mode_type != type_from_string(ptr)) {
die("entry '%s' object type (%s) doesn't match mode type (%s)",
path, ptr, type_name(mode_type));
}
/* Check the type of object identified by sha1 */
obj_type = oid_object_info(the_repository, &oid, NULL);
if (obj_type < 0) {
if (allow_missing) {
; /* no problem - missing objects are presumed to be of the right type */
} else {
die("entry '%s' object %s is unavailable", path, oid_to_hex(&oid));
}
} else {
if (obj_type != mode_type) {
/*
* The object exists but is of the wrong type.
* This is a problem regardless of allow_missing
* because the new tree entry will never be correct.
*/
die("entry '%s' object %s is a %s but specified type was (%s)",
path, oid_to_hex(&oid), type_name(obj_type), type_name(mode_type));
}
}
append_to_tree(mode, &oid, path);
free(to_free);
}
int cmd_mktree(int ac, const char **av, const char *prefix)
{
struct strbuf sb = STRBUF_INIT;
struct object_id oid;
int nul_term_line = 0;
int allow_missing = 0;
int is_batch_mode = 0;
int got_eof = 0;
strbuf_getline_fn getline_fn;
const struct option option[] = {
OPT_BOOL('z', NULL, &nul_term_line, N_("input is NUL terminated")),
OPT_SET_INT( 0 , "missing", &allow_missing, N_("allow missing objects"), 1),
OPT_SET_INT( 0 , "batch", &is_batch_mode, N_("allow creation of more than one tree"), 1),
OPT_END()
};
ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
while (!got_eof) {
while (1) {
if (getline_fn(&sb, stdin) == EOF) {
got_eof = 1;
break;
}
if (sb.buf[0] == '\0') {
/* empty lines denote tree boundaries in batch mode */
if (is_batch_mode)
break;
die("input format error: (blank line only valid in batch mode)");
}
mktree_line(sb.buf, nul_term_line, allow_missing);
}
if (is_batch_mode && got_eof && used < 1) {
/*
* Execution gets here if the last tree entry is terminated with a
* new-line. The final new-line has been made optional to be
* consistent with the original non-batch behaviour of mktree.
*/
; /* skip creating an empty tree */
} else {
write_tree(&oid);
puts(oid_to_hex(&oid));
fflush(stdout);
}
used=0; /* reset tree entry buffer for re-use in batch mode */
}
strbuf_release(&sb);
exit(0);
}

View file

@ -0,0 +1,62 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "parse-options.h"
#include "midx.h"
#include "trace2.h"
static char const * const builtin_multi_pack_index_usage[] = {
N_("git multi-pack-index [--object-dir=<dir>] (write|verify|expire|repack --batch-size=<size>)"),
NULL
};
static struct opts_multi_pack_index {
const char *object_dir;
unsigned long batch_size;
} opts;
int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
static struct option builtin_multi_pack_index_options[] = {
OPT_FILENAME(0, "object-dir", &opts.object_dir,
N_("object directory containing set of packfile and pack-index pairs")),
OPT_MAGNITUDE(0, "batch-size", &opts.batch_size,
N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage, 0);
if (!opts.object_dir)
opts.object_dir = get_object_directory();
if (argc == 0)
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
if (argc > 1) {
die(_("too many arguments"));
return 1;
}
trace2_cmd_mode(argv[0]);
if (!strcmp(argv[0], "repack"))
return midx_repack(the_repository, opts.object_dir, (size_t)opts.batch_size);
if (opts.batch_size)
die(_("--batch-size option is only for 'repack' subcommand"));
if (!strcmp(argv[0], "write"))
return write_midx_file(opts.object_dir);
if (!strcmp(argv[0], "verify"))
return verify_midx_file(the_repository, opts.object_dir);
if (!strcmp(argv[0], "expire"))
return expire_midx_packs(the_repository, opts.object_dir);
die(_("unrecognized subcommand: %s"), argv[0]);
}

304
third_party/git/builtin/mv.c vendored Normal file
View file

@ -0,0 +1,304 @@
/*
* "git mv" builtin command
*
* Copyright (C) 2006 Johannes Schindelin
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "pathspec.h"
#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "string-list.h"
#include "parse-options.h"
#include "submodule.h"
static const char * const builtin_mv_usage[] = {
N_("git mv [<options>] <source>... <destination>"),
NULL
};
#define DUP_BASENAME 1
#define KEEP_TRAILING_SLASH 2
static const char **internal_prefix_pathspec(const char *prefix,
const char **pathspec,
int count, unsigned flags)
{
int i;
const char **result;
int prefixlen = prefix ? strlen(prefix) : 0;
ALLOC_ARRAY(result, count + 1);
/* Create an intermediate copy of the pathspec based on the flags */
for (i = 0; i < count; i++) {
int length = strlen(pathspec[i]);
int to_copy = length;
char *it;
while (!(flags & KEEP_TRAILING_SLASH) &&
to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1]))
to_copy--;
it = xmemdupz(pathspec[i], to_copy);
if (flags & DUP_BASENAME) {
result[i] = xstrdup(basename(it));
free(it);
} else {
result[i] = it;
}
}
result[count] = NULL;
/* Prefix the pathspec and free the old intermediate strings */
for (i = 0; i < count; i++) {
const char *match = prefix_path(prefix, prefixlen, result[i]);
free((char *) result[i]);
result[i] = match;
}
return result;
}
static const char *add_slash(const char *path)
{
size_t len = strlen(path);
if (path[len - 1] != '/') {
char *with_slash = xmalloc(st_add(len, 2));
memcpy(with_slash, path, len);
with_slash[len++] = '/';
with_slash[len] = 0;
return with_slash;
}
return path;
}
#define SUBMODULE_WITH_GITDIR ((const char *)1)
static void prepare_move_submodule(const char *src, int first,
const char **submodule_gitfile)
{
struct strbuf submodule_dotgit = STRBUF_INIT;
if (!S_ISGITLINK(active_cache[first]->ce_mode))
die(_("Directory %s is in index and no submodule?"), src);
if (!is_staging_gitmodules_ok(&the_index))
die(_("Please stage your changes to .gitmodules or stash them to proceed"));
strbuf_addf(&submodule_dotgit, "%s/.git", src);
*submodule_gitfile = read_gitfile(submodule_dotgit.buf);
if (*submodule_gitfile)
*submodule_gitfile = xstrdup(*submodule_gitfile);
else
*submodule_gitfile = SUBMODULE_WITH_GITDIR;
strbuf_release(&submodule_dotgit);
}
static int index_range_of_same_dir(const char *src, int length,
int *first_p, int *last_p)
{
const char *src_w_slash = add_slash(src);
int first, last, len_w_slash = length + 1;
first = cache_name_pos(src_w_slash, len_w_slash);
if (first >= 0)
die(_("%.*s is in index"), len_w_slash, src_w_slash);
first = -1 - first;
for (last = first; last < active_nr; last++) {
const char *path = active_cache[last]->name;
if (strncmp(path, src_w_slash, len_w_slash))
break;
}
if (src_w_slash != src)
free((char *)src_w_slash);
*first_p = first;
*last_p = last;
return last - first;
}
int cmd_mv(int argc, const char **argv, const char *prefix)
{
int i, flags, gitmodules_modified = 0;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__FORCE(&force, N_("force move/rename even if target exists"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
OPT_END(),
};
const char **source, **destination, **dest_path, **submodule_gitfile;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
struct lock_file lock_file = LOCK_INIT;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_mv_options,
builtin_mv_usage, 0);
if (--argc < 1)
usage_with_options(builtin_mv_usage, builtin_mv_options);
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
if (read_cache() < 0)
die(_("index file corrupt"));
source = internal_prefix_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
/*
* Keep trailing slash, needed to let
* "git mv file no-such-dir/" error out, except in the case
* "git mv directory no-such-dir/".
*/
flags = KEEP_TRAILING_SLASH;
if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1]))
flags = 0;
dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags);
submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
} else {
if (argc != 1)
die(_("destination '%s' is not a directory"), dest_path[0]);
destination = dest_path;
}
/* Checking */
for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
int length, src_is_dir;
const char *bad = NULL;
if (show_only)
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
length = strlen(src);
if (lstat(src, &st) < 0)
bad = _("bad source");
else if (!strncmp(src, dst, length) &&
(dst[length] == 0 || dst[length] == '/')) {
bad = _("can not move directory into itself");
} else if ((src_is_dir = S_ISDIR(st.st_mode))
&& lstat(dst, &st) == 0)
bad = _("cannot move directory over file");
else if (src_is_dir) {
int first = cache_name_pos(src, length), last;
if (first >= 0)
prepare_move_submodule(src, first,
submodule_gitfile + i);
else if (index_range_of_same_dir(src, length,
&first, &last) < 1)
bad = _("source directory is empty");
else { /* last - first >= 1 */
int j, dst_len, n;
modes[i] = WORKING_DIRECTORY;
n = argc + last - first;
REALLOC_ARRAY(source, n);
REALLOC_ARRAY(destination, n);
REALLOC_ARRAY(modes, n);
REALLOC_ARRAY(submodule_gitfile, n);
dst = add_slash(dst);
dst_len = strlen(dst);
for (j = 0; j < last - first; j++) {
const char *path = active_cache[first + j]->name;
source[argc + j] = path;
destination[argc + j] =
prefix_path(dst, dst_len, path + length + 1);
modes[argc + j] = INDEX;
submodule_gitfile[argc + j] = NULL;
}
argc += last - first;
}
} else if (cache_name_pos(src, length) < 0)
bad = _("not under version control");
else if (lstat(dst, &st) == 0 &&
(!ignore_case || strcasecmp(src, dst))) {
bad = _("destination exists");
if (force) {
/*
* only files can overwrite each other:
* check both source and destination
*/
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
if (verbose)
warning(_("overwriting '%s'"), dst);
bad = NULL;
} else
bad = _("Cannot overwrite");
}
} else if (string_list_has_string(&src_for_dst, dst))
bad = _("multiple sources for the same target");
else if (is_dir_sep(dst[strlen(dst) - 1]))
bad = _("destination directory does not exist");
else
string_list_insert(&src_for_dst, dst);
if (!bad)
continue;
if (!ignore_errors)
die(_("%s, source=%s, destination=%s"),
bad, src, dst);
if (--argc > 0) {
int n = argc - i;
memmove(source + i, source + i + 1,
n * sizeof(char *));
memmove(destination + i, destination + i + 1,
n * sizeof(char *));
memmove(modes + i, modes + i + 1,
n * sizeof(enum update_mode));
memmove(submodule_gitfile + i, submodule_gitfile + i + 1,
n * sizeof(char *));
i--;
}
}
for (i = 0; i < argc; i++) {
const char *src = source[i], *dst = destination[i];
enum update_mode mode = modes[i];
int pos;
if (show_only || verbose)
printf(_("Renaming %s to %s\n"), src, dst);
if (show_only)
continue;
if (mode != INDEX && rename(src, dst) < 0) {
if (ignore_errors)
continue;
die_errno(_("renaming '%s' failed"), src);
}
if (submodule_gitfile[i]) {
if (!update_path_in_gitmodules(src, dst))
gitmodules_modified = 1;
if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
connect_work_tree_and_git_dir(dst,
submodule_gitfile[i],
1);
}
if (mode == WORKING_DIRECTORY)
continue;
pos = cache_name_pos(src, strlen(src));
assert(pos >= 0);
rename_cache_entry_at(pos, dst);
}
if (gitmodules_modified)
stage_updated_gitmodules(&the_index);
if (write_locked_index(&the_index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
return 0;
}

517
third_party/git/builtin/name-rev.c vendored Normal file
View file

@ -0,0 +1,517 @@
#include "builtin.h"
#include "cache.h"
#include "repository.h"
#include "config.h"
#include "commit.h"
#include "tag.h"
#include "refs.h"
#include "parse-options.h"
#include "sha1-lookup.h"
#include "commit-slab.h"
#define CUTOFF_DATE_SLOP 86400 /* one day */
typedef struct rev_name {
const char *tip_name;
timestamp_t taggerdate;
int generation;
int distance;
int from_tag;
} rev_name;
define_commit_slab(commit_rev_name, struct rev_name *);
static timestamp_t cutoff = TIME_MAX;
static struct commit_rev_name rev_names;
/* How many generations are maximally preferred over _one_ merge traversal? */
#define MERGE_TRAVERSAL_WEIGHT 65535
static struct rev_name *get_commit_rev_name(struct commit *commit)
{
struct rev_name **slot = commit_rev_name_peek(&rev_names, commit);
return slot ? *slot : NULL;
}
static void set_commit_rev_name(struct commit *commit, struct rev_name *name)
{
*commit_rev_name_at(&rev_names, commit) = name;
}
static int is_better_name(struct rev_name *name,
timestamp_t taggerdate,
int distance,
int from_tag)
{
/*
* When comparing names based on tags, prefer names
* based on the older tag, even if it is farther away.
*/
if (from_tag && name->from_tag)
return (name->taggerdate > taggerdate ||
(name->taggerdate == taggerdate &&
name->distance > distance));
/*
* We know that at least one of them is a non-tag at this point.
* favor a tag over a non-tag.
*/
if (name->from_tag != from_tag)
return from_tag;
/*
* We are now looking at two non-tags. Tiebreak to favor
* shorter hops.
*/
if (name->distance != distance)
return name->distance > distance;
/* ... or tiebreak to favor older date */
if (name->taggerdate != taggerdate)
return name->taggerdate > taggerdate;
/* keep the current one if we cannot decide */
return 0;
}
static void name_rev(struct commit *commit,
const char *tip_name, timestamp_t taggerdate,
int generation, int distance, int from_tag,
int deref)
{
struct rev_name *name = get_commit_rev_name(commit);
struct commit_list *parents;
int parent_number = 1;
char *to_free = NULL;
parse_commit(commit);
if (commit->date < cutoff)
return;
if (deref) {
tip_name = to_free = xstrfmt("%s^0", tip_name);
if (generation)
die("generation: %d, but deref?", generation);
}
if (name == NULL) {
name = xmalloc(sizeof(rev_name));
set_commit_rev_name(commit, name);
goto copy_data;
} else if (is_better_name(name, taggerdate, distance, from_tag)) {
copy_data:
name->tip_name = tip_name;
name->taggerdate = taggerdate;
name->generation = generation;
name->distance = distance;
name->from_tag = from_tag;
} else {
free(to_free);
return;
}
for (parents = commit->parents;
parents;
parents = parents->next, parent_number++) {
if (parent_number > 1) {
size_t len;
char *new_name;
strip_suffix(tip_name, "^0", &len);
if (generation > 0)
new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
generation, parent_number);
else
new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
parent_number);
name_rev(parents->item, new_name, taggerdate, 0,
distance + MERGE_TRAVERSAL_WEIGHT,
from_tag, 0);
} else {
name_rev(parents->item, tip_name, taggerdate,
generation + 1, distance + 1,
from_tag, 0);
}
}
}
static int subpath_matches(const char *path, const char *filter)
{
const char *subpath = path;
while (subpath) {
if (!wildmatch(filter, subpath, 0))
return subpath - path;
subpath = strchr(subpath, '/');
if (subpath)
subpath++;
}
return -1;
}
static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
{
if (shorten_unambiguous)
refname = shorten_unambiguous_ref(refname, 0);
else if (starts_with(refname, "refs/heads/"))
refname = refname + 11;
else if (starts_with(refname, "refs/"))
refname = refname + 5;
return refname;
}
struct name_ref_data {
int tags_only;
int name_only;
struct string_list ref_filters;
struct string_list exclude_filters;
};
static struct tip_table {
struct tip_table_entry {
struct object_id oid;
const char *refname;
} *table;
int nr;
int alloc;
int sorted;
} tip_table;
static void add_to_tip_table(const struct object_id *oid, const char *refname,
int shorten_unambiguous)
{
refname = name_ref_abbrev(refname, shorten_unambiguous);
ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
oidcpy(&tip_table.table[tip_table.nr].oid, oid);
tip_table.table[tip_table.nr].refname = xstrdup(refname);
tip_table.nr++;
tip_table.sorted = 0;
}
static int tipcmp(const void *a_, const void *b_)
{
const struct tip_table_entry *a = a_, *b = b_;
return oidcmp(&a->oid, &b->oid);
}
static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
{
struct object *o = parse_object(the_repository, oid);
struct name_ref_data *data = cb_data;
int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
timestamp_t taggerdate = TIME_MAX;
if (data->tags_only && !starts_with(path, "refs/tags/"))
return 0;
if (data->exclude_filters.nr) {
struct string_list_item *item;
for_each_string_list_item(item, &data->exclude_filters) {
if (subpath_matches(path, item->string) >= 0)
return 0;
}
}
if (data->ref_filters.nr) {
struct string_list_item *item;
int matched = 0;
/* See if any of the patterns match. */
for_each_string_list_item(item, &data->ref_filters) {
/*
* Check all patterns even after finding a match, so
* that we can see if a match with a subpath exists.
* When a user asked for 'refs/tags/v*' and 'v1.*',
* both of which match, the user is showing her
* willingness to accept a shortened output by having
* the 'v1.*' in the acceptable refnames, so we
* shouldn't stop when seeing 'refs/tags/v1.4' matches
* 'refs/tags/v*'. We should show it as 'v1.4'.
*/
switch (subpath_matches(path, item->string)) {
case -1: /* did not match */
break;
case 0: /* matched fully */
matched = 1;
break;
default: /* matched subpath */
matched = 1;
can_abbreviate_output = 1;
break;
}
}
/* If none of the patterns matched, stop now */
if (!matched)
return 0;
}
add_to_tip_table(oid, path, can_abbreviate_output);
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
break; /* broken repository */
o = parse_object(the_repository, &t->tagged->oid);
deref = 1;
taggerdate = t->date;
}
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
int from_tag = starts_with(path, "refs/tags/");
if (taggerdate == TIME_MAX)
taggerdate = ((struct commit *)o)->date;
path = name_ref_abbrev(path, can_abbreviate_output);
name_rev(commit, xstrdup(path), taggerdate, 0, 0,
from_tag, deref);
}
return 0;
}
static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
{
struct tip_table_entry *table = table_;
return table[ix].oid.hash;
}
static const char *get_exact_ref_match(const struct object *o)
{
int found;
if (!tip_table.table || !tip_table.nr)
return NULL;
if (!tip_table.sorted) {
QSORT(tip_table.table, tip_table.nr, tipcmp);
tip_table.sorted = 1;
}
found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr,
nth_tip_table_ent);
if (0 <= found)
return tip_table.table[found].refname;
return NULL;
}
/* may return a constant string or use "buf" as scratch space */
static const char *get_rev_name(const struct object *o, struct strbuf *buf)
{
struct rev_name *n;
struct commit *c;
if (o->type != OBJ_COMMIT)
return get_exact_ref_match(o);
c = (struct commit *) o;
n = get_commit_rev_name(c);
if (!n)
return NULL;
if (!n->generation)
return n->tip_name;
else {
int len = strlen(n->tip_name);
if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
len -= 2;
strbuf_reset(buf);
strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation);
return buf->buf;
}
}
static void show_name(const struct object *obj,
const char *caller_name,
int always, int allow_undefined, int name_only)
{
const char *name;
const struct object_id *oid = &obj->oid;
struct strbuf buf = STRBUF_INIT;
if (!name_only)
printf("%s ", caller_name ? caller_name : oid_to_hex(oid));
name = get_rev_name(obj, &buf);
if (name)
printf("%s\n", name);
else if (allow_undefined)
printf("undefined\n");
else if (always)
printf("%s\n", find_unique_abbrev(oid, DEFAULT_ABBREV));
else
die("cannot describe '%s'", oid_to_hex(oid));
strbuf_release(&buf);
}
static char const * const name_rev_usage[] = {
N_("git name-rev [<options>] <commit>..."),
N_("git name-rev [<options>] --all"),
N_("git name-rev [<options>] --stdin"),
NULL
};
static void name_rev_line(char *p, struct name_ref_data *data)
{
struct strbuf buf = STRBUF_INIT;
int counter = 0;
char *p_start;
const unsigned hexsz = the_hash_algo->hexsz;
for (p_start = p; *p; p++) {
#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
if (!ishex(*p))
counter = 0;
else if (++counter == hexsz &&
!ishex(*(p+1))) {
struct object_id oid;
const char *name = NULL;
char c = *(p+1);
int p_len = p - p_start + 1;
counter = 0;
*(p+1) = 0;
if (!get_oid(p - (hexsz - 1), &oid)) {
struct object *o =
lookup_object(the_repository, &oid);
if (o)
name = get_rev_name(o, &buf);
}
*(p+1) = c;
if (!name)
continue;
if (data->name_only)
printf("%.*s%s", p_len - hexsz, p_start, name);
else
printf("%.*s (%s)", p_len, p_start, name);
p_start = p + 1;
}
}
/* flush */
if (p_start != p)
fwrite(p_start, p - p_start, 1, stdout);
strbuf_release(&buf);
}
int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = OBJECT_ARRAY_INIT;
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
struct option opts[] = {
OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
N_("only use refs matching <pattern>")),
OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
N_("ignore refs matching <pattern>")),
OPT_GROUP(""),
OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
OPT_BOOL(0, "always", &always,
N_("show abbreviated commit object as fallback")),
{
/* A Hidden OPT_BOOL */
OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL,
N_("dereference tags in the input (internal use)"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1,
},
OPT_END(),
};
init_commit_rev_name(&rev_names);
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
if (all + transform_stdin + !!argc > 1) {
error("Specify either a list, or --all, not both!");
usage_with_options(name_rev_usage, opts);
}
if (all || transform_stdin)
cutoff = 0;
for (; argc; argc--, argv++) {
struct object_id oid;
struct object *object;
struct commit *commit;
if (get_oid(*argv, &oid)) {
fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
*argv);
continue;
}
commit = NULL;
object = parse_object(the_repository, &oid);
if (object) {
struct object *peeled = deref_tag(the_repository,
object, *argv, 0);
if (peeled && peeled->type == OBJ_COMMIT)
commit = (struct commit *)peeled;
}
if (!object) {
fprintf(stderr, "Could not get object for %s. Skipping.\n",
*argv);
continue;
}
if (commit) {
if (cutoff > commit->date)
cutoff = commit->date;
}
if (peel_tag) {
if (!commit) {
fprintf(stderr, "Could not get commit for %s. Skipping.\n",
*argv);
continue;
}
object = (struct object *)commit;
}
add_object_array(object, *argv, &revs);
}
if (cutoff)
cutoff = cutoff - CUTOFF_DATE_SLOP;
for_each_ref(name_ref, &data);
if (transform_stdin) {
char buffer[2048];
while (!feof(stdin)) {
char *p = fgets(buffer, sizeof(buffer), stdin);
if (!p)
break;
name_rev_line(p, &data);
}
} else if (all) {
int i, max;
max = get_max_object_index();
for (i = 0; i < max; i++) {
struct object *obj = get_indexed_object(i);
if (!obj || obj->type != OBJ_COMMIT)
continue;
show_name(obj, NULL,
always, allow_undefined, data.name_only);
}
} else {
int i;
for (i = 0; i < revs.nr; i++)
show_name(revs.objects[i].item, revs.objects[i].name,
always, allow_undefined, data.name_only);
}
UNLEAK(revs);
return 0;
}

1039
third_party/git/builtin/notes.c vendored Normal file

File diff suppressed because it is too large Load diff

3531
third_party/git/builtin/pack-objects.c vendored Normal file

File diff suppressed because it is too large Load diff

653
third_party/git/builtin/pack-redundant.c vendored Normal file
View file

@ -0,0 +1,653 @@
/*
*
* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
*
* This file is licensed under the GPL v2.
*
*/
#include "builtin.h"
#include "repository.h"
#include "packfile.h"
#include "object-store.h"
#define BLKSIZE 512
static const char pack_redundant_usage[] =
"git pack-redundant [--verbose] [--alt-odb] (--all | <filename.pack>...)";
static int load_all_packs, verbose, alt_odb;
struct llist_item {
struct llist_item *next;
const struct object_id *oid;
};
static struct llist {
struct llist_item *front;
struct llist_item *back;
size_t size;
} *all_objects; /* all objects which must be present in local packfiles */
static struct pack_list {
struct pack_list *next;
struct packed_git *pack;
struct llist *unique_objects;
struct llist *remaining_objects;
size_t all_objects_size;
} *local_packs = NULL, *altodb_packs = NULL;
static struct llist_item *free_nodes;
static inline void llist_item_put(struct llist_item *item)
{
item->next = free_nodes;
free_nodes = item;
}
static inline struct llist_item *llist_item_get(void)
{
struct llist_item *new_item;
if ( free_nodes ) {
new_item = free_nodes;
free_nodes = free_nodes->next;
} else {
int i = 1;
ALLOC_ARRAY(new_item, BLKSIZE);
for (; i < BLKSIZE; i++)
llist_item_put(&new_item[i]);
}
return new_item;
}
static inline void llist_init(struct llist **list)
{
*list = xmalloc(sizeof(struct llist));
(*list)->front = (*list)->back = NULL;
(*list)->size = 0;
}
static struct llist * llist_copy(struct llist *list)
{
struct llist *ret;
struct llist_item *new_item, *old_item, *prev;
llist_init(&ret);
if ((ret->size = list->size) == 0)
return ret;
new_item = ret->front = llist_item_get();
new_item->oid = list->front->oid;
old_item = list->front->next;
while (old_item) {
prev = new_item;
new_item = llist_item_get();
prev->next = new_item;
new_item->oid = old_item->oid;
old_item = old_item->next;
}
new_item->next = NULL;
ret->back = new_item;
return ret;
}
static inline struct llist_item *llist_insert(struct llist *list,
struct llist_item *after,
const struct object_id *oid)
{
struct llist_item *new_item = llist_item_get();
new_item->oid = oid;
new_item->next = NULL;
if (after != NULL) {
new_item->next = after->next;
after->next = new_item;
if (after == list->back)
list->back = new_item;
} else {/* insert in front */
if (list->size == 0)
list->back = new_item;
else
new_item->next = list->front;
list->front = new_item;
}
list->size++;
return new_item;
}
static inline struct llist_item *llist_insert_back(struct llist *list,
const struct object_id *oid)
{
return llist_insert(list, list->back, oid);
}
static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
const struct object_id *oid, struct llist_item *hint)
{
struct llist_item *prev = NULL, *l;
l = (hint == NULL) ? list->front : hint;
while (l) {
int cmp = oidcmp(l->oid, oid);
if (cmp > 0) { /* we insert before this entry */
return llist_insert(list, prev, oid);
}
if (!cmp) { /* already exists */
return l;
}
prev = l;
l = l->next;
}
/* insert at the end */
return llist_insert_back(list, oid);
}
/* returns a pointer to an item in front of sha1 */
static inline struct llist_item * llist_sorted_remove(struct llist *list, const struct object_id *oid, struct llist_item *hint)
{
struct llist_item *prev, *l;
redo_from_start:
l = (hint == NULL) ? list->front : hint;
prev = NULL;
while (l) {
const int cmp = oidcmp(l->oid, oid);
if (cmp > 0) /* not in list, since sorted */
return prev;
if (!cmp) { /* found */
if (prev == NULL) {
if (hint != NULL && hint != list->front) {
/* we don't know the previous element */
hint = NULL;
goto redo_from_start;
}
list->front = l->next;
} else
prev->next = l->next;
if (l == list->back)
list->back = prev;
llist_item_put(l);
list->size--;
return prev;
}
prev = l;
l = l->next;
}
return prev;
}
/* computes A\B */
static void llist_sorted_difference_inplace(struct llist *A,
struct llist *B)
{
struct llist_item *hint, *b;
hint = NULL;
b = B->front;
while (b) {
hint = llist_sorted_remove(A, b->oid, hint);
b = b->next;
}
}
static inline struct pack_list * pack_list_insert(struct pack_list **pl,
struct pack_list *entry)
{
struct pack_list *p = xmalloc(sizeof(struct pack_list));
memcpy(p, entry, sizeof(struct pack_list));
p->next = *pl;
*pl = p;
return p;
}
static inline size_t pack_list_size(struct pack_list *pl)
{
size_t ret = 0;
while (pl) {
ret++;
pl = pl->next;
}
return ret;
}
static struct pack_list * pack_list_difference(const struct pack_list *A,
const struct pack_list *B)
{
struct pack_list *ret;
const struct pack_list *pl;
if (A == NULL)
return NULL;
pl = B;
while (pl != NULL) {
if (A->pack == pl->pack)
return pack_list_difference(A->next, B);
pl = pl->next;
}
ret = xmalloc(sizeof(struct pack_list));
memcpy(ret, A, sizeof(struct pack_list));
ret->next = pack_list_difference(A->next, B);
return ret;
}
static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
{
unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
const unsigned char *p1_base, *p2_base;
struct llist_item *p1_hint = NULL, *p2_hint = NULL;
const unsigned int hashsz = the_hash_algo->rawsz;
if (!p1->unique_objects)
p1->unique_objects = llist_copy(p1->remaining_objects);
if (!p2->unique_objects)
p2->unique_objects = llist_copy(p2->remaining_objects);
p1_base = p1->pack->index_data;
p2_base = p2->pack->index_data;
p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
p1_step = hashsz + ((p1->pack->index_version < 2) ? 4 : 0);
p2_step = hashsz + ((p2->pack->index_version < 2) ? 4 : 0);
while (p1_off < p1->pack->num_objects * p1_step &&
p2_off < p2->pack->num_objects * p2_step)
{
const int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
/* cmp ~ p1 - p2 */
if (cmp == 0) {
p1_hint = llist_sorted_remove(p1->unique_objects,
(const struct object_id *)(p1_base + p1_off),
p1_hint);
p2_hint = llist_sorted_remove(p2->unique_objects,
(const struct object_id *)(p1_base + p1_off),
p2_hint);
p1_off += p1_step;
p2_off += p2_step;
continue;
}
if (cmp < 0) { /* p1 has the object, p2 doesn't */
p1_off += p1_step;
} else { /* p2 has the object, p1 doesn't */
p2_off += p2_step;
}
}
}
static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
{
size_t ret = 0;
unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
const unsigned char *p1_base, *p2_base;
const unsigned int hashsz = the_hash_algo->rawsz;
p1_base = p1->index_data;
p2_base = p2->index_data;
p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
p1_step = hashsz + ((p1->index_version < 2) ? 4 : 0);
p2_step = hashsz + ((p2->index_version < 2) ? 4 : 0);
while (p1_off < p1->num_objects * p1_step &&
p2_off < p2->num_objects * p2_step)
{
int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
/* cmp ~ p1 - p2 */
if (cmp == 0) {
ret++;
p1_off += p1_step;
p2_off += p2_step;
continue;
}
if (cmp < 0) { /* p1 has the object, p2 doesn't */
p1_off += p1_step;
} else { /* p2 has the object, p1 doesn't */
p2_off += p2_step;
}
}
return ret;
}
/* another O(n^2) function ... */
static size_t get_pack_redundancy(struct pack_list *pl)
{
struct pack_list *subset;
size_t ret = 0;
if (pl == NULL)
return 0;
while ((subset = pl->next)) {
while (subset) {
ret += sizeof_union(pl->pack, subset->pack);
subset = subset->next;
}
pl = pl->next;
}
return ret;
}
static inline off_t pack_set_bytecount(struct pack_list *pl)
{
off_t ret = 0;
while (pl) {
ret += pl->pack->pack_size;
ret += pl->pack->index_size;
pl = pl->next;
}
return ret;
}
static int cmp_remaining_objects(const void *a, const void *b)
{
struct pack_list *pl_a = *((struct pack_list **)a);
struct pack_list *pl_b = *((struct pack_list **)b);
if (pl_a->remaining_objects->size == pl_b->remaining_objects->size) {
/* have the same remaining_objects, big pack first */
if (pl_a->all_objects_size == pl_b->all_objects_size)
return 0;
else if (pl_a->all_objects_size < pl_b->all_objects_size)
return 1;
else
return -1;
} else if (pl_a->remaining_objects->size < pl_b->remaining_objects->size) {
/* sort by remaining objects, more objects first */
return 1;
} else {
return -1;
}
}
/* Sort pack_list, greater size of remaining_objects first */
static void sort_pack_list(struct pack_list **pl)
{
struct pack_list **ary, *p;
int i;
size_t n = pack_list_size(*pl);
if (n < 2)
return;
/* prepare an array of packed_list for easier sorting */
ary = xcalloc(n, sizeof(struct pack_list *));
for (n = 0, p = *pl; p; p = p->next)
ary[n++] = p;
QSORT(ary, n, cmp_remaining_objects);
/* link them back again */
for (i = 0; i < n - 1; i++)
ary[i]->next = ary[i + 1];
ary[n - 1]->next = NULL;
*pl = ary[0];
free(ary);
}
static void minimize(struct pack_list **min)
{
struct pack_list *pl, *unique = NULL, *non_unique = NULL;
struct llist *missing, *unique_pack_objects;
pl = local_packs;
while (pl) {
if (pl->unique_objects->size)
pack_list_insert(&unique, pl);
else
pack_list_insert(&non_unique, pl);
pl = pl->next;
}
/* find out which objects are missing from the set of unique packs */
missing = llist_copy(all_objects);
pl = unique;
while (pl) {
llist_sorted_difference_inplace(missing, pl->remaining_objects);
pl = pl->next;
}
*min = unique;
/* return if there are no objects missing from the unique set */
if (missing->size == 0) {
free(missing);
return;
}
unique_pack_objects = llist_copy(all_objects);
llist_sorted_difference_inplace(unique_pack_objects, missing);
/* remove unique pack objects from the non_unique packs */
pl = non_unique;
while (pl) {
llist_sorted_difference_inplace(pl->remaining_objects, unique_pack_objects);
pl = pl->next;
}
while (non_unique) {
/* sort the non_unique packs, greater size of remaining_objects first */
sort_pack_list(&non_unique);
if (non_unique->remaining_objects->size == 0)
break;
pack_list_insert(min, non_unique);
for (pl = non_unique->next; pl && pl->remaining_objects->size > 0; pl = pl->next)
llist_sorted_difference_inplace(pl->remaining_objects, non_unique->remaining_objects);
non_unique = non_unique->next;
}
}
static void load_all_objects(void)
{
struct pack_list *pl = local_packs;
struct llist_item *hint, *l;
llist_init(&all_objects);
while (pl) {
hint = NULL;
l = pl->remaining_objects->front;
while (l) {
hint = llist_insert_sorted_unique(all_objects,
l->oid, hint);
l = l->next;
}
pl = pl->next;
}
/* remove objects present in remote packs */
pl = altodb_packs;
while (pl) {
llist_sorted_difference_inplace(all_objects, pl->remaining_objects);
pl = pl->next;
}
}
/* this scales like O(n^2) */
static void cmp_local_packs(void)
{
struct pack_list *subset, *pl = local_packs;
while ((subset = pl)) {
while ((subset = subset->next))
cmp_two_packs(pl, subset);
pl = pl->next;
}
}
static void scan_alt_odb_packs(void)
{
struct pack_list *local, *alt;
alt = altodb_packs;
while (alt) {
local = local_packs;
while (local) {
llist_sorted_difference_inplace(local->remaining_objects,
alt->remaining_objects);
local = local->next;
}
alt = alt->next;
}
}
static struct pack_list * add_pack(struct packed_git *p)
{
struct pack_list l;
unsigned long off = 0, step;
const unsigned char *base;
if (!p->pack_local && !(alt_odb || verbose))
return NULL;
l.pack = p;
llist_init(&l.remaining_objects);
if (open_pack_index(p))
return NULL;
base = p->index_data;
base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0);
while (off < p->num_objects * step) {
llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off));
off += step;
}
l.all_objects_size = l.remaining_objects->size;
l.unique_objects = NULL;
if (p->pack_local)
return pack_list_insert(&local_packs, &l);
else
return pack_list_insert(&altodb_packs, &l);
}
static struct pack_list * add_pack_file(const char *filename)
{
struct packed_git *p = get_all_packs(the_repository);
if (strlen(filename) < 40)
die("Bad pack filename: %s", filename);
while (p) {
if (strstr(p->pack_name, filename))
return add_pack(p);
p = p->next;
}
die("Filename %s not found in packed_git", filename);
}
static void load_all(void)
{
struct packed_git *p = get_all_packs(the_repository);
while (p) {
add_pack(p);
p = p->next;
}
}
int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
{
int i;
struct pack_list *min = NULL, *red, *pl;
struct llist *ignore;
struct object_id *oid;
char buf[GIT_MAX_HEXSZ + 2]; /* hex hash + \n + \0 */
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(pack_redundant_usage);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--")) {
i++;
break;
}
if (!strcmp(arg, "--all")) {
load_all_packs = 1;
continue;
}
if (!strcmp(arg, "--verbose")) {
verbose = 1;
continue;
}
if (!strcmp(arg, "--alt-odb")) {
alt_odb = 1;
continue;
}
if (*arg == '-')
usage(pack_redundant_usage);
else
break;
}
if (load_all_packs)
load_all();
else
while (*(argv + i) != NULL)
add_pack_file(*(argv + i++));
if (local_packs == NULL)
die("Zero packs found!");
load_all_objects();
if (alt_odb)
scan_alt_odb_packs();
/* ignore objects given on stdin */
llist_init(&ignore);
if (!isatty(0)) {
while (fgets(buf, sizeof(buf), stdin)) {
oid = xmalloc(sizeof(*oid));
if (get_oid_hex(buf, oid))
die("Bad object ID on stdin: %s", buf);
llist_insert_sorted_unique(ignore, oid, NULL);
}
}
llist_sorted_difference_inplace(all_objects, ignore);
pl = local_packs;
while (pl) {
llist_sorted_difference_inplace(pl->remaining_objects, ignore);
pl = pl->next;
}
cmp_local_packs();
minimize(&min);
if (verbose) {
fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
(unsigned long)pack_list_size(altodb_packs));
fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
pl = min;
while (pl) {
fprintf(stderr, "\t%s\n", pl->pack->pack_name);
pl = pl->next;
}
fprintf(stderr, "containing %lu duplicate objects "
"with a total size of %lukb.\n",
(unsigned long)get_pack_redundancy(min),
(unsigned long)pack_set_bytecount(min)/1024);
fprintf(stderr, "A total of %lu unique objects were considered.\n",
(unsigned long)all_objects->size);
fprintf(stderr, "Redundant packs (with indexes):\n");
}
pl = red = pack_list_difference(local_packs, min);
while (pl) {
printf("%s\n%s\n",
sha1_pack_index_name(pl->pack->hash),
pl->pack->pack_name);
pl = pl->next;
}
if (verbose)
fprintf(stderr, "%luMB of redundant packs in total.\n",
(unsigned long)pack_set_bytecount(red)/(1024*1024));
return 0;
}

24
third_party/git/builtin/pack-refs.c vendored Normal file
View file

@ -0,0 +1,24 @@
#include "builtin.h"
#include "config.h"
#include "parse-options.h"
#include "refs.h"
#include "repository.h"
static char const * const pack_refs_usage[] = {
N_("git pack-refs [<options>]"),
NULL
};
int cmd_pack_refs(int argc, const char **argv, const char *prefix)
{
unsigned int flags = PACK_REFS_PRUNE;
struct option opts[] = {
OPT_BIT(0, "all", &flags, N_("pack everything"), PACK_REFS_ALL),
OPT_BIT(0, "prune", &flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
OPT_END(),
};
git_config(git_default_config, NULL);
if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
usage_with_options(pack_refs_usage, opts);
return refs_pack_refs(get_main_ref_store(the_repository), flags);
}

184
third_party/git/builtin/patch-id.c vendored Normal file
View file

@ -0,0 +1,184 @@
#include "builtin.h"
#include "config.h"
#include "diff.h"
static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
{
char name[50];
if (!patchlen)
return;
memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
printf("%s %s\n", oid_to_hex(result), name);
}
static int remove_space(char *line)
{
char *src = line;
char *dst = line;
unsigned char c;
while ((c = *src++) != '\0') {
if (!isspace(c))
*dst++ = c;
}
return dst - line;
}
static int scan_hunk_header(const char *p, int *p_before, int *p_after)
{
static const char digits[] = "0123456789";
const char *q, *r;
int n;
q = p + 4;
n = strspn(q, digits);
if (q[n] == ',') {
q += n + 1;
n = strspn(q, digits);
}
if (n == 0 || q[n] != ' ' || q[n+1] != '+')
return 0;
r = q + n + 2;
n = strspn(r, digits);
if (r[n] == ',') {
r += n + 1;
n = strspn(r, digits);
}
if (n == 0)
return 0;
*p_before = atoi(q);
*p_after = atoi(r);
return 1;
}
static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
git_SHA_CTX ctx;
git_SHA1_Init(&ctx);
oidclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
char *line = line_buf->buf;
const char *p = line;
int len;
if (!skip_prefix(line, "diff-tree ", &p) &&
!skip_prefix(line, "commit ", &p) &&
!skip_prefix(line, "From ", &p) &&
starts_with(line, "\\ ") && 12 < strlen(line))
continue;
if (!get_oid_hex(p, next_oid)) {
found_next = 1;
break;
}
/* Ignore commit comments */
if (!patchlen && !starts_with(line, "diff "))
continue;
/* Parsing diff header? */
if (before == -1) {
if (starts_with(line, "index "))
continue;
else if (starts_with(line, "--- "))
before = after = 1;
else if (!isalpha(line[0]))
break;
}
/* Looking for a valid hunk header? */
if (before == 0 && after == 0) {
if (starts_with(line, "@@ -")) {
/* Parse next hunk, but ignore line numbers. */
scan_hunk_header(line, &before, &after);
continue;
}
/* Split at the end of the patch. */
if (!starts_with(line, "diff "))
break;
/* Else we're parsing another header. */
if (stable)
flush_one_hunk(result, &ctx);
before = after = -1;
}
/* If we get here, we're inside a hunk. */
if (line[0] == '-' || line[0] == ' ')
before--;
if (line[0] == '+' || line[0] == ' ')
after--;
/* Compute the sha without whitespace */
len = remove_space(line);
patchlen += len;
git_SHA1_Update(&ctx, line, len);
}
if (!found_next)
oidclr(next_oid);
flush_one_hunk(result, &ctx);
return patchlen;
}
static void generate_id_list(int stable)
{
struct object_id oid, n, result;
int patchlen;
struct strbuf line_buf = STRBUF_INIT;
oidclr(&oid);
while (!feof(stdin)) {
patchlen = get_one_patchid(&n, &result, &line_buf, stable);
flush_current_id(patchlen, &oid, &result);
oidcpy(&oid, &n);
}
strbuf_release(&line_buf);
}
static const char patch_id_usage[] = "git patch-id [--stable | --unstable]";
static int git_patch_id_config(const char *var, const char *value, void *cb)
{
int *stable = cb;
if (!strcmp(var, "patchid.stable")) {
*stable = git_config_bool(var, value);
return 0;
}
return git_default_config(var, value, cb);
}
int cmd_patch_id(int argc, const char **argv, const char *prefix)
{
int stable = -1;
git_config(git_patch_id_config, &stable);
/* If nothing is set, default to unstable. */
if (stable < 0)
stable = 0;
if (argc == 2 && !strcmp(argv[1], "--stable"))
stable = 1;
else if (argc == 2 && !strcmp(argv[1], "--unstable"))
stable = 0;
else if (argc != 1)
usage(patch_id_usage);
generate_id_list(stable);
return 0;
}

73
third_party/git/builtin/prune-packed.c vendored Normal file
View file

@ -0,0 +1,73 @@
#include "builtin.h"
#include "cache.h"
#include "progress.h"
#include "parse-options.h"
#include "packfile.h"
#include "object-store.h"
static const char * const prune_packed_usage[] = {
N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
NULL
};
static struct progress *progress;
static int prune_subdir(unsigned int nr, const char *path, void *data)
{
int *opts = data;
display_progress(progress, nr + 1);
if (!(*opts & PRUNE_PACKED_DRY_RUN))
rmdir(path);
return 0;
}
static int prune_object(const struct object_id *oid, const char *path,
void *data)
{
int *opts = data;
if (!has_object_pack(oid))
return 0;
if (*opts & PRUNE_PACKED_DRY_RUN)
printf("rm -f %s\n", path);
else
unlink_or_warn(path);
return 0;
}
void prune_packed_objects(int opts)
{
if (opts & PRUNE_PACKED_VERBOSE)
progress = start_delayed_progress(_("Removing duplicate objects"), 256);
for_each_loose_file_in_objdir(get_object_directory(),
prune_object, NULL, prune_subdir, &opts);
/* Ensure we show 100% before finishing progress */
display_progress(progress, 256);
stop_progress(&progress);
}
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
const struct option prune_packed_options[] = {
OPT_BIT('n', "dry-run", &opts, N_("dry run"),
PRUNE_PACKED_DRY_RUN),
OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"),
PRUNE_PACKED_VERBOSE),
OPT_END()
};
argc = parse_options(argc, argv, prefix, prune_packed_options,
prune_packed_usage, 0);
if (argc > 0)
usage_msg_opt(_("too many arguments"),
prune_packed_usage,
prune_packed_options);
prune_packed_objects(opts);
return 0;
}

187
third_party/git/builtin/prune.c vendored Normal file
View file

@ -0,0 +1,187 @@
#include "cache.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "builtin.h"
#include "reachable.h"
#include "parse-options.h"
#include "progress.h"
#include "object-store.h"
static const char * const prune_usage[] = {
N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
NULL
};
static int show_only;
static int verbose;
static timestamp_t expire;
static int show_progress = -1;
static int prune_tmp_file(const char *fullpath)
{
struct stat st;
if (lstat(fullpath, &st))
return error("Could not stat '%s'", fullpath);
if (st.st_mtime > expire)
return 0;
if (show_only || verbose)
printf("Removing stale temporary file %s\n", fullpath);
if (!show_only)
unlink_or_warn(fullpath);
return 0;
}
static void perform_reachability_traversal(struct rev_info *revs)
{
static int initialized;
struct progress *progress = NULL;
if (initialized)
return;
if (show_progress)
progress = start_delayed_progress(_("Checking connectivity"), 0);
mark_reachable_objects(revs, 1, expire, progress);
stop_progress(&progress);
initialized = 1;
}
static int is_object_reachable(const struct object_id *oid,
struct rev_info *revs)
{
struct object *obj;
perform_reachability_traversal(revs);
obj = lookup_object(the_repository, oid);
return obj && (obj->flags & SEEN);
}
static int prune_object(const struct object_id *oid, const char *fullpath,
void *data)
{
struct rev_info *revs = data;
struct stat st;
if (is_object_reachable(oid, revs))
return 0;
if (lstat(fullpath, &st)) {
/* report errors, but do not stop pruning */
error("Could not stat '%s'", fullpath);
return 0;
}
if (st.st_mtime > expire)
return 0;
if (show_only || verbose) {
enum object_type type = oid_object_info(the_repository, oid,
NULL);
printf("%s %s\n", oid_to_hex(oid),
(type > 0) ? type_name(type) : "unknown");
}
if (!show_only)
unlink_or_warn(fullpath);
return 0;
}
static int prune_cruft(const char *basename, const char *path, void *data)
{
if (starts_with(basename, "tmp_obj_"))
prune_tmp_file(path);
else
fprintf(stderr, "bad sha1 file: %s\n", path);
return 0;
}
static int prune_subdir(unsigned int nr, const char *path, void *data)
{
if (!show_only)
rmdir(path);
return 0;
}
/*
* Write errors (particularly out of space) can result in
* failed temporary packs (and more rarely indexes and other
* files beginning with "tmp_") accumulating in the object
* and the pack directories.
*/
static void remove_temporary_files(const char *path)
{
DIR *dir;
struct dirent *de;
dir = opendir(path);
if (!dir) {
fprintf(stderr, "Unable to open directory %s\n", path);
return;
}
while ((de = readdir(dir)) != NULL)
if (starts_with(de->d_name, "tmp_"))
prune_tmp_file(mkpath("%s/%s", path, de->d_name));
closedir(dir);
}
int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
int exclude_promisor_objects = 0;
const struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire objects older than <time>")),
OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects,
N_("limit traversal to objects outside promisor packfiles")),
OPT_END()
};
char *s;
expire = TIME_MAX;
save_commit_buffer = 0;
read_replace_refs = 0;
ref_paranoia = 1;
repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
if (repository_format_precious_objects)
die(_("cannot prune in a precious-objects repo"));
while (argc--) {
struct object_id oid;
const char *name = *argv++;
if (!get_oid(name, &oid)) {
struct object *object = parse_object_or_die(&oid,
name);
add_pending_object(&revs, object, "");
}
else
die("unrecognized argument: %s", name);
}
if (show_progress == -1)
show_progress = isatty(2);
if (exclude_promisor_objects) {
fetch_if_missing = 0;
revs.exclude_promisor_objects = 1;
}
for_each_loose_file_in_objdir(get_object_directory(), prune_object,
prune_cruft, prune_subdir, &revs);
prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
remove_temporary_files(get_object_directory());
s = mkpathdup("%s/pack", get_object_directory());
remove_temporary_files(s);
free(s);
if (is_repository_shallow(the_repository)) {
perform_reachability_traversal(&revs);
prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0);
}
return 0;
}

1016
third_party/git/builtin/pull.c vendored Normal file

File diff suppressed because it is too large Load diff

646
third_party/git/builtin/push.c vendored Normal file
View file

@ -0,0 +1,646 @@
/*
* "git push"
*/
#include "cache.h"
#include "config.h"
#include "refs.h"
#include "refspec.h"
#include "run-command.h"
#include "builtin.h"
#include "remote.h"
#include "transport.h"
#include "parse-options.h"
#include "submodule.h"
#include "submodule-config.h"
#include "send-pack.h"
#include "color.h"
static const char * const push_usage[] = {
N_("git push [<options>] [<repository> [<refspec>...]]"),
NULL,
};
static int push_use_color = -1;
static char push_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
GIT_COLOR_RED, /* ERROR */
};
enum color_push {
PUSH_COLOR_RESET = 0,
PUSH_COLOR_ERROR = 1
};
static int parse_push_color_slot(const char *slot)
{
if (!strcasecmp(slot, "reset"))
return PUSH_COLOR_RESET;
if (!strcasecmp(slot, "error"))
return PUSH_COLOR_ERROR;
return -1;
}
static const char *push_get_color(enum color_push ix)
{
if (want_color_stderr(push_use_color))
return push_colors[ix];
return "";
}
static int thin = 1;
static int deleterefs;
static const char *receivepack;
static int verbosity;
static int progress = -1;
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static enum transport_family family;
static struct push_cas_option cas;
static struct refspec rs = REFSPEC_INIT_PUSH;
static struct string_list push_options_config = STRING_LIST_INIT_DUP;
static const char *map_refspec(const char *ref,
struct remote *remote, struct ref *local_refs)
{
struct ref *matched = NULL;
/* Does "ref" uniquely name our ref? */
if (count_refspec_match(ref, local_refs, &matched) != 1)
return ref;
if (remote->push.nr) {
struct refspec_item query;
memset(&query, 0, sizeof(struct refspec_item));
query.src = matched->name;
if (!query_refspecs(&remote->push, &query) && query.dst) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s%s:%s",
query.force ? "+" : "",
query.src, query.dst);
return strbuf_detach(&buf, NULL);
}
}
if (push_default == PUSH_DEFAULT_UPSTREAM &&
starts_with(matched->name, "refs/heads/")) {
struct branch *branch = branch_get(matched->name + 11);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s:%s",
ref, branch->merge[0]->src);
return strbuf_detach(&buf, NULL);
}
}
return ref;
}
static void set_refspecs(const char **refs, int nr, const char *repo)
{
struct remote *remote = NULL;
struct ref *local_refs = NULL;
int i;
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
ref = refs[i];
if (deleterefs)
strbuf_addf(&tagref, ":refs/tags/%s", ref);
else
strbuf_addf(&tagref, "refs/tags/%s", ref);
ref = strbuf_detach(&tagref, NULL);
} else if (deleterefs) {
struct strbuf delref = STRBUF_INIT;
if (strchr(ref, ':'))
die(_("--delete only accepts plain target ref names"));
strbuf_addf(&delref, ":%s", ref);
ref = strbuf_detach(&delref, NULL);
} else if (!strchr(ref, ':')) {
if (!remote) {
/* lazily grab remote and local_refs */
remote = remote_get(repo);
local_refs = get_local_heads();
}
ref = map_refspec(ref, remote, local_refs);
}
refspec_append(&rs, ref);
}
}
static int push_url_of_remote(struct remote *remote, const char ***url_p)
{
if (remote->pushurl_nr) {
*url_p = remote->pushurl;
return remote->pushurl_nr;
}
*url_p = remote->url;
return remote->url_nr;
}
static NORETURN int die_push_simple(struct branch *branch,
struct remote *remote)
{
/*
* There's no point in using shorten_unambiguous_ref here,
* as the ambiguity would be on the remote side, not what
* we have locally. Plus, this is supposed to be the simple
* mode. If the user is doing something crazy like setting
* upstream to a non-branch, we should probably be showing
* them the big ugly fully qualified ref.
*/
const char *advice_maybe = "";
const char *short_upstream = branch->merge[0]->src;
skip_prefix(short_upstream, "refs/heads/", &short_upstream);
/*
* Don't show advice for people who explicitly set
* push.default.
*/
if (push_default == PUSH_DEFAULT_UNSPECIFIED)
advice_maybe = _("\n"
"To choose either option permanently, "
"see push.default in 'git help config'.");
die(_("The upstream branch of your current branch does not match\n"
"the name of your current branch. To push to the upstream branch\n"
"on the remote, use\n"
"\n"
" git push %s HEAD:%s\n"
"\n"
"To push to the branch of the same name on the remote, use\n"
"\n"
" git push %s HEAD\n"
"%s"),
remote->name, short_upstream,
remote->name, advice_maybe);
}
static const char message_detached_head_die[] =
N_("You are not currently on a branch.\n"
"To push the history leading to the current (detached HEAD)\n"
"state now, use\n"
"\n"
" git push %s HEAD:<name-of-remote-branch>\n");
static void setup_push_upstream(struct remote *remote, struct branch *branch,
int triangular, int simple)
{
struct strbuf refspec = STRBUF_INIT;
if (!branch)
die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
" git push --set-upstream %s %s\n"),
branch->name,
remote->name,
branch->name);
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
if (triangular)
die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
if (simple) {
/* Additional safety */
if (strcmp(branch->refname, branch->merge[0]->src))
die_push_simple(branch, remote);
}
strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
refspec_append(&rs, refspec.buf);
}
static void setup_push_current(struct remote *remote, struct branch *branch)
{
struct strbuf refspec = STRBUF_INIT;
if (!branch)
die(_(message_detached_head_die), remote->name);
strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname);
refspec_append(&rs, refspec.buf);
}
static int is_workflow_triangular(struct remote *remote)
{
struct remote *fetch_remote = remote_get(NULL);
return (fetch_remote && fetch_remote != remote);
}
static void setup_default_push_refspecs(struct remote *remote)
{
struct branch *branch = branch_get(NULL);
int triangular = is_workflow_triangular(remote);
switch (push_default) {
default:
case PUSH_DEFAULT_MATCHING:
refspec_append(&rs, ":");
break;
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
if (triangular)
setup_push_current(remote, branch);
else
setup_push_upstream(remote, branch, triangular, 1);
break;
case PUSH_DEFAULT_UPSTREAM:
setup_push_upstream(remote, branch, triangular, 0);
break;
case PUSH_DEFAULT_CURRENT:
setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
die(_("You didn't specify any refspecs to push, and "
"push.default is \"nothing\"."));
break;
}
}
static const char message_advice_pull_before_push[] =
N_("Updates were rejected because the tip of your current branch is behind\n"
"its remote counterpart. Integrate the remote changes (e.g.\n"
"'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
"counterpart. Check out this branch and integrate the remote changes\n"
"(e.g. 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_fetch_first[] =
N_("Updates were rejected because the remote contains work that you do\n"
"not have locally. This is usually caused by another repository pushing\n"
"to the same ref. You may want to first integrate the remote changes\n"
"(e.g., 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_already_exists[] =
N_("Updates were rejected because the tag already exists in the remote.");
static const char message_advice_ref_needs_force[] =
N_("You cannot update a remote ref that points at a non-commit object,\n"
"or update a remote ref to make it point at a non-commit object,\n"
"without using the '--force' option.\n");
static void advise_pull_before_push(void)
{
if (!advice_push_non_ff_current || !advice_push_update_rejected)
return;
advise(_(message_advice_pull_before_push));
}
static void advise_checkout_pull_push(void)
{
if (!advice_push_non_ff_matching || !advice_push_update_rejected)
return;
advise(_(message_advice_checkout_pull_push));
}
static void advise_ref_already_exists(void)
{
if (!advice_push_already_exists || !advice_push_update_rejected)
return;
advise(_(message_advice_ref_already_exists));
}
static void advise_ref_fetch_first(void)
{
if (!advice_push_fetch_first || !advice_push_update_rejected)
return;
advise(_(message_advice_ref_fetch_first));
}
static void advise_ref_needs_force(void)
{
if (!advice_push_needs_force || !advice_push_update_rejected)
return;
advise(_(message_advice_ref_needs_force));
}
static int push_with_options(struct transport *transport, struct refspec *rs,
int flags)
{
int err;
unsigned int reject_reasons;
transport_set_verbosity(transport, verbosity, progress);
transport->family = family;
if (receivepack)
transport_set_option(transport,
TRANS_OPT_RECEIVEPACK, receivepack);
transport_set_option(transport, TRANS_OPT_THIN, thin ? "yes" : NULL);
if (!is_empty_cas(&cas)) {
if (!transport->smart_options)
die("underlying transport does not support --%s option",
CAS_OPT_NAME);
transport->smart_options->cas = &cas;
}
if (verbosity > 0)
fprintf(stderr, _("Pushing to %s\n"), transport->url);
err = transport_push(the_repository, transport,
rs, flags, &reject_reasons);
if (err != 0) {
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
error(_("failed to push some refs to '%s'"), transport->url);
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET));
}
err |= transport_disconnect(transport);
if (!err)
return 0;
if (reject_reasons & REJECT_NON_FF_HEAD) {
advise_pull_before_push();
} else if (reject_reasons & REJECT_NON_FF_OTHER) {
advise_checkout_pull_push();
} else if (reject_reasons & REJECT_ALREADY_EXISTS) {
advise_ref_already_exists();
} else if (reject_reasons & REJECT_FETCH_FIRST) {
advise_ref_fetch_first();
} else if (reject_reasons & REJECT_NEEDS_FORCE) {
advise_ref_needs_force();
}
return 1;
}
static int do_push(const char *repo, int flags,
const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
const char **url;
int url_nr;
struct refspec *push_refspec = &rs;
if (!remote) {
if (repo)
die(_("bad repository '%s'"), repo);
die(_("No configured push destination.\n"
"Either specify the URL from the command-line or configure a remote repository using\n"
"\n"
" git remote add <name> <url>\n"
"\n"
"and then push using the remote name\n"
"\n"
" git push <name>\n"));
}
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
if (push_options->nr)
flags |= TRANSPORT_PUSH_OPTIONS;
if (!push_refspec->nr && !(flags & TRANSPORT_PUSH_ALL)) {
if (remote->push.nr) {
push_refspec = &remote->push;
} else if (!(flags & TRANSPORT_PUSH_MIRROR))
setup_default_push_refspecs(remote);
}
errs = 0;
url_nr = push_url_of_remote(remote, &url);
if (url_nr) {
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
if (flags & TRANSPORT_PUSH_OPTIONS)
transport->push_options = push_options;
if (push_with_options(transport, push_refspec, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
if (flags & TRANSPORT_PUSH_OPTIONS)
transport->push_options = push_options;
if (push_with_options(transport, push_refspec, flags))
errs++;
}
return !!errs;
}
static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset)
{
int *recurse_submodules = opt->value;
if (unset)
*recurse_submodules = RECURSE_SUBMODULES_OFF;
else if (arg)
*recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
else
die("%s missing parameter", opt->long_name);
return 0;
}
static void set_push_cert_flags(int *flags, int v)
{
switch (v) {
case SEND_PACK_PUSH_CERT_NEVER:
*flags &= ~(TRANSPORT_PUSH_CERT_ALWAYS | TRANSPORT_PUSH_CERT_IF_ASKED);
break;
case SEND_PACK_PUSH_CERT_ALWAYS:
*flags |= TRANSPORT_PUSH_CERT_ALWAYS;
*flags &= ~TRANSPORT_PUSH_CERT_IF_ASKED;
break;
case SEND_PACK_PUSH_CERT_IF_ASKED:
*flags |= TRANSPORT_PUSH_CERT_IF_ASKED;
*flags &= ~TRANSPORT_PUSH_CERT_ALWAYS;
break;
}
}
static int git_push_config(const char *k, const char *v, void *cb)
{
const char *slot_name;
int *flags = cb;
int status;
status = git_gpg_config(k, v, NULL);
if (status)
return status;
if (!strcmp(k, "push.followtags")) {
if (git_config_bool(k, v))
*flags |= TRANSPORT_PUSH_FOLLOW_TAGS;
else
*flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS;
return 0;
} else if (!strcmp(k, "push.gpgsign")) {
const char *value;
if (!git_config_get_value("push.gpgsign", &value)) {
switch (git_parse_maybe_bool(value)) {
case 0:
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER);
break;
case 1:
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_ALWAYS);
break;
default:
if (value && !strcasecmp(value, "if-asked"))
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
else
return error("Invalid value for '%s'", k);
}
}
} else if (!strcmp(k, "push.recursesubmodules")) {
const char *value;
if (!git_config_get_value("push.recursesubmodules", &value))
recurse_submodules = parse_push_recurse_submodules_arg(k, value);
} else if (!strcmp(k, "submodule.recurse")) {
int val = git_config_bool(k, v) ?
RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF;
recurse_submodules = val;
} else if (!strcmp(k, "push.pushoption")) {
if (!v)
return config_error_nonbool(k);
else
if (!*v)
string_list_clear(&push_options_config, 0);
else
string_list_append(&push_options_config, v);
return 0;
} else if (!strcmp(k, "color.push")) {
push_use_color = git_config_colorbool(k, v);
return 0;
} else if (skip_prefix(k, "color.push.", &slot_name)) {
int slot = parse_push_color_slot(slot_name);
if (slot < 0)
return 0;
if (!v)
return config_error_nonbool(k);
return color_parse(v, push_colors[slot]);
}
return git_default_config(k, v, NULL);
}
int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
int tags = 0;
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
struct string_list push_options_cmdline = STRING_LIST_INIT_DUP;
struct string_list *push_options;
const struct string_list_item *item;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL),
OPT_BIT( 0 , "mirror", &flags, N_("mirror all refs"),
(TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
OPT_BOOL('d', "delete", &deleterefs, N_("delete refs")),
OPT_BOOL( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")),
OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
{ OPTION_CALLBACK,
0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
N_("require old value of ref to be at this value"),
PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option },
{ OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
N_("control recursive pushing of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE),
OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")),
OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"),
TRANSPORT_PUSH_SET_UPSTREAM),
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
TRANSPORT_PUSH_PRUNE),
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
{ OPTION_CALLBACK,
0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_END()
};
packet_trace_identity("push");
git_config(git_push_config, &flags);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
push_options = (push_options_cmdline.nr
? &push_options_cmdline
: &push_options_config);
set_push_cert_flags(&flags, push_cert);
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 (flags & TRANSPORT_PUSH_ALL) {
if (tags)
die(_("--all and --tags are incompatible"));
if (argc >= 2)
die(_("--all can't be combined with refspecs"));
}
if (flags & TRANSPORT_PUSH_MIRROR) {
if (tags)
die(_("--mirror and --tags are incompatible"));
if (argc >= 2)
die(_("--mirror can't be combined with refspecs"));
}
if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR))
die(_("--all and --mirror are incompatible"));
if (recurse_submodules == RECURSE_SUBMODULES_CHECK)
flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
else if (recurse_submodules == RECURSE_SUBMODULES_ONLY)
flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY;
if (tags)
refspec_append(&rs, "refs/tags/*");
if (argc > 0) {
repo = argv[0];
set_refspecs(argv + 1, argc - 1, repo);
}
for_each_string_list_item(item, push_options)
if (strchr(item->string, '\n'))
die(_("push options must not have new line characters"));
rc = do_push(repo, flags, push_options);
string_list_clear(&push_options_cmdline, 0);
string_list_clear(&push_options_config, 0);
if (rc == -1)
usage_with_options(push_usage, options);
else
return rc;
}

87
third_party/git/builtin/range-diff.c vendored Normal file
View file

@ -0,0 +1,87 @@
#include "cache.h"
#include "builtin.h"
#include "parse-options.h"
#include "range-diff.h"
#include "config.h"
static const char * const builtin_range_diff_usage[] = {
N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
N_("git range-diff [<options>] <old-tip>...<new-tip>"),
N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
NULL
};
int cmd_range_diff(int argc, const char **argv, const char *prefix)
{
int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
struct diff_options diffopt = { NULL };
int simple_color = -1;
struct option range_diff_options[] = {
OPT_INTEGER(0, "creation-factor", &creation_factor,
N_("Percentage by which creation is weighted")),
OPT_BOOL(0, "no-dual-color", &simple_color,
N_("use simple diff colors")),
OPT_END()
};
struct option *options;
int res = 0;
struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
git_config(git_diff_ui_config, NULL);
repo_diff_setup(the_repository, &diffopt);
options = parse_options_concat(range_diff_options, diffopt.parseopts);
argc = parse_options(argc, argv, prefix, options,
builtin_range_diff_usage, 0);
diff_setup_done(&diffopt);
/* force color when --dual-color was used */
if (!simple_color)
diffopt.use_color = 1;
if (argc == 2) {
if (!strstr(argv[0], ".."))
die(_("no .. in range: '%s'"), argv[0]);
strbuf_addstr(&range1, argv[0]);
if (!strstr(argv[1], ".."))
die(_("no .. in range: '%s'"), argv[1]);
strbuf_addstr(&range2, argv[1]);
} else if (argc == 3) {
strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
} else if (argc == 1) {
const char *b = strstr(argv[0], "..."), *a = argv[0];
int a_len;
if (!b) {
error(_("single arg format must be symmetric range"));
usage_with_options(builtin_range_diff_usage, options);
}
a_len = (int)(b - a);
if (!a_len) {
a = "HEAD";
a_len = strlen(a);
}
b += 3;
if (!*b)
b = "HEAD";
strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
} else {
error(_("need two commit ranges"));
usage_with_options(builtin_range_diff_usage, options);
}
FREE_AND_NULL(options);
res = show_range_diff(range1.buf, range2.buf, creation_factor,
simple_color < 1, &diffopt);
strbuf_release(&range1);
strbuf_release(&range2);
return res;
}

270
third_party/git/builtin/read-tree.c vendored Normal file
View file

@ -0,0 +1,270 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "lockfile.h"
#include "object.h"
#include "tree.h"
#include "tree-walk.h"
#include "cache-tree.h"
#include "unpack-trees.h"
#include "dir.h"
#include "builtin.h"
#include "parse-options.h"
#include "resolve-undo.h"
#include "submodule.h"
#include "submodule-config.h"
static int nr_trees;
static int read_empty;
static struct tree *trees[MAX_UNPACK_TREES];
static int list_tree(struct object_id *oid)
{
struct tree *tree;
if (nr_trees >= MAX_UNPACK_TREES)
die("I cannot read more than %d trees", MAX_UNPACK_TREES);
tree = parse_tree_indirect(oid);
if (!tree)
return -1;
trees[nr_trees++] = tree;
return 0;
}
static const char * const read_tree_usage[] = {
N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
NULL
};
static int index_output_cb(const struct option *opt, const char *arg,
int unset)
{
BUG_ON_OPT_NEG(unset);
set_alternate_index_output(arg);
return 0;
}
static int exclude_per_directory_cb(const struct option *opt, const char *arg,
int unset)
{
struct dir_struct *dir;
struct unpack_trees_options *opts;
BUG_ON_OPT_NEG(unset);
opts = (struct unpack_trees_options *)opt->value;
if (opts->dir)
die("more than one --exclude-per-directory given.");
dir = xcalloc(1, sizeof(*opts->dir));
dir->flags |= DIR_SHOW_IGNORED;
dir->exclude_per_dir = arg;
opts->dir = dir;
/* We do not need to nor want to do read-directory
* here; we are merely interested in reusing the
* per directory ignore stack mechanism.
*/
return 0;
}
static void debug_stage(const char *label, const struct cache_entry *ce,
struct unpack_trees_options *o)
{
printf("%s ", label);
if (!ce)
printf("(missing)\n");
else if (ce == o->df_conflict_entry)
printf("(conflict)\n");
else
printf("%06o #%d %s %.8s\n",
ce->ce_mode, ce_stage(ce), ce->name,
oid_to_hex(&ce->oid));
}
static int debug_merge(const struct cache_entry * const *stages,
struct unpack_trees_options *o)
{
int i;
printf("* %d-way merge\n", o->merge_size);
debug_stage("index", stages[0], o);
for (i = 1; i <= o->merge_size; i++) {
char buf[24];
xsnprintf(buf, sizeof(buf), "ent#%d", i);
debug_stage(buf, stages[i], o);
}
return 0;
}
static int git_read_tree_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "submodule.recurse"))
return git_default_submodule_config(var, value, cb);
return git_default_config(var, value, cb);
}
int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
{
int i, stage = 0;
struct object_id oid;
struct tree_desc t[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
int prefix_set = 0;
struct lock_file lock_file = LOCK_INIT;
const struct option read_tree_options[] = {
{ OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
N_("write resulting index to <file>"),
PARSE_OPT_NONEG, index_output_cb },
OPT_BOOL(0, "empty", &read_empty,
N_("only empty the index")),
OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
OPT_GROUP(N_("Merging")),
OPT_BOOL('m', NULL, &opts.merge,
N_("perform a merge in addition to a read")),
OPT_BOOL(0, "trivial", &opts.trivial_merges_only,
N_("3-way merge if no file level merging required")),
OPT_BOOL(0, "aggressive", &opts.aggressive,
N_("3-way merge in presence of adds and removes")),
OPT_BOOL(0, "reset", &opts.reset,
N_("same as -m, but discard unmerged entries")),
{ OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"),
N_("read the tree into the index under <subdirectory>/"),
PARSE_OPT_NONEG },
OPT_BOOL('u', NULL, &opts.update,
N_("update working tree with merge result")),
{ OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
N_("gitignore"),
N_("allow explicitly ignored files to be overwritten"),
PARSE_OPT_NONEG, exclude_per_directory_cb },
OPT_BOOL('i', NULL, &opts.index_only,
N_("don't check the working tree after merging")),
OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
N_("skip applying sparse checkout filter")),
OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
N_("debug unpack-trees")),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
"checkout", "control recursive updating of submodules",
PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
OPT__QUIET(&opts.quiet, N_("suppress feedback messages")),
OPT_END()
};
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
opts.src_index = &the_index;
opts.dst_index = &the_index;
git_config(git_read_tree_config, NULL);
argc = parse_options(argc, argv, cmd_prefix, read_tree_options,
read_tree_usage, 0);
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
prefix_set = opts.prefix ? 1 : 0;
if (1 < opts.merge + opts.reset + prefix_set)
die("Which one? -m, --reset, or --prefix?");
/*
* NEEDSWORK
*
* The old index should be read anyway even if we're going to
* destroy all index entries because we still need to preserve
* certain information such as index version or split-index
* mode.
*/
if (opts.reset || opts.merge || opts.prefix) {
if (read_cache_unmerged() && (opts.prefix || opts.merge))
die("You need to resolve your current index first");
stage = opts.merge = 1;
}
resolve_undo_clear();
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
if (get_oid(arg, &oid))
die("Not a valid object name %s", arg);
if (list_tree(&oid) < 0)
die("failed to unpack tree object %s", arg);
stage++;
}
if (!nr_trees && !read_empty && !opts.merge)
warning("read-tree: emptying the index with no arguments is deprecated; use --empty");
else if (nr_trees > 0 && read_empty)
die("passing trees as arguments contradicts --empty");
if (1 < opts.index_only + opts.update)
die("-u and -i at the same time makes no sense");
if ((opts.update || opts.index_only) && !opts.merge)
die("%s is meaningless without -m, --reset, or --prefix",
opts.update ? "-u" : "-i");
if ((opts.dir && !opts.update))
die("--exclude-per-directory is meaningless unless -u");
if (opts.merge && !opts.index_only)
setup_work_tree();
if (opts.merge) {
switch (stage - 1) {
case 0:
die("you must specify at least one tree to merge");
break;
case 1:
opts.fn = opts.prefix ? bind_merge : oneway_merge;
break;
case 2:
opts.fn = twoway_merge;
opts.initial_checkout = is_cache_unborn();
break;
case 3:
default:
opts.fn = threeway_merge;
break;
}
if (stage - 1 >= 3)
opts.head_idx = stage - 2;
else
opts.head_idx = 1;
}
if (opts.debug_unpack)
opts.fn = debug_merge;
cache_tree_free(&active_cache_tree);
for (i = 0; i < nr_trees; i++) {
struct tree *tree = trees[i];
parse_tree(tree);
init_tree_desc(t+i, tree->buffer, tree->size);
}
if (unpack_trees(nr_trees, t, &opts))
return 128;
if (opts.debug_unpack || opts.dry_run)
return 0; /* do not write the index out */
/*
* When reading only one tree (either the most basic form,
* "-m ent" or "--reset ent" form), we can obtain a fully
* valid cache-tree because the index must match exactly
* what came from the tree.
*/
if (nr_trees == 1 && !opts.prefix)
prime_cache_tree(the_repository,
the_repository->index,
trees[0]);
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("unable to write new index file");
return 0;
}

2169
third_party/git/builtin/rebase.c vendored Normal file

File diff suppressed because it is too large Load diff

2061
third_party/git/builtin/receive-pack.c vendored Normal file

File diff suppressed because it is too large Load diff

788
third_party/git/builtin/reflog.c vendored Normal file
View file

@ -0,0 +1,788 @@
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
#include "object-store.h"
#include "repository.h"
#include "commit.h"
#include "refs.h"
#include "dir.h"
#include "tree-walk.h"
#include "diff.h"
#include "revision.h"
#include "reachable.h"
#include "worktree.h"
/* NEEDSWORK: switch to using parse_options */
static const char reflog_expire_usage[] =
N_("git reflog expire [--expire=<time>] "
"[--expire-unreachable=<time>] "
"[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
"[--verbose] [--all] <refs>...");
static const char reflog_delete_usage[] =
N_("git reflog delete [--rewrite] [--updateref] "
"[--dry-run | -n] [--verbose] <refs>...");
static const char reflog_exists_usage[] =
N_("git reflog exists <ref>");
static timestamp_t default_reflog_expire;
static timestamp_t default_reflog_expire_unreachable;
struct cmd_reflog_expire_cb {
struct rev_info revs;
int stalefix;
timestamp_t expire_total;
timestamp_t expire_unreachable;
int recno;
};
struct expire_reflog_policy_cb {
enum {
UE_NORMAL,
UE_ALWAYS,
UE_HEAD
} unreachable_expire_kind;
struct commit_list *mark_list;
unsigned long mark_limit;
struct cmd_reflog_expire_cb cmd;
struct commit *tip_commit;
struct commit_list *tips;
};
struct collected_reflog {
struct object_id oid;
char reflog[FLEX_ARRAY];
};
struct collect_reflog_cb {
struct collected_reflog **e;
int alloc;
int nr;
struct worktree *wt;
};
/* Remember to update object flag allocation in object.h */
#define INCOMPLETE (1u<<10)
#define STUDYING (1u<<11)
#define REACHABLE (1u<<12)
static int tree_is_complete(const struct object_id *oid)
{
struct tree_desc desc;
struct name_entry entry;
int complete;
struct tree *tree;
tree = lookup_tree(the_repository, oid);
if (!tree)
return 0;
if (tree->object.flags & SEEN)
return 1;
if (tree->object.flags & INCOMPLETE)
return 0;
if (!tree->buffer) {
enum object_type type;
unsigned long size;
void *data = read_object_file(oid, &type, &size);
if (!data) {
tree->object.flags |= INCOMPLETE;
return 0;
}
tree->buffer = data;
tree->size = size;
}
init_tree_desc(&desc, tree->buffer, tree->size);
complete = 1;
while (tree_entry(&desc, &entry)) {
if (!has_object_file(&entry.oid) ||
(S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {
tree->object.flags |= INCOMPLETE;
complete = 0;
}
}
free_tree_buffer(tree);
if (complete)
tree->object.flags |= SEEN;
return complete;
}
static int commit_is_complete(struct commit *commit)
{
struct object_array study;
struct object_array found;
int is_incomplete = 0;
int i;
/* early return */
if (commit->object.flags & SEEN)
return 1;
if (commit->object.flags & INCOMPLETE)
return 0;
/*
* Find all commits that are reachable and are not marked as
* SEEN. Then make sure the trees and blobs contained are
* complete. After that, mark these commits also as SEEN.
* If some of the objects that are needed to complete this
* commit are missing, mark this commit as INCOMPLETE.
*/
memset(&study, 0, sizeof(study));
memset(&found, 0, sizeof(found));
add_object_array(&commit->object, NULL, &study);
add_object_array(&commit->object, NULL, &found);
commit->object.flags |= STUDYING;
while (study.nr) {
struct commit *c;
struct commit_list *parent;
c = (struct commit *)object_array_pop(&study);
if (!c->object.parsed && !parse_object(the_repository, &c->object.oid))
c->object.flags |= INCOMPLETE;
if (c->object.flags & INCOMPLETE) {
is_incomplete = 1;
break;
}
else if (c->object.flags & SEEN)
continue;
for (parent = c->parents; parent; parent = parent->next) {
struct commit *p = parent->item;
if (p->object.flags & STUDYING)
continue;
p->object.flags |= STUDYING;
add_object_array(&p->object, NULL, &study);
add_object_array(&p->object, NULL, &found);
}
}
if (!is_incomplete) {
/*
* make sure all commits in "found" array have all the
* necessary objects.
*/
for (i = 0; i < found.nr; i++) {
struct commit *c =
(struct commit *)found.objects[i].item;
if (!tree_is_complete(get_commit_tree_oid(c))) {
is_incomplete = 1;
c->object.flags |= INCOMPLETE;
}
}
if (!is_incomplete) {
/* mark all found commits as complete, iow SEEN */
for (i = 0; i < found.nr; i++)
found.objects[i].item->flags |= SEEN;
}
}
/* clear flags from the objects we traversed */
for (i = 0; i < found.nr; i++)
found.objects[i].item->flags &= ~STUDYING;
if (is_incomplete)
commit->object.flags |= INCOMPLETE;
else {
/*
* If we come here, we have (1) traversed the ancestry chain
* from the "commit" until we reach SEEN commits (which are
* known to be complete), and (2) made sure that the commits
* encountered during the above traversal refer to trees that
* are complete. Which means that we know *all* the commits
* we have seen during this process are complete.
*/
for (i = 0; i < found.nr; i++)
found.objects[i].item->flags |= SEEN;
}
/* free object arrays */
object_array_clear(&study);
object_array_clear(&found);
return !is_incomplete;
}
static int keep_entry(struct commit **it, struct object_id *oid)
{
struct commit *commit;
if (is_null_oid(oid))
return 1;
commit = lookup_commit_reference_gently(the_repository, oid, 1);
if (!commit)
return 0;
/*
* Make sure everything in this commit exists.
*
* We have walked all the objects reachable from the refs
* and cache earlier. The commits reachable by this commit
* must meet SEEN commits -- and then we should mark them as
* SEEN as well.
*/
if (!commit_is_complete(commit))
return 0;
*it = commit;
return 1;
}
/*
* Starting from commits in the cb->mark_list, mark commits that are
* reachable from them. Stop the traversal at commits older than
* the expire_limit and queue them back, so that the caller can call
* us again to restart the traversal with longer expire_limit.
*/
static void mark_reachable(struct expire_reflog_policy_cb *cb)
{
struct commit_list *pending;
timestamp_t expire_limit = cb->mark_limit;
struct commit_list *leftover = NULL;
for (pending = cb->mark_list; pending; pending = pending->next)
pending->item->object.flags &= ~REACHABLE;
pending = cb->mark_list;
while (pending) {
struct commit_list *parent;
struct commit *commit = pop_commit(&pending);
if (commit->object.flags & REACHABLE)
continue;
if (parse_commit(commit))
continue;
commit->object.flags |= REACHABLE;
if (commit->date < expire_limit) {
commit_list_insert(commit, &leftover);
continue;
}
commit->object.flags |= REACHABLE;
parent = commit->parents;
while (parent) {
commit = parent->item;
parent = parent->next;
if (commit->object.flags & REACHABLE)
continue;
commit_list_insert(commit, &pending);
}
}
cb->mark_list = leftover;
}
static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)
{
/*
* We may or may not have the commit yet - if not, look it
* up using the supplied sha1.
*/
if (!commit) {
if (is_null_oid(oid))
return 0;
commit = lookup_commit_reference_gently(the_repository, oid,
1);
/* Not a commit -- keep it */
if (!commit)
return 0;
}
/* Reachable from the current ref? Don't prune. */
if (commit->object.flags & REACHABLE)
return 0;
if (cb->mark_list && cb->mark_limit) {
cb->mark_limit = 0; /* dig down to the root */
mark_reachable(cb);
}
return !(commit->object.flags & REACHABLE);
}
/*
* Return true iff the specified reflog entry should be expired.
*/
static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
const char *email, timestamp_t timestamp, int tz,
const char *message, void *cb_data)
{
struct expire_reflog_policy_cb *cb = cb_data;
struct commit *old_commit, *new_commit;
if (timestamp < cb->cmd.expire_total)
return 1;
old_commit = new_commit = NULL;
if (cb->cmd.stalefix &&
(!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))
return 1;
if (timestamp < cb->cmd.expire_unreachable) {
if (cb->unreachable_expire_kind == UE_ALWAYS)
return 1;
if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))
return 1;
}
if (cb->cmd.recno && --(cb->cmd.recno) == 0)
return 1;
return 0;
}
static int push_tip_to_list(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
struct commit_list **list = cb_data;
struct commit *tip_commit;
if (flags & REF_ISSYMREF)
return 0;
tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);
if (!tip_commit)
return 0;
commit_list_insert(tip_commit, list);
return 0;
}
static int is_head(const char *refname)
{
switch (ref_type(refname)) {
case REF_TYPE_OTHER_PSEUDOREF:
case REF_TYPE_MAIN_PSEUDOREF:
if (parse_worktree_ref(refname, NULL, NULL, &refname))
BUG("not a worktree ref: %s", refname);
break;
default:
break;
}
return !strcmp(refname, "HEAD");
}
static void reflog_expiry_prepare(const char *refname,
const struct object_id *oid,
void *cb_data)
{
struct expire_reflog_policy_cb *cb = cb_data;
if (!cb->cmd.expire_unreachable || is_head(refname)) {
cb->tip_commit = NULL;
cb->unreachable_expire_kind = UE_HEAD;
} else {
cb->tip_commit = lookup_commit_reference_gently(the_repository,
oid, 1);
if (!cb->tip_commit)
cb->unreachable_expire_kind = UE_ALWAYS;
else
cb->unreachable_expire_kind = UE_NORMAL;
}
if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)
cb->unreachable_expire_kind = UE_ALWAYS;
cb->mark_list = NULL;
cb->tips = NULL;
if (cb->unreachable_expire_kind != UE_ALWAYS) {
if (cb->unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
for_each_ref(push_tip_to_list, &cb->tips);
for (elem = cb->tips; elem; elem = elem->next)
commit_list_insert(elem->item, &cb->mark_list);
} else {
commit_list_insert(cb->tip_commit, &cb->mark_list);
}
cb->mark_limit = cb->cmd.expire_total;
mark_reachable(cb);
}
}
static void reflog_expiry_cleanup(void *cb_data)
{
struct expire_reflog_policy_cb *cb = cb_data;
if (cb->unreachable_expire_kind != UE_ALWAYS) {
if (cb->unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
for (elem = cb->tips; elem; elem = elem->next)
clear_commit_marks(elem->item, REACHABLE);
free_commit_list(cb->tips);
} else {
clear_commit_marks(cb->tip_commit, REACHABLE);
}
}
}
static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
{
struct collected_reflog *e;
struct collect_reflog_cb *cb = cb_data;
struct strbuf newref = STRBUF_INIT;
/*
* Avoid collecting the same shared ref multiple times because
* they are available via all worktrees.
*/
if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL)
return 0;
strbuf_worktree_ref(cb->wt, &newref, ref);
FLEX_ALLOC_STR(e, reflog, newref.buf);
strbuf_release(&newref);
oidcpy(&e->oid, oid);
ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
cb->e[cb->nr++] = e;
return 0;
}
static struct reflog_expire_cfg {
struct reflog_expire_cfg *next;
timestamp_t expire_total;
timestamp_t expire_unreachable;
char pattern[FLEX_ARRAY];
} *reflog_expire_cfg, **reflog_expire_cfg_tail;
static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
{
struct reflog_expire_cfg *ent;
if (!reflog_expire_cfg_tail)
reflog_expire_cfg_tail = &reflog_expire_cfg;
for (ent = reflog_expire_cfg; ent; ent = ent->next)
if (!strncmp(ent->pattern, pattern, len) &&
ent->pattern[len] == '\0')
return ent;
FLEX_ALLOC_MEM(ent, pattern, pattern, len);
*reflog_expire_cfg_tail = ent;
reflog_expire_cfg_tail = &(ent->next);
return ent;
}
/* expiry timer slot */
#define EXPIRE_TOTAL 01
#define EXPIRE_UNREACH 02
static int reflog_expire_config(const char *var, const char *value, void *cb)
{
const char *pattern, *key;
int pattern_len;
timestamp_t expire;
int slot;
struct reflog_expire_cfg *ent;
if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
return git_default_config(var, value, cb);
if (!strcmp(key, "reflogexpire")) {
slot = EXPIRE_TOTAL;
if (git_config_expiry_date(&expire, var, value))
return -1;
} else if (!strcmp(key, "reflogexpireunreachable")) {
slot = EXPIRE_UNREACH;
if (git_config_expiry_date(&expire, var, value))
return -1;
} else
return git_default_config(var, value, cb);
if (!pattern) {
switch (slot) {
case EXPIRE_TOTAL:
default_reflog_expire = expire;
break;
case EXPIRE_UNREACH:
default_reflog_expire_unreachable = expire;
break;
}
return 0;
}
ent = find_cfg_ent(pattern, pattern_len);
if (!ent)
return -1;
switch (slot) {
case EXPIRE_TOTAL:
ent->expire_total = expire;
break;
case EXPIRE_UNREACH:
ent->expire_unreachable = expire;
break;
}
return 0;
}
static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
{
struct reflog_expire_cfg *ent;
if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
return; /* both given explicitly -- nothing to tweak */
for (ent = reflog_expire_cfg; ent; ent = ent->next) {
if (!wildmatch(ent->pattern, ref, 0)) {
if (!(slot & EXPIRE_TOTAL))
cb->expire_total = ent->expire_total;
if (!(slot & EXPIRE_UNREACH))
cb->expire_unreachable = ent->expire_unreachable;
return;
}
}
/*
* If unconfigured, make stash never expire
*/
if (!strcmp(ref, "refs/stash")) {
if (!(slot & EXPIRE_TOTAL))
cb->expire_total = 0;
if (!(slot & EXPIRE_UNREACH))
cb->expire_unreachable = 0;
return;
}
/* Nothing matched -- use the default value */
if (!(slot & EXPIRE_TOTAL))
cb->expire_total = default_reflog_expire;
if (!(slot & EXPIRE_UNREACH))
cb->expire_unreachable = default_reflog_expire_unreachable;
}
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
struct expire_reflog_policy_cb cb;
timestamp_t now = time(NULL);
int i, status, do_all, all_worktrees = 1;
int explicit_expiry = 0;
unsigned int flags = 0;
default_reflog_expire_unreachable = now - 30 * 24 * 3600;
default_reflog_expire = now - 90 * 24 * 3600;
git_config(reflog_expire_config, NULL);
save_commit_buffer = 0;
do_all = status = 0;
memset(&cb, 0, sizeof(cb));
cb.cmd.expire_total = default_reflog_expire;
cb.cmd.expire_unreachable = default_reflog_expire_unreachable;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
flags |= EXPIRE_REFLOGS_DRY_RUN;
else if (starts_with(arg, "--expire=")) {
if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
else if (starts_with(arg, "--expire-unreachable=")) {
if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
else if (!strcmp(arg, "--stale-fix"))
cb.cmd.stalefix = 1;
else if (!strcmp(arg, "--rewrite"))
flags |= EXPIRE_REFLOGS_REWRITE;
else if (!strcmp(arg, "--updateref"))
flags |= EXPIRE_REFLOGS_UPDATE_REF;
else if (!strcmp(arg, "--all"))
do_all = 1;
else if (!strcmp(arg, "--single-worktree"))
all_worktrees = 0;
else if (!strcmp(arg, "--verbose"))
flags |= EXPIRE_REFLOGS_VERBOSE;
else if (!strcmp(arg, "--")) {
i++;
break;
}
else if (arg[0] == '-')
usage(_(reflog_expire_usage));
else
break;
}
/*
* We can trust the commits and objects reachable from refs
* even in older repository. We cannot trust what's reachable
* from reflog if the repository was pruned with older git.
*/
if (cb.cmd.stalefix) {
repo_init_revisions(the_repository, &cb.cmd.revs, prefix);
if (flags & EXPIRE_REFLOGS_VERBOSE)
printf(_("Marking reachable objects..."));
mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
if (flags & EXPIRE_REFLOGS_VERBOSE)
putchar('\n');
}
if (do_all) {
struct collect_reflog_cb collected;
struct worktree **worktrees, **p;
int i;
memset(&collected, 0, sizeof(collected));
worktrees = get_worktrees(0);
for (p = worktrees; *p; p++) {
if (!all_worktrees && !(*p)->is_current)
continue;
collected.wt = *p;
refs_for_each_reflog(get_worktree_ref_store(*p),
collect_reflog, &collected);
}
free_worktrees(worktrees);
for (i = 0; i < collected.nr; i++) {
struct collected_reflog *e = collected.e[i];
set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
status |= reflog_expire(e->reflog, &e->oid, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
&cb);
free(e);
}
free(collected.e);
}
for (; i < argc; i++) {
char *ref;
struct object_id oid;
if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
status |= error(_("%s points nowhere!"), argv[i]);
continue;
}
set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
status |= reflog_expire(ref, &oid, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
&cb);
}
return status;
}
static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
const char *email, timestamp_t timestamp, int tz,
const char *message, void *cb_data)
{
struct expire_reflog_policy_cb *cb = cb_data;
if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total)
cb->cmd.recno++;
return 0;
}
static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
{
struct expire_reflog_policy_cb cb;
int i, status = 0;
unsigned int flags = 0;
memset(&cb, 0, sizeof(cb));
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
flags |= EXPIRE_REFLOGS_DRY_RUN;
else if (!strcmp(arg, "--rewrite"))
flags |= EXPIRE_REFLOGS_REWRITE;
else if (!strcmp(arg, "--updateref"))
flags |= EXPIRE_REFLOGS_UPDATE_REF;
else if (!strcmp(arg, "--verbose"))
flags |= EXPIRE_REFLOGS_VERBOSE;
else if (!strcmp(arg, "--")) {
i++;
break;
}
else if (arg[0] == '-')
usage(_(reflog_delete_usage));
else
break;
}
if (argc - i < 1)
return error(_("no reflog specified to delete"));
for ( ; i < argc; i++) {
const char *spec = strstr(argv[i], "@{");
struct object_id oid;
char *ep, *ref;
int recno;
if (!spec) {
status |= error(_("not a reflog: %s"), argv[i]);
continue;
}
if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
status |= error(_("no reflog for '%s'"), argv[i]);
continue;
}
recno = strtoul(spec + 2, &ep, 10);
if (*ep == '}') {
cb.cmd.recno = -recno;
for_each_reflog_ent(ref, count_reflog_ent, &cb);
} else {
cb.cmd.expire_total = approxidate(spec + 2);
for_each_reflog_ent(ref, count_reflog_ent, &cb);
cb.cmd.expire_total = 0;
}
status |= reflog_expire(ref, &oid, flags,
reflog_expiry_prepare,
should_expire_reflog_ent,
reflog_expiry_cleanup,
&cb);
free(ref);
}
return status;
}
static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
{
int i, start = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--")) {
i++;
break;
}
else if (arg[0] == '-')
usage(_(reflog_exists_usage));
else
break;
}
start = i;
if (argc - start != 1)
usage(_(reflog_exists_usage));
if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
die(_("invalid ref format: %s"), argv[start]);
return !reflog_exists(argv[start]);
}
/*
* main "reflog"
*/
static const char reflog_usage[] =
N_("git reflog [ show | expire | delete | exists ]");
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
if (argc > 1 && !strcmp(argv[1], "-h"))
usage(_(reflog_usage));
/* With no command, we default to showing it. */
if (argc < 2 || *argv[1] == '-')
return cmd_log_reflog(argc, argv, prefix);
if (!strcmp(argv[1], "show"))
return cmd_log_reflog(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "expire"))
return cmd_reflog_expire(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "delete"))
return cmd_reflog_delete(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "exists"))
return cmd_reflog_exists(argc - 1, argv + 1, prefix);
return cmd_log_reflog(argc, argv, prefix);
}

202
third_party/git/builtin/remote-ext.c vendored Normal file
View file

@ -0,0 +1,202 @@
#include "builtin.h"
#include "transport.h"
#include "run-command.h"
#include "pkt-line.h"
static const char usage_msg[] =
"git remote-ext <remote> <url>";
/*
* URL syntax:
* 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
* Special characters:
* '% ': Literal space in argument.
* '%%': Literal percent sign.
* '%S': Name of service (git-upload-pack/git-upload-archive/
* git-receive-pack.
* '%s': Same as \s, but with possible git- prefix stripped.
* '%G': Only allowed as first 'character' of argument. Do not pass this
* Argument to command, instead send this as name of repository
* in in-line git://-style request (also activates sending this
* style of request).
* '%V': Only allowed as first 'character' of argument. Used in
* conjunction with '%G': Do not pass this argument to command,
* instead send this as vhost in git://-style request (note: does
* not activate sending git:// style request).
*/
static char *git_req;
static char *git_req_vhost;
static char *strip_escapes(const char *str, const char *service,
const char **next)
{
size_t rpos = 0;
int escape = 0;
char special = 0;
const char *service_noprefix = service;
struct strbuf ret = STRBUF_INIT;
skip_prefix(service_noprefix, "git-", &service_noprefix);
/* Pass the service to command. */
setenv("GIT_EXT_SERVICE", service, 1);
setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
/* Scan the length of argument. */
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
case 's':
case 'S':
break;
case 'G':
case 'V':
special = str[rpos];
if (rpos == 1)
break;
/* fallthrough */
default:
die("Bad remote-ext placeholder '%%%c'.",
str[rpos]);
}
escape = 0;
} else
escape = (str[rpos] == '%');
rpos++;
}
if (escape && !str[rpos])
die("remote-ext command has incomplete placeholder");
*next = str + rpos;
if (**next == ' ')
++*next; /* Skip over space */
/*
* Do the actual placeholder substitution. The string will be short
* enough not to overflow integers.
*/
rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
escape = 0;
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
strbuf_addch(&ret, str[rpos]);
break;
case 's':
strbuf_addstr(&ret, service_noprefix);
break;
case 'S':
strbuf_addstr(&ret, service);
break;
}
escape = 0;
} else
switch (str[rpos]) {
case '%':
escape = 1;
break;
default:
strbuf_addch(&ret, str[rpos]);
break;
}
rpos++;
}
switch (special) {
case 'G':
git_req = strbuf_detach(&ret, NULL);
return NULL;
case 'V':
git_req_vhost = strbuf_detach(&ret, NULL);
return NULL;
default:
return strbuf_detach(&ret, NULL);
}
}
static void parse_argv(struct argv_array *out, const char *arg, const char *service)
{
while (*arg) {
char *expanded = strip_escapes(arg, service, &arg);
if (expanded)
argv_array_push(out, expanded);
free(expanded);
}
}
static void send_git_request(int stdin_fd, const char *serv, const char *repo,
const char *vhost)
{
if (!vhost)
packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0);
else
packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
vhost, 0);
}
static int run_child(const char *arg, const char *service)
{
int r;
struct child_process child = CHILD_PROCESS_INIT;
child.in = -1;
child.out = -1;
child.err = 0;
parse_argv(&child.args, arg, service);
if (start_command(&child) < 0)
die("Can't run specified command");
if (git_req)
send_git_request(child.in, service, git_req, git_req_vhost);
r = bidirectional_transfer_loop(child.out, child.in);
if (!r)
r = finish_command(&child);
else
finish_command(&child);
return r;
}
#define MAXCOMMAND 4096
static int command_loop(const char *child)
{
char buffer[MAXCOMMAND];
while (1) {
size_t i;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
die("Command input error");
exit(0);
}
/* Strip end of line characters. */
i = strlen(buffer);
while (i > 0 && isspace(buffer[i - 1]))
buffer[--i] = 0;
if (!strcmp(buffer, "capabilities")) {
printf("*connect\n\n");
fflush(stdout);
} else if (!strncmp(buffer, "connect ", 8)) {
printf("\n");
fflush(stdout);
return run_child(child, buffer + 8);
} else {
fprintf(stderr, "Bad command");
return 1;
}
}
}
int cmd_remote_ext(int argc, const char **argv, const char *prefix)
{
if (argc != 3)
usage(usage_msg);
return command_loop(argv[2]);
}

82
third_party/git/builtin/remote-fd.c vendored Normal file
View file

@ -0,0 +1,82 @@
#include "builtin.h"
#include "transport.h"
static const char usage_msg[] =
"git remote-fd <remote> <url>";
/*
* URL syntax:
* 'fd::<inoutfd>[/<anything>]' Read/write socket pair
* <inoutfd>.
* 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write
* pipe <outfd>.
* [foo] indicates 'foo' is optional. <anything> is any string.
*
* The data output to <outfd>/<inoutfd> should be passed unmolested to
* git-receive-pack/git-upload-pack/git-upload-archive and output of
* git-receive-pack/git-upload-pack/git-upload-archive should be passed
* unmolested to <infd>/<inoutfd>.
*
*/
#define MAXCOMMAND 4096
static void command_loop(int input_fd, int output_fd)
{
char buffer[MAXCOMMAND];
while (1) {
size_t i;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
die("Input error");
return;
}
/* Strip end of line characters. */
i = strlen(buffer);
while (i > 0 && isspace(buffer[i - 1]))
buffer[--i] = 0;
if (!strcmp(buffer, "capabilities")) {
printf("*connect\n\n");
fflush(stdout);
} else if (!strncmp(buffer, "connect ", 8)) {
printf("\n");
fflush(stdout);
if (bidirectional_transfer_loop(input_fd,
output_fd))
die("Copying data between file descriptors failed");
return;
} else {
die("Bad command: %s", buffer);
}
}
}
int cmd_remote_fd(int argc, const char **argv, const char *prefix)
{
int input_fd = -1;
int output_fd = -1;
char *end;
if (argc != 3)
usage(usage_msg);
input_fd = (int)strtoul(argv[2], &end, 10);
if ((end == argv[2]) || (*end != ',' && *end != '/' && *end))
die("Bad URL syntax");
if (*end == '/' || !*end) {
output_fd = input_fd;
} else {
char *end2;
output_fd = (int)strtoul(end + 1, &end2, 10);
if ((end2 == end + 1) || (*end2 != '/' && *end2))
die("Bad URL syntax");
}
command_loop(input_fd, output_fd);
return 0;
}

1646
third_party/git/builtin/remote.c vendored Normal file

File diff suppressed because it is too large Load diff

572
third_party/git/builtin/repack.c vendored Normal file
View file

@ -0,0 +1,572 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "parse-options.h"
#include "run-command.h"
#include "sigchain.h"
#include "strbuf.h"
#include "string-list.h"
#include "argv-array.h"
#include "midx.h"
#include "packfile.h"
#include "object-store.h"
static int delta_base_offset = 1;
static int pack_kept_objects = -1;
static int write_bitmaps = -1;
static int use_delta_islands;
static char *packdir, *packtmp;
static const char *const git_repack_usage[] = {
N_("git repack [<options>]"),
NULL
};
static const char incremental_bitmap_conflict_error[] = N_(
"Incremental repacks are incompatible with bitmap indexes. Use\n"
"--no-write-bitmap-index or disable the pack.writebitmaps configuration."
);
static int repack_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "repack.usedeltabaseoffset")) {
delta_base_offset = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "repack.packkeptobjects")) {
pack_kept_objects = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "repack.writebitmaps") ||
!strcmp(var, "pack.writebitmaps")) {
write_bitmaps = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "repack.usedeltaislands")) {
use_delta_islands = git_config_bool(var, value);
return 0;
}
return git_default_config(var, value, cb);
}
/*
* Remove temporary $GIT_OBJECT_DIRECTORY/pack/.tmp-$$-pack-* files.
*/
static void remove_temporary_files(void)
{
struct strbuf buf = STRBUF_INIT;
size_t dirlen, prefixlen;
DIR *dir;
struct dirent *e;
dir = opendir(packdir);
if (!dir)
return;
/* Point at the slash at the end of ".../objects/pack/" */
dirlen = strlen(packdir) + 1;
strbuf_addstr(&buf, packtmp);
/* Hold the length of ".tmp-%d-pack-" */
prefixlen = buf.len - dirlen;
while ((e = readdir(dir))) {
if (strncmp(e->d_name, buf.buf + dirlen, prefixlen))
continue;
strbuf_setlen(&buf, dirlen);
strbuf_addstr(&buf, e->d_name);
unlink(buf.buf);
}
closedir(dir);
strbuf_release(&buf);
}
static void remove_pack_on_signal(int signo)
{
remove_temporary_files();
sigchain_pop(signo);
raise(signo);
}
/*
* Adds all packs hex strings to the fname list, which do not
* have a corresponding .keep file. These packs are not to
* be kept if we are going to pack everything into one file.
*/
static void get_non_kept_pack_filenames(struct string_list *fname_list,
const struct string_list *extra_keep)
{
DIR *dir;
struct dirent *e;
char *fname;
if (!(dir = opendir(packdir)))
return;
while ((e = readdir(dir)) != NULL) {
size_t len;
int i;
for (i = 0; i < extra_keep->nr; i++)
if (!fspathcmp(e->d_name, extra_keep->items[i].string))
break;
if (extra_keep->nr > 0 && i < extra_keep->nr)
continue;
if (!strip_suffix(e->d_name, ".pack", &len))
continue;
fname = xmemdupz(e->d_name, len);
if (!file_exists(mkpath("%s/%s.keep", packdir, fname)))
string_list_append_nodup(fname_list, fname);
else
free(fname);
}
closedir(dir);
}
static void remove_redundant_pack(const char *dir_name, const char *base_name)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name);
unlink_pack_path(buf.buf, 1);
strbuf_release(&buf);
}
struct pack_objects_args {
const char *window;
const char *window_memory;
const char *depth;
const char *threads;
const char *max_pack_size;
int no_reuse_delta;
int no_reuse_object;
int quiet;
int local;
};
static void prepare_pack_objects(struct child_process *cmd,
const struct pack_objects_args *args)
{
argv_array_push(&cmd->args, "pack-objects");
if (args->window)
argv_array_pushf(&cmd->args, "--window=%s", args->window);
if (args->window_memory)
argv_array_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
if (args->depth)
argv_array_pushf(&cmd->args, "--depth=%s", args->depth);
if (args->threads)
argv_array_pushf(&cmd->args, "--threads=%s", args->threads);
if (args->max_pack_size)
argv_array_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size);
if (args->no_reuse_delta)
argv_array_pushf(&cmd->args, "--no-reuse-delta");
if (args->no_reuse_object)
argv_array_pushf(&cmd->args, "--no-reuse-object");
if (args->local)
argv_array_push(&cmd->args, "--local");
if (args->quiet)
argv_array_push(&cmd->args, "--quiet");
if (delta_base_offset)
argv_array_push(&cmd->args, "--delta-base-offset");
argv_array_push(&cmd->args, packtmp);
cmd->git_cmd = 1;
cmd->out = -1;
}
/*
* Write oid to the given struct child_process's stdin, starting it first if
* necessary.
*/
static int write_oid(const struct object_id *oid, struct packed_git *pack,
uint32_t pos, void *data)
{
struct child_process *cmd = data;
if (cmd->in == -1) {
if (start_command(cmd))
die(_("could not start pack-objects to repack promisor objects"));
}
xwrite(cmd->in, oid_to_hex(oid), GIT_SHA1_HEXSZ);
xwrite(cmd->in, "\n", 1);
return 0;
}
static void repack_promisor_objects(const struct pack_objects_args *args,
struct string_list *names)
{
struct child_process cmd = CHILD_PROCESS_INIT;
FILE *out;
struct strbuf line = STRBUF_INIT;
prepare_pack_objects(&cmd, args);
cmd.in = -1;
/*
* NEEDSWORK: Giving pack-objects only the OIDs without any ordering
* hints may result in suboptimal deltas in the resulting pack. See if
* the OIDs can be sent with fake paths such that pack-objects can use a
* {type -> existing pack order} ordering when computing deltas instead
* of a {type -> size} ordering, which may produce better deltas.
*/
for_each_packed_object(write_oid, &cmd,
FOR_EACH_OBJECT_PROMISOR_ONLY);
if (cmd.in == -1)
/* No packed objects; cmd was never started */
return;
close(cmd.in);
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
char *promisor_name;
int fd;
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only from pack-objects."));
string_list_append(names, line.buf);
/*
* pack-objects creates the .pack and .idx files, but not the
* .promisor file. Create the .promisor file, which is empty.
*/
promisor_name = mkpathdup("%s-%s.promisor", packtmp,
line.buf);
fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
if (fd < 0)
die_errno(_("unable to create '%s'"), promisor_name);
close(fd);
free(promisor_name);
}
fclose(out);
if (finish_command(&cmd))
die(_("could not finish pack-objects to repack promisor objects"));
}
#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
int cmd_repack(int argc, const char **argv, const char *prefix)
{
struct {
const char *name;
unsigned optional:1;
} exts[] = {
{".pack"},
{".idx"},
{".bitmap", 1},
{".promisor", 1},
};
struct child_process cmd = CHILD_PROCESS_INIT;
struct string_list_item *item;
struct string_list names = STRING_LIST_INIT_DUP;
struct string_list rollback = STRING_LIST_INIT_NODUP;
struct string_list existing_packs = STRING_LIST_INIT_DUP;
struct strbuf line = STRBUF_INIT;
int i, ext, ret, failed;
FILE *out;
/* variables to be filled by option parsing */
int pack_everything = 0;
int delete_redundant = 0;
const char *unpack_unreachable = NULL;
int keep_unreachable = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
int no_update_server_info = 0;
int midx_cleared = 0;
struct pack_objects_args po_args = {NULL};
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
N_("pack everything in a single pack"), ALL_INTO_ONE),
OPT_BIT('A', NULL, &pack_everything,
N_("same as -a, and turn unreachable objects loose"),
LOOSEN_UNREACHABLE | ALL_INTO_ONE),
OPT_BOOL('d', NULL, &delete_redundant,
N_("remove redundant packs, and run git-prune-packed")),
OPT_BOOL('f', NULL, &po_args.no_reuse_delta,
N_("pass --no-reuse-delta to git-pack-objects")),
OPT_BOOL('F', NULL, &po_args.no_reuse_object,
N_("pass --no-reuse-object to git-pack-objects")),
OPT_BOOL('n', NULL, &no_update_server_info,
N_("do not run git-update-server-info")),
OPT__QUIET(&po_args.quiet, N_("be quiet")),
OPT_BOOL('l', "local", &po_args.local,
N_("pass --local to git-pack-objects")),
OPT_BOOL('b', "write-bitmap-index", &write_bitmaps,
N_("write bitmap index")),
OPT_BOOL('i', "delta-islands", &use_delta_islands,
N_("pass --delta-islands to git-pack-objects")),
OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"),
N_("with -A, do not loosen objects older than this")),
OPT_BOOL('k', "keep-unreachable", &keep_unreachable,
N_("with -a, repack unreachable objects")),
OPT_STRING(0, "window", &po_args.window, N_("n"),
N_("size of the window used for delta compression")),
OPT_STRING(0, "window-memory", &po_args.window_memory, N_("bytes"),
N_("same as the above, but limit memory size instead of entries count")),
OPT_STRING(0, "depth", &po_args.depth, N_("n"),
N_("limits the maximum delta depth")),
OPT_STRING(0, "threads", &po_args.threads, N_("n"),
N_("limits the maximum number of threads")),
OPT_STRING(0, "max-pack-size", &po_args.max_pack_size, N_("bytes"),
N_("maximum size of each packfile")),
OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects,
N_("repack objects in packs marked with .keep")),
OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
N_("do not repack this pack")),
OPT_END()
};
git_config(repack_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_repack_options,
git_repack_usage, 0);
if (delete_redundant && repository_format_precious_objects)
die(_("cannot delete packs in a precious-objects repo"));
if (keep_unreachable &&
(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE)))
die(_("--keep-unreachable and -A are incompatible"));
if (write_bitmaps < 0) {
if (!(pack_everything & ALL_INTO_ONE) ||
!is_bare_repository())
write_bitmaps = 0;
}
if (pack_kept_objects < 0)
pack_kept_objects = write_bitmaps > 0;
if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
die(_(incremental_bitmap_conflict_error));
packdir = mkpathdup("%s/pack", get_object_directory());
packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
sigchain_push_common(remove_pack_on_signal);
prepare_pack_objects(&cmd, &po_args);
argv_array_push(&cmd.args, "--keep-true-parents");
if (!pack_kept_objects)
argv_array_push(&cmd.args, "--honor-pack-keep");
for (i = 0; i < keep_pack_list.nr; i++)
argv_array_pushf(&cmd.args, "--keep-pack=%s",
keep_pack_list.items[i].string);
argv_array_push(&cmd.args, "--non-empty");
argv_array_push(&cmd.args, "--all");
argv_array_push(&cmd.args, "--reflog");
argv_array_push(&cmd.args, "--indexed-objects");
if (repository_format_partial_clone)
argv_array_push(&cmd.args, "--exclude-promisor-objects");
if (write_bitmaps > 0)
argv_array_push(&cmd.args, "--write-bitmap-index");
else if (write_bitmaps < 0)
argv_array_push(&cmd.args, "--write-bitmap-index-quiet");
if (use_delta_islands)
argv_array_push(&cmd.args, "--delta-islands");
if (pack_everything & ALL_INTO_ONE) {
get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
repack_promisor_objects(&po_args, &names);
if (existing_packs.nr && delete_redundant) {
if (unpack_unreachable) {
argv_array_pushf(&cmd.args,
"--unpack-unreachable=%s",
unpack_unreachable);
argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
} else if (pack_everything & LOOSEN_UNREACHABLE) {
argv_array_push(&cmd.args,
"--unpack-unreachable");
} else if (keep_unreachable) {
argv_array_push(&cmd.args, "--keep-unreachable");
argv_array_push(&cmd.args, "--pack-loose-unreachable");
} else {
argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
}
}
} else {
argv_array_push(&cmd.args, "--unpacked");
argv_array_push(&cmd.args, "--incremental");
}
cmd.no_stdin = 1;
ret = start_command(&cmd);
if (ret)
return ret;
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only from pack-objects."));
string_list_append(&names, line.buf);
}
fclose(out);
ret = finish_command(&cmd);
if (ret)
return ret;
if (!names.nr && !po_args.quiet)
printf_ln(_("Nothing new to pack."));
close_object_store(the_repository->objects);
/*
* Ok we have prepared all new packfiles.
* First see if there are packs of the same name and if so
* if we can move them out of the way (this can happen if we
* repacked immediately after packing fully.
*/
failed = 0;
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname, *fname_old;
if (!midx_cleared) {
clear_midx_file(the_repository);
midx_cleared = 1;
}
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
free(fname);
continue;
}
fname_old = mkpathdup("%s/old-%s%s", packdir,
item->string, exts[ext].name);
if (file_exists(fname_old))
if (unlink(fname_old))
failed = 1;
if (!failed && rename(fname, fname_old)) {
free(fname);
free(fname_old);
failed = 1;
break;
} else {
string_list_append(&rollback, fname);
free(fname_old);
}
}
if (failed)
break;
}
if (failed) {
struct string_list rollback_failure = STRING_LIST_INIT_DUP;
for_each_string_list_item(item, &rollback) {
char *fname, *fname_old;
fname = mkpathdup("%s/%s", packdir, item->string);
fname_old = mkpathdup("%s/old-%s", packdir, item->string);
if (rename(fname_old, fname))
string_list_append(&rollback_failure, fname);
free(fname);
free(fname_old);
}
if (rollback_failure.nr) {
int i;
fprintf(stderr,
_("WARNING: Some packs in use have been renamed by\n"
"WARNING: prefixing old- to their name, in order to\n"
"WARNING: replace them with the new version of the\n"
"WARNING: file. But the operation failed, and the\n"
"WARNING: attempt to rename them back to their\n"
"WARNING: original names also failed.\n"
"WARNING: Please rename them in %s manually:\n"), packdir);
for (i = 0; i < rollback_failure.nr; i++)
fprintf(stderr, "WARNING: old-%s -> %s\n",
rollback_failure.items[i].string,
rollback_failure.items[i].string);
}
exit(1);
}
/* Now the ones with the same name are out of the way... */
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname, *fname_old;
struct stat statbuffer;
int exists = 0;
fname = mkpathdup("%s/pack-%s%s",
packdir, item->string, exts[ext].name);
fname_old = mkpathdup("%s-%s%s",
packtmp, item->string, exts[ext].name);
if (!stat(fname_old, &statbuffer)) {
statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
chmod(fname_old, statbuffer.st_mode);
exists = 1;
}
if (exists || !exts[ext].optional) {
if (rename(fname_old, fname))
die_errno(_("renaming '%s' failed"), fname_old);
}
free(fname);
free(fname_old);
}
}
/* Remove the "old-" files */
for_each_string_list_item(item, &names) {
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname;
fname = mkpathdup("%s/old-%s%s",
packdir,
item->string,
exts[ext].name);
if (remove_path(fname))
warning(_("failed to remove '%s'"), fname);
free(fname);
}
}
/* End of pack replacement. */
reprepare_packed_git(the_repository);
if (delete_redundant) {
const int hexsz = the_hash_algo->hexsz;
int opts = 0;
string_list_sort(&names);
for_each_string_list_item(item, &existing_packs) {
char *sha1;
size_t len = strlen(item->string);
if (len < hexsz)
continue;
sha1 = item->string + len - hexsz;
if (!string_list_has_string(&names, sha1))
remove_redundant_pack(packdir, item->string);
}
if (!po_args.quiet && isatty(2))
opts |= PRUNE_PACKED_VERBOSE;
prune_packed_objects(opts);
if (!keep_unreachable &&
(!(pack_everything & LOOSEN_UNREACHABLE) ||
unpack_unreachable) &&
is_repository_shallow(the_repository))
prune_shallow(PRUNE_QUICK);
}
if (!no_update_server_info)
update_server_info(0);
remove_temporary_files();
if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
write_midx_file(get_object_directory());
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
string_list_clear(&existing_packs, 0);
strbuf_release(&line);
return 0;
}

622
third_party/git/builtin/replace.c vendored Normal file
View file

@ -0,0 +1,622 @@
/*
* Builtin "git replace"
*
* Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
*
* Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
* and Carlos Rica <jasampler@gmail.com> that was itself based on
* git-tag.sh and mktag.c by Linus Torvalds.
*/
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "refs.h"
#include "parse-options.h"
#include "run-command.h"
#include "object-store.h"
#include "repository.h"
#include "tag.h"
static const char * const git_replace_usage[] = {
N_("git replace [-f] <object> <replacement>"),
N_("git replace [-f] --edit <object>"),
N_("git replace [-f] --graft <commit> [<parent>...]"),
N_("git replace [-f] --convert-graft-file"),
N_("git replace -d <object>..."),
N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL
};
enum replace_format {
REPLACE_FORMAT_SHORT,
REPLACE_FORMAT_MEDIUM,
REPLACE_FORMAT_LONG
};
struct show_data {
const char *pattern;
enum replace_format format;
};
static int show_reference(struct repository *r, const char *refname,
const struct object_id *oid,
int flag, void *cb_data)
{
struct show_data *data = cb_data;
if (!wildmatch(data->pattern, refname, 0)) {
if (data->format == REPLACE_FORMAT_SHORT)
printf("%s\n", refname);
else if (data->format == REPLACE_FORMAT_MEDIUM)
printf("%s -> %s\n", refname, oid_to_hex(oid));
else { /* data->format == REPLACE_FORMAT_LONG */
struct object_id object;
enum object_type obj_type, repl_type;
if (get_oid(refname, &object))
return error(_("failed to resolve '%s' as a valid ref"), refname);
obj_type = oid_object_info(r, &object, NULL);
repl_type = oid_object_info(r, oid, NULL);
printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
oid_to_hex(oid), type_name(repl_type));
}
}
return 0;
}
static int list_replace_refs(const char *pattern, const char *format)
{
struct show_data data;
if (pattern == NULL)
pattern = "*";
data.pattern = pattern;
if (format == NULL || *format == '\0' || !strcmp(format, "short"))
data.format = REPLACE_FORMAT_SHORT;
else if (!strcmp(format, "medium"))
data.format = REPLACE_FORMAT_MEDIUM;
else if (!strcmp(format, "long"))
data.format = REPLACE_FORMAT_LONG;
/*
* Please update _git_replace() in git-completion.bash when
* you add new format
*/
else
return error(_("invalid replace format '%s'\n"
"valid formats are 'short', 'medium' and 'long'"),
format);
for_each_replace_ref(the_repository, show_reference, (void *)&data);
return 0;
}
typedef int (*each_replace_name_fn)(const char *name, const char *ref,
const struct object_id *oid);
static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
{
const char **p, *full_hex;
struct strbuf ref = STRBUF_INIT;
size_t base_len;
int had_error = 0;
struct object_id oid;
strbuf_addstr(&ref, git_replace_ref_base);
base_len = ref.len;
for (p = argv; *p; p++) {
if (get_oid(*p, &oid)) {
error("failed to resolve '%s' as a valid ref", *p);
had_error = 1;
continue;
}
strbuf_setlen(&ref, base_len);
strbuf_addstr(&ref, oid_to_hex(&oid));
full_hex = ref.buf + base_len;
if (read_ref(ref.buf, &oid)) {
error(_("replace ref '%s' not found"), full_hex);
had_error = 1;
continue;
}
if (fn(full_hex, ref.buf, &oid))
had_error = 1;
}
strbuf_release(&ref);
return had_error;
}
static int delete_replace_ref(const char *name, const char *ref,
const struct object_id *oid)
{
if (delete_ref(NULL, ref, oid, 0))
return 1;
printf_ln(_("Deleted replace ref '%s'"), name);
return 0;
}
static int check_ref_valid(struct object_id *object,
struct object_id *prev,
struct strbuf *ref,
int force)
{
strbuf_reset(ref);
strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object));
if (check_refname_format(ref->buf, 0))
return error(_("'%s' is not a valid ref name"), ref->buf);
if (read_ref(ref->buf, prev))
oidclr(prev);
else if (!force)
return error(_("replace ref '%s' already exists"), ref->buf);
return 0;
}
static int replace_object_oid(const char *object_ref,
struct object_id *object,
const char *replace_ref,
struct object_id *repl,
int force)
{
struct object_id prev;
enum object_type obj_type, repl_type;
struct strbuf ref = STRBUF_INIT;
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
int res = 0;
obj_type = oid_object_info(the_repository, object, NULL);
repl_type = oid_object_info(the_repository, repl, NULL);
if (!force && obj_type != repl_type)
return error(_("Objects must be of the same type.\n"
"'%s' points to a replaced object of type '%s'\n"
"while '%s' points to a replacement object of "
"type '%s'."),
object_ref, type_name(obj_type),
replace_ref, type_name(repl_type));
if (check_ref_valid(object, &prev, &ref, force)) {
strbuf_release(&ref);
return -1;
}
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
ref_transaction_free(transaction);
strbuf_release(&ref);
return res;
}
static int replace_object(const char *object_ref, const char *replace_ref, int force)
{
struct object_id object, repl;
if (get_oid(object_ref, &object))
return error(_("failed to resolve '%s' as a valid ref"),
object_ref);
if (get_oid(replace_ref, &repl))
return error(_("failed to resolve '%s' as a valid ref"),
replace_ref);
return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
}
/*
* Write the contents of the object named by "sha1" to the file "filename".
* If "raw" is true, then the object's raw contents are printed according to
* "type". Otherwise, we pretty-print the contents for human editing.
*/
static int export_object(const struct object_id *oid, enum object_type type,
int raw, const char *filename)
{
struct child_process cmd = CHILD_PROCESS_INIT;
int fd;
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
return error_errno(_("unable to open %s for writing"), filename);
argv_array_push(&cmd.args, "--no-replace-objects");
argv_array_push(&cmd.args, "cat-file");
if (raw)
argv_array_push(&cmd.args, type_name(type));
else
argv_array_push(&cmd.args, "-p");
argv_array_push(&cmd.args, oid_to_hex(oid));
cmd.git_cmd = 1;
cmd.out = fd;
if (run_command(&cmd))
return error(_("cat-file reported failure"));
return 0;
}
/*
* Read a previously-exported (and possibly edited) object back from "filename",
* interpreting it as "type", and writing the result to the object database.
* The sha1 of the written object is returned via sha1.
*/
static int import_object(struct object_id *oid, enum object_type type,
int raw, const char *filename)
{
int fd;
fd = open(filename, O_RDONLY);
if (fd < 0)
return error_errno(_("unable to open %s for reading"), filename);
if (!raw && type == OBJ_TREE) {
const char *argv[] = { "mktree", NULL };
struct child_process cmd = CHILD_PROCESS_INIT;
struct strbuf result = STRBUF_INIT;
cmd.argv = argv;
cmd.git_cmd = 1;
cmd.in = fd;
cmd.out = -1;
if (start_command(&cmd)) {
close(fd);
return error(_("unable to spawn mktree"));
}
if (strbuf_read(&result, cmd.out, 41) < 0) {
error_errno(_("unable to read from mktree"));
close(fd);
close(cmd.out);
return -1;
}
close(cmd.out);
if (finish_command(&cmd)) {
strbuf_release(&result);
return error(_("mktree reported failure"));
}
if (get_oid_hex(result.buf, oid) < 0) {
strbuf_release(&result);
return error(_("mktree did not return an object name"));
}
strbuf_release(&result);
} else {
struct stat st;
int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
if (fstat(fd, &st) < 0) {
error_errno(_("unable to fstat %s"), filename);
close(fd);
return -1;
}
if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0)
return error(_("unable to write object to database"));
/* index_fd close()s fd for us */
}
/*
* No need to close(fd) here; both run-command and index-fd
* will have done it for us.
*/
return 0;
}
static int edit_and_replace(const char *object_ref, int force, int raw)
{
char *tmpfile;
enum object_type type;
struct object_id old_oid, new_oid, prev;
struct strbuf ref = STRBUF_INIT;
if (get_oid(object_ref, &old_oid) < 0)
return error(_("not a valid object name: '%s'"), object_ref);
type = oid_object_info(the_repository, &old_oid, NULL);
if (type < 0)
return error(_("unable to get object type for %s"),
oid_to_hex(&old_oid));
if (check_ref_valid(&old_oid, &prev, &ref, force)) {
strbuf_release(&ref);
return -1;
}
strbuf_release(&ref);
tmpfile = git_pathdup("REPLACE_EDITOBJ");
if (export_object(&old_oid, type, raw, tmpfile)) {
free(tmpfile);
return -1;
}
if (launch_editor(tmpfile, NULL, NULL) < 0) {
free(tmpfile);
return error(_("editing object file failed"));
}
if (import_object(&new_oid, type, raw, tmpfile)) {
free(tmpfile);
return -1;
}
free(tmpfile);
if (oideq(&old_oid, &new_oid))
return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid));
return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
}
static int replace_parents(struct strbuf *buf, int argc, const char **argv)
{
struct strbuf new_parents = STRBUF_INIT;
const char *parent_start, *parent_end;
int i;
/* find existing parents */
parent_start = buf->buf;
parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */
parent_end = parent_start;
while (starts_with(parent_end, "parent "))
parent_end += 48; /* "parent " + "hex sha1" + "\n" */
/* prepare new parents */
for (i = 0; i < argc; i++) {
struct object_id oid;
struct commit *commit;
if (get_oid(argv[i], &oid) < 0) {
strbuf_release(&new_parents);
return error(_("not a valid object name: '%s'"),
argv[i]);
}
commit = lookup_commit_reference(the_repository, &oid);
if (!commit) {
strbuf_release(&new_parents);
return error(_("could not parse %s as a commit"), argv[i]);
}
strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid));
}
/* replace existing parents with new ones */
strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
new_parents.buf, new_parents.len);
strbuf_release(&new_parents);
return 0;
}
struct check_mergetag_data {
int argc;
const char **argv;
};
static int check_one_mergetag(struct commit *commit,
struct commit_extra_header *extra,
void *data)
{
struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
const char *ref = mergetag_data->argv[0];
struct object_id tag_oid;
struct tag *tag;
int i;
hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);
tag = lookup_tag(the_repository, &tag_oid);
if (!tag)
return error(_("bad mergetag in commit '%s'"), ref);
if (parse_tag_buffer(the_repository, tag, extra->value, extra->len))
return error(_("malformed mergetag in commit '%s'"), ref);
/* iterate over new parents */
for (i = 1; i < mergetag_data->argc; i++) {
struct object_id oid;
if (get_oid(mergetag_data->argv[i], &oid) < 0)
return error(_("not a valid object name: '%s'"),
mergetag_data->argv[i]);
if (oideq(&tag->tagged->oid, &oid))
return 0; /* found */
}
return error(_("original commit '%s' contains mergetag '%s' that is "
"discarded; use --edit instead of --graft"), ref,
oid_to_hex(&tag_oid));
}
static int check_mergetags(struct commit *commit, int argc, const char **argv)
{
struct check_mergetag_data mergetag_data;
mergetag_data.argc = argc;
mergetag_data.argv = argv;
return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
}
static int create_graft(int argc, const char **argv, int force, int gentle)
{
struct object_id old_oid, new_oid;
const char *old_ref = argv[0];
struct commit *commit;
struct strbuf buf = STRBUF_INIT;
const char *buffer;
unsigned long size;
if (get_oid(old_ref, &old_oid) < 0)
return error(_("not a valid object name: '%s'"), old_ref);
commit = lookup_commit_reference(the_repository, &old_oid);
if (!commit)
return error(_("could not parse %s"), old_ref);
buffer = get_commit_buffer(commit, &size);
strbuf_add(&buf, buffer, size);
unuse_commit_buffer(commit, buffer);
if (replace_parents(&buf, argc - 1, &argv[1]) < 0) {
strbuf_release(&buf);
return -1;
}
if (remove_signature(&buf)) {
warning(_("the original commit '%s' has a gpg signature"), old_ref);
warning(_("the signature will be removed in the replacement commit!"));
}
if (check_mergetags(commit, argc, argv)) {
strbuf_release(&buf);
return -1;
}
if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {
strbuf_release(&buf);
return error(_("could not write replacement commit for: '%s'"),
old_ref);
}
strbuf_release(&buf);
if (oideq(&commit->object.oid, &new_oid)) {
if (gentle) {
warning(_("graft for '%s' unnecessary"),
oid_to_hex(&commit->object.oid));
return 0;
}
return error(_("new commit is the same as the old one: '%s'"),
oid_to_hex(&commit->object.oid));
}
return replace_object_oid(old_ref, &commit->object.oid,
"replacement", &new_oid, force);
}
static int convert_graft_file(int force)
{
const char *graft_file = get_graft_file(the_repository);
FILE *fp = fopen_or_warn(graft_file, "r");
struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
struct argv_array args = ARGV_ARRAY_INIT;
if (!fp)
return -1;
advice_graft_file_deprecated = 0;
while (strbuf_getline(&buf, fp) != EOF) {
if (*buf.buf == '#')
continue;
argv_array_split(&args, buf.buf);
if (args.argc && create_graft(args.argc, args.argv, force, 1))
strbuf_addf(&err, "\n\t%s", buf.buf);
argv_array_clear(&args);
}
fclose(fp);
strbuf_release(&buf);
if (!err.len)
return unlink_or_warn(graft_file);
warning(_("could not convert the following graft(s):\n%s"), err.buf);
strbuf_release(&err);
return -1;
}
int cmd_replace(int argc, const char **argv, const char *prefix)
{
int force = 0;
int raw = 0;
const char *format = NULL;
enum {
MODE_UNSPECIFIED = 0,
MODE_LIST,
MODE_DELETE,
MODE_EDIT,
MODE_GRAFT,
MODE_CONVERT_GRAFT_FILE,
MODE_REPLACE
} cmdmode = MODE_UNSPECIFIED;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE),
OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
OPT_END()
};
read_replace_refs = 0;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
if (!cmdmode)
cmdmode = argc ? MODE_REPLACE : MODE_LIST;
if (format && cmdmode != MODE_LIST)
usage_msg_opt(_("--format cannot be used when not listing"),
git_replace_usage, options);
if (force &&
cmdmode != MODE_REPLACE &&
cmdmode != MODE_EDIT &&
cmdmode != MODE_GRAFT &&
cmdmode != MODE_CONVERT_GRAFT_FILE)
usage_msg_opt(_("-f only makes sense when writing a replacement"),
git_replace_usage, options);
if (raw && cmdmode != MODE_EDIT)
usage_msg_opt(_("--raw only makes sense with --edit"),
git_replace_usage, options);
switch (cmdmode) {
case MODE_DELETE:
if (argc < 1)
usage_msg_opt(_("-d needs at least one argument"),
git_replace_usage, options);
return for_each_replace_name(argv, delete_replace_ref);
case MODE_REPLACE:
if (argc != 2)
usage_msg_opt(_("bad number of arguments"),
git_replace_usage, options);
return replace_object(argv[0], argv[1], force);
case MODE_EDIT:
if (argc != 1)
usage_msg_opt(_("-e needs exactly one argument"),
git_replace_usage, options);
return edit_and_replace(argv[0], force, raw);
case MODE_GRAFT:
if (argc < 1)
usage_msg_opt(_("-g needs at least one argument"),
git_replace_usage, options);
return create_graft(argc, argv, force, 0);
case MODE_CONVERT_GRAFT_FILE:
if (argc != 0)
usage_msg_opt(_("--convert-graft-file takes no argument"),
git_replace_usage, options);
return !!convert_graft_file(force);
case MODE_LIST:
if (argc > 1)
usage_msg_opt(_("only one pattern can be given with -l"),
git_replace_usage, options);
return list_replace_refs(argv[0], format);
default:
BUG("invalid cmdmode %d", (int)cmdmode);
}
}

120
third_party/git/builtin/rerere.c vendored Normal file
View file

@ -0,0 +1,120 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "parse-options.h"
#include "string-list.h"
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
#include "pathspec.h"
static const char * const rerere_usage[] = {
N_("git rerere [clear | forget <path>... | status | remaining | diff | gc]"),
NULL,
};
static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
{
int i;
for (i = 0; i < nbuf; i++)
if (write_in_full(1, ptr[i].ptr, ptr[i].size) < 0)
return -1;
return 0;
}
static int diff_two(const char *file1, const char *label1,
const char *file2, const char *label2)
{
xpparam_t xpp;
xdemitconf_t xecfg;
xdemitcb_t ecb;
mmfile_t minus, plus;
int ret;
if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
return -1;
printf("--- a/%s\n+++ b/%s\n", label1, label2);
fflush(stdout);
memset(&xpp, 0, sizeof(xpp));
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
ecb.out_hunk = NULL;
ecb.out_line = outf;
ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
return ret;
}
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
struct string_list merge_rr = STRING_LIST_INIT_DUP;
int i, autoupdate = -1, flags = 0;
struct option options[] = {
OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
N_("register clean resolutions in index"), 1),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
git_config(git_xmerge_config, NULL);
if (autoupdate == 1)
flags = RERERE_AUTOUPDATE;
if (autoupdate == 0)
flags = RERERE_NOAUTOUPDATE;
if (argc < 1)
return repo_rerere(the_repository, flags);
if (!strcmp(argv[0], "forget")) {
struct pathspec pathspec;
if (argc < 2)
warning(_("'git rerere forget' without paths is deprecated"));
parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
prefix, argv + 1);
return rerere_forget(the_repository, &pathspec);
}
if (!strcmp(argv[0], "clear")) {
rerere_clear(the_repository, &merge_rr);
} else if (!strcmp(argv[0], "gc"))
rerere_gc(the_repository, &merge_rr);
else if (!strcmp(argv[0], "status")) {
if (setup_rerere(the_repository, &merge_rr,
flags | RERERE_READONLY) < 0)
return 0;
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
} else if (!strcmp(argv[0], "remaining")) {
rerere_remaining(the_repository, &merge_rr);
for (i = 0; i < merge_rr.nr; i++) {
if (merge_rr.items[i].util != RERERE_RESOLVED)
printf("%s\n", merge_rr.items[i].string);
else
/* prepare for later call to
* string_list_clear() */
merge_rr.items[i].util = NULL;
}
} else if (!strcmp(argv[0], "diff")) {
if (setup_rerere(the_repository, &merge_rr,
flags | RERERE_READONLY) < 0)
return 0;
for (i = 0; i < merge_rr.nr; i++) {
const char *path = merge_rr.items[i].string;
const struct rerere_id *id = merge_rr.items[i].util;
if (diff_two(rerere_path(id, "preimage"), path, path, path))
die(_("unable to generate diff for '%s'"), rerere_path(id, NULL));
}
} else
usage_with_options(rerere_usage, options);
string_list_clear(&merge_rr, 1);
return 0;
}

427
third_party/git/builtin/reset.c vendored Normal file
View file

@ -0,0 +1,427 @@
/*
* "git reset" builtin command
*
* Copyright (c) 2007 Carlos Rica
*
* Based on git-reset.sh, which is
*
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
#include "tag.h"
#include "object.h"
#include "pretty.h"
#include "run-command.h"
#include "refs.h"
#include "diff.h"
#include "diffcore.h"
#include "tree.h"
#include "branch.h"
#include "parse-options.h"
#include "unpack-trees.h"
#include "cache-tree.h"
#include "submodule.h"
#include "submodule-config.h"
#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)
static const char * const git_reset_usage[] = {
N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
NULL
};
enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
static const char *reset_type_names[] = {
N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
};
static inline int is_merge(void)
{
return !access(git_path_merge_head(the_repository), F_OK);
}
static int reset_index(const struct object_id *oid, int reset_type, int quiet)
{
int i, nr = 0;
struct tree_desc desc[2];
struct tree *tree;
struct unpack_trees_options opts;
int ret = -1;
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.src_index = &the_index;
opts.dst_index = &the_index;
opts.fn = oneway_merge;
opts.merge = 1;
if (!quiet)
opts.verbose_update = 1;
switch (reset_type) {
case KEEP:
case MERGE:
opts.update = 1;
break;
case HARD:
opts.update = 1;
/* fallthrough */
default:
opts.reset = 1;
}
read_cache_unmerged();
if (reset_type == KEEP) {
struct object_id head_oid;
if (get_oid("HEAD", &head_oid))
return error(_("You do not have a valid HEAD."));
if (!fill_tree_descriptor(the_repository, desc + nr, &head_oid))
return error(_("Failed to find tree of HEAD."));
nr++;
opts.fn = twoway_merge;
}
if (!fill_tree_descriptor(the_repository, desc + nr, oid)) {
error(_("Failed to find tree of %s."), oid_to_hex(oid));
goto out;
}
nr++;
if (unpack_trees(nr, desc, &opts))
goto out;
if (reset_type == MIXED || reset_type == HARD) {
tree = parse_tree_indirect(oid);
prime_cache_tree(the_repository, the_repository->index, tree);
}
ret = 0;
out:
for (i = 0; i < nr; i++)
free((void *)desc[i].buffer);
return ret;
}
static void print_new_head_line(struct commit *commit)
{
struct strbuf buf = STRBUF_INIT;
printf(_("HEAD is now at %s"),
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);
if (buf.len > 0)
printf(" %s", buf.buf);
putchar('\n');
strbuf_release(&buf);
}
static void update_index_from_diff(struct diff_queue_struct *q,
struct diff_options *opt, void *data)
{
int i;
int intent_to_add = *(int *)data;
for (i = 0; i < q->nr; i++) {
struct diff_filespec *one = q->queue[i]->one;
int is_missing = !(one->mode && !is_null_oid(&one->oid));
struct cache_entry *ce;
if (is_missing && !intent_to_add) {
remove_file_from_cache(one->path);
continue;
}
ce = make_cache_entry(&the_index, one->mode, &one->oid, one->path,
0, 0);
if (!ce)
die(_("make_cache_entry failed for path '%s'"),
one->path);
if (is_missing) {
ce->ce_flags |= CE_INTENT_TO_ADD;
set_object_name_for_intent_to_add_entry(ce);
}
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
}
}
static int read_from_tree(const struct pathspec *pathspec,
struct object_id *tree_oid,
int intent_to_add)
{
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
opt.format_callback_data = &intent_to_add;
opt.flags.override_submodule_config = 1;
opt.repo = the_repository;
if (do_diff_cache(tree_oid, &opt))
return 1;
diffcore_std(&opt);
diff_flush(&opt);
clear_pathspec(&opt.pathspec);
return 0;
}
static void set_reflog_message(struct strbuf *sb, const char *action,
const char *rev)
{
const char *rla = getenv("GIT_REFLOG_ACTION");
strbuf_reset(sb);
if (rla)
strbuf_addf(sb, "%s: %s", rla, action);
else if (rev)
strbuf_addf(sb, "reset: moving to %s", rev);
else
strbuf_addf(sb, "reset: %s", action);
}
static void die_if_unmerged_cache(int reset_type)
{
if (is_merge() || unmerged_cache())
die(_("Cannot do a %s reset in the middle of a merge."),
_(reset_type_names[reset_type]));
}
static void parse_args(struct pathspec *pathspec,
const char **argv, const char *prefix,
int patch_mode,
const char **rev_ret)
{
const char *rev = "HEAD";
struct object_id unused;
/*
* Possible arguments are:
*
* git reset [-opts] [<rev>]
* git reset [-opts] <tree> [<paths>...]
* git reset [-opts] <tree> -- [<paths>...]
* git reset [-opts] -- [<paths>...]
* git reset [-opts] <paths>...
*
* At this point, argv points immediately after [-opts].
*/
if (argv[0]) {
if (!strcmp(argv[0], "--")) {
argv++; /* reset to HEAD, possibly with paths */
} else if (argv[1] && !strcmp(argv[1], "--")) {
rev = argv[0];
argv += 2;
}
/*
* Otherwise, argv[0] could be either <rev> or <paths> and
* has to be unambiguous. If there is a single argument, it
* can not be a tree
*/
else if ((!argv[1] && !get_oid_committish(argv[0], &unused)) ||
(argv[1] && !get_oid_treeish(argv[0], &unused))) {
/*
* Ok, argv[0] looks like a commit/tree; it should not
* be a filename.
*/
verify_non_filename(prefix, argv[0]);
rev = *argv++;
} else {
/* Otherwise we treat this as a filename */
verify_filename(prefix, argv[0], 1);
}
}
*rev_ret = rev;
if (read_cache() < 0)
die(_("index file corrupt"));
parse_pathspec(pathspec, 0,
PATHSPEC_PREFER_FULL |
(patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
prefix, argv);
}
static int reset_refs(const char *rev, const struct object_id *oid)
{
int update_ref_status;
struct strbuf msg = STRBUF_INIT;
struct object_id *orig = NULL, oid_orig,
*old_orig = NULL, oid_old_orig;
if (!get_oid("ORIG_HEAD", &oid_old_orig))
old_orig = &oid_old_orig;
if (!get_oid("HEAD", &oid_orig)) {
orig = &oid_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
set_reflog_message(&msg, "updating HEAD", rev);
update_ref_status = update_ref(msg.buf, "HEAD", oid, orig, 0,
UPDATE_REFS_MSG_ON_ERR);
strbuf_release(&msg);
return update_ref_status;
}
static int git_reset_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "submodule.recurse"))
return git_default_submodule_config(var, value, cb);
return git_default_config(var, value, cb);
}
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int reset_type = NONE, update_ref_status = 0, quiet = 0;
int patch_mode = 0, unborn;
const char *rev;
struct object_id oid;
struct pathspec pathspec;
int intent_to_add = 0;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_SET_INT(0, "mixed", &reset_type,
N_("reset HEAD and index"), MIXED),
OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
OPT_SET_INT(0, "hard", &reset_type,
N_("reset HEAD, index and working tree"), HARD),
OPT_SET_INT(0, "merge", &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),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
"reset", "control recursive updating of submodules",
PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
OPT_BOOL('N', "intent-to-add", &intent_to_add,
N_("record only the fact that removed paths will be added later")),
OPT_END()
};
git_config(git_reset_config, NULL);
git_config_get_bool("reset.quiet", &quiet);
argc = parse_options(argc, argv, prefix, options, git_reset_usage,
PARSE_OPT_KEEP_DASHDASH);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);
unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
oidcpy(&oid, the_hash_algo->empty_tree);
} else if (!pathspec.nr) {
struct commit *commit;
if (get_oid_committish(rev, &oid))
die(_("Failed to resolve '%s' as a valid revision."), rev);
commit = lookup_commit_reference(the_repository, &oid);
if (!commit)
die(_("Could not parse object '%s'."), rev);
oidcpy(&oid, &commit->object.oid);
} else {
struct tree *tree;
if (get_oid_treeish(rev, &oid))
die(_("Failed to resolve '%s' as a valid tree."), rev);
tree = parse_tree_indirect(&oid);
if (!tree)
die(_("Could not parse object '%s'."), rev);
oidcpy(&oid, &tree->object.oid);
}
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
trace2_cmd_mode("patch-interactive");
return run_add_interactive(rev, "--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.nr) {
if (reset_type == MIXED)
warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
die(_("Cannot do %s reset with paths."),
_(reset_type_names[reset_type]));
}
if (reset_type == NONE)
reset_type = MIXED; /* by default */
if (pathspec.nr)
trace2_cmd_mode("path");
else
trace2_cmd_mode(reset_type_names[reset_type]);
if (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))
setup_work_tree();
if (reset_type == MIXED && is_bare_repository())
die(_("%s reset is not allowed in a bare repository"),
_(reset_type_names[reset_type]));
if (intent_to_add && reset_type != MIXED)
die(_("-N can only be used with --mixed"));
/* Soft reset does not touch the index file nor the working tree
* at all, but requires them in a good order. Other resets reset
* the index file to the tree object we are switching to. */
if (reset_type == SOFT || reset_type == KEEP)
die_if_unmerged_cache(reset_type);
if (reset_type != SOFT) {
struct lock_file lock = LOCK_INIT;
hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
if (reset_type == MIXED) {
int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
if (read_from_tree(&pathspec, &oid, intent_to_add))
return 1;
the_index.updated_skipworktree = 1;
if (!quiet && get_git_work_tree()) {
uint64_t t_begin, t_delta_in_ms;
t_begin = getnanotime();
refresh_index(&the_index, flags, NULL, NULL,
_("Unstaged changes after reset:"));
t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n"
"use '--quiet' to avoid this. Set the config setting reset.quiet to true\n"
"to make this the default.\n"), t_delta_in_ms / 1000.0);
}
}
} else {
int err = reset_index(&oid, reset_type, quiet);
if (reset_type == KEEP && !err)
err = reset_index(&oid, MIXED, quiet);
if (err)
die(_("Could not reset index file to revision '%s'."), rev);
}
if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
die(_("Could not write new index file."));
}
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 = reset_refs(rev, &oid);
if (reset_type == HARD && !update_ref_status && !quiet)
print_new_head_line(lookup_commit_reference(the_repository, &oid));
}
if (!pathspec.nr)
remove_branch_state(the_repository, 0);
return update_ref_status;
}

619
third_party/git/builtin/rev-list.c vendored Normal file
View file

@ -0,0 +1,619 @@
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "list-objects.h"
#include "list-objects-filter.h"
#include "list-objects-filter-options.h"
#include "object.h"
#include "object-store.h"
#include "pack.h"
#include "pack-bitmap.h"
#include "builtin.h"
#include "log-tree.h"
#include "graph.h"
#include "bisect.h"
#include "progress.h"
#include "reflog-walk.h"
#include "oidset.h"
#include "packfile.h"
#include "object-store.h"
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
" limiting output:\n"
" --max-count=<n>\n"
" --max-age=<epoch>\n"
" --min-age=<epoch>\n"
" --sparse\n"
" --no-merges\n"
" --min-parents=<n>\n"
" --no-min-parents\n"
" --max-parents=<n>\n"
" --no-max-parents\n"
" --remove-empty\n"
" --all\n"
" --branches\n"
" --tags\n"
" --remotes\n"
" --stdin\n"
" --quiet\n"
" ordering output:\n"
" --topo-order\n"
" --date-order\n"
" --reverse\n"
" formatting output:\n"
" --parents\n"
" --children\n"
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
" --[no-]object-names\n"
" --abbrev=<n> | --no-abbrev\n"
" --abbrev-commit\n"
" --left-right\n"
" --count\n"
" special purpose:\n"
" --bisect\n"
" --bisect-vars\n"
" --bisect-all"
;
static struct progress *progress;
static unsigned progress_counter;
static struct list_objects_filter_options filter_options;
static struct oidset omitted_objects;
static int arg_print_omitted; /* print objects omitted by filter */
static struct oidset missing_objects;
enum missing_action {
MA_ERROR = 0, /* fail if any missing objects are encountered */
MA_ALLOW_ANY, /* silently allow ALL missing objects */
MA_PRINT, /* print ALL missing objects in special section */
MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */
};
static enum missing_action arg_missing_action;
/* display only the oid of each object encountered */
static int arg_show_object_names = 1;
#define DEFAULT_OIDSET_SIZE (16*1024)
static void finish_commit(struct commit *commit);
static void show_commit(struct commit *commit, void *data)
{
struct rev_list_info *info = data;
struct rev_info *revs = info->revs;
display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET) {
finish_commit(commit);
return;
}
graph_show_commit(revs->graph);
if (revs->count) {
if (commit->object.flags & PATCHSAME)
revs->count_same++;
else if (commit->object.flags & SYMMETRIC_LEFT)
revs->count_left++;
else
revs->count_right++;
finish_commit(commit);
return;
}
if (info->show_timestamp)
printf("%"PRItime" ", commit->date);
if (info->header_prefix)
fputs(info->header_prefix, stdout);
if (!revs->graph)
fputs(get_revision_mark(revs, commit), stdout);
if (revs->abbrev_commit && revs->abbrev)
fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev),
stdout);
else
fputs(oid_to_hex(&commit->object.oid), stdout);
if (revs->print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
printf(" %s", oid_to_hex(&parents->item->object.oid));
parents = parents->next;
}
}
if (revs->children.name) {
struct commit_list *children;
children = lookup_decoration(&revs->children, &commit->object);
while (children) {
printf(" %s", oid_to_hex(&children->item->object.oid));
children = children->next;
}
}
show_decorations(revs, commit);
if (revs->commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
if (revs->verbose_header) {
struct strbuf buf = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.abbrev = revs->abbrev;
ctx.date_mode = revs->date_mode;
ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
ctx.output_encoding = get_log_output_encoding();
ctx.color = revs->diffopt.use_color;
pretty_print_commit(&ctx, commit, &buf);
if (buf.len) {
if (revs->commit_format != CMIT_FMT_ONELINE)
graph_show_oneline(revs->graph);
graph_show_commit_msg(revs->graph, stdout, &buf);
/*
* Add a newline after the commit message.
*
* Usually, this newline produces a blank
* padding line between entries, in which case
* we need to add graph padding on this line.
*
* However, the commit message may not end in a
* newline. In this case the newline simply
* ends the last line of the commit message,
* and we don't need any graph output. (This
* always happens with CMIT_FMT_ONELINE, and it
* happens with CMIT_FMT_USERFORMAT when the
* format doesn't explicitly end in a newline.)
*/
if (buf.len && buf.buf[buf.len - 1] == '\n')
graph_show_padding(revs->graph);
putchar(info->hdr_termination);
} else {
/*
* If the message buffer is empty, just show
* the rest of the graph output for this
* commit.
*/
if (graph_show_remainder(revs->graph))
putchar('\n');
if (revs->commit_format == CMIT_FMT_ONELINE)
putchar('\n');
}
strbuf_release(&buf);
} else {
if (graph_show_remainder(revs->graph))
putchar('\n');
}
maybe_flush_or_die(stdout, "stdout");
finish_commit(commit);
}
static void finish_commit(struct commit *commit)
{
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
}
free_commit_buffer(the_repository->parsed_objects,
commit);
}
static inline void finish_object__ma(struct object *obj)
{
/*
* Whether or not we try to dynamically fetch missing objects
* from the server, we currently DO NOT have the object. We
* can either print, allow (ignore), or conditionally allow
* (ignore) them.
*/
switch (arg_missing_action) {
case MA_ERROR:
die("missing %s object '%s'",
type_name(obj->type), oid_to_hex(&obj->oid));
return;
case MA_ALLOW_ANY:
return;
case MA_PRINT:
oidset_insert(&missing_objects, &obj->oid);
return;
case MA_ALLOW_PROMISOR:
if (is_promisor_object(&obj->oid))
return;
die("unexpected missing %s object '%s'",
type_name(obj->type), oid_to_hex(&obj->oid));
return;
default:
BUG("unhandled missing_action");
return;
}
}
static int finish_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) {
finish_object__ma(obj);
return 1;
}
if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
parse_object(the_repository, &obj->oid);
return 0;
}
static void show_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
if (finish_object(obj, name, cb_data))
return;
display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET)
return;
if (arg_show_object_names)
show_object_with_name(stdout, obj, name);
else
printf("%s\n", oid_to_hex(&obj->oid));
}
static void show_edge(struct commit *commit)
{
printf("-%s\n", oid_to_hex(&commit->object.oid));
}
static void print_var_str(const char *var, const char *val)
{
printf("%s='%s'\n", var, val);
}
static void print_var_int(const char *var, int val)
{
printf("%s=%d\n", var, val);
}
static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
{
int cnt, flags = info->flags;
char hex[GIT_MAX_HEXSZ + 1] = "";
struct commit_list *tried;
struct rev_info *revs = info->revs;
if (!revs->commits)
return 1;
revs->commits = filter_skipped(revs->commits, &tried,
flags & BISECT_SHOW_ALL,
NULL, NULL);
/*
* revs->commits can reach "reaches" commits among
* "all" commits. If it is good, then there are
* (all-reaches) commits left to be bisected.
* On the other hand, if it is bad, then the set
* to bisect is "reaches".
* A bisect set of size N has (N-1) commits further
* to test, as we already know one bad one.
*/
cnt = all - reaches;
if (cnt < reaches)
cnt = reaches;
if (revs->commits)
oid_to_hex_r(hex, &revs->commits->item->object.oid);
if (flags & BISECT_SHOW_ALL) {
traverse_commit_list(revs, show_commit, show_object, info);
printf("------\n");
}
print_var_str("bisect_rev", hex);
print_var_int("bisect_nr", cnt - 1);
print_var_int("bisect_good", all - reaches - 1);
print_var_int("bisect_bad", reaches - 1);
print_var_int("bisect_all", all);
print_var_int("bisect_steps", estimate_bisect_steps(all));
return 0;
}
static int show_object_fast(
const struct object_id *oid,
enum object_type type,
int exclude,
uint32_t name_hash,
struct packed_git *found_pack,
off_t found_offset)
{
fprintf(stdout, "%s\n", oid_to_hex(oid));
return 1;
}
static inline int parse_missing_action_value(const char *value)
{
if (!strcmp(value, "error")) {
arg_missing_action = MA_ERROR;
return 1;
}
if (!strcmp(value, "allow-any")) {
arg_missing_action = MA_ALLOW_ANY;
fetch_if_missing = 0;
return 1;
}
if (!strcmp(value, "print")) {
arg_missing_action = MA_PRINT;
fetch_if_missing = 0;
return 1;
}
if (!strcmp(value, "allow-promisor")) {
arg_missing_action = MA_ALLOW_PROMISOR;
fetch_if_missing = 0;
return 1;
}
return 0;
}
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct rev_list_info info;
struct setup_revision_opt s_r_opt = {
.allow_exclude_promisor_objects = 1,
};
int i;
int bisect_list = 0;
int bisect_show_vars = 0;
int bisect_find_all = 0;
int use_bitmap_index = 0;
const char *show_progress = NULL;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(rev_list_usage);
git_config(git_default_config, NULL);
repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
/*
* Scan the argument list before invoking setup_revisions(), so that we
* know if fetch_if_missing needs to be set to 0.
*
* "--exclude-promisor-objects" acts as a pre-filter on missing objects
* by not crossing the boundary from realized objects to promisor
* objects.
*
* Let "--missing" to conditionally set fetch_if_missing.
*/
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--exclude-promisor-objects")) {
fetch_if_missing = 0;
revs.exclude_promisor_objects = 1;
break;
}
}
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (skip_prefix(arg, "--missing=", &arg)) {
if (revs.exclude_promisor_objects)
die(_("cannot combine --exclude-promisor-objects and --missing"));
if (parse_missing_action_value(arg))
break;
}
}
if (arg_missing_action)
revs.do_not_die_on_missing_tree = 1;
argc = setup_revisions(argc, argv, &revs, &s_r_opt);
memset(&info, 0, sizeof(info));
info.revs = &revs;
if (revs.bisect)
bisect_list = 1;
if (revs.diffopt.flags.quick)
info.flags |= REV_LIST_QUIET;
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--header")) {
revs.verbose_header = 1;
continue;
}
if (!strcmp(arg, "--timestamp")) {
info.show_timestamp = 1;
continue;
}
if (!strcmp(arg, "--bisect")) {
bisect_list = 1;
continue;
}
if (!strcmp(arg, "--bisect-all")) {
bisect_list = 1;
bisect_find_all = 1;
info.flags |= BISECT_SHOW_ALL;
revs.show_decorations = 1;
continue;
}
if (!strcmp(arg, "--bisect-vars")) {
bisect_list = 1;
bisect_show_vars = 1;
continue;
}
if (!strcmp(arg, "--use-bitmap-index")) {
use_bitmap_index = 1;
continue;
}
if (!strcmp(arg, "--test-bitmap")) {
test_bitmap_walk(&revs);
return 0;
}
if (skip_prefix(arg, "--progress=", &arg)) {
show_progress = arg;
continue;
}
if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
parse_list_objects_filter(&filter_options, arg);
if (filter_options.choice && !revs.blob_objects)
die(_("object filtering requires --objects"));
if (filter_options.choice == LOFC_SPARSE_OID &&
!filter_options.sparse_oid_value)
die(_("invalid sparse value '%s'"),
filter_options.filter_spec);
continue;
}
if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
list_objects_filter_set_no_filter(&filter_options);
continue;
}
if (!strcmp(arg, "--filter-print-omitted")) {
arg_print_omitted = 1;
continue;
}
if (!strcmp(arg, "--exclude-promisor-objects"))
continue; /* already handled above */
if (skip_prefix(arg, "--missing=", &arg))
continue; /* already handled above */
if (!strcmp(arg, ("--no-object-names"))) {
arg_show_object_names = 0;
continue;
}
if (!strcmp(arg, ("--object-names"))) {
arg_show_object_names = 1;
continue;
}
usage(rev_list_usage);
}
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
/* The command line has a --pretty */
info.hdr_termination = '\n';
if (revs.commit_format == CMIT_FMT_ONELINE)
info.header_prefix = "";
else
info.header_prefix = "commit ";
}
else if (revs.verbose_header)
/* Only --header was specified */
revs.commit_format = CMIT_FMT_RAW;
if ((!revs.commits && reflog_walk_empty(revs.reflog_info) &&
(!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
!revs.pending.nr) &&
!revs.rev_input_given && !revs.read_from_stdin) ||
revs.diff)
usage(rev_list_usage);
if (revs.show_notes)
die(_("rev-list does not support display of notes"));
if (filter_options.choice && use_bitmap_index)
die(_("cannot combine --use-bitmap-index with object filtering"));
save_commit_buffer = (revs.verbose_header ||
revs.grep_filter.pattern_list ||
revs.grep_filter.header_list);
if (bisect_list)
revs.limited = 1;
if (show_progress)
progress = start_delayed_progress(show_progress, 0);
if (use_bitmap_index && !revs.prune) {
if (revs.count && !revs.left_right && !revs.cherry_mark) {
uint32_t commit_count;
int max_count = revs.max_count;
struct bitmap_index *bitmap_git;
if ((bitmap_git = prepare_bitmap_walk(&revs))) {
count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL);
if (max_count >= 0 && max_count < commit_count)
commit_count = max_count;
printf("%d\n", commit_count);
free_bitmap_index(bitmap_git);
return 0;
}
} else if (revs.max_count < 0 &&
revs.tag_objects && revs.tree_objects && revs.blob_objects) {
struct bitmap_index *bitmap_git;
if ((bitmap_git = prepare_bitmap_walk(&revs))) {
traverse_bitmap_commit_list(bitmap_git, &show_object_fast);
free_bitmap_index(bitmap_git);
return 0;
}
}
}
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
if (revs.tree_objects)
mark_edges_uninteresting(&revs, show_edge, 0);
if (bisect_list) {
int reaches, all;
find_bisection(&revs.commits, &reaches, &all, bisect_find_all);
if (bisect_show_vars)
return show_bisect_vars(&info, reaches, all);
}
if (arg_print_omitted)
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT)
oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE);
traverse_commit_list_filtered(
&filter_options, &revs, show_commit, show_object, &info,
(arg_print_omitted ? &omitted_objects : NULL));
if (arg_print_omitted) {
struct oidset_iter iter;
struct object_id *oid;
oidset_iter_init(&omitted_objects, &iter);
while ((oid = oidset_iter_next(&iter)))
printf("~%s\n", oid_to_hex(oid));
oidset_clear(&omitted_objects);
}
if (arg_missing_action == MA_PRINT) {
struct oidset_iter iter;
struct object_id *oid;
oidset_iter_init(&missing_objects, &iter);
while ((oid = oidset_iter_next(&iter)))
printf("?%s\n", oid_to_hex(oid));
oidset_clear(&missing_objects);
}
stop_progress(&progress);
if (revs.count) {
if (revs.left_right && revs.cherry_mark)
printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same);
else if (revs.left_right)
printf("%d\t%d\n", revs.count_left, revs.count_right);
else if (revs.cherry_mark)
printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same);
else
printf("%d\n", revs.count_left + revs.count_right);
}
return 0;
}

965
third_party/git/builtin/rev-parse.c vendored Normal file
View file

@ -0,0 +1,965 @@
/*
* rev-parse.c
*
* Copyright (C) Linus Torvalds, 2005
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "refs.h"
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
#include "split-index.h"
#include "submodule.h"
#include "commit-reach.h"
#define DO_REVS 1
#define DO_NOREV 2
#define DO_FLAGS 4
#define DO_NONFLAGS 8
static int filter = ~0;
static const char *def;
#define NORMAL 0
#define REVERSED 1
static int show_type = NORMAL;
#define SHOW_SYMBOLIC_ASIS 1
#define SHOW_SYMBOLIC_FULL 2
static int symbolic;
static int abbrev;
static int abbrev_ref;
static int abbrev_ref_strict;
static int output_sq;
static int stuck_long;
static struct string_list *ref_excludes;
/*
* Some arguments are relevant "revision" arguments,
* others are about output format or other details.
* This sorts it all out.
*/
static int is_rev_argument(const char *arg)
{
static const char *rev_args[] = {
"--all",
"--bisect",
"--dense",
"--branches=",
"--branches",
"--header",
"--ignore-missing",
"--max-age=",
"--max-count=",
"--min-age=",
"--no-merges",
"--min-parents=",
"--no-min-parents",
"--max-parents=",
"--no-max-parents",
"--objects",
"--objects-edge",
"--parents",
"--pretty",
"--remotes=",
"--remotes",
"--glob=",
"--sparse",
"--tags=",
"--tags",
"--topo-order",
"--date-order",
"--unpacked",
NULL
};
const char **p = rev_args;
/* accept -<digit>, like traditional "head" */
if ((*arg == '-') && isdigit(arg[1]))
return 1;
for (;;) {
const char *str = *p++;
int len;
if (!str)
return 0;
len = strlen(str);
if (!strcmp(arg, str) ||
(str[len-1] == '=' && !strncmp(arg, str, len)))
return 1;
}
}
/* Output argument as a string, either SQ or normal */
static void show(const char *arg)
{
if (output_sq) {
int sq = '\'', ch;
putchar(sq);
while ((ch = *arg++)) {
if (ch == sq)
fputs("'\\'", stdout);
putchar(ch);
}
putchar(sq);
putchar(' ');
}
else
puts(arg);
}
/* Like show(), but with a negation prefix according to type */
static void show_with_type(int type, const char *arg)
{
if (type != show_type)
putchar('^');
show(arg);
}
/* Output a revision, only if filter allows it */
static void show_rev(int type, const struct object_id *oid, const char *name)
{
if (!(filter & DO_REVS))
return;
def = NULL;
if ((symbolic || abbrev_ref) && name) {
if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
struct object_id discard;
char *full;
switch (dwim_ref(name, strlen(name), &discard, &full)) {
case 0:
/*
* Not found -- not a ref. We could
* emit "name" here, but symbolic-full
* users are interested in finding the
* refs spelled in full, and they would
* need to filter non-refs if we did so.
*/
break;
case 1: /* happy */
if (abbrev_ref)
full = shorten_unambiguous_ref(full,
abbrev_ref_strict);
show_with_type(type, full);
break;
default: /* ambiguous */
error("refname '%s' is ambiguous", name);
break;
}
free(full);
} else {
show_with_type(type, name);
}
}
else if (abbrev)
show_with_type(type, find_unique_abbrev(oid, abbrev));
else
show_with_type(type, oid_to_hex(oid));
}
/* Output a flag, only if filter allows it. */
static int show_flag(const char *arg)
{
if (!(filter & DO_FLAGS))
return 0;
if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV)) {
show(arg);
return 1;
}
return 0;
}
static int show_default(void)
{
const char *s = def;
if (s) {
struct object_id oid;
def = NULL;
if (!get_oid(s, &oid)) {
show_rev(NORMAL, &oid, s);
return 1;
}
}
return 0;
}
static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
if (ref_excluded(ref_excludes, refname))
return 0;
show_rev(NORMAL, oid, refname);
return 0;
}
static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
show_rev(REVERSED, oid, refname);
return 0;
}
static int show_abbrev(const struct object_id *oid, void *cb_data)
{
show_rev(NORMAL, oid, NULL);
return 0;
}
static void show_datestring(const char *flag, const char *datestr)
{
char *buffer;
/* date handling requires both flags and revs */
if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
return;
buffer = xstrfmt("%s%"PRItime, flag, approxidate(datestr));
show(buffer);
free(buffer);
}
static int show_file(const char *arg, int output_prefix)
{
show_default();
if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
if (output_prefix) {
const char *prefix = startup_info->prefix;
char *fname = prefix_filename(prefix, arg);
show(fname);
free(fname);
} else
show(arg);
return 1;
}
return 0;
}
static int try_difference(const char *arg)
{
char *dotdot;
struct object_id start_oid;
struct object_id end_oid;
const char *end;
const char *start;
int symmetric;
static const char head_by_default[] = "HEAD";
if (!(dotdot = strstr(arg, "..")))
return 0;
end = dotdot + 2;
start = arg;
symmetric = (*end == '.');
*dotdot = 0;
end += symmetric;
if (!*end)
end = head_by_default;
if (dotdot == arg)
start = head_by_default;
if (start == head_by_default && end == head_by_default &&
!symmetric) {
/*
* Just ".."? That is not a range but the
* pathspec for the parent directory.
*/
*dotdot = '.';
return 0;
}
if (!get_oid_committish(start, &start_oid) && !get_oid_committish(end, &end_oid)) {
show_rev(NORMAL, &end_oid, end);
show_rev(symmetric ? NORMAL : REVERSED, &start_oid, start);
if (symmetric) {
struct commit_list *exclude;
struct commit *a, *b;
a = lookup_commit_reference(the_repository, &start_oid);
b = lookup_commit_reference(the_repository, &end_oid);
if (!a || !b) {
*dotdot = '.';
return 0;
}
exclude = get_merge_bases(a, b);
while (exclude) {
struct commit *commit = pop_commit(&exclude);
show_rev(REVERSED, &commit->object.oid, NULL);
}
}
*dotdot = '.';
return 1;
}
*dotdot = '.';
return 0;
}
static int try_parent_shorthands(const char *arg)
{
char *dotdot;
struct object_id oid;
struct commit *commit;
struct commit_list *parents;
int parent_number;
int include_rev = 0;
int include_parents = 0;
int exclude_parent = 0;
if ((dotdot = strstr(arg, "^!"))) {
include_rev = 1;
if (dotdot[2])
return 0;
} else if ((dotdot = strstr(arg, "^@"))) {
include_parents = 1;
if (dotdot[2])
return 0;
} else if ((dotdot = strstr(arg, "^-"))) {
include_rev = 1;
exclude_parent = 1;
if (dotdot[2]) {
char *end;
exclude_parent = strtoul(dotdot + 2, &end, 10);
if (*end != '\0' || !exclude_parent)
return 0;
}
} else
return 0;
*dotdot = 0;
if (get_oid_committish(arg, &oid) ||
!(commit = lookup_commit_reference(the_repository, &oid))) {
*dotdot = '^';
return 0;
}
if (exclude_parent &&
exclude_parent > commit_list_count(commit->parents)) {
*dotdot = '^';
return 0;
}
if (include_rev)
show_rev(NORMAL, &oid, arg);
for (parents = commit->parents, parent_number = 1;
parents;
parents = parents->next, parent_number++) {
char *name = NULL;
if (exclude_parent && parent_number != exclude_parent)
continue;
if (symbolic)
name = xstrfmt("%s^%d", arg, parent_number);
show_rev(include_parents ? NORMAL : REVERSED,
&parents->item->object.oid, name);
free(name);
}
*dotdot = '^';
return 1;
}
static int parseopt_dump(const struct option *o, const char *arg, int unset)
{
struct strbuf *parsed = o->value;
if (unset)
strbuf_addf(parsed, " --no-%s", o->long_name);
else if (o->short_name && (o->long_name == NULL || !stuck_long))
strbuf_addf(parsed, " -%c", o->short_name);
else
strbuf_addf(parsed, " --%s", o->long_name);
if (arg) {
if (!stuck_long)
strbuf_addch(parsed, ' ');
else if (o->long_name)
strbuf_addch(parsed, '=');
sq_quote_buf(parsed, arg);
}
return 0;
}
static const char *skipspaces(const char *s)
{
while (isspace(*s))
s++;
return s;
}
static char *findspace(const char *s)
{
for (; *s; s++)
if (isspace(*s))
return (char*)s;
return NULL;
}
static int cmd_parseopt(int argc, const char **argv, const char *prefix)
{
static int keep_dashdash = 0, stop_at_non_option = 0;
static char const * const parseopt_usage[] = {
N_("git rev-parse --parseopt [<options>] -- [<args>...]"),
NULL
};
static struct option parseopt_opts[] = {
OPT_BOOL(0, "keep-dashdash", &keep_dashdash,
N_("keep the `--` passed as an arg")),
OPT_BOOL(0, "stop-at-non-option", &stop_at_non_option,
N_("stop parsing after the "
"first non-option argument")),
OPT_BOOL(0, "stuck-long", &stuck_long,
N_("output in stuck long form")),
OPT_END(),
};
static const char * const flag_chars = "*=?!";
struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
const char **usage = NULL;
struct option *opts = NULL;
int onb = 0, osz = 0, unb = 0, usz = 0;
strbuf_addstr(&parsed, "set --");
argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
PARSE_OPT_KEEP_DASHDASH);
if (argc < 1 || strcmp(argv[0], "--"))
usage_with_options(parseopt_usage, parseopt_opts);
/* get the usage up to the first line with a -- on it */
for (;;) {
if (strbuf_getline(&sb, stdin) == EOF)
die("premature end of input");
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) {
if (unb < 1)
die("no usage string given before the `--' separator");
usage[unb] = NULL;
break;
}
usage[unb++] = strbuf_detach(&sb, NULL);
}
/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
while (strbuf_getline(&sb, stdin) != EOF) {
const char *s;
char *help;
struct option *o;
if (!sb.len)
continue;
ALLOC_GROW(opts, onb + 1, osz);
memset(opts + onb, 0, sizeof(opts[onb]));
o = &opts[onb++];
help = findspace(sb.buf);
if (!help || sb.buf == help) {
o->type = OPTION_GROUP;
o->help = xstrdup(skipspaces(sb.buf));
continue;
}
*help = '\0';
o->type = OPTION_CALLBACK;
o->help = xstrdup(skipspaces(help+1));
o->value = &parsed;
o->flags = PARSE_OPT_NOARG;
o->callback = &parseopt_dump;
/* name(s) */
s = strpbrk(sb.buf, flag_chars);
if (s == NULL)
s = help;
if (s - sb.buf == 1) /* short option only */
o->short_name = *sb.buf;
else if (sb.buf[1] != ',') /* long option only */
o->long_name = xmemdupz(sb.buf, s - sb.buf);
else {
o->short_name = *sb.buf;
o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
}
/* flags */
while (s < help) {
switch (*s++) {
case '=':
o->flags &= ~PARSE_OPT_NOARG;
continue;
case '?':
o->flags &= ~PARSE_OPT_NOARG;
o->flags |= PARSE_OPT_OPTARG;
continue;
case '!':
o->flags |= PARSE_OPT_NONEG;
continue;
case '*':
o->flags |= PARSE_OPT_HIDDEN;
continue;
}
s--;
break;
}
if (s < help)
o->argh = xmemdupz(s, help - s);
}
strbuf_release(&sb);
/* put an OPT_END() */
ALLOC_GROW(opts, onb + 1, osz);
memset(opts + onb, 0, sizeof(opts[onb]));
argc = parse_options(argc, argv, prefix, opts, usage,
(keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |
(stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |
PARSE_OPT_SHELL_EVAL);
strbuf_addstr(&parsed, " --");
sq_quote_argv(&parsed, argv);
puts(parsed.buf);
return 0;
}
static int cmd_sq_quote(int argc, const char **argv)
{
struct strbuf buf = STRBUF_INIT;
if (argc)
sq_quote_argv(&buf, argv);
printf("%s\n", buf.buf);
strbuf_release(&buf);
return 0;
}
static void die_no_single_rev(int quiet)
{
if (quiet)
exit(1);
else
die("Needed a single revision");
}
static const char builtin_rev_parse_usage[] =
N_("git rev-parse --parseopt [<options>] -- [<args>...]\n"
" or: git rev-parse --sq-quote [<arg>...]\n"
" or: git rev-parse [<options>] [<arg>...]\n"
"\n"
"Run \"git rev-parse --parseopt -h\" for more information on the first usage.");
/*
* Parse "opt" or "opt=<value>", setting value respectively to either
* NULL or the string after "=".
*/
static int opt_with_value(const char *arg, const char *opt, const char **value)
{
if (skip_prefix(arg, opt, &arg)) {
if (!*arg) {
*value = NULL;
return 1;
}
if (*arg++ == '=') {
*value = arg;
return 1;
}
}
return 0;
}
static void handle_ref_opt(const char *pattern, const char *prefix)
{
if (pattern)
for_each_glob_ref_in(show_reference, pattern, prefix, NULL);
else
for_each_ref_in(prefix, show_reference, NULL);
clear_ref_exclusion(&ref_excludes);
}
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
int did_repo_setup = 0;
int has_dashdash = 0;
int output_prefix = 0;
struct object_id oid;
unsigned int flags = 0;
const char *name = NULL;
struct object_context unused;
struct strbuf buf = STRBUF_INIT;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
if (argc > 1 && !strcmp("--sq-quote", argv[1]))
return cmd_sq_quote(argc - 2, argv + 2);
if (argc > 1 && !strcmp("-h", argv[1]))
usage(builtin_rev_parse_usage);
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--")) {
has_dashdash = 1;
break;
}
}
/* No options; just report on whether we're in a git repo or not. */
if (argc == 1) {
setup_git_directory();
git_config(git_default_config, NULL);
return 0;
}
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--local-env-vars")) {
int i;
for (i = 0; local_repo_env[i]; i++)
printf("%s\n", local_repo_env[i]);
continue;
}
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir = argv[++i];
if (!gitdir)
die("--resolve-git-dir requires an argument");
gitdir = resolve_gitdir(gitdir);
if (!gitdir)
die("not a gitdir '%s'", argv[i]);
puts(gitdir);
continue;
}
/* The rest of the options require a git repository. */
if (!did_repo_setup) {
prefix = setup_git_directory();
git_config(git_default_config, NULL);
did_repo_setup = 1;
}
if (!strcmp(arg, "--git-path")) {
if (!argv[i + 1])
die("--git-path requires an argument");
strbuf_reset(&buf);
puts(relative_path(git_path("%s", argv[i + 1]),
prefix, &buf));
i++;
continue;
}
if (as_is) {
if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
continue;
}
if (!strcmp(arg,"-n")) {
if (++i >= argc)
die("-n requires an argument");
if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
show(arg);
show(argv[i]);
}
continue;
}
if (starts_with(arg, "-n")) {
if ((filter & DO_FLAGS) && (filter & DO_REVS))
show(arg);
continue;
}
if (*arg == '-') {
if (!strcmp(arg, "--")) {
as_is = 2;
/* Pass on the "--" if we show anything but files.. */
if (filter & (DO_FLAGS | DO_REVS))
show_file(arg, 0);
continue;
}
if (!strcmp(arg, "--default")) {
def = argv[++i];
if (!def)
die("--default requires an argument");
continue;
}
if (!strcmp(arg, "--prefix")) {
prefix = argv[++i];
if (!prefix)
die("--prefix requires an argument");
startup_info->prefix = prefix;
output_prefix = 1;
continue;
}
if (!strcmp(arg, "--revs-only")) {
filter &= ~DO_NOREV;
continue;
}
if (!strcmp(arg, "--no-revs")) {
filter &= ~DO_REVS;
continue;
}
if (!strcmp(arg, "--flags")) {
filter &= ~DO_NONFLAGS;
continue;
}
if (!strcmp(arg, "--no-flags")) {
filter &= ~DO_FLAGS;
continue;
}
if (!strcmp(arg, "--verify")) {
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
continue;
}
if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
quiet = 1;
flags |= GET_OID_QUIETLY;
continue;
}
if (opt_with_value(arg, "--short", &arg)) {
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
abbrev = DEFAULT_ABBREV;
if (!arg)
continue;
abbrev = strtoul(arg, NULL, 10);
if (abbrev < MINIMUM_ABBREV)
abbrev = MINIMUM_ABBREV;
else if (40 <= abbrev)
abbrev = 40;
continue;
}
if (!strcmp(arg, "--sq")) {
output_sq = 1;
continue;
}
if (!strcmp(arg, "--not")) {
show_type ^= REVERSED;
continue;
}
if (!strcmp(arg, "--symbolic")) {
symbolic = SHOW_SYMBOLIC_ASIS;
continue;
}
if (!strcmp(arg, "--symbolic-full-name")) {
symbolic = SHOW_SYMBOLIC_FULL;
continue;
}
if (opt_with_value(arg, "--abbrev-ref", &arg)) {
abbrev_ref = 1;
abbrev_ref_strict = warn_ambiguous_refs;
if (arg) {
if (!strcmp(arg, "strict"))
abbrev_ref_strict = 1;
else if (!strcmp(arg, "loose"))
abbrev_ref_strict = 0;
else
die("unknown mode for --abbrev-ref: %s",
arg);
}
continue;
}
if (!strcmp(arg, "--all")) {
for_each_ref(show_reference, NULL);
clear_ref_exclusion(&ref_excludes);
continue;
}
if (skip_prefix(arg, "--disambiguate=", &arg)) {
for_each_abbrev(arg, show_abbrev, NULL);
continue;
}
if (!strcmp(arg, "--bisect")) {
for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0);
for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {
handle_ref_opt(arg, "refs/heads/");
continue;
}
if (opt_with_value(arg, "--tags", &arg)) {
handle_ref_opt(arg, "refs/tags/");
continue;
}
if (skip_prefix(arg, "--glob=", &arg)) {
handle_ref_opt(arg, NULL);
continue;
}
if (opt_with_value(arg, "--remotes", &arg)) {
handle_ref_opt(arg, "refs/remotes/");
continue;
}
if (skip_prefix(arg, "--exclude=", &arg)) {
add_ref_exclusion(&ref_excludes, arg);
continue;
}
if (!strcmp(arg, "--show-toplevel")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
puts(work_tree);
continue;
}
if (!strcmp(arg, "--show-superproject-working-tree")) {
const char *superproject = get_superproject_working_tree();
if (superproject)
puts(superproject);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
if (prefix)
puts(prefix);
else
putchar('\n');
continue;
}
if (!strcmp(arg, "--show-cdup")) {
const char *pfx = prefix;
if (!is_inside_work_tree()) {
const char *work_tree =
get_git_work_tree();
if (work_tree)
printf("%s\n", work_tree);
continue;
}
while (pfx) {
pfx = strchr(pfx, '/');
if (pfx) {
pfx++;
printf("../");
}
}
putchar('\n');
continue;
}
if (!strcmp(arg, "--git-dir") ||
!strcmp(arg, "--absolute-git-dir")) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
char *cwd;
int len;
if (arg[2] == 'g') { /* --git-dir */
if (gitdir) {
puts(gitdir);
continue;
}
if (!prefix) {
puts(".git");
continue;
}
} else { /* --absolute-git-dir */
if (!gitdir && !prefix)
gitdir = ".git";
if (gitdir) {
puts(real_path(gitdir));
continue;
}
}
cwd = xgetcwd();
len = strlen(cwd);
printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
free(cwd);
continue;
}
if (!strcmp(arg, "--git-common-dir")) {
strbuf_reset(&buf);
puts(relative_path(get_git_common_dir(),
prefix, &buf));
continue;
}
if (!strcmp(arg, "--is-inside-git-dir")) {
printf("%s\n", is_inside_git_dir() ? "true"
: "false");
continue;
}
if (!strcmp(arg, "--is-inside-work-tree")) {
printf("%s\n", is_inside_work_tree() ? "true"
: "false");
continue;
}
if (!strcmp(arg, "--is-bare-repository")) {
printf("%s\n", is_bare_repository() ? "true"
: "false");
continue;
}
if (!strcmp(arg, "--is-shallow-repository")) {
printf("%s\n",
is_repository_shallow(the_repository) ? "true"
: "false");
continue;
}
if (!strcmp(arg, "--shared-index-path")) {
if (read_cache() < 0)
die(_("Could not read the index"));
if (the_index.split_index) {
const struct object_id *oid = &the_index.split_index->base_oid;
const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
strbuf_reset(&buf);
puts(relative_path(path, prefix, &buf));
}
continue;
}
if (skip_prefix(arg, "--since=", &arg)) {
show_datestring("--max-age=", arg);
continue;
}
if (skip_prefix(arg, "--after=", &arg)) {
show_datestring("--max-age=", arg);
continue;
}
if (skip_prefix(arg, "--before=", &arg)) {
show_datestring("--min-age=", arg);
continue;
}
if (skip_prefix(arg, "--until=", &arg)) {
show_datestring("--min-age=", arg);
continue;
}
if (show_flag(arg) && verify)
die_no_single_rev(quiet);
continue;
}
/* Not a flag argument */
if (try_difference(arg))
continue;
if (try_parent_shorthands(arg))
continue;
name = arg;
type = NORMAL;
if (*arg == '^') {
name++;
type = REVERSED;
}
if (!get_oid_with_context(the_repository, name,
flags, &oid, &unused)) {
if (verify)
revs_count++;
else
show_rev(type, &oid, name);
continue;
}
if (verify)
die_no_single_rev(quiet);
if (has_dashdash)
die("bad revision '%s'", arg);
as_is = 1;
if (!show_file(arg, output_prefix))
continue;
verify_filename(prefix, arg, 1);
}
strbuf_release(&buf);
if (verify) {
if (revs_count == 1) {
show_rev(type, &oid, name);
return 0;
} else if (revs_count == 0 && show_default())
return 0;
die_no_single_rev(quiet);
} else
show_default();
return 0;
}

247
third_party/git/builtin/revert.c vendored Normal file
View file

@ -0,0 +1,247 @@
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
#include "rerere.h"
#include "dir.h"
#include "sequencer.h"
#include "branch.h"
/*
* This implements the builtins revert and cherry-pick.
*
* Copyright (c) 2007 Johannes E. Schindelin
*
* Based on git-revert.sh, which is
*
* Copyright (c) 2005 Linus Torvalds
* Copyright (c) 2005 Junio C Hamano
*/
static const char * const revert_usage[] = {
N_("git revert [<options>] <commit-ish>..."),
N_("git revert <subcommand>"),
NULL
};
static const char * const cherry_pick_usage[] = {
N_("git cherry-pick [<options>] <commit-ish>..."),
N_("git cherry-pick <subcommand>"),
NULL
};
static const char *action_name(const struct replay_opts *opts)
{
return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
}
static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
}
static int option_parse_x(const struct option *opt,
const char *arg, int unset)
{
struct replay_opts **opts_ptr = opt->value;
struct replay_opts *opts = *opts_ptr;
if (unset)
return 0;
ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
opts->xopts[opts->xopts_nr++] = xstrdup(arg);
return 0;
}
static int option_parse_m(const struct option *opt,
const char *arg, int unset)
{
struct replay_opts *replay = opt->value;
char *end;
if (unset) {
replay->mainline = 0;
return 0;
}
replay->mainline = strtol(arg, &end, 10);
if (*end || replay->mainline <= 0)
return error(_("option `%s' expects a number greater than zero"),
opt->long_name);
return 0;
}
LAST_ARG_MUST_BE_NULL
static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
const char *this_opt;
va_list ap;
va_start(ap, base_opt);
while ((this_opt = va_arg(ap, const char *))) {
if (va_arg(ap, int))
break;
}
va_end(ap);
if (this_opt)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
const char *cleanup_arg = NULL;
int cmd = 0;
struct option base_options[] = {
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'),
OPT_CLEANUP(&cleanup_arg),
OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")),
OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")),
OPT_NOOP_NOARG('r', NULL),
OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")),
OPT_CALLBACK('m', "mainline", opts, N_("parent-number"),
N_("select mainline parent"), option_parse_m),
OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
OPT_STRING(0, "strategy", &opts->strategy, N_("strategy"), N_("merge strategy")),
OPT_CALLBACK('X', "strategy-option", &opts, N_("option"),
N_("option for merge strategy"), option_parse_x),
{ OPTION_STRING, 'S', "gpg-sign", &opts->gpg_sign, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
OPT_END()
};
struct option *options = base_options;
if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
OPT_BOOL('x', NULL, &opts->record_origin, N_("append commit name")),
OPT_BOOL(0, "ff", &opts->allow_ff, N_("allow fast-forward")),
OPT_BOOL(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")),
OPT_BOOL(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")),
OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")),
OPT_END(),
};
options = parse_options_concat(options, cp_extra);
}
argc = parse_options(argc, argv, NULL, options, usage_str,
PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN);
/* implies allow_empty */
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
if (cleanup_arg) {
opts->default_msg_cleanup = get_cleanup_mode(cleanup_arg, 1);
opts->explicit_cleanup = 1;
}
/* Check for incompatible command line arguments */
if (cmd) {
char *this_operation;
if (cmd == 'q')
this_operation = "--quit";
else if (cmd == 'c')
this_operation = "--continue";
else if (cmd == 's')
this_operation = "--skip";
else {
assert(cmd == 'a');
this_operation = "--abort";
}
verify_opt_compatible(me, this_operation,
"--no-commit", opts->no_commit,
"--signoff", opts->signoff,
"--mainline", opts->mainline,
"--strategy", opts->strategy ? 1 : 0,
"--strategy-option", opts->xopts ? 1 : 0,
"-x", opts->record_origin,
"--ff", opts->allow_ff,
"--rerere-autoupdate", opts->allow_rerere_auto == RERERE_AUTOUPDATE,
"--no-rerere-autoupdate", opts->allow_rerere_auto == RERERE_NOAUTOUPDATE,
NULL);
}
if (opts->allow_ff)
verify_opt_compatible(me, "--ff",
"--signoff", opts->signoff,
"--no-commit", opts->no_commit,
"-x", opts->record_origin,
"--edit", opts->edit,
NULL);
if (cmd) {
opts->revs = NULL;
} else {
struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs));
repo_init_revisions(the_repository, opts->revs, NULL);
opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
if (argc < 2)
usage_with_options(usage_str, options);
if (!strcmp(argv[1], "-"))
argv[1] = "@{-1}";
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.assume_dashdash = 1;
argc = setup_revisions(argc, argv, opts->revs, &s_r_opt);
}
if (argc > 1)
usage_with_options(usage_str, options);
/* These option values will be free()d */
opts->gpg_sign = xstrdup_or_null(opts->gpg_sign);
opts->strategy = xstrdup_or_null(opts->strategy);
if (cmd == 'q') {
int ret = sequencer_remove_state(opts);
if (!ret)
remove_branch_state(the_repository, 0);
return ret;
}
if (cmd == 'c')
return sequencer_continue(the_repository, opts);
if (cmd == 'a')
return sequencer_rollback(the_repository, opts);
if (cmd == 's')
return sequencer_skip(the_repository, opts);
return sequencer_pick_revisions(the_repository, opts);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
if (isatty(0))
opts.edit = 1;
opts.action = REPLAY_REVERT;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("revert failed"));
return res;
}
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
opts.action = REPLAY_PICK;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;
}

393
third_party/git/builtin/rm.c vendored Normal file
View file

@ -0,0 +1,393 @@
/*
* "git rm" builtin command
*
* Copyright (C) Linus Torvalds 2006
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "tree-walk.h"
#include "parse-options.h"
#include "string-list.h"
#include "submodule.h"
#include "pathspec.h"
static const char * const builtin_rm_usage[] = {
N_("git rm [<options>] [--] <file>..."),
NULL
};
static struct {
int nr, alloc;
struct {
const char *name;
char is_submodule;
} *entry;
} list;
static int get_ours_cache_pos(const char *path, int pos)
{
int i = -pos - 1;
while ((i < active_nr) && !strcmp(active_cache[i]->name, path)) {
if (ce_stage(active_cache[i]) == 2)
return i;
i++;
}
return -1;
}
static void print_error_files(struct string_list *files_list,
const char *main_msg,
const char *hints_msg,
int *errs)
{
if (files_list->nr) {
int i;
struct strbuf err_msg = STRBUF_INIT;
strbuf_addstr(&err_msg, main_msg);
for (i = 0; i < files_list->nr; i++)
strbuf_addf(&err_msg,
"\n %s",
files_list->items[i].string);
if (advice_rm_hints)
strbuf_addstr(&err_msg, hints_msg);
*errs = error("%s", err_msg.buf);
strbuf_release(&err_msg);
}
}
static void submodules_absorb_gitdir_if_needed(void)
{
int i;
for (i = 0; i < list.nr; i++) {
const char *name = list.entry[i].name;
int pos;
const struct cache_entry *ce;
pos = cache_name_pos(name, strlen(name));
if (pos < 0) {
pos = get_ours_cache_pos(name, pos);
if (pos < 0)
continue;
}
ce = active_cache[pos];
if (!S_ISGITLINK(ce->ce_mode) ||
!file_exists(ce->name) ||
is_empty_dir(name))
continue;
if (!submodule_uses_gitfile(name))
absorb_git_dir_into_superproject(name,
ABSORB_GITDIR_RECURSE_SUBMODULES);
}
}
static int check_local_mod(struct object_id *head, int index_only)
{
/*
* Items in list are already sorted in the cache order,
* so we could do this a lot more efficiently by using
* tree_desc based traversal if we wanted to, but I am
* lazy, and who cares if removal of files is a tad
* slower than the theoretical maximum speed?
*/
int i, no_head;
int errs = 0;
struct string_list files_staged = STRING_LIST_INIT_NODUP;
struct string_list files_cached = STRING_LIST_INIT_NODUP;
struct string_list files_local = STRING_LIST_INIT_NODUP;
no_head = is_null_oid(head);
for (i = 0; i < list.nr; i++) {
struct stat st;
int pos;
const struct cache_entry *ce;
const char *name = list.entry[i].name;
struct object_id oid;
unsigned short mode;
int local_changes = 0;
int staged_changes = 0;
pos = cache_name_pos(name, strlen(name));
if (pos < 0) {
/*
* Skip unmerged entries except for populated submodules
* that could lose history when removed.
*/
pos = get_ours_cache_pos(name, pos);
if (pos < 0)
continue;
if (!S_ISGITLINK(active_cache[pos]->ce_mode) ||
is_empty_dir(name))
continue;
}
ce = active_cache[pos];
if (lstat(ce->name, &st) < 0) {
if (!is_missing_file_error(errno))
warning_errno(_("failed to stat '%s'"), ce->name);
/* It already vanished from the working tree */
continue;
}
else if (S_ISDIR(st.st_mode)) {
/* if a file was removed and it is now a
* directory, that is the same as ENOENT as
* far as git is concerned; we do not track
* directories unless they are submodules.
*/
if (!S_ISGITLINK(ce->ce_mode))
continue;
}
/*
* "rm" of a path that has changes need to be treated
* carefully not to allow losing local changes
* accidentally. A local change could be (1) file in
* work tree is different since the index; and/or (2)
* the user staged a content that is different from
* the current commit in the index.
*
* In such a case, you would need to --force the
* removal. However, "rm --cached" (remove only from
* the index) is safe if the index matches the file in
* the work tree or the HEAD commit, as it means that
* the content being removed is available elsewhere.
*/
/*
* Is the index different from the file in the work tree?
* If it's a submodule, is its work tree modified?
*/
if (ce_match_stat(ce, &st, 0) ||
(S_ISGITLINK(ce->ce_mode) &&
bad_to_remove_submodule(ce->name,
SUBMODULE_REMOVAL_DIE_ON_ERROR |
SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)))
local_changes = 1;
/*
* Is the index different from the HEAD commit? By
* definition, before the very initial commit,
* anything staged in the index is treated by the same
* way as changed from the HEAD.
*/
if (no_head
|| get_tree_entry(the_repository, head, name, &oid, &mode)
|| ce->ce_mode != create_ce_mode(mode)
|| !oideq(&ce->oid, &oid))
staged_changes = 1;
/*
* If the index does not match the file in the work
* tree and if it does not match the HEAD commit
* either, (1) "git rm" without --cached definitely
* will lose information; (2) "git rm --cached" will
* lose information unless it is about removing an
* "intent to add" entry.
*/
if (local_changes && staged_changes) {
if (!index_only || !ce_intent_to_add(ce))
string_list_append(&files_staged, name);
}
else if (!index_only) {
if (staged_changes)
string_list_append(&files_cached, name);
if (local_changes)
string_list_append(&files_local, name);
}
}
print_error_files(&files_staged,
Q_("the following file has staged content different "
"from both the\nfile and the HEAD:",
"the following files have staged content different"
" from both the\nfile and the HEAD:",
files_staged.nr),
_("\n(use -f to force removal)"),
&errs);
string_list_clear(&files_staged, 0);
print_error_files(&files_cached,
Q_("the following file has changes "
"staged in the index:",
"the following files have changes "
"staged in the index:", files_cached.nr),
_("\n(use --cached to keep the file,"
" or -f to force removal)"),
&errs);
string_list_clear(&files_cached, 0);
print_error_files(&files_local,
Q_("the following file has local modifications:",
"the following files have local modifications:",
files_local.nr),
_("\n(use --cached to keep the file,"
" or -f to force removal)"),
&errs);
string_list_clear(&files_local, 0);
return errs;
}
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
static int ignore_unmatch = 0;
static struct option builtin_rm_options[] = {
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__QUIET(&quiet, N_("do not list removed files")),
OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")),
OPT__FORCE(&force, N_("override the up-to-date check"), PARSE_OPT_NOCOMPLETE),
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
N_("exit with a zero status even if nothing matched")),
OPT_END(),
};
int cmd_rm(int argc, const char **argv, const char *prefix)
{
struct lock_file lock_file = LOCK_INIT;
int i;
struct pathspec pathspec;
char *seen;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_rm_options,
builtin_rm_usage, 0);
if (!argc)
usage_with_options(builtin_rm_usage, builtin_rm_options);
if (!index_only)
setup_work_tree();
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
if (read_cache() < 0)
die(_("index file corrupt"));
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,
prefix, argv);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL);
seen = xcalloc(pathspec.nr, 1);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!ce_path_match(&the_index, ce, &pathspec, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
list.entry[list.nr].name = xstrdup(ce->name);
list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
if (list.entry[list.nr++].is_submodule &&
!is_staging_gitmodules_ok(&the_index))
die(_("please stage your changes to .gitmodules or stash them to proceed"));
}
if (pathspec.nr) {
const char *original;
int seen_any = 0;
for (i = 0; i < pathspec.nr; i++) {
original = pathspec.items[i].original;
if (!seen[i]) {
if (!ignore_unmatch) {
die(_("pathspec '%s' did not match any files"),
original);
}
}
else {
seen_any = 1;
}
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
die(_("not removing '%s' recursively without -r"),
*original ? original : ".");
}
if (!seen_any)
exit(0);
}
if (!index_only)
submodules_absorb_gitdir_if_needed();
/*
* If not forced, the file, the index and the HEAD (if exists)
* must match; but the file can already been removed, since
* this sequence is a natural "novice" way:
*
* rm F; git rm F
*
* Further, if HEAD commit exists, "diff-index --cached" must
* report no changes unless forced.
*/
if (!force) {
struct object_id oid;
if (get_oid("HEAD", &oid))
oidclr(&oid);
if (check_local_mod(&oid, index_only))
exit(1);
}
/*
* First remove the names from the index: we won't commit
* the index unless all of them succeed.
*/
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (!quiet)
printf("rm '%s'\n", path);
if (remove_file_from_cache(path))
die(_("git rm: unable to remove %s"), path);
}
if (show_only)
return 0;
/*
* Then, unless we used "--cached", remove the filenames from
* the workspace. If we fail to remove the first one, we
* abort the "git rm" (but once we've successfully removed
* any file at all, we'll go ahead and commit to it all:
* by then we've already committed ourselves and can't fail
* in the middle)
*/
if (!index_only) {
int removed = 0, gitmodules_modified = 0;
struct strbuf buf = STRBUF_INIT;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
strbuf_reset(&buf);
strbuf_addstr(&buf, path);
if (remove_dir_recursively(&buf, 0))
die(_("could not remove '%s'"), path);
removed = 1;
if (!remove_path_from_gitmodules(path))
gitmodules_modified = 1;
continue;
}
if (!remove_path(path)) {
removed = 1;
continue;
}
if (!removed)
die_errno("git rm: '%s'", path);
}
strbuf_release(&buf);
if (gitmodules_modified)
stage_updated_gitmodules(&the_index);
}
if (write_locked_index(&the_index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
return 0;
}

311
third_party/git/builtin/send-pack.c vendored Normal file
View file

@ -0,0 +1,311 @@
#include "builtin.h"
#include "config.h"
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
#include "sideband.h"
#include "run-command.h"
#include "remote.h"
#include "connect.h"
#include "send-pack.h"
#include "quote.h"
#include "transport.h"
#include "version.h"
#include "sha1-array.h"
#include "gpg-interface.h"
#include "gettext.h"
#include "protocol.h"
static const char * const send_pack_usage[] = {
N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
"[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
"[<host>:]<directory> [<ref>...]\n"
" --all and explicit <ref> specification are mutually exclusive."),
NULL,
};
static struct send_pack_args args;
static void print_helper_status(struct ref *ref)
{
struct strbuf buf = STRBUF_INIT;
for (; ref; ref = ref->next) {
const char *msg = NULL;
const char *res;
switch(ref->status) {
case REF_STATUS_NONE:
res = "error";
msg = "no match";
break;
case REF_STATUS_OK:
res = "ok";
break;
case REF_STATUS_UPTODATE:
res = "ok";
msg = "up to date";
break;
case REF_STATUS_REJECT_NONFASTFORWARD:
res = "error";
msg = "non-fast forward";
break;
case REF_STATUS_REJECT_FETCH_FIRST:
res = "error";
msg = "fetch first";
break;
case REF_STATUS_REJECT_NEEDS_FORCE:
res = "error";
msg = "needs force";
break;
case REF_STATUS_REJECT_STALE:
res = "error";
msg = "stale info";
break;
case REF_STATUS_REJECT_ALREADY_EXISTS:
res = "error";
msg = "already exists";
break;
case REF_STATUS_REJECT_NODELETE:
case REF_STATUS_REMOTE_REJECT:
res = "error";
break;
case REF_STATUS_EXPECTING_REPORT:
default:
continue;
}
strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s", res, ref->name);
if (ref->remote_status)
msg = ref->remote_status;
if (msg) {
strbuf_addch(&buf, ' ');
quote_two_c_style(&buf, "", msg, 0);
}
strbuf_addch(&buf, '\n');
write_or_die(1, buf.buf, buf.len);
}
strbuf_release(&buf);
}
static int send_pack_config(const char *k, const char *v, void *cb)
{
git_gpg_config(k, v, NULL);
if (!strcmp(k, "push.gpgsign")) {
const char *value;
if (!git_config_get_value("push.gpgsign", &value)) {
switch (git_parse_maybe_bool(value)) {
case 0:
args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
break;
case 1:
args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
break;
default:
if (value && !strcasecmp(value, "if-asked"))
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
else
return error("Invalid value for '%s'", k);
}
}
}
return git_default_config(k, v, cb);
}
int cmd_send_pack(int argc, const char **argv, const char *prefix)
{
struct refspec rs = REFSPEC_INIT_PUSH;
const char *remote_name = NULL;
struct remote *remote = NULL;
const char *dest = NULL;
int fd[2];
struct child_process *conn;
struct oid_array extra_have = OID_ARRAY_INIT;
struct oid_array shallow = OID_ARRAY_INIT;
struct ref *remote_refs, *local_refs;
int ret;
int helper_status = 0;
int send_all = 0;
int verbose = 0;
const char *receivepack = "git-receive-pack";
unsigned dry_run = 0;
unsigned send_mirror = 0;
unsigned force_update = 0;
unsigned quiet = 0;
int push_cert = 0;
struct string_list push_options = STRING_LIST_INIT_NODUP;
unsigned use_thin_pack = 0;
unsigned atomic = 0;
unsigned stateless_rpc = 0;
int flags;
unsigned int reject_reasons;
int progress = -1;
int from_stdin = 0;
struct push_cas_option cas = {0};
struct packet_reader reader;
struct option options[] = {
OPT__VERBOSITY(&verbose),
OPT_STRING(0, "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING(0, "exec", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING(0, "remote", &remote_name, "remote", N_("remote name")),
OPT_BOOL(0, "all", &send_all, N_("push all refs")),
OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")),
OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")),
OPT_BOOL('f', "force", &force_update, N_("force updates")),
{ OPTION_CALLBACK,
0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_STRING_LIST(0, "push-option", &push_options,
N_("server-specific"),
N_("option to transmit")),
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")),
OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")),
OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")),
OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")),
{ OPTION_CALLBACK,
0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
N_("require old value of ref to be at this value"),
PARSE_OPT_OPTARG, parseopt_push_cas_option },
OPT_END()
};
git_config(send_pack_config, NULL);
argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0);
if (argc > 0) {
dest = argv[0];
refspec_appendn(&rs, argv + 1, argc - 1);
}
if (!dest)
usage_with_options(send_pack_usage, options);
args.verbose = verbose;
args.dry_run = dry_run;
args.send_mirror = send_mirror;
args.force_update = force_update;
args.quiet = quiet;
args.push_cert = push_cert;
args.progress = progress;
args.use_thin_pack = use_thin_pack;
args.atomic = atomic;
args.stateless_rpc = stateless_rpc;
args.push_options = push_options.nr ? &push_options : NULL;
if (from_stdin) {
if (args.stateless_rpc) {
const char *buf;
while ((buf = packet_read_line(0, NULL)))
refspec_append(&rs, buf);
} else {
struct strbuf line = STRBUF_INIT;
while (strbuf_getline(&line, stdin) != EOF)
refspec_append(&rs, line.buf);
strbuf_release(&line);
}
}
/*
* --all and --mirror are incompatible; neither makes sense
* with any refspecs.
*/
if ((rs.nr > 0 && (send_all || args.send_mirror)) ||
(send_all && args.send_mirror))
usage_with_options(send_pack_usage, options);
if (remote_name) {
remote = remote_get(remote_name);
if (!remote_has_url(remote, dest)) {
die("Destination %s is not a uri for %s",
dest, remote_name);
}
}
if (progress == -1)
progress = !args.quiet && isatty(2);
args.progress = progress;
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
fd[1] = 1;
} else {
conn = git_connect(fd, dest, receivepack,
args.verbose ? CONNECT_VERBOSE : 0);
}
packet_reader_init(&reader, fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_DIE_ON_ERR_PACKET);
switch (discover_version(&reader)) {
case protocol_v2:
die("support for protocol v2 not implemented yet");
break;
case protocol_v1:
case protocol_v0:
get_remote_heads(&reader, &remote_refs, REF_NORMAL,
&extra_have, &shallow);
break;
case protocol_unknown_version:
BUG("unknown protocol version");
}
local_refs = get_local_heads();
flags = MATCH_REFS_NONE;
if (send_all)
flags |= MATCH_REFS_ALL;
if (args.send_mirror)
flags |= MATCH_REFS_MIRROR;
/* match them up */
if (match_push_refs(local_refs, &remote_refs, &rs, flags))
return -1;
if (!is_empty_cas(&cas))
apply_push_cas(&cas, remote, remote_refs);
set_ref_status_for_push(remote_refs, args.send_mirror,
args.force_update);
ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
if (helper_status)
print_helper_status(remote_refs);
close(fd[1]);
close(fd[0]);
ret |= finish_connect(conn);
if (!helper_status)
transport_print_push_status(dest, remote_refs, args.verbose, 0, &reject_reasons);
if (!args.dry_run && remote) {
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next)
transport_update_tracking_ref(remote, ref, args.verbose);
}
if (!ret && !transport_refs_pushed(remote_refs))
fprintf(stderr, "Everything up-to-date\n");
return ret;
}

379
third_party/git/builtin/shortlog.c vendored Normal file
View file

@ -0,0 +1,379 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "diff.h"
#include "string-list.h"
#include "revision.h"
#include "utf8.h"
#include "mailmap.h"
#include "shortlog.h"
#include "parse-options.h"
static char const * const shortlog_usage[] = {
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
N_("git log --pretty=short | git shortlog [<options>]"),
NULL
};
/*
* The util field of our string_list_items will contain one of two things:
*
* - if --summary is not in use, it will point to a string list of the
* oneline subjects assigned to this author
*
* - if --summary is in use, we don't need that list; we only need to know
* its size. So we abuse the pointer slot to store our integer counter.
*
* This macro accesses the latter.
*/
#define UTIL_TO_INT(x) ((intptr_t)(x)->util)
static int compare_by_counter(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1, *i2 = a2;
return UTIL_TO_INT(i2) - UTIL_TO_INT(i1);
}
static int compare_by_list(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1, *i2 = a2;
const struct string_list *l1 = i1->util, *l2 = i2->util;
if (l1->nr < l2->nr)
return 1;
else if (l1->nr == l2->nr)
return 0;
else
return -1;
}
static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
{
struct string_list_item *item;
item = string_list_insert(&log->list, author);
if (log->summary)
item->util = (void *)(UTIL_TO_INT(item) + 1);
else {
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct strbuf subject = STRBUF_INIT;
const char *eol;
/* Skip any leading whitespace, including any blank lines. */
while (*oneline && isspace(*oneline))
oneline++;
eol = strchr(oneline, '\n');
if (!eol)
eol = oneline + strlen(oneline);
if (starts_with(oneline, "[PATCH")) {
char *eob = strchr(oneline, ']');
if (eob && (!eol || eob < eol))
oneline = eob + 1;
}
while (*oneline && isspace(*oneline) && *oneline != '\n')
oneline++;
format_subject(&subject, oneline, " ");
buffer = strbuf_detach(&subject, NULL);
if (dot3) {
int dot3len = strlen(dot3);
if (dot3len > 5) {
while ((p = strstr(buffer, dot3)) != NULL) {
int taillen = strlen(p) - dot3len;
memcpy(p, "/.../", 5);
memmove(p + 5, p + dot3len, taillen + 1);
}
}
}
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
string_list_append(item->util, buffer);
}
}
static int parse_stdin_author(struct shortlog *log,
struct strbuf *out, const char *in)
{
const char *mailbuf, *namebuf;
size_t namelen, maillen;
struct ident_split ident;
if (split_ident_line(&ident, in, strlen(in)))
return -1;
namebuf = ident.name_begin;
mailbuf = ident.mail_begin;
namelen = ident.name_end - ident.name_begin;
maillen = ident.mail_end - ident.mail_begin;
map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
strbuf_add(out, namebuf, namelen);
if (log->email)
strbuf_addf(out, " <%.*s>", (int)maillen, mailbuf);
return 0;
}
static void read_from_stdin(struct shortlog *log)
{
struct strbuf author = STRBUF_INIT;
struct strbuf mapped_author = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
static const char *author_match[2] = { "Author: ", "author " };
static const char *committer_match[2] = { "Commit: ", "committer " };
const char **match;
match = log->committer ? committer_match : author_match;
while (strbuf_getline_lf(&author, stdin) != EOF) {
const char *v;
if (!skip_prefix(author.buf, match[0], &v) &&
!skip_prefix(author.buf, match[1], &v))
continue;
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
oneline.len)
; /* discard headers */
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
!oneline.len)
; /* discard blanks */
strbuf_reset(&mapped_author);
if (parse_stdin_author(log, &mapped_author, v) < 0)
continue;
insert_one_record(log, mapped_author.buf, oneline.buf);
}
strbuf_release(&author);
strbuf_release(&mapped_author);
strbuf_release(&oneline);
}
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
struct strbuf author = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
struct pretty_print_context ctx = {0};
const char *fmt;
ctx.fmt = CMIT_FMT_USERFORMAT;
ctx.abbrev = log->abbrev;
ctx.print_email_subject = 1;
ctx.date_mode.type = DATE_NORMAL;
ctx.output_encoding = get_log_output_encoding();
fmt = log->committer ?
(log->email ? "%cN <%cE>" : "%cN") :
(log->email ? "%aN <%aE>" : "%aN");
format_commit_message(commit, fmt, &author, &ctx);
if (!log->summary) {
if (log->user_format)
pretty_print_commit(&ctx, commit, &oneline);
else
format_commit_message(commit, "%s", &oneline, &ctx);
}
insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
strbuf_release(&author);
strbuf_release(&oneline);
}
static void get_from_rev(struct rev_info *rev, struct shortlog *log)
{
struct commit *commit;
if (prepare_revision_walk(rev))
die(_("revision walk setup failed"));
while ((commit = get_revision(rev)) != NULL)
shortlog_add_commit(log, commit);
}
static int parse_uint(char const **arg, int comma, int defval)
{
unsigned long ul;
int ret;
char *endp;
ul = strtoul(*arg, &endp, 10);
if (*endp && *endp != comma)
return -1;
if (ul > INT_MAX)
return -1;
ret = *arg == endp ? defval : (int)ul;
*arg = *endp ? endp + 1 : endp;
return ret;
}
static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
#define DEFAULT_WRAPLEN 76
#define DEFAULT_INDENT1 6
#define DEFAULT_INDENT2 9
static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
{
struct shortlog *log = opt->value;
log->wrap_lines = !unset;
if (unset)
return 0;
if (!arg) {
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
return 0;
}
log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
return error(wrap_arg_usage);
if (log->wrap &&
((log->in1 && log->wrap <= log->in1) ||
(log->in2 && log->wrap <= log->in2)))
return error(wrap_arg_usage);
return 0;
}
void shortlog_init(struct shortlog *log)
{
memset(log, 0, sizeof(*log));
read_mailmap(&log->mailmap, &log->common_repo_prefix);
log->list.strdup_strings = 1;
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
}
int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
struct shortlog log = { STRING_LIST_INIT_NODUP };
struct rev_info rev;
int nongit = !startup_info->have_repository;
const struct option options[] = {
OPT_BOOL('c', "committer", &log.committer,
N_("Group by committer rather than author")),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
N_("Suppress commit descriptions, only provides commit count")),
OPT_BOOL('e', "email", &log.email,
N_("Show the email address of each author")),
{ OPTION_CALLBACK, 'w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
N_("Linewrap output"), PARSE_OPT_OPTARG,
&parse_wrap_args },
OPT_END(),
};
struct parse_opt_ctx_t ctx;
git_config(git_default_config, NULL);
shortlog_init(&log);
repo_init_revisions(the_repository, &rev, prefix);
parse_options_start(&ctx, argc, argv, prefix, options,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, shortlog_usage)) {
case PARSE_OPT_HELP:
case PARSE_OPT_ERROR:
exit(129);
case PARSE_OPT_COMPLETE:
exit(0);
case PARSE_OPT_DONE:
goto parse_done;
}
parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
parse_done:
argc = parse_options_end(&ctx);
if (nongit && argc > 1) {
error(_("too many arguments given outside repository"));
usage_with_options(shortlog_usage, options);
}
if (setup_revisions(argc, argv, &rev, NULL) != 1) {
error(_("unrecognized argument: %s"), argv[1]);
usage_with_options(shortlog_usage, options);
}
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
log.abbrev = rev.abbrev;
log.file = rev.diffopt.file;
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))
add_head_to_pending(&rev);
if (rev.pending.nr == 0) {
if (isatty(0))
fprintf(stderr, _("(reading log message from standard input)\n"));
read_from_stdin(&log);
}
else
get_from_rev(&rev, &log);
shortlog_output(&log);
if (log.file != stdout)
fclose(log.file);
return 0;
}
static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
const struct shortlog *log)
{
strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
strbuf_addch(sb, '\n');
}
void shortlog_output(struct shortlog *log)
{
int i, j;
struct strbuf sb = STRBUF_INIT;
if (log->sort_by_number)
QSORT(log->list.items, log->list.nr,
log->summary ? compare_by_counter : compare_by_list);
for (i = 0; i < log->list.nr; i++) {
const struct string_list_item *item = &log->list.items[i];
if (log->summary) {
fprintf(log->file, "%6d\t%s\n",
(int)UTIL_TO_INT(item), item->string);
} else {
struct string_list *onelines = item->util;
fprintf(log->file, "%s (%d):\n",
item->string, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
const char *msg = onelines->items[j].string;
if (log->wrap_lines) {
strbuf_reset(&sb);
add_wrapped_shortlog_msg(&sb, msg, log);
fwrite(sb.buf, sb.len, 1, log->file);
}
else
fprintf(log->file, " %s\n", msg);
}
putc('\n', log->file);
onelines->strdup_strings = 1;
string_list_clear(onelines, 0);
free(onelines);
}
log->list.items[i].util = NULL;
}
strbuf_release(&sb);
log->list.strdup_strings = 1;
string_list_clear(&log->list, 1);
clear_mailmap(&log->mailmap);
}

954
third_party/git/builtin/show-branch.c vendored Normal file
View file

@ -0,0 +1,954 @@
#include "cache.h"
#include "config.h"
#include "pretty.h"
#include "refs.h"
#include "builtin.h"
#include "color.h"
#include "argv-array.h"
#include "parse-options.h"
#include "dir.h"
#include "commit-slab.h"
static const char* show_branch_usage[] = {
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
" [--current] [--color[=<when>] | --no-color] [--sparse]\n"
" [--more=<n> | --list | --independent | --merge-base]\n"
" [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"),
NULL
};
static int showbranch_use_color = -1;
static struct argv_array default_args = ARGV_ARRAY_INIT;
/*
* TODO: convert this use of commit->object.flags to commit-slab
* instead to store a pointer to ref name directly. Then use the same
* UNINTERESTING definition from revision.h here.
*/
#define UNINTERESTING 01
#define REV_SHIFT 2
#define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
#define DEFAULT_REFLOG 4
static const char *get_color_code(int idx)
{
if (want_color(showbranch_use_color))
return column_colors_ansi[idx % column_colors_ansi_max];
return "";
}
static const char *get_color_reset_code(void)
{
if (want_color(showbranch_use_color))
return GIT_COLOR_RESET;
return "";
}
static struct commit *interesting(struct commit_list *list)
{
while (list) {
struct commit *commit = list->item;
list = list->next;
if (commit->object.flags & UNINTERESTING)
continue;
return commit;
}
return NULL;
}
struct commit_name {
const char *head_name; /* which head's ancestor? */
int generation; /* how many parents away from head_name */
};
define_commit_slab(commit_name_slab, struct commit_name *);
static struct commit_name_slab name_slab;
static struct commit_name *commit_to_name(struct commit *commit)
{
return *commit_name_slab_at(&name_slab, commit);
}
/* Name the commit as nth generation ancestor of head_name;
* we count only the first-parent relationship for naming purposes.
*/
static void name_commit(struct commit *commit, const char *head_name, int nth)
{
struct commit_name *name;
name = *commit_name_slab_at(&name_slab, commit);
if (!name) {
name = xmalloc(sizeof(*name));
*commit_name_slab_at(&name_slab, commit) = name;
}
name->head_name = head_name;
name->generation = nth;
}
/* Parent is the first parent of the commit. We may name it
* as (n+1)th generation ancestor of the same head_name as
* commit is nth generation ancestor of, if that generation
* number is better than the name it already has.
*/
static void name_parent(struct commit *commit, struct commit *parent)
{
struct commit_name *commit_name = commit_to_name(commit);
struct commit_name *parent_name = commit_to_name(parent);
if (!commit_name)
return;
if (!parent_name ||
commit_name->generation + 1 < parent_name->generation)
name_commit(parent, commit_name->head_name,
commit_name->generation + 1);
}
static int name_first_parent_chain(struct commit *c)
{
int i = 0;
while (c) {
struct commit *p;
if (!commit_to_name(c))
break;
if (!c->parents)
break;
p = c->parents->item;
if (!commit_to_name(p)) {
name_parent(c, p);
i++;
}
else
break;
c = p;
}
return i;
}
static void name_commits(struct commit_list *list,
struct commit **rev,
char **ref_name,
int num_rev)
{
struct commit_list *cl;
struct commit *c;
int i;
/* First give names to the given heads */
for (cl = list; cl; cl = cl->next) {
c = cl->item;
if (commit_to_name(c))
continue;
for (i = 0; i < num_rev; i++) {
if (rev[i] == c) {
name_commit(c, ref_name[i], 0);
break;
}
}
}
/* Then commits on the first parent ancestry chain */
do {
i = 0;
for (cl = list; cl; cl = cl->next) {
i += name_first_parent_chain(cl->item);
}
} while (i);
/* Finally, any unnamed commits */
do {
i = 0;
for (cl = list; cl; cl = cl->next) {
struct commit_list *parents;
struct commit_name *n;
int nth;
c = cl->item;
if (!commit_to_name(c))
continue;
n = commit_to_name(c);
parents = c->parents;
nth = 0;
while (parents) {
struct commit *p = parents->item;
struct strbuf newname = STRBUF_INIT;
parents = parents->next;
nth++;
if (commit_to_name(p))
continue;
switch (n->generation) {
case 0:
strbuf_addstr(&newname, n->head_name);
break;
case 1:
strbuf_addf(&newname, "%s^", n->head_name);
break;
default:
strbuf_addf(&newname, "%s~%d",
n->head_name, n->generation);
break;
}
if (nth == 1)
strbuf_addch(&newname, '^');
else
strbuf_addf(&newname, "^%d", nth);
name_commit(p, strbuf_detach(&newname, NULL), 0);
i++;
name_first_parent_chain(p);
}
}
} while (i);
}
static int mark_seen(struct commit *commit, struct commit_list **seen_p)
{
if (!commit->object.flags) {
commit_list_insert(commit, seen_p);
return 1;
}
return 0;
}
static void join_revs(struct commit_list **list_p,
struct commit_list **seen_p,
int num_rev, int extra)
{
int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
while (*list_p) {
struct commit_list *parents;
int still_interesting = !!interesting(*list_p);
struct commit *commit = pop_commit(list_p);
int flags = commit->object.flags & all_mask;
if (!still_interesting && extra <= 0)
break;
mark_seen(commit, seen_p);
if ((flags & all_revs) == all_revs)
flags |= UNINTERESTING;
parents = commit->parents;
while (parents) {
struct commit *p = parents->item;
int this_flag = p->object.flags;
parents = parents->next;
if ((this_flag & flags) == flags)
continue;
parse_commit(p);
if (mark_seen(p, seen_p) && !still_interesting)
extra--;
p->object.flags |= flags;
commit_list_insert_by_date(p, list_p);
}
}
/*
* Postprocess to complete well-poisoning.
*
* At this point we have all the commits we have seen in
* seen_p list. Mark anything that can be reached from
* uninteresting commits not interesting.
*/
for (;;) {
int changed = 0;
struct commit_list *s;
for (s = *seen_p; s; s = s->next) {
struct commit *c = s->item;
struct commit_list *parents;
if (((c->object.flags & all_revs) != all_revs) &&
!(c->object.flags & UNINTERESTING))
continue;
/* The current commit is either a merge base or
* already uninteresting one. Mark its parents
* as uninteresting commits _only_ if they are
* already parsed. No reason to find new ones
* here.
*/
parents = c->parents;
while (parents) {
struct commit *p = parents->item;
parents = parents->next;
if (!(p->object.flags & UNINTERESTING)) {
p->object.flags |= UNINTERESTING;
changed = 1;
}
}
}
if (!changed)
break;
}
}
static void show_one_commit(struct commit *commit, int no_name)
{
struct strbuf pretty = STRBUF_INIT;
const char *pretty_str = "(unavailable)";
struct commit_name *name = commit_to_name(commit);
if (commit->object.parsed) {
pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
pretty_str = pretty.buf;
}
skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
if (!no_name) {
if (name && name->head_name) {
printf("[%s", name->head_name);
if (name->generation) {
if (name->generation == 1)
printf("^");
else
printf("~%d", name->generation);
}
printf("] ");
}
else
printf("[%s] ",
find_unique_abbrev(&commit->object.oid,
DEFAULT_ABBREV));
}
puts(pretty_str);
strbuf_release(&pretty);
}
static char *ref_name[MAX_REVS + 1];
static int ref_name_cnt;
static const char *find_digit_prefix(const char *s, int *v)
{
const char *p;
int ver;
char ch;
for (p = s, ver = 0;
'0' <= (ch = *p) && ch <= '9';
p++)
ver = ver * 10 + ch - '0';
*v = ver;
return p;
}
static int version_cmp(const char *a, const char *b)
{
while (1) {
int va, vb;
a = find_digit_prefix(a, &va);
b = find_digit_prefix(b, &vb);
if (va != vb)
return va - vb;
while (1) {
int ca = *a;
int cb = *b;
if ('0' <= ca && ca <= '9')
ca = 0;
if ('0' <= cb && cb <= '9')
cb = 0;
if (ca != cb)
return ca - cb;
if (!ca)
break;
a++;
b++;
}
if (!*a && !*b)
return 0;
}
}
static int compare_ref_name(const void *a_, const void *b_)
{
const char * const*a = a_, * const*b = b_;
return version_cmp(*a, *b);
}
static void sort_ref_range(int bottom, int top)
{
QSORT(ref_name + bottom, top - bottom, compare_ref_name);
}
static int append_ref(const char *refname, const struct object_id *oid,
int allow_dups)
{
struct commit *commit = lookup_commit_reference_gently(the_repository,
oid, 1);
int i;
if (!commit)
return 0;
if (!allow_dups) {
/* Avoid adding the same thing twice */
for (i = 0; i < ref_name_cnt; i++)
if (!strcmp(refname, ref_name[i]))
return 0;
}
if (MAX_REVS <= ref_name_cnt) {
warning(Q_("ignoring %s; cannot handle more than %d ref",
"ignoring %s; cannot handle more than %d refs",
MAX_REVS), refname, MAX_REVS);
return 0;
}
ref_name[ref_name_cnt++] = xstrdup(refname);
ref_name[ref_name_cnt] = NULL;
return 0;
}
static int append_head_ref(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct object_id tmp;
int ofs = 11;
if (!starts_with(refname, "refs/heads/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid))
ofs = 5;
return append_ref(refname + ofs, oid, 0);
}
static int append_remote_ref(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct object_id tmp;
int ofs = 13;
if (!starts_with(refname, "refs/remotes/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid))
ofs = 5;
return append_ref(refname + ofs, oid, 0);
}
static int append_tag_ref(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
if (!starts_with(refname, "refs/tags/"))
return 0;
return append_ref(refname + 5, oid, 0);
}
static const char *match_ref_pattern = NULL;
static int match_ref_slash = 0;
static int append_matching_ref(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
* refs/tags/v0.99.9a and friends.
*/
const char *tail;
int slash = count_slashes(refname);
for (tail = refname; *tail && match_ref_slash < slash; )
if (*tail++ == '/')
slash--;
if (!*tail)
return 0;
if (wildmatch(match_ref_pattern, tail, 0))
return 0;
if (starts_with(refname, "refs/heads/"))
return append_head_ref(refname, oid, flag, cb_data);
if (starts_with(refname, "refs/tags/"))
return append_tag_ref(refname, oid, flag, cb_data);
return append_ref(refname, oid, 0);
}
static void snarf_refs(int head, int remotes)
{
if (head) {
int orig_cnt = ref_name_cnt;
for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (remotes) {
int orig_cnt = ref_name_cnt;
for_each_ref(append_remote_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
static int rev_is_head(const char *head, const char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
return 0;
skip_prefix(head, "refs/heads/", &head);
if (!skip_prefix(name, "refs/heads/", &name))
skip_prefix(name, "heads/", &name);
return !strcmp(head, name);
}
static int show_merge_base(struct commit_list *seen, int num_rev)
{
int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
int exit_status = 1;
while (seen) {
struct commit *commit = pop_commit(&seen);
int flags = commit->object.flags & all_mask;
if (!(flags & UNINTERESTING) &&
((flags & all_revs) == all_revs)) {
puts(oid_to_hex(&commit->object.oid));
exit_status = 0;
commit->object.flags |= UNINTERESTING;
}
}
return exit_status;
}
static int show_independent(struct commit **rev,
int num_rev,
unsigned int *rev_mask)
{
int i;
for (i = 0; i < num_rev; i++) {
struct commit *commit = rev[i];
unsigned int flag = rev_mask[i];
if (commit->object.flags == flag)
puts(oid_to_hex(&commit->object.oid));
commit->object.flags |= UNINTERESTING;
}
return 0;
}
static void append_one_rev(const char *av)
{
struct object_id revkey;
if (!get_oid(av, &revkey)) {
append_ref(av, &revkey, 0);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
/* glob style match */
int saved_matches = ref_name_cnt;
match_ref_pattern = av;
match_ref_slash = count_slashes(av);
for_each_ref(append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
error(_("no matching refs with %s"), av);
sort_ref_range(saved_matches, ref_name_cnt);
return;
}
die("bad sha1 reference %s", av);
}
static int git_show_branch_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "showbranch.default")) {
if (!value)
return config_error_nonbool(var);
/*
* default_arg is now passed to parse_options(), so we need to
* mimic the real argv a bit better.
*/
if (!default_args.argc)
argv_array_push(&default_args, "show-branch");
argv_array_push(&default_args, value);
return 0;
}
if (!strcmp(var, "color.showbranch")) {
showbranch_use_color = git_config_colorbool(var, value);
return 0;
}
return git_color_default_config(var, value, cb);
}
static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
{
/* If the commit is tip of the named branches, do not
* omit it.
* Otherwise, if it is a merge that is reachable from only one
* tip, it is not that interesting.
*/
int i, flag, count;
for (i = 0; i < n; i++)
if (rev[i] == commit)
return 0;
flag = commit->object.flags;
for (i = count = 0; i < n; i++) {
if (flag & (1u << (i + REV_SHIFT)))
count++;
}
if (count == 1)
return 1;
return 0;
}
static int reflog = 0;
static int parse_reflog_param(const struct option *opt, const char *arg,
int unset)
{
char *ep;
const char **base = (const char **)opt->value;
BUG_ON_OPT_NEG(unset);
if (!arg)
arg = "";
reflog = strtoul(arg, &ep, 10);
if (*ep == ',')
*base = ep + 1;
else if (*ep)
return error("unrecognized reflog param '%s'", arg);
else
*base = NULL;
if (reflog <= 0)
reflog = DEFAULT_REFLOG;
return 0;
}
int cmd_show_branch(int ac, const char **av, const char *prefix)
{
struct commit *rev[MAX_REVS], *commit;
char *reflog_msg[MAX_REVS];
struct commit_list *list = NULL, *seen = NULL;
unsigned int rev_mask[MAX_REVS];
int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char *head;
struct object_id head_oid;
int merge_base = 0;
int independent = 0;
int no_name = 0;
int sha1_name = 0;
int shown_merge_point = 0;
int with_current_branch = 0;
int head_at = -1;
int topics = 0;
int dense = 1;
const char *reflog_base = NULL;
struct option builtin_show_branch_options[] = {
OPT_BOOL('a', "all", &all_heads,
N_("show remote-tracking and local branches")),
OPT_BOOL('r', "remotes", &all_remotes,
N_("show remote-tracking branches")),
OPT__COLOR(&showbranch_use_color,
N_("color '*!+-' corresponding to the branch")),
{ OPTION_INTEGER, 0, "more", &extra, N_("n"),
N_("show <n> more commits after the common ancestor"),
PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1),
OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")),
OPT_BOOL(0, "current", &with_current_branch,
N_("include the current branch")),
OPT_BOOL(0, "sha1-name", &sha1_name,
N_("name commits with their object names")),
OPT_BOOL(0, "merge-base", &merge_base,
N_("show possible merge bases")),
OPT_BOOL(0, "independent", &independent,
N_("show refs unreachable from any other ref")),
OPT_SET_INT(0, "topo-order", &sort_order,
N_("show commits in topological order"),
REV_SORT_IN_GRAPH_ORDER),
OPT_BOOL(0, "topics", &topics,
N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0),
OPT_SET_INT(0, "date-order", &sort_order,
N_("topologically sort, maintaining date order "
"where possible"),
REV_SORT_BY_COMMIT_DATE),
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
parse_reflog_param },
OPT_END()
};
init_commit_name_slab(&name_slab);
git_config(git_show_branch_config, NULL);
/* If nothing is specified, try the default first */
if (ac == 1 && default_args.argc) {
ac = default_args.argc;
av = default_args.argv;
}
ac = parse_options(ac, av, prefix, builtin_show_branch_options,
show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION);
if (all_heads)
all_remotes = 1;
if (extra || reflog) {
/* "listing" mode is incompatible with
* independent nor merge-base modes.
*/
if (independent || merge_base)
usage_with_options(show_branch_usage,
builtin_show_branch_options);
if (reflog && ((0 < extra) || all_heads || all_remotes))
/*
* Asking for --more in reflog mode does not
* make sense. --list is Ok.
*
* Also --all and --remotes do not make sense either.
*/
die(_("--reflog is incompatible with --all, --remotes, "
"--independent or --merge-base"));
}
/* If nothing is specified, show all branches by default */
if (ac <= topics && all_heads + all_remotes == 0)
all_heads = 1;
if (reflog) {
struct object_id oid;
char *ref;
int base = 0;
unsigned int flags = 0;
if (ac == 0) {
static const char *fake_av[2];
fake_av[0] = resolve_refdup("HEAD",
RESOLVE_REF_READING, &oid,
NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
if (!*av)
die(_("no branches given, and HEAD is not valid"));
}
if (ac != 1)
die(_("--reflog option needs one branch name"));
if (MAX_REVS < reflog)
die(Q_("only %d entry can be shown at one time.",
"only %d entries can be shown at one time.",
MAX_REVS), MAX_REVS);
if (!dwim_ref(*av, strlen(*av), &oid, &ref))
die(_("no such ref %s"), *av);
/* Has the base been specified? */
if (reflog_base) {
char *ep;
base = strtoul(reflog_base, &ep, 10);
if (*ep) {
/* Ah, that is a date spec... */
timestamp_t at;
at = approxidate(reflog_base);
read_ref_at(get_main_ref_store(the_repository),
ref, flags, at, -1, &oid, NULL,
NULL, NULL, &base);
}
}
for (i = 0; i < reflog; i++) {
char *logmsg;
char *nth_desc;
const char *msg;
timestamp_t timestamp;
int tz;
if (read_ref_at(get_main_ref_store(the_repository),
ref, flags, 0, base + i, &oid, &logmsg,
&timestamp, &tz, NULL)) {
reflog = i;
break;
}
msg = strchr(logmsg, '\t');
if (!msg)
msg = "(none)";
else
msg++;
reflog_msg[i] = xstrfmt("(%s) %s",
show_date(timestamp, tz,
DATE_MODE(RELATIVE)),
msg);
free(logmsg);
nth_desc = xstrfmt("%s@{%d}", *av, base+i);
append_ref(nth_desc, &oid, 1);
free(nth_desc);
}
free(ref);
}
else {
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
}
if (all_heads + all_remotes)
snarf_refs(all_heads, all_remotes);
}
head = resolve_refdup("HEAD", RESOLVE_REF_READING,
&head_oid, NULL);
if (with_current_branch && head) {
int has_head = 0;
for (i = 0; !has_head && i < ref_name_cnt; i++) {
/* We are only interested in adding the branch
* HEAD points at.
*/
if (rev_is_head(head,
ref_name[i],
head_oid.hash, NULL))
has_head++;
}
if (!has_head) {
const char *name = head;
skip_prefix(name, "refs/heads/", &name);
append_one_rev(name);
}
}
if (!ref_name_cnt) {
fprintf(stderr, "No revs to be shown.\n");
exit(0);
}
for (num_rev = 0; ref_name[num_rev]; num_rev++) {
struct object_id revkey;
unsigned int flag = 1u << (num_rev + REV_SHIFT);
if (MAX_REVS <= num_rev)
die(Q_("cannot handle more than %d rev.",
"cannot handle more than %d revs.",
MAX_REVS), MAX_REVS);
if (get_oid(ref_name[num_rev], &revkey))
die(_("'%s' is not a valid ref."), ref_name[num_rev]);
commit = lookup_commit_reference(the_repository, &revkey);
if (!commit)
die(_("cannot find commit %s (%s)"),
ref_name[num_rev], oid_to_hex(&revkey));
parse_commit(commit);
mark_seen(commit, &seen);
/* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
* and so on. REV_SHIFT bits from bit 0 are used for
* internal bookkeeping.
*/
commit->object.flags |= flag;
if (commit->object.flags == flag)
commit_list_insert_by_date(commit, &list);
rev[num_rev] = commit;
}
for (i = 0; i < num_rev; i++)
rev_mask[i] = rev[i]->object.flags;
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
commit_list_sort_by_date(&seen);
if (merge_base)
return show_merge_base(seen, num_rev);
if (independent)
return show_independent(rev, num_rev, rev_mask);
/* Show list; --more=-1 means list-only */
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
int is_head = rev_is_head(head,
ref_name[i],
head_oid.hash,
rev[i]->object.oid.hash);
if (extra < 0)
printf("%c [%s] ",
is_head ? '*' : ' ', ref_name[i]);
else {
for (j = 0; j < i; j++)
putchar(' ');
printf("%s%c%s [%s] ",
get_color_code(i),
is_head ? '*' : '!',
get_color_reset_code(), ref_name[i]);
}
if (!reflog) {
/* header lines never need name */
show_one_commit(rev[i], 1);
}
else
puts(reflog_msg[i]);
if (is_head)
head_at = i;
}
if (0 <= extra) {
for (i = 0; i < num_rev; i++)
putchar('-');
putchar('\n');
}
}
if (extra < 0)
exit(0);
/* Sort topologically */
sort_in_topological_order(&seen, sort_order);
/* Give names to commits */
if (!sha1_name && !no_name)
name_commits(seen, rev, ref_name, num_rev);
all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
while (seen) {
struct commit *commit = pop_commit(&seen);
int this_flag = commit->object.flags;
int is_merge_point = ((this_flag & all_revs) == all_revs);
shown_merge_point |= is_merge_point;
if (1 < num_rev) {
int is_merge = !!(commit->parents &&
commit->parents->next);
if (topics &&
!is_merge_point &&
(this_flag & (1u << REV_SHIFT)))
continue;
if (dense && is_merge &&
omit_in_dense(commit, rev, num_rev))
continue;
for (i = 0; i < num_rev; i++) {
int mark;
if (!(this_flag & (1u << (i + REV_SHIFT))))
mark = ' ';
else if (is_merge)
mark = '-';
else if (i == head_at)
mark = '*';
else
mark = '+';
printf("%s%c%s",
get_color_code(i),
mark, get_color_reset_code());
}
putchar(' ');
}
show_one_commit(commit, no_name);
if (shown_merge_point && --extra < 0)
break;
}
return 0;
}

86
third_party/git/builtin/show-index.c vendored Normal file
View file

@ -0,0 +1,86 @@
#include "builtin.h"
#include "cache.h"
#include "pack.h"
static const char show_index_usage[] =
"git show-index";
int cmd_show_index(int argc, const char **argv, const char *prefix)
{
int i;
unsigned nr;
unsigned int version;
static unsigned int top_index[256];
if (argc != 1)
usage(show_index_usage);
if (fread(top_index, 2 * 4, 1, stdin) != 1)
die("unable to read header");
if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
version = ntohl(top_index[1]);
if (version < 2 || version > 2)
die("unknown index version");
if (fread(top_index, 256 * 4, 1, stdin) != 1)
die("unable to read index");
} else {
version = 1;
if (fread(&top_index[2], 254 * 4, 1, stdin) != 1)
die("unable to read index");
}
nr = 0;
for (i = 0; i < 256; i++) {
unsigned n = ntohl(top_index[i]);
if (n < nr)
die("corrupt index file");
nr = n;
}
if (version == 1) {
for (i = 0; i < nr; i++) {
unsigned int offset, entry[6];
if (fread(entry, 4 + 20, 1, stdin) != 1)
die("unable to read entry %u/%u", i, nr);
offset = ntohl(entry[0]);
printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
}
} else {
unsigned off64_nr = 0;
struct {
unsigned char sha1[20];
uint32_t crc;
uint32_t off;
} *entries;
ALLOC_ARRAY(entries, nr);
for (i = 0; i < nr; i++)
if (fread(entries[i].sha1, 20, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
for (i = 0; i < nr; i++)
if (fread(&entries[i].crc, 4, 1, stdin) != 1)
die("unable to read crc %u/%u", i, nr);
for (i = 0; i < nr; i++)
if (fread(&entries[i].off, 4, 1, stdin) != 1)
die("unable to read 32b offset %u/%u", i, nr);
for (i = 0; i < nr; i++) {
uint64_t offset;
uint32_t off = ntohl(entries[i].off);
if (!(off & 0x80000000)) {
offset = off;
} else {
uint32_t off64[2];
if ((off & 0x7fffffff) != off64_nr)
die("inconsistent 64b offset index");
if (fread(off64, 8, 1, stdin) != 1)
die("unable to read 64b offset %u", off64_nr);
offset = (((uint64_t)ntohl(off64[0])) << 32) |
ntohl(off64[1]);
off64_nr++;
}
printf("%" PRIuMAX " %s (%08"PRIx32")\n",
(uintmax_t) offset,
sha1_to_hex(entries[i].sha1),
ntohl(entries[i].crc));
}
free(entries);
}
return 0;
}

226
third_party/git/builtin/show-ref.c vendored Normal file
View file

@ -0,0 +1,226 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "refs.h"
#include "object-store.h"
#include "object.h"
#include "tag.h"
#include "string-list.h"
#include "parse-options.h"
static const char * const show_ref_usage[] = {
N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"),
N_("git show-ref --exclude-existing[=<pattern>]"),
NULL
};
static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
quiet, hash_only, abbrev, exclude_arg;
static const char **pattern;
static const char *exclude_existing_arg;
static void show_one(const char *refname, const struct object_id *oid)
{
const char *hex;
struct object_id peeled;
if (!has_object_file(oid))
die("git show-ref: bad ref %s (%s)", refname,
oid_to_hex(oid));
if (quiet)
return;
hex = find_unique_abbrev(oid, abbrev);
if (hash_only)
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
if (!deref_tags)
return;
if (!peel_ref(refname, &peeled)) {
hex = find_unique_abbrev(&peeled, abbrev);
printf("%s %s^{}\n", hex, refname);
}
}
static int show_ref(const char *refname, const struct object_id *oid,
int flag, void *cbdata)
{
if (show_head && !strcmp(refname, "HEAD"))
goto match;
if (tags_only || heads_only) {
int match;
match = heads_only && starts_with(refname, "refs/heads/");
match |= tags_only && starts_with(refname, "refs/tags/");
if (!match)
return 0;
}
if (pattern) {
int reflen = strlen(refname);
const char **p = pattern, *m;
while ((m = *p++) != NULL) {
int len = strlen(m);
if (len > reflen)
continue;
if (memcmp(m, refname + reflen - len, len))
continue;
if (len == reflen)
goto match;
if (refname[reflen - len - 1] == '/')
goto match;
}
return 0;
}
match:
found_match++;
show_one(refname, oid);
return 0;
}
static int add_existing(const char *refname, const struct object_id *oid,
int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
string_list_insert(list, refname);
return 0;
}
/*
* read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
* and
* (1) strip "^{}" at the end of line if any;
* (2) ignore if match is provided and does not head-match refname;
* (3) warn if refname is not a well-formed refname and skip;
* (4) ignore if refname is a ref that exists in the local repository;
* (5) otherwise output the line.
*/
static int exclude_existing(const char *match)
{
static struct string_list existing_refs = STRING_LIST_INIT_DUP;
char buf[1024];
int matchlen = match ? strlen(match) : 0;
for_each_ref(add_existing, &existing_refs);
while (fgets(buf, sizeof(buf), stdin)) {
char *ref;
int len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[--len] = '\0';
if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
len -= 3;
buf[len] = '\0';
}
for (ref = buf + len; buf < ref; ref--)
if (isspace(ref[-1]))
break;
if (match) {
int reflen = buf + len - ref;
if (reflen < matchlen)
continue;
if (strncmp(ref, match, matchlen))
continue;
}
if (check_refname_format(ref, 0)) {
warning("ref '%s' ignored", ref);
continue;
}
if (!string_list_has_string(&existing_refs, ref)) {
printf("%s\n", buf);
}
}
return 0;
}
static int hash_callback(const struct option *opt, const char *arg, int unset)
{
hash_only = 1;
/* Use full length SHA1 if no argument */
if (!arg)
return 0;
return parse_opt_abbrev_cb(opt, arg, unset);
}
static int exclude_existing_callback(const struct option *opt, const char *arg,
int unset)
{
BUG_ON_OPT_NEG(unset);
exclude_arg = 1;
*(const char **)opt->value = arg;
return 0;
}
static const struct option show_ref_options[] = {
OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
"requires exact ref path")),
OPT_HIDDEN_BOOL('h', NULL, &show_head,
N_("show the HEAD reference, even if it would be filtered out")),
OPT_BOOL(0, "head", &show_head,
N_("show the HEAD reference, even if it would be filtered out")),
OPT_BOOL('d', "dereference", &deref_tags,
N_("dereference tags into object IDs")),
{ OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"),
N_("only show SHA1 hash using <n> digits"),
PARSE_OPT_OPTARG, &hash_callback },
OPT__ABBREV(&abbrev),
OPT__QUIET(&quiet,
N_("do not print results to stdout (useful with --verify)")),
{ OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
N_("pattern"), N_("show refs from stdin that aren't in local repository"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
OPT_END()
};
int cmd_show_ref(int argc, const char **argv, const char *prefix)
{
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, show_ref_options,
show_ref_usage, 0);
if (exclude_arg)
return exclude_existing(exclude_existing_arg);
pattern = argv;
if (!*pattern)
pattern = NULL;
if (verify) {
if (!pattern)
die("--verify requires a reference");
while (*pattern) {
struct object_id oid;
if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
!read_ref(*pattern, &oid)) {
show_one(*pattern, &oid);
}
else if (!quiet)
die("'%s' - not a valid ref", *pattern);
else
return 1;
pattern++;
}
return 0;
}
if (show_head)
head_ref(show_ref, NULL);
for_each_ref(show_ref, NULL);
if (!found_match) {
if (verify && !quiet)
die("No match");
return 1;
}
return 0;
}

1635
third_party/git/builtin/stash.c vendored Normal file

File diff suppressed because it is too large Load diff

65
third_party/git/builtin/stripspace.c vendored Normal file
View file

@ -0,0 +1,65 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "parse-options.h"
#include "strbuf.h"
static void comment_lines(struct strbuf *buf)
{
char *msg;
size_t len;
msg = strbuf_detach(buf, &len);
strbuf_add_commented_lines(buf, msg, len);
free(msg);
}
static const char * const stripspace_usage[] = {
N_("git stripspace [-s | --strip-comments]"),
N_("git stripspace [-c | --comment-lines]"),
NULL
};
enum stripspace_mode {
STRIP_DEFAULT = 0,
STRIP_COMMENTS,
COMMENT_LINES
};
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
enum stripspace_mode mode = STRIP_DEFAULT;
int nongit;
const struct option options[] = {
OPT_CMDMODE('s', "strip-comments", &mode,
N_("skip and remove all lines starting with comment character"),
STRIP_COMMENTS),
OPT_CMDMODE('c', "comment-lines", &mode,
N_("prepend comment character and space to each line"),
COMMENT_LINES),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, stripspace_usage, 0);
if (argc)
usage_with_options(stripspace_usage, options);
if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) {
setup_git_directory_gently(&nongit);
git_config(git_default_config, NULL);
}
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno("could not read the input");
if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS)
strbuf_stripspace(&buf, mode == STRIP_COMMENTS);
else
comment_lines(&buf);
write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
return 0;
}

File diff suppressed because it is too large Load diff

78
third_party/git/builtin/symbolic-ref.c vendored Normal file
View file

@ -0,0 +1,78 @@
#include "builtin.h"
#include "config.h"
#include "cache.h"
#include "refs.h"
#include "parse-options.h"
static const char * const git_symbolic_ref_usage[] = {
N_("git symbolic-ref [<options>] <name> [<ref>]"),
N_("git symbolic-ref -d [-q] <name>"),
NULL
};
static int check_symref(const char *HEAD, int quiet, int shorten, int print)
{
int flag;
const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
if (!refname)
die("No such ref: %s", HEAD);
else if (!(flag & REF_ISSYMREF)) {
if (!quiet)
die("ref %s is not a symbolic ref", HEAD);
else
return 1;
}
if (print) {
if (shorten)
refname = shorten_unambiguous_ref(refname, 0);
puts(refname);
}
return 0;
}
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
{
int quiet = 0, delete = 0, shorten = 0, ret = 0;
const char *msg = NULL;
struct option options[] = {
OPT__QUIET(&quiet,
N_("suppress error message for non-symbolic (detached) refs")),
OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
git_symbolic_ref_usage, 0);
if (msg && !*msg)
die("Refusing to perform update with empty message");
if (delete) {
if (argc != 1)
usage_with_options(git_symbolic_ref_usage, options);
ret = check_symref(argv[0], 1, 0, 0);
if (ret)
die("Cannot delete %s, not a symbolic ref", argv[0]);
if (!strcmp(argv[0], "HEAD"))
die("deleting '%s' is not allowed", argv[0]);
return delete_ref(NULL, argv[0], NULL, REF_NO_DEREF);
}
switch (argc) {
case 1:
ret = check_symref(argv[0], quiet, shorten, 1);
break;
case 2:
if (!strcmp(argv[0], "HEAD") &&
!starts_with(argv[1], "refs/"))
die("Refusing to point HEAD outside of refs/");
ret = !!create_symref(argv[0], argv[1], msg);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
}
return ret;
}

593
third_party/git/builtin/tag.c vendored Normal file
View file

@ -0,0 +1,593 @@
/*
* Builtin "git tag"
*
* Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
* Carlos Rica <jasampler@gmail.com>
* Based on git-tag.sh and mktag.c by Linus Torvalds.
*/
#include "cache.h"
#include "config.h"
#include "builtin.h"
#include "refs.h"
#include "object-store.h"
#include "tag.h"
#include "run-command.h"
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
#include "gpg-interface.h"
#include "sha1-array.h"
#include "column.h"
#include "ref-filter.h"
static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
"\t\t<tagname> [<head>]"),
N_("git tag -d <tagname>..."),
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
"\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
N_("git tag -v [--format=<format>] <tagname>..."),
NULL
};
static unsigned int colopts;
static int force_sign_annotate;
static int config_sign_tag = -1; /* unspecified */
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format)
{
struct ref_array array;
char *to_free = NULL;
int i;
memset(&array, 0, sizeof(array));
if (filter->lines == -1)
filter->lines = 0;
if (!format->format) {
if (filter->lines) {
to_free = xstrfmt("%s %%(contents:lines=%d)",
"%(align:15)%(refname:lstrip=2)%(end)",
filter->lines);
format->format = to_free;
} else
format->format = "%(refname:lstrip=2)";
}
if (verify_ref_format(format))
die(_("unable to parse format string"));
filter->with_commit_tag_algo = 1;
filter_refs(&array, filter, FILTER_REFS_TAGS);
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++)
show_ref_array_item(array.items[i], format);
ref_array_clear(&array);
free(to_free);
return 0;
}
typedef int (*each_tag_name_fn)(const char *name, const char *ref,
const struct object_id *oid, const void *cb_data);
static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
const void *cb_data)
{
const char **p;
struct strbuf ref = STRBUF_INIT;
int had_error = 0;
struct object_id oid;
for (p = argv; *p; p++) {
strbuf_reset(&ref);
strbuf_addf(&ref, "refs/tags/%s", *p);
if (read_ref(ref.buf, &oid)) {
error(_("tag '%s' not found."), *p);
had_error = 1;
continue;
}
if (fn(*p, ref.buf, &oid, cb_data))
had_error = 1;
}
strbuf_release(&ref);
return had_error;
}
static int delete_tag(const char *name, const char *ref,
const struct object_id *oid, const void *cb_data)
{
if (delete_ref(NULL, ref, oid, 0))
return 1;
printf(_("Deleted tag '%s' (was %s)\n"), name,
find_unique_abbrev(oid, DEFAULT_ABBREV));
return 0;
}
static int verify_tag(const char *name, const char *ref,
const struct object_id *oid, const void *cb_data)
{
int flags;
const struct ref_format *format = cb_data;
flags = GPG_VERIFY_VERBOSE;
if (format->format)
flags = GPG_VERIFY_OMIT_STATUS;
if (gpg_verify_tag(oid, name, flags))
return -1;
if (format->format)
pretty_print_ref(name, oid, format);
return 0;
}
static int do_sign(struct strbuf *buffer)
{
return sign_buffer(buffer, buffer, get_signing_key());
}
static const char tag_template[] =
N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be ignored.\n");
static const char tag_template_nocleanup[] =
N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be kept; you may remove them"
" yourself if you want to.\n");
static int git_tag_config(const char *var, const char *value, void *cb)
{
int status;
struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
if (!strcmp(var, "tag.gpgsign")) {
config_sign_tag = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "tag.sort")) {
if (!value)
return config_error_nonbool(var);
parse_ref_sorting(sorting_tail, value);
return 0;
}
status = git_gpg_config(var, value, cb);
if (status)
return status;
if (!strcmp(var, "tag.forcesignannotated")) {
force_sign_annotate = git_config_bool(var, value);
return 0;
}
if (starts_with(var, "column."))
return git_column_config(var, value, "tag", &colopts);
return git_color_default_config(var, value, cb);
}
static void write_tag_body(int fd, const struct object_id *oid)
{
unsigned long size;
enum object_type type;
char *buf, *sp;
buf = read_object_file(oid, &type, &size);
if (!buf)
return;
/* skip header */
sp = strstr(buf, "\n\n");
if (!sp || !size || type != OBJ_TAG) {
free(buf);
return;
}
sp += 2; /* skip the 2 LFs */
write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
free(buf);
}
static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result)
{
if (sign && do_sign(buf) < 0)
return error(_("unable to sign the tag"));
if (write_object_file(buf->buf, buf->len, tag_type, result) < 0)
return error(_("unable to write tag file"));
return 0;
}
struct create_tag_options {
unsigned int message_given:1;
unsigned int use_editor:1;
unsigned int sign;
enum {
CLEANUP_NONE,
CLEANUP_SPACE,
CLEANUP_ALL
} cleanup_mode;
};
static const char message_advice_nested_tag[] =
N_("You have created a nested tag. The object referred to by your new tag is\n"
"already a tag. If you meant to tag the object that it points to, use:\n"
"\n"
"\tgit tag -f %s %s^{}");
static void create_tag(const struct object_id *object, const char *object_ref,
const char *tag,
struct strbuf *buf, struct create_tag_options *opt,
struct object_id *prev, struct object_id *result)
{
enum object_type type;
struct strbuf header = STRBUF_INIT;
char *path = NULL;
type = oid_object_info(the_repository, object, NULL);
if (type <= OBJ_NONE)
die(_("bad object type."));
if (type == OBJ_TAG && advice_nested_tag)
advise(_(message_advice_nested_tag), tag, object_ref);
strbuf_addf(&header,
"object %s\n"
"type %s\n"
"tag %s\n"
"tagger %s\n\n",
oid_to_hex(object),
type_name(type),
tag,
git_committer_info(IDENT_STRICT));
if (!opt->message_given || opt->use_editor) {
int fd;
/* write the template message before editing: */
path = git_pathdup("TAG_EDITMSG");
fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0)
die_errno(_("could not create file '%s'"), path);
if (opt->message_given) {
write_or_die(fd, buf->buf, buf->len);
strbuf_reset(buf);
} else if (!is_null_oid(prev)) {
write_tag_body(fd, prev);
} else {
struct strbuf buf = STRBUF_INIT;
strbuf_addch(&buf, '\n');
if (opt->cleanup_mode == CLEANUP_ALL)
strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
else
strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
write_or_die(fd, buf.buf, buf.len);
strbuf_release(&buf);
}
close(fd);
if (launch_editor(path, buf, NULL)) {
fprintf(stderr,
_("Please supply the message using either -m or -F option.\n"));
exit(1);
}
}
if (opt->cleanup_mode != CLEANUP_NONE)
strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
if (!opt->message_given && !buf->len)
die(_("no tag message?"));
strbuf_insert(buf, 0, header.buf, header.len);
strbuf_release(&header);
if (build_tag_object(buf, opt->sign, result) < 0) {
if (path)
fprintf(stderr, _("The tag message has been left in %s\n"),
path);
exit(128);
}
if (path) {
unlink_or_warn(path);
free(path);
}
}
static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb)
{
enum object_type type;
struct commit *c;
char *buf;
unsigned long size;
int subject_len = 0;
const char *subject_start;
char *rla = getenv("GIT_REFLOG_ACTION");
if (rla) {
strbuf_addstr(sb, rla);
} else {
strbuf_addstr(sb, "tag: tagging ");
strbuf_add_unique_abbrev(sb, oid, DEFAULT_ABBREV);
}
strbuf_addstr(sb, " (");
type = oid_object_info(the_repository, oid, NULL);
switch (type) {
default:
strbuf_addstr(sb, "object of unknown type");
break;
case OBJ_COMMIT:
if ((buf = read_object_file(oid, &type, &size)) != NULL) {
subject_len = find_commit_subject(buf, &subject_start);
strbuf_insert(sb, sb->len, subject_start, subject_len);
} else {
strbuf_addstr(sb, "commit object");
}
free(buf);
if ((c = lookup_commit_reference(the_repository, oid)) != NULL)
strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
break;
case OBJ_TREE:
strbuf_addstr(sb, "tree object");
break;
case OBJ_BLOB:
strbuf_addstr(sb, "blob object");
break;
case OBJ_TAG:
strbuf_addstr(sb, "other tag object");
break;
}
strbuf_addch(sb, ')');
}
struct msg_arg {
int given;
struct strbuf buf;
};
static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
{
struct msg_arg *msg = opt->value;
BUG_ON_OPT_NEG(unset);
if (!arg)
return -1;
if (msg->buf.len)
strbuf_addstr(&(msg->buf), "\n\n");
strbuf_addstr(&(msg->buf), arg);
msg->given = 1;
return 0;
}
static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
{
if (name[0] == '-')
return -1;
strbuf_reset(sb);
strbuf_addf(sb, "refs/tags/%s", name);
return check_refname_format(sb->buf, 0);
}
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf ref = STRBUF_INIT;
struct strbuf reflog_msg = STRBUF_INIT;
struct object_id object, prev;
const char *object_ref, *tag;
struct create_tag_options opt;
char *cleanup_arg = NULL;
int create_reflog = 0;
int annotate = 0, force = 0;
int cmdmode = 0, create_tag_object = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
struct ref_filter filter;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
struct ref_format format = REF_FORMAT_INIT;
int icase = 0;
int edit_flag = 0;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
N_("print <n> lines of each tag message"),
PARSE_OPT_OPTARG, NULL, 1 },
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_BOOL('a', "annotate", &annotate,
N_("annotated tag, needs a message")),
{ OPTION_CALLBACK, 'm', "message", &msg, N_("message"),
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg },
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
OPT_CLEANUP(&cleanup_arg),
OPT_STRING('u', "local-user", &keyid, N_("key-id"),
N_("use another key to sign the tag")),
OPT__FORCE(&force, N_("replace the tag if exists"), 0),
OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
OPT_GROUP(N_("Tag listing options")),
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_MERGED(&filter, N_("print only tags that are merged")),
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
OPT_REF_SORT(sorting_tail),
{
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
parse_opt_object_name, (intptr_t) "HEAD"
},
OPT_STRING( 0 , "format", &format.format, N_("format"),
N_("format to use for the output")),
OPT__COLOR(&format.use_color, N_("respect format colors")),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END()
};
setup_ref_filter_porcelain_msg();
git_config(git_tag_config, sorting_tail);
memset(&opt, 0, sizeof(opt));
memset(&filter, 0, sizeof(filter));
filter.lines = -1;
opt.sign = -1;
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
if (!cmdmode) {
if (argc == 0)
cmdmode = 'l';
else if (filter.with_commit || filter.no_commit ||
filter.points_at.nr || filter.merge_commit ||
filter.lines != -1)
cmdmode = 'l';
}
if (cmdmode == 'l')
setup_auto_pager("tag", 1);
if (opt.sign == -1)
opt.sign = cmdmode ? 0 : config_sign_tag > 0;
if (keyid) {
opt.sign = 1;
set_signing_key(keyid);
}
create_tag_object = (opt.sign || annotate || msg.given || msgfile);
if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
finalize_colopts(&colopts, -1);
if (cmdmode == 'l' && filter.lines != -1) {
if (explicitly_enable_column(colopts))
die(_("--column and -n are incompatible"));
colopts = 0;
}
if (!sorting)
sorting = ref_default_sorting();
sorting->ignore_case = icase;
filter.ignore_case = icase;
if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
struct column_options copts;
memset(&copts, 0, sizeof(copts));
copts.padding = 2;
run_column_filter(colopts, &copts);
}
filter.name_patterns = argv;
ret = list_tags(&filter, sorting, &format);
if (column_active(colopts))
stop_column_filter();
return ret;
}
if (filter.lines != -1)
die(_("-n option is only allowed in list mode"));
if (filter.with_commit)
die(_("--contains option is only allowed in list mode"));
if (filter.no_commit)
die(_("--no-contains option is only allowed in list mode"));
if (filter.points_at.nr)
die(_("--points-at option is only allowed in list mode"));
if (filter.merge_commit)
die(_("--merged and --no-merged options are only allowed in list mode"));
if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag, NULL);
if (cmdmode == 'v') {
if (format.format && verify_ref_format(&format))
usage_with_options(git_tag_usage, options);
return for_each_tag_name(argv, verify_tag, &format);
}
if (msg.given || msgfile) {
if (msg.given && msgfile)
die(_("only one -F or -m option is allowed."));
if (msg.given)
strbuf_addbuf(&buf, &(msg.buf));
else {
if (!strcmp(msgfile, "-")) {
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno(_("cannot read '%s'"), msgfile);
} else {
if (strbuf_read_file(&buf, msgfile, 1024) < 0)
die_errno(_("could not open or read '%s'"),
msgfile);
}
}
}
tag = argv[0];
object_ref = argc == 2 ? argv[1] : "HEAD";
if (argc > 2)
die(_("too many params"));
if (get_oid(object_ref, &object))
die(_("Failed to resolve '%s' as a valid ref."), object_ref);
if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
if (read_ref(ref.buf, &prev))
oidclr(&prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
opt.message_given = msg.given || msgfile;
opt.use_editor = edit_flag;
if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
opt.cleanup_mode = CLEANUP_ALL;
else if (!strcmp(cleanup_arg, "verbatim"))
opt.cleanup_mode = CLEANUP_NONE;
else if (!strcmp(cleanup_arg, "whitespace"))
opt.cleanup_mode = CLEANUP_SPACE;
else
die(_("Invalid cleanup mode %s"), cleanup_arg);
create_reflog_msg(&object, &reflog_msg);
if (create_tag_object) {
if (force_sign_annotate && !annotate)
opt.sign = 1;
create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object);
}
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
if (force && !is_null_oid(&prev) && !oideq(&prev, &object))
printf(_("Updated tag '%s' (was %s)\n"), tag,
find_unique_abbrev(&prev, DEFAULT_ABBREV));
UNLEAK(buf);
UNLEAK(ref);
UNLEAK(reflog_msg);
UNLEAK(msg);
UNLEAK(err);
return 0;
}

38
third_party/git/builtin/unpack-file.c vendored Normal file
View file

@ -0,0 +1,38 @@
#include "builtin.h"
#include "config.h"
#include "object-store.h"
static char *create_temp_file(struct object_id *oid)
{
static char path[50];
void *buf;
enum object_type type;
unsigned long size;
int fd;
buf = read_object_file(oid, &type, &size);
if (!buf || type != OBJ_BLOB)
die("unable to read blob object %s", oid_to_hex(oid));
xsnprintf(path, sizeof(path), ".merge_file_XXXXXX");
fd = xmkstemp(path);
if (write_in_full(fd, buf, size) < 0)
die_errno("unable to write temp-file");
close(fd);
return path;
}
int cmd_unpack_file(int argc, const char **argv, const char *prefix)
{
struct object_id oid;
if (argc != 2 || !strcmp(argv[1], "-h"))
usage("git unpack-file <sha1>");
if (get_oid(argv[1], &oid))
die("Not a valid object name %s", argv[1]);
git_config(git_default_config, NULL);
puts(create_temp_file(&oid));
return 0;
}

Some files were not shown because too many files have changed in this diff Show more