merge(third_party/git): Merge squashed git subtree at v2.23.0
Merge commit '1b593e1ea4' as 'third_party/git'
This commit is contained in:
commit
7ef0d62730
3629 changed files with 1139935 additions and 0 deletions
545
third_party/git/builtin/add.c
vendored
Normal file
545
third_party/git/builtin/add.c
vendored
Normal 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
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
22
third_party/git/builtin/annotate.c
vendored
Normal 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
34
third_party/git/builtin/apply.c
vendored
Normal 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
112
third_party/git/builtin/archive.c
vendored
Normal 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
714
third_party/git/builtin/bisect--helper.c
vendored
Normal 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
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
855
third_party/git/builtin/branch.c
vendored
Normal 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", ©, N_("copy a branch and its reflog"), 1),
|
||||
OPT_BIT('C', NULL, ©, 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
67
third_party/git/builtin/bundle.c
vendored
Normal 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
717
third_party/git/builtin/cat-file.c
vendored
Normal 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
189
third_party/git/builtin/check-attr.c
vendored
Normal 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
193
third_party/git/builtin/check-ignore.c
vendored
Normal 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
67
third_party/git/builtin/check-mailmap.c
vendored
Normal 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;
|
||||
}
|
||||
91
third_party/git/builtin/check-ref-format.c
vendored
Normal file
91
third_party/git/builtin/check-ref-format.c
vendored
Normal 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
262
third_party/git/builtin/checkout-index.c
vendored
Normal 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", ¬_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
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
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
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
59
third_party/git/builtin/column.c
vendored
Normal 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
263
third_party/git/builtin/commit-graph.c
vendored
Normal 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
152
third_party/git/builtin/commit-tree.c
vendored
Normal 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
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
872
third_party/git/builtin/config.c
vendored
Normal 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
172
third_party/git/builtin/count-objects.c
vendored
Normal 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
31
third_party/git/builtin/credential.c
vendored
Normal 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
662
third_party/git/builtin/describe.c
vendored
Normal 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
75
third_party/git/builtin/diff-files.c
vendored
Normal 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
62
third_party/git/builtin/diff-index.c
vendored
Normal 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
196
third_party/git/builtin/diff-tree.c
vendored
Normal 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
465
third_party/git/builtin/diff.c
vendored
Normal 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
764
third_party/git/builtin/difftool.c
vendored
Normal 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
95
third_party/git/builtin/env--helper.c
vendored
Normal 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
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
270
third_party/git/builtin/fetch-pack.c
vendored
Normal 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
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
719
third_party/git/builtin/fmt-merge-msg.c
vendored
Normal 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
87
third_party/git/builtin/for-each-ref.c
vendored
Normal 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
976
third_party/git/builtin/fsck.c
vendored
Normal 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
702
third_party/git/builtin/gc.c
vendored
Normal 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;
|
||||
}
|
||||
52
third_party/git/builtin/get-tar-commit-id.c
vendored
Normal file
52
third_party/git/builtin/get-tar-commit-id.c
vendored
Normal 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
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
162
third_party/git/builtin/hash-object.c
vendored
Normal 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
537
third_party/git/builtin/help.c
vendored
Normal 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
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
601
third_party/git/builtin/init-db.c
vendored
Normal 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);
|
||||
}
|
||||
140
third_party/git/builtin/interpret-trailers.c
vendored
Normal file
140
third_party/git/builtin/interpret-trailers.c
vendored
Normal 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
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
692
third_party/git/builtin/ls-files.c
vendored
Normal 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
152
third_party/git/builtin/ls-remote.c
vendored
Normal 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
190
third_party/git/builtin/ls-tree.c
vendored
Normal 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
65
third_party/git/builtin/mailinfo.c
vendored
Normal 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
369
third_party/git/builtin/mailsplit.c
vendored
Normal 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
198
third_party/git/builtin/merge-base.c
vendored
Normal 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
118
third_party/git/builtin/merge-file.c
vendored
Normal 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
111
third_party/git/builtin/merge-index.c
vendored
Normal 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
33
third_party/git/builtin/merge-ours.c
vendored
Normal 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);
|
||||
}
|
||||
88
third_party/git/builtin/merge-recursive.c
vendored
Normal file
88
third_party/git/builtin/merge-recursive.c
vendored
Normal 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
389
third_party/git/builtin/merge-tree.c
vendored
Normal 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
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
176
third_party/git/builtin/mktag.c
vendored
Normal 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
193
third_party/git/builtin/mktree.c
vendored
Normal 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);
|
||||
}
|
||||
62
third_party/git/builtin/multi-pack-index.c
vendored
Normal file
62
third_party/git/builtin/multi-pack-index.c
vendored
Normal 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
304
third_party/git/builtin/mv.c
vendored
Normal 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
517
third_party/git/builtin/name-rev.c
vendored
Normal 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
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
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
653
third_party/git/builtin/pack-redundant.c
vendored
Normal 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
24
third_party/git/builtin/pack-refs.c
vendored
Normal 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
184
third_party/git/builtin/patch-id.c
vendored
Normal 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
73
third_party/git/builtin/prune-packed.c
vendored
Normal 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
187
third_party/git/builtin/prune.c
vendored
Normal 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
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
646
third_party/git/builtin/push.c
vendored
Normal 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
87
third_party/git/builtin/range-diff.c
vendored
Normal 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
270
third_party/git/builtin/read-tree.c
vendored
Normal 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
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
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
788
third_party/git/builtin/reflog.c
vendored
Normal 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
202
third_party/git/builtin/remote-ext.c
vendored
Normal 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
82
third_party/git/builtin/remote-fd.c
vendored
Normal 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
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
572
third_party/git/builtin/repack.c
vendored
Normal 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
622
third_party/git/builtin/replace.c
vendored
Normal 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
120
third_party/git/builtin/rerere.c
vendored
Normal 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
427
third_party/git/builtin/reset.c
vendored
Normal 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
619
third_party/git/builtin/rev-list.c
vendored
Normal 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
965
third_party/git/builtin/rev-parse.c
vendored
Normal 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
247
third_party/git/builtin/revert.c
vendored
Normal 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
393
third_party/git/builtin/rm.c
vendored
Normal 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
311
third_party/git/builtin/send-pack.c
vendored
Normal 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
379
third_party/git/builtin/shortlog.c
vendored
Normal 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
954
third_party/git/builtin/show-branch.c
vendored
Normal 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,
|
||||
×tamp, &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
86
third_party/git/builtin/show-index.c
vendored
Normal 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
226
third_party/git/builtin/show-ref.c
vendored
Normal 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
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
65
third_party/git/builtin/stripspace.c
vendored
Normal 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;
|
||||
}
|
||||
2243
third_party/git/builtin/submodule--helper.c
vendored
Normal file
2243
third_party/git/builtin/submodule--helper.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
78
third_party/git/builtin/symbolic-ref.c
vendored
Normal file
78
third_party/git/builtin/symbolic-ref.c
vendored
Normal 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
593
third_party/git/builtin/tag.c
vendored
Normal 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
38
third_party/git/builtin/unpack-file.c
vendored
Normal 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
Loading…
Add table
Add a link
Reference in a new issue