Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Documentation/RelNotes/2.56.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ UI, Workflows & Features
syscall fails has also been improved to name both the source and
the destination.

* 'git add' has been taught a new '--resolved' option to stage
conflict-resolved paths, while leaving unrelated local changes
unstaged. It scans the unmerged paths for leftover conflict
markers and aborts if any are found.


Performance, Internal Implementation, Development Support etc.
--------------------------------------------------------------
Expand Down Expand Up @@ -334,6 +339,22 @@ Performance, Internal Implementation, Development Support etc.
exit codes (0 for success, 1 for non-ancestor, 128 for errors) and
to ensure it cannot be combined with '--all'.

* The 'TRACE2_ANCESTRY' prerequisite in the 't0213' test script has been
refined to avoid failures under user-mode emulation by verifying that
the ancestry collector reports the expected process names rather than
the emulator binary name.

* Concurrent downloads of packfiles via packfile URIs and dumb HTTP are
safer by avoiding concurrent appends to the staging file. Opening in
read-write mode with separate file offsets prevents corruption and
preserves resumability. 'fetch-pack' now tolerates pre-existing
'.keep' files.

* The 'ssh-agent' tests in 't7528' have been fixed to work when the
user's login shell is csh-like, by explicitly passing '-s' to
'ssh-agent' to force Bourne shell syntax.
(merge d5dd17756d kl/t7528-ssh-agent-for-csh-users later to maint).


Fixes since v2.55
-----------------
Expand Down
10 changes: 9 additions & 1 deletion Documentation/git-add.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SYNOPSIS
git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
[--edit | -e] [--[no-]all | -A | --[no-]ignore-removal | [--update | -u]] [--sparse]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--resolved] [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]

DESCRIPTION
Expand Down Expand Up @@ -195,6 +195,14 @@ for `git add --no-all <pathspec>...`, i.e. ignored removed files.
while a _CRLF_ cleans to _LF_, a _CRCRLF_ sequence is only partially
cleaned to _CRLF_.

`--resolved`::
Update the index for unmerged paths matching _<pathspec>_ where
no conflict markers remain in the working tree. Unmerged paths
without conflict markers (including binary files and file
deletions) are staged as resolved, while any path with leftover
conflict markers causes the command to refuse to stage any files.
Cannot be combined with `-u` or `-A`.

`--chmod=(+|-)x`::
Override the executable bit of the added files. The executable
bit is only changed in the index, the files on disk are left
Expand Down
14 changes: 8 additions & 6 deletions Documentation/git-http-fetch.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ commit-id::
line (which is not expected in
this case), 'git http-fetch' fetches the packfile directly at the given
URL and uses index-pack to generate corresponding .idx and .keep files.
The hash is used to determine the name of the temporary file and is
arbitrary. The output of index-pack is printed to stdout. Requires
--index-pack-args.
The hash is used to determine the name of the temporary file. It need
not be the pack hash, but it must uniquely identify the pack contents
for resumption. The output of index-pack is printed to stdout. Requires
one or more --index-pack-arg options.

--index-pack-args=<args>::
For internal use only. The command to run on the contents of the
downloaded pack. Arguments are URL-encoded separated by spaces.
--index-pack-arg=<arg>::
For internal use only. The first instance specifies the command run on
the contents of the downloaded pack. Subsequent instances specify its
arguments.

--recover::
Verify that everything reachable from target is fetched. Used after
Expand Down
92 changes: 86 additions & 6 deletions builtin/add.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "strvec.h"
#include "submodule.h"
#include "add-interactive.h"
#include "merge-ll.h"

static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
Expand All @@ -35,6 +36,7 @@ static int patch_interactive, add_interactive, edit_interactive;
static struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT;
static int take_worktree_changes;
static int add_renormalize;
static int add_resolved;
static int pathspec_file_nul;
static int include_sparse;
static const char *pathspec_from_file;
Expand Down Expand Up @@ -265,6 +267,7 @@ static struct option builtin_add_options[] = {
OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
OPT_BOOL(0, "resolved", &add_resolved, N_("add conflict-resolved tracked files")),
OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
Expand Down Expand Up @@ -379,6 +382,76 @@ static int add_files(struct repository *repo, struct dir_struct *dir, int flags)
return exit_status;
}

static int failed_to_add(int flags, const char *path)
{
if (!(flags & ADD_CACHE_IGNORE_ERRORS))
die(_("updating file '%s' failed"), path);
return 1;
}

static int add_resolved_files(struct repository *repo,
const struct pathspec *pathspec,
int flags)
{
struct index_state *istate = repo->index;
struct string_list unmerged_paths = STRING_LIST_INIT_DUP;
struct string_list unresolved_paths = STRING_LIST_INIT_DUP;
int exit_status = 0;
size_t i;

for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
continue;
if (pathspec->nr && !ce_path_match(istate, ce, pathspec, NULL))
continue;
if (!unmerged_paths.nr ||
strcmp(unmerged_paths.items[unmerged_paths.nr - 1].string, ce->name))
string_list_append(&unmerged_paths, ce->name);
}

if (!unmerged_paths.nr) {
string_list_clear(&unmerged_paths, 0);
return 0;
}

for (i = 0; i < unmerged_paths.nr; i++) {
const char *path = unmerged_paths.items[i].string;
struct stat st;

if (!lstat(path, &st) && S_ISREG(st.st_mode)) {
if (has_conflict_markers(istate, path))
string_list_append(&unresolved_paths, path);
}
}

if (unresolved_paths.nr) {
struct strbuf sb = STRBUF_INIT;
for (i = 0; i < unresolved_paths.nr; i++)
strbuf_addf(&sb, "\t%s\n", unresolved_paths.items[i].string);
die(_("the following paths still have conflict markers:\n%s"), sb.buf);
}

for (i = 0; i < unmerged_paths.nr; i++) {
const char *path = unmerged_paths.items[i].string;
struct stat st;

if (lstat(path, &st)) {
if (errno != ENOENT)
die_errno(_("cannot lstat: '%s'"), path);
if (remove_file_from_index_with_flags(istate, path, flags))
exit_status = failed_to_add(flags, path);
} else {
if (add_file_to_index(istate, path, flags))
exit_status = failed_to_add(flags, path);
}
}

string_list_clear(&unmerged_paths, 0);
string_list_clear(&unresolved_paths, 0);
return exit_status;
}

int cmd_add(int argc,
const char **argv,
const char *prefix,
Expand Down Expand Up @@ -438,8 +511,9 @@ int cmd_add(int argc,
else if (take_worktree_changes && ADDREMOVE_DEFAULT)
addremove = 0; /* "-u" was given but not "-A" */

if (addremove && take_worktree_changes)
die(_("options '%s' and '%s' cannot be used together"), "-A", "-u");
die_for_incompatible_opt3(take_worktree_changes, "-u/--update",
0 < addremove_explicit, "-A/--all",
add_resolved, "--resolved");

if (!show_only && ignore_missing)
die(_("the option '%s' requires '%s'"), "--ignore-missing", "--dry-run");
Expand All @@ -448,8 +522,11 @@ int cmd_add(int argc,
chmod_arg[1] != 'x' || chmod_arg[2]))
die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);

add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
add_new_files = !take_worktree_changes && !refresh_only &&
!add_renormalize && !add_resolved;
require_pathspec = !(take_worktree_changes ||
(0 < addremove_explicit) ||
add_resolved);

repo_hold_locked_index(repo, &lock_file, LOCK_DIE_ON_ERROR);

Expand Down Expand Up @@ -481,7 +558,8 @@ int cmd_add(int argc,
return 0;
}

if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
if (!take_worktree_changes && !add_resolved &&
addremove_explicit < 0 && pathspec.nr)
/* Turn "git add pathspec..." to "git add -A pathspec..." */
addremove = 1;

Expand Down Expand Up @@ -584,7 +662,9 @@ int cmd_add(int argc,
odb_transaction_begin_or_die(repo->objects, &transaction, 0);

ps_matched = xcalloc(pathspec.nr, 1);
if (add_renormalize)
if (add_resolved)
exit_status |= add_resolved_files(repo, &pathspec, flags);
else if (add_renormalize)
exit_status |= renormalize_tracked_files(repo, &pathspec, flags);
else
exit_status |= add_files_to_cache(repo, prefix,
Expand Down
25 changes: 1 addition & 24 deletions diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -3519,29 +3519,6 @@ struct checkdiff_t {
int last_line_kind;
};

static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
{
char firstchar;
int cnt;

if (len < marker_size + 1)
return 0;
firstchar = line[0];
switch (firstchar) {
case '=': case '>': case '<': case '|':
break;
default:
return 0;
}
for (cnt = 1; cnt < marker_size; cnt++)
if (line[cnt] != firstchar)
return 0;
/* line[1] through line[marker_size-1] are same as firstchar */
if (len < marker_size + 1 || !isspace(line[marker_size]))
return 0;
return 1;
}

static void checkdiff_consume_hunk(void *priv,
long ob UNUSED, long on UNUSED,
long nb, long nn UNUSED,
Expand Down Expand Up @@ -3571,7 +3548,7 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
if (line[0] == '+') {
unsigned bad;
data->lineno++;
if (is_conflict_marker(line + 1, marker_size, len - 1)) {
if (is_conflict_marker_line(line + 1, len - 1, marker_size)) {
data->status |= 1;
fprintf(data->o->file,
"%s%s:%d: leftover conflict marker\n",
Expand Down
33 changes: 18 additions & 15 deletions fetch-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -1854,9 +1854,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
}

for (i = 0; i < packfile_uris.nr; i++) {
bool created_keep;
int j;
struct child_process cmd = CHILD_PROCESS_INIT;
char packname[GIT_MAX_HEXSZ + 1];
char packhash[GIT_MAX_HEXSZ + 1];
const char *uri = packfile_uris.items[i].string +
the_hash_algo->hexsz + 1;

Expand All @@ -1874,16 +1875,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
if (start_command(&cmd))
die("fetch-pack: unable to spawn http-fetch");

if (read_in_full(cmd.out, packname, 5) < 0 ||
memcmp(packname, "keep\t", 5))
die("fetch-pack: expected keep then TAB at start of http-fetch output");
if (read_in_full(cmd.out, packhash, 5) != 5 ||
(memcmp(packhash, "keep\t", 5) &&
memcmp(packhash, "pack\t", 5)))
die("fetch-pack: expected pack or keep then TAB at start of http-fetch output");
created_keep = !memcmp(packhash, "keep\t", 5);

if (read_in_full(cmd.out, packname,
the_hash_algo->hexsz + 1) < 0 ||
packname[the_hash_algo->hexsz] != '\n')
die("fetch-pack: expected hash then LF at end of http-fetch output");

packname[the_hash_algo->hexsz] = '\0';
if (read_in_full(cmd.out, packhash,
the_hash_algo->hexsz + 1) != the_hash_algo->hexsz + 1 ||
packhash[the_hash_algo->hexsz] != '\n')
die("fetch-pack: expected hash then LF in http-fetch output");
packhash[the_hash_algo->hexsz] = '\0';

parse_gitmodules_oids(cmd.out, &fsck_options.gitmodules_found);

Expand All @@ -1892,16 +1894,17 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
if (finish_command(&cmd))
die("fetch-pack: unable to finish http-fetch");

if (memcmp(packfile_uris.items[i].string, packname,
if (memcmp(packfile_uris.items[i].string, packhash,
the_hash_algo->hexsz))
die("fetch-pack: pack downloaded from %s does not match expected hash %.*s",
uri, (int) the_hash_algo->hexsz,
packfile_uris.items[i].string);

string_list_append_nodup(pack_lockfiles,
xstrfmt("%s/pack/pack-%s.keep",
repo_get_object_directory(the_repository),
packname));
if (created_keep)
string_list_append_nodup(pack_lockfiles,
xstrfmt("%s/pack/pack-%s.keep",
repo_get_object_directory(the_repository),
packhash));
}
string_list_clear(&packfile_uris, 0);
strvec_clear(&index_pack_args);
Expand Down
7 changes: 4 additions & 3 deletions http-fetch.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ static void fetch_single_packfile(struct object_id *packfile_hash,

if (start_active_slot(preq->slot)) {
run_active_slot(preq->slot);
if (results.curl_result != CURLE_OK) {
if (results.curl_result != CURLE_OK &&
results.http_code != 416) {
struct url_info url;
char *nurl = url_normalize(preq->url, &url);
if (!nurl || !git_env_bool("GIT_TRACE_REDACT", 1)) {
Expand Down Expand Up @@ -155,7 +156,7 @@ int cmd_main(int argc, const char **argv)

if (packfile) {
if (!index_pack_args.nr)
die(_("the option '%s' requires '%s'"), "--packfile", "--index-pack-args");
die(_("the option '%s' requires '%s'"), "--packfile", "--index-pack-arg");

fetch_single_packfile(&packfile_hash, argv[arg],
index_pack_args.v);
Expand All @@ -164,7 +165,7 @@ int cmd_main(int argc, const char **argv)
}

if (index_pack_args.nr)
die(_("the option '%s' requires '%s'"), "--index-pack-args", "--packfile");
die(_("the option '%s' requires '%s'"), "--index-pack-arg", "--packfile");

if (commits_on_stdin) {
commits = walker_targets_stdin(&commit_id, &write_ref);
Expand Down
3 changes: 2 additions & 1 deletion http-push.c
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,8 @@ static void finish_request(struct transfer_request *request)

} else if (request->state == RUN_FETCH_PACKED) {
int fail = 1;
if (request->curl_result != CURLE_OK) {
if (request->curl_result != CURLE_OK &&
request->http_code != 416) {
fprintf(stderr, "Unable to get pack file %s\n%s",
request->url, curl_errorstr);
} else {
Expand Down
3 changes: 2 additions & 1 deletion http-walker.c
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo,

if (start_active_slot(preq->slot)) {
run_active_slot(preq->slot);
if (results.curl_result != CURLE_OK) {
if (results.curl_result != CURLE_OK &&
results.http_code != 416) {
error("Unable to get pack file %s\n%s", preq->url,
curl_errorstr);
goto abort;
Expand Down
Loading