Update to the "linked checkout" in 2.5.0-rc1.
Instead of "checkout --to" that does not do what "checkout"
normally does, move the functionality to "git worktree add".
* es/worktree-add: (24 commits)
Revert "checkout: retire --ignore-other-worktrees in favor of --force"
checkout: retire --ignore-other-worktrees in favor of --force
worktree: add: auto-vivify new branch when <branch> is omitted
worktree: add: make -b/-B default to HEAD when <branch> is omitted
worktree: extract basename computation to new function
checkout: require worktree unconditionally
checkout: retire --to option
tests: worktree: retrofit "checkout --to" tests for "worktree add"
worktree: add -b/-B options
worktree: add --detach option
worktree: add --force option
worktree: introduce "add" command
checkout: drop 'checkout_opts' dependency from prepare_linked_checkout
checkout: make --to unconditionally verbose
checkout: prepare_linked_checkout: drop now-unused 'new' argument
checkout: relocate --to's "no branch specified" check
checkout: fix bug with --to and relative HEAD
Documentation/git-worktree: add EXAMPLES section
Documentation/git-worktree: add high-level 'lock' overview
Documentation/git-worktree: split technical info from general description
...
NAME
----
-git-checkout - Checkout a branch or paths to the working tree
+git-checkout - Switch branches or restore working tree files
SYNOPSIS
--------
(i.e. commit, tag or tree) to update the index for the given
paths before updating the working tree.
+
+'git checkout' with <paths> or `--patch` is used to restore modified or
+deleted paths to their original contents from the index or replace paths
+with the contents from a named <tree-ish> (most often a commit-ish).
++
The index may contain unmerged entries because of a previous failed merge.
By default, if you try to check out such an entry from the index, the
checkout operation will fail and nothing will be checked out.
--no-track::
Do not set up "upstream" configuration, even if the
- branch.autosetupmerge configuration variable is true.
+ branch.autoSetupMerge configuration variable is true.
-l::
Create the new branch's reflog; see linkgit:git-branch[1] for
--conflict=<style>::
The same as --merge option above, but changes the way the
conflicting hunks are presented, overriding the
- merge.conflictstyle configuration variable. Possible values are
+ merge.conflictStyle configuration variable. Possible values are
"merge" (default) and "diff3" (in addition to what is shown by
"merge" style, shows the original contents).
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
- --to=<path>::
- Check out a branch in a separate working directory at
- `<path>`. A new working directory is linked to the current
- repository, sharing everything except working directory
- specific files such as HEAD, index... See "MULTIPLE WORKING
- TREES" section for more information.
-
--ignore-other-worktrees::
`git checkout` refuses when the wanted ref is already checked
out by another worktree. This option makes it check the ref
$ git log -g -2 HEAD
------------
- MULTIPLE WORKING TREES
- ----------------------
-
- A git repository can support multiple working trees, allowing you to check
- out more than one branch at a time. With `git checkout --to` a new working
- tree is associated with the repository. This new working tree is called a
- "linked working tree" as opposed to the "main working tree" prepared by "git
- init" or "git clone". A repository has one main working tree (if it's not a
- bare repository) and zero or more linked working trees.
-
- Each linked working tree has a private sub-directory in the repository's
- $GIT_DIR/worktrees directory. The private sub-directory's name is usually
- the base name of the linked working tree's path, possibly appended with a
- number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
- command `git checkout --to /path/other/test-next next` creates the linked
- working tree in `/path/other/test-next` and also creates a
- `$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
- if `test-next` is already taken).
-
- Within a linked working tree, $GIT_DIR is set to point to this private
- directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
- $GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
- (e.g. `/path/main/.git`). These settings are made in a `.git` file located at
- the top directory of the linked working tree.
-
- Path resolution via `git rev-parse --git-path` uses either
- $GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
- linked working tree `git rev-parse --git-path HEAD` returns
- `/path/main/.git/worktrees/test-next/HEAD` (not
- `/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
- rev-parse --git-path refs/heads/master` uses
- $GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
- since refs are shared across all working trees.
-
- See linkgit:gitrepository-layout[5] for more information. The rule of
- thumb is do not make any assumption about whether a path belongs to
- $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
- inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
-
- When you are done with a linked working tree you can simply delete it.
- The working tree's entry in the repository's $GIT_DIR/worktrees
- directory will eventually be removed automatically (see
- `gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
- `git prune --worktrees` in the main or any linked working tree to
- clean up any stale entries in $GIT_DIR/worktrees.
-
- If you move a linked working directory to another file system, or
- within a file system that does not support hard links, you need to run
- at least one git command inside the linked working directory
- (e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
- so that it does not get automatically removed.
-
- To prevent a $GIT_DIR/worktrees entry from from being pruned (which
- can be useful in some situations, such as when the
- entry's working tree is stored on a portable device), add a file named
- 'locked' to the entry's directory. The file contains the reason in
- plain text. For example, if a linked working tree's `.git` file points
- to `/path/main/.git/worktrees/test-next` then a file named
- `/path/main/.git/worktrees/test-next/locked` will prevent the
- `test-next` entry from being pruned. See
- linkgit:gitrepository-layout[5] for details.
-
- Multiple checkout support for submodules is incomplete. It is NOT
- recommended to make multiple checkouts of a superproject.
-
EXAMPLES
--------
#include "ll-merge.h"
#include "resolve-undo.h"
#include "submodule.h"
- #include "argv-array.h"
- #include "sigchain.h"
static const char * const checkout_usage[] = {
- N_("git checkout [options] <branch>"),
- N_("git checkout [options] [<branch>] -- <file>..."),
+ N_("git checkout [<options>] <branch>"),
+ N_("git checkout [<options>] [<branch>] -- <file>..."),
NULL,
};
struct pathspec pathspec;
struct tree *source_tree;
- const char *new_worktree;
- const char **saved_argv;
int new_worktree_mode;
};
}
-static int update_some(const unsigned char *sha1, const char *base, int baselen,
+static int update_some(const unsigned char *sha1, struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
int len;
struct cache_entry *ce;
+ int pos;
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
- len = baselen + strlen(pathname);
+ len = base->len + strlen(pathname);
ce = xcalloc(1, cache_entry_size(len));
hashcpy(ce->sha1, sha1);
- memcpy(ce->name, base, baselen);
- memcpy(ce->name + baselen, pathname, len - baselen);
+ memcpy(ce->name, base->buf, base->len);
+ memcpy(ce->name + base->len, pathname, len - base->len);
ce->ce_flags = create_ce_flags(0) | CE_UPDATE;
ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
+
+ /*
+ * If the entry is the same as the current index, we can leave the old
+ * entry in place. Whether it is UPTODATE or not, checkout_entry will
+ * do the right thing.
+ */
+ pos = cache_name_pos(ce->name, ce->ce_namelen);
+ if (pos >= 0) {
+ struct cache_entry *old = active_cache[pos];
+ if (ce->ce_mode == old->ce_mode &&
+ !hashcmp(ce->sha1, old->sha1)) {
+ old->ce_flags |= CE_UPDATE;
+ free(ce);
+ return 0;
+ }
+ }
+
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
return 0;
}
die(_("Cannot update paths and switch to branch '%s' at the same time."),
opts->new_branch);
- if (opts->new_worktree)
- die(_("'%s' cannot be used with updating paths"), "--to");
-
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
&opts->pathspec);
}
static int add_pending_uninteresting_ref(const char *refname,
- const unsigned char *sha1,
+ const struct object_id *oid,
int flags, void *cb_data)
{
- add_pending_sha1(cb_data, refname, sha1, UNINTERESTING);
+ add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
return 0;
}
if (advice_detached_head)
fprintf(stderr,
- _(
+ Q_(
+ /* The singular version */
+ "If you want to keep it by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch <new-branch-name> %s\n\n",
+ /* The plural version */
"If you want to keep them by creating a new branch, "
"this may be a good time\nto do so with:\n\n"
- " git branch new_branch_name %s\n\n"),
+ " git branch <new-branch-name> %s\n\n",
+ /* Give ngettext() the count */
+ lost),
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
}
return ret || writeout_error;
}
- static char *junk_work_tree;
- static char *junk_git_dir;
- static int is_junk;
- static pid_t junk_pid;
-
- static void remove_junk(void)
- {
- struct strbuf sb = STRBUF_INIT;
- if (!is_junk || getpid() != junk_pid)
- return;
- if (junk_git_dir) {
- strbuf_addstr(&sb, junk_git_dir);
- remove_dir_recursively(&sb, 0);
- strbuf_reset(&sb);
- }
- if (junk_work_tree) {
- strbuf_addstr(&sb, junk_work_tree);
- remove_dir_recursively(&sb, 0);
- }
- strbuf_release(&sb);
- }
-
- static void remove_junk_on_signal(int signo)
- {
- remove_junk();
- sigchain_pop(signo);
- raise(signo);
- }
-
- static int prepare_linked_checkout(const struct checkout_opts *opts,
- struct branch_info *new)
- {
- struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
- struct strbuf sb = STRBUF_INIT;
- const char *path = opts->new_worktree, *name;
- struct stat st;
- struct child_process cp;
- int counter = 0, len, ret;
-
- if (!new->commit)
- die(_("no branch specified"));
- if (file_exists(path) && !is_empty_dir(path))
- die(_("'%s' already exists"), path);
-
- len = strlen(path);
- while (len && is_dir_sep(path[len - 1]))
- len--;
-
- for (name = path + len - 1; name > path; name--)
- if (is_dir_sep(*name)) {
- name++;
- break;
- }
- strbuf_addstr(&sb_repo,
- git_path("worktrees/%.*s", (int)(path + len - name), name));
- len = sb_repo.len;
- if (safe_create_leading_directories_const(sb_repo.buf))
- die_errno(_("could not create leading directories of '%s'"),
- sb_repo.buf);
- while (!stat(sb_repo.buf, &st)) {
- counter++;
- strbuf_setlen(&sb_repo, len);
- strbuf_addf(&sb_repo, "%d", counter);
- }
- name = strrchr(sb_repo.buf, '/') + 1;
-
- junk_pid = getpid();
- atexit(remove_junk);
- sigchain_push_common(remove_junk_on_signal);
-
- if (mkdir(sb_repo.buf, 0777))
- die_errno(_("could not create directory of '%s'"), sb_repo.buf);
- junk_git_dir = xstrdup(sb_repo.buf);
- is_junk = 1;
-
- /*
- * lock the incomplete repo so prune won't delete it, unlock
- * after the preparation is over.
- */
- strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- write_file(sb.buf, 1, "initializing\n");
-
- strbuf_addf(&sb_git, "%s/.git", path);
- if (safe_create_leading_directories_const(sb_git.buf))
- die_errno(_("could not create leading directories of '%s'"),
- sb_git.buf);
- junk_work_tree = xstrdup(path);
-
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
- write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
- write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
- real_path(get_git_common_dir()), name);
- /*
- * This is to keep resolve_ref() happy. We need a valid HEAD
- * or is_git_directory() will reject the directory. Any valid
- * value would do because this value will be ignored and
- * replaced at the next (real) checkout.
- */
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
- write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
- write_file(sb.buf, 1, "../..\n");
-
- if (!opts->quiet)
- fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
-
- setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
- setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
- setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
- memset(&cp, 0, sizeof(cp));
- cp.git_cmd = 1;
- cp.argv = opts->saved_argv;
- ret = run_command(&cp);
- if (!ret) {
- is_junk = 0;
- free(junk_work_tree);
- free(junk_git_dir);
- junk_work_tree = NULL;
- junk_git_dir = NULL;
- }
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- unlink_or_warn(sb.buf);
- strbuf_release(&sb);
- strbuf_release(&sb_repo);
- strbuf_release(&sb_git);
- return ret;
- }
-
static int git_checkout_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "diff.ignoresubmodules")) {
free(head_ref);
}
- if (opts->new_worktree)
- return prepare_linked_checkout(opts, new);
-
if (!new->commit && opts->new_branch) {
unsigned char rev[20];
int flag;
OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
N_("do not limit pathspecs to sparse entries only")),
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
- N_("second guess 'git checkout no-such-branch'")),
+ N_("second guess 'git checkout <no-such-branch>'")),
- OPT_FILENAME(0, "to", &opts.new_worktree,
- N_("check a branch out in a separate working directory")),
OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
N_("do not check if another worktree is holding the given ref")),
OPT_END(),
opts.overwrite_ignore = 1;
opts.prefix = prefix;
- opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
- memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
-
gitmodules_config();
git_config(git_checkout_config, &opts);
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
- /* recursive execution from checkout_new_worktree() */
opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
- if (opts.new_worktree_mode)
- opts.new_worktree = NULL;
-
- if (!opts.new_worktree)
- setup_work_tree();
if (conflict_style) {
opts.merge = 1; /* implied */
#include "builtin.h"
#include "dir.h"
#include "parse-options.h"
+ #include "argv-array.h"
+ #include "run-command.h"
+ #include "sigchain.h"
++#include "refs.h"
static const char * const worktree_usage[] = {
+ N_("git worktree add [<options>] <path> <branch>"),
N_("git worktree prune [<options>]"),
NULL
};
return 0;
}
+ static char *junk_work_tree;
+ static char *junk_git_dir;
+ static int is_junk;
+ static pid_t junk_pid;
+
+ static void remove_junk(void)
+ {
+ struct strbuf sb = STRBUF_INIT;
+ if (!is_junk || getpid() != junk_pid)
+ return;
+ if (junk_git_dir) {
+ strbuf_addstr(&sb, junk_git_dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+ if (junk_work_tree) {
+ strbuf_addstr(&sb, junk_work_tree);
+ remove_dir_recursively(&sb, 0);
+ }
+ strbuf_release(&sb);
+ }
+
+ static void remove_junk_on_signal(int signo)
+ {
+ remove_junk();
+ sigchain_pop(signo);
+ raise(signo);
+ }
+
+ static const char *worktree_basename(const char *path, int *olen)
+ {
+ const char *name;
+ int len;
+
+ len = strlen(path);
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+
+ *olen = len;
+ return name;
+ }
+
+ static int add_worktree(const char *path, const char **child_argv)
+ {
+ struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ const char *name;
+ struct stat st;
+ struct child_process cp;
+ int counter = 0, len, ret;
+ unsigned char rev[20];
+
+ if (file_exists(path) && !is_empty_dir(path))
+ die(_("'%s' already exists"), path);
+
+ name = worktree_basename(path, &len);
+ strbuf_addstr(&sb_repo,
+ git_path("worktrees/%.*s", (int)(path + len - name), name));
+ len = sb_repo.len;
+ if (safe_create_leading_directories_const(sb_repo.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_repo.buf);
+ while (!stat(sb_repo.buf, &st)) {
+ counter++;
+ strbuf_setlen(&sb_repo, len);
+ strbuf_addf(&sb_repo, "%d", counter);
+ }
+ name = strrchr(sb_repo.buf, '/') + 1;
+
+ junk_pid = getpid();
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ if (mkdir(sb_repo.buf, 0777))
+ die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+ junk_git_dir = xstrdup(sb_repo.buf);
+ is_junk = 1;
+
+ /*
+ * lock the incomplete repo so prune won't delete it, unlock
+ * after the preparation is over.
+ */
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ write_file(sb.buf, 1, "initializing\n");
+
+ strbuf_addf(&sb_git, "%s/.git", path);
+ if (safe_create_leading_directories_const(sb_git.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_git.buf);
+ junk_work_tree = xstrdup(path);
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
+ write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+ real_path(get_git_common_dir()), name);
+ /*
+ * This is to keep resolve_ref() happy. We need a valid HEAD
+ * or is_git_directory() will reject the directory. Moreover, HEAD
+ * in the new worktree must resolve to the same value as HEAD in
+ * the current tree since the command invoked to populate the new
+ * worktree will be handed the branch/ref specified by the user.
+ * For instance, if the user asks for the new worktree to be based
+ * at HEAD~5, then the resolved HEAD~5 in the new worktree must
+ * match the resolved HEAD~5 in the current tree in order to match
+ * the user's expectation.
+ */
+ if (!resolve_ref_unsafe("HEAD", 0, rev, NULL))
+ die(_("unable to resolve HEAD"));
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+ write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev));
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+ write_file(sb.buf, 1, "../..\n");
+
+ fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+ setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+ setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+ setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = child_argv;
+ ret = run_command(&cp);
+ if (!ret) {
+ is_junk = 0;
+ free(junk_work_tree);
+ free(junk_git_dir);
+ junk_work_tree = NULL;
+ junk_git_dir = NULL;
+ }
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ unlink_or_warn(sb.buf);
+ strbuf_release(&sb);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+ }
+
+ static int add(int ac, const char **av, const char *prefix)
+ {
+ int force = 0, detach = 0;
+ const char *new_branch = NULL, *new_branch_force = NULL;
+ const char *path, *branch;
+ struct argv_array cmd = ARGV_ARRAY_INIT;
+ struct option options[] = {
+ OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
+ OPT_STRING('b', NULL, &new_branch, N_("branch"),
+ N_("create a new branch")),
+ OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
+ N_("create or reset a branch")),
+ OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")),
+ OPT_END()
+ };
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (new_branch && new_branch_force)
+ die(_("-b and -B are mutually exclusive"));
+ if (ac < 1 || ac > 2)
+ usage_with_options(worktree_usage, options);
+
+ path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+ branch = ac < 2 ? "HEAD" : av[1];
+
+ if (ac < 2 && !new_branch && !new_branch_force) {
+ int n;
+ const char *s = worktree_basename(path, &n);
+ new_branch = xstrndup(s, n);
+ }
+
+ argv_array_push(&cmd, "checkout");
+ if (force)
+ argv_array_push(&cmd, "--ignore-other-worktrees");
+ if (new_branch)
+ argv_array_pushl(&cmd, "-b", new_branch, NULL);
+ if (new_branch_force)
+ argv_array_pushl(&cmd, "-B", new_branch_force, NULL);
+ if (detach)
+ argv_array_push(&cmd, "--detach");
+ argv_array_push(&cmd, branch);
+
+ return add_worktree(path, cmd.argv);
+ }
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
if (ac < 2)
usage_with_options(worktree_usage, options);
+ if (!strcmp(av[1], "add"))
+ return add(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "prune"))
return prune(ac - 1, av + 1, prefix);
usage_with_options(worktree_usage, options);
#include "builtin.h"
-#include "cache.h"
#include "exec_cmd.h"
#include "help.h"
-#include "quote.h"
#include "run-command.h"
-#include "commit.h"
const char git_usage_string[] =
"git [--version] [--help] [-C <path>] [-c name=value]\n"
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
- " [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n"
+ " [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n"
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
" <command> [<args>]";
fprintf(stderr, "No directory given for -C.\n" );
usage(git_usage_string);
}
- if (chdir((*argv)[1]))
- die_errno("Cannot change to '%s'", (*argv)[1]);
- if (envchanged)
- *envchanged = 1;
+ if ((*argv)[1][0]) {
+ if (chdir((*argv)[1]))
+ die_errno("Cannot change to '%s'", (*argv)[1]);
+ if (envchanged)
+ *envchanged = 1;
+ }
(*argv)++;
(*argc)--;
} else {
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
{ "check-mailmap", cmd_check_mailmap, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "checkout", cmd_checkout, RUN_SETUP },
+ { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "cherry", cmd_cherry, RUN_SETUP },
{ "write-tree", cmd_write_tree, RUN_SETUP },
};
-int is_builtin(const char *s)
+static struct cmd_struct *get_builtin(const char *s)
{
int i;
for (i = 0; i < ARRAY_SIZE(commands); i++) {
- struct cmd_struct *p = commands+i;
+ struct cmd_struct *p = commands + i;
if (!strcmp(s, p->cmd))
- return 1;
+ return p;
}
- return 0;
+ return NULL;
+}
+
+int is_builtin(const char *s)
+{
+ return !!get_builtin(s);
}
static void handle_builtin(int argc, const char **argv)
const char *cmd = argv[0];
int i;
static const char ext[] = STRIP_EXTENSION;
+ struct cmd_struct *builtin;
if (sizeof(ext) > 1) {
i = strlen(argv[0]) - strlen(ext);
argv[0] = cmd = "help";
}
- for (i = 0; i < ARRAY_SIZE(commands); i++) {
- struct cmd_struct *p = commands+i;
- if (strcmp(p->cmd, cmd))
- continue;
- if (saved_environment && (p->option & NO_SETUP)) {
+ builtin = get_builtin(cmd);
+ if (builtin) {
+ if (saved_environment && (builtin->option & NO_SETUP))
restore_env();
- break;
- }
- exit(run_builtin(p, argc, argv));
+ else
+ exit(run_builtin(builtin, argc, argv));
}
}
{
const char **argv = (const char **) av;
const char *cmd;
+ int done_help = 0;
startup_info = &git_startup_info;
setup_path();
while (1) {
- static int done_help = 0;
- static int was_alias = 0;
- was_alias = run_argv(&argc, &argv);
+ int was_alias = run_argv(&argc, &argv);
if (errno != ENOENT)
break;
if (was_alias) {