Merge branch 'master' of git://repo.or.cz/alt-git

This commit is contained in:
Johannes Sixt
2010-01-14 21:23:05 +01:00
54 changed files with 2247 additions and 234 deletions

5
.gitignore vendored
View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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];

View File

@@ -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
---

View File

@@ -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
--------

View File

@@ -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
-------------

View File

@@ -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))`.

View File

@@ -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:

View File

@@ -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,

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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 &&

View File

@@ -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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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()
};

View File

@@ -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. */

View File

@@ -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
View File

@@ -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[];

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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"
;;
--)

View File

@@ -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" "$@"

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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"

View 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

View File

@@ -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

View 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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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
View 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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 = &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);

View File

@@ -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,