'--file <filename>' can be used to tell the command to write to
that location (you can say '--local' but that is the default).
- This command will fail (with exit code ret) if:
+ This command will fail with non-zero status upon error. Some exit
+ codes are:
. The config file is invalid (ret=3),
. can not write to the config file (ret=4),
. no section or name was provided (ret=2),
. the section or key is invalid (ret=1),
. you try to unset an option which does not exist (ret=5),
- . you try to unset/set an option for which multiple lines match (ret=5),
- . you try to use an invalid regexp (ret=6), or
- . you use '--global' option without $HOME being properly set (ret=128).
+ . you try to unset/set an option for which multiple lines match (ret=5), or
+ . you try to use an invalid regexp (ret=6).
On success, the command returns the exit code 0.
--global::
For writing options: write to global ~/.gitconfig file rather than
- the repository .git/config.
+ the repository .git/config, write to $XDG_CONFIG_HOME/git/config file
+ if this file exists and the ~/.gitconfig file doesn't.
+
-For reading options: read only from global ~/.gitconfig rather than
-from all available files.
+For reading options: read only from global ~/.gitconfig and from
+$XDG_CONFIG_HOME/git/config rather than from all available files.
+
See also <<FILES>>.
FILES
-----
-If not set explicitly with '--file', there are three files where
+If not set explicitly with '--file', there are four files where
'git config' will search for configuration options:
$GIT_DIR/config::
User-specific configuration file. Also called "global"
configuration file.
+$XDG_CONFIG_HOME/git/config::
+ Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
+ or empty, $HOME/.config/git/config will be used. Any single-valued
+ variable set in this file will be overwritten by whatever is in
+ ~/.gitconfig. It is a good idea not to create this file if
+ you sometimes use older versions of Git, as support for this
+ file was added fairly recently.
+
$(prefix)/etc/gitconfig::
System-wide configuration file.
; Proxy settings
[core]
- gitproxy="proxy-command" for kernel.org
+ gitproxy=proxy-command for kernel.org
gitproxy=default-proxy ; for all the rest
you can set the filemode to true with
To add a new proxy, without altering any of the existing ones, use
------------
- % git config core.gitproxy '"proxy-command" for example.com'
+ % git config --add core.gitproxy '"proxy-command" for example.com'
------------
An example to use customized color from the configuration in your
and full access to internals.
See linkgit:gittutorial[7] to get started, then see
-link:everyday.html[Everyday Git] for a useful minimum set of commands, and
-"man git-commandname" for documentation of each command. CVS users may
-also want to read linkgit:gitcvs-migration[7]. See
-the link:user-manual.html[Git User's Manual] for a more in-depth
-introduction.
+link:everyday.html[Everyday Git] for a useful minimum set of
+commands. The link:user-manual.html[Git User's Manual] has a more
+in-depth introduction.
-The '<command>' is either a name of a Git command (see below) or an alias
-as defined in the configuration file (see linkgit:git-config[1]).
+After you mastered the basic concepts, you can come back to this
+page to learn what commands git offers. You can learn more about
+individual git commands with "git help command". linkgit:gitcli[7]
+manual page gives you an overview of the command line command syntax.
-Formatted and hyperlinked version of the latest git
-documentation can be viewed at
-`http://www.kernel.org/pub/software/scm/git/docs/`.
+Formatted and hyperlinked version of the latest git documentation
+can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`.
ifdef::stalenotes[]
[NOTE]
branch of the `git.git` repository.
Documentation for older releases are available here:
- * link:v1.7.11.5/git.html[documentation for release 1.7.11.5]
+* link:v1.7.12/git.html[documentation for release 1.7.12]
+
+* release notes for
+ link:RelNotes/1.7.12.txt[1.7.12].
+
+ * link:v1.7.11.6/git.html[documentation for release 1.7.11.6]
* release notes for
+ link:RelNotes/1.7.11.6.txt[1.7.11.6],
link:RelNotes/1.7.11.5.txt[1.7.11.5],
link:RelNotes/1.7.11.4.txt[1.7.11.4],
link:RelNotes/1.7.11.3.txt[1.7.11.3],
linkgit:git-replace[1] for more information.
-FURTHER DOCUMENTATION
----------------------
-
-See the references above to get started using git. The following is
-probably more detail than necessary for a first-time user.
-
-The link:user-manual.html#git-concepts[git concepts chapter of the
-user-manual] and linkgit:gitcore-tutorial[7] both provide
-introductions to the underlying git architecture.
-
-See linkgit:gitworkflows[7] for an overview of recommended workflows.
-
-See also the link:howto-index.html[howto] documents for some useful
-examples.
-
-The internals are documented in the
-link:technical/api-index.html[GIT API documentation].
-
GIT COMMANDS
------------
for a given pathname. These stages are used to hold the various
unmerged version of a file when a merge is in progress.
+FURTHER DOCUMENTATION
+---------------------
+
+See the references in the "description" section to get started
+using git. The following is probably more detail than necessary
+for a first-time user.
+
+The link:user-manual.html#git-concepts[git concepts chapter of the
+user-manual] and linkgit:gitcore-tutorial[7] both provide
+introductions to the underlying git architecture.
+
+See linkgit:gitworkflows[7] for an overview of recommended workflows.
+
+See also the link:howto-index.html[howto] documents for some useful
+examples.
+
+The internals are documented in the
+link:technical/api-index.html[GIT API documentation].
+
+Users migrating from CVS may also want to
+read linkgit:gitcvs-migration[7].
+
+
Authors
-------
Git was started by Linus Torvalds, and is currently maintained by Junio
#include "dir.h"
#include "diff.h"
#include "parse-options.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "rerere.h"
/*
* --check turns on checking that the working tree matches the
static int apply_verbosely;
static int allow_overlap;
static int no_add;
+static int threeway;
static const char *fake_ancestor;
static int line_termination = '\n';
static unsigned int p_context = UINT_MAX;
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
unsigned ws_rule;
- unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
unsigned int is_toplevel_relative:1;
unsigned int is_copy:1;
unsigned int is_rename:1;
unsigned int recount:1;
+ unsigned int conflicted_threeway:1;
+ unsigned int direct_to_threeway:1;
struct fragment *fragments;
char *result;
size_t resultsize;
char old_sha1_prefix[41];
char new_sha1_prefix[41];
struct patch *next;
+
+ /* three-way fallback result */
+ unsigned char threeway_stage[3][20];
};
static void free_fragment_list(struct fragment *list)
static void clear_image(struct image *image)
{
free(image->buf);
- image->buf = NULL;
- image->len = 0;
+ free(image->line_allocated);
+ memset(image, 0, sizeof(*image));
}
/* fmt must contain _one_ %s and no other substitution */
return 0;
}
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
{
- if (!ce)
- return 0;
-
- if (S_ISGITLINK(ce->ce_mode)) {
+ if (S_ISGITLINK(mode)) {
strbuf_grow(buf, 100);
- strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
+ strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1));
} else {
enum object_type type;
unsigned long sz;
char *result;
- result = read_sha1_file(ce->sha1, &type, &sz);
+ result = read_sha1_file(sha1, &type, &sz);
if (!result)
return -1;
/* XXX read_sha1_file NUL-terminates */
return 0;
}
+static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+{
+ if (!ce)
+ return 0;
+ return read_blob_object(buf, ce->sha1, ce->ce_mode);
+}
+
static struct patch *in_fn_table(const char *name)
{
struct string_list_item *item;
* item->util in the filename table records the status of the path.
* Usually it points at a patch (whose result records the contents
* of it after applying it), but it could be PATH_WAS_DELETED for a
- * path that a previously applied patch has already removed.
+ * path that a previously applied patch has already removed, or
+ * PATH_TO_BE_DELETED for a path that a later patch would remove.
+ *
+ * The latter is needed to deal with a case where two paths A and B
+ * are swapped by first renaming A to B and then renaming B to A;
+ * moving A to B should not be prevented due to presense of B as we
+ * will remove it in a later patch.
*/
- #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_TO_BE_DELETED ((struct patch *) -2)
#define PATH_WAS_DELETED ((struct patch *) -1)
static int to_be_deleted(struct patch *patch)
}
}
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+static int checkout_target(struct cache_entry *ce, struct stat *st)
+{
+ struct checkout costate;
+
+ memset(&costate, 0, sizeof(costate));
+ costate.base_dir = "";
+ costate.refresh_cache = 1;
+ if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
+ return error(_("cannot checkout %s"), ce->name);
+ return 0;
+}
+
+static struct patch *previous_patch(struct patch *patch, int *gone)
+{
+ struct patch *previous;
+
+ *gone = 0;
+ if (patch->is_copy || patch->is_rename)
+ return NULL; /* "git" patches do not depend on the order */
+
+ previous = in_fn_table(patch->old_name);
+ if (!previous)
+ return NULL;
+
+ if (to_be_deleted(previous))
+ return NULL; /* the deletion hasn't happened yet */
+
+ if (was_deleted(previous))
+ *gone = 1;
+
+ return previous;
+}
+
+static int verify_index_match(struct cache_entry *ce, struct stat *st)
+{
+ if (S_ISGITLINK(ce->ce_mode)) {
+ if (!S_ISDIR(st->st_mode))
+ return -1;
+ return 0;
+ }
+ return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+}
+
+#define SUBMODULE_PATCH_WITHOUT_INDEX 1
+
+static int load_patch_target(struct strbuf *buf,
+ struct cache_entry *ce,
+ struct stat *st,
+ const char *name,
+ unsigned expected_mode)
+{
+ if (cached) {
+ if (read_file_or_gitlink(ce, buf))
+ return error(_("read of %s failed"), name);
+ } else if (name) {
+ if (S_ISGITLINK(expected_mode)) {
+ if (ce)
+ return read_file_or_gitlink(ce, buf);
+ else
+ return SUBMODULE_PATCH_WITHOUT_INDEX;
+ } else {
+ if (read_old_data(st, name, buf))
+ return error(_("read of %s failed"), name);
+ }
+ }
+ return 0;
+}
+
+/*
+ * We are about to apply "patch"; populate the "image" with the
+ * current version we have, from the working tree or from the index,
+ * depending on the situation e.g. --cached/--index. If we are
+ * applying a non-git patch that incrementally updates the tree,
+ * we read from the result of a previous diff.
+ */
+static int load_preimage(struct image *image,
+ struct patch *patch, struct stat *st, struct cache_entry *ce)
{
struct strbuf buf = STRBUF_INIT;
- struct image image;
size_t len;
char *img;
- struct patch *tpatch;
+ struct patch *previous;
+ int status;
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch)) {
- return error(_("patch %s has been renamed/deleted"),
- patch->old_name);
- }
+ previous = previous_patch(patch, &status);
+ if (status)
+ return error(_("path %s has been renamed/deleted"),
+ patch->old_name);
+ if (previous) {
/* We have a patched copy in memory; use that. */
- strbuf_add(&buf, tpatch->result, tpatch->resultsize);
- } else if (cached) {
- if (read_file_or_gitlink(ce, &buf))
+ strbuf_add(&buf, previous->result, previous->resultsize);
+ } else {
+ status = load_patch_target(&buf, ce, st,
+ patch->old_name, patch->old_mode);
+ if (status < 0)
+ return status;
+ else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
+ /*
+ * There is no way to apply subproject
+ * patch without looking at the index.
+ * NEEDSWORK: shouldn't this be flagged
+ * as an error???
+ */
+ free_fragment_list(patch->fragments);
+ patch->fragments = NULL;
+ } else if (status) {
return error(_("read of %s failed"), patch->old_name);
- } else if (patch->old_name) {
- if (S_ISGITLINK(patch->old_mode)) {
- if (ce) {
- read_file_or_gitlink(ce, &buf);
- } else {
- /*
- * There is no way to apply subproject
- * patch without looking at the index.
- * NEEDSWORK: shouldn't this be flagged
- * as an error???
- */
- free_fragment_list(patch->fragments);
- patch->fragments = NULL;
- }
- } else {
- if (read_old_data(st, patch->old_name, &buf))
- return error(_("read of %s failed"), patch->old_name);
}
}
img = strbuf_detach(&buf, &len);
- prepare_image(&image, img, len, !patch->is_binary);
+ prepare_image(image, img, len, !patch->is_binary);
+ return 0;
+}
- if (apply_fragments(&image, patch) < 0)
- return -1; /* note with --reject this succeeds. */
- patch->result = image.buf;
- patch->resultsize = image.len;
- add_to_fn_table(patch);
- free(image.line_allocated);
+static int three_way_merge(struct image *image,
+ char *path,
+ const unsigned char *base,
+ const unsigned char *ours,
+ const unsigned char *theirs)
+{
+ mmfile_t base_file, our_file, their_file;
+ mmbuffer_t result = { NULL };
+ int status;
- if (0 < patch->is_delete && patch->resultsize)
- return error(_("removal patch leaves file contents"));
+ read_mmblob(&base_file, base);
+ read_mmblob(&our_file, ours);
+ read_mmblob(&their_file, theirs);
+ status = ll_merge(&result, path,
+ &base_file, "base",
+ &our_file, "ours",
+ &their_file, "theirs", NULL);
+ free(base_file.ptr);
+ free(our_file.ptr);
+ free(their_file.ptr);
+ if (status < 0 || !result.ptr) {
+ free(result.ptr);
+ return -1;
+ }
+ clear_image(image);
+ image->buf = result.ptr;
+ image->len = result.size;
+ return status;
+}
+
+/*
+ * When directly falling back to add/add three-way merge, we read from
+ * the current contents of the new_name. In no cases other than that
+ * this function will be called.
+ */
+static int load_current(struct image *image, struct patch *patch)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int status, pos;
+ size_t len;
+ char *img;
+ struct stat st;
+ struct cache_entry *ce;
+ char *name = patch->new_name;
+ unsigned mode = patch->new_mode;
+
+ if (!patch->is_new)
+ die("BUG: patch to %s is not a creation", patch->old_name);
+
+ pos = cache_name_pos(name, strlen(name));
+ if (pos < 0)
+ return error(_("%s: does not exist in index"), name);
+ ce = active_cache[pos];
+ if (lstat(name, &st)) {
+ if (errno != ENOENT)
+ return error(_("%s: %s"), name, strerror(errno));
+ if (checkout_target(ce, &st))
+ return -1;
+ }
+ if (verify_index_match(ce, &st))
+ return error(_("%s: does not match index"), name);
+
+ status = load_patch_target(&buf, ce, &st, name, mode);
+ if (status < 0)
+ return status;
+ else if (status)
+ return -1;
+ img = strbuf_detach(&buf, &len);
+ prepare_image(image, img, len, !patch->is_binary);
return 0;
}
-static int check_to_create_blob(const char *new_name, int ok_if_exists)
+static int try_threeway(struct image *image, struct patch *patch,
+ struct stat *st, struct cache_entry *ce)
{
- struct stat nst;
- if (!lstat(new_name, &nst)) {
- if (S_ISDIR(nst.st_mode) || ok_if_exists)
- return 0;
- /*
- * A leading component of new_name might be a symlink
- * that is going to be removed with this patch, but
- * still pointing at somewhere that has the path.
- * In such a case, path "new_name" does not exist as
- * far as git is concerned.
- */
- if (has_symlink_leading_path(new_name, strlen(new_name)))
- return 0;
+ unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+ size_t len;
+ int status;
+ char *img;
+ struct image tmp_image;
+
+ /* No point falling back to 3-way merge in these cases */
+ if (patch->is_delete ||
+ S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
+ return -1;
- return error(_("%s: already exists in working directory"), new_name);
+ /* Preimage the patch was prepared for */
+ if (patch->is_new)
+ write_sha1_file("", 0, blob_type, pre_sha1);
+ else if (get_sha1(patch->old_sha1_prefix, pre_sha1) ||
+ read_blob_object(&buf, pre_sha1, patch->old_mode))
+ return error("repository lacks the necessary blob to fall back on 3-way merge.");
+
+ fprintf(stderr, "Falling back to three-way merge...\n");
+
+ img = strbuf_detach(&buf, &len);
+ prepare_image(&tmp_image, img, len, 1);
+ /* Apply the patch to get the post image */
+ if (apply_fragments(&tmp_image, patch) < 0) {
+ clear_image(&tmp_image);
+ return -1;
+ }
+ /* post_sha1[] is theirs */
+ write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1);
+ clear_image(&tmp_image);
+
+ /* our_sha1[] is ours */
+ if (patch->is_new) {
+ if (load_current(&tmp_image, patch))
+ return error("cannot read the current contents of '%s'",
+ patch->new_name);
+ } else {
+ if (load_preimage(&tmp_image, patch, st, ce))
+ return error("cannot read the current contents of '%s'",
+ patch->old_name);
+ }
+ write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1);
+ clear_image(&tmp_image);
+
+ /* in-core three-way merge between post and our using pre as base */
+ status = three_way_merge(image, patch->new_name,
+ pre_sha1, our_sha1, post_sha1);
+ if (status < 0) {
+ fprintf(stderr, "Failed to fall back on three-way merge...\n");
+ return status;
+ }
+
+ if (status) {
+ patch->conflicted_threeway = 1;
+ if (patch->is_new)
+ hashclr(patch->threeway_stage[0]);
+ else
+ hashcpy(patch->threeway_stage[0], pre_sha1);
+ hashcpy(patch->threeway_stage[1], our_sha1);
+ hashcpy(patch->threeway_stage[2], post_sha1);
+ fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
+ } else {
+ fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
}
- else if ((errno != ENOENT) && (errno != ENOTDIR))
- return error("%s: %s", new_name, strerror(errno));
return 0;
}
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
+static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
{
- if (S_ISGITLINK(ce->ce_mode)) {
- if (!S_ISDIR(st->st_mode))
+ struct image image;
+
+ if (load_preimage(&image, patch, st, ce) < 0)
+ return -1;
+
+ if (patch->direct_to_threeway ||
+ apply_fragments(&image, patch) < 0) {
+ /* Note: with --reject, apply_fragments() returns 0 */
+ if (!threeway || try_threeway(&image, patch, st, ce) < 0)
return -1;
- return 0;
}
- return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+ patch->result = image.buf;
+ patch->resultsize = image.len;
+ add_to_fn_table(patch);
+ free(image.line_allocated);
+
+ if (0 < patch->is_delete && patch->resultsize)
+ return error(_("removal patch leaves file contents"));
+
+ return 0;
}
+/*
+ * If "patch" that we are looking at modifies or deletes what we have,
+ * we would want it not to lose any local modification we have, either
+ * in the working tree or in the index.
+ *
+ * This also decides if a non-git patch is a creation patch or a
+ * modification to an existing empty file. We do not check the state
+ * of the current tree for a creation patch in this function; the caller
+ * check_patch() separately makes sure (and errors out otherwise) that
+ * the path the patch creates does not exist in the current tree.
+ */
static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
{
const char *old_name = patch->old_name;
- struct patch *tpatch = NULL;
- int stat_ret = 0;
+ struct patch *previous = NULL;
+ int stat_ret = 0, status;
unsigned st_mode = 0;
- /*
- * Make sure that we do not have local modifications from the
- * index when we are looking at the index. Also make sure
- * we have the preimage file to be patched in the work tree,
- * unless --cached, which tells git to apply only in the index.
- */
if (!old_name)
return 0;
assert(patch->is_new <= 0);
+ previous = previous_patch(patch, &status);
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch))
- return error(_("%s: has been deleted/renamed"), old_name);
- st_mode = tpatch->new_mode;
+ if (status)
+ return error(_("path %s has been renamed/deleted"), old_name);
+ if (previous) {
+ st_mode = previous->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
if (stat_ret && errno != ENOENT)
return error(_("%s: %s"), old_name, strerror(errno));
}
- if (to_be_deleted(tpatch))
- tpatch = NULL;
-
- if (check_index && !tpatch) {
+ if (check_index && !previous) {
int pos = cache_name_pos(old_name, strlen(old_name));
if (pos < 0) {
if (patch->is_new < 0)
}
*ce = active_cache[pos];
if (stat_ret < 0) {
- struct checkout costate;
- /* checkout */
- memset(&costate, 0, sizeof(costate));
- costate.base_dir = "";
- costate.refresh_cache = 1;
- if (checkout_entry(*ce, &costate, NULL) ||
- lstat(old_name, st))
+ if (checkout_target(*ce, st))
return -1;
}
if (!cached && verify_index_match(*ce, st))
return error(_("%s: %s"), old_name, strerror(errno));
}
- if (!cached && !tpatch)
+ if (!cached && !previous)
st_mode = ce_mode_from_stat(*ce, st->st_mode);
if (patch->is_new < 0)
return 0;
}
+
+#define EXISTS_IN_INDEX 1
+#define EXISTS_IN_WORKTREE 2
+
+static int check_to_create(const char *new_name, int ok_if_exists)
+{
+ struct stat nst;
+
+ if (check_index &&
+ cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+ !ok_if_exists)
+ return EXISTS_IN_INDEX;
+ if (cached)
+ return 0;
+
+ if (!lstat(new_name, &nst)) {
+ if (S_ISDIR(nst.st_mode) || ok_if_exists)
+ return 0;
+ /*
+ * A leading component of new_name might be a symlink
+ * that is going to be removed with this patch, but
+ * still pointing at somewhere that has the path.
+ * In such a case, path "new_name" does not exist as
+ * far as git is concerned.
+ */
+ if (has_symlink_leading_path(new_name, strlen(new_name)))
+ return 0;
+
+ return EXISTS_IN_WORKTREE;
+ } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
+ return error("%s: %s", new_name, strerror(errno));
+ }
+ return 0;
+}
+
/*
* Check and apply the patch in-core; leave the result in patch->result
* for the caller to write it out to the final destination.
return status;
old_name = patch->old_name;
+ /*
+ * A type-change diff is always split into a patch to delete
+ * old, immediately followed by a patch to create new (see
+ * diff.c::run_diff()); in such a case it is Ok that the entry
+ * to be deleted by the previous patch is still in the working
+ * tree and in the index.
+ *
+ * A patch to swap-rename between A and B would first rename A
+ * to B and then rename B to A. While applying the first one,
+ * the presense of B should not stop A from getting renamed to
+ * B; ask to_be_deleted() about the later rename. Removal of
+ * B and rename from A to B is handled the same way by asking
+ * was_deleted().
+ */
if ((tpatch = in_fn_table(new_name)) &&
- (was_deleted(tpatch) || to_be_deleted(tpatch)))
- /*
- * A type-change diff is always split into a patch to
- * delete old, immediately followed by a patch to
- * create new (see diff.c::run_diff()); in such a case
- * it is Ok that the entry to be deleted by the
- * previous patch is still in the working tree and in
- * the index.
- */
+ (was_deleted(tpatch) || to_be_deleted(tpatch)))
ok_if_exists = 1;
else
ok_if_exists = 0;
if (new_name &&
((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
- if (check_index &&
- cache_name_pos(new_name, strlen(new_name)) >= 0 &&
- !ok_if_exists)
+ int err = check_to_create(new_name, ok_if_exists);
+
+ if (err && threeway) {
+ patch->direct_to_threeway = 1;
+ } else switch (err) {
+ case 0:
+ break; /* happy */
+ case EXISTS_IN_INDEX:
return error(_("%s: already exists in index"), new_name);
- if (!cached) {
- int err = check_to_create_blob(new_name, ok_if_exists);
- if (err)
- return err;
+ break;
+ case EXISTS_IN_WORKTREE:
+ return error(_("%s: already exists in working directory"),
+ new_name);
+ default:
+ return err;
}
+
if (!patch->new_mode) {
if (0 < patch->is_new)
patch->new_mode = S_IFREG | 0644;
name = patch->old_name ? patch->old_name : patch->new_name;
if (0 < patch->is_new)
continue;
- else if (get_sha1(patch->old_sha1_prefix, sha1))
+ else if (get_sha1_blob(patch->old_sha1_prefix, sha1))
/* git diff has no index line for mode/type changes */
if (!patch->lines_added && !patch->lines_deleted) {
if (get_current_sha1(patch->old_name, sha1))
ce = xcalloc(1, ce_size);
memcpy(ce->name, path, namelen);
ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = namelen;
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = namelen;
if (S_ISGITLINK(mode)) {
const char *s = buf;
die_errno(_("unable to write file '%s' mode %o"), path, mode);
}
+static void add_conflicted_stages_file(struct patch *patch)
+{
+ int stage, namelen;
+ unsigned ce_size, mode;
+ struct cache_entry *ce;
+
+ if (!update_index)
+ return;
+ namelen = strlen(patch->new_name);
+ ce_size = cache_entry_size(namelen);
+ mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
+
+ remove_file_from_cache(patch->new_name);
+ for (stage = 1; stage < 4; stage++) {
+ if (is_null_sha1(patch->threeway_stage[stage - 1]))
+ continue;
+ ce = xcalloc(1, ce_size);
+ memcpy(ce->name, patch->new_name, namelen);
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = namelen;
+ hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+ die(_("unable to add cache entry for %s"), patch->new_name);
+ }
+}
+
static void create_file(struct patch *patch)
{
char *path = patch->new_name;
if (!mode)
mode = S_IFREG | 0644;
create_one_file(path, mode, buf, size);
- add_index_file(path, mode, buf, size);
+
+ if (patch->conflicted_threeway)
+ add_conflicted_stages_file(patch);
+ else
+ add_index_file(path, mode, buf, size);
}
/* phase zero is to remove, phase one is to create */
int phase;
int errs = 0;
struct patch *l;
+ struct string_list cpath = STRING_LIST_INIT_DUP;
for (phase = 0; phase < 2; phase++) {
l = list;
errs = 1;
else {
write_out_one_result(l, phase);
- if (phase == 1 && write_out_one_reject(l))
- errs = 1;
+ if (phase == 1) {
+ if (write_out_one_reject(l))
+ errs = 1;
+ if (l->conflicted_threeway) {
+ string_list_append(&cpath, l->new_name);
+ errs = 1;
+ }
+ }
}
l = l->next;
}
}
+
+ if (cpath.nr) {
+ struct string_list_item *item;
+
+ sort_string_list(&cpath);
+ for_each_string_list_item(item, &cpath)
+ fprintf(stderr, "U %s\n", item->string);
+ string_list_clear(&cpath, 0);
+
+ rerere(0);
+ }
+
return errs;
}
!apply_with_reject)
exit(1);
- if (apply && write_out_results(list))
- exit(1);
+ if (apply && write_out_results(list)) {
+ if (apply_with_reject)
+ exit(1);
+ /* with --3way, we still need to write the index out */
+ return 1;
+ }
if (fake_ancestor)
build_fake_ancestor(list, fake_ancestor);
N_("apply a patch without touching the working tree")),
OPT_BOOLEAN(0, "apply", &force_apply,
N_("also apply the patch (use with --stat/--summary/--check)")),
+ OPT_BOOL('3', "3way", &threeway,
+ N_( "attempt three-way merge if a patch does not apply")),
OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
N_("build a temporary index based on embedded index information")),
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
argc = parse_options(argc, argv, prefix, builtin_apply_options,
apply_usage, 0);
+ if (apply_with_reject && threeway)
+ die("--reject and --3way cannot be used together.");
+ if (cached && threeway)
+ die("--cached and --3way cannot be used together.");
+ if (threeway) {
+ if (is_not_gitdir)
+ die(_("--3way outside a repository"));
+ check_index = 1;
+ }
if (apply_with_reject)
apply = apply_verbosely = 1;
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
paths[1] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
diff_opts.single_follow = origin->path;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
* we do not want to use diffcore_rename() actually to
ce = xcalloc(1, size);
hashcpy(ce->sha1, origin->blob_sha1);
memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
hashcpy(ce->sha1, sha1);
memcpy(ce->name, base, baselen);
memcpy(ce->name + baselen, pathname, len - baselen);
- ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
+ ce->ce_flags = create_ce_flags(0) | CE_UPDATE;
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
return 0;
init_revisions(&rev, NULL);
rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
- if (diff_setup_done(&rev.diffopt) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
}
static int get_value(const char *key_, const char *regex_)
{
- int ret = -1;
+ int ret = CONFIG_GENERIC_ERROR;
- char *global = NULL, *repo_config = NULL;
+ char *global = NULL, *xdg = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
struct config_include_data inc = CONFIG_INCLUDE_INIT;
config_fn_t fn;
local = given_config_file;
if (!local) {
- const char *home = getenv("HOME");
local = repo_config = git_pathdup("config");
- if (home)
- global = xstrdup(mkpath("%s/.gitconfig", home));
if (git_config_system())
system_wide = git_etc_gitconfig();
+ home_config_paths(&global, &xdg, "config");
}
if (use_key_regexp) {
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
free(key);
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
} else {
- if (git_config_parse_key(key_, &key, NULL))
+ if (git_config_parse_key(key_, &key, NULL)) {
+ ret = CONFIG_INVALID_KEY;
goto free_strings;
+ }
}
if (regex_) {
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
}
if (do_all && system_wide)
git_config_from_file(fn, system_wide, data);
+ if (do_all && xdg)
+ git_config_from_file(fn, xdg, data);
if (do_all && global)
git_config_from_file(fn, global, data);
if (do_all)
git_config_from_file(fn, local, data);
if (!do_all && !seen && global)
git_config_from_file(fn, global, data);
+ if (!do_all && !seen && xdg)
+ git_config_from_file(fn, xdg, data);
if (!do_all && !seen && system_wide)
git_config_from_file(fn, system_wide, data);
free_strings:
free(repo_config);
free(global);
+ free(xdg);
return ret;
}
}
if (use_global_config) {
- char *home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- given_config_file = user_config;
- } else {
+ char *user_config = NULL;
+ char *xdg_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
+
+ if (!user_config)
+ /*
+ * It is unknown if HOME/.gitconfig exists, so
+ * we do not know if we should write to XDG
+ * location; error out even if XDG_CONFIG_HOME
+ * is set and points at a sane location.
+ */
die("$HOME not set");
- }
+
+ if (access(user_config, R_OK) &&
+ xdg_config && !access(xdg_config, R_OK))
+ given_config_file = xdg_config;
+ else
+ given_config_file = user_config;
}
else if (use_system_config)
given_config_file = git_etc_gitconfig();
unsigned int ce_gid;
unsigned int ce_size;
unsigned int ce_flags;
+ unsigned int ce_namelen;
unsigned char sha1[20];
struct cache_entry *next;
struct cache_entry *dir_next;
char name[FLEX_ARRAY]; /* more */
};
-#define CE_NAMEMASK (0x0fff)
#define CE_STAGEMASK (0x3000)
#define CE_EXTENDED (0x4000)
#define CE_VALID (0x8000)
dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
}
-static inline unsigned create_ce_flags(size_t len, unsigned stage)
+static inline unsigned create_ce_flags(unsigned stage)
{
- if (len >= CE_NAMEMASK)
- len = CE_NAMEMASK;
- return (len | (stage << CE_STAGESHIFT));
-}
-
-static inline size_t ce_namelen(const struct cache_entry *ce)
-{
- size_t len = ce->ce_flags & CE_NAMEMASK;
- if (len < CE_NAMEMASK)
- return len;
- return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
+ return (stage << CE_STAGESHIFT);
}
+#define ce_namelen(ce) ((ce)->ce_namelen)
#define ce_size(ce) 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)
extern int unmerged_index(const struct index_state *);
extern int verify_path(const char *path);
extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
+extern int index_name_stage_pos(const struct index_state *, const char *name, int namelen, int stage);
extern int index_name_pos(const struct index_state *, const char *name, int namelen);
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
extern int fsync_object_files;
extern int core_preload_index;
extern int core_apply_sparse_checkout;
+extern int precomposed_unicode;
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
__attribute__((format (printf, 3, 4)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
+extern char *mkpathdup(const char *fmt, ...)
+ __attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
int mkdir_in_gitdir(const char *path);
+extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path);
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
unsigned mode;
};
+#define GET_SHA1_QUIETLY 01
+#define GET_SHA1_COMMIT 02
+#define GET_SHA1_COMMITTISH 04
+#define GET_SHA1_TREE 010
+#define GET_SHA1_TREEISH 020
+#define GET_SHA1_BLOB 040
+#define GET_SHA1_ONLY_TO_DIE 04000
+
extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix);
-static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
-{
- return get_sha1_with_mode_1(str, sha1, mode, 0, NULL);
-}
-extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, const char *prefix);
-static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc)
-{
- return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
-}
+extern int get_sha1_commit(const char *str, unsigned char *sha1);
+extern int get_sha1_committish(const char *str, unsigned char *sha1);
+extern int get_sha1_tree(const char *str, unsigned char *sha1);
+extern int get_sha1_treeish(const char *str, unsigned char *sha1);
+extern int get_sha1_blob(const char *str, unsigned char *sha1);
+extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
+extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc);
+
+typedef int each_abbrev_fn(const unsigned char *sha1, void *);
+extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
/*
* Try to read a SHA1 in hexadecimal format from the 40 characters
extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
+extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
extern void *read_object_with_reference(const unsigned char *sha1,
const char *required_type,
};
extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
extern int server_supports(const char *feature);
-extern const char *parse_feature_request(const char *features, const char *feature);
+extern int parse_feature_request(const char *features, const char *feature);
+extern const char *server_feature_value(const char *feature, int *len_ret);
+extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
#define CONFIG_NO_WRITE 4
#define CONFIG_NOTHING_SET 5
#define CONFIG_INVALID_PATTERN 6
+ #define CONFIG_GENERIC_ERROR 7
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
if (!endp) {
const char *plain = diff_get_color(ecb->color_diff,
DIFF_PLAIN);
+ putc('\n', ecb->opt->file);
emit_line_0(ecb->opt, plain, reset, '\\',
nneof, strlen(nneof));
}
if (!files) {
assert(insertions == 0 && deletions == 0);
- return fputs(_(" 0 files changed\n"), fp);
+ return fprintf(fp, "%s\n", _(" 0 files changed"));
}
strbuf_addf(&sb,
}
}
- int diff_setup_done(struct diff_options *options)
+ void diff_setup_done(struct diff_options *options)
{
int count = 0;
options->output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
}
-
- return 0;
}
static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
update=
prefix=
-# Resolve relative url by appending to parent's url
+# The function takes at most 2 arguments. The first argument is the
+# URL that navigates to the submodule origin repo. When relative, this URL
+# is relative to the superproject origin URL repo. The second up_path
+# argument, if specified, is the relative path that navigates
+# from the submodule working tree to the superproject working tree.
+#
+# The output of the function is the origin URL of the submodule.
+#
+# The output will either be an absolute URL or filesystem path (if the
+# superproject origin URL is an absolute URL or filesystem path,
+# respectively) or a relative file system path (if the superproject
+# origin URL is a relative file system path).
+#
+# When the output is a relative file system path, the path is either
+# relative to the submodule working tree, if up_path is specified, or to
+# the superproject working tree otherwise.
resolve_relative_url ()
{
remote=$(get_default_remote)
url="$1"
remoteurl=${remoteurl%/}
sep=/
+ up_path="$2"
+
+ case "$remoteurl" in
+ *:*|/*)
+ is_relative=
+ ;;
+ ./*|../*)
+ is_relative=t
+ ;;
+ *)
+ is_relative=t
+ remoteurl="./$remoteurl"
+ ;;
+ esac
+
while test -n "$url"
do
case "$url" in
sep=:
;;
*)
- die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ if test -z "$is_relative" || test "." = "$remoteurl"
+ then
+ die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ else
+ remoteurl=.
+ fi
;;
esac
;;
break;;
esac
done
- echo "$remoteurl$sep${url%/}"
+ remoteurl="$remoteurl$sep${url%/}"
+ echo "${is_relative:+${up_path}}${remoteurl#./}"
}
#
#
module_list()
{
- git ls-files --error-unmatch --stage -- "$@" |
+ (
+ git ls-files --error-unmatch --stage -- "$@" ||
+ echo "unmatched pathspec exists"
+ ) |
perl -e '
my %unmerged = ();
my ($null_sha1) = ("0" x 40);
+ my @out = ();
+ my $unmatched = 0;
while (<STDIN>) {
+ if (/^unmatched pathspec/) {
+ $unmatched = 1;
+ next;
+ }
chomp;
my ($mode, $sha1, $stage, $path) =
/^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
next unless $mode eq "160000";
if ($stage ne "0") {
if (!$unmerged{$path}++) {
- print "$mode $null_sha1 U\t$path\n";
+ push @out, "$mode $null_sha1 U\t$path\n";
}
next;
}
- print "$_\n";
+ push @out, "$_\n";
+ }
+ if ($unmatched) {
+ print "#unmatched\n";
+ } else {
+ print for (@out);
}
'
}
+ die_if_unmatched ()
+ {
+ if test "$1" = "#unmatched"
+ then
+ exit 1
+ fi
+ }
+
#
# Map submodule path to submodule name
#
rm -f "$gitdir/index"
else
mkdir -p "$gitdir_base"
- git clone $quiet -n ${reference:+"$reference"} \
- --separate-git-dir "$gitdir" "$url" "$sm_path" ||
+ (
+ clear_local_git_env
+ git clone $quiet -n ${reference:+"$reference"} \
+ --separate-git-dir "$gitdir" "$url" "$sm_path"
+ ) ||
die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
fi
module_list |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test -e "$sm_path"/.git
then
say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
# Copy url setting when it is not set yet
err=
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test "$stage" = U
then
echo >&2 "Skipping unmerged submodule $sm_path"
die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi
- if test "$subsha1" != "$sha1"
+ if test "$subsha1" != "$sha1" -o -n "$force"
then
subforce=$force
# If we don't already have a -f flag and the submodule has never been checked out
if [ -n "$files" ]
then
test -n "$cached" &&
- die "$(gettext -- "--cached cannot be used with --files")"
+ die "$(gettext "The --cached option cannot be used with the --files option")"
diff_cmd=diff-files
head=
fi
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
displaypath="$prefix$sm_path"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url)
# Possibly a url relative to parent
case "$url" in
./*|../*)
- url=$(resolve_relative_url "$url") || exit
+ # rewrite foo/bar as ../.. to find path from
+ # submodule work tree to superproject work tree
+ up_path="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" &&
+ # guarantee a trailing /
+ up_path=${up_path%/}/ &&
+ # path from submodule work tree to submodule origin repo
+ sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
+ # path from superproject work tree to submodule origin repo
+ super_config_url=$(resolve_relative_url "$url") || exit
+ ;;
+ *)
+ sub_origin_url="$url"
+ super_config_url="$url"
;;
esac
if git config "submodule.$name.url" >/dev/null 2>/dev/null
then
say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
- git config submodule."$name".url "$url"
+ git config submodule."$name".url "$super_config_url"
if test -e "$sm_path"/.git
then
clear_local_git_env
cd "$sm_path"
remote=$(get_default_remote)
- git config remote."$remote".url "$url"
+ git config remote."$remote".url "$sub_origin_url"
)
fi
fi
else {
printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
if (parse_commit(commit) != 0)
- printf("(bad commit)\n");
+ printf(_("(bad commit)\n"));
else {
const char *title;
int len = find_commit_subject(commit->buffer, &title);
struct cache_entry *ce;
ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
if (!ce)
- return error("addinfo_cache failed for path '%s'", path);
+ return error(_("addinfo_cache failed for path '%s'"), path);
return add_cache_entry(ce, options);
}
if (!cache_tree_fully_valid(active_cache_tree) &&
cache_tree_update(active_cache_tree,
active_cache, active_nr, 0) < 0)
- die("error building trees");
+ die(_("error building trees"));
result = lookup_tree(active_cache_tree->sha1);
opts.rename_score = o->rename_score;
opts.show_rename_progress = o->show_rename_progress;
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opts) < 0)
- die(_("diff setup failed"));
+ diff_setup_done(&opts);
diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
diffcore_std(&opts);
if (opts.needed_rename_limit > o->needed_rename_limit)
return newpath;
}
- static void flush_buffer(int fd, const char *buf, unsigned long size)
- {
- while (size > 0) {
- long ret = write_in_full(fd, buf, size);
- if (ret < 0) {
- /* Ignore epipe */
- if (errno == EPIPE)
- break;
- die_errno("merge-recursive");
- } else if (!ret) {
- die(_("merge-recursive: disk full?"));
- }
- size -= ret;
- buf += ret;
- }
- }
-
static int dir_in_way(const char *path, int check_working_copy)
{
int pos, pathlen = strlen(path);
static int make_room_for_path(struct merge_options *o, const char *path)
{
int status, i;
- const char *msg = "failed to create path '%s'%s";
+ const char *msg = _("failed to create path '%s'%s");
/* Unlink any D/F conflict files that are in the way */
for (i = 0; i < o->df_conflict_file_set.nr; i++) {
path[df_pathlen] == '/' &&
strncmp(path, df_path, df_pathlen) == 0) {
output(o, 3,
- "Removing %s to make room for subdirectory\n",
+ _("Removing %s to make room for subdirectory\n"),
df_path);
unlink(df_path);
unsorted_string_list_delete_item(&o->df_conflict_file_set,
if (status) {
if (status == -3) {
/* something else exists */
- error(msg, path, ": perhaps a D/F conflict?");
+ error(msg, path, _(": perhaps a D/F conflict?"));
return -1;
}
die(msg, path, "");
* tracking it.
*/
if (would_lose_untracked(path))
- return error("refusing to lose untracked file at '%s'",
+ return error(_("refusing to lose untracked file at '%s'"),
path);
/* Successful unlink is good.. */
if (errno == ENOENT)
return 0;
/* .. but not some other error (who really cares what?) */
- return error(msg, path, ": perhaps a D/F conflict?");
+ return error(msg, path, _(": perhaps a D/F conflict?"));
}
static void update_file_flags(struct merge_options *o,
buf = read_sha1_file(sha, &type, &size);
if (!buf)
- die("cannot read object %s '%s'", sha1_to_hex(sha), path);
+ die(_("cannot read object %s '%s'"), sha1_to_hex(sha), path);
if (type != OBJ_BLOB)
- die("blob expected for %s '%s'", sha1_to_hex(sha), path);
+ die(_("blob expected for %s '%s'"), sha1_to_hex(sha), path);
if (S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
if (convert_to_working_tree(path, buf, size, &strbuf)) {
mode = 0666;
fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
if (fd < 0)
- die_errno("failed to open '%s'", path);
+ die_errno(_("failed to open '%s'"), path);
- flush_buffer(fd, buf, size);
+ write_in_full(fd, buf, size);
close(fd);
} else if (S_ISLNK(mode)) {
char *lnk = xmemdupz(buf, size);
safe_create_leading_directories_const(path);
unlink(path);
if (symlink(lnk, path))
- die_errno("failed to symlink '%s'", path);
+ die_errno(_("failed to symlink '%s'"), path);
free(lnk);
} else
- die("do not know what to do with %06o %s '%s'",
+ die(_("do not know what to do with %06o %s '%s'"),
mode, sha1_to_hex(sha), path);
free(buf);
}
branch1, branch2);
if ((merge_status < 0) || !result_buf.ptr)
- die("Failed to execute internal merge");
+ die(_("Failed to execute internal merge"));
if (write_sha1_file(result_buf.ptr, result_buf.size,
blob_type, result.sha))
- die("Unable to add %s to database",
+ die(_("Unable to add %s to database"),
a->path);
free(result_buf.ptr);
if (!sha_eq(a->sha1, b->sha1))
result.clean = 0;
} else {
- die("unsupported object type in the tree");
+ die(_("unsupported object type in the tree"));
}
}
remove_file_from_cache(path);
update_file(o, 0, o_sha, o_mode, renamed ? renamed : path);
} else if (!a_sha) {
- output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
- "and %s in %s. Version %s of %s left in tree%s%s.",
- change, path, o->branch1,
- change_past, o->branch2, o->branch2, path,
- NULL == renamed ? "" : " at ",
- NULL == renamed ? "" : renamed);
- update_file(o, 0, b_sha, b_mode, renamed ? renamed : path);
+ if (!renamed) {
+ output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree."),
+ change, path, o->branch1, change_past,
+ o->branch2, o->branch2, path);
+ update_file(o, 0, b_sha, b_mode, path);
+ } else {
+ output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree at %s."),
+ change, path, o->branch1, change_past,
+ o->branch2, o->branch2, path, renamed);
+ update_file(o, 0, b_sha, b_mode, renamed);
+ }
} else {
- output(o, 1, "CONFLICT (%s/delete): %s deleted in %s "
- "and %s in %s. Version %s of %s left in tree%s%s.",
- change, path, o->branch2,
- change_past, o->branch1, o->branch1, path,
- NULL == renamed ? "" : " at ",
- NULL == renamed ? "" : renamed);
- if (renamed)
+ if (!renamed) {
+ output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree."),
+ change, path, o->branch2, change_past,
+ o->branch1, o->branch1, path);
+ } else {
+ output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
+ "and %s in %s. Version %s of %s left in tree at %s."),
+ change, path, o->branch2, change_past,
+ o->branch1, o->branch1, path, renamed);
update_file(o, 0, a_sha, a_mode, renamed);
+ }
/*
* No need to call update_file() on path when !renamed, since
* that would needlessly touch path. We could call
orig->sha1, orig->mode,
a_sha, a_mode,
b_sha, b_mode,
- "rename", "renamed");
+ _("rename"), _("renamed"));
if (o->call_depth) {
remove_file_from_cache(dest->path);
} else {
if (dir_in_way(rename->path, !o->call_depth)) {
dst_name = unique_path(o, rename->path, cur_branch);
- output(o, 1, "%s is a directory in %s adding as %s instead",
+ output(o, 1, _("%s is a directory in %s adding as %s instead"),
rename->path, other_branch, dst_name);
}
}
struct diff_filespec *a = ci->pair1->two;
struct diff_filespec *b = ci->pair2->two;
- output(o, 1, "CONFLICT (rename/rename): "
+ output(o, 1, _("CONFLICT (rename/rename): "
"Rename \"%s\"->\"%s\" in branch \"%s\" "
- "rename \"%s\"->\"%s\" in \"%s\"%s",
+ "rename \"%s\"->\"%s\" in \"%s\"%s"),
one->path, a->path, ci->branch1,
one->path, b->path, ci->branch2,
- o->call_depth ? " (left unresolved)" : "");
+ o->call_depth ? _(" (left unresolved)") : "");
if (o->call_depth) {
struct merge_file_info mfi;
struct diff_filespec other;
struct merge_file_info mfi_c1;
struct merge_file_info mfi_c2;
- output(o, 1, "CONFLICT (rename/rename): "
+ output(o, 1, _("CONFLICT (rename/rename): "
"Rename %s->%s in %s. "
- "Rename %s->%s in %s",
+ "Rename %s->%s in %s"),
a->path, c1->path, ci->branch1,
b->path, c2->path, ci->branch2);
} else {
char *new_path1 = unique_path(o, path, ci->branch1);
char *new_path2 = unique_path(o, path, ci->branch2);
- output(o, 1, "Renaming %s to %s and %s to %s instead",
+ output(o, 1, _("Renaming %s to %s and %s to %s instead"),
a->path, new_path1, b->path, new_path2);
remove_file(o, 0, path, 0);
update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
} else if (!sha_eq(dst_other.sha1, null_sha1)) {
clean_merge = 0;
try_merge = 1;
- output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. "
- "%s added in %s",
+ output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. "
+ "%s added in %s"),
ren1_src, ren1_dst, branch1,
ren1_dst, branch2);
if (o->call_depth) {
ren1->pair->two->sha1, ren1->pair->two->mode,
dst_other.sha1, dst_other.mode,
branch1, branch2);
- output(o, 1, "Adding merged %s", ren1_dst);
+ output(o, 1, _("Adding merged %s"), ren1_dst);
update_file(o, 0, mfi.sha, mfi.mode, ren1_dst);
try_merge = 0;
} else {
char *new_path = unique_path(o, ren1_dst, branch2);
- output(o, 1, "Adding as %s instead", new_path);
+ output(o, 1, _("Adding as %s instead"), new_path);
update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
free(new_path);
}
unsigned long size;
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
- return error("cannot read object %s", sha1_to_hex(sha1));
+ return error(_("cannot read object %s"), sha1_to_hex(sha1));
if (type != OBJ_BLOB) {
free(buf);
- return error("object %s is not a blob", sha1_to_hex(sha1));
+ return error(_("object %s is not a blob"), sha1_to_hex(sha1));
}
strbuf_attach(dst, buf, size, size + 1);
return 0;
o_sha, o_mode,
a_sha, a_mode,
b_sha, b_mode,
- "modify", "modified");
+ _("modify"), _("modified"));
}
static int merge_content(struct merge_options *o,
unsigned char *b_sha, int b_mode,
struct rename_conflict_info *rename_conflict_info)
{
- const char *reason = "content";
+ const char *reason = _("content");
const char *path1 = NULL, *path2 = NULL;
struct merge_file_info mfi;
struct diff_filespec one, a, b;
unsigned df_conflict_remains = 0;
if (!o_sha) {
- reason = "add/add";
+ reason = _("add/add");
o_sha = (unsigned char *)null_sha1;
}
one.path = a.path = b.path = (char *)path;
if (mfi.clean && !df_conflict_remains &&
sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {
int path_renamed_outside_HEAD;
- output(o, 3, "Skipped %s (merged same as existing)", path);
+ output(o, 3, _("Skipped %s (merged same as existing)"), path);
/*
* The content merge resulted in the same file contents we
* already had. We can return early if those file contents
return mfi.clean;
}
} else
- output(o, 2, "Auto-merging %s", path);
+ output(o, 2, _("Auto-merging %s"), path);
if (!mfi.clean) {
if (S_ISGITLINK(mfi.mode))
- reason = "submodule";
- output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+ reason = _("submodule");
+ output(o, 1, _("CONFLICT (%s): Merge conflict in %s"),
reason, path);
if (rename_conflict_info && !df_conflict_remains)
update_stages(path, &one, &a, &b);
}
new_path = unique_path(o, path, rename_conflict_info->branch1);
- output(o, 1, "Adding as %s instead", new_path);
+ output(o, 1, _("Adding as %s instead"), new_path);
update_file(o, 0, mfi.sha, mfi.mode, new_path);
free(new_path);
mfi.clean = 0;
/* Deleted in both or deleted in one and
* unchanged in the other */
if (a_sha)
- output(o, 2, "Removing %s", path);
+ output(o, 2, _("Removing %s"), path);
/* do not touch working file if it did not exist */
remove_file(o, 1, path, !a_sha);
} else {
other_branch = o->branch2;
mode = a_mode;
sha = a_sha;
- conf = "file/directory";
+ conf = _("file/directory");
} else {
add_branch = o->branch2;
other_branch = o->branch1;
mode = b_mode;
sha = b_sha;
- conf = "directory/file";
+ conf = _("directory/file");
}
if (dir_in_way(path, !o->call_depth)) {
char *new_path = unique_path(o, path, add_branch);
clean_merge = 0;
- output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
- "Adding %s as %s",
+ output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
+ "Adding %s as %s"),
conf, path, other_branch, path, new_path);
if (o->call_depth)
remove_file_from_cache(path);
remove_file_from_cache(path);
free(new_path);
} else {
- output(o, 2, "Adding %s", path);
+ output(o, 2, _("Adding %s"), path);
/* do not overwrite file if already present */
update_file_flags(o, sha, mode, path, 1, !a_sha);
}
*/
remove_file(o, 1, path, !a_mode);
} else
- die("Fatal merge failure, shouldn't happen.");
+ die(_("Fatal merge failure, shouldn't happen."));
return clean_merge;
}
}
if (sha_eq(common->object.sha1, merge->object.sha1)) {
- output(o, 0, "Already up-to-date!");
+ output(o, 0, _("Already up-to-date!"));
*result = head;
return 1;
}
if (code != 0) {
if (show(o, 4) || o->call_depth)
- die("merging of trees %s and %s failed",
+ die(_("merging of trees %s and %s failed"),
sha1_to_hex(head->object.sha1),
sha1_to_hex(merge->object.sha1));
else
for (i = 0; i < entries->nr; i++) {
struct stage_data *e = entries->items[i].util;
if (!e->processed)
- die("Unprocessed path??? %s",
+ die(_("Unprocessed path??? %s"),
entries->items[i].string);
}
int clean;
if (show(o, 4)) {
- output(o, 4, "Merging:");
+ output(o, 4, _("Merging:"));
output_commit_title(o, h1);
output_commit_title(o, h2);
}
}
if (show(o, 5)) {
- output(o, 5, "found %u common ancestor(s):", commit_list_count(ca));
+ unsigned cnt = commit_list_count(ca);
+
+ output(o, 5, Q_("found %u common ancestor:",
+ "found %u common ancestors:", cnt), cnt);
for (iter = ca; iter; iter = iter->next)
output_commit_title(o, iter->item);
}
o->call_depth--;
if (!merged_common_ancestors)
- die("merge returned no commit");
+ die(_("merge returned no commit"));
}
discard_cache();
for (i = 0; i < num_base_list; ++i) {
struct commit *base;
if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i]))))
- return error("Could not parse object '%s'",
+ return error(_("Could not parse object '%s'"),
sha1_to_hex(base_list[i]));
commit_list_insert(base, &ca);
}
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(lock)))
- return error("Unable to write index.");
+ return error(_("Unable to write index."));
return clean ? 0 : 1;
}
diff_setup(&opt);
DIFF_OPT_SET(&opt, RECURSIVE);
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&opt);
diff_tree_sha1(base, remote, "", &opt);
diffcore_std(&opt);
diff_setup(&opt);
DIFF_OPT_SET(&opt, RECURSIVE);
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&opt);
diff_tree_sha1(base, local, "", &opt);
diffcore_std(&opt);
free(changes);
if (o->verbosity >= 4)
- printf("Merge result: %i unmerged notes and a %s notes tree\n",
- conflicts, t->dirty ? "dirty" : "clean");
+ printf(t->dirty ?
+ "Merge result: %i unmerged notes and a dirty notes tree\n" :
+ "Merge result: %i unmerged notes and a clean notes tree\n",
+ conflicts);
return conflicts ? -1 : 1;
}
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
+/* Mask for the name length in ce_flags in the on-disk index */
+
+#define CE_NAMEMASK (0x0fff)
+
/* Index extensions.
*
* The first letter should be 'A'..'Z' for extensions that are not
new = xmalloc(cache_entry_size(namelen));
copy_cache_entry(new, old);
- new->ce_flags &= ~(CE_STATE_MASK | CE_NAMEMASK);
- new->ce_flags |= (namelen >= CE_NAMEMASK ? CE_NAMEMASK : namelen);
+ new->ce_flags &= ~CE_STATE_MASK;
+ new->ce_namelen = namelen;
memcpy(new->name, new_name, namelen + 1);
cache_tree_invalidate_path(istate->cache_tree, old->name);
return c1 - c2;
}
-int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
+int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
{
- int len1, len2, len, cmp;
-
- len1 = flags1 & CE_NAMEMASK;
- if (CE_NAMEMASK <= len1)
- len1 = strlen(name1 + CE_NAMEMASK) + CE_NAMEMASK;
- len2 = flags2 & CE_NAMEMASK;
- if (CE_NAMEMASK <= len2)
- len2 = strlen(name2 + CE_NAMEMASK) + CE_NAMEMASK;
- len = len1 < len2 ? len1 : len2;
+ int len = len1 < len2 ? len1 : len2;
+ int cmp;
cmp = memcmp(name1, name2, len);
if (cmp)
if (len1 > len2)
return 1;
- /* Compare stages */
- flags1 &= CE_STAGEMASK;
- flags2 &= CE_STAGEMASK;
-
- if (flags1 < flags2)
+ if (stage1 < stage2)
return -1;
- if (flags1 > flags2)
+ if (stage1 > stage2)
return 1;
return 0;
}
-int index_name_pos(const struct index_state *istate, const char *name, int namelen)
+int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
+{
+ return cache_name_stage_compare(name1, len1, 0, name2, len2, 0);
+}
+
+int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
{
int first, last;
while (last > first) {
int next = (last + first) >> 1;
struct cache_entry *ce = istate->cache[next];
- int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags);
+ int cmp = cache_name_stage_compare(name, namelen, stage, ce->name, ce_namelen(ce), ce_stage(ce));
if (!cmp)
return next;
if (cmp < 0) {
return -first-1;
}
+int index_name_pos(const struct index_state *istate, const char *name, int namelen)
+{
+ return index_name_stage_pos(istate, name, namelen, 0);
+}
+
/* Remove entry, return true if there are more entries to go.. */
int remove_index_entry_at(struct index_state *istate, int pos)
{
size = cache_entry_size(namelen);
ce = xcalloc(1, size);
memcpy(ce->name, path, namelen);
- ce->ce_flags = namelen;
+ ce->ce_namelen = namelen;
if (!intent_only)
fill_stat_cache_info(ce, st);
else
hashcpy(ce->sha1, sha1);
memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, stage);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
if (refresh)
}
len = slash - name;
- pos = index_name_pos(istate, name, create_ce_flags(len, stage));
+ pos = index_name_stage_pos(istate, name, len, stage);
if (pos >= 0) {
/*
* Found one, but not so fast. This could
int new_only = option & ADD_CACHE_NEW_ONLY;
cache_tree_invalidate_path(istate->cache_tree, ce->name);
- pos = index_name_pos(istate, ce->name, ce->ce_flags);
+ pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
/* existing match? Just replace it. */
if (pos >= 0) {
if (!ok_to_replace)
return error("'%s' appears as both a file and as a directory",
ce->name);
- pos = index_name_pos(istate, ce->name, ce->ce_flags);
+ pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
pos = -pos-1;
}
return pos + 1;
continue;
if (pathspec &&
- !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+ !match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
filtered = 1;
if (ce_stage(ce)) {
ce->ce_uid = ntoh_l(ondisk->uid);
ce->ce_gid = ntoh_l(ondisk->gid);
ce->ce_size = ntoh_l(ondisk->size);
- ce->ce_flags = flags;
+ ce->ce_flags = flags & ~CE_NAMEMASK;
+ ce->ce_namelen = len;
hashcpy(ce->sha1, ondisk->sha1);
memcpy(ce->name, name, len);
ce->name[len] = '\0';
size_t mmap_size;
struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
- errno = EBUSY;
if (istate->initialized)
return istate->cache_nr;
- errno = ENOENT;
istate->timestamp.sec = 0;
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY);
if (fstat(fd, &st))
die_errno("cannot stat the open index");
- errno = EINVAL;
mmap_size = xsize_t(st.st_size);
if (mmap_size < sizeof(struct cache_header) + 20)
die("index file smaller than expected");
mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
- close(fd);
if (mmap == MAP_FAILED)
die_errno("unable to map index file");
+ close(fd);
hdr = mmap;
if (verify_hdr(hdr, mmap_size) < 0)
unmap:
munmap(mmap, mmap_size);
- errno = EINVAL;
die("index file corrupt");
}
static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
struct cache_entry *ce)
{
+ short flags;
+
ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
ondisk->gid = htonl(ce->ce_gid);
ondisk->size = htonl(ce->ce_size);
hashcpy(ondisk->sha1, ce->sha1);
- ondisk->flags = htons(ce->ce_flags);
+
+ flags = ce->ce_flags;
+ flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce));
+ ondisk->flags = htons(flags);
if (ce->ce_flags & CE_EXTENDED) {
struct ondisk_cache_entry_extended *ondisk2;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
if (!ce_stage(ce))
continue;
unmerged = 1;
- len = strlen(ce->name);
+ len = ce_namelen(ce);
size = cache_entry_size(len);
new_ce = xcalloc(1, size);
memcpy(new_ce->name, ce->name, len);
- new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
+ new_ce->ce_flags = create_ce_flags(0) | CE_CONFLICTED;
+ new_ce->ce_namelen = len;
new_ce->ce_mode = ce->ce_mode;
if (add_index_entry(istate, new_ce, 0))
return error("%s: cannot drop to stage #0",
flags ^= UNINTERESTING;
arg++;
}
- if (get_sha1(arg, sha1))
+ if (get_sha1_committish(arg, sha1))
return 0;
while (1) {
it = get_reference(revs, arg, sha1, 0);
revs->limited = 1;
}
-int handle_revision_arg(const char *arg_, struct rev_info *revs,
- int flags,
- int cant_be_filename)
+int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
{
- unsigned mode;
+ struct object_context oc;
char *dotdot;
struct object *object;
unsigned char sha1[20];
int local_flags;
const char *arg = arg_;
+ int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
+ unsigned get_sha1_flags = 0;
dotdot = strstr(arg, "..");
if (dotdot) {
next = "HEAD";
if (dotdot == arg)
this = "HEAD";
- if (!get_sha1(this, from_sha1) &&
- !get_sha1(next, sha1)) {
+ if (!get_sha1_committish(this, from_sha1) &&
+ !get_sha1_committish(next, sha1)) {
struct commit *a, *b;
struct commit_list *exclude;
local_flags = UNINTERESTING;
arg++;
}
- if (get_sha1_with_mode(arg, sha1, &mode))
+
+ if (revarg_opt & REVARG_COMMITTISH)
+ get_sha1_flags = GET_SHA1_COMMITTISH;
+
+ if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
return revs->ignore_missing ? 0 : -1;
if (!cant_be_filename)
verify_non_filename(revs->prefix, arg);
object = get_reference(revs, arg, sha1, flags ^ local_flags);
add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
- add_pending_object_with_mode(revs, object, arg, mode);
+ add_pending_object_with_mode(revs, object, arg, oc.mode);
return 0;
}
}
die("options not supported in --stdin mode");
}
- if (handle_revision_arg(sb.buf, revs, 0, 1))
+ if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
die("bad revision '%s'", sb.buf);
}
if (seen_dashdash)
*/
int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
{
- int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
+ int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
struct cmdline_pathspec prune_data;
const char *submodule = NULL;
/* Second, deal with arguments and options */
flags = 0;
+ revarg_opt = opt ? opt->revarg_opt : 0;
+ if (seen_dashdash)
+ revarg_opt |= REVARG_CANNOT_BE_FILENAME;
read_from_stdin = 0;
for (left = i = 1; i < argc; i++) {
const char *arg = argv[i];
continue;
}
- if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
+
+ if (handle_revision_arg(arg, revs, flags, revarg_opt)) {
int j;
if (seen_dashdash || *arg == '^')
die("bad revision '%s'", arg);
if (revs->def && !revs->pending.nr && !got_rev_arg) {
unsigned char sha1[20];
struct object *object;
- unsigned mode;
- if (get_sha1_with_mode(revs->def, sha1, &mode))
+ struct object_context oc;
+ if (get_sha1_with_context(revs->def, 0, sha1, &oc))
die("bad default revision '%s'", revs->def);
object = get_reference(revs, revs->def, sha1, 0);
- add_pending_object_with_mode(revs, object, revs->def, mode);
+ add_pending_object_with_mode(revs, object, revs->def, oc.mode);
}
/* Did the user ask for any diff output? Run the diff! */
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
- if (diff_setup_done(&revs->diffopt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&revs->diffopt);
compile_grep_patterns(&revs->grep_filter);
}
/*
- * Now pick up what they want to give us
+ * If our max_count counter has reached zero, then we are done. We
+ * don't simply return NULL because we still might need to show
+ * boundary commits. But we want to avoid calling get_revision_1, which
+ * might do a considerable amount of work finding the next commit only
+ * for us to throw it away.
+ *
+ * If it is non-zero, then either we don't have a max_count at all
+ * (-1), or it is still counting, in which case we decrement.
*/
- c = get_revision_1(revs);
- if (c) {
- while (0 < revs->skip_count) {
- revs->skip_count--;
- c = get_revision_1(revs);
- if (!c)
- break;
+ if (revs->max_count) {
+ c = get_revision_1(revs);
+ if (c) {
+ while (0 < revs->skip_count) {
+ revs->skip_count--;
+ c = get_revision_1(revs);
+ if (!c)
+ break;
+ }
}
- }
- /*
- * Check the max_count.
- */
- switch (revs->max_count) {
- case -1:
- break;
- case 0:
- c = NULL;
- break;
- default:
- revs->max_count--;
+ if (revs->max_count > 0)
+ revs->max_count--;
}
if (c)
test_cmp expect url
'
+ test_failure_with_unknown_submodule () {
+ test_must_fail git submodule $1 no-such-submodule 2>output.err &&
+ grep "^error: .*no-such-submodule" output.err
+ }
+
+ test_expect_success 'init should fail with unknown submodule' '
+ test_failure_with_unknown_submodule init
+ '
+
+ test_expect_success 'update should fail with unknown submodule' '
+ test_failure_with_unknown_submodule update
+ '
+
+ test_expect_success 'status should fail with unknown submodule' '
+ test_failure_with_unknown_submodule status
+ '
+
+ test_expect_success 'sync should fail with unknown submodule' '
+ test_failure_with_unknown_submodule sync
+ '
+
test_expect_success 'update should fail when path is used by a file' '
echo hello >expect &&
'
test_expect_success 'submodule <invalid-path> warns' '
-
- git submodule no-such-submodule 2> output.err &&
- grep "^error: .*no-such-submodule" output.err
-
+ test_failure_with_unknown_submodule
'
test_expect_success 'add submodules without specifying an explicit path' '
git add sub &&
git config -f .gitmodules submodule.sub.path sub &&
git config -f .gitmodules submodule.sub.url ../subrepo &&
- cp .git/config pristine-.git-config
+ cp .git/config pristine-.git-config &&
+ cp .gitmodules pristine-.gitmodules
)
'
-test_expect_success 'relative path works with URL' '
+test_expect_success '../subrepo works with URL - ssh://hostname/repo' '
(
cd reltest &&
cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
git config remote.origin.url ssh://hostname/repo &&
git submodule init &&
test "$(git config submodule.sub.url)" = ssh://hostname/subrepo
)
'
-test_expect_success 'relative path works with user@host:path' '
+test_expect_success '../subrepo works with port-qualified URL - ssh://hostname:22/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ssh://hostname:22/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ssh://hostname:22/subrepo
+ )
+'
+
+# About the choice of the path in the next test:
+# - double-slash side-steps path mangling issues on Windows
+# - it is still an absolute local path
+# - there cannot be a server with a blank in its name just in case the
+# path is used erroneously to access a //server/share style path
+test_expect_success '../subrepo path works with local path - //somewhere else/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url "//somewhere else/repo" &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = "//somewhere else/subrepo"
+ )
+'
+
+test_expect_success '../subrepo works with file URL - file:///tmp/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url file:///tmp/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = file:///tmp/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with helper URL- helper:://hostname/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url helper:://hostname/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = helper:://hostname/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with scp-style URL - user@host:repo' '
(
cd reltest &&
cp pristine-.git-config .git/config &&
)
'
+test_expect_success '../subrepo works with scp-style URL - user@host:path/to/repo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url user@host:path/to/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = user@host:path/to/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url foo &&
+ # actual: fails with an error
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = foo/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ./foo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ./foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = foo/subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ../foo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ../subrepo
+ )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo/bar' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ git config remote.origin.url ../foo/bar &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ../foo/subrepo
+ )
+'
+
+test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.git' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ cp pristine-.gitmodules .gitmodules &&
+ mkdir -p a/b/c &&
+ (cd a/b/c; git init) &&
+ git config remote.origin.url ../foo/bar.git &&
+ git submodule add ../bar/a/b/c ./a/b/c &&
+ git submodule init &&
+ test "$(git config submodule.a/b/c.url)" = ../foo/bar/a/b/c
+ )
+'
+
test_expect_success 'moving the superproject does not break submodules' '
(
cd addtest &&