зеркало из https://github.com/microsoft/git.git
Merge branch 'nd/multiple-work-trees'
A replacement for contrib/workdir/git-new-workdir that does not rely on symbolic links and make sharing of objects and refs safer by making the borrowee and borrowers aware of each other. * nd/multiple-work-trees: (41 commits) prune --worktrees: fix expire vs worktree existence condition t1501: fix test with split index t2026: fix broken &&-chain t2026 needs procondition SANITY git-checkout.txt: a note about multiple checkout support for submodules checkout: add --ignore-other-wortrees checkout: pass whole struct to parse_branchname_arg instead of individual flags git-common-dir: make "modules/" per-working-directory directory checkout: do not fail if target is an empty directory t2025: add a test to make sure grafts is working from a linked checkout checkout: don't require a work tree when checking out into a new one git_path(): keep "info/sparse-checkout" per work-tree count-objects: report unused files in $GIT_DIR/worktrees/... gc: support prune --worktrees gc: factor out gc.pruneexpire parsing code gc: style change -- no SP before closing parenthesis checkout: clean up half-prepared directories in --to mode checkout: reject if the branch is already checked out elsewhere prune: strategies for linked checkouts checkout: support checking out into a new working directory ...
This commit is contained in:
Коммит
68a2e6a2c8
|
@ -453,6 +453,8 @@ false), while all other repositories are assumed to be bare (bare
|
|||
|
||||
core.worktree::
|
||||
Set the path to the root of the working tree.
|
||||
If GIT_COMMON_DIR environment variable is set, core.worktree
|
||||
is ignored and not used for determining the root of working tree.
|
||||
This can be overridden by the GIT_WORK_TREE environment
|
||||
variable and the '--work-tree' command-line option.
|
||||
The value can be an absolute path or relative to the path to
|
||||
|
@ -1274,6 +1276,13 @@ gc.pruneExpire::
|
|||
"now" may be used to disable this grace period and always prune
|
||||
unreachable objects immediately.
|
||||
|
||||
gc.pruneWorktreesExpire::
|
||||
When 'git gc' is run, it will call
|
||||
'prune --worktrees --expire 3.months.ago'.
|
||||
Override the grace period with this config variable. The value
|
||||
"now" may be used to disable the grace period and prune
|
||||
$GIT_DIR/worktrees immediately.
|
||||
|
||||
gc.reflogExpire::
|
||||
gc.<pattern>.reflogExpire::
|
||||
'git reflog expire' removes reflog entries older than
|
||||
|
|
|
@ -225,6 +225,19 @@ This means that you can use `git checkout -p` to selectively discard
|
|||
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
|
||||
out anyway. In other words, the ref can be held by more than one
|
||||
worktree.
|
||||
|
||||
<branch>::
|
||||
Branch to checkout; if it refers to a branch (i.e., a name that,
|
||||
when prepended with "refs/heads/", is a valid ref), then that
|
||||
|
@ -388,6 +401,71 @@ $ git reflog -2 HEAD # or
|
|||
$ 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
|
||||
--------
|
||||
|
||||
|
|
|
@ -48,6 +48,9 @@ OPTIONS
|
|||
--expire <time>::
|
||||
Only expire loose objects older than <time>.
|
||||
|
||||
--worktrees::
|
||||
Prune dead working tree information in $GIT_DIR/worktrees.
|
||||
|
||||
<head>...::
|
||||
In addition to objects
|
||||
reachable from any of our references, keep objects
|
||||
|
|
|
@ -216,6 +216,9 @@ If `$GIT_DIR` is not defined and the current directory
|
|||
is not detected to lie in a Git repository or work tree
|
||||
print a message to stderr and exit with nonzero status.
|
||||
|
||||
--git-common-dir::
|
||||
Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
|
||||
|
||||
--is-inside-git-dir::
|
||||
When the current working directory is below the repository
|
||||
directory print "true", otherwise "false".
|
||||
|
@ -233,6 +236,13 @@ print a message to stderr and exit with nonzero status.
|
|||
repository. If <path> is a gitfile then the resolved path
|
||||
to the real repository is printed.
|
||||
|
||||
--git-path <path>::
|
||||
Resolve "$GIT_DIR/<path>" and takes other path relocation
|
||||
variables such as $GIT_OBJECT_DIRECTORY,
|
||||
$GIT_INDEX_FILE... into account. For example, if
|
||||
$GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
|
||||
--git-path objects/abc" returns /foo/bar/abc.
|
||||
|
||||
--show-cdup::
|
||||
When the command is invoked from a subdirectory, show the
|
||||
path of the top-level directory relative to the current
|
||||
|
|
|
@ -833,6 +833,15 @@ Git so take care if using Cogito etc.
|
|||
an explicit repository directory set via 'GIT_DIR' or on the
|
||||
command line.
|
||||
|
||||
'GIT_COMMON_DIR'::
|
||||
If this variable is set to a path, non-worktree files that are
|
||||
normally in $GIT_DIR will be taken from this path
|
||||
instead. Worktree-specific files such as HEAD or index are
|
||||
taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
|
||||
the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
|
||||
details. This variable has lower precedence than other path
|
||||
variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
|
||||
|
||||
Git Commits
|
||||
~~~~~~~~~~~
|
||||
'GIT_AUTHOR_NAME'::
|
||||
|
|
|
@ -46,6 +46,9 @@ of incomplete object store is not suitable to be published for
|
|||
use with dumb transports but otherwise is OK as long as
|
||||
`objects/info/alternates` points at the object stores it
|
||||
borrows from.
|
||||
+
|
||||
This directory is ignored if $GIT_COMMON_DIR is set and
|
||||
"$GIT_COMMON_DIR/objects" will be used instead.
|
||||
|
||||
objects/[0-9a-f][0-9a-f]::
|
||||
A newly created object is stored in its own file.
|
||||
|
@ -92,7 +95,8 @@ refs::
|
|||
References are stored in subdirectories of this
|
||||
directory. The 'git prune' command knows to preserve
|
||||
objects reachable from refs found in this directory and
|
||||
its subdirectories.
|
||||
its subdirectories. This directory is ignored if $GIT_COMMON_DIR
|
||||
is set and "$GIT_COMMON_DIR/refs" will be used instead.
|
||||
|
||||
refs/heads/`name`::
|
||||
records tip-of-the-tree commit objects of branch `name`
|
||||
|
@ -114,7 +118,8 @@ refs/replace/`<obj-sha1>`::
|
|||
packed-refs::
|
||||
records the same information as refs/heads/, refs/tags/,
|
||||
and friends record in a more efficient way. See
|
||||
linkgit:git-pack-refs[1].
|
||||
linkgit:git-pack-refs[1]. This file is ignored if $GIT_COMMON_DIR
|
||||
is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.
|
||||
|
||||
HEAD::
|
||||
A symref (see glossary) to the `refs/heads/` namespace
|
||||
|
@ -133,6 +138,11 @@ being a symref to point at the current branch. Such a state
|
|||
is often called 'detached HEAD.' See linkgit:git-checkout[1]
|
||||
for details.
|
||||
|
||||
config::
|
||||
Repository specific configuration file. This file is ignored
|
||||
if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
|
||||
used instead.
|
||||
|
||||
branches::
|
||||
A slightly deprecated way to store shorthands to be used
|
||||
to specify a URL to 'git fetch', 'git pull' and 'git push'.
|
||||
|
@ -140,7 +150,10 @@ branches::
|
|||
'name' can be given to these commands in place of
|
||||
'repository' argument. See the REMOTES section in
|
||||
linkgit:git-fetch[1] for details. This mechanism is legacy
|
||||
and not likely to be found in modern repositories.
|
||||
and not likely to be found in modern repositories. This
|
||||
directory is ignored if $GIT_COMMON_DIR is set and
|
||||
"$GIT_COMMON_DIR/branches" will be used instead.
|
||||
|
||||
|
||||
hooks::
|
||||
Hooks are customization scripts used by various Git
|
||||
|
@ -149,7 +162,9 @@ hooks::
|
|||
default. To enable, the `.sample` suffix has to be
|
||||
removed from the filename by renaming.
|
||||
Read linkgit:githooks[5] for more details about
|
||||
each hook.
|
||||
each hook. This directory is ignored if $GIT_COMMON_DIR is set
|
||||
and "$GIT_COMMON_DIR/hooks" will be used instead.
|
||||
|
||||
|
||||
index::
|
||||
The current index file for the repository. It is
|
||||
|
@ -161,7 +176,8 @@ sharedindex.<SHA-1>::
|
|||
|
||||
info::
|
||||
Additional information about the repository is recorded
|
||||
in this directory.
|
||||
in this directory. This directory is ignored if $GIT_COMMON_DIR
|
||||
is set and "$GIT_COMMON_DIR/index" will be used instead.
|
||||
|
||||
info/refs::
|
||||
This file helps dumb transports discover what refs are
|
||||
|
@ -201,12 +217,15 @@ remotes::
|
|||
when interacting with remote repositories via 'git fetch',
|
||||
'git pull' and 'git push' commands. See the REMOTES section
|
||||
in linkgit:git-fetch[1] for details. This mechanism is legacy
|
||||
and not likely to be found in modern repositories.
|
||||
and not likely to be found in modern repositories. This
|
||||
directory is ignored if $GIT_COMMON_DIR is set and
|
||||
"$GIT_COMMON_DIR/remotes" will be used instead.
|
||||
|
||||
logs::
|
||||
Records of changes made to refs are stored in this
|
||||
directory. See linkgit:git-update-ref[1]
|
||||
for more information.
|
||||
Records of changes made to refs are stored in this directory.
|
||||
See linkgit:git-update-ref[1] for more information. This
|
||||
directory is ignored if $GIT_COMMON_DIR is set and
|
||||
"$GIT_COMMON_DIR/logs" will be used instead.
|
||||
|
||||
logs/refs/heads/`name`::
|
||||
Records all changes made to the branch tip named `name`.
|
||||
|
@ -217,11 +236,46 @@ logs/refs/tags/`name`::
|
|||
shallow::
|
||||
This is similar to `info/grafts` but is internally used
|
||||
and maintained by shallow clone mechanism. See `--depth`
|
||||
option to linkgit:git-clone[1] and linkgit:git-fetch[1].
|
||||
option to linkgit:git-clone[1] and linkgit:git-fetch[1]. This
|
||||
file is ignored if $GIT_COMMON_DIR is set and
|
||||
"$GIT_COMMON_DIR/shallow" will be used instead.
|
||||
|
||||
commondir::
|
||||
If this file exists, $GIT_COMMON_DIR (see linkgit:git[1]) will
|
||||
be set to the path specified in this file if it is not
|
||||
explicitly set. If the specified path is relative, it is
|
||||
relative to $GIT_DIR. The repository with commondir is
|
||||
incomplete without the repository pointed by "commondir".
|
||||
|
||||
modules::
|
||||
Contains the git-repositories of the submodules.
|
||||
|
||||
worktrees::
|
||||
Contains worktree specific information of linked
|
||||
checkouts. Each subdirectory contains the worktree-related
|
||||
part of a linked checkout. This directory is ignored if
|
||||
$GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/worktrees" will be
|
||||
used instead.
|
||||
|
||||
worktrees/<id>/gitdir::
|
||||
A text file containing the absolute path back to the .git file
|
||||
that points to here. This is used to check if the linked
|
||||
repository has been manually removed and there is no need to
|
||||
keep this directory any more. mtime of this file should be
|
||||
updated every time the linked repository is accessed.
|
||||
|
||||
worktrees/<id>/locked::
|
||||
If this file exists, the linked repository may be on a
|
||||
portable device and not available. It does not mean that the
|
||||
linked repository is gone and `worktrees/<id>` could be
|
||||
removed. The file's content contains a reason string on why
|
||||
the repository is locked.
|
||||
|
||||
worktrees/<id>/link::
|
||||
If this file exists, it is a hard link to the linked .git
|
||||
file. It is used to detect if the linked repository is
|
||||
manually removed.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-init[1],
|
||||
|
|
|
@ -771,7 +771,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
|
|||
|
||||
static int edit_branch_description(const char *branch_name)
|
||||
{
|
||||
FILE *fp;
|
||||
int status;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct strbuf name = STRBUF_INIT;
|
||||
|
@ -784,8 +783,7 @@ static int edit_branch_description(const char *branch_name)
|
|||
" %s\n"
|
||||
"Lines starting with '%c' will be stripped.\n",
|
||||
branch_name, comment_line_char);
|
||||
fp = fopen(git_path(edit_description), "w");
|
||||
if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
|
||||
if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
|
||||
strbuf_release(&buf);
|
||||
return error(_("could not write branch description template: %s"),
|
||||
strerror(errno));
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#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>"),
|
||||
|
@ -36,6 +37,7 @@ struct checkout_opts {
|
|||
int writeout_stage;
|
||||
int overwrite_ignore;
|
||||
int ignore_skipworktree;
|
||||
int ignore_other_worktrees;
|
||||
|
||||
const char *new_branch;
|
||||
const char *new_branch_force;
|
||||
|
@ -48,6 +50,10 @@ struct checkout_opts {
|
|||
const char *prefix;
|
||||
struct pathspec pathspec;
|
||||
struct tree *source_tree;
|
||||
|
||||
const char *new_worktree;
|
||||
const char **saved_argv;
|
||||
int new_worktree_mode;
|
||||
};
|
||||
|
||||
static int post_checkout_hook(struct commit *old, struct commit *new,
|
||||
|
@ -267,6 +273,9 @@ static int checkout_paths(const struct checkout_opts *opts,
|
|||
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);
|
||||
|
@ -441,6 +450,11 @@ struct branch_info {
|
|||
const char *name; /* The short name used */
|
||||
const char *path; /* The full name of a real branch */
|
||||
struct commit *commit; /* The named commit */
|
||||
/*
|
||||
* if not null the branch is detached because it's already
|
||||
* checked out in this checkout
|
||||
*/
|
||||
char *checkout;
|
||||
};
|
||||
|
||||
static void setup_branch_path(struct branch_info *branch)
|
||||
|
@ -502,7 +516,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
|
|||
topts.dir->flags |= DIR_SHOW_IGNORED;
|
||||
setup_standard_excludes(topts.dir);
|
||||
}
|
||||
tree = parse_tree_indirect(old->commit ?
|
||||
tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
|
||||
old->commit->object.sha1 :
|
||||
EMPTY_TREE_SHA1_BIN);
|
||||
init_tree_desc(&trees[0], tree->buffer, tree->size);
|
||||
|
@ -606,18 +620,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
|
|||
if (opts->new_orphan_branch) {
|
||||
if (opts->new_branch_log && !log_all_ref_updates) {
|
||||
int temp;
|
||||
char log_file[PATH_MAX];
|
||||
char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
|
||||
struct strbuf log_file = STRBUF_INIT;
|
||||
int ret;
|
||||
const char *ref_name;
|
||||
|
||||
ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
|
||||
temp = log_all_ref_updates;
|
||||
log_all_ref_updates = 1;
|
||||
if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
|
||||
ret = log_ref_setup(ref_name, &log_file);
|
||||
log_all_ref_updates = temp;
|
||||
strbuf_release(&log_file);
|
||||
if (ret) {
|
||||
fprintf(stderr, _("Can not do reflog for '%s'\n"),
|
||||
opts->new_orphan_branch);
|
||||
log_all_ref_updates = temp;
|
||||
return;
|
||||
}
|
||||
log_all_ref_updates = temp;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -822,7 +839,8 @@ static int switch_branches(const struct checkout_opts *opts,
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
|
||||
if (!opts->quiet && !old.path && old.commit &&
|
||||
new->commit != old.commit && !opts->new_worktree_mode)
|
||||
orphaned_commit_warning(old.commit, new->commit);
|
||||
|
||||
update_refs_for_switch(opts, &old, new);
|
||||
|
@ -832,6 +850,138 @@ static int switch_branches(const struct checkout_opts *opts,
|
|||
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")) {
|
||||
|
@ -887,13 +1037,80 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void check_linked_checkout(struct branch_info *new, const char *id)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct strbuf gitdir = STRBUF_INIT;
|
||||
const char *start, *end;
|
||||
|
||||
if (id)
|
||||
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
|
||||
else
|
||||
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
|
||||
|
||||
if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
|
||||
!skip_prefix(sb.buf, "ref:", &start))
|
||||
goto done;
|
||||
while (isspace(*start))
|
||||
start++;
|
||||
end = start;
|
||||
while (*end && !isspace(*end))
|
||||
end++;
|
||||
if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
|
||||
goto done;
|
||||
if (id) {
|
||||
strbuf_reset(&path);
|
||||
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
|
||||
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
|
||||
goto done;
|
||||
strbuf_rtrim(&gitdir);
|
||||
} else
|
||||
strbuf_addstr(&gitdir, get_git_common_dir());
|
||||
die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
|
||||
done:
|
||||
strbuf_release(&path);
|
||||
strbuf_release(&sb);
|
||||
strbuf_release(&gitdir);
|
||||
}
|
||||
|
||||
static void check_linked_checkouts(struct branch_info *new)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
DIR *dir;
|
||||
struct dirent *d;
|
||||
|
||||
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
||||
if ((dir = opendir(path.buf)) == NULL) {
|
||||
strbuf_release(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* $GIT_COMMON_DIR/HEAD is practically outside
|
||||
* $GIT_DIR so resolve_ref_unsafe() won't work (it
|
||||
* uses git_path). Parse the ref ourselves.
|
||||
*/
|
||||
check_linked_checkout(new, NULL);
|
||||
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
check_linked_checkout(new, d->d_name);
|
||||
}
|
||||
strbuf_release(&path);
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static int parse_branchname_arg(int argc, const char **argv,
|
||||
int dwim_new_local_branch_ok,
|
||||
struct branch_info *new,
|
||||
struct tree **source_tree,
|
||||
unsigned char rev[20],
|
||||
const char **new_branch)
|
||||
struct checkout_opts *opts,
|
||||
unsigned char rev[20])
|
||||
{
|
||||
struct tree **source_tree = &opts->source_tree;
|
||||
const char **new_branch = &opts->new_branch;
|
||||
int force_detach = opts->force_detach;
|
||||
int argcount = 0;
|
||||
unsigned char branch_rev[20];
|
||||
const char *arg;
|
||||
|
@ -1014,6 +1231,17 @@ static int parse_branchname_arg(int argc, const char **argv,
|
|||
else
|
||||
new->path = NULL; /* not an existing branch */
|
||||
|
||||
if (new->path && !force_detach && !*new_branch) {
|
||||
unsigned char sha1[20];
|
||||
int flag;
|
||||
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
|
||||
if (head_ref &&
|
||||
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
|
||||
!opts->ignore_other_worktrees)
|
||||
check_linked_checkouts(new);
|
||||
free(head_ref);
|
||||
}
|
||||
|
||||
new->commit = lookup_commit_reference_gently(rev, 1);
|
||||
if (!new->commit) {
|
||||
/* not a commit */
|
||||
|
@ -1093,6 +1321,9 @@ static int checkout_branch(struct checkout_opts *opts,
|
|||
die(_("Cannot switch branch to a non-commit '%s'"),
|
||||
new->name);
|
||||
|
||||
if (opts->new_worktree)
|
||||
return prepare_linked_checkout(opts, new);
|
||||
|
||||
if (!new->commit && opts->new_branch) {
|
||||
unsigned char rev[20];
|
||||
int flag;
|
||||
|
@ -1135,6 +1366,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
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>'")),
|
||||
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(),
|
||||
};
|
||||
|
||||
|
@ -1143,6 +1378,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
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);
|
||||
|
||||
|
@ -1151,6 +1389,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
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 */
|
||||
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
|
||||
|
@ -1204,8 +1450,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
opts.track == BRANCH_TRACK_UNSPECIFIED &&
|
||||
!opts.new_branch;
|
||||
int n = parse_branchname_arg(argc, argv, dwim_ok,
|
||||
&new, &opts.source_tree,
|
||||
rev, &opts.new_branch);
|
||||
&new, &opts, rev);
|
||||
argv += n;
|
||||
argc -= n;
|
||||
}
|
||||
|
|
|
@ -293,16 +293,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
|
|||
struct strbuf line = STRBUF_INIT;
|
||||
|
||||
while (strbuf_getline(&line, in, '\n') != EOF) {
|
||||
char *abs_path, abs_buf[PATH_MAX];
|
||||
char *abs_path;
|
||||
if (!line.len || line.buf[0] == '#')
|
||||
continue;
|
||||
if (is_absolute_path(line.buf)) {
|
||||
add_to_alternates_file(line.buf);
|
||||
continue;
|
||||
}
|
||||
abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
|
||||
normalize_path_copy(abs_buf, abs_path);
|
||||
add_to_alternates_file(abs_buf);
|
||||
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
|
||||
normalize_path_copy(abs_path, abs_path);
|
||||
add_to_alternates_file(abs_path);
|
||||
free(abs_path);
|
||||
}
|
||||
strbuf_release(&line);
|
||||
fclose(in);
|
||||
|
|
|
@ -170,7 +170,7 @@ static void determine_whence(struct wt_status *s)
|
|||
whence = FROM_MERGE;
|
||||
else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
|
||||
whence = FROM_CHERRY_PICK;
|
||||
if (file_exists(git_path("sequencer")))
|
||||
if (file_exists(git_path(SEQ_DIR)))
|
||||
sequencer_in_use = 1;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -70,8 +70,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
|
|||
/* we do not take arguments other than flags for now */
|
||||
if (argc)
|
||||
usage_with_options(count_objects_usage, opts);
|
||||
if (verbose)
|
||||
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);
|
||||
|
|
|
@ -588,7 +588,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
|
|||
struct strbuf note = STRBUF_INIT;
|
||||
const char *what, *kind;
|
||||
struct ref *rm;
|
||||
char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
|
||||
char *url;
|
||||
const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
|
||||
int want_status;
|
||||
|
||||
fp = fopen(filename, "a");
|
||||
|
@ -822,7 +823,7 @@ static void check_not_current_branch(struct ref *ref_map)
|
|||
|
||||
static int truncate_fetch_head(void)
|
||||
{
|
||||
char *filename = git_path("FETCH_HEAD");
|
||||
const char *filename = git_path("FETCH_HEAD");
|
||||
FILE *fp = fopen(filename, "w");
|
||||
|
||||
if (!fp)
|
||||
|
|
|
@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj)
|
|||
printf("dangling %s %s\n", typename(obj->type),
|
||||
sha1_to_hex(obj->sha1));
|
||||
if (write_lost_and_found) {
|
||||
char *filename = git_path("lost-found/%s/%s",
|
||||
const char *filename = git_path("lost-found/%s/%s",
|
||||
obj->type == OBJ_COMMIT ? "commit" : "other",
|
||||
sha1_to_hex(obj->sha1));
|
||||
FILE *f;
|
||||
|
||||
if (safe_create_leading_directories(filename)) {
|
||||
if (safe_create_leading_directories_const(filename)) {
|
||||
error("Could not create lost-found");
|
||||
return;
|
||||
}
|
||||
|
|
34
builtin/gc.c
34
builtin/gc.c
|
@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700;
|
|||
static int gc_auto_pack_limit = 50;
|
||||
static int detach_auto = 1;
|
||||
static const char *prune_expire = "2.weeks.ago";
|
||||
static const char *prune_worktrees_expire = "3.months.ago";
|
||||
|
||||
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 char *pidfile;
|
||||
|
@ -55,6 +57,17 @@ static void remove_pidfile_on_signal(int signo)
|
|||
raise(signo);
|
||||
}
|
||||
|
||||
static void git_config_date_string(const char *key, const char **output)
|
||||
{
|
||||
if (git_config_get_string_const(key, output))
|
||||
return;
|
||||
if (strcmp(*output, "now")) {
|
||||
unsigned long now = approxidate("now");
|
||||
if (approxidate(*output) >= now)
|
||||
git_die_config(key, _("Invalid %s: '%s'"), key, *output);
|
||||
}
|
||||
}
|
||||
|
||||
static void gc_config(void)
|
||||
{
|
||||
const char *value;
|
||||
|
@ -71,16 +84,8 @@ static void gc_config(void)
|
|||
git_config_get_int("gc.auto", &gc_auto_threshold);
|
||||
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
|
||||
git_config_get_bool("gc.autodetach", &detach_auto);
|
||||
|
||||
if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) {
|
||||
if (strcmp(prune_expire, "now")) {
|
||||
unsigned long now = approxidate("now");
|
||||
if (approxidate(prune_expire) >= now) {
|
||||
git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"),
|
||||
prune_expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
git_config_date_string("gc.pruneexpire", &prune_expire);
|
||||
git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
|
||||
git_config(git_default_config, NULL);
|
||||
}
|
||||
|
||||
|
@ -287,7 +292,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
|
|||
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, "prune", "--expire", NULL);
|
||||
argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
|
||||
argv_array_pushl(&rerere, "rerere", "gc", NULL);
|
||||
|
||||
gc_config();
|
||||
|
@ -357,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
|
|||
return error(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))
|
||||
return error(FAILED_RUN, prune_worktrees.argv[0]);
|
||||
}
|
||||
|
||||
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
|
||||
return error(FAILED_RUN, rerere.argv[0]);
|
||||
|
||||
|
|
|
@ -362,7 +362,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
|
|||
static void separate_git_dir(const char *git_dir)
|
||||
{
|
||||
struct stat st;
|
||||
FILE *fp;
|
||||
|
||||
if (!stat(git_link, &st)) {
|
||||
const char *src;
|
||||
|
@ -378,11 +377,7 @@ static void separate_git_dir(const char *git_dir)
|
|||
die_errno(_("unable to move %s to %s"), src, git_dir);
|
||||
}
|
||||
|
||||
fp = fopen(git_link, "w");
|
||||
if (!fp)
|
||||
die(_("Could not create git link %s"), git_link);
|
||||
fprintf(fp, "gitdir: %s\n", git_dir);
|
||||
fclose(fp);
|
||||
write_file(git_link, 1, "gitdir: %s\n", git_dir);
|
||||
}
|
||||
|
||||
int init_db(const char *template_dir, unsigned int flags)
|
||||
|
|
|
@ -76,6 +76,95 @@ static int prune_subdir(int nr, const char *path, void *data)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int prune_worktree(const char *id, struct strbuf *reason)
|
||||
{
|
||||
struct stat st;
|
||||
char *path;
|
||||
int fd, len;
|
||||
|
||||
if (!is_directory(git_path("worktrees/%s", id))) {
|
||||
strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
|
||||
return 1;
|
||||
}
|
||||
if (file_exists(git_path("worktrees/%s/locked", id)))
|
||||
return 0;
|
||||
if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
|
||||
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
|
||||
return 1;
|
||||
}
|
||||
fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
|
||||
id, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
len = st.st_size;
|
||||
path = xmalloc(len + 1);
|
||||
read_in_full(fd, path, len);
|
||||
close(fd);
|
||||
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
|
||||
len--;
|
||||
if (!len) {
|
||||
strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
|
||||
free(path);
|
||||
return 1;
|
||||
}
|
||||
path[len] = '\0';
|
||||
if (!file_exists(path)) {
|
||||
struct stat st_link;
|
||||
free(path);
|
||||
/*
|
||||
* the repo is moved manually and has not been
|
||||
* accessed since?
|
||||
*/
|
||||
if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
|
||||
st_link.st_nlink > 1)
|
||||
return 0;
|
||||
if (st.st_mtime <= expire) {
|
||||
strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void prune_worktrees(void)
|
||||
{
|
||||
struct strbuf reason = STRBUF_INIT;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
DIR *dir = opendir(git_path("worktrees"));
|
||||
struct dirent *d;
|
||||
int ret;
|
||||
if (!dir)
|
||||
return;
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
strbuf_reset(&reason);
|
||||
if (!prune_worktree(d->d_name, &reason))
|
||||
continue;
|
||||
if (show_only || verbose)
|
||||
printf("%s\n", reason.buf);
|
||||
if (show_only)
|
||||
continue;
|
||||
strbuf_reset(&path);
|
||||
strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
|
||||
ret = remove_dir_recursively(&path, 0);
|
||||
if (ret < 0 && errno == ENOTDIR)
|
||||
ret = unlink(path.buf);
|
||||
if (ret)
|
||||
error(_("failed to remove: %s"), strerror(errno));
|
||||
}
|
||||
closedir(dir);
|
||||
if (!show_only)
|
||||
rmdir(git_path("worktrees"));
|
||||
strbuf_release(&reason);
|
||||
strbuf_release(&path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write errors (particularly out of space) can result in
|
||||
* failed temporary packs (and more rarely indexes and other
|
||||
|
@ -102,10 +191,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
|
|||
{
|
||||
struct rev_info revs;
|
||||
struct progress *progress = NULL;
|
||||
int do_prune_worktrees = 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_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
|
||||
OPT_EXPIRY_DATE(0, "expire", &expire,
|
||||
N_("expire objects older than <time>")),
|
||||
OPT_END()
|
||||
|
@ -119,6 +210,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
|
|||
init_revisions(&revs, prefix);
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
|
||||
|
||||
if (do_prune_worktrees) {
|
||||
if (argc)
|
||||
die(_("--worktrees does not take extra arguments"));
|
||||
prune_worktrees();
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (argc--) {
|
||||
unsigned char sha1[20];
|
||||
const char *name = *argv++;
|
||||
|
|
|
@ -1008,7 +1008,7 @@ static void run_update_post_hook(struct command *commands)
|
|||
int argc;
|
||||
const char **argv;
|
||||
struct child_process proc = CHILD_PROCESS_INIT;
|
||||
char *hook;
|
||||
const char *hook;
|
||||
|
||||
hook = find_hook("post-update");
|
||||
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
|
||||
|
|
|
@ -584,7 +584,7 @@ static int migrate_file(struct remote *remote)
|
|||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int i;
|
||||
char *path = NULL;
|
||||
const char *path = NULL;
|
||||
|
||||
strbuf_addf(&buf, "remote.%s.url", remote->name);
|
||||
for (i = 0; i < remote->url_nr; i++)
|
||||
|
|
|
@ -285,7 +285,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
|
|||
failed = 0;
|
||||
for_each_string_list_item(item, &names) {
|
||||
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
|
||||
char *fname, *fname_old;
|
||||
const char *fname_old;
|
||||
char *fname;
|
||||
fname = mkpathdup("%s/pack-%s%s", packdir,
|
||||
item->string, exts[ext].name);
|
||||
if (!file_exists(fname)) {
|
||||
|
@ -313,7 +314,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
|
|||
if (failed) {
|
||||
struct string_list rollback_failure = STRING_LIST_INIT_DUP;
|
||||
for_each_string_list_item(item, &rollback) {
|
||||
char *fname, *fname_old;
|
||||
const char *fname_old;
|
||||
char *fname;
|
||||
fname = mkpathdup("%s/%s", packdir, item->string);
|
||||
fname_old = mkpath("%s/old-%s", packdir, item->string);
|
||||
if (rename(fname_old, fname))
|
||||
|
@ -366,7 +368,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
|
|||
/* Remove the "old-" files */
|
||||
for_each_string_list_item(item, &names) {
|
||||
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
|
||||
char *fname;
|
||||
const char *fname;
|
||||
fname = mkpath("%s/old-%s%s",
|
||||
packdir,
|
||||
item->string,
|
||||
|
|
|
@ -533,6 +533,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
|
|||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (!strcmp(arg, "--git-path")) {
|
||||
if (!argv[i + 1])
|
||||
die("--git-path requires an argument");
|
||||
puts(git_path("%s", argv[i + 1]));
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (as_is) {
|
||||
if (show_file(arg, output_prefix) && as_is < 2)
|
||||
verify_filename(prefix, arg, 0);
|
||||
|
@ -755,6 +762,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
|
|||
free(cwd);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--git-common-dir")) {
|
||||
puts(get_git_common_dir());
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--resolve-git-dir")) {
|
||||
const char *gitdir = argv[++i];
|
||||
if (!gitdir)
|
||||
|
|
17
cache.h
17
cache.h
|
@ -378,6 +378,7 @@ static inline enum object_type object_type(unsigned int mode)
|
|||
|
||||
/* Double-check local_repo_env below if you add to this list. */
|
||||
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
|
||||
#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
|
||||
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
|
||||
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
|
||||
#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
|
||||
|
@ -431,11 +432,13 @@ extern int is_inside_git_dir(void);
|
|||
extern char *git_work_tree_cfg;
|
||||
extern int is_inside_work_tree(void);
|
||||
extern const char *get_git_dir(void);
|
||||
extern const char *get_git_common_dir(void);
|
||||
extern int is_git_directory(const char *path);
|
||||
extern char *get_object_directory(void);
|
||||
extern char *get_index_file(void);
|
||||
extern char *get_graft_file(void);
|
||||
extern int set_git_dir(const char *path);
|
||||
extern int get_common_dir(struct strbuf *sb, const char *gitdir);
|
||||
extern const char *get_git_namespace(void);
|
||||
extern const char *strip_namespace(const char *namespaced_ref);
|
||||
extern const char *get_git_work_tree(void);
|
||||
|
@ -620,6 +623,7 @@ extern int core_apply_sparse_checkout;
|
|||
extern int precomposed_unicode;
|
||||
extern int protect_hfs;
|
||||
extern int protect_ntfs;
|
||||
extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
|
||||
|
||||
/*
|
||||
* Include broken refs in all ref iterations, which will
|
||||
|
@ -690,18 +694,19 @@ extern int check_repository_format(void);
|
|||
|
||||
extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
||||
__attribute__((format (printf, 3, 4)));
|
||||
extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
|
||||
__attribute__((format (printf, 3, 4)));
|
||||
extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
extern char *git_pathdup(const char *fmt, ...)
|
||||
__attribute__((format (printf, 1, 2)));
|
||||
extern char *mkpathdup(const char *fmt, ...)
|
||||
__attribute__((format (printf, 1, 2)));
|
||||
|
||||
/* Return a statically allocated filename matching the sha1 signature */
|
||||
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern const char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
extern void report_linked_checkout_garbage(void);
|
||||
|
||||
/*
|
||||
* Return the name of the file in the local object database that would
|
||||
|
@ -1543,6 +1548,8 @@ static inline ssize_t write_str_in_full(int fd, const char *str)
|
|||
{
|
||||
return write_in_full(fd, str, strlen(str));
|
||||
}
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
extern int write_file(const char *path, int fatal, const char *fmt, ...);
|
||||
|
||||
/* pager.c */
|
||||
extern void setup_pager(void);
|
||||
|
|
11
daemon.c
11
daemon.c
|
@ -1166,15 +1166,6 @@ static struct credentials *prepare_credentials(const char *user_name,
|
|||
}
|
||||
#endif
|
||||
|
||||
static void store_pid(const char *path)
|
||||
{
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f)
|
||||
die_errno("cannot open pid file '%s'", path);
|
||||
if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
|
||||
die_errno("failed to write pid file '%s'", path);
|
||||
}
|
||||
|
||||
static int serve(struct string_list *listen_addr, int listen_port,
|
||||
struct credentials *cred)
|
||||
{
|
||||
|
@ -1385,7 +1376,7 @@ int main(int argc, char **argv)
|
|||
sanitize_stdfds();
|
||||
|
||||
if (pid_file)
|
||||
store_pid(pid_file);
|
||||
write_file(pid_file, 1, "%"PRIuMAX"\n", (uintmax_t) getpid());
|
||||
|
||||
/* prepare argv for serving-processes */
|
||||
cld_argv = xmalloc(sizeof (char *) * (argc + 2));
|
||||
|
|
|
@ -92,8 +92,9 @@ static char *work_tree;
|
|||
static const char *namespace;
|
||||
static size_t namespace_len;
|
||||
|
||||
static const char *git_dir;
|
||||
static const char *git_dir, *git_common_dir;
|
||||
static char *git_object_dir, *git_index_file, *git_graft_file;
|
||||
int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
|
||||
|
||||
/*
|
||||
* Repository-local GIT_* environment variables; see cache.h for details.
|
||||
|
@ -111,6 +112,7 @@ const char * const local_repo_env[] = {
|
|||
NO_REPLACE_OBJECTS_ENVIRONMENT,
|
||||
GIT_PREFIX_ENVIRONMENT,
|
||||
GIT_SHALLOW_FILE_ENVIRONMENT,
|
||||
GIT_COMMON_DIR_ENVIRONMENT,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -135,14 +137,23 @@ static char *expand_namespace(const char *raw_namespace)
|
|||
return strbuf_detach(&buf, NULL);
|
||||
}
|
||||
|
||||
static char *git_path_from_env(const char *envvar, const char *path)
|
||||
static char *git_path_from_env(const char *envvar, const char *git_dir,
|
||||
const char *path, int *fromenv)
|
||||
{
|
||||
const char *value = getenv(envvar);
|
||||
return value ? xstrdup(value) : git_pathdup("%s", path);
|
||||
if (!value) {
|
||||
char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);
|
||||
sprintf(buf, "%s/%s", git_dir, path);
|
||||
return buf;
|
||||
}
|
||||
if (fromenv)
|
||||
*fromenv = 1;
|
||||
return xstrdup(value);
|
||||
}
|
||||
|
||||
static void setup_git_env(void)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *gitfile;
|
||||
const char *shallow_file;
|
||||
|
||||
|
@ -151,9 +162,15 @@ static void setup_git_env(void)
|
|||
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
|
||||
gitfile = read_gitfile(git_dir);
|
||||
git_dir = xstrdup(gitfile ? gitfile : git_dir);
|
||||
git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects");
|
||||
git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index");
|
||||
git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts");
|
||||
if (get_common_dir(&sb, git_dir))
|
||||
git_common_dir_env = 1;
|
||||
git_common_dir = strbuf_detach(&sb, NULL);
|
||||
git_object_dir = git_path_from_env(DB_ENVIRONMENT, git_common_dir,
|
||||
"objects", &git_db_env);
|
||||
git_index_file = git_path_from_env(INDEX_ENVIRONMENT, git_dir,
|
||||
"index", &git_index_env);
|
||||
git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, git_common_dir,
|
||||
"info/grafts", &git_graft_env);
|
||||
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
|
||||
check_replace_refs = 0;
|
||||
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
|
||||
|
@ -176,6 +193,11 @@ const char *get_git_dir(void)
|
|||
return git_dir;
|
||||
}
|
||||
|
||||
const char *get_git_common_dir(void)
|
||||
{
|
||||
return git_common_dir;
|
||||
}
|
||||
|
||||
const char *get_git_namespace(void)
|
||||
{
|
||||
if (!namespace)
|
||||
|
|
|
@ -405,7 +405,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
|
|||
|
||||
static void write_crash_report(const char *err)
|
||||
{
|
||||
char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
|
||||
const char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
|
||||
FILE *rpt = fopen(loc, "w");
|
||||
struct branch *b;
|
||||
unsigned long lu;
|
||||
|
@ -3113,12 +3113,9 @@ static void parse_progress(void)
|
|||
|
||||
static char* make_fast_import_path(const char *path)
|
||||
{
|
||||
struct strbuf abs_path = STRBUF_INIT;
|
||||
|
||||
if (!relative_marks_paths || is_absolute_path(path))
|
||||
return xstrdup(path);
|
||||
strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
|
||||
return strbuf_detach(&abs_path, NULL);
|
||||
return xstrdup(git_path("info/fast-import/%s", path));
|
||||
}
|
||||
|
||||
static void option_import_marks(const char *marks,
|
||||
|
|
22
git-am.sh
22
git-am.sh
|
@ -827,10 +827,10 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"."
|
|||
continue
|
||||
fi
|
||||
|
||||
if test -x "$GIT_DIR"/hooks/applypatch-msg
|
||||
hook="$(git rev-parse --git-path hooks/applypatch-msg)"
|
||||
if test -x "$hook"
|
||||
then
|
||||
"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
|
||||
stop_here $this
|
||||
"$hook" "$dotest/final-commit" || stop_here $this
|
||||
fi
|
||||
|
||||
if test -f "$dotest/final-commit"
|
||||
|
@ -904,9 +904,10 @@ did you forget to use 'git add'?"
|
|||
stop_here_user_resolve $this
|
||||
fi
|
||||
|
||||
if test -x "$GIT_DIR"/hooks/pre-applypatch
|
||||
hook="$(git rev-parse --git-path hooks/pre-applypatch)"
|
||||
if test -x "$hook"
|
||||
then
|
||||
"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
|
||||
"$hook" || stop_here $this
|
||||
fi
|
||||
|
||||
tree=$(git write-tree) &&
|
||||
|
@ -933,18 +934,17 @@ did you forget to use 'git add'?"
|
|||
echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
|
||||
fi
|
||||
|
||||
if test -x "$GIT_DIR"/hooks/post-applypatch
|
||||
then
|
||||
"$GIT_DIR"/hooks/post-applypatch
|
||||
fi
|
||||
hook="$(git rev-parse --git-path hooks/post-applypatch)"
|
||||
test -x "$hook" && "$hook"
|
||||
|
||||
go_next
|
||||
done
|
||||
|
||||
if test -s "$dotest"/rewritten; then
|
||||
git notes copy --for-rewrite=rebase < "$dotest"/rewritten
|
||||
if test -x "$GIT_DIR"/hooks/post-rewrite; then
|
||||
"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
|
||||
hook="$(git rev-parse --git-path hooks/post-rewrite)"
|
||||
if test -x "$hook"; then
|
||||
"$hook" rebase < "$dotest"/rewritten
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
|
@ -240,7 +240,7 @@ test true = "$rebase" && {
|
|||
if ! git rev-parse -q --verify HEAD >/dev/null
|
||||
then
|
||||
# On an unborn branch
|
||||
if test -f "$GIT_DIR/index"
|
||||
if test -f "$(git rev-parse --git-path index)"
|
||||
then
|
||||
die "$(gettext "updating an unborn branch with changes added to the index")"
|
||||
fi
|
||||
|
|
|
@ -642,9 +642,9 @@ do_next () {
|
|||
git notes copy --for-rewrite=rebase < "$rewritten_list" ||
|
||||
true # we don't care if this copying failed
|
||||
} &&
|
||||
if test -x "$GIT_DIR"/hooks/post-rewrite &&
|
||||
test -s "$rewritten_list"; then
|
||||
"$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
|
||||
hook="$(git rev-parse --git-path hooks/post-rewrite)"
|
||||
if test -x "$hook" && test -s "$rewritten_list"; then
|
||||
"$hook" rebase < "$rewritten_list"
|
||||
true # we don't care if this hook failed
|
||||
fi &&
|
||||
warn "Successfully rebased and updated $head_name."
|
||||
|
|
|
@ -94,10 +94,8 @@ finish_rb_merge () {
|
|||
if test -s "$state_dir"/rewritten
|
||||
then
|
||||
git notes copy --for-rewrite=rebase <"$state_dir"/rewritten
|
||||
if test -x "$GIT_DIR"/hooks/post-rewrite
|
||||
then
|
||||
"$GIT_DIR"/hooks/post-rewrite rebase <"$state_dir"/rewritten
|
||||
fi
|
||||
hook="$(git rev-parse --git-path hooks/post-rewrite)"
|
||||
test -x "$hook" && "$hook" rebase <"$state_dir"/rewritten
|
||||
fi
|
||||
say All done.
|
||||
}
|
||||
|
|
|
@ -202,9 +202,9 @@ run_specific_rebase () {
|
|||
|
||||
run_pre_rebase_hook () {
|
||||
if test -z "$ok_to_skip_pre_rebase" &&
|
||||
test -x "$GIT_DIR/hooks/pre-rebase"
|
||||
test -x "$(git rev-parse --git-path hooks/pre-rebase)"
|
||||
then
|
||||
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
|
||||
"$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
|
||||
die "$(gettext "The pre-rebase hook refused to rebase.")"
|
||||
fi
|
||||
}
|
||||
|
|
|
@ -344,7 +344,7 @@ git_dir_init () {
|
|||
echo >&2 "Unable to determine absolute path of git directory"
|
||||
exit 1
|
||||
}
|
||||
: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
|
||||
: ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
|
||||
}
|
||||
|
||||
if test -z "$NONGIT_OK"
|
||||
|
|
|
@ -20,7 +20,7 @@ require_work_tree
|
|||
cd_to_toplevel
|
||||
|
||||
TMP="$GIT_DIR/.git-stash.$$"
|
||||
TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
|
||||
TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
|
||||
trap 'rm -f "$TMP-"* "$TMPindex"' 0
|
||||
|
||||
ref_stash=refs/stash
|
||||
|
@ -184,7 +184,7 @@ store_stash () {
|
|||
fi
|
||||
|
||||
# Make sure the reflog for stash is kept.
|
||||
: >>"$GIT_DIR/logs/$ref_stash"
|
||||
: >>"$(git rev-parse --git-path logs/$ref_stash)"
|
||||
git update-ref -m "$stash_msg" $ref_stash $w_commit
|
||||
ret=$?
|
||||
test $ret != 0 && test -z $quiet &&
|
||||
|
@ -259,7 +259,7 @@ save_stash () {
|
|||
say "$(gettext "No local changes to save")"
|
||||
exit 0
|
||||
fi
|
||||
test -f "$GIT_DIR/logs/$ref_stash" ||
|
||||
test -f "$(git rev-parse --git-path logs/$ref_stash)" ||
|
||||
clear_stash || die "$(gettext "Cannot initialize stash")"
|
||||
|
||||
create_stash "$stash_msg" $untracked
|
||||
|
|
2
git.c
2
git.c
|
@ -382,7 +382,7 @@ static struct cmd_struct commands[] = {
|
|||
{ "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 | NEED_WORK_TREE },
|
||||
{ "checkout", cmd_checkout, RUN_SETUP },
|
||||
{ "checkout-index", cmd_checkout_index,
|
||||
RUN_SETUP | NEED_WORK_TREE},
|
||||
{ "cherry", cmd_cherry, RUN_SETUP },
|
||||
|
|
|
@ -280,7 +280,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
|
|||
"(%s exists).", git_path("NOTES_MERGE_*"));
|
||||
}
|
||||
|
||||
if (safe_create_leading_directories(git_path(
|
||||
if (safe_create_leading_directories_const(git_path(
|
||||
NOTES_MERGE_WORKTREE "/.test")))
|
||||
die_errno("unable to create directory %s",
|
||||
git_path(NOTES_MERGE_WORKTREE));
|
||||
|
@ -295,8 +295,8 @@ static void write_buf_to_worktree(const unsigned char *obj,
|
|||
const char *buf, unsigned long size)
|
||||
{
|
||||
int fd;
|
||||
char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
|
||||
if (safe_create_leading_directories(path))
|
||||
const char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
|
||||
if (safe_create_leading_directories_const(path))
|
||||
die_errno("unable to create directory for '%s'", path);
|
||||
if (file_exists(path))
|
||||
die("found existing file at '%s'", path);
|
||||
|
|
240
path.c
240
path.c
|
@ -4,6 +4,7 @@
|
|||
#include "cache.h"
|
||||
#include "strbuf.h"
|
||||
#include "string-list.h"
|
||||
#include "dir.h"
|
||||
|
||||
static int get_st_mode_bits(const char *path, int *mode)
|
||||
{
|
||||
|
@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode)
|
|||
|
||||
static char bad_path[] = "/bad-path/";
|
||||
|
||||
static char *get_pathname(void)
|
||||
static struct strbuf *get_pathname(void)
|
||||
{
|
||||
static char pathname_array[4][PATH_MAX];
|
||||
static struct strbuf pathname_array[4] = {
|
||||
STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
|
||||
};
|
||||
static int index;
|
||||
return pathname_array[3 & ++index];
|
||||
struct strbuf *sb = &pathname_array[3 & ++index];
|
||||
strbuf_reset(sb);
|
||||
return sb;
|
||||
}
|
||||
|
||||
static char *cleanup_path(char *path)
|
||||
|
@ -34,6 +39,13 @@ static char *cleanup_path(char *path)
|
|||
return path;
|
||||
}
|
||||
|
||||
static void strbuf_cleanup_path(struct strbuf *sb)
|
||||
{
|
||||
char *path = cleanup_path(sb->buf);
|
||||
if (path > sb->buf)
|
||||
strbuf_remove(sb, 0, path - sb->buf);
|
||||
}
|
||||
|
||||
char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
@ -49,85 +61,167 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
|||
return cleanup_path(buf);
|
||||
}
|
||||
|
||||
static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
|
||||
static int dir_prefix(const char *buf, const char *dir)
|
||||
{
|
||||
const char *git_dir = get_git_dir();
|
||||
size_t len;
|
||||
|
||||
len = strlen(git_dir);
|
||||
if (n < len + 1)
|
||||
goto bad;
|
||||
memcpy(buf, git_dir, len);
|
||||
if (len && !is_dir_sep(git_dir[len-1]))
|
||||
buf[len++] = '/';
|
||||
len += vsnprintf(buf + len, n - len, fmt, args);
|
||||
if (len >= n)
|
||||
goto bad;
|
||||
return cleanup_path(buf);
|
||||
bad:
|
||||
strlcpy(buf, bad_path, n);
|
||||
return buf;
|
||||
int len = strlen(dir);
|
||||
return !strncmp(buf, dir, len) &&
|
||||
(is_dir_sep(buf[len]) || buf[len] == '\0');
|
||||
}
|
||||
|
||||
char *git_snpath(char *buf, size_t n, const char *fmt, ...)
|
||||
/* $buf =~ m|$dir/+$file| but without regex */
|
||||
static int is_dir_file(const char *buf, const char *dir, const char *file)
|
||||
{
|
||||
int len = strlen(dir);
|
||||
if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
|
||||
return 0;
|
||||
while (is_dir_sep(buf[len]))
|
||||
len++;
|
||||
return !strcmp(buf + len, file);
|
||||
}
|
||||
|
||||
static void replace_dir(struct strbuf *buf, int len, const char *newdir)
|
||||
{
|
||||
int newlen = strlen(newdir);
|
||||
int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
|
||||
!is_dir_sep(newdir[newlen - 1]);
|
||||
if (need_sep)
|
||||
len--; /* keep one char, to be replaced with '/' */
|
||||
strbuf_splice(buf, 0, len, newdir, newlen);
|
||||
if (need_sep)
|
||||
buf->buf[newlen] = '/';
|
||||
}
|
||||
|
||||
static const char *common_list[] = {
|
||||
"/branches", "/hooks", "/info", "!/logs", "/lost-found",
|
||||
"/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
|
||||
"config", "!gc.pid", "packed-refs", "shallow",
|
||||
NULL
|
||||
};
|
||||
|
||||
static void update_common_dir(struct strbuf *buf, int git_dir_len)
|
||||
{
|
||||
char *base = buf->buf + git_dir_len;
|
||||
const char **p;
|
||||
|
||||
if (is_dir_file(base, "logs", "HEAD") ||
|
||||
is_dir_file(base, "info", "sparse-checkout"))
|
||||
return; /* keep this in $GIT_DIR */
|
||||
for (p = common_list; *p; p++) {
|
||||
const char *path = *p;
|
||||
int is_dir = 0;
|
||||
if (*path == '!')
|
||||
path++;
|
||||
if (*path == '/') {
|
||||
path++;
|
||||
is_dir = 1;
|
||||
}
|
||||
if (is_dir && dir_prefix(base, path)) {
|
||||
replace_dir(buf, git_dir_len, get_git_common_dir());
|
||||
return;
|
||||
}
|
||||
if (!is_dir && !strcmp(base, path)) {
|
||||
replace_dir(buf, git_dir_len, get_git_common_dir());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void report_linked_checkout_garbage(void)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char **p;
|
||||
int len;
|
||||
|
||||
if (!git_common_dir_env)
|
||||
return;
|
||||
strbuf_addf(&sb, "%s/", get_git_dir());
|
||||
len = sb.len;
|
||||
for (p = common_list; *p; p++) {
|
||||
const char *path = *p;
|
||||
if (*path == '!')
|
||||
continue;
|
||||
strbuf_setlen(&sb, len);
|
||||
strbuf_addstr(&sb, path);
|
||||
if (file_exists(sb.buf))
|
||||
report_garbage("unused in linked checkout", sb.buf);
|
||||
}
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
static void adjust_git_path(struct strbuf *buf, int git_dir_len)
|
||||
{
|
||||
const char *base = buf->buf + git_dir_len;
|
||||
if (git_graft_env && is_dir_file(base, "info", "grafts"))
|
||||
strbuf_splice(buf, 0, buf->len,
|
||||
get_graft_file(), strlen(get_graft_file()));
|
||||
else if (git_index_env && !strcmp(base, "index"))
|
||||
strbuf_splice(buf, 0, buf->len,
|
||||
get_index_file(), strlen(get_index_file()));
|
||||
else if (git_db_env && dir_prefix(base, "objects"))
|
||||
replace_dir(buf, git_dir_len + 7, get_object_directory());
|
||||
else if (git_common_dir_env)
|
||||
update_common_dir(buf, git_dir_len);
|
||||
}
|
||||
|
||||
static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
|
||||
{
|
||||
int gitdir_len;
|
||||
strbuf_addstr(buf, get_git_dir());
|
||||
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
|
||||
strbuf_addch(buf, '/');
|
||||
gitdir_len = buf->len;
|
||||
strbuf_vaddf(buf, fmt, args);
|
||||
adjust_git_path(buf, gitdir_len);
|
||||
strbuf_cleanup_path(buf);
|
||||
}
|
||||
|
||||
void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
|
||||
{
|
||||
char *ret;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(buf, n, fmt, args);
|
||||
do_git_path(sb, fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *git_path(const char *fmt, ...)
|
||||
{
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(pathname, fmt, args);
|
||||
va_end(args);
|
||||
return pathname->buf;
|
||||
}
|
||||
|
||||
char *git_pathdup(const char *fmt, ...)
|
||||
{
|
||||
char path[PATH_MAX], *ret;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(path, sizeof(path), fmt, args);
|
||||
do_git_path(&path, fmt, args);
|
||||
va_end(args);
|
||||
return xstrdup(ret);
|
||||
return strbuf_detach(&path, NULL);
|
||||
}
|
||||
|
||||
char *mkpathdup(const char *fmt, ...)
|
||||
{
|
||||
char *path;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
strbuf_vaddf(&sb, fmt, args);
|
||||
va_end(args);
|
||||
path = xstrdup(cleanup_path(sb.buf));
|
||||
|
||||
strbuf_release(&sb);
|
||||
return path;
|
||||
strbuf_cleanup_path(&sb);
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
char *mkpath(const char *fmt, ...)
|
||||
const char *mkpath(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
unsigned len;
|
||||
char *pathname = get_pathname();
|
||||
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_start(args, fmt);
|
||||
len = vsnprintf(pathname, PATH_MAX, fmt, args);
|
||||
strbuf_vaddf(pathname, fmt, args);
|
||||
va_end(args);
|
||||
if (len >= PATH_MAX)
|
||||
return bad_path;
|
||||
return cleanup_path(pathname);
|
||||
}
|
||||
|
||||
char *git_path(const char *fmt, ...)
|
||||
{
|
||||
char *pathname = get_pathname();
|
||||
va_list args;
|
||||
char *ret;
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnpath(pathname, PATH_MAX, fmt, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
return cleanup_path(pathname->buf);
|
||||
}
|
||||
|
||||
void home_config_paths(char **global, char **xdg, char *file)
|
||||
|
@ -158,43 +252,29 @@ void home_config_paths(char **global, char **xdg, char *file)
|
|||
free(to_free);
|
||||
}
|
||||
|
||||
char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
const char *git_path_submodule(const char *path, const char *fmt, ...)
|
||||
{
|
||||
char *pathname = get_pathname();
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct strbuf *buf = get_pathname();
|
||||
const char *git_dir;
|
||||
va_list args;
|
||||
unsigned len;
|
||||
|
||||
len = strlen(path);
|
||||
if (len > PATH_MAX-100)
|
||||
return bad_path;
|
||||
strbuf_addstr(buf, path);
|
||||
if (buf->len && buf->buf[buf->len - 1] != '/')
|
||||
strbuf_addch(buf, '/');
|
||||
strbuf_addstr(buf, ".git");
|
||||
|
||||
strbuf_addstr(&buf, path);
|
||||
if (len && path[len-1] != '/')
|
||||
strbuf_addch(&buf, '/');
|
||||
strbuf_addstr(&buf, ".git");
|
||||
|
||||
git_dir = read_gitfile(buf.buf);
|
||||
git_dir = read_gitfile(buf->buf);
|
||||
if (git_dir) {
|
||||
strbuf_reset(&buf);
|
||||
strbuf_addstr(&buf, git_dir);
|
||||
strbuf_reset(buf);
|
||||
strbuf_addstr(buf, git_dir);
|
||||
}
|
||||
strbuf_addch(&buf, '/');
|
||||
|
||||
if (buf.len >= PATH_MAX)
|
||||
return bad_path;
|
||||
memcpy(pathname, buf.buf, buf.len + 1);
|
||||
|
||||
strbuf_release(&buf);
|
||||
len = strlen(pathname);
|
||||
strbuf_addch(buf, '/');
|
||||
|
||||
va_start(args, fmt);
|
||||
len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
|
||||
strbuf_vaddf(buf, fmt, args);
|
||||
va_end(args);
|
||||
if (len >= PATH_MAX)
|
||||
return bad_path;
|
||||
return cleanup_path(pathname);
|
||||
strbuf_cleanup_path(buf);
|
||||
return buf->buf;
|
||||
}
|
||||
|
||||
int validate_headref(const char *path)
|
||||
|
|
59
refs.c
59
refs.c
|
@ -1382,7 +1382,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
|
|||
{
|
||||
int fd, len;
|
||||
char buffer[128], *p;
|
||||
char *path;
|
||||
const char *path;
|
||||
|
||||
if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
|
||||
return -1;
|
||||
|
@ -1475,7 +1475,11 @@ static int resolve_missing_loose_ref(const char *refname,
|
|||
}
|
||||
|
||||
/* This function needs to return a meaningful errno on failure */
|
||||
const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
|
||||
static const char *resolve_ref_unsafe_1(const char *refname,
|
||||
int resolve_flags,
|
||||
unsigned char *sha1,
|
||||
int *flags,
|
||||
struct strbuf *sb_path)
|
||||
{
|
||||
int depth = MAXDEPTH;
|
||||
ssize_t len;
|
||||
|
@ -1506,7 +1510,7 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
|
|||
bad_name = 1;
|
||||
}
|
||||
for (;;) {
|
||||
char path[PATH_MAX];
|
||||
const char *path;
|
||||
struct stat st;
|
||||
char *buf;
|
||||
int fd;
|
||||
|
@ -1516,7 +1520,9 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
|
|||
return NULL;
|
||||
}
|
||||
|
||||
git_snpath(path, sizeof(path), "%s", refname);
|
||||
strbuf_reset(sb_path);
|
||||
strbuf_git_path(sb_path, "%s", refname);
|
||||
path = sb_path->buf;
|
||||
|
||||
/*
|
||||
* We might have to loop back here to avoid a race
|
||||
|
@ -1643,6 +1649,16 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
|
|||
}
|
||||
}
|
||||
|
||||
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
|
||||
unsigned char *sha1, int *flags)
|
||||
{
|
||||
struct strbuf sb_path = STRBUF_INIT;
|
||||
const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
|
||||
sha1, flags, &sb_path);
|
||||
strbuf_release(&sb_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
|
||||
{
|
||||
return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
|
||||
|
@ -2274,7 +2290,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
|||
const struct string_list *skip,
|
||||
unsigned int flags, int *type_p)
|
||||
{
|
||||
char *ref_file;
|
||||
const char *ref_file;
|
||||
const char *orig_refname = refname;
|
||||
struct ref_lock *lock;
|
||||
int last_errno = 0;
|
||||
|
@ -2343,7 +2359,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
|||
ref_file = git_path("%s", refname);
|
||||
|
||||
retry:
|
||||
switch (safe_create_leading_directories(ref_file)) {
|
||||
switch (safe_create_leading_directories_const(ref_file)) {
|
||||
case SCLD_OK:
|
||||
break; /* success */
|
||||
case SCLD_VANISHED:
|
||||
|
@ -2721,7 +2737,7 @@ static int rename_tmp_log(const char *newrefname)
|
|||
int attempts_remaining = 4;
|
||||
|
||||
retry:
|
||||
switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
|
||||
switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
|
||||
case SCLD_OK:
|
||||
break; /* success */
|
||||
case SCLD_VANISHED:
|
||||
|
@ -2907,11 +2923,15 @@ static int copy_msg(char *buf, const char *msg)
|
|||
}
|
||||
|
||||
/* This function must set a meaningful errno on failure */
|
||||
int log_ref_setup(const char *refname, char *logfile, int bufsize)
|
||||
int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
|
||||
{
|
||||
int logfd, oflags = O_APPEND | O_WRONLY;
|
||||
char *logfile;
|
||||
|
||||
git_snpath(logfile, bufsize, "logs/%s", refname);
|
||||
strbuf_git_path(sb_logfile, "logs/%s", refname);
|
||||
logfile = sb_logfile->buf;
|
||||
/* make sure the rest of the function can't change "logfile" */
|
||||
sb_logfile = NULL;
|
||||
if (log_all_ref_updates &&
|
||||
(starts_with(refname, "refs/heads/") ||
|
||||
starts_with(refname, "refs/remotes/") ||
|
||||
|
@ -2982,18 +3002,22 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int log_ref_write(const char *refname, const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1, const char *msg)
|
||||
static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1, const char *msg,
|
||||
struct strbuf *sb_log_file)
|
||||
{
|
||||
int logfd, result, oflags = O_APPEND | O_WRONLY;
|
||||
char log_file[PATH_MAX];
|
||||
char *log_file;
|
||||
|
||||
if (log_all_ref_updates < 0)
|
||||
log_all_ref_updates = !is_bare_repository();
|
||||
|
||||
result = log_ref_setup(refname, log_file, sizeof(log_file));
|
||||
result = log_ref_setup(refname, sb_log_file);
|
||||
if (result)
|
||||
return result;
|
||||
log_file = sb_log_file->buf;
|
||||
/* make sure the rest of the function can't change "log_file" */
|
||||
sb_log_file = NULL;
|
||||
|
||||
logfd = open(log_file, oflags);
|
||||
if (logfd < 0)
|
||||
|
@ -3016,6 +3040,15 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int log_ref_write(const char *refname, const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1, const char *msg)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);
|
||||
strbuf_release(&sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int is_branch(const char *refname)
|
||||
{
|
||||
return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
|
||||
|
|
2
refs.h
2
refs.h
|
@ -191,7 +191,7 @@ extern int peel_ref(const char *refname, unsigned char *sha1);
|
|||
/*
|
||||
* Setup reflog before using. Set errno to something meaningful on failure.
|
||||
*/
|
||||
int log_ref_setup(const char *refname, char *logfile, int bufsize);
|
||||
int log_ref_setup(const char *refname, struct strbuf *logfile);
|
||||
|
||||
/** Reads log for the value of ref during at_time. **/
|
||||
extern int read_ref_at(const char *refname, unsigned int flags,
|
||||
|
|
|
@ -795,9 +795,9 @@ int finish_async(struct async *async)
|
|||
#endif
|
||||
}
|
||||
|
||||
char *find_hook(const char *name)
|
||||
const char *find_hook(const char *name)
|
||||
{
|
||||
char *path = git_path("hooks/%s", name);
|
||||
const char *path = git_path("hooks/%s", name);
|
||||
if (access(path, X_OK) < 0)
|
||||
path = NULL;
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ int start_command(struct child_process *);
|
|||
int finish_command(struct child_process *);
|
||||
int run_command(struct child_process *);
|
||||
|
||||
extern char *find_hook(const char *name);
|
||||
extern const char *find_hook(const char *name);
|
||||
LAST_ARG_MUST_BE_NULL
|
||||
extern int run_hook_le(const char *const *env, const char *name, ...);
|
||||
extern int run_hook_ve(const char *const *env, const char *name, va_list args);
|
||||
|
|
124
setup.c
124
setup.c
|
@ -224,6 +224,36 @@ void verify_non_filename(const char *prefix, const char *arg)
|
|||
"'git <command> [<revision>...] -- [<file>...]'", arg);
|
||||
}
|
||||
|
||||
int get_common_dir(struct strbuf *sb, const char *gitdir)
|
||||
{
|
||||
struct strbuf data = STRBUF_INIT;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
|
||||
int ret = 0;
|
||||
if (git_common_dir) {
|
||||
strbuf_addstr(sb, git_common_dir);
|
||||
return 1;
|
||||
}
|
||||
strbuf_addf(&path, "%s/commondir", gitdir);
|
||||
if (file_exists(path.buf)) {
|
||||
if (strbuf_read_file(&data, path.buf, 0) <= 0)
|
||||
die_errno(_("failed to read %s"), path.buf);
|
||||
while (data.len && (data.buf[data.len - 1] == '\n' ||
|
||||
data.buf[data.len - 1] == '\r'))
|
||||
data.len--;
|
||||
data.buf[data.len] = '\0';
|
||||
strbuf_reset(&path);
|
||||
if (!is_absolute_path(data.buf))
|
||||
strbuf_addf(&path, "%s/", gitdir);
|
||||
strbuf_addbuf(&path, &data);
|
||||
strbuf_addstr(sb, real_path(path.buf));
|
||||
ret = 1;
|
||||
} else
|
||||
strbuf_addstr(sb, gitdir);
|
||||
strbuf_release(&data);
|
||||
strbuf_release(&path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if it looks like we're at a git directory.
|
||||
|
@ -238,31 +268,40 @@ void verify_non_filename(const char *prefix, const char *arg)
|
|||
*/
|
||||
int is_git_directory(const char *suspect)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
size_t len = strlen(suspect);
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
int ret = 0;
|
||||
size_t len;
|
||||
|
||||
if (PATH_MAX <= len + strlen("/objects"))
|
||||
die("Too long path: %.*s", 60, suspect);
|
||||
strcpy(path, suspect);
|
||||
/* Check worktree-related signatures */
|
||||
strbuf_addf(&path, "%s/HEAD", suspect);
|
||||
if (validate_headref(path.buf))
|
||||
goto done;
|
||||
|
||||
strbuf_reset(&path);
|
||||
get_common_dir(&path, suspect);
|
||||
len = path.len;
|
||||
|
||||
/* Check non-worktree-related signatures */
|
||||
if (getenv(DB_ENVIRONMENT)) {
|
||||
if (access(getenv(DB_ENVIRONMENT), X_OK))
|
||||
return 0;
|
||||
goto done;
|
||||
}
|
||||
else {
|
||||
strcpy(path + len, "/objects");
|
||||
if (access(path, X_OK))
|
||||
return 0;
|
||||
strbuf_setlen(&path, len);
|
||||
strbuf_addstr(&path, "/objects");
|
||||
if (access(path.buf, X_OK))
|
||||
goto done;
|
||||
}
|
||||
|
||||
strcpy(path + len, "/refs");
|
||||
if (access(path, X_OK))
|
||||
return 0;
|
||||
strbuf_setlen(&path, len);
|
||||
strbuf_addstr(&path, "/refs");
|
||||
if (access(path.buf, X_OK))
|
||||
goto done;
|
||||
|
||||
strcpy(path + len, "/HEAD");
|
||||
if (validate_headref(path))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
ret = 1;
|
||||
done:
|
||||
strbuf_release(&path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int is_inside_git_dir(void)
|
||||
|
@ -304,9 +343,28 @@ void setup_work_tree(void)
|
|||
initialized = 1;
|
||||
}
|
||||
|
||||
static int check_repo_format(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (strcmp(var, "core.repositoryformatversion") == 0)
|
||||
repository_format_version = git_config_int(var, value);
|
||||
else if (strcmp(var, "core.sharedrepository") == 0)
|
||||
shared_repository = git_config_perm(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
|
||||
{
|
||||
char repo_config[PATH_MAX+1];
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *repo_config;
|
||||
config_fn_t fn;
|
||||
int ret = 0;
|
||||
|
||||
if (get_common_dir(&sb, gitdir))
|
||||
fn = check_repo_format;
|
||||
else
|
||||
fn = check_repository_format_version;
|
||||
strbuf_addstr(&sb, "/config");
|
||||
repo_config = sb.buf;
|
||||
|
||||
/*
|
||||
* git_config() can't be used here because it calls git_pathdup()
|
||||
|
@ -317,8 +375,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
|
|||
* Use a gentler version of git_config() to check if this repo
|
||||
* is a good one.
|
||||
*/
|
||||
snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
|
||||
git_config_early(check_repository_format_version, NULL, repo_config);
|
||||
git_config_early(fn, NULL, repo_config);
|
||||
if (GIT_REPO_VERSION < repository_format_version) {
|
||||
if (!nongit_ok)
|
||||
die ("Expected git repo version <= %d, found %d",
|
||||
|
@ -327,9 +384,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
|
|||
GIT_REPO_VERSION, repository_format_version);
|
||||
warning("Please upgrade Git");
|
||||
*nongit_ok = -1;
|
||||
return -1;
|
||||
ret = -1;
|
||||
}
|
||||
return 0;
|
||||
strbuf_release(&sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void update_linked_gitdir(const char *gitfile, const char *gitdir)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
strbuf_addf(&path, "%s/gitfile", gitdir);
|
||||
if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
|
||||
write_file(path.buf, 0, "%s\n", gitfile);
|
||||
strbuf_release(&path);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -380,6 +449,8 @@ const char *read_gitfile(const char *path)
|
|||
|
||||
if (!is_git_directory(dir))
|
||||
die("Not a git repository: %s", dir);
|
||||
|
||||
update_linked_gitdir(path, dir);
|
||||
path = real_path(dir);
|
||||
|
||||
free(buf);
|
||||
|
@ -799,11 +870,10 @@ int git_config_perm(const char *var, const char *value)
|
|||
|
||||
int check_repository_format_version(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (strcmp(var, "core.repositoryformatversion") == 0)
|
||||
repository_format_version = git_config_int(var, value);
|
||||
else if (strcmp(var, "core.sharedrepository") == 0)
|
||||
shared_repository = git_config_perm(var, value);
|
||||
else if (strcmp(var, "core.bare") == 0) {
|
||||
int ret = check_repo_format(var, value, cb);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (strcmp(var, "core.bare") == 0) {
|
||||
is_bare_repository_cfg = git_config_bool(var, value);
|
||||
if (is_bare_repository_cfg == 1)
|
||||
inside_work_tree = -1;
|
||||
|
|
|
@ -405,7 +405,7 @@ void add_to_alternates_file(const char *reference)
|
|||
{
|
||||
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
|
||||
int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
|
||||
char *alt = mkpath("%s\n", reference);
|
||||
const char *alt = mkpath("%s\n", reference);
|
||||
write_or_die(fd, alt, strlen(alt));
|
||||
if (commit_lock_file(lock))
|
||||
die("could not close alternates file");
|
||||
|
|
|
@ -1100,16 +1100,11 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
|
|||
struct strbuf file_name = STRBUF_INIT;
|
||||
struct strbuf rel_path = STRBUF_INIT;
|
||||
const char *real_work_tree = xstrdup(real_path(work_tree));
|
||||
FILE *fp;
|
||||
|
||||
/* Update gitfile */
|
||||
strbuf_addf(&file_name, "%s/.git", work_tree);
|
||||
fp = fopen(file_name.buf, "w");
|
||||
if (!fp)
|
||||
die(_("Could not create git link %s"), file_name.buf);
|
||||
fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
|
||||
&rel_path));
|
||||
fclose(fp);
|
||||
write_file(file_name.buf, 1, "gitdir: %s\n",
|
||||
relative_path(git_dir, real_work_tree, &rel_path));
|
||||
|
||||
/* Update core.worktree setting */
|
||||
strbuf_reset(&file_name);
|
||||
|
|
|
@ -19,6 +19,14 @@ relative_path() {
|
|||
"test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
|
||||
}
|
||||
|
||||
test_git_path() {
|
||||
test_expect_success "git-path $1 $2 => $3" "
|
||||
$1 git rev-parse --git-path $2 >actual &&
|
||||
echo $3 >expect &&
|
||||
test_cmp expect actual
|
||||
"
|
||||
}
|
||||
|
||||
# On Windows, we are using MSYS's bash, which mangles the paths.
|
||||
# Absolute paths are anchored at the MSYS installation directory,
|
||||
# which means that the path / accounts for this many characters:
|
||||
|
@ -244,4 +252,32 @@ relative_path "<null>" "<empty>" ./
|
|||
relative_path "<null>" "<null>" ./
|
||||
relative_path "<null>" /foo/a/b ./
|
||||
|
||||
test_git_path A=B info/grafts .git/info/grafts
|
||||
test_git_path GIT_GRAFT_FILE=foo info/grafts foo
|
||||
test_git_path GIT_GRAFT_FILE=foo info/////grafts foo
|
||||
test_git_path GIT_INDEX_FILE=foo index foo
|
||||
test_git_path GIT_INDEX_FILE=foo index/foo .git/index/foo
|
||||
test_git_path GIT_INDEX_FILE=foo index2 .git/index2
|
||||
test_expect_success 'setup fake objects directory foo' 'mkdir foo'
|
||||
test_git_path GIT_OBJECT_DIRECTORY=foo objects foo
|
||||
test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
|
||||
test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
|
||||
test_expect_success 'setup common repository' 'git --git-dir=bar init'
|
||||
test_git_path GIT_COMMON_DIR=bar index .git/index
|
||||
test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD
|
||||
test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD
|
||||
test_git_path GIT_COMMON_DIR=bar objects bar/objects
|
||||
test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar
|
||||
test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude
|
||||
test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts
|
||||
test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout
|
||||
test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar
|
||||
test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar
|
||||
test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master
|
||||
test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master
|
||||
test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me
|
||||
test_git_path GIT_COMMON_DIR=bar config bar/config
|
||||
test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs
|
||||
test_git_path GIT_COMMON_DIR=bar shallow bar/shallow
|
||||
|
||||
test_done
|
||||
|
|
|
@ -346,4 +346,81 @@ test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
|
|||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'Multi-worktree setup' '
|
||||
mkdir work &&
|
||||
mkdir -p repo.git/repos/foo &&
|
||||
cp repo.git/HEAD repo.git/index repo.git/repos/foo &&
|
||||
test_might_fail cp repo.git/sharedindex.* repo.git/repos/foo &&
|
||||
sane_unset GIT_DIR GIT_CONFIG GIT_WORK_TREE
|
||||
'
|
||||
|
||||
test_expect_success 'GIT_DIR set (1)' '
|
||||
echo "gitdir: repo.git/repos/foo" >gitfile &&
|
||||
echo ../.. >repo.git/repos/foo/commondir &&
|
||||
(
|
||||
cd work &&
|
||||
GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
|
||||
test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'GIT_DIR set (2)' '
|
||||
echo "gitdir: repo.git/repos/foo" >gitfile &&
|
||||
echo "$(pwd)/repo.git" >repo.git/repos/foo/commondir &&
|
||||
(
|
||||
cd work &&
|
||||
GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
|
||||
test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'Auto discovery' '
|
||||
echo "gitdir: repo.git/repos/foo" >.git &&
|
||||
echo ../.. >repo.git/repos/foo/commondir &&
|
||||
(
|
||||
cd work &&
|
||||
git rev-parse --git-common-dir >actual &&
|
||||
test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
|
||||
test_cmp expect actual &&
|
||||
echo haha >data1 &&
|
||||
git add data1 &&
|
||||
git ls-files --full-name :/ | grep data1 >actual &&
|
||||
echo work/data1 >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '$GIT_DIR/common overrides core.worktree' '
|
||||
mkdir elsewhere &&
|
||||
git --git-dir=repo.git config core.worktree "$TRASH_DIRECTORY/elsewhere" &&
|
||||
echo "gitdir: repo.git/repos/foo" >.git &&
|
||||
echo ../.. >repo.git/repos/foo/commondir &&
|
||||
(
|
||||
cd work &&
|
||||
git rev-parse --git-common-dir >actual &&
|
||||
test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
|
||||
test_cmp expect actual &&
|
||||
echo haha >data2 &&
|
||||
git add data2 &&
|
||||
git ls-files --full-name :/ | grep data2 >actual &&
|
||||
echo work/data2 >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' '
|
||||
echo "gitdir: repo.git/repos/foo" >.git &&
|
||||
echo ../.. >repo.git/repos/foo/commondir &&
|
||||
(
|
||||
cd work &&
|
||||
echo haha >data3 &&
|
||||
git --git-dir=../.git --work-tree=. add data3 &&
|
||||
git ls-files --full-name -- :/ | grep data3 >actual &&
|
||||
echo data3 >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -106,6 +106,7 @@ setup_env () {
|
|||
expect () {
|
||||
cat >"$1/expected" <<-EOF
|
||||
setup: git_dir: $2
|
||||
setup: git_common_dir: $2
|
||||
setup: worktree: $3
|
||||
setup: cwd: $4
|
||||
setup: prefix: $5
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='test git checkout --to'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit init
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to not updating paths' '
|
||||
test_must_fail git checkout --to -- init.t
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to an existing worktree' '
|
||||
mkdir -p existing/subtree &&
|
||||
test_must_fail git checkout --detach --to existing master
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to an existing empty worktree' '
|
||||
mkdir existing_empty &&
|
||||
git checkout --detach --to existing_empty master
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to refuses to checkout locked branch' '
|
||||
test_must_fail git checkout --to zere master &&
|
||||
! test -d zere &&
|
||||
! test -d .git/worktrees/zere
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to a new worktree' '
|
||||
git rev-parse HEAD >expect &&
|
||||
git checkout --detach --to here master &&
|
||||
(
|
||||
cd here &&
|
||||
test_cmp ../init.t init.t &&
|
||||
test_must_fail git symbolic-ref HEAD &&
|
||||
git rev-parse HEAD >actual &&
|
||||
test_cmp ../expect actual &&
|
||||
git fsck
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to a new worktree from a subdir' '
|
||||
(
|
||||
mkdir sub &&
|
||||
cd sub &&
|
||||
git checkout --detach --to here master &&
|
||||
cd here &&
|
||||
test_cmp ../../init.t init.t
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to from a linked checkout' '
|
||||
(
|
||||
cd here &&
|
||||
git checkout --detach --to nested-here master &&
|
||||
cd nested-here &&
|
||||
git fsck
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to a new worktree creating new branch' '
|
||||
git checkout --to there -b newmaster master &&
|
||||
(
|
||||
cd there &&
|
||||
test_cmp ../init.t init.t &&
|
||||
git symbolic-ref HEAD >actual &&
|
||||
echo refs/heads/newmaster >expect &&
|
||||
test_cmp expect actual &&
|
||||
git fsck
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'die the same branch is already checked out' '
|
||||
(
|
||||
cd here &&
|
||||
test_must_fail git checkout newmaster
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'not die the same branch is already checked out' '
|
||||
(
|
||||
cd here &&
|
||||
git checkout --ignore-other-worktrees --to anothernewmaster newmaster
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'not die on re-checking out current branch' '
|
||||
(
|
||||
cd there &&
|
||||
git checkout newmaster
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout --to from a bare repo' '
|
||||
(
|
||||
git clone --bare . bare &&
|
||||
cd bare &&
|
||||
git checkout --to ../there2 -b bare-master master
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout from a bare repo without --to' '
|
||||
(
|
||||
cd bare &&
|
||||
test_must_fail git checkout master
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout with grafts' '
|
||||
test_when_finished rm .git/info/grafts &&
|
||||
test_commit abc &&
|
||||
SHA1=`git rev-parse HEAD` &&
|
||||
test_commit def &&
|
||||
test_commit xyz &&
|
||||
echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
|
||||
cat >expected <<-\EOF &&
|
||||
xyz
|
||||
abc
|
||||
EOF
|
||||
git log --format=%s -2 >actual &&
|
||||
test_cmp expected actual &&
|
||||
git checkout --detach --to grafted master &&
|
||||
git --git-dir=grafted/.git log --format=%s -2 >actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_done
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='prune $GIT_DIR/worktrees'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success initialize '
|
||||
git commit --allow-empty -m init
|
||||
'
|
||||
|
||||
test_expect_success 'prune --worktrees on normal repo' '
|
||||
git prune --worktrees &&
|
||||
test_must_fail git prune --worktrees abc
|
||||
'
|
||||
|
||||
test_expect_success 'prune files inside $GIT_DIR/worktrees' '
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/abc &&
|
||||
git prune --worktrees --verbose >actual &&
|
||||
cat >expect <<EOF &&
|
||||
Removing worktrees/abc: not a valid directory
|
||||
EOF
|
||||
test_i18ncmp expect actual &&
|
||||
! test -f .git/worktrees/abc &&
|
||||
! test -d .git/worktrees
|
||||
'
|
||||
|
||||
test_expect_success 'prune directories without gitdir' '
|
||||
mkdir -p .git/worktrees/def/abc &&
|
||||
: >.git/worktrees/def/def &&
|
||||
cat >expect <<EOF &&
|
||||
Removing worktrees/def: gitdir file does not exist
|
||||
EOF
|
||||
git prune --worktrees --verbose >actual &&
|
||||
test_i18ncmp expect actual &&
|
||||
! test -d .git/worktrees/def &&
|
||||
! test -d .git/worktrees
|
||||
'
|
||||
|
||||
test_expect_success SANITY 'prune directories with unreadable gitdir' '
|
||||
mkdir -p .git/worktrees/def/abc &&
|
||||
: >.git/worktrees/def/def &&
|
||||
: >.git/worktrees/def/gitdir &&
|
||||
chmod u-r .git/worktrees/def/gitdir &&
|
||||
git prune --worktrees --verbose >actual &&
|
||||
test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
|
||||
! test -d .git/worktrees/def &&
|
||||
! test -d .git/worktrees
|
||||
'
|
||||
|
||||
test_expect_success 'prune directories with invalid gitdir' '
|
||||
mkdir -p .git/worktrees/def/abc &&
|
||||
: >.git/worktrees/def/def &&
|
||||
: >.git/worktrees/def/gitdir &&
|
||||
git prune --worktrees --verbose >actual &&
|
||||
test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
|
||||
! test -d .git/worktrees/def &&
|
||||
! test -d .git/worktrees
|
||||
'
|
||||
|
||||
test_expect_success 'prune directories with gitdir pointing to nowhere' '
|
||||
mkdir -p .git/worktrees/def/abc &&
|
||||
: >.git/worktrees/def/def &&
|
||||
echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
|
||||
git prune --worktrees --verbose >actual &&
|
||||
test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
|
||||
! test -d .git/worktrees/def &&
|
||||
! test -d .git/worktrees
|
||||
'
|
||||
|
||||
test_expect_success 'not prune locked checkout' '
|
||||
test_when_finished rm -r .git/worktrees &&
|
||||
mkdir -p .git/worktrees/ghi &&
|
||||
: >.git/worktrees/ghi/locked &&
|
||||
git prune --worktrees &&
|
||||
test -d .git/worktrees/ghi
|
||||
'
|
||||
|
||||
test_expect_success 'not prune recent checkouts' '
|
||||
test_when_finished rm -r .git/worktrees &&
|
||||
mkdir zz &&
|
||||
mkdir -p .git/worktrees/jlm &&
|
||||
echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
|
||||
rmdir zz &&
|
||||
git prune --worktrees --verbose --expire=2.days.ago &&
|
||||
test -d .git/worktrees/jlm
|
||||
'
|
||||
|
||||
test_expect_success 'not prune proper checkouts' '
|
||||
test_when_finished rm -r .git/worktrees &&
|
||||
git checkout "--to=$PWD/nop" --detach master &&
|
||||
git prune --worktrees &&
|
||||
test -d .git/worktrees/nop
|
||||
'
|
||||
|
||||
test_done
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Combination of submodules and multiple workdirs'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
base_path=$(pwd -P)
|
||||
|
||||
test_expect_success 'setup: make origin' \
|
||||
'mkdir -p origin/sub && ( cd origin/sub && git init &&
|
||||
echo file1 >file1 &&
|
||||
git add file1 &&
|
||||
git commit -m file1 ) &&
|
||||
mkdir -p origin/main && ( cd origin/main && git init &&
|
||||
git submodule add ../sub &&
|
||||
git commit -m "add sub" ) &&
|
||||
( cd origin/sub &&
|
||||
echo file1updated >file1 &&
|
||||
git add file1 &&
|
||||
git commit -m "file1 updated" ) &&
|
||||
( cd origin/main/sub && git pull ) &&
|
||||
( cd origin/main &&
|
||||
git add sub &&
|
||||
git commit -m "sub updated" )'
|
||||
|
||||
test_expect_success 'setup: clone' \
|
||||
'mkdir clone && ( cd clone &&
|
||||
git clone --recursive "$base_path/origin/main")'
|
||||
|
||||
rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1")
|
||||
rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1")
|
||||
|
||||
test_expect_success 'checkout main' \
|
||||
'mkdir default_checkout &&
|
||||
(cd clone/main &&
|
||||
git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
|
||||
|
||||
test_expect_failure 'can see submodule diffs just after checkout' \
|
||||
'(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
|
||||
|
||||
test_expect_success 'checkout main and initialize independed clones' \
|
||||
'mkdir fully_cloned_submodule &&
|
||||
(cd clone/main &&
|
||||
git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
|
||||
(cd fully_cloned_submodule/main && git submodule update)'
|
||||
|
||||
test_expect_success 'can see submodule diffs after independed cloning' \
|
||||
'(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")'
|
||||
|
||||
test_done
|
|
@ -10,6 +10,6 @@
|
|||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/commit-msg" &&
|
||||
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
|
|
|
@ -9,6 +9,6 @@
|
|||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
|
||||
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||
:
|
||||
|
|
1
trace.c
1
trace.c
|
@ -310,6 +310,7 @@ void trace_repo_setup(const char *prefix)
|
|||
prefix = "(null)";
|
||||
|
||||
trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
|
||||
trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
|
||||
trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
|
||||
trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
|
||||
trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
|
||||
|
|
|
@ -283,7 +283,6 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
|
|||
{
|
||||
struct strbuf *buf = data;
|
||||
int len = buf->len;
|
||||
FILE *f;
|
||||
|
||||
/* when called via for_each_ref(), flags is non-zero */
|
||||
if (flags && !starts_with(name, "refs/heads/") &&
|
||||
|
@ -292,10 +291,9 @@ static int write_one_ref(const char *name, const unsigned char *sha1,
|
|||
|
||||
strbuf_addstr(buf, name);
|
||||
if (safe_create_leading_directories(buf->buf) ||
|
||||
!(f = fopen(buf->buf, "w")) ||
|
||||
fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 ||
|
||||
fclose(f))
|
||||
return error("problems writing temporary file %s", buf->buf);
|
||||
write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1)))
|
||||
return error("problems writing temporary file %s: %s",
|
||||
buf->buf, strerror(errno));
|
||||
strbuf_setlen(buf, len);
|
||||
return 0;
|
||||
}
|
||||
|
|
31
wrapper.c
31
wrapper.c
|
@ -564,3 +564,34 @@ char *xgetcwd(void)
|
|||
die_errno(_("unable to get current working directory"));
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
int write_file(const char *path, int fatal, const char *fmt, ...)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
va_list params;
|
||||
int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0) {
|
||||
if (fatal)
|
||||
die_errno(_("could not open %s for writing"), path);
|
||||
return -1;
|
||||
}
|
||||
va_start(params, fmt);
|
||||
strbuf_vaddf(&sb, fmt, params);
|
||||
va_end(params);
|
||||
if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
|
||||
int err = errno;
|
||||
close(fd);
|
||||
strbuf_release(&sb);
|
||||
errno = err;
|
||||
if (fatal)
|
||||
die_errno(_("could not write to %s"), path);
|
||||
return -1;
|
||||
}
|
||||
strbuf_release(&sb);
|
||||
if (close(fd)) {
|
||||
if (fatal)
|
||||
die_errno(_("could not close %s"), path);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче