mirror of
https://github.com/git/git.git
synced 2026-04-01 20:40:08 +02:00
Merge branch 'master' of git://repo.or.cz/alt-git
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -108,6 +108,10 @@
|
||||
/git-relink
|
||||
/git-remote
|
||||
/git-remote-curl
|
||||
/git-remote-http
|
||||
/git-remote-https
|
||||
/git-remote-ftp
|
||||
/git-remote-ftps
|
||||
/git-repack
|
||||
/git-replace
|
||||
/git-repo-config
|
||||
@@ -158,6 +162,7 @@
|
||||
/test-delta
|
||||
/test-dump-cache-tree
|
||||
/test-genrandom
|
||||
/test-index-version
|
||||
/test-match-trees
|
||||
/test-parse-options
|
||||
/test-path-utils
|
||||
|
||||
@@ -502,6 +502,10 @@ notes should be printed.
|
||||
This setting defaults to "refs/notes/commits", and can be overridden by
|
||||
the `GIT_NOTES_REF` environment variable.
|
||||
|
||||
core.sparseCheckout::
|
||||
Enable "sparse checkout" feature. See section "Sparse checkout" in
|
||||
linkgit:git-read-tree[1] for more information.
|
||||
|
||||
add.ignore-errors::
|
||||
Tells 'git-add' to continue adding files when some files cannot be
|
||||
added due to indexing errors. Equivalent to the '--ignore-errors'
|
||||
|
||||
@@ -109,6 +109,7 @@ OPTIONS
|
||||
Identify the file status with the following tags (followed by
|
||||
a space) at the start of each line:
|
||||
H:: cached
|
||||
S:: skip-worktree
|
||||
M:: unmerged
|
||||
R:: removed/deleted
|
||||
C:: modified/changed
|
||||
|
||||
@@ -10,7 +10,7 @@ SYNOPSIS
|
||||
--------
|
||||
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
|
||||
[-u [--exclude-per-directory=<gitignore>] | -i]]
|
||||
[--index-output=<file>]
|
||||
[--index-output=<file>] [--no-sparse-checkout]
|
||||
<tree-ish1> [<tree-ish2> [<tree-ish3>]]
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ OPTIONS
|
||||
directories the index file and index output file are
|
||||
located in.
|
||||
|
||||
--no-sparse-checkout::
|
||||
Disable sparse checkout support even if `core.sparseCheckout`
|
||||
is true.
|
||||
|
||||
<tree-ish#>::
|
||||
The id of the tree object(s) to be read/merged.
|
||||
|
||||
@@ -360,6 +364,52 @@ middle of doing, and when your working tree is ready (i.e. you
|
||||
have finished your work-in-progress), attempt the merge again.
|
||||
|
||||
|
||||
Sparse checkout
|
||||
---------------
|
||||
|
||||
"Sparse checkout" allows to sparsely populate working directory.
|
||||
It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
|
||||
Git whether a file on working directory is worth looking at.
|
||||
|
||||
"git read-tree" and other merge-based commands ("git merge", "git
|
||||
checkout"...) can help maintaining skip-worktree bitmap and working
|
||||
directory update. `$GIT_DIR/info/sparse-checkout` is used to
|
||||
define the skip-worktree reference bitmap. When "git read-tree" needs
|
||||
to update working directory, it will reset skip-worktree bit in index
|
||||
based on this file, which uses the same syntax as .gitignore files.
|
||||
If an entry matches a pattern in this file, skip-worktree will be
|
||||
set on that entry. Otherwise, skip-worktree will be unset.
|
||||
|
||||
Then it compares the new skip-worktree value with the previous one. If
|
||||
skip-worktree turns from unset to set, it will add the corresponding
|
||||
file back. If it turns from set to unset, that file will be removed.
|
||||
|
||||
While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
|
||||
files are in. You can also specify what files are _not_ in, using
|
||||
negate patterns. For example, to remove file "unwanted":
|
||||
|
||||
----------------
|
||||
*
|
||||
!unwanted
|
||||
----------------
|
||||
|
||||
Another tricky thing is fully repopulating working directory when you
|
||||
no longer want sparse checkout. You cannot just disable "sparse
|
||||
checkout" because skip-worktree are still in the index and you working
|
||||
directory is still sparsely populated. You should re-populate working
|
||||
directory with the `$GIT_DIR/info/sparse-checkout` file content as
|
||||
follows:
|
||||
|
||||
----------------
|
||||
*
|
||||
----------------
|
||||
|
||||
Then you can disable sparse checkout. Sparse checkout support in "git
|
||||
read-tree" and similar commands is disabled by default. You need to
|
||||
turn `core.sparseCheckout` on in order to have sparse checkout
|
||||
support.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
|
||||
|
||||
@@ -25,7 +25,10 @@ Commands are given by the caller on the helper's standard input, one per line.
|
||||
|
||||
'capabilities'::
|
||||
Lists the capabilities of the helper, one per line, ending
|
||||
with a blank line.
|
||||
with a blank line. Each capability may be preceeded with '*'.
|
||||
This marks them mandatory for git version using the remote
|
||||
helper to understand (unknown mandatory capability is fatal
|
||||
error).
|
||||
|
||||
'list'::
|
||||
Lists the refs, one per line, in the format "<value> <name>
|
||||
@@ -90,6 +93,20 @@ Supported if the helper has the "push" capability.
|
||||
+
|
||||
Supported if the helper has the "import" capability.
|
||||
|
||||
'connect' <service>::
|
||||
Connects to given service. Standard input and standard output
|
||||
of helper are connected to specified service (git prefix is
|
||||
included in service name so e.g. fetching uses 'git-upload-pack'
|
||||
as service) on remote side. Valid replies to this command are
|
||||
empty line (connection established), 'fallback' (no smart
|
||||
transport support, fall back to dumb transports) and just
|
||||
exiting with error message printed (can't connect, don't
|
||||
bother trying to fall back). After line feed terminating the
|
||||
positive (empty) response, the output of service starts. After
|
||||
the connection ends, the remote helper exits.
|
||||
+
|
||||
Supported if the helper has the "connect" capability.
|
||||
|
||||
If a fatal error occurs, the program writes the error message to
|
||||
stderr and exits. The caller should expect that a suitable error
|
||||
message has been printed if the child closes the connection without
|
||||
@@ -123,6 +140,9 @@ CAPABILITIES
|
||||
all, it must cover all refs reported by the list command; if
|
||||
it is not used, it is effectively "*:*"
|
||||
|
||||
'connect'::
|
||||
This helper supports the 'connect' command.
|
||||
|
||||
REF LIST ATTRIBUTES
|
||||
-------------------
|
||||
|
||||
@@ -165,9 +185,15 @@ OPTIONS
|
||||
but don't actually change any repository data. For most
|
||||
helpers this only applies to the 'push', if supported.
|
||||
|
||||
'option servpath <c-style-quoted-path>'::
|
||||
Set service path (--upload-pack, --receive-pack etc.) for
|
||||
next connect. Remote helper MAY support this option. Remote
|
||||
helper MUST NOT rely on this option being set before
|
||||
connect request occurs.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Documentation by Daniel Barkalow.
|
||||
Documentation by Daniel Barkalow and Ilari Liusvaara
|
||||
|
||||
GIT
|
||||
---
|
||||
|
||||
@@ -68,6 +68,95 @@ linkgit:git-add[1]).
|
||||
<commit>::
|
||||
Commit to make the current HEAD. If not given defaults to HEAD.
|
||||
|
||||
DISCUSSION
|
||||
----------
|
||||
|
||||
The tables below show what happens when running:
|
||||
|
||||
----------
|
||||
git reset --option target
|
||||
----------
|
||||
|
||||
to reset the HEAD to another commit (`target`) with the different
|
||||
reset options depending on the state of the files.
|
||||
|
||||
In these tables, A, B, C and D are some different states of a
|
||||
file. For example, the first line of the first table means that if a
|
||||
file is in state A in the working tree, in state B in the index, in
|
||||
state C in HEAD and in state D in the target, then "git reset --soft
|
||||
target" will put the file in state A in the working tree, in state B
|
||||
in the index and in state D in HEAD.
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
A B C D --soft A B D
|
||||
--mixed A D D
|
||||
--hard D D D
|
||||
--merge (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
A B C C --soft A B C
|
||||
--mixed A C C
|
||||
--hard C C C
|
||||
--merge (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
B B C D --soft B B D
|
||||
--mixed B D D
|
||||
--hard D D D
|
||||
--merge D D D
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
B B C C --soft B B C
|
||||
--mixed B C C
|
||||
--hard C C C
|
||||
--merge C C C
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
B C C D --soft B C D
|
||||
--mixed B D D
|
||||
--hard D D D
|
||||
--merge (disallowed)
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
B C C C --soft B C C
|
||||
--mixed B C C
|
||||
--hard C C C
|
||||
--merge B C C
|
||||
|
||||
"reset --merge" is meant to be used when resetting out of a conflicted
|
||||
merge. Any mergy operation guarantees that the work tree file that is
|
||||
involved in the merge does not have local change wrt the index before
|
||||
it starts, and that it writes the result out to the work tree. So if
|
||||
we see some difference between the index and the target and also
|
||||
between the index and the work tree, then it means that we are not
|
||||
resetting out from a state that a mergy operation left after failing
|
||||
with a conflict. That is why we disallow --merge option in this case.
|
||||
|
||||
The following tables show what happens when there are unmerged
|
||||
entries:
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
X U A B --soft (disallowed)
|
||||
--mixed X B B
|
||||
--hard B B B
|
||||
--merge B B B
|
||||
|
||||
working index HEAD target working index HEAD
|
||||
----------------------------------------------------
|
||||
X U A A --soft (disallowed)
|
||||
--mixed X A A
|
||||
--hard A A A
|
||||
--merge A A A
|
||||
|
||||
X means any state and U means an unmerged index.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ SYNOPSIS
|
||||
[--cacheinfo <mode> <object> <file>]\*
|
||||
[--chmod=(+|-)x]
|
||||
[--assume-unchanged | --no-assume-unchanged]
|
||||
[--skip-worktree | --no-skip-worktree]
|
||||
[--ignore-submodules]
|
||||
[--really-refresh] [--unresolve] [--again | -g]
|
||||
[--info-only] [--index-info]
|
||||
@@ -103,6 +104,13 @@ you will need to handle the situation manually.
|
||||
Like '--refresh', but checks stat information unconditionally,
|
||||
without regard to the "assume unchanged" setting.
|
||||
|
||||
--skip-worktree::
|
||||
--no-skip-worktree::
|
||||
When one of these flags is specified, the object name recorded
|
||||
for the paths are not updated. Instead, these options
|
||||
set and unset the "skip-worktree" bit for the paths. See
|
||||
section "Skip-worktree bit" below for more information.
|
||||
|
||||
-g::
|
||||
--again::
|
||||
Runs 'git-update-index' itself on the paths whose index
|
||||
@@ -308,6 +316,27 @@ M foo.c
|
||||
<9> now it checks with lstat(2) and finds it has been changed.
|
||||
|
||||
|
||||
Skip-worktree bit
|
||||
-----------------
|
||||
|
||||
Skip-worktree bit can be defined in one (long) sentence: When reading
|
||||
an entry, if it is marked as skip-worktree, then Git pretends its
|
||||
working directory version is up to date and read the index version
|
||||
instead.
|
||||
|
||||
To elaborate, "reading" means checking for file existence, reading
|
||||
file attributes or file content. The working directory version may be
|
||||
present or absent. If present, its content may match against the index
|
||||
version or not. Writing is not affected by this bit, content safety
|
||||
is still first priority. Note that Git _can_ update working directory
|
||||
file, that is marked skip-worktree, if it is safe to do so (i.e.
|
||||
working directory version matches index version)
|
||||
|
||||
Although this bit looks similar to assume-unchanged bit, its goal is
|
||||
different from assume-unchanged bit's. Skip-worktree also takes
|
||||
precedence over assume-unchanged bit when both are set.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ The result of the enumeration is left in these fields::
|
||||
Calling sequence
|
||||
----------------
|
||||
|
||||
Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE
|
||||
marked. If you to exclude files, make sure you have loaded index first.
|
||||
|
||||
* Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
|
||||
sizeof(dir))`.
|
||||
|
||||
|
||||
28
Makefile
28
Makefile
@@ -424,6 +424,16 @@ BUILT_INS += git-stage$X
|
||||
BUILT_INS += git-status$X
|
||||
BUILT_INS += git-whatchanged$X
|
||||
|
||||
ifdef NO_CURL
|
||||
REMOTE_CURL_PRIMARY =
|
||||
REMOTE_CURL_ALIASES =
|
||||
REMOTE_CURL_NAMES =
|
||||
else
|
||||
REMOTE_CURL_PRIMARY = git-remote-http$X
|
||||
REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
|
||||
REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
|
||||
endif
|
||||
|
||||
# what 'all' will build and 'install' will install in gitexecdir,
|
||||
# excluding programs for built-in commands
|
||||
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
|
||||
@@ -1107,7 +1117,7 @@ else
|
||||
else
|
||||
CURL_LIBCURL = -lcurl
|
||||
endif
|
||||
PROGRAMS += git-remote-curl$X git-http-fetch$X
|
||||
PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X
|
||||
curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
|
||||
ifeq "$(curl_check)" "070908"
|
||||
ifndef NO_EXPAT
|
||||
@@ -1686,7 +1696,13 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
|
||||
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
|
||||
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
|
||||
|
||||
git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS)
|
||||
$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
|
||||
$(QUIET_LNCP)$(RM) $@ && \
|
||||
ln $< $@ 2>/dev/null || \
|
||||
ln -s $< $@ 2>/dev/null || \
|
||||
cp $< $@
|
||||
|
||||
$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
|
||||
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
|
||||
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
|
||||
|
||||
@@ -1782,6 +1798,7 @@ TEST_PROGRAMS_NEED_X += test-parse-options
|
||||
TEST_PROGRAMS_NEED_X += test-path-utils
|
||||
TEST_PROGRAMS_NEED_X += test-sha1
|
||||
TEST_PROGRAMS_NEED_X += test-sigchain
|
||||
TEST_PROGRAMS_NEED_X += test-index-version
|
||||
|
||||
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
|
||||
|
||||
@@ -1876,6 +1893,7 @@ endif
|
||||
ifneq (,$X)
|
||||
$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
|
||||
endif
|
||||
|
||||
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
|
||||
execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
|
||||
{ test "$$bindir/" = "$$execdir/" || \
|
||||
@@ -1889,6 +1907,12 @@ endif
|
||||
ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
|
||||
cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
|
||||
done; } && \
|
||||
{ for p in $(REMOTE_CURL_ALIASES); do \
|
||||
$(RM) "$$execdir/$$p" && \
|
||||
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
|
||||
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
|
||||
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
|
||||
done; } && \
|
||||
./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
|
||||
|
||||
install-doc:
|
||||
|
||||
25
archive.c
25
archive.c
@@ -211,10 +211,33 @@ static const struct archiver *lookup_archiver(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int reject_entry(const unsigned char *sha1, const char *base,
|
||||
int baselen, const char *filename, unsigned mode,
|
||||
int stage, void *context)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int path_exists(struct tree *tree, const char *path)
|
||||
{
|
||||
const char *pathspec[] = { path, NULL };
|
||||
|
||||
if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parse_pathspec_arg(const char **pathspec,
|
||||
struct archiver_args *ar_args)
|
||||
{
|
||||
ar_args->pathspec = get_pathspec("", pathspec);
|
||||
ar_args->pathspec = pathspec = get_pathspec("", pathspec);
|
||||
if (pathspec) {
|
||||
while (*pathspec) {
|
||||
if (!path_exists(ar_args->tree, *pathspec))
|
||||
die("path not found: %s", *pathspec);
|
||||
pathspec++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_treeish_arg(const char **argv,
|
||||
|
||||
@@ -2666,7 +2666,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
|
||||
return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
}
|
||||
|
||||
static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "archive.h"
|
||||
#include "transport.h"
|
||||
#include "parse-options.h"
|
||||
#include "pkt-line.h"
|
||||
#include "sideband.h"
|
||||
@@ -25,12 +26,16 @@ static void create_output_file(const char *output_file)
|
||||
static int run_remote_archiver(int argc, const char **argv,
|
||||
const char *remote, const char *exec)
|
||||
{
|
||||
char *url, buf[LARGE_PACKET_MAX];
|
||||
char buf[LARGE_PACKET_MAX];
|
||||
int fd[2], i, len, rv;
|
||||
struct child_process *conn;
|
||||
struct transport *transport;
|
||||
struct remote *_remote;
|
||||
|
||||
url = xstrdup(remote);
|
||||
conn = git_connect(fd, url, exec, 0);
|
||||
_remote = remote_get(remote);
|
||||
if (!_remote->url[0])
|
||||
die("git archive: Remote with no URL");
|
||||
transport = transport_get(_remote, _remote->url[0]);
|
||||
transport_connect(transport, "git-upload-archive", exec, fd);
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
packet_write(fd[1], "argument %s\n", argv[i]);
|
||||
@@ -53,9 +58,7 @@ static int run_remote_archiver(int argc, const char **argv,
|
||||
|
||||
/* Now, start reading from fd[0] and spit it out to stdout */
|
||||
rv = recv_sideband("archive", fd[0], 1);
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
rv |= finish_connect(conn);
|
||||
rv |= transport_disconnect(transport);
|
||||
|
||||
return !!rv;
|
||||
}
|
||||
|
||||
@@ -696,7 +696,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
* case 3: git checkout <something> [<paths>]
|
||||
*
|
||||
* With no paths, if <something> is a commit, that is to
|
||||
* switch to the branch or detach HEAD at it.
|
||||
* switch to the branch or detach HEAD at it. As a special case,
|
||||
* if <something> is A...B (missing A or B means HEAD but you can
|
||||
* omit at most one side), and if there is a unique merge base
|
||||
* between A and B, A...B names that merge base.
|
||||
*
|
||||
* With no paths, if <something> is _not_ a commit, no -t nor -b
|
||||
* was given, and there is a tracking branch whose name is
|
||||
@@ -722,7 +725,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
||||
if (!strcmp(arg, "-"))
|
||||
arg = "@{-1}";
|
||||
|
||||
if (get_sha1(arg, rev)) {
|
||||
if (get_sha1_mb(arg, rev)) {
|
||||
if (has_dash_dash) /* case (1) */
|
||||
die("invalid reference: %s", arg);
|
||||
if (!patch_mode &&
|
||||
|
||||
@@ -75,11 +75,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
||||
|
||||
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
||||
|
||||
if (read_cache() < 0)
|
||||
die("index file corrupt");
|
||||
|
||||
if (!ignored)
|
||||
setup_standard_excludes(&dir);
|
||||
|
||||
pathspec = get_pathspec(prefix, argv);
|
||||
read_cache();
|
||||
|
||||
fill_directory(&dir, pathspec);
|
||||
|
||||
|
||||
@@ -183,11 +183,15 @@ static int list_paths(struct string_list *list, const char *with_tree,
|
||||
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
struct string_list_item *item;
|
||||
|
||||
if (ce->ce_flags & CE_UPDATE)
|
||||
continue;
|
||||
if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
|
||||
continue;
|
||||
string_list_insert(ce->name, list);
|
||||
item = string_list_insert(ce->name, list);
|
||||
if (ce_skip_worktree(ce))
|
||||
item->util = item; /* better a valid pointer than a fake one */
|
||||
}
|
||||
|
||||
return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
|
||||
@@ -200,6 +204,10 @@ static void add_remove_files(struct string_list *list)
|
||||
struct stat st;
|
||||
struct string_list_item *p = &(list->items[i]);
|
||||
|
||||
/* p->util is skip-worktree */
|
||||
if (p->util)
|
||||
continue;
|
||||
|
||||
if (!lstat(p->string, &st)) {
|
||||
if (add_to_cache(p->string, &st, 0))
|
||||
die("updating files failed");
|
||||
|
||||
@@ -191,8 +191,6 @@ static int grep_file(struct grep_opt *opt, const char *filename)
|
||||
error("'%s': %s", filename, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
if (!st.st_size)
|
||||
return 0; /* empty file -- no grep hit */
|
||||
if (!S_ISREG(st.st_mode))
|
||||
return 0;
|
||||
sz = xsize_t(st.st_size);
|
||||
@@ -222,6 +220,7 @@ static int exec_grep(int argc, const char **argv)
|
||||
int status;
|
||||
|
||||
argv[argc] = NULL;
|
||||
trace_argv_printf(argv, "trace: grep:");
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
return pid;
|
||||
@@ -347,6 +346,21 @@ static void grep_add_color(struct strbuf *sb, const char *escape_seq)
|
||||
strbuf_setlen(sb, sb->len - 1);
|
||||
}
|
||||
|
||||
static int has_skip_worktree_entry(struct grep_opt *opt, const char **paths)
|
||||
{
|
||||
int nr;
|
||||
for (nr = 0; nr < active_nr; nr++) {
|
||||
struct cache_entry *ce = active_cache[nr];
|
||||
if (!S_ISREG(ce->ce_mode))
|
||||
continue;
|
||||
if (!pathspec_matches(paths, ce->name, opt->max_depth))
|
||||
continue;
|
||||
if (ce_skip_worktree(ce))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
||||
{
|
||||
int i, nr, argc, hit, len, status;
|
||||
@@ -355,7 +369,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
||||
char *argptr = randarg;
|
||||
struct grep_pat *p;
|
||||
|
||||
if (opt->extended || (opt->relative && opt->prefix_length))
|
||||
if (opt->extended || (opt->relative && opt->prefix_length)
|
||||
|| has_skip_worktree_entry(opt, paths))
|
||||
return -1;
|
||||
len = nr = 0;
|
||||
push_arg("grep");
|
||||
@@ -512,7 +527,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
|
||||
* are identical, even if worktree file has been modified, so use
|
||||
* cache version instead
|
||||
*/
|
||||
if (cached || (ce->ce_flags & CE_VALID)) {
|
||||
if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
|
||||
if (ce_stage(ce))
|
||||
continue;
|
||||
hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
|
||||
|
||||
@@ -37,6 +37,7 @@ static const char *tag_removed = "";
|
||||
static const char *tag_other = "";
|
||||
static const char *tag_killed = "";
|
||||
static const char *tag_modified = "";
|
||||
static const char *tag_skip_worktree = "";
|
||||
|
||||
static void show_dir_entry(const char *tag, struct dir_entry *ent)
|
||||
{
|
||||
@@ -178,7 +179,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
|
||||
continue;
|
||||
if (ce->ce_flags & CE_UPDATE)
|
||||
continue;
|
||||
show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
|
||||
show_ce_entry(ce_stage(ce) ? tag_unmerged :
|
||||
(ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
|
||||
}
|
||||
}
|
||||
if (show_deleted | show_modified) {
|
||||
@@ -192,6 +194,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
|
||||
continue;
|
||||
if (ce->ce_flags & CE_UPDATE)
|
||||
continue;
|
||||
if (ce_skip_worktree(ce))
|
||||
continue;
|
||||
err = lstat(ce->name, &st);
|
||||
if (show_deleted && err)
|
||||
show_ce_entry(tag_removed, ce);
|
||||
@@ -481,6 +485,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
||||
prefix_offset = strlen(prefix);
|
||||
git_config(git_default_config, NULL);
|
||||
|
||||
if (read_cache() < 0)
|
||||
die("index file corrupt");
|
||||
|
||||
argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
|
||||
ls_files_usage, 0);
|
||||
if (show_tag || show_valid_bit) {
|
||||
@@ -490,6 +497,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
||||
tag_modified = "C ";
|
||||
tag_other = "? ";
|
||||
tag_killed = "K ";
|
||||
tag_skip_worktree = "S ";
|
||||
}
|
||||
if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
|
||||
require_work_tree = 1;
|
||||
@@ -508,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
||||
pathspec = get_pathspec(prefix, argv);
|
||||
|
||||
/* be nice with submodule paths ending in a slash */
|
||||
read_cache();
|
||||
if (pathspec)
|
||||
strip_trailing_slash_from_submodules();
|
||||
|
||||
|
||||
@@ -125,8 +125,8 @@ static int push_with_options(struct transport *transport, int flags)
|
||||
|
||||
if (nonfastforward && advice_push_nonfastforward) {
|
||||
printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
|
||||
"Merge the remote changes before pushing again. See the 'non-fast-forward'\n"
|
||||
"section of 'git push --help' for details.\n");
|
||||
"Merge the remote changes before pushing again. See the 'Note about\n"
|
||||
"fast-forwards' section of 'git push --help' for details.\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@@ -31,7 +31,7 @@ static int list_tree(unsigned char *sha1)
|
||||
}
|
||||
|
||||
static const char * const read_tree_usage[] = {
|
||||
"git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
|
||||
"git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
||||
PARSE_OPT_NONEG, exclude_per_directory_cb },
|
||||
OPT_SET_INT('i', NULL, &opts.index_only,
|
||||
"don't check the working tree after merging", 1),
|
||||
OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
|
||||
"skip applying sparse checkout filter", 1),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "tree.h"
|
||||
#include "branch.h"
|
||||
#include "parse-options.h"
|
||||
#include "unpack-trees.h"
|
||||
#include "cache-tree.h"
|
||||
|
||||
static const char * const git_reset_usage[] = {
|
||||
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
|
||||
@@ -54,27 +56,44 @@ static inline int is_merge(void)
|
||||
|
||||
static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
|
||||
{
|
||||
int i = 0;
|
||||
const char *args[6];
|
||||
int nr = 1;
|
||||
int newfd;
|
||||
struct tree_desc desc[2];
|
||||
struct unpack_trees_options opts;
|
||||
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
|
||||
|
||||
args[i++] = "read-tree";
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.head_idx = 1;
|
||||
opts.src_index = &the_index;
|
||||
opts.dst_index = &the_index;
|
||||
opts.fn = oneway_merge;
|
||||
opts.merge = 1;
|
||||
if (!quiet)
|
||||
args[i++] = "-v";
|
||||
opts.verbose_update = 1;
|
||||
switch (reset_type) {
|
||||
case MERGE:
|
||||
args[i++] = "-u";
|
||||
args[i++] = "-m";
|
||||
opts.update = 1;
|
||||
break;
|
||||
case HARD:
|
||||
args[i++] = "-u";
|
||||
opts.update = 1;
|
||||
/* fallthrough */
|
||||
default:
|
||||
args[i++] = "--reset";
|
||||
opts.reset = 1;
|
||||
}
|
||||
args[i++] = sha1_to_hex(sha1);
|
||||
args[i] = NULL;
|
||||
|
||||
return run_command_v_opt(args, RUN_GIT_CMD);
|
||||
newfd = hold_locked_index(lock, 1);
|
||||
|
||||
read_cache_unmerged();
|
||||
|
||||
if (!fill_tree_descriptor(desc + nr - 1, sha1))
|
||||
return error("Failed to find tree of %s.", sha1_to_hex(sha1));
|
||||
if (unpack_trees(nr, desc, &opts))
|
||||
return -1;
|
||||
if (write_cache(newfd, active_cache, active_nr) ||
|
||||
commit_locked_index(lock))
|
||||
return error("Could not write new index file.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_new_head_line(struct commit *commit)
|
||||
@@ -288,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
if (reset_type == HARD || reset_type == MERGE)
|
||||
setup_work_tree();
|
||||
|
||||
if (reset_type == MIXED && is_bare_repository())
|
||||
die("%s reset is not allowed in a bare repository",
|
||||
reset_type_names[reset_type]);
|
||||
|
||||
/* Soft reset does not touch the index file nor the working tree
|
||||
* at all, but requires them in a good order. Other resets reset
|
||||
* the index file to the tree object we are switching to. */
|
||||
|
||||
@@ -24,8 +24,9 @@ static int info_only;
|
||||
static int force_remove;
|
||||
static int verbose;
|
||||
static int mark_valid_only;
|
||||
#define MARK_VALID 1
|
||||
#define UNMARK_VALID 2
|
||||
static int mark_skip_worktree_only;
|
||||
#define MARK_FLAG 1
|
||||
#define UNMARK_FLAG 2
|
||||
|
||||
__attribute__((format (printf, 1, 2)))
|
||||
static void report(const char *fmt, ...)
|
||||
@@ -41,19 +42,15 @@ static void report(const char *fmt, ...)
|
||||
va_end(vp);
|
||||
}
|
||||
|
||||
static int mark_valid(const char *path)
|
||||
static int mark_ce_flags(const char *path, int flag, int mark)
|
||||
{
|
||||
int namelen = strlen(path);
|
||||
int pos = cache_name_pos(path, namelen);
|
||||
if (0 <= pos) {
|
||||
switch (mark_valid_only) {
|
||||
case MARK_VALID:
|
||||
active_cache[pos]->ce_flags |= CE_VALID;
|
||||
break;
|
||||
case UNMARK_VALID:
|
||||
active_cache[pos]->ce_flags &= ~CE_VALID;
|
||||
break;
|
||||
}
|
||||
if (mark)
|
||||
active_cache[pos]->ce_flags |= flag;
|
||||
else
|
||||
active_cache[pos]->ce_flags &= ~flag;
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
active_cache_changed = 1;
|
||||
return 0;
|
||||
@@ -176,29 +173,29 @@ static int process_directory(const char *path, int len, struct stat *st)
|
||||
return error("%s: is a directory - add files inside instead", path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a regular file
|
||||
*/
|
||||
static int process_file(const char *path, int len, struct stat *st)
|
||||
{
|
||||
int pos = cache_name_pos(path, len);
|
||||
struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
|
||||
|
||||
if (ce && S_ISGITLINK(ce->ce_mode))
|
||||
return error("%s is already a gitlink, not replacing", path);
|
||||
|
||||
return add_one_path(ce, path, len, st);
|
||||
}
|
||||
|
||||
static int process_path(const char *path)
|
||||
{
|
||||
int len;
|
||||
int pos, len;
|
||||
struct stat st;
|
||||
struct cache_entry *ce;
|
||||
|
||||
len = strlen(path);
|
||||
if (has_symlink_leading_path(path, len))
|
||||
return error("'%s' is beyond a symbolic link", path);
|
||||
|
||||
pos = cache_name_pos(path, len);
|
||||
ce = pos < 0 ? NULL : active_cache[pos];
|
||||
if (ce && ce_skip_worktree(ce)) {
|
||||
/*
|
||||
* working directory version is assumed "good"
|
||||
* so updating it does not make sense.
|
||||
* On the other hand, removing it from index should work
|
||||
*/
|
||||
if (allow_remove && remove_file_from_cache(path))
|
||||
return error("%s: cannot remove from the index", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* First things first: get the stat information, to decide
|
||||
* what to do about the pathname!
|
||||
@@ -209,7 +206,13 @@ static int process_path(const char *path)
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return process_directory(path, len, &st);
|
||||
|
||||
return process_file(path, len, &st);
|
||||
/*
|
||||
* Process a regular file
|
||||
*/
|
||||
if (ce && S_ISGITLINK(ce->ce_mode))
|
||||
return error("%s is already a gitlink, not replacing", path);
|
||||
|
||||
return add_one_path(ce, path, len, &st);
|
||||
}
|
||||
|
||||
static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
|
||||
@@ -277,7 +280,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
|
||||
goto free_return;
|
||||
}
|
||||
if (mark_valid_only) {
|
||||
if (mark_valid(p))
|
||||
if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
|
||||
die("Unable to mark file %s", path);
|
||||
goto free_return;
|
||||
}
|
||||
if (mark_skip_worktree_only) {
|
||||
if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
|
||||
die("Unable to mark file %s", path);
|
||||
goto free_return;
|
||||
}
|
||||
@@ -389,7 +397,7 @@ static void read_index_info(int line_termination)
|
||||
}
|
||||
|
||||
static const char update_index_usage[] =
|
||||
"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
|
||||
"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
|
||||
|
||||
static unsigned char head_sha1[20];
|
||||
static unsigned char merge_head_sha1[20];
|
||||
@@ -648,11 +656,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--assume-unchanged")) {
|
||||
mark_valid_only = MARK_VALID;
|
||||
mark_valid_only = MARK_FLAG;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--no-assume-unchanged")) {
|
||||
mark_valid_only = UNMARK_VALID;
|
||||
mark_valid_only = UNMARK_FLAG;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--no-skip-worktree")) {
|
||||
mark_skip_worktree_only = UNMARK_FLAG;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--skip-worktree")) {
|
||||
mark_skip_worktree_only = MARK_FLAG;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--info-only")) {
|
||||
|
||||
14
cache.h
14
cache.h
@@ -177,15 +177,20 @@ struct cache_entry {
|
||||
|
||||
#define CE_HASHED (0x100000)
|
||||
#define CE_UNHASHED (0x200000)
|
||||
#define CE_CONFLICTED (0x800000)
|
||||
|
||||
/* Only remove in work directory, not index */
|
||||
#define CE_WT_REMOVE (0x400000)
|
||||
|
||||
/*
|
||||
* Extended on-disk flags
|
||||
*/
|
||||
#define CE_INTENT_TO_ADD 0x20000000
|
||||
#define CE_SKIP_WORKTREE 0x40000000
|
||||
/* CE_EXTENDED2 is for future extension */
|
||||
#define CE_EXTENDED2 0x80000000
|
||||
|
||||
#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
|
||||
#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
|
||||
|
||||
/*
|
||||
* Safeguard to avoid saving wrong flags:
|
||||
@@ -234,6 +239,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
|
||||
ondisk_cache_entry_size(ce_namelen(ce)))
|
||||
#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
|
||||
#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
|
||||
#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
|
||||
#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
|
||||
|
||||
#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
|
||||
@@ -464,7 +470,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
|
||||
/* do stat comparison even if CE_VALID is true */
|
||||
#define CE_MATCH_IGNORE_VALID 01
|
||||
/* do not check the contents but report dirty on racily-clean entries */
|
||||
#define CE_MATCH_RACY_IS_DIRTY 02
|
||||
#define CE_MATCH_RACY_IS_DIRTY 02
|
||||
/* do stat comparison even if CE_SKIP_WORKTREE is true */
|
||||
#define CE_MATCH_IGNORE_SKIP_WORKTREE 04
|
||||
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
||||
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
||||
|
||||
@@ -529,6 +537,7 @@ extern int auto_crlf;
|
||||
extern int read_replace_refs;
|
||||
extern int fsync_object_files;
|
||||
extern int core_preload_index;
|
||||
extern int core_apply_sparse_checkout;
|
||||
|
||||
enum safe_crlf {
|
||||
SAFE_CRLF_FALSE = 0,
|
||||
@@ -714,6 +723,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
|
||||
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
|
||||
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
|
||||
extern int interpret_branch_name(const char *str, struct strbuf *);
|
||||
extern int get_sha1_mb(const char *str, unsigned char *sha1);
|
||||
|
||||
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
|
||||
extern const char *ref_rev_parse_rules[];
|
||||
|
||||
5
config.c
5
config.c
@@ -518,6 +518,11 @@ static int git_default_core_config(const char *var, const char *value)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.sparsecheckout")) {
|
||||
core_apply_sparse_checkout = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add other config variables here and to Documentation/config.txt. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -59,14 +59,14 @@ def getgitenv(user, date):
|
||||
elems = re.compile('(.*?)\s+<(.*)>').match(user)
|
||||
if elems:
|
||||
env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
|
||||
env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
|
||||
env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
|
||||
env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
|
||||
env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
|
||||
env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
|
||||
else:
|
||||
env += 'export GIT_AUTHOR_NAME="%s" ;' % user
|
||||
env += 'export GIT_COMMITER_NAME="%s" ;' % user
|
||||
env += 'export GIT_COMMITTER_NAME="%s" ;' % user
|
||||
env += 'export GIT_AUTHOR_EMAIL= ;'
|
||||
env += 'export GIT_COMMITER_EMAIL= ;'
|
||||
env += 'export GIT_COMMITTER_EMAIL= ;'
|
||||
|
||||
env += 'export GIT_AUTHOR_DATE="%s" ;' % date
|
||||
env += 'export GIT_COMMITTER_DATE="%s" ;' % date
|
||||
|
||||
@@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ce_uptodate(ce))
|
||||
if (ce_uptodate(ce) || ce_skip_worktree(ce))
|
||||
continue;
|
||||
|
||||
/* If CE_VALID is set, don't look at workdir for file removal */
|
||||
@@ -323,7 +323,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
|
||||
int match_missing, cached;
|
||||
|
||||
/* if the entry is not checked out, don't examine work tree */
|
||||
cached = o->index_only || (idx && (idx->ce_flags & CE_VALID));
|
||||
cached = o->index_only ||
|
||||
(idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
|
||||
/*
|
||||
* Backward compatibility wart - "diff-index -m" does
|
||||
* not mean "do not ignore merges", but "match_missing".
|
||||
|
||||
2
diff.c
2
diff.c
@@ -1997,7 +1997,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
|
||||
* If ce is marked as "assume unchanged", there is no
|
||||
* guarantee that work tree matches what we are looking for.
|
||||
*/
|
||||
if (ce->ce_flags & CE_VALID)
|
||||
if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
||||
100
dir.c
100
dir.c
@@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base,
|
||||
which->excludes[which->nr++] = x;
|
||||
}
|
||||
|
||||
static int add_excludes_from_file_1(const char *fname,
|
||||
const char *base,
|
||||
int baselen,
|
||||
char **buf_p,
|
||||
struct exclude_list *which)
|
||||
static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
|
||||
{
|
||||
int pos, len;
|
||||
unsigned long sz;
|
||||
enum object_type type;
|
||||
void *data;
|
||||
struct index_state *istate = &the_index;
|
||||
|
||||
len = strlen(path);
|
||||
pos = index_name_pos(istate, path, len);
|
||||
if (pos < 0)
|
||||
return NULL;
|
||||
if (!ce_skip_worktree(istate->cache[pos]))
|
||||
return NULL;
|
||||
data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
|
||||
if (!data || type != OBJ_BLOB) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
*size = xsize_t(sz);
|
||||
return data;
|
||||
}
|
||||
|
||||
int add_excludes_from_file_to_list(const char *fname,
|
||||
const char *base,
|
||||
int baselen,
|
||||
char **buf_p,
|
||||
struct exclude_list *which,
|
||||
int check_index)
|
||||
{
|
||||
struct stat st;
|
||||
int fd, i;
|
||||
@@ -212,27 +236,32 @@ static int add_excludes_from_file_1(const char *fname,
|
||||
char *buf, *entry;
|
||||
|
||||
fd = open(fname, O_RDONLY);
|
||||
if (fd < 0 || fstat(fd, &st) < 0)
|
||||
goto err;
|
||||
size = xsize_t(st.st_size);
|
||||
if (size == 0) {
|
||||
if (fd < 0 || fstat(fd, &st) < 0) {
|
||||
if (0 <= fd)
|
||||
close(fd);
|
||||
if (!check_index ||
|
||||
(buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
size = xsize_t(st.st_size);
|
||||
if (size == 0) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
buf = xmalloc(size);
|
||||
if (read_in_full(fd, buf, size) != size) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
buf = xmalloc(size+1);
|
||||
if (read_in_full(fd, buf, size) != size)
|
||||
{
|
||||
free(buf);
|
||||
goto err;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (buf_p)
|
||||
*buf_p = buf;
|
||||
buf[size++] = '\n';
|
||||
entry = buf;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (buf[i] == '\n') {
|
||||
for (i = 0; i <= size; i++) {
|
||||
if (i == size || buf[i] == '\n') {
|
||||
if (entry != buf + i && entry[0] != '#') {
|
||||
buf[i - (i && buf[i-1] == '\r')] = 0;
|
||||
add_exclude(entry, base, baselen, which);
|
||||
@@ -241,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname,
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (0 <= fd)
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void add_excludes_from_file(struct dir_struct *dir, const char *fname)
|
||||
{
|
||||
if (add_excludes_from_file_1(fname, "", 0, NULL,
|
||||
&dir->exclude_list[EXC_FILE]) < 0)
|
||||
if (add_excludes_from_file_to_list(fname, "", 0, NULL,
|
||||
&dir->exclude_list[EXC_FILE], 0) < 0)
|
||||
die("cannot use %s as an exclude file", fname);
|
||||
}
|
||||
|
||||
@@ -300,9 +324,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
|
||||
memcpy(dir->basebuf + current, base + current,
|
||||
stk->baselen - current);
|
||||
strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
|
||||
add_excludes_from_file_1(dir->basebuf,
|
||||
dir->basebuf, stk->baselen,
|
||||
&stk->filebuf, el);
|
||||
add_excludes_from_file_to_list(dir->basebuf,
|
||||
dir->basebuf, stk->baselen,
|
||||
&stk->filebuf, el, 1);
|
||||
dir->exclude_stack = stk;
|
||||
current = stk->baselen;
|
||||
}
|
||||
@@ -312,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
|
||||
/* Scan the list and let the last match determine the fate.
|
||||
* Return 1 for exclude, 0 for include and -1 for undecided.
|
||||
*/
|
||||
static int excluded_1(const char *pathname,
|
||||
int pathlen, const char *basename, int *dtype,
|
||||
struct exclude_list *el)
|
||||
int excluded_from_list(const char *pathname,
|
||||
int pathlen, const char *basename, int *dtype,
|
||||
struct exclude_list *el)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -325,6 +349,12 @@ static int excluded_1(const char *pathname,
|
||||
int to_exclude = x->to_exclude;
|
||||
|
||||
if (x->flags & EXC_FLAG_MUSTBEDIR) {
|
||||
if (!dtype) {
|
||||
if (!prefixcmp(pathname, exclude))
|
||||
return to_exclude;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
if (*dtype == DT_UNKNOWN)
|
||||
*dtype = get_dtype(NULL, pathname, pathlen);
|
||||
if (*dtype != DT_DIR)
|
||||
@@ -382,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
|
||||
|
||||
prep_exclude(dir, pathname, basename-pathname);
|
||||
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
|
||||
switch (excluded_1(pathname, pathlen, basename,
|
||||
dtype_p, &dir->exclude_list[st])) {
|
||||
switch (excluded_from_list(pathname, pathlen, basename,
|
||||
dtype_p, &dir->exclude_list[st])) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
|
||||
4
dir.h
4
dir.h
@@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
|
||||
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
|
||||
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
|
||||
|
||||
extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
|
||||
int *dtype, struct exclude_list *el);
|
||||
extern int excluded(struct dir_struct *, const char *, int *);
|
||||
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
|
||||
char **buf_p, struct exclude_list *which, int check_index);
|
||||
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
|
||||
extern void add_exclude(const char *string, const char *base,
|
||||
int baselen, struct exclude_list *which);
|
||||
|
||||
2
entry.c
2
entry.c
@@ -206,7 +206,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
|
||||
len += ce_namelen(ce);
|
||||
|
||||
if (!check_path(path, len, &st, state->base_dir_len)) {
|
||||
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
|
||||
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
if (!changed)
|
||||
return 0;
|
||||
if (!state->force) {
|
||||
|
||||
@@ -51,6 +51,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
|
||||
enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
|
||||
char *notes_ref_name;
|
||||
int grafts_replace_parents = 1;
|
||||
int core_apply_sparse_checkout;
|
||||
|
||||
/* Parallel index stat data preload? */
|
||||
int core_preload_index = 0;
|
||||
|
||||
@@ -322,7 +322,7 @@ make_squash_message () {
|
||||
}
|
||||
|
||||
peek_next_command () {
|
||||
sed -n "1s/ .*$//p" < "$TODO"
|
||||
sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO"
|
||||
}
|
||||
|
||||
do_next () {
|
||||
@@ -495,6 +495,25 @@ get_saved_options () {
|
||||
test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
|
||||
}
|
||||
|
||||
LF='
|
||||
'
|
||||
parse_onto () {
|
||||
case "$1" in
|
||||
*...*)
|
||||
if left=${1%...*} right=${1#*...} &&
|
||||
onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
|
||||
then
|
||||
case "$onto" in
|
||||
?*"$LF"?* | '')
|
||||
exit 1 ;;
|
||||
esac
|
||||
echo "$onto"
|
||||
exit 0
|
||||
fi
|
||||
esac
|
||||
git rev-parse --verify "$1^0"
|
||||
}
|
||||
|
||||
while test $# != 0
|
||||
do
|
||||
case "$1" in
|
||||
@@ -602,7 +621,7 @@ first and then run 'git rebase --continue' again."
|
||||
;;
|
||||
--onto)
|
||||
shift
|
||||
ONTO=$(git rev-parse --verify "$1") ||
|
||||
ONTO=$(parse_onto "$1") ||
|
||||
die "Does not point to a valid commit: $1"
|
||||
;;
|
||||
--)
|
||||
|
||||
@@ -34,6 +34,8 @@ set_reflog_action rebase
|
||||
require_work_tree
|
||||
cd_to_toplevel
|
||||
|
||||
LF='
|
||||
'
|
||||
OK_TO_SKIP_PRE_REBASE=
|
||||
RESOLVEMSG="
|
||||
When you have resolved this problem run \"git rebase --continue\".
|
||||
@@ -417,7 +419,27 @@ fi
|
||||
|
||||
# Make sure the branch to rebase onto is valid.
|
||||
onto_name=${newbase-"$upstream_name"}
|
||||
onto=$(git rev-parse --verify "${onto_name}^0") || exit
|
||||
case "$onto_name" in
|
||||
*...*)
|
||||
if left=${onto_name%...*} right=${onto_name#*...} &&
|
||||
onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
|
||||
then
|
||||
case "$onto" in
|
||||
?*"$LF"?*)
|
||||
die "$onto_name: there are more than one merge bases"
|
||||
;;
|
||||
'')
|
||||
die "$onto_name: there is no merge base"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
die "$onto_name: there is no merge base"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
onto=$(git rev-parse --verify "${onto_name}^0") || exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# If a hook exists, give it a chance to interrupt
|
||||
run_pre_rebase_hook "$upstream_arg" "$@"
|
||||
|
||||
@@ -164,9 +164,10 @@ static char *unable_to_lock_message(const char *path, int err)
|
||||
"If no other git process is currently running, this probably means a\n"
|
||||
"git process crashed in this repository earlier. Make sure no other git\n"
|
||||
"process is running and remove the file manually to continue.",
|
||||
path, strerror(err));
|
||||
make_nonrelative_path(path), strerror(err));
|
||||
} else
|
||||
strbuf_addf(&buf, "Unable to create '%s.lock': %s", path, strerror(err));
|
||||
strbuf_addf(&buf, "Unable to create '%s.lock': %s",
|
||||
make_nonrelative_path(path), strerror(err));
|
||||
return strbuf_detach(&buf, NULL);
|
||||
}
|
||||
|
||||
|
||||
20
read-cache.c
20
read-cache.c
@@ -259,12 +259,17 @@ int ie_match_stat(const struct index_state *istate,
|
||||
{
|
||||
unsigned int changed;
|
||||
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
||||
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
||||
int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
|
||||
|
||||
/*
|
||||
* If it's marked as always valid in the index, it's
|
||||
* valid whatever the checked-out copy says.
|
||||
*
|
||||
* skip-worktree has the same effect with higher precedence
|
||||
*/
|
||||
if (!ignore_skip_worktree && ce_skip_worktree(ce))
|
||||
return 0;
|
||||
if (!ignore_valid && (ce->ce_flags & CE_VALID))
|
||||
return 0;
|
||||
|
||||
@@ -564,7 +569,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
|
||||
int size, namelen, was_same;
|
||||
mode_t st_mode = st->st_mode;
|
||||
struct cache_entry *ce, *alias;
|
||||
unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
|
||||
unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
|
||||
int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
|
||||
int pretend = flags & ADD_CACHE_PRETEND;
|
||||
int intent_only = flags & ADD_CACHE_INTENT;
|
||||
@@ -1000,14 +1005,20 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
|
||||
struct cache_entry *updated;
|
||||
int changed, size;
|
||||
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
||||
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
||||
|
||||
if (ce_uptodate(ce))
|
||||
return ce;
|
||||
|
||||
/*
|
||||
* CE_VALID means the user promised us that the change to
|
||||
* the work tree does not matter and told us not to worry.
|
||||
* CE_VALID or CE_SKIP_WORKTREE means the user promised us
|
||||
* that the change to the work tree does not matter and told
|
||||
* us not to worry.
|
||||
*/
|
||||
if (!ignore_skip_worktree && ce_skip_worktree(ce)) {
|
||||
ce_mark_uptodate(ce);
|
||||
return ce;
|
||||
}
|
||||
if (!ignore_valid && (ce->ce_flags & CE_VALID)) {
|
||||
ce_mark_uptodate(ce);
|
||||
return ce;
|
||||
@@ -1606,9 +1617,8 @@ int read_index_unmerged(struct index_state *istate)
|
||||
len = strlen(ce->name);
|
||||
size = cache_entry_size(len);
|
||||
new_ce = xcalloc(1, size);
|
||||
hashcpy(new_ce->sha1, ce->sha1);
|
||||
memcpy(new_ce->name, ce->name, len);
|
||||
new_ce->ce_flags = create_ce_flags(len, 0);
|
||||
new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
|
||||
new_ce->ce_mode = ce->ce_mode;
|
||||
if (add_index_entry(istate, new_ce, 0))
|
||||
return error("%s: cannot drop to stage #0",
|
||||
|
||||
@@ -510,7 +510,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
|
||||
strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc);
|
||||
rpc->hdr_content_type = strbuf_detach(&buf, NULL);
|
||||
|
||||
strbuf_addf(&buf, "Accept: application/x-%s-response", svc);
|
||||
strbuf_addf(&buf, "Accept: application/x-%s-result", svc);
|
||||
rpc->hdr_accept = strbuf_detach(&buf, NULL);
|
||||
|
||||
while (!err) {
|
||||
|
||||
42
sha1_name.c
42
sha1_name.c
@@ -794,6 +794,48 @@ release_return:
|
||||
return retval;
|
||||
}
|
||||
|
||||
int get_sha1_mb(const char *name, unsigned char *sha1)
|
||||
{
|
||||
struct commit *one, *two;
|
||||
struct commit_list *mbs;
|
||||
unsigned char sha1_tmp[20];
|
||||
const char *dots;
|
||||
int st;
|
||||
|
||||
dots = strstr(name, "...");
|
||||
if (!dots)
|
||||
return get_sha1(name, sha1);
|
||||
if (dots == name)
|
||||
st = get_sha1("HEAD", sha1_tmp);
|
||||
else {
|
||||
struct strbuf sb;
|
||||
strbuf_init(&sb, dots - name);
|
||||
strbuf_add(&sb, name, dots - name);
|
||||
st = get_sha1(sb.buf, sha1_tmp);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
if (st)
|
||||
return st;
|
||||
one = lookup_commit_reference_gently(sha1_tmp, 0);
|
||||
if (!one)
|
||||
return -1;
|
||||
|
||||
if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
|
||||
return -1;
|
||||
two = lookup_commit_reference_gently(sha1_tmp, 0);
|
||||
if (!two)
|
||||
return -1;
|
||||
mbs = get_merge_bases(one, two, 1);
|
||||
if (!mbs || mbs->next)
|
||||
st = -1;
|
||||
else {
|
||||
st = 0;
|
||||
hashcpy(sha1, mbs->item->object.sha1);
|
||||
}
|
||||
free_commit_list(mbs);
|
||||
return st;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
|
||||
* notably "xyz^" for "parent of xyz"
|
||||
|
||||
150
t/t1011-read-tree-sparse-checkout.sh
Executable file
150
t/t1011-read-tree-sparse-checkout.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='sparse checkout tests'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
cat >expected <<EOF
|
||||
100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
|
||||
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
|
||||
EOF
|
||||
test_expect_success 'setup' '
|
||||
test_commit init &&
|
||||
echo modified >> init.t &&
|
||||
mkdir sub &&
|
||||
touch sub/added &&
|
||||
git add init.t sub/added &&
|
||||
git commit -m "modified and added" &&
|
||||
git tag top &&
|
||||
git rm sub/added &&
|
||||
git commit -m removed &&
|
||||
git tag removed &&
|
||||
git checkout top &&
|
||||
git ls-files --stage > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
cat >expected.swt <<EOF
|
||||
H init.t
|
||||
H sub/added
|
||||
EOF
|
||||
test_expect_success 'read-tree without .git/info/sparse-checkout' '
|
||||
git read-tree -m -u HEAD &&
|
||||
git ls-files --stage > result &&
|
||||
test_cmp expected result &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
|
||||
echo > .git/info/sparse-checkout
|
||||
git read-tree -m -u HEAD &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test -f init.t &&
|
||||
test -f sub/added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
|
||||
git config core.sparsecheckout true &&
|
||||
echo > .git/info/sparse-checkout &&
|
||||
git read-tree --no-sparse-checkout -m -u HEAD &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test -f init.t &&
|
||||
test -f sub/added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
|
||||
git config core.sparsecheckout true &&
|
||||
echo > .git/info/sparse-checkout &&
|
||||
test_must_fail git read-tree -m -u HEAD &&
|
||||
git ls-files --stage > result &&
|
||||
test_cmp expected result &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test -f init.t &&
|
||||
test -f sub/added
|
||||
'
|
||||
|
||||
cat >expected.swt <<EOF
|
||||
S init.t
|
||||
H sub/added
|
||||
EOF
|
||||
test_expect_success 'match directories with trailing slash' '
|
||||
echo sub/ > .git/info/sparse-checkout &&
|
||||
git read-tree -m -u HEAD &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test ! -f init.t &&
|
||||
test -f sub/added
|
||||
'
|
||||
|
||||
cat >expected.swt <<EOF
|
||||
H init.t
|
||||
H sub/added
|
||||
EOF
|
||||
test_expect_failure 'match directories without trailing slash' '
|
||||
echo init.t > .git/info/sparse-checkout &&
|
||||
echo sub >> .git/info/sparse-checkout &&
|
||||
git read-tree -m -u HEAD &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test ! -f init.t &&
|
||||
test -f sub/added
|
||||
'
|
||||
|
||||
cat >expected.swt <<EOF
|
||||
H init.t
|
||||
S sub/added
|
||||
EOF
|
||||
test_expect_success 'checkout area changes' '
|
||||
echo init.t > .git/info/sparse-checkout &&
|
||||
git read-tree -m -u HEAD &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected.swt result &&
|
||||
test -f init.t &&
|
||||
test ! -f sub/added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree updates worktree, absent case' '
|
||||
echo sub/added > .git/info/sparse-checkout &&
|
||||
git checkout -f top &&
|
||||
git read-tree -m -u HEAD^ &&
|
||||
test ! -f init.t
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree updates worktree, dirty case' '
|
||||
echo sub/added > .git/info/sparse-checkout &&
|
||||
git checkout -f top &&
|
||||
echo dirty > init.t &&
|
||||
git read-tree -m -u HEAD^ &&
|
||||
grep -q dirty init.t &&
|
||||
rm init.t
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree removes worktree, dirty case' '
|
||||
echo init.t > .git/info/sparse-checkout &&
|
||||
git checkout -f top &&
|
||||
echo dirty > added &&
|
||||
git read-tree -m -u HEAD^ &&
|
||||
grep -q dirty added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree adds to worktree, absent case' '
|
||||
echo init.t > .git/info/sparse-checkout &&
|
||||
git checkout -f removed &&
|
||||
git read-tree -u -m HEAD^ &&
|
||||
test ! -f sub/added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree adds to worktree, dirty case' '
|
||||
echo init.t > .git/info/sparse-checkout &&
|
||||
git checkout -f removed &&
|
||||
mkdir sub &&
|
||||
echo dirty > sub/added &&
|
||||
git read-tree -u -m HEAD^ &&
|
||||
grep -q dirty sub/added
|
||||
'
|
||||
|
||||
test_done
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='checkout can switch to last branch'
|
||||
test_description='checkout can switch to last branch and merge base'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
@@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '
|
||||
test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
|
||||
'
|
||||
|
||||
test_expect_success 'merge base test setup' '
|
||||
git checkout -b another other &&
|
||||
echo "hello again" >>world &&
|
||||
git add world &&
|
||||
git commit -m third
|
||||
'
|
||||
|
||||
test_expect_success 'another...master' '
|
||||
git checkout another &&
|
||||
git checkout another...master &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_expect_success '...master' '
|
||||
git checkout another &&
|
||||
git checkout ...master &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_expect_success 'master...' '
|
||||
git checkout another &&
|
||||
git checkout master... &&
|
||||
test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
57
t/t2104-update-index-skip-worktree.sh
Executable file
57
t/t2104-update-index-skip-worktree.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
|
||||
#
|
||||
|
||||
test_description='skip-worktree bit test'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
cat >expect.full <<EOF
|
||||
H 1
|
||||
H 2
|
||||
H sub/1
|
||||
H sub/2
|
||||
EOF
|
||||
|
||||
cat >expect.skip <<EOF
|
||||
S 1
|
||||
H 2
|
||||
S sub/1
|
||||
H sub/2
|
||||
EOF
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir sub &&
|
||||
touch ./1 ./2 sub/1 sub/2 &&
|
||||
git add 1 2 sub/1 sub/2 &&
|
||||
git ls-files -t | test_cmp expect.full -
|
||||
'
|
||||
|
||||
test_expect_success 'index is at version 2' '
|
||||
test "$(test-index-version < .git/index)" = 2
|
||||
'
|
||||
|
||||
test_expect_success 'update-index --skip-worktree' '
|
||||
git update-index --skip-worktree 1 sub/1 &&
|
||||
git ls-files -t | test_cmp expect.skip -
|
||||
'
|
||||
|
||||
test_expect_success 'index is at version 3 after having some skip-worktree entries' '
|
||||
test "$(test-index-version < .git/index)" = 3
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files -t' '
|
||||
git ls-files -t | test_cmp expect.skip -
|
||||
'
|
||||
|
||||
test_expect_success 'update-index --no-skip-worktree' '
|
||||
git update-index --no-skip-worktree 1 sub/1 &&
|
||||
git ls-files -t | test_cmp expect.full -
|
||||
'
|
||||
|
||||
test_expect_success 'index version is back to 2 when there is no skip-worktree entry' '
|
||||
test "$(test-index-version < .git/index)" = 2
|
||||
'
|
||||
|
||||
test_done
|
||||
@@ -64,6 +64,8 @@ two/*.4
|
||||
echo '!*.2
|
||||
!*.8' >one/two/.gitignore
|
||||
|
||||
allignores='.gitignore one/.gitignore one/two/.gitignore'
|
||||
|
||||
test_expect_success \
|
||||
'git ls-files --others with various exclude options.' \
|
||||
'git ls-files --others \
|
||||
@@ -85,6 +87,26 @@ test_expect_success \
|
||||
>output &&
|
||||
test_cmp expect output'
|
||||
|
||||
test_expect_success 'setup skip-worktree gitignore' '
|
||||
git add $allignores &&
|
||||
git update-index --skip-worktree $allignores &&
|
||||
rm $allignores
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'git ls-files --others with various exclude options.' \
|
||||
'git ls-files --others \
|
||||
--exclude=\*.6 \
|
||||
--exclude-per-directory=.gitignore \
|
||||
--exclude-from=.git/ignore \
|
||||
>output &&
|
||||
test_cmp expect output'
|
||||
|
||||
test_expect_success 'restore gitignore' '
|
||||
git checkout $allignores &&
|
||||
rm .git/index
|
||||
'
|
||||
|
||||
cat > excludes-file <<\EOF
|
||||
*.[1-8]
|
||||
e*
|
||||
|
||||
105
t/t3415-rebase-onto-threedots.sh
Executable file
105
t/t3415-rebase-onto-threedots.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git rebase --onto A...B'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY/lib-rebase.sh"
|
||||
|
||||
# Rebase only the tip commit of "topic" on merge base between "master"
|
||||
# and "topic". Cannot do this for "side" with "master" because there
|
||||
# is no single merge base.
|
||||
#
|
||||
#
|
||||
# F---G topic G'
|
||||
# / /
|
||||
# A---B---C---D---E master --> A---B---C---D---E
|
||||
# \ \ /
|
||||
# \ x
|
||||
# \ / \
|
||||
# H---I---J---K side
|
||||
|
||||
test_expect_success setup '
|
||||
test_commit A &&
|
||||
test_commit B &&
|
||||
git branch side &&
|
||||
test_commit C &&
|
||||
git branch topic &&
|
||||
git checkout side &&
|
||||
test_commit H &&
|
||||
git checkout master &&
|
||||
test_tick &&
|
||||
git merge H &&
|
||||
git tag D &&
|
||||
test_commit E &&
|
||||
git checkout topic &&
|
||||
test_commit F &&
|
||||
test_commit G &&
|
||||
git checkout side &&
|
||||
test_tick &&
|
||||
git merge C &&
|
||||
git tag I &&
|
||||
test_commit J &&
|
||||
test_commit K
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...topic' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
|
||||
git rebase --onto master...topic F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
|
||||
git rebase --onto master... F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase --onto master...side' '
|
||||
git reset --hard &&
|
||||
git checkout side &&
|
||||
git reset --hard K &&
|
||||
|
||||
test_must_fail git rebase --onto master...side J
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...topic' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
set_fake_editor &&
|
||||
EXPECT_COUNT=1 git rebase -i --onto master...topic F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...' '
|
||||
git reset --hard &&
|
||||
git checkout topic &&
|
||||
git reset --hard G &&
|
||||
set_fake_editor &&
|
||||
EXPECT_COUNT=1 git rebase -i --onto master... F &&
|
||||
git rev-parse HEAD^1 >actual &&
|
||||
git rev-parse C^0 >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --onto master...side' '
|
||||
git reset --hard &&
|
||||
git checkout side &&
|
||||
git reset --hard K &&
|
||||
|
||||
test_must_fail git rebase -i --onto master...side J
|
||||
'
|
||||
|
||||
test_done
|
||||
@@ -38,7 +38,7 @@ cat >exp <<EOF
|
||||
> POST /smart/repo.git/git-upload-pack HTTP/1.1
|
||||
> Accept-Encoding: deflate, gzip
|
||||
> Content-Type: application/x-git-upload-pack-request
|
||||
> Accept: application/x-git-upload-pack-response
|
||||
> Accept: application/x-git-upload-pack-result
|
||||
> Content-Length: xxx
|
||||
< HTTP/1.1 200 OK
|
||||
< Pragma: no-cache
|
||||
|
||||
@@ -8,6 +8,18 @@ test_description='git grep various.
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'Check for external grep support' '
|
||||
case "$(git grep -h 2>&1|grep ext-grep)" in
|
||||
*"(default)"*)
|
||||
test_set_prereq EXTGREP
|
||||
true;;
|
||||
*"(ignored by this build)"*)
|
||||
true;;
|
||||
*)
|
||||
false;;
|
||||
esac
|
||||
'
|
||||
|
||||
cat >hello.c <<EOF
|
||||
#include <stdio.h>
|
||||
int main(int argc, const char **argv)
|
||||
@@ -426,4 +438,16 @@ test_expect_success 'grep -Fi' '
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success EXTGREP 'external grep is called' '
|
||||
GIT_TRACE=2 git grep foo >/dev/null 2>actual &&
|
||||
grep "trace: grep:.*foo" actual >/dev/null
|
||||
'
|
||||
|
||||
test_expect_success EXTGREP 'no external grep when skip-worktree entries exist' '
|
||||
git update-index --skip-worktree file &&
|
||||
GIT_TRACE=2 git grep foo >/dev/null 2>actual &&
|
||||
! grep "trace: grep:" actual >/dev/null &&
|
||||
git update-index --no-skip-worktree file
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
163
t/t7011-skip-worktree-reading.sh
Executable file
163
t/t7011-skip-worktree-reading.sh
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
|
||||
#
|
||||
|
||||
test_description='skip-worktree bit test'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
cat >expect.full <<EOF
|
||||
H 1
|
||||
H 2
|
||||
H init.t
|
||||
H sub/1
|
||||
H sub/2
|
||||
EOF
|
||||
|
||||
cat >expect.skip <<EOF
|
||||
S 1
|
||||
H 2
|
||||
H init.t
|
||||
S sub/1
|
||||
H sub/2
|
||||
EOF
|
||||
|
||||
NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
ZERO_SHA0=0000000000000000000000000000000000000000
|
||||
setup_absent() {
|
||||
test -f 1 && rm 1
|
||||
git update-index --remove 1 &&
|
||||
git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
|
||||
git update-index --skip-worktree 1
|
||||
}
|
||||
|
||||
test_absent() {
|
||||
echo "100644 $NULL_SHA1 0 1" > expected &&
|
||||
git ls-files --stage 1 > result &&
|
||||
test_cmp expected result &&
|
||||
test ! -f 1
|
||||
}
|
||||
|
||||
setup_dirty() {
|
||||
git update-index --force-remove 1 &&
|
||||
echo dirty > 1 &&
|
||||
git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
|
||||
git update-index --skip-worktree 1
|
||||
}
|
||||
|
||||
test_dirty() {
|
||||
echo "100644 $NULL_SHA1 0 1" > expected &&
|
||||
git ls-files --stage 1 > result &&
|
||||
test_cmp expected result &&
|
||||
echo dirty > expected
|
||||
test_cmp expected 1
|
||||
}
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit init &&
|
||||
mkdir sub &&
|
||||
touch ./1 ./2 sub/1 sub/2 &&
|
||||
git add 1 2 sub/1 sub/2 &&
|
||||
git update-index --skip-worktree 1 sub/1 &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expect.skip result
|
||||
'
|
||||
|
||||
test_expect_success 'update-index' '
|
||||
setup_absent &&
|
||||
git update-index 1 &&
|
||||
test_absent
|
||||
'
|
||||
|
||||
test_expect_success 'update-index' '
|
||||
setup_dirty &&
|
||||
git update-index 1 &&
|
||||
test_dirty
|
||||
'
|
||||
|
||||
test_expect_success 'update-index --remove' '
|
||||
setup_absent &&
|
||||
git update-index --remove 1 &&
|
||||
test -z "$(git ls-files 1)" &&
|
||||
test ! -f 1
|
||||
'
|
||||
|
||||
test_expect_success 'update-index --remove' '
|
||||
setup_dirty &&
|
||||
git update-index --remove 1 &&
|
||||
test -z "$(git ls-files 1)" &&
|
||||
echo dirty > expected &&
|
||||
test_cmp expected 1
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --delete' '
|
||||
setup_absent &&
|
||||
test -z "$(git ls-files -d)"
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --delete' '
|
||||
setup_dirty &&
|
||||
test -z "$(git ls-files -d)"
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --modified' '
|
||||
setup_absent &&
|
||||
test -z "$(git ls-files -m)"
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --modified' '
|
||||
setup_dirty &&
|
||||
test -z "$(git ls-files -m)"
|
||||
'
|
||||
|
||||
test_expect_success 'grep with skip-worktree file' '
|
||||
git update-index --no-skip-worktree 1 &&
|
||||
echo test > 1 &&
|
||||
git update-index 1 &&
|
||||
git update-index --skip-worktree 1 &&
|
||||
rm 1 &&
|
||||
test "$(git grep --no-ext-grep test)" = "1:test"
|
||||
'
|
||||
|
||||
echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected
|
||||
test_expect_success 'diff-index does not examine skip-worktree absent entries' '
|
||||
setup_absent &&
|
||||
git diff-index HEAD -- 1 > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
test_expect_success 'diff-index does not examine skip-worktree dirty entries' '
|
||||
setup_dirty &&
|
||||
git diff-index HEAD -- 1 > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
test_expect_success 'diff-files does not examine skip-worktree absent entries' '
|
||||
setup_absent &&
|
||||
test -z "$(git diff-files -- one)"
|
||||
'
|
||||
|
||||
test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
|
||||
setup_dirty &&
|
||||
test -z "$(git diff-files -- one)"
|
||||
'
|
||||
|
||||
test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
|
||||
setup_absent &&
|
||||
git rm 1
|
||||
'
|
||||
|
||||
test_expect_success 'commit on skip-worktree absent entries' '
|
||||
git reset &&
|
||||
setup_absent &&
|
||||
test_must_fail git commit -m null 1
|
||||
'
|
||||
|
||||
test_expect_success 'commit on skip-worktree dirty entries' '
|
||||
git reset &&
|
||||
setup_dirty &&
|
||||
test_must_fail git commit -m null 1
|
||||
'
|
||||
|
||||
test_done
|
||||
146
t/t7012-skip-worktree-writing.sh
Executable file
146
t/t7012-skip-worktree-writing.sh
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
|
||||
#
|
||||
|
||||
test_description='test worktree writing operations when skip-worktree is used'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit init &&
|
||||
echo modified >> init.t &&
|
||||
touch added &&
|
||||
git add init.t added &&
|
||||
git commit -m "modified and added" &&
|
||||
git tag top
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree updates worktree, absent case' '
|
||||
git checkout -f top &&
|
||||
git update-index --skip-worktree init.t &&
|
||||
rm init.t &&
|
||||
git read-tree -m -u HEAD^ &&
|
||||
echo init > expected &&
|
||||
test_cmp expected init.t
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree updates worktree, dirty case' '
|
||||
git checkout -f top &&
|
||||
git update-index --skip-worktree init.t &&
|
||||
echo dirty >> init.t &&
|
||||
test_must_fail git read-tree -m -u HEAD^ &&
|
||||
grep -q dirty init.t &&
|
||||
test "$(git ls-files -t init.t)" = "S init.t" &&
|
||||
git update-index --no-skip-worktree init.t
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree removes worktree, absent case' '
|
||||
git checkout -f top &&
|
||||
git update-index --skip-worktree added &&
|
||||
rm added &&
|
||||
git read-tree -m -u HEAD^ &&
|
||||
test ! -f added
|
||||
'
|
||||
|
||||
test_expect_success 'read-tree removes worktree, dirty case' '
|
||||
git checkout -f top &&
|
||||
git update-index --skip-worktree added &&
|
||||
echo dirty >> added &&
|
||||
test_must_fail git read-tree -m -u HEAD^ &&
|
||||
grep -q dirty added &&
|
||||
test "$(git ls-files -t added)" = "S added" &&
|
||||
git update-index --no-skip-worktree added
|
||||
'
|
||||
|
||||
NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
ZERO_SHA0=0000000000000000000000000000000000000000
|
||||
setup_absent() {
|
||||
test -f 1 && rm 1
|
||||
git update-index --remove 1 &&
|
||||
git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
|
||||
git update-index --skip-worktree 1
|
||||
}
|
||||
|
||||
test_absent() {
|
||||
echo "100644 $NULL_SHA1 0 1" > expected &&
|
||||
git ls-files --stage 1 > result &&
|
||||
test_cmp expected result &&
|
||||
test ! -f 1
|
||||
}
|
||||
|
||||
setup_dirty() {
|
||||
git update-index --force-remove 1 &&
|
||||
echo dirty > 1 &&
|
||||
git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
|
||||
git update-index --skip-worktree 1
|
||||
}
|
||||
|
||||
test_dirty() {
|
||||
echo "100644 $NULL_SHA1 0 1" > expected &&
|
||||
git ls-files --stage 1 > result &&
|
||||
test_cmp expected result &&
|
||||
echo dirty > expected
|
||||
test_cmp expected 1
|
||||
}
|
||||
|
||||
cat >expected <<EOF
|
||||
S 1
|
||||
H 2
|
||||
H init.t
|
||||
S sub/1
|
||||
H sub/2
|
||||
EOF
|
||||
|
||||
test_expect_success 'index setup' '
|
||||
git checkout -f init &&
|
||||
mkdir sub &&
|
||||
touch ./1 ./2 sub/1 sub/2 &&
|
||||
git add 1 2 sub/1 sub/2 &&
|
||||
git update-index --skip-worktree 1 sub/1 &&
|
||||
git ls-files -t > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
test_expect_success 'git-add ignores worktree content' '
|
||||
setup_absent &&
|
||||
git add 1 &&
|
||||
test_absent
|
||||
'
|
||||
|
||||
test_expect_success 'git-add ignores worktree content' '
|
||||
setup_dirty &&
|
||||
git add 1 &&
|
||||
test_dirty
|
||||
'
|
||||
|
||||
test_expect_success 'git-rm fails if worktree is dirty' '
|
||||
setup_dirty &&
|
||||
test_must_fail git rm 1 &&
|
||||
test_dirty
|
||||
'
|
||||
|
||||
cat >expected <<EOF
|
||||
Would remove expected
|
||||
Would remove result
|
||||
EOF
|
||||
test_expect_success 'git-clean, absent case' '
|
||||
setup_absent &&
|
||||
git clean -n > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
test_expect_success 'git-clean, dirty case' '
|
||||
setup_dirty &&
|
||||
git clean -n > result &&
|
||||
test_cmp expected result
|
||||
'
|
||||
|
||||
test_expect_failure 'git-apply adds file' false
|
||||
test_expect_failure 'git-apply updates file' false
|
||||
test_expect_failure 'git-apply removes file' false
|
||||
test_expect_failure 'git-mv to skip-worktree' false
|
||||
test_expect_failure 'git-mv from skip-worktree' false
|
||||
test_expect_failure 'git-checkout' false
|
||||
|
||||
test_done
|
||||
183
t/t7110-reset-merge.sh
Executable file
183
t/t7110-reset-merge.sh
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2009 Christian Couder
|
||||
#
|
||||
|
||||
test_description='Tests for "git reset --merge"'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success setup '
|
||||
for i in 1 2 3; do echo line $i; done >file1 &&
|
||||
cat file1 >file2 &&
|
||||
git add file1 file2 &&
|
||||
test_tick &&
|
||||
git commit -m "Initial commit" &&
|
||||
git tag initial &&
|
||||
echo line 4 >>file1 &&
|
||||
cat file1 >file2 &&
|
||||
test_tick &&
|
||||
git commit -m "add line 4 to file1" file1 &&
|
||||
git tag second
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --merge D D D
|
||||
# file2: C D D D --merge C D D
|
||||
test_expect_success 'reset --merge is ok with changes in file it does not touch' '
|
||||
git reset --merge HEAD^ &&
|
||||
! grep 4 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge is ok when switching back' '
|
||||
git reset --merge second &&
|
||||
grep 4 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: B B C D --merge D D D
|
||||
# file2: C D D D --merge C D D
|
||||
test_expect_success 'reset --merge discards changes added to index (1)' '
|
||||
git reset --hard second &&
|
||||
cat file1 >file2 &&
|
||||
echo "line 5" >> file1 &&
|
||||
git add file1 &&
|
||||
git reset --merge HEAD^ &&
|
||||
! grep 4 file1 &&
|
||||
! grep 5 file1 &&
|
||||
grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge is ok again when switching back (1)' '
|
||||
git reset --hard initial &&
|
||||
echo "line 5" >> file2 &&
|
||||
git add file2 &&
|
||||
git reset --merge second &&
|
||||
! grep 4 file2 &&
|
||||
! grep 5 file1 &&
|
||||
grep 4 file1 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: C C C D --merge D D D
|
||||
# file2: C C D D --merge D D D
|
||||
test_expect_success 'reset --merge discards changes added to index (2)' '
|
||||
git reset --hard second &&
|
||||
echo "line 4" >> file2 &&
|
||||
git add file2 &&
|
||||
git reset --merge HEAD^ &&
|
||||
! grep 4 file2 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
|
||||
test -z "$(git diff)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge is ok again when switching back (2)' '
|
||||
git reset --hard initial &&
|
||||
git reset --merge second &&
|
||||
! grep 4 file2 &&
|
||||
grep 4 file1 &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: A B B C --merge (disallowed)
|
||||
test_expect_success 'reset --merge fails with changes in file it touches' '
|
||||
git reset --hard second &&
|
||||
echo "line 5" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -m "add line 5" file1 &&
|
||||
sed -e "s/line 1/changed line 1/" <file1 >file3 &&
|
||||
mv file3 file1 &&
|
||||
test_must_fail git reset --merge HEAD^ 2>err.log &&
|
||||
grep file1 err.log | grep "not uptodate"
|
||||
'
|
||||
|
||||
test_expect_success 'setup 3 different branches' '
|
||||
git reset --hard second &&
|
||||
git branch branch1 &&
|
||||
git branch branch2 &&
|
||||
git branch branch3 &&
|
||||
git checkout branch1 &&
|
||||
echo "line 5 in branch1" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch1" &&
|
||||
git checkout branch2 &&
|
||||
echo "line 5 in branch2" >> file1 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch2" &&
|
||||
git tag third &&
|
||||
git checkout branch3 &&
|
||||
echo a new file >file3 &&
|
||||
rm -f file1 &&
|
||||
git add file3 &&
|
||||
test_tick &&
|
||||
git commit -a -m "change in branch3"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B C --merge C C C
|
||||
test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
|
||||
git checkout third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
git reset --merge HEAD^ &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
|
||||
test -z "$(git diff --cached)" &&
|
||||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
# The next test will test the following:
|
||||
#
|
||||
# working index HEAD target working index HEAD
|
||||
# ----------------------------------------------------
|
||||
# file1: X U B B --merge B B B
|
||||
test_expect_success '"reset --merge HEAD" is ok with pending merge' '
|
||||
git reset --hard third &&
|
||||
test_must_fail git merge branch1 &&
|
||||
git reset --merge HEAD &&
|
||||
test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
|
||||
test -z "$(git diff --cached)" &&
|
||||
test -z "$(git diff)"
|
||||
'
|
||||
|
||||
test_expect_success '--merge with added/deleted' '
|
||||
git reset --hard third &&
|
||||
rm -f file2 &&
|
||||
test_must_fail git merge branch3 &&
|
||||
! test -f file2 &&
|
||||
test -f file3 &&
|
||||
git diff --exit-code file3 &&
|
||||
git diff --exit-code branch3 file3 &&
|
||||
git reset --merge HEAD &&
|
||||
! test -f file3 &&
|
||||
! test -f file2 &&
|
||||
git diff --exit-code --cached
|
||||
'
|
||||
|
||||
test_done
|
||||
113
t/t7111-reset-table.sh
Executable file
113
t/t7111-reset-table.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2010 Christian Couder
|
||||
#
|
||||
|
||||
test_description='Tests to check that "reset" options follow a known table'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
|
||||
test_expect_success 'creating initial commits' '
|
||||
test_commit E file1 &&
|
||||
test_commit D file1 &&
|
||||
test_commit C file1
|
||||
'
|
||||
|
||||
while read W1 I1 H1 T opt W2 I2 H2
|
||||
do
|
||||
test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
|
||||
git reset --hard C &&
|
||||
if test "$I1" != "$H1"
|
||||
then
|
||||
echo "$I1" >file1 &&
|
||||
git add file1
|
||||
fi &&
|
||||
if test "$W1" != "$I1"
|
||||
then
|
||||
echo "$W1" >file1
|
||||
fi &&
|
||||
if test "$W2" != "XXXXX"
|
||||
then
|
||||
git reset --$opt $T &&
|
||||
test "$(cat file1)" = "$W2" &&
|
||||
git checkout-index -f -- file1 &&
|
||||
test "$(cat file1)" = "$I2" &&
|
||||
git checkout -f HEAD -- file1 &&
|
||||
test "$(cat file1)" = "$H2"
|
||||
else
|
||||
test_must_fail git reset --$opt $T
|
||||
fi
|
||||
'
|
||||
done <<\EOF
|
||||
A B C D soft A B D
|
||||
A B C D mixed A D D
|
||||
A B C D hard D D D
|
||||
A B C D merge XXXXX
|
||||
A B C C soft A B C
|
||||
A B C C mixed A C C
|
||||
A B C C hard C C C
|
||||
A B C C merge XXXXX
|
||||
B B C D soft B B D
|
||||
B B C D mixed B D D
|
||||
B B C D hard D D D
|
||||
B B C D merge D D D
|
||||
B B C C soft B B C
|
||||
B B C C mixed B C C
|
||||
B B C C hard C C C
|
||||
B B C C merge C C C
|
||||
B C C D soft B C D
|
||||
B C C D mixed B D D
|
||||
B C C D hard D D D
|
||||
B C C D merge XXXXX
|
||||
B C C C soft B C C
|
||||
B C C C mixed B C C
|
||||
B C C C hard C C C
|
||||
B C C C merge B C C
|
||||
EOF
|
||||
|
||||
test_expect_success 'setting up branches to test with unmerged entries' '
|
||||
git reset --hard C &&
|
||||
git branch branch1 &&
|
||||
git branch branch2 &&
|
||||
git checkout branch1 &&
|
||||
test_commit B1 file1 &&
|
||||
git checkout branch2 &&
|
||||
test_commit B2 file1
|
||||
'
|
||||
|
||||
while read W1 I1 H1 T opt W2 I2 H2
|
||||
do
|
||||
test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
|
||||
git reset --hard B2 &&
|
||||
test_must_fail git merge branch1 &&
|
||||
cat file1 >X_file1 &&
|
||||
if test "$W2" != "XXXXX"
|
||||
then
|
||||
git reset --$opt $T &&
|
||||
if test "$W2" = "X"
|
||||
then
|
||||
test_cmp file1 X_file1
|
||||
else
|
||||
test "$(cat file1)" = "$W2"
|
||||
fi &&
|
||||
git checkout-index -f -- file1 &&
|
||||
test "$(cat file1)" = "$I2" &&
|
||||
git checkout -f HEAD -- file1 &&
|
||||
test "$(cat file1)" = "$H2"
|
||||
else
|
||||
test_must_fail git reset --$opt $T
|
||||
fi
|
||||
'
|
||||
done <<\EOF
|
||||
X U C D soft XXXXX
|
||||
X U C D mixed X D D
|
||||
X U C D hard D D D
|
||||
X U C D merge D D D
|
||||
X U C C soft XXXXX
|
||||
X U C C mixed X C C
|
||||
X U C C hard C C C
|
||||
X U C C merge C C C
|
||||
EOF
|
||||
|
||||
test_done
|
||||
@@ -22,6 +22,25 @@ test_expect_success 'setup' '
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'git clean with skip-worktree .gitignore' '
|
||||
git update-index --skip-worktree .gitignore &&
|
||||
rm .gitignore &&
|
||||
mkdir -p build docs &&
|
||||
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
|
||||
git clean &&
|
||||
test -f Makefile &&
|
||||
test -f README &&
|
||||
test -f src/part1.c &&
|
||||
test -f src/part2.c &&
|
||||
test ! -f a.out &&
|
||||
test ! -f src/part3.c &&
|
||||
test -f docs/manual.txt &&
|
||||
test -f obj.o &&
|
||||
test -f build/lib.so &&
|
||||
git update-index --no-skip-worktree .gitignore &&
|
||||
git checkout .gitignore
|
||||
'
|
||||
|
||||
test_expect_success 'git clean' '
|
||||
|
||||
mkdir -p build docs &&
|
||||
|
||||
14
test-index-version.c
Normal file
14
test-index-version.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "cache.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
struct cache_header hdr;
|
||||
int version;
|
||||
|
||||
memset(&hdr,0,sizeof(hdr));
|
||||
if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
|
||||
return 0;
|
||||
version = ntohl(hdr.hdr_version);
|
||||
printf("%d\n", version);
|
||||
return 0;
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "quote.h"
|
||||
#include "remote.h"
|
||||
|
||||
static int debug;
|
||||
|
||||
struct helper_data
|
||||
{
|
||||
const char *name;
|
||||
@@ -16,12 +18,81 @@ struct helper_data
|
||||
unsigned fetch : 1,
|
||||
import : 1,
|
||||
option : 1,
|
||||
push : 1;
|
||||
push : 1,
|
||||
connect : 1,
|
||||
no_disconnect_req : 1;
|
||||
/* These go from remote name (as in "list") to private name */
|
||||
struct refspec *refspecs;
|
||||
int refspec_nr;
|
||||
/* Transport options for fetch-pack/send-pack (should one of
|
||||
* those be invoked).
|
||||
*/
|
||||
struct git_transport_options transport_options;
|
||||
};
|
||||
|
||||
static void sendline(struct helper_data *helper, struct strbuf *buffer)
|
||||
{
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
|
||||
if (write_in_full(helper->helper->in, buffer->buf, buffer->len)
|
||||
!= buffer->len)
|
||||
die_errno("Full write to remote helper failed");
|
||||
}
|
||||
|
||||
static int recvline_fh(FILE *helper, struct strbuf *buffer)
|
||||
{
|
||||
strbuf_reset(buffer);
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Remote helper: Waiting...\n");
|
||||
if (strbuf_getline(buffer, helper, '\n') == EOF) {
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Remote helper quit.\n");
|
||||
exit(128);
|
||||
}
|
||||
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int recvline(struct helper_data *helper, struct strbuf *buffer)
|
||||
{
|
||||
return recvline_fh(helper->out, buffer);
|
||||
}
|
||||
|
||||
static void xchgline(struct helper_data *helper, struct strbuf *buffer)
|
||||
{
|
||||
sendline(helper, buffer);
|
||||
recvline(helper, buffer);
|
||||
}
|
||||
|
||||
static void write_constant(int fd, const char *str)
|
||||
{
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Remote helper: -> %s", str);
|
||||
if (write_in_full(fd, str, strlen(str)) != strlen(str))
|
||||
die_errno("Full write to remote helper failed");
|
||||
}
|
||||
|
||||
const char *remove_ext_force(const char *url)
|
||||
{
|
||||
if (url) {
|
||||
const char *colon = strchr(url, ':');
|
||||
if (colon && colon[1] == ':')
|
||||
return colon + 2;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
static void do_take_over(struct transport *transport)
|
||||
{
|
||||
struct helper_data *data;
|
||||
data = (struct helper_data *)transport->data;
|
||||
transport_take_over(transport, data->helper);
|
||||
fclose(data->out);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static struct child_process *get_helper(struct transport *transport)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
@@ -30,6 +101,7 @@ static struct child_process *get_helper(struct transport *transport)
|
||||
const char **refspecs = NULL;
|
||||
int refspec_nr = 0;
|
||||
int refspec_alloc = 0;
|
||||
int duped;
|
||||
|
||||
if (data->helper)
|
||||
return data->helper;
|
||||
@@ -42,34 +114,60 @@ static struct child_process *get_helper(struct transport *transport)
|
||||
strbuf_addf(&buf, "remote-%s", data->name);
|
||||
helper->argv[0] = strbuf_detach(&buf, NULL);
|
||||
helper->argv[1] = transport->remote->name;
|
||||
helper->argv[2] = transport->url;
|
||||
helper->argv[2] = remove_ext_force(transport->url);
|
||||
helper->git_cmd = 1;
|
||||
if (start_command(helper))
|
||||
die("Unable to run helper: git %s", helper->argv[0]);
|
||||
data->helper = helper;
|
||||
data->no_disconnect_req = 0;
|
||||
|
||||
write_str_in_full(helper->in, "capabilities\n");
|
||||
/*
|
||||
* Open the output as FILE* so strbuf_getline() can be used.
|
||||
* Do this with duped fd because fclose() will close the fd,
|
||||
* and stuff like taking over will require the fd to remain.
|
||||
*/
|
||||
duped = dup(helper->out);
|
||||
if (duped < 0)
|
||||
die_errno("Can't dup helper output fd");
|
||||
data->out = xfdopen(duped, "r");
|
||||
|
||||
write_constant(helper->in, "capabilities\n");
|
||||
|
||||
data->out = xfdopen(helper->out, "r");
|
||||
while (1) {
|
||||
if (strbuf_getline(&buf, data->out, '\n') == EOF)
|
||||
exit(128); /* child died, message supplied already */
|
||||
const char *capname;
|
||||
int mandatory = 0;
|
||||
recvline(data, &buf);
|
||||
|
||||
if (!*buf.buf)
|
||||
break;
|
||||
if (!strcmp(buf.buf, "fetch"))
|
||||
|
||||
if (*buf.buf == '*') {
|
||||
capname = buf.buf + 1;
|
||||
mandatory = 1;
|
||||
} else
|
||||
capname = buf.buf;
|
||||
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Got cap %s\n", capname);
|
||||
if (!strcmp(capname, "fetch"))
|
||||
data->fetch = 1;
|
||||
if (!strcmp(buf.buf, "option"))
|
||||
else if (!strcmp(capname, "option"))
|
||||
data->option = 1;
|
||||
if (!strcmp(buf.buf, "push"))
|
||||
else if (!strcmp(capname, "push"))
|
||||
data->push = 1;
|
||||
if (!strcmp(buf.buf, "import"))
|
||||
else if (!strcmp(capname, "import"))
|
||||
data->import = 1;
|
||||
if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
|
||||
else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
|
||||
ALLOC_GROW(refspecs,
|
||||
refspec_nr + 1,
|
||||
refspec_alloc);
|
||||
refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
|
||||
} else if (!strcmp(capname, "connect")) {
|
||||
data->connect = 1;
|
||||
} else if (mandatory) {
|
||||
die("Unknown madatory capability %s. This remote "
|
||||
"helper probably needs newer version of Git.\n",
|
||||
capname);
|
||||
}
|
||||
}
|
||||
if (refspecs) {
|
||||
@@ -82,15 +180,25 @@ static struct child_process *get_helper(struct transport *transport)
|
||||
free(refspecs);
|
||||
}
|
||||
strbuf_release(&buf);
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Capabilities complete.\n");
|
||||
return data->helper;
|
||||
}
|
||||
|
||||
static int disconnect_helper(struct transport *transport)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
if (data->helper) {
|
||||
write_str_in_full(data->helper->in, "\n");
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Disconnecting.\n");
|
||||
if (!data->no_disconnect_req) {
|
||||
strbuf_addf(&buf, "\n");
|
||||
sendline(data, &buf);
|
||||
}
|
||||
close(data->helper->in);
|
||||
close(data->helper->out);
|
||||
fclose(data->out);
|
||||
finish_command(data->helper);
|
||||
free((char *)data->helper->argv[0]);
|
||||
@@ -117,10 +225,11 @@ static int set_helper_option(struct transport *transport,
|
||||
const char *name, const char *value)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
struct child_process *helper = get_helper(transport);
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int i, ret, is_bool = 0;
|
||||
|
||||
get_helper(transport);
|
||||
|
||||
if (!data->option)
|
||||
return 1;
|
||||
|
||||
@@ -143,12 +252,7 @@ static int set_helper_option(struct transport *transport,
|
||||
quote_c_style(value, &buf, NULL, 0);
|
||||
strbuf_addch(&buf, '\n');
|
||||
|
||||
if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
|
||||
die_errno("cannot send option to %s", data->name);
|
||||
|
||||
strbuf_reset(&buf);
|
||||
if (strbuf_getline(&buf, data->out, '\n') == EOF)
|
||||
exit(128); /* child died, message supplied already */
|
||||
xchgline(data, &buf);
|
||||
|
||||
if (!strcmp(buf.buf, "ok"))
|
||||
ret = 0;
|
||||
@@ -208,13 +312,10 @@ static int fetch_with_fetch(struct transport *transport,
|
||||
}
|
||||
|
||||
strbuf_addch(&buf, '\n');
|
||||
if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
|
||||
die_errno("cannot send fetch to %s", data->name);
|
||||
sendline(data, &buf);
|
||||
|
||||
while (1) {
|
||||
strbuf_reset(&buf);
|
||||
if (strbuf_getline(&buf, data->out, '\n') == EOF)
|
||||
exit(128); /* child died, message supplied already */
|
||||
recvline(data, &buf);
|
||||
|
||||
if (!prefixcmp(buf.buf, "lock ")) {
|
||||
const char *name = buf.buf + 5;
|
||||
@@ -249,12 +350,13 @@ static int fetch_with_import(struct transport *transport,
|
||||
int nr_heads, struct ref **to_fetch)
|
||||
{
|
||||
struct child_process fastimport;
|
||||
struct child_process *helper = get_helper(transport);
|
||||
struct helper_data *data = transport->data;
|
||||
int i;
|
||||
struct ref *posn;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
get_helper(transport);
|
||||
|
||||
if (get_importer(transport, &fastimport))
|
||||
die("Couldn't run fast-import");
|
||||
|
||||
@@ -264,7 +366,7 @@ static int fetch_with_import(struct transport *transport,
|
||||
continue;
|
||||
|
||||
strbuf_addf(&buf, "import %s\n", posn->name);
|
||||
write_in_full(helper->in, buf.buf, buf.len);
|
||||
sendline(data, &buf);
|
||||
strbuf_reset(&buf);
|
||||
}
|
||||
disconnect_helper(transport);
|
||||
@@ -288,12 +390,112 @@ static int fetch_with_import(struct transport *transport,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_connect_service(struct transport *transport,
|
||||
const char *name, const char *exec)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
struct strbuf cmdbuf = STRBUF_INIT;
|
||||
struct child_process *helper;
|
||||
int r, duped, ret = 0;
|
||||
FILE *input;
|
||||
|
||||
helper = get_helper(transport);
|
||||
|
||||
/*
|
||||
* Yes, dup the pipe another time, as we need unbuffered version
|
||||
* of input pipe as FILE*. fclose() closes the underlying fd and
|
||||
* stream buffering only can be changed before first I/O operation
|
||||
* on it.
|
||||
*/
|
||||
duped = dup(helper->out);
|
||||
if (duped < 0)
|
||||
die_errno("Can't dup helper output fd");
|
||||
input = xfdopen(duped, "r");
|
||||
setvbuf(input, NULL, _IONBF, 0);
|
||||
|
||||
/*
|
||||
* Handle --upload-pack and friends. This is fire and forget...
|
||||
* just warn if it fails.
|
||||
*/
|
||||
if (strcmp(name, exec)) {
|
||||
r = set_helper_option(transport, "servpath", exec);
|
||||
if (r > 0)
|
||||
warning("Setting remote service path not supported by protocol.");
|
||||
else if (r < 0)
|
||||
warning("Invalid remote service path.");
|
||||
}
|
||||
|
||||
if (data->connect)
|
||||
strbuf_addf(&cmdbuf, "connect %s\n", name);
|
||||
else
|
||||
goto exit;
|
||||
|
||||
sendline(data, &cmdbuf);
|
||||
recvline_fh(input, &cmdbuf);
|
||||
if (!strcmp(cmdbuf.buf, "")) {
|
||||
data->no_disconnect_req = 1;
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Smart transport connection "
|
||||
"ready.\n");
|
||||
ret = 1;
|
||||
} else if (!strcmp(cmdbuf.buf, "fallback")) {
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Falling back to dumb "
|
||||
"transport.\n");
|
||||
} else
|
||||
die("Unknown response to connect: %s",
|
||||
cmdbuf.buf);
|
||||
|
||||
exit:
|
||||
fclose(input);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int process_connect(struct transport *transport,
|
||||
int for_push)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
const char *name;
|
||||
const char *exec;
|
||||
|
||||
name = for_push ? "git-receive-pack" : "git-upload-pack";
|
||||
if (for_push)
|
||||
exec = data->transport_options.receivepack;
|
||||
else
|
||||
exec = data->transport_options.uploadpack;
|
||||
|
||||
return process_connect_service(transport, name, exec);
|
||||
}
|
||||
|
||||
static int connect_helper(struct transport *transport, const char *name,
|
||||
const char *exec, int fd[2])
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
|
||||
/* Get_helper so connect is inited. */
|
||||
get_helper(transport);
|
||||
if (!data->connect)
|
||||
die("Operation not supported by protocol.");
|
||||
|
||||
if (!process_connect_service(transport, name, exec))
|
||||
die("Can't connect to subservice %s.", name);
|
||||
|
||||
fd[0] = data->helper->out;
|
||||
fd[1] = data->helper->in;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fetch(struct transport *transport,
|
||||
int nr_heads, struct ref **to_fetch)
|
||||
{
|
||||
struct helper_data *data = transport->data;
|
||||
int i, count;
|
||||
|
||||
if (process_connect(transport, 0)) {
|
||||
do_take_over(transport);
|
||||
return transport->fetch(transport, nr_heads, to_fetch);
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < nr_heads; i++)
|
||||
if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
|
||||
@@ -321,6 +523,11 @@ static int push_refs(struct transport *transport,
|
||||
struct child_process *helper;
|
||||
struct ref *ref;
|
||||
|
||||
if (process_connect(transport, 1)) {
|
||||
do_take_over(transport);
|
||||
return transport->push_refs(transport, remote_refs, flags);
|
||||
}
|
||||
|
||||
if (!remote_refs)
|
||||
return 0;
|
||||
|
||||
@@ -369,17 +576,14 @@ static int push_refs(struct transport *transport,
|
||||
}
|
||||
|
||||
strbuf_addch(&buf, '\n');
|
||||
if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
|
||||
exit(128);
|
||||
sendline(data, &buf);
|
||||
|
||||
ref = remote_refs;
|
||||
while (1) {
|
||||
char *refname, *msg;
|
||||
int status;
|
||||
|
||||
strbuf_reset(&buf);
|
||||
if (strbuf_getline(&buf, data->out, '\n') == EOF)
|
||||
exit(128); /* child died, message supplied already */
|
||||
recvline(data, &buf);
|
||||
if (!buf.len)
|
||||
break;
|
||||
|
||||
@@ -464,6 +668,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
|
||||
|
||||
helper = get_helper(transport);
|
||||
|
||||
if (process_connect(transport, for_push)) {
|
||||
do_take_over(transport);
|
||||
return transport->get_refs_list(transport, for_push);
|
||||
}
|
||||
|
||||
if (data->push && for_push)
|
||||
write_str_in_full(helper->in, "list for-push\n");
|
||||
else
|
||||
@@ -471,8 +680,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
|
||||
|
||||
while (1) {
|
||||
char *eov, *eon;
|
||||
if (strbuf_getline(&buf, data->out, '\n') == EOF)
|
||||
exit(128); /* child died, message supplied already */
|
||||
recvline(data, &buf);
|
||||
|
||||
if (!*buf.buf)
|
||||
break;
|
||||
@@ -497,6 +705,8 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
|
||||
}
|
||||
tail = &((*tail)->next);
|
||||
}
|
||||
if (debug)
|
||||
fprintf(stderr, "Debug: Read ref listing.\n");
|
||||
strbuf_release(&buf);
|
||||
|
||||
for (posn = ret; posn; posn = posn->next)
|
||||
@@ -510,11 +720,16 @@ int transport_helper_init(struct transport *transport, const char *name)
|
||||
struct helper_data *data = xcalloc(sizeof(*data), 1);
|
||||
data->name = name;
|
||||
|
||||
if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
|
||||
debug = 1;
|
||||
|
||||
transport->data = data;
|
||||
transport->set_option = set_helper_option;
|
||||
transport->get_refs_list = get_refs_list;
|
||||
transport->fetch = fetch;
|
||||
transport->push_refs = push_refs;
|
||||
transport->disconnect = release_helper;
|
||||
transport->connect = connect_helper;
|
||||
transport->smart_options = &(data->transport_options);
|
||||
return 0;
|
||||
}
|
||||
|
||||
217
transport.c
217
transport.c
@@ -395,41 +395,36 @@ static int close_bundle(struct transport *transport)
|
||||
}
|
||||
|
||||
struct git_transport_data {
|
||||
unsigned thin : 1;
|
||||
unsigned keep : 1;
|
||||
unsigned followtags : 1;
|
||||
int depth;
|
||||
struct git_transport_options options;
|
||||
struct child_process *conn;
|
||||
int fd[2];
|
||||
const char *uploadpack;
|
||||
const char *receivepack;
|
||||
unsigned got_remote_heads : 1;
|
||||
struct extra_have_objects extra_have;
|
||||
};
|
||||
|
||||
static int set_git_option(struct transport *connection,
|
||||
static int set_git_option(struct git_transport_options *opts,
|
||||
const char *name, const char *value)
|
||||
{
|
||||
struct git_transport_data *data = connection->data;
|
||||
if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
|
||||
data->uploadpack = value;
|
||||
opts->uploadpack = value;
|
||||
return 0;
|
||||
} else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
|
||||
data->receivepack = value;
|
||||
opts->receivepack = value;
|
||||
return 0;
|
||||
} else if (!strcmp(name, TRANS_OPT_THIN)) {
|
||||
data->thin = !!value;
|
||||
opts->thin = !!value;
|
||||
return 0;
|
||||
} else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
|
||||
data->followtags = !!value;
|
||||
opts->followtags = !!value;
|
||||
return 0;
|
||||
} else if (!strcmp(name, TRANS_OPT_KEEP)) {
|
||||
data->keep = !!value;
|
||||
opts->keep = !!value;
|
||||
return 0;
|
||||
} else if (!strcmp(name, TRANS_OPT_DEPTH)) {
|
||||
if (!value)
|
||||
data->depth = 0;
|
||||
opts->depth = 0;
|
||||
else
|
||||
data->depth = atoi(value);
|
||||
opts->depth = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -438,9 +433,15 @@ static int set_git_option(struct transport *connection,
|
||||
static int connect_setup(struct transport *transport, int for_push, int verbose)
|
||||
{
|
||||
struct git_transport_data *data = transport->data;
|
||||
|
||||
if (data->conn)
|
||||
return 0;
|
||||
|
||||
data->conn = git_connect(data->fd, transport->url,
|
||||
for_push ? data->receivepack : data->uploadpack,
|
||||
for_push ? data->options.receivepack :
|
||||
data->options.uploadpack,
|
||||
verbose ? CONNECT_VERBOSE : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -452,6 +453,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
|
||||
connect_setup(transport, for_push, 0);
|
||||
get_remote_heads(data->fd[0], &refs, 0, NULL,
|
||||
for_push ? REF_NORMAL : 0, &data->extra_have);
|
||||
data->got_remote_heads = 1;
|
||||
|
||||
return refs;
|
||||
}
|
||||
@@ -469,22 +471,23 @@ static int fetch_refs_via_pack(struct transport *transport,
|
||||
struct ref *refs_tmp = NULL;
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.uploadpack = data->uploadpack;
|
||||
args.keep_pack = data->keep;
|
||||
args.uploadpack = data->options.uploadpack;
|
||||
args.keep_pack = data->options.keep;
|
||||
args.lock_pack = 1;
|
||||
args.use_thin_pack = data->thin;
|
||||
args.include_tag = data->followtags;
|
||||
args.use_thin_pack = data->options.thin;
|
||||
args.include_tag = data->options.followtags;
|
||||
args.verbose = (transport->verbose > 0);
|
||||
args.quiet = (transport->verbose < 0);
|
||||
args.no_progress = args.quiet || (!transport->progress && !isatty(1));
|
||||
args.depth = data->depth;
|
||||
args.depth = data->options.depth;
|
||||
|
||||
for (i = 0; i < nr_heads; i++)
|
||||
origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
|
||||
|
||||
if (!data->conn) {
|
||||
if (!data->got_remote_heads) {
|
||||
connect_setup(transport, 0, 0);
|
||||
get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
|
||||
data->got_remote_heads = 1;
|
||||
}
|
||||
|
||||
refs = fetch_pack(&args, data->fd, data->conn,
|
||||
@@ -495,6 +498,7 @@ static int fetch_refs_via_pack(struct transport *transport,
|
||||
if (finish_connect(data->conn))
|
||||
refs = NULL;
|
||||
data->conn = NULL;
|
||||
data->got_remote_heads = 0;
|
||||
|
||||
free_refs(refs_tmp);
|
||||
|
||||
@@ -723,18 +727,19 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
|
||||
struct send_pack_args args;
|
||||
int ret;
|
||||
|
||||
if (!data->conn) {
|
||||
if (!data->got_remote_heads) {
|
||||
struct ref *tmp_refs;
|
||||
connect_setup(transport, 1, 0);
|
||||
|
||||
get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
|
||||
NULL);
|
||||
data->got_remote_heads = 1;
|
||||
}
|
||||
|
||||
memset(&args, 0, sizeof(args));
|
||||
args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
|
||||
args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
|
||||
args.use_thin_pack = data->thin;
|
||||
args.use_thin_pack = data->options.thin;
|
||||
args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
|
||||
args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
|
||||
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
|
||||
@@ -746,15 +751,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
|
||||
close(data->fd[0]);
|
||||
ret |= finish_connect(data->conn);
|
||||
data->conn = NULL;
|
||||
data->got_remote_heads = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int connect_git(struct transport *transport, const char *name,
|
||||
const char *executable, int fd[2])
|
||||
{
|
||||
struct git_transport_data *data = transport->data;
|
||||
data->conn = git_connect(data->fd, transport->url,
|
||||
executable, 0);
|
||||
fd[0] = data->fd[0];
|
||||
fd[1] = data->fd[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disconnect_git(struct transport *transport)
|
||||
{
|
||||
struct git_transport_data *data = transport->data;
|
||||
if (data->conn) {
|
||||
packet_flush(data->fd[1]);
|
||||
if (data->got_remote_heads)
|
||||
packet_flush(data->fd[1]);
|
||||
close(data->fd[0]);
|
||||
close(data->fd[1]);
|
||||
finish_connect(data->conn);
|
||||
@@ -764,6 +782,32 @@ static int disconnect_git(struct transport *transport)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void transport_take_over(struct transport *transport,
|
||||
struct child_process *child)
|
||||
{
|
||||
struct git_transport_data *data;
|
||||
|
||||
if (!transport->smart_options)
|
||||
die("Bug detected: Taking over transport requires non-NULL "
|
||||
"smart_options field.");
|
||||
|
||||
data = xcalloc(1, sizeof(*data));
|
||||
data->options = *transport->smart_options;
|
||||
data->conn = child;
|
||||
data->fd[0] = data->conn->out;
|
||||
data->fd[1] = data->conn->in;
|
||||
data->got_remote_heads = 0;
|
||||
transport->data = data;
|
||||
|
||||
transport->set_option = NULL;
|
||||
transport->get_refs_list = get_refs_via_connect;
|
||||
transport->fetch = fetch_refs_via_pack;
|
||||
transport->push = NULL;
|
||||
transport->push_refs = git_transport_push;
|
||||
transport->disconnect = disconnect_git;
|
||||
transport->smart_options = &(data->options);
|
||||
}
|
||||
|
||||
static int is_local(const char *url)
|
||||
{
|
||||
const char *colon = strchr(url, ':');
|
||||
@@ -780,6 +824,44 @@ static int is_file(const char *url)
|
||||
return S_ISREG(buf.st_mode);
|
||||
}
|
||||
|
||||
static int is_url(const char *url)
|
||||
{
|
||||
const char *url2, *first_slash;
|
||||
|
||||
if (!url)
|
||||
return 0;
|
||||
url2 = url;
|
||||
first_slash = strchr(url, '/');
|
||||
|
||||
/* Input with no slash at all or slash first can't be URL. */
|
||||
if (!first_slash || first_slash == url)
|
||||
return 0;
|
||||
/* Character before must be : and next must be /. */
|
||||
if (first_slash[-1] != ':' || first_slash[1] != '/')
|
||||
return 0;
|
||||
/* There must be something before the :// */
|
||||
if (first_slash == url + 1)
|
||||
return 0;
|
||||
/*
|
||||
* Check all characters up to first slash - 1. Only alphanum
|
||||
* is allowed.
|
||||
*/
|
||||
url2 = url;
|
||||
while (url2 < first_slash - 1) {
|
||||
if (!isalnum((unsigned char)*url2))
|
||||
return 0;
|
||||
url2++;
|
||||
}
|
||||
|
||||
/* Valid enough. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int external_specification_len(const char *url)
|
||||
{
|
||||
return strchr(url, ':') - url;
|
||||
}
|
||||
|
||||
struct transport *transport_get(struct remote *remote, const char *url)
|
||||
{
|
||||
struct transport *ret = xcalloc(1, sizeof(*ret));
|
||||
@@ -793,6 +875,9 @@ struct transport *transport_get(struct remote *remote, const char *url)
|
||||
url = remote->url[0];
|
||||
ret->url = url;
|
||||
|
||||
/* In case previous URL had helper forced, reset it. */
|
||||
remote->foreign_vcs = NULL;
|
||||
|
||||
/* maybe it is a foreign URL? */
|
||||
if (url) {
|
||||
const char *p = url;
|
||||
@@ -805,46 +890,54 @@ struct transport *transport_get(struct remote *remote, const char *url)
|
||||
|
||||
if (remote && remote->foreign_vcs) {
|
||||
transport_helper_init(ret, remote->foreign_vcs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!prefixcmp(url, "rsync:")) {
|
||||
} else if (!prefixcmp(url, "rsync:")) {
|
||||
ret->get_refs_list = get_refs_via_rsync;
|
||||
ret->fetch = fetch_objs_via_rsync;
|
||||
ret->push = rsync_transport_push;
|
||||
|
||||
} else if (!prefixcmp(url, "http://")
|
||||
|| !prefixcmp(url, "https://")
|
||||
|| !prefixcmp(url, "ftp://")) {
|
||||
transport_helper_init(ret, "curl");
|
||||
#ifdef NO_CURL
|
||||
error("git was compiled without libcurl support.");
|
||||
#endif
|
||||
|
||||
ret->smart_options = NULL;
|
||||
} else if (is_local(url) && is_file(url)) {
|
||||
struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
|
||||
ret->data = data;
|
||||
ret->get_refs_list = get_refs_from_bundle;
|
||||
ret->fetch = fetch_refs_from_bundle;
|
||||
ret->disconnect = close_bundle;
|
||||
|
||||
} else {
|
||||
ret->smart_options = NULL;
|
||||
} else if (!is_url(url)
|
||||
|| !prefixcmp(url, "file://")
|
||||
|| !prefixcmp(url, "git://")
|
||||
|| !prefixcmp(url, "ssh://")
|
||||
|| !prefixcmp(url, "git+ssh://")
|
||||
|| !prefixcmp(url, "ssh+git://")) {
|
||||
/* These are builtin smart transports. */
|
||||
struct git_transport_data *data = xcalloc(1, sizeof(*data));
|
||||
ret->data = data;
|
||||
ret->set_option = set_git_option;
|
||||
ret->set_option = NULL;
|
||||
ret->get_refs_list = get_refs_via_connect;
|
||||
ret->fetch = fetch_refs_via_pack;
|
||||
ret->push_refs = git_transport_push;
|
||||
ret->connect = connect_git;
|
||||
ret->disconnect = disconnect_git;
|
||||
ret->smart_options = &(data->options);
|
||||
|
||||
data->thin = 1;
|
||||
data->conn = NULL;
|
||||
data->uploadpack = "git-upload-pack";
|
||||
data->got_remote_heads = 0;
|
||||
} else {
|
||||
/* Unknown protocol in URL. Pass to external handler. */
|
||||
int len = external_specification_len(url);
|
||||
char *handler = xmalloc(len + 1);
|
||||
handler[len] = 0;
|
||||
strncpy(handler, url, len);
|
||||
transport_helper_init(ret, handler);
|
||||
}
|
||||
|
||||
if (ret->smart_options) {
|
||||
ret->smart_options->thin = 1;
|
||||
ret->smart_options->uploadpack = "git-upload-pack";
|
||||
if (remote->uploadpack)
|
||||
data->uploadpack = remote->uploadpack;
|
||||
data->receivepack = "git-receive-pack";
|
||||
ret->smart_options->uploadpack = remote->uploadpack;
|
||||
ret->smart_options->receivepack = "git-receive-pack";
|
||||
if (remote->receivepack)
|
||||
data->receivepack = remote->receivepack;
|
||||
ret->smart_options->receivepack = remote->receivepack;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -853,8 +946,23 @@ struct transport *transport_get(struct remote *remote, const char *url)
|
||||
int transport_set_option(struct transport *transport,
|
||||
const char *name, const char *value)
|
||||
{
|
||||
int git_reports = 1, protocol_reports = 1;
|
||||
|
||||
if (transport->smart_options)
|
||||
git_reports = set_git_option(transport->smart_options,
|
||||
name, value);
|
||||
|
||||
if (transport->set_option)
|
||||
return transport->set_option(transport, name, value);
|
||||
protocol_reports = transport->set_option(transport, name,
|
||||
value);
|
||||
|
||||
/* If either report is 0, report 0 (success). */
|
||||
if (!git_reports || !protocol_reports)
|
||||
return 0;
|
||||
/* If either reports -1 (invalid value), report -1. */
|
||||
if ((git_reports == -1) || (protocol_reports == -1))
|
||||
return -1;
|
||||
/* Otherwise if both report unknown, report unknown. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -865,9 +973,9 @@ int transport_push(struct transport *transport,
|
||||
*nonfastforward = 0;
|
||||
verify_remote_names(refspec_nr, refspec);
|
||||
|
||||
if (transport->push)
|
||||
if (transport->push) {
|
||||
return transport->push(transport, refspec_nr, refspec, flags);
|
||||
if (transport->push_refs) {
|
||||
} else if (transport->push_refs) {
|
||||
struct ref *remote_refs =
|
||||
transport->get_refs_list(transport, 1);
|
||||
struct ref *local_refs = get_local_heads();
|
||||
@@ -911,6 +1019,7 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
|
||||
{
|
||||
if (!transport->remote_refs)
|
||||
transport->remote_refs = transport->get_refs_list(transport, 0);
|
||||
|
||||
return transport->remote_refs;
|
||||
}
|
||||
|
||||
@@ -945,6 +1054,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
|
||||
}
|
||||
|
||||
rc = transport->fetch(transport, nr_heads, heads);
|
||||
|
||||
free(heads);
|
||||
return rc;
|
||||
}
|
||||
@@ -958,6 +1068,15 @@ void transport_unlock_pack(struct transport *transport)
|
||||
}
|
||||
}
|
||||
|
||||
int transport_connect(struct transport *transport, const char *name,
|
||||
const char *exec, int fd[2])
|
||||
{
|
||||
if (transport->connect)
|
||||
return transport->connect(transport, name, exec, fd);
|
||||
else
|
||||
die("Operation not supported by protocol");
|
||||
}
|
||||
|
||||
int transport_disconnect(struct transport *transport)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
22
transport.h
22
transport.h
@@ -4,6 +4,15 @@
|
||||
#include "cache.h"
|
||||
#include "remote.h"
|
||||
|
||||
struct git_transport_options {
|
||||
unsigned thin : 1;
|
||||
unsigned keep : 1;
|
||||
unsigned followtags : 1;
|
||||
int depth;
|
||||
const char *uploadpack;
|
||||
const char *receivepack;
|
||||
};
|
||||
|
||||
struct transport {
|
||||
struct remote *remote;
|
||||
const char *url;
|
||||
@@ -55,6 +64,8 @@ struct transport {
|
||||
**/
|
||||
int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
|
||||
int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
|
||||
int (*connect)(struct transport *connection, const char *name,
|
||||
const char *executable, int fd[2]);
|
||||
|
||||
/** get_refs_list(), fetch(), and push_refs() can keep
|
||||
* resources (such as a connection) reserved for futher
|
||||
@@ -65,6 +76,12 @@ struct transport {
|
||||
signed verbose : 3;
|
||||
/* Force progress even if the output is not a tty */
|
||||
unsigned progress : 1;
|
||||
/*
|
||||
* If transport is at least potentially smart, this points to
|
||||
* git_transport_options structure to use in case transport
|
||||
* actually turns out to be smart.
|
||||
*/
|
||||
struct git_transport_options *smart_options;
|
||||
};
|
||||
|
||||
#define TRANSPORT_PUSH_ALL 1
|
||||
@@ -115,6 +132,11 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);
|
||||
void transport_unlock_pack(struct transport *transport);
|
||||
int transport_disconnect(struct transport *transport);
|
||||
char *transport_anonymize_url(const char *url);
|
||||
void transport_take_over(struct transport *transport,
|
||||
struct child_process *child);
|
||||
|
||||
int transport_connect(struct transport *transport, const char *name,
|
||||
const char *exec, int fd[2]);
|
||||
|
||||
/* Transport methods defined outside transport.c */
|
||||
int transport_helper_init(struct transport *transport, const char *name);
|
||||
|
||||
202
unpack-trees.c
202
unpack-trees.c
@@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = {
|
||||
|
||||
/* bind_overlap */
|
||||
"Entry '%s' overlaps with '%s'. Cannot bind.",
|
||||
|
||||
/* sparse_not_uptodate_file */
|
||||
"Entry '%s' not uptodate. Cannot update sparse checkout.",
|
||||
|
||||
/* would_lose_orphaned */
|
||||
"Working tree file '%s' would be %s by sparse checkout update.",
|
||||
};
|
||||
|
||||
#define ERRORMSG(o,fld) \
|
||||
@@ -78,7 +84,7 @@ static int check_updates(struct unpack_trees_options *o)
|
||||
if (o->update && o->verbose_update) {
|
||||
for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
|
||||
struct cache_entry *ce = index->cache[cnt];
|
||||
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
|
||||
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE))
|
||||
total++;
|
||||
}
|
||||
|
||||
@@ -92,6 +98,13 @@ static int check_updates(struct unpack_trees_options *o)
|
||||
for (i = 0; i < index->cache_nr; i++) {
|
||||
struct cache_entry *ce = index->cache[i];
|
||||
|
||||
if (ce->ce_flags & CE_WT_REMOVE) {
|
||||
display_progress(progress, ++cnt);
|
||||
if (o->update)
|
||||
unlink_entry(ce);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ce->ce_flags & CE_REMOVE) {
|
||||
display_progress(progress, ++cnt);
|
||||
if (o->update)
|
||||
@@ -118,6 +131,57 @@ static int check_updates(struct unpack_trees_options *o)
|
||||
return errs != 0;
|
||||
}
|
||||
|
||||
static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
|
||||
static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o);
|
||||
|
||||
static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
|
||||
{
|
||||
const char *basename;
|
||||
|
||||
if (ce_stage(ce))
|
||||
return 0;
|
||||
|
||||
basename = strrchr(ce->name, '/');
|
||||
basename = basename ? basename+1 : ce->name;
|
||||
return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
|
||||
}
|
||||
|
||||
static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
|
||||
{
|
||||
int was_skip_worktree = ce_skip_worktree(ce);
|
||||
|
||||
if (will_have_skip_worktree(ce, o))
|
||||
ce->ce_flags |= CE_SKIP_WORKTREE;
|
||||
else
|
||||
ce->ce_flags &= ~CE_SKIP_WORKTREE;
|
||||
|
||||
/*
|
||||
* We only care about files getting into the checkout area
|
||||
* If merge strategies want to remove some, go ahead, this
|
||||
* flag will be removed eventually in unpack_trees() if it's
|
||||
* outside checkout area.
|
||||
*/
|
||||
if (ce->ce_flags & CE_REMOVE)
|
||||
return 0;
|
||||
|
||||
if (!was_skip_worktree && ce_skip_worktree(ce)) {
|
||||
/*
|
||||
* If CE_UPDATE is set, verify_uptodate() must be called already
|
||||
* also stat info may have lost after merged_entry() so calling
|
||||
* verify_uptodate() again may fail
|
||||
*/
|
||||
if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
|
||||
return -1;
|
||||
ce->ce_flags |= CE_WT_REMOVE;
|
||||
}
|
||||
if (was_skip_worktree && !ce_skip_worktree(ce)) {
|
||||
if (verify_absent_sparse(ce, "overwritten", o))
|
||||
return -1;
|
||||
ce->ce_flags |= CE_UPDATE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
|
||||
{
|
||||
int ret = o->fn(src, o);
|
||||
@@ -369,8 +433,9 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
|
||||
*/
|
||||
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
|
||||
{
|
||||
int ret;
|
||||
int i, ret;
|
||||
static struct cache_entry *dfc;
|
||||
struct exclude_list el;
|
||||
|
||||
if (len > MAX_UNPACK_TREES)
|
||||
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
|
||||
@@ -380,6 +445,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||
state.quiet = 1;
|
||||
state.refresh_cache = 1;
|
||||
|
||||
memset(&el, 0, sizeof(el));
|
||||
if (!core_apply_sparse_checkout || !o->update)
|
||||
o->skip_sparse_checkout = 1;
|
||||
if (!o->skip_sparse_checkout) {
|
||||
if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
|
||||
o->skip_sparse_checkout = 1;
|
||||
else
|
||||
o->el = ⪙
|
||||
}
|
||||
|
||||
memset(&o->result, 0, sizeof(o->result));
|
||||
o->result.initialized = 1;
|
||||
if (o->src_index) {
|
||||
@@ -400,26 +475,65 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||
info.fn = unpack_callback;
|
||||
info.data = o;
|
||||
|
||||
if (traverse_trees(len, t, &info) < 0)
|
||||
return unpack_failed(o, NULL);
|
||||
if (traverse_trees(len, t, &info) < 0) {
|
||||
ret = unpack_failed(o, NULL);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Any left-over entries in the index? */
|
||||
if (o->merge) {
|
||||
while (o->pos < o->src_index->cache_nr) {
|
||||
struct cache_entry *ce = o->src_index->cache[o->pos];
|
||||
if (unpack_index_entry(ce, o) < 0)
|
||||
return unpack_failed(o, NULL);
|
||||
if (unpack_index_entry(ce, o) < 0) {
|
||||
ret = unpack_failed(o, NULL);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (o->trivial_merges_only && o->nontrivial_merge)
|
||||
return unpack_failed(o, "Merge requires file-level merging");
|
||||
if (o->trivial_merges_only && o->nontrivial_merge) {
|
||||
ret = unpack_failed(o, "Merge requires file-level merging");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!o->skip_sparse_checkout) {
|
||||
int empty_worktree = 1;
|
||||
for (i = 0;i < o->result.cache_nr;i++) {
|
||||
struct cache_entry *ce = o->result.cache[i];
|
||||
|
||||
if (apply_sparse_checkout(ce, o)) {
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
/*
|
||||
* Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
|
||||
* area as a result of ce_skip_worktree() shortcuts in
|
||||
* verify_absent() and verify_uptodate(). Clear them.
|
||||
*/
|
||||
if (ce_skip_worktree(ce))
|
||||
ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE);
|
||||
else
|
||||
empty_worktree = 0;
|
||||
|
||||
}
|
||||
if (o->result.cache_nr && empty_worktree) {
|
||||
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
o->src_index = NULL;
|
||||
ret = check_updates(o) ? (-2) : 0;
|
||||
if (o->dst_index)
|
||||
*o->dst_index = o->result;
|
||||
|
||||
done:
|
||||
for (i = 0;i < el.nr;i++)
|
||||
free(el.excludes[i]);
|
||||
if (el.excludes)
|
||||
free(el.excludes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -436,6 +550,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
|
||||
return 0;
|
||||
if (!a && !b)
|
||||
return 1;
|
||||
if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
|
||||
return 0;
|
||||
return a->ce_mode == b->ce_mode &&
|
||||
!hashcmp(a->sha1, b->sha1);
|
||||
}
|
||||
@@ -445,16 +561,17 @@ static int same(struct cache_entry *a, struct cache_entry *b)
|
||||
* When a CE gets turned into an unmerged entry, we
|
||||
* want it to be up-to-date
|
||||
*/
|
||||
static int verify_uptodate(struct cache_entry *ce,
|
||||
struct unpack_trees_options *o)
|
||||
static int verify_uptodate_1(struct cache_entry *ce,
|
||||
struct unpack_trees_options *o,
|
||||
const char *error_msg)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (o->index_only || o->reset || ce_uptodate(ce))
|
||||
if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce))))
|
||||
return 0;
|
||||
|
||||
if (!lstat(ce->name, &st)) {
|
||||
unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID);
|
||||
unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
if (!changed)
|
||||
return 0;
|
||||
/*
|
||||
@@ -471,7 +588,21 @@ static int verify_uptodate(struct cache_entry *ce,
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return o->gently ? -1 :
|
||||
error(ERRORMSG(o, not_uptodate_file), ce->name);
|
||||
error(error_msg, ce->name);
|
||||
}
|
||||
|
||||
static int verify_uptodate(struct cache_entry *ce,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
|
||||
return 0;
|
||||
return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file));
|
||||
}
|
||||
|
||||
static int verify_uptodate_sparse(struct cache_entry *ce,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file));
|
||||
}
|
||||
|
||||
static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
|
||||
@@ -572,15 +703,16 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst,
|
||||
struct cache_entry *src;
|
||||
|
||||
src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
|
||||
return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID);
|
||||
return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
}
|
||||
|
||||
/*
|
||||
* We do not want to remove or overwrite a working tree file that
|
||||
* is not tracked, unless it is ignored.
|
||||
*/
|
||||
static int verify_absent(struct cache_entry *ce, const char *action,
|
||||
struct unpack_trees_options *o)
|
||||
static int verify_absent_1(struct cache_entry *ce, const char *action,
|
||||
struct unpack_trees_options *o,
|
||||
const char *error_msg)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
@@ -660,13 +792,30 @@ static int verify_absent(struct cache_entry *ce, const char *action,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int verify_absent(struct cache_entry *ce, const char *action,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
|
||||
return 0;
|
||||
return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked));
|
||||
}
|
||||
|
||||
static int verify_absent_sparse(struct cache_entry *ce, const char *action,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned));
|
||||
}
|
||||
|
||||
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
int update = CE_UPDATE;
|
||||
|
||||
if (old) {
|
||||
if (!old) {
|
||||
if (verify_absent(merge, "overwritten", o))
|
||||
return -1;
|
||||
invalidate_ce_path(merge, o);
|
||||
} else if (!(old->ce_flags & CE_CONFLICTED)) {
|
||||
/*
|
||||
* See if we can re-use the old CE directly?
|
||||
* That way we get the uptodate stat info.
|
||||
@@ -680,13 +829,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
|
||||
} else {
|
||||
if (verify_uptodate(old, o))
|
||||
return -1;
|
||||
if (ce_skip_worktree(old))
|
||||
update |= CE_SKIP_WORKTREE;
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (verify_absent(merge, "overwritten", o))
|
||||
return -1;
|
||||
invalidate_ce_path(merge, o);
|
||||
} else {
|
||||
/*
|
||||
* Previously unmerged entry left as an existence
|
||||
* marker by read_index_unmerged();
|
||||
*/
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
|
||||
add_entry(o, merge, update, CE_STAGEMASK);
|
||||
@@ -702,7 +854,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (verify_uptodate(old, o))
|
||||
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
|
||||
return -1;
|
||||
add_entry(o, ce, CE_REMOVE, 0);
|
||||
invalidate_ce_path(ce, o);
|
||||
@@ -1004,10 +1156,10 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
|
||||
|
||||
if (old && same(old, a)) {
|
||||
int update = 0;
|
||||
if (o->reset && !ce_uptodate(old)) {
|
||||
if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) {
|
||||
struct stat st;
|
||||
if (lstat(old->name, &st) ||
|
||||
ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
|
||||
ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
|
||||
update |= CE_UPDATE;
|
||||
}
|
||||
add_entry(o, old, update, 0);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define MAX_UNPACK_TREES 8
|
||||
|
||||
struct unpack_trees_options;
|
||||
struct exclude_list;
|
||||
|
||||
typedef int (*merge_fn_t)(struct cache_entry **src,
|
||||
struct unpack_trees_options *options);
|
||||
@@ -14,6 +15,8 @@ struct unpack_trees_error_msgs {
|
||||
const char *not_uptodate_dir;
|
||||
const char *would_lose_untracked;
|
||||
const char *bind_overlap;
|
||||
const char *sparse_not_uptodate_file;
|
||||
const char *would_lose_orphaned;
|
||||
};
|
||||
|
||||
struct unpack_trees_options {
|
||||
@@ -28,6 +31,7 @@ struct unpack_trees_options {
|
||||
skip_unmerged,
|
||||
initial_checkout,
|
||||
diff_index_cached,
|
||||
skip_sparse_checkout,
|
||||
gently;
|
||||
const char *prefix;
|
||||
int pos;
|
||||
@@ -44,6 +48,8 @@ struct unpack_trees_options {
|
||||
struct index_state *dst_index;
|
||||
struct index_state *src_index;
|
||||
struct index_state result;
|
||||
|
||||
struct exclude_list *el; /* for internal use */
|
||||
};
|
||||
|
||||
extern int unpack_trees(unsigned n, struct tree_desc *t,
|
||||
|
||||
Reference in New Issue
Block a user